首頁>技術>

資料科學三分天下,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迴圈,並使的程式碼更簡潔—:

它將沿重複索引的陣列求和。在此特定示例中,這兩種情況都可以滿足要求,但是在更復雜的情況下,一旦您瞭解其背後的邏輯,它們的工作速度可能會更快,並且通常更容易讀寫。

14
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • ​VIKOR簡介及例題分析