數據科學三分天下,Python佔其一。Python數據科學 NumPy是基礎,不管pandas還是tensorflow, NumPy都是基礎庫,學習NumPy基礎類型和操作必不可少。本文我們就介紹NumPy基礎,並以圖形方式展現,以方便初學者理解。
概述
NumPy中最基本數據類型是數組,所有數據組織都是n維數組形式組織的。其中一維和二維數組是基礎,其他多維數組的操作和其類似。
NumPy數組形式上和Python列表相似。兩者都可以用作數據容器,可以快速訪問和設置項目,但是數據插入和移動比較慢。
NumPy數組支持對其進行簡單算術運算:
NumPy數組還具有:
比較緊湊,尤其是在一個以上的維度上;
可以向量化操作時比列表快;
將元素追加到末尾比列表慢(O(1)和O(N));
通常是同質的:只能快速處理一種類型的元素;
注意:O(N)表示完成操作所需的時間與數組的大小成正比,以及O(1),表示消耗時間固定和數組的大小無關。
向量——一維數組
向量初始化:創建NumPy數組的一種方法通過Python列錶轉換。數組類型會從列表元素類型中自動推導:
確保輸入為同類型列表,否則其類型dtype='object',不僅轉換耗時,而且只保留NumPy中包含的語法糖。
NumPy數組無法像Python列表那樣增長:在數組末尾沒有保留空間以方便快速追加。
一種常見的做法要麼長出一個Python列表,並將其轉換為NumPy的數組時,它已準備就緒或預分配必要的空間,可以用np.zeros和np.empty。
通常可以使用zeros_like()函數創建一個空數組,以大小和元素類型匹配現有數組:
創建以常量值填充的數組的函數都有一個對應的的_like函數:
在NumPy中,對有序數組有兩個初始化函數:argnge()和lispace()
如果需要類似的浮點數組,例如[0., 1., 2.],可以修改arange輸出的類型:arange(3).astype(float)。arange函數對類型敏感:如果將int作為參數輸入,它將生成int,並且如果輸入float(例如arange(3.)),則將生成float。
但是arange在處理浮點數方面並不是特別擅長:
0.1對於我們來說,這看起來像是一個有限的十進位數,但對計算機而言卻不是:用二進位表示,它是一個無窮小數,必須四捨五入到精度需求的位數。所以給arange賦值為小數通常會有問題:會拋出一個錯誤。可以使間隔的非整數步數,但這會降低可讀性和可維護性。
而linspace不受捨入錯誤的影響,它始終生成要求的元素數量。不過,需要注意的的是它計算的是點,而不是間隔,因此最後一個參數始終,通常認為是加一。因此結果11,而不是上面示例中的10。
在數值計算中,通常需要生成隨機數組:
向量索引
一旦將數據保存在了數組中,就可以使用NumPy數組索引對其進行操作。
除花式索引外,以上介紹的所有索引方法實際上都是所謂的"視圖":如果索引的值做操作發生更改,則它們不會影響原始數組中存儲的數據。
所有這些方法,包括花式索引,都是可變的:如上所述,它們允許通過分配修改原始數組的內容。該功能通過切片來改變數組複製行為:
從NumPy數組中獲取數據的另一種超級有用的方法是布爾索引,它允許使用各種邏輯運算符:
any和all行為都和python中的一樣,但不支持短路。
Python中的"三元"比較,比如3<=a<=5不支持。
如上所述,布爾索引也是可寫的。它有兩個常見的用例,它們是專用功能:過度重載的功能np.where和np.clip。
向量運算
算術是NumPy性能最耀眼的地方之一。向量運算符已經用c++重構,可使我們避免慢的Python循環。NumPy允許像普通數字一樣操作整個數組:
和Python中一樣,a//b表示div b(除法的商),x ** n表示x
將加法或減法將int提升為浮點數的方式相同,將標量提升(也稱為broadcast)至數組:
大多數數學函數都有NumPy對應項,可以處理矢量:
標量類型支持特殊的運算符:
三角函數:
數組可以進行四捨五入:
名稱np.around只是np.round為了避免round和Python函數幹擾,而引入的別名from numpy import *(對比常見的import numpy as np)。也可以使用a.round()。
NumPy還可以執行以下基本統計計算:
這些函數中的每一個都有支持nan的變體:例如nansum,nanmax等
排序功能比Python對應功能具有更少的功能:
向量搜索
與Python列表相反,NumPy數組沒有index方法。
查找元素的一種方法是np.where(a==x)[0][0],它既不優雅也不高效,因為即使要查找的項在開頭,從一開始就需要遍歷數組的所有元素。
更快的方式做到這一點是通過Numba加速:
next((i[0] for i, v in np.ndenumerate(a) if v==x), -1)
但是,一旦對數組進行排序,情況就會變得更好:複雜度為O(log N)的情況下確實非常快,但是首先需要O(N log N)的時間。
v = np.searchsorted(a, x); return v if a[v]==x else -1
實際上,通過在C中實現搜索來加速搜索不是問題。問題是浮點數比較。
浮點數比較
函數np.allclose(a, b)比較具有給定精確度範圍內的浮點數組
np.allclose假設所有的比較數字是1。例如一個典型的規模,如果在納秒範圍的操作,你需要設置默認atol為1E9:
np.allclose(1e-9, 2e-9, atol=1e-17) == False。
math.isclose使得要比較沒有關於數字的假設,而是基於用戶給出一個合理的abs_tol值,而不是(以默認np.allclose atol的1E-8是為1的典型比例的數字不夠好值)
math.isclose(0.1+0.2–0.3, abs_tol=1e-8)==True。
除此之外np.allclose,絕對和相對公差公式中還存在一些小問題,例如,a,b allclose(a, b) != allclose(b, a)。
矩陣——二維數組
matrixNumPy中曾經有一個專用的類,但現在已棄用,因此我將交替使用矩陣和二維數組一詞。
矩陣初始化語法和向量相似:
這裡需要雙括號,因為第二個位置參數是為(可選)dtype(也接受整數)保留的。
隨機矩陣的生成也類似於矢量的生成:
二維索引語法比嵌套列表更方便:
"視圖"符號表示切片數組時實際上並未進行任何複製。修改數組後,更改也將只反映在切片中。
軸參數
在許多操作中(例如sum),需要告訴NumPy是否要跨行或跨列進行操作。為了擁有適用於任意數量維的通用符號,NumPy引入了軸的概念:axis事實上,參數的值是所討論索引的數量:第一個索引是axis=0,第二個索引是axis=1,依此類推。因此在二維數組中axis=0是按列的,axis=1意味著按行。
矩陣算術
除了普通的運算符(如+,-,*,/,//和**)以元素方式工作外,還有一個@運算符可計算矩陣乘積:
作為在第一部分中已經看到的從標量廣播的概括,NumPy允許向量和矩陣之間,甚至兩個向量之間的混合運算:
請注意,在最後一個示例中,這是一個對稱的逐元素乘法。要使用非對稱線性代數矩陣乘法來計算外部乘積,應反轉操作數的順序:
行向量和列向量
從上面的示例可以看出,在二維上下文中,行向量和列向量被不同地對待。這與通常的NumPy做法相反,後者在任何可能的情況下都具有一種類型的一維數組(例如二維數組的a[:,j],第j列a是一維數組)。默認情況下,一維數組在二維操作中被視為行向量,因此,將矩陣乘以行向量時,可以使用形狀(n,)或(1,n)-結果將相同。如果需要列向量,則有幾種方法可以從一維數組中對其進行操作,但令人驚訝的transpose是,它們不是其中一種:
能夠從一維數組中生成二維列向量的兩個操作是使用reshape和index:
這裡的-1參數指示reshape一個方向的密度大。
因此,NumPy中總共有三種類型的向量:一維數組,二維行向量和二維列向量。這是兩者之間顯式轉換的示意圖:
根據廣播規則,一維數組被隱式解釋為二維行向量,因此通常不必在這兩個數組之間進行轉換。
矩陣操作
連接數組有兩個主要功能:
這兩種方法僅適用於僅堆疊矩陣或僅堆疊矢量,但在將一維數組和矩陣進行混合堆疊時,僅vstack按預期工作:hstack報帶下不匹配錯誤,因為如上所述,一維數組被解釋為行向量,而不是列向量。解決方法是將其轉換為行向量,或者使用column_stack自動執行此功能的專用功能:
堆疊的逆向是split:
可以通過兩種方式完成矩陣複製:tile類似於複製粘貼;repeat類似分頁列印的行為:
特定的列和行可以delete像這樣:
逆運算為insert:
append和hstack類似無法自動轉置一維數組,因此再次需要對向量進行整形或添加大小,或者column_stack需要使用向量代替:
如果需要做的是向數組的邊界添加常量值,則pad函數就足夠了:
網狀網格
廣播規則使使用網格網格的工作更加簡單。假設需要以下矩陣(但尺寸很大):
上面兩種方法明顯都很慢,因為都要使用Python循環。在MATLAB中處理這類問題的方法是創建一個meshgrid:
meshgrid函數接受任意一組索引,mgrid僅是切片,indices並且只能生成完整的索引範圍。fromfunction如上所述,僅使用I和J參數一次調用提供的函數。
在NumPy中實際上還有一種更好的方法。無需在整個I和J矩陣上花費內存。僅存儲形狀正確的矢量就足夠了,廣播規則將處理其餘的內容:
沒有indexing='ij'參數的情況下,meshgrid將更改參數的順序:J, I= np.meshgrid(j, i)—這是一種" xy"模式,用於可視化3D圖。
除了在二維或三維網格上初始化函數外,網格還可以用於索引數組:
也適用於稀疏網格
矩陣統計
和sum一樣,所有其他的統計功能接受參數(,,,),並進行對應計算:
二維及更高版本中的argmin和argmax函數討厭返回平坦索引(最小和最大值的第一個實例)。要將其轉換為兩個坐標,需要一個函數:
量詞all和any也支持axis參數:
矩陣排序
儘管axis參數對上面列出的函數很有用,但對二維排序卻沒有幫助:
這並不是通常希望通過對矩陣或電子表格進行排序得到的結果:axis絕不是key參數的替代。但是幸運的是,NumPy具有幾個幫助程序功能,這些功能允許按列或按需要按幾列進行排序:
按第一列對數組排序:a[a[:,0].argsort()]
argsort排序後,此處返回原始數組的索引數組。
這個技巧可以重複,但是必須小心,以免下一類混淆前一類的結果:
a = a[a[:,2].argsort()]
a = a[a[:,1].argsort(kind='stable')]
a = a[a[:,0].argsort(kind='stable')]
有一個輔助函數lexsort,該函數按上述方式對所有可用列進行排序,但始終按行執行,並且要排序的行的順序顛倒了(即,從下到上),因此它的用法有點做作,例如
-a[np.lexsort(np.flipud(a[2,5].T))]排序由5列2列的第一和然後(其中在第2列中的值是相等的)
-a[np.lexsort(np.flipud(a.T))]種由所有列在左到右的順序。
此處flipud沿上下方向翻轉矩陣(準確地說,是在與axis=0相同的方向上,a[::-1,...]其中三個點表示"所有其他維度"",因此翻轉一維數組flipud並不是突然的fliplr)。
還有一個order參數sort,但是如果從普通(非結構化)數組開始,則既不快速也不容易使用。
因為這個特殊的操作方式更具可讀性和它可能是一個更好的選擇,pandas是用這種方式,不易出錯有:通過第二列和第五列排序: pd.DataFrame(a).sort_values(by=[2,5]).to_numpy()
按照從左到右的按照各列排序:
pd.DataFrame(a).sort_values().to_numpy()
三維及以上
通過重塑一維向量或轉換嵌套的Python列表來創建三維數組時,索引的含義為(z,y,x)。第一個索引是平面的編號,然後坐標在該平面上移動:
該索引順序很方便,例如,用於保留一堆灰度圖像:這a[i]是引用第i個圖像的快捷方式。
但是此索引順序不是通用的。在處理RGB圖像時,通常使用(y,x,z)順序:第一個是兩個像素坐標,最後一個是顏色坐標:
這樣,可以方便地引用特定像素:a[i,j]給出像素的RGB元組(i,j)。
因此,創建特定幾何形狀的實際命令取決於正在處理的域的約定:
NumPy函數類似於hstack,vstack或,但是使用硬編碼的索引順序是(y,x,z),RGB圖像順序:
堆疊RGB圖像(此處僅兩種顏色)
如果數據的布局不同,則使用concatenate命令堆疊圖像,並在axis參數中提供顯式索引號會更方便:
堆疊通用3D陣列
如果不方便考慮軸數,可以將數組轉換為硬編碼為hstack和co的形式:
這種轉換是便宜的:沒有實際的複製發生。它只是動態混合索引的順序。
混合索引順序的另一個操作是數組轉置。檢查它可能會讓三維陣列更加熟悉。根據決定的軸順序,轉置數組所有平面的實際命令將有所不同:對於通用數組,它交換索引1和2,對於RGB圖像,它交換0和1:
有趣的是,(和唯一的操作模式)默認axes參數顛倒了索引順序,這與上述兩個索引順序約定都不相符。transposea.T
einsum函數,可以在處理多維數組時為節省很多Python循環,並使的代碼更簡潔—:
它將沿重複索引的數組求和。在此特定示例中,這兩種情況都可以滿足要求,但是在更複雜的情況下,一旦您了解其背後的邏輯,它們的工作速度可能會更快,並且通常更容易讀寫。
總結
本文我們介紹了NumPy的基本類型和對應的操作,每一步都用圖形化方式進行展示,希望能對大家學習有益。