【翻譯】《利用Python進行數據分析·第2版》第4章(中)NumPy基礎:數組和矢量計算

作者:SeanCheney Python愛好者社區專欄作者

簡書專欄:jianshu.com/u/130f76596

前文傳送門:

【翻譯】《利用Python進行數據分析·第2版》第1章 準備工作

【翻譯】《利用Python進行數據分析·第2版》第2章(上)Python語法基礎,IPython和Jupyter

【翻譯】《利用Python進行數據分析·第2版》第2章(中)Python語法基礎,IPython和Jupyter

【翻譯】《利用Python進行數據分析·第2版》第2章(下)Python語法基礎,IPython和Jupyter

【翻譯】《利用Python進行數據分析·第2版》第3章(上)Python的數據結構、函數和文件

【翻譯】《利用Python進行數據分析·第2版》第3章(中)Python的數據結構、函數和文件

【翻譯】《利用Python進行數據分析·第2版》第3章(下)Python的數據結構、函數和文件

【翻譯】《利用Python進行數據分析·第2版》第4章(上)NumPy基礎:數組和矢量計算

切片索引

ndarray的切片語法跟Python列表這樣的一維對象差不多:

In [88]: arr

Out[88]: array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])

In [89]: arr[1:6]

Out[89]: array([ 1, 2, 3, 4, 64])

對於之前的二維數組arr2d,其切片方式稍顯不同:

In [90]: arr2d

Out[90]:

array([[1, 2, 3],

[4, 5, 6],

[7, 8, 9]])

In [91]: arr2d[:2]

Out[91]:

array([[1, 2, 3],

[4, 5, 6]])

可以看出,它是沿著第0軸(即第一個軸)切片的。也就是說,切片是沿著一個軸向選取元素的。表達式arr2d[:2]可以被認為是「選取arr2d的前兩行」。

你可以一次傳入多個切片,就像傳入多個索引那樣:

In [92]: arr2d[:2, 1:]

Out[92]:

array([[2, 3],

[5, 6]])

像這樣進行切片時,只能得到相同維數的數組視圖。通過將整數索引和切片混合,可以得到低維度的切片。

例如,我可以選取第二行的前兩列:

In [93]: arr2d[1, :2]

Out[93]: array([4, 5])

相似的,還可以選擇第三列的前兩行:

In [94]: arr2d[:2, 2]

Out[94]: array([3, 6])

圖4-2對此進行了說明。注意,「只有冒號」表示選取整個軸,因此你可以像下面這樣只對高維軸進行切片:

In [95]: arr2d[:, :1]

Out[95]:

array([[1],

[4],

[7]])

圖4-2 二維數組切片

自然,對切片表達式的賦值操作也會被擴散到整個選區:

In [96]: arr2d[:2, 1:] = 0

In [97]: arr2d

Out[97]:

array([[1, 0, 0],

[4, 0, 0],

[7, 8, 9]])

布爾型索引

來看這樣一個例子,假設我們有一個用於存儲數據的數組以及一個存儲姓名的數組(含有重複項)。在這裡,我將使用numpy.random中的randn函數生成一些正態分布的隨機數據:

In [98]: names = np.array([Bob, Joe, Will, Bob, Will, Joe, Joe])

In [99]: data = np.random.randn(7, 4)

In [100]: names

Out[100]:

array([Bob, Joe, Will, Bob, Will, Joe, Joe],

dtype=<U4)

In [101]: data

Out[101]:

array([[ 0.0929, 0.2817, 0.769 , 1.2464],

[ 1.0072, -1.2962, 0.275 , 0.2289],

[ 1.3529, 0.8864, -2.0016, -0.3718],

[ 1.669 , -0.4386, -0.5397, 0.477 ],

[ 3.2489, -1.0212, -0.5771, 0.1241],

[ 0.3026, 0.5238, 0.0009, 1.3438],

[-0.7135, -0.8312, -2.3702, -1.8608]])

假設每個名字都對應data數組中的一行,而我們想要選出對應於名字"Bob"的所有行。跟算術運算一樣,數組的比較運算(如==)也是矢量化的。因此,對names和字元串"Bob"的比較運算將會產生一個布爾型數組:

In [102]: names == Bob

Out[102]: array([ True, False, False, True, False, False, False], dtype=bool)

這個布爾型數組可用於數組索引:

In [103]: data[names == Bob]

Out[103]:

array([[ 0.0929, 0.2817, 0.769 , 1.2464],

[ 1.669 , -0.4386, -0.5397, 0.477 ]])

布爾型數組的長度必須跟被索引的軸長度一致。此外,還可以將布爾型數組跟切片、整數(或整數序列,稍後將對此進行詳細講解)混合使用:

In [103]: data[names == Bob]

Out[103]:

array([[ 0.0929, 0.2817, 0.769 , 1.2464],

[ 1.669 , -0.4386, -0.5397, 0.477 ]])

注意:如果布爾型數組的長度不對,布爾型選擇就會出錯,因此一定要小心。

下面的例子,我選取了names == Bob的行,並索引了列:

In [104]: data[names == Bob, 2:]

Out[104]:

array([[ 0.769 , 1.2464],

[-0.5397, 0.477 ]])

In [105]: data[names == Bob, 3]

Out[105]: array([ 1.2464, 0.477 ])

要選擇除"Bob"以外的其他值,既可以使用不等於符號(!=),也可以通過~對條件進行否定:

In [106]: names != Bob

Out[106]: array([False, True, True, False, True, True, True], dtype=bool)

In [107]: data[~(names == Bob)]

Out[107]:

array([[ 1.0072, -1.2962, 0.275 , 0.2289],

[ 1.3529, 0.8864, -2.0016, -0.3718],

[ 3.2489, -1.0212, -0.5771, 0.1241],

[ 0.3026, 0.5238, 0.0009, 1.3438],

[-0.7135, -0.8312, -2.3702, -1.8608]])

~操作符用來反轉條件很好用:

In [108]: cond = names == Bob

In [109]: data[~cond]

Out[109]:

array([[ 1.0072, -1.2962, 0.275 , 0.2289],

[ 1.3529, 0.8864, -2.0016, -0.3718],

[ 3.2489, -1.0212, -0.5771, 0.1241],

[ 0.3026, 0.5238, 0.0009, 1.3438],

[-0.7135, -0.8312, -2.3702, -1.8608]])

選取這三個名字中的兩個需要組合應用多個布爾條件,使用&(和)、|(或)之類的布爾算術運算符即可:

In [110]: mask = (names == Bob) | (names == Will)

In [111]: mask

Out[111]: array([ True, False, True, True, True, False, False], dtype=bool)

In [112]: data[mask]

Out[112]:

array([[ 0.0929, 0.2817, 0.769 , 1.2464],

[ 1.3529, 0.8864, -2.0016, -0.3718],

[ 1.669 , -0.4386, -0.5397, 0.477 ],

[ 3.2489, -1.0212, -0.5771, 0.1241]])

通過布爾型索引選取數組中的數據,將總是創建數據的副本,即使返回一模一樣的數組也是如此。

注意:Python關鍵字and和or在布爾型數組中無效。要是用&與|。

通過布爾型數組設置值是一種經常用到的手段。為了將data中的所有負值都設置為0,我們只需:

In [113]: data[data < 0] = 0

In [114]: data

Out[114]:

array([[ 0.0929, 0.2817, 0.769 , 1.2464],

[ 1.0072, 0. , 0.275 , 0.2289],

[ 1.3529, 0.8864, 0. , 0. ],

[ 1.669 , 0. , 0. , 0.477 ],

[ 3.2489, 0. , 0. , 0.1241],

[ 0.3026, 0.5238, 0.0009, 1.3438],

[ 0. , 0. , 0. , 0. ]])

通過一維布爾數組設置整行或列的值也很簡單:

In [115]: data[names != Joe] = 7

In [116]: data

Out[116]:

array([[ 7. , 7. , 7. , 7. ],

[ 1.0072, 0. , 0.275 , 0.2289],

[ 7. , 7. , 7. , 7. ],

[ 7. , 7. , 7. , 7. ],

[ 7. , 7. , 7. , 7. ],

[ 0.3026, 0.5238, 0.0009, 1.3438],

[ 0. , 0. , 0. , 0. ]])

後面會看到,這類二維數據的操作也可以用pandas方便的來做。

花式索引

花式索引(Fancy indexing)是一個NumPy術語,它指的是利用整數數組進行索引。假設我們有一個8×4數組:

In [117]: arr = np.empty((8, 4))

In [118]: for i in range(8):

.....: arr[i] = i

In [119]: arr

Out[119]:

array([[ 0., 0., 0., 0.],

[ 1., 1., 1., 1.],

[ 2., 2., 2., 2.],

[ 3., 3., 3., 3.],

[ 4., 4., 4., 4.],

[ 5., 5., 5., 5.],

[ 6., 6., 6., 6.],

[ 7., 7., 7., 7.]])

為了以特定順序選取行子集,只需傳入一個用於指定順序的整數列表或ndarray即可:

In [120]: arr[[4, 3, 0, 6]]

Out[120]:

array([[ 4., 4., 4., 4.],

[ 3., 3., 3., 3.],

[ 0., 0., 0., 0.],

[ 6., 6., 6., 6.]])

這段代碼確實達到我們的要求了!使用負數索引將會從末尾開始選取行:

In [121]: arr[[-3, -5, -7]]

Out[121]:

array([[ 5., 5., 5., 5.],

[ 3., 3., 3., 3.],

[ 1., 1., 1., 1.]])

一次傳入多個索引數組會有一點特別。它返回的是一個一維數組,其中的元素對應各個索引元組:

In [122]: arr = np.arange(32).reshape((8, 4))

In [123]: arr

Out[123]:

array([[ 0, 1, 2, 3],

[ 4, 5, 6, 7],

[ 8, 9, 10, 11],

[12, 13, 14, 15],

[16, 17, 18, 19],

[20, 21, 22, 23],

[24, 25, 26, 27],

[28, 29, 30, 31]])

In [124]: arr[[1, 5, 7, 2], [0, 3, 1, 2]]

Out[124]: array([ 4, 23, 29, 10])

附錄A中會詳細介紹reshape方法。

最終選出的是元素(1,0)、(5,3)、(7,1)和(2,2)。無論數組是多少維的,花式索引總是一維的。

這個花式索引的行為可能會跟某些用戶的預期不一樣(包括我在內),選取矩陣的行列子集應該是矩形區域的形式才對。下面是得到該結果的一個辦法:

In [125]: arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]]

Out[125]:

array([[ 4, 7, 5, 6],

[20, 23, 21, 22],

[28, 31, 29, 30],

[ 8, 11, 9, 10]])

記住,花式索引跟切片不一樣,它總是將數據複製到新數組中。

數組轉置和軸對換

轉置是重塑的一種特殊形式,它返回的是源數據的視圖(不會進行任何複製操作)。數組不僅有transpose方法,還有一個特殊的T屬性:

In [126]: arr = np.arange(15).reshape((3, 5))

In [127]: arr

Out[127]:

array([[ 0, 1, 2, 3, 4],

[ 5, 6, 7, 8, 9],

[10, 11, 12, 13, 14]])

In [128]: arr.T

Out[128]:

array([[ 0, 5, 10],

[ 1, 6, 11],

[ 2, 7, 12],

[ 3, 8, 13],

[ 4, 9, 14]])

在進行矩陣計算時,經常需要用到該操作,比如利用np.dot計算矩陣內積:

In [129]: arr = np.random.randn(6, 3)

In [130]: arr

Out[130]:

array([[-0.8608, 0.5601, -1.2659],

[ 0.1198, -1.0635, 0.3329],

[-2.3594, -0.1995, -1.542 ],

[-0.9707, -1.307 , 0.2863],

[ 0.378 , -0.7539, 0.3313],

[ 1.3497, 0.0699, 0.2467]])

In [131]: np.dot(arr.T, arr)

Out[131]:

array([[ 9.2291, 0.9394, 4.948 ],

[ 0.9394, 3.7662, -1.3622],

[ 4.948 , -1.3622, 4.3437]])

對於高維數組,transpose需要得到一個由軸編號組成的元組才能對這些軸進行轉置(比較費腦子):

In [132]: arr = np.arange(16).reshape((2, 2, 4))

In [133]: arr

Out[133]:

array([[[ 0, 1, 2, 3],

[ 4, 5, 6, 7]],

[[ 8, 9, 10, 11],

[12, 13, 14, 15]]])

In [134]: arr.transpose((1, 0, 2))

Out[134]:

array([[[ 0, 1, 2, 3],

[ 8, 9, 10, 11]],

[[ 4, 5, 6, 7],

[12, 13, 14, 15]]])

這裡,第一個軸被換成了第二個,第二個軸被換成了第一個,最後一個軸不變。

簡單的轉置可以使用.T,它其實就是進行軸對換而已。ndarray還有一個swapaxes方法,它需要接受一對軸編號:

In [135]: arr

Out[135]:

array([[[ 0, 1, 2, 3],

[ 4, 5, 6, 7]],

[[ 8, 9, 10, 11],

[12, 13, 14, 15]]])

In [136]: arr.swapaxes(1, 2)

Out[136]:

array([[[ 0, 4],

[ 1, 5],

[ 2, 6],

[ 3, 7]],

[[ 8, 12],

[ 9, 13],

[10, 14],

[11, 15]]])

swapaxes也是返回源數據的視圖(不會進行任何複製操作)。

4.2 通用函數:快速的元素級數組函數

通用函數(即ufunc)是一種對ndarray中的數據執行元素級運算的函數。你可以將其看做簡單函數(接受一個或多個標量值,併產生一個或多個標量值)的矢量化包裝器。

許多ufunc都是簡單的元素級變體,如sqrt和exp:

In [137]: arr = np.arange(10)

In [138]: arr

Out[138]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [139]: np.sqrt(arr)

Out[139]:

array([ 0. , 1. , 1.4142, 1.7321, 2. , 2.2361, 2.4495,

2.6458, 2.8284, 3. ])

In [140]: np.exp(arr)

Out[140]:

array([ 1. , 2.7183, 7.3891, 20.0855, 54.5982,

148.4132, 403.4288, 1096.6332, 2980.958 , 8103.0839])

這些都是一元(unary)ufunc。另外一些(如add或maximum)接受2個數組(因此也叫二元(binary)ufunc),並返回一個結果數組:

In [141]: x = np.random.randn(8)

In [142]: y = np.random.randn(8)

In [143]: x

Out[143]:

array([-0.0119, 1.0048, 1.3272, -0.9193, -1.5491, 0.0222, 0.7584,

-0.6605])

In [144]: y

Out[144]:

array([ 0.8626, -0.01 , 0.05 , 0.6702, 0.853 , -0.9559, -0.0235,

-2.3042])

In [145]: np.maximum(x, y)

Out[145]:

array([ 0.8626, 1.0048, 1.3272, 0.6702, 0.853 , 0.0222, 0.7584,

-0.6605])

這裡,numpy.maximum計算了x和y中元素級別最大的元素。

雖然並不常見,但有些ufunc的確可以返回多個數組。modf就是一個例子,它是Python內置函數divmod的矢量化版本,它會返回浮點數數組的小數和整數部分:

In [146]: arr = np.random.randn(7) * 5

In [147]: arr

Out[147]: array([-3.2623, -6.0915, -6.663 , 5.3731, 3.6182, 3.45 , 5.0077])

In [148]: remainder, whole_part = np.modf(arr)

In [149]: remainder

Out[149]: array([-0.2623, -0.0915, -0.663 , 0.3731,

0.6182, 0.45 , 0.0077])

In [150]: whole_part

Out[150]: array([-3., -6., -6., 5., 3., 3., 5.])

Ufuncs接受out選項參數,可以讓它們在數組的原地進行操作:

In [151]: arrOut[151]: array([-3.2623, -6.0915, -6.663 , 5.3731, 3.6182, 3.45 , 5.0077])In [152]: np.sqrt(arr)Out[152]: array([ nan, nan, nan, 2.318 , 1.9022, 1.8574, 2.2378])In [153]: np.sqrt(arr, arr)Out[153]: array([ nan, nan, nan, 2.318 , 1.9022, 1.8574, 2.2378])In [154]: arrOut[154]: array([ nan, nan, nan, 2.318 , 1.9022, 1.8574, 2.2378])

表4-3和表4-4分別列出了一些一元和二元ufunc。


推薦閱讀:

唯一化及集合邏輯
Python C Extesion (pyd)
ImagePy教程 —— 幾何變換
數據分析之numpy(二)

TAG:Python | 數據分析 | numpy |