什麼是 NumPy?#
NumPy 是 Python 中用於科學計算的基礎套件。它是一個 Python 函式庫,提供多維陣列物件、各種衍生物件(例如遮罩陣列和矩陣),以及用於對陣列進行快速運算的各種例程,包括數學、邏輯、形狀操作、排序、選擇、I/O、離散傅立葉變換、基本線性代數、基本統計運算、隨機模擬等等。
NumPy 套件的核心是 ndarray
物件。它封裝了同質資料類型的n維陣列,許多操作都在編譯後的程式碼中執行以提高效能。NumPy 陣列和標準 Python 序列之間存在幾個重要的差異
NumPy 陣列在建立時具有固定大小,這與 Python 列表(可以動態增長)不同。更改
ndarray
的大小將會建立一個新陣列並刪除原始陣列。NumPy 陣列中的元素都必須是相同的資料類型,因此在記憶體中將會是相同的大小。例外情況:可以擁有(Python,包括 NumPy)物件的陣列,從而允許不同大小元素的陣列。
NumPy 陣列有助於對大量資料進行進階數學和其他類型的運算。通常,與使用 Python 的內建序列相比,此類運算的執行效率更高,程式碼更少。
越來越多的科學和數學 Python 基礎套件正在使用 NumPy 陣列;儘管這些套件通常支援 Python 序列輸入,但它們會在處理之前將此類輸入轉換為 NumPy 陣列,並且它們通常輸出 NumPy 陣列。換句話說,為了有效率地使用當今許多(甚至大多數)科學/數學 Python 基礎軟體,僅僅知道如何使用 Python 的內建序列類型是不夠的 - 還需要知道如何使用 NumPy 陣列。
關於序列大小和速度的重點在科學計算中尤其重要。舉一個簡單的例子,考慮將 1 維序列中的每個元素與另一個相同長度序列中的對應元素相乘的情況。如果資料儲存在兩個 Python 列表中,a
和 b
,我們可以迭代每個元素
c = []
for i in range(len(a)):
c.append(a[i]*b[i])
這會產生正確的答案,但是如果 a
和 b
各自包含數百萬個數字,我們將為 Python 中迴圈的低效率付出代價。我們可以透過在 C 中編寫(為了清楚起見,我們忽略變數宣告和初始化、記憶體分配等)來更快地完成相同的任務
for (i = 0; i < rows; i++) {
c[i] = a[i]*b[i];
}
這節省了解釋 Python 程式碼和操作 Python 物件所涉及的所有開銷,但代價是犧牲了從 Python 編碼中獲得的好處。此外,所需的編碼工作量會隨著資料的維度而增加。以二維陣列為例,C 程式碼(如前所述已縮減)擴展為
for (i = 0; i < rows; i++) {
for (j = 0; j < columns; j++) {
c[i][j] = a[i][j]*b[i][j];
}
}
NumPy 給了我們兩全其美的方案:當涉及 ndarray
時,逐元素運算是「預設模式」,但逐元素運算由預先編譯的 C 程式碼快速執行。在 NumPy 中
c = a * b
以接近 C 語言的速度完成了先前的範例所做的事情,但具有我們期望從基於 Python 的事物獲得的程式碼簡潔性。實際上,NumPy 的慣用語甚至更簡單!最後一個範例說明了 NumPy 的兩個特性,這是其大部分威力的基礎:向量化和廣播。
為什麼 NumPy 這麼快?#
向量化描述了程式碼中沒有任何顯式的迴圈、索引等 - 當然,這些事情只是在優化、預先編譯的 C 程式碼中「幕後」進行。向量化程式碼有很多優點,其中包括
向量化程式碼更簡潔且更易於閱讀
更少的程式碼行數通常意味著更少的錯誤
程式碼更接近標準數學符號(通常使其更容易正確編碼數學結構)
向量化產生更「Pythonic」的程式碼。如果沒有向量化,我們的程式碼將會充斥著低效率且難以閱讀的
for
迴圈。
廣播是用於描述運算的隱式逐元素行為的術語;一般來說,在 NumPy 中,所有運算,不僅是算術運算,還有邏輯、位元運算、函數運算等,都以這種隱式逐元素的方式運作,也就是說,它們會廣播。此外,在上面的範例中,a
和 b
可以是相同形狀的多維陣列,或純量和陣列,甚至可以是形狀不同的兩個陣列,前提是較小的陣列可以「擴展」到較大陣列的形狀,以便產生的廣播是明確的。有關廣播的詳細「規則」,請參閱 廣播。
還有誰使用 NumPy?#
NumPy 完全支援物件導向的方法,再次從 ndarray
開始。例如,ndarray
是一個類別,具有眾多方法和屬性。它的許多方法都與最外層 NumPy 命名空間中的函數相互呼應,讓程式設計師可以使用他們喜歡的任何範例進行編碼。這種靈活性使得 NumPy 陣列方言和 NumPy ndarray
類別成為 Python 中使用的多維資料交換的事實語言。