【翻譯】《利用Python進行數據分析·第2版》第4章(中)NumPy基礎:數組和矢量計算
作者:SeanCheney Python愛好者社區專欄作者
簡書專欄:https://www.jianshu.com/u/130f76596b02
前文傳送門:
【翻譯】《利用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]: arr2dOut[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]: namesOut[100]: array([Bob, Joe, Will, Bob, Will, Joe, Joe], dtype=<U4)In [101]: dataOut[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]: maskOut[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]: dataOut[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]: dataOut[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] = iIn [119]: arrOut[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]: arrOut[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]: arrOut[127]: array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14]])In [128]: arr.TOut[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]: arrOut[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]: arrOut[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]: arrOut[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]: xOut[143]: array([-0.0119, 1.0048, 1.3272, -0.9193, -1.5491, 0.0222, 0.7584, -0.6605])In [144]: yOut[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]: arrOut[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]: remainderOut[149]: array([-0.2623, -0.0915, -0.663 , 0.3731,0.6182, 0.45 , 0.0077])In [150]: whole_partOut[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(二)