Numpy的ndarry:一種多維數組對象
Numpy最重要的一個特點就是其N維數組對象(即ndarry),該對象是一個快速而靈活的大數據集容器。你可以利用這種數組對整塊數據執行一些數學運算,其語法跟標量元素之間的運算一樣:
In [52]: data=np.array([[1,2,3],[4,5,6]])
In [53]: data
Out[53]:
array([[1, 2, 3],
[4, 5, 6]])
In [54]: data*10
Out[54]:
array([[10, 20, 30],
[40, 50, 60]])
In [55]: data+data
Out[55]:
array([[ 2, 4, 6],
[ 8, 10, 12]])
ndarry是一個通用的同構數據多維容器,也就是說,其中的所有元素必須是相同類型的。每個數組都有一個shape(一個表示維度大小的元組)和一個dtype(一個說明數組數據類型的對象):
In [56]: data.shape
Out[56]: (2, 3)
In [57]: data.dtype
Out[57]: dtype('int32')
創建ndarry
創建數組的最簡單辦法就是使用array函數。它接受一切序列型對象(包括其他數組),然后產生一個新的含有傳入數據的Numpy數組。
In [59]: data1=[6,7.8,8,0,True] In [60]: arr1=np.array(data1) In [61]: arr1 Out[61]: array([6. , 7.8, 8. , 0. , 1. ])
嵌套序列(比如由一組等長列表組成的列表)將會被轉換為一個多維數組:
In [62]: data2=[[1,2,3,4],[5,6,7,8]]
In [63]: arr2=np.array(data2)
In [64]: arr2
Out[64]:
array([[1, 2, 3, 4],
[5, 6, 7, 8]])
In [65]: arr2.ndim
Out[65]: 2
In [66]: arr2.shape
Out[66]: (2, 4)
除非顯示說明,np.array會嘗試為新建的這個數組推斷出一個較為合適的數據類型。數據類型會保存在一個特殊的dtype對象中。
In [69]: arr1.dtype
Out[69]: dtype('float64')
In [70]: arr2.dtype
Out[70]: dtype('int32')
除np.array外,還有一些函數也可以新建數組。例如,zeros和ones分別可以創建指定長度或形狀的全0或全1數組。empty可以創建一個沒有任何具體指的數組。要用這些方法創建多維數組,只需要傳入一個表示形狀的元組即可:
In [73]: np.zeros(10)
Out[73]: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
In [74]: np.zeros((3,6))
Out[74]:
array([[0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0.]])
In [75]: np.empty((2,3,2))
Out[75]:
array([[[6.23042070e-307, 4.67296746e-307],
[1.69121096e-306, 8.90101523e-307],
[1.55762979e-307, 1.78022342e-306]],
[[8.06635958e-308, 1.86921415e-306],
[1.27946330e-307, 1.11262266e-307],
[1.00133162e-307, 1.22387381e-307]]])
注意:認為np.empty會返回全0數組的想法是不安全的。很多情況下,它返回的都是一些未初始化的垃圾值。
arange是python內置函數range的數組版:
In [76]: np.arange(15) Out[76]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
數組創建函數:
array:將輸入數據(列表 元組 數組或其他序列類型)轉換未ndarry。要么推斷出dtype,要么顯示指定dtype。默認執行復制輸入數據。
asarry:將輸入轉換未ndarry,如果輸入本身就是一個ndarry就不進行復制。
arange:類似與內置的range,返回的是一個ndarry而不是列表
ones丶ones_like:根據指定的形狀和dtype創建一個全1數組。ones_like以另外一個數組為參數,並根據其形狀和dtype創建一個全1數組。
zeros丶zeros_like:類似ones丶ones_like,不過產生的是全0數組。
empty丶empty:創建新數組,只分配內存空間但不填充任何值。
eyp丶identity:創建一個正方的N x N單位矩陣(對角線為1,其余為0)
ndarry的數據類型
dtype是一個特殊的對象,它含有ndarry將一塊內存解釋為特定數據類型所需的信息:
它包含的數據類型:int8丶uint8丶int16丶unit16丶int32丶uint32丶int64丶uint64丶float16丶float32丶float64丶float128丶bool等
可以通過ndarry的astype方法顯式地轉換其dtype:
In [78]: arr=np.array([1,2,3,4,5])
In [79]: arr.dtype
Out[79]: dtype('int32')
In [80]: float_arr=arr.astype(np.float64)
In [81]: float_arr.dtype
Out[81]: dtype('float64')
數組和標量之間的運算
數組很重要,因為你使它不用編寫循環即可對數組執行批量運算,這通常就叫矢量化。
In [83]: arr=np.array([[1,2,3],[4,5,6]])
In [84]: arr
Out[84]:
array([[1, 2, 3],
[4, 5, 6]])
In [85]: arr*arr
Out[85]:
array([[ 1, 4, 9],
[16, 25, 36]])
In [86]: arr -arr
Out[86]:
array([[0, 0, 0],
[0, 0, 0]])
In [87]: 1/arr
Out[87]:
array([[1. , 0.5 , 0.33333333],
[0.25 , 0.2 , 0.16666667]])
In [88]: arr ** 2
Out[88]:
array([[ 1, 4, 9],
[16, 25, 36]], dtype=int32)
基本的索引和切片
一維數組選取數據子集或單個元素的方式跟python列表功能差不多:
In [90]: arr Out[90]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) In [91]: arr[5] Out[91]: 5 In [92]: arr[5:8] Out[92]: array([5, 6, 7])
Out[94]: array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9])
當in將一個標量值賦值給一個切片時(如arr[5:8]=12),該值會自動傳播到整個選區,也就是廣播。跟列表最重要的區別在於,數組切片是原始數組的視圖。這意味着數據不會被復制,視圖上任何修改都會直接反映到源數組上:
In [94]: arr
Out[94]: array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9])
In [95]: arr_slice=arr[5:8]
In [96]: arr_slice[1]
Out[96]: 12
In [97]: arr_slice[1]=12345
In [98]: arr
Out[98]:
array([ 0, 1, 2, 3, 4, 12, 12345, 12, 8,
9])
In [99]: arr_slice[:]=64
In [100]: arr
Out[100]: array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])
由於Numpy的設計目的是處理大數據,可以想象下加入numpy堅持將數據復制來復制去的話會產生什么樣的性能和內存問題。如果你想要得到的是ndarry切片的一份副本而非視圖,就需要顯式的進行復制操作,例如arr[5:8].copy() 。
在一個二維數組中,各索引位置上的元素不再是標量而是一維數組:
In [101]: arr2=np.array([[1,2,3],[4,5,6],[7,8,9]]) In [102]: arr2[2] Out[102]: array([7, 8, 9])
因此,可以對多個元素進行遞歸訪問,,也可以傳入一個逗號隔開的索引列表來選取單個元素,兩種方式是等價的:
In [109]: arr2[0][2] Out[109]: 3 In [110]: arr2[0,2] Out[110]: 3
在多維數組中,如果省略了后面的索引,則返回對象是一個維度低一點的ndarry。
In [111]: arr3d=np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])
In [112]: arr3d
Out[112]:
array([[[ 1, 2, 3],
[ 4, 5, 6]],
[[ 7, 8, 9],
[10, 11, 12]]])
arr3d是一個2x3的數組
In [113]: arr3d[0]
Out[113]:
array([[1, 2, 3],
[4, 5, 6]])
標量值和數組都可以被賦值給arr3d[0]:
In [114]: old_values=arr3d[0].copy()
In [115]: arr3d[0]=42
In [116]: arr3d
Out[116]:
array([[[42, 42, 42],
[42, 42, 42]],
[[ 7, 8, 9],
[10, 11, 12]]])
In [117]: arr3d[0]=old_values
In [118]: arr3d
Out[118]:
array([[[ 1, 2, 3],
[ 4, 5, 6]],
[[ 7, 8, 9],
[10, 11, 12]]])
以此類推,arr3d[1,0]可以訪問索引以(1,0)開頭的那些值:
In [119]: arr3d[1,0] Out[119]: array([7, 8, 9])
注意:上面所有這些選取數組子集的例子中,返回的數組都是視圖。
切片索引
ndarry的切片語法跟python列表這樣的一維對象差不多:
In [120]: arr[1:6] Out[120]: array([ 1, 2, 3, 4, 64])
高維度對象的花樣更多,你可以子啊一個或多個軸上進行切片,也可以跟整數索引混合使用。
In [122]: arr2d=np.array([[1,2,3],[4,5,6],[7,8,9]])
In [123]: arr2d
Out[123]:
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
In [124]: arr2d[:2]
Out[124]:
array([[1, 2, 3],
[4, 5, 6]])
可以看出,它是沿着第0軸切片的。也就是說切片是沿着一個軸向選取元素的。你可以一次傳入多個切片,就像傳入多個索引那樣:
In [125]: arr2d[:2,1:]
Out[125]:
array([[2, 3],
[5, 6]])
像這樣進行切片時,只能得到相同維度的數組視圖。通過將整數索引和切片混合,可以得到低維度的切片:
In [127]: arr2d[1,:2] Out[127]: array([4, 5]) In [128]: arr2d[2,:1] Out[128]: array([7])
注意:只有冒號表示選取整個軸:、
In [129]: arr2d[:,:1]
Out[129]:
array([[1],
[4],
[7]])
自然,對切片表達式的賦值操作也會被擴散到整個選區:
In [130]: arr2d[:2,1:]
Out[130]:
array([[2, 3],
[5, 6]])
In [131]: arr2d[:2,1:]=0
In [132]: arr2d
Out[132]:
array([[1, 0, 0],
[4, 0, 0],
[7, 8, 9]])
布爾型索引
假設我們有一個用於存儲數據的數組以及一個存儲姓名的數組(含有重復項)。在這里,我們將使用numpy.random中的randn函數生成一些正態分布的隨機數據:
In [133]: names=np.array(['Bob','Joe','Will','Bob','Will','Joe','Joe'])
In [134]: data=randn(7,4)
In [135]: names
Out[135]: array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], dtype='<U4')
In [136]: data
Out[136]:
array([[ 1.49700553, -0.81010655, 1.38541267, 0.77762598],
[-1.04760513, -0.25802261, -0.07125992, -0.03431114],
[-0.49107174, -1.33574227, 0.10408644, -0.46662551],
[ 0.71487411, 1.18651842, -0.34714216, 0.46087672],
[ 0.2254356 , 0.76541494, 0.12525111, -0.92919057],
[-1.20091987, 0.37064336, -0.77465353, 0.43543748],
[ 2.63967324, -0.7687978 , 0.78363981, 0.02063025]])
假設每個名字對應data數組的一行,而我們想選粗話對應與名字'Bob'的所有行。跟算術運算一樣,數組的比較運算(如==)也是矢量化的。因此,對names和字符串‘Bob’的比較運算將會產生一個布爾型數組:
n [137]: names=='Bob' Out[137]: array([ True, False, False, True, False, False, False])
這個布爾型數組可用於數組索引:
In [139]: data[names=='Bob']
Out[139]:
array([[ 1.49700553, -0.81010655, 1.38541267, 0.77762598],
[ 0.71487411, 1.18651842, -0.34714216, 0.46087672]])
布爾型數組的長度必須跟被索引的軸長度一樣。因此,還可以將布爾型數組跟切片丶整數混合使用:
In [140]: data[names=='Bob',2:]
Out[140]:
array([[ 1.38541267, 0.77762598],
[-0.34714216, 0.46087672]])
In [141]: data[names=='Bob',3]
Out[141]: array([0.77762598, 0.46087672])
要選擇除"Bob"以外的其他值,既可以使用不等於符號(!=),也可以通過符號(-)對條件進行否定:
In [141]: data[names=='Bob',3] Out[141]: array([0.77762598, 0.46087672])
In [151]: data[~(names == 'Bob')]
Out[151]:
array([[-1.04760513, -0.25802261, -0.07125992, -0.03431114],
[-0.49107174, -1.33574227, 0.10408644, -0.46662551],
[ 0.2254356 , 0.76541494, 0.12525111, -0.92919057],
[-1.20091987, 0.37064336, -0.77465353, 0.43543748],
[ 2.63967324, -0.7687978 , 0.78363981, 0.02063025]])
這里試了下使用符號(-)會報錯TypeError: The numpy boolean negative, the `-` operator, is not supported, use the `~` operator or the logical_not function instead.提示,需要使用~符號表示取反
選取這3個名字中的2個需要組合應用多個布爾條件,使用&(和)丶|(或)之類的布爾算術運算符即可:
In [152]: mask=(names=='Bob') |(names=='Will') In [153]: mask Out[153]: array([ True, False, True, True, True, False, False])
In [154]: data[mask]
Out[154]:
array([[ 1.49700553, -0.81010655, 1.38541267, 0.77762598],
[-0.49107174, -1.33574227, 0.10408644, -0.46662551],
[ 0.71487411, 1.18651842, -0.34714216, 0.46087672],
[ 0.2254356 , 0.76541494, 0.12525111, -0.92919057]])
通過布爾型索引選取數組中的數據,將總是創建數據的副本,即使返回一模一樣的數組,也是如此。
通過布爾型數組設置值是一種經常使用的手段。為了將data中所有的負值設置為0:
In [155]: data[data<0]=0
In [156]: data
Out[156]:
array([[1.49700553, 0. , 1.38541267, 0.77762598],
[0. , 0. , 0. , 0. ],
[0. , 0. , 0.10408644, 0. ],
[0.71487411, 1.18651842, 0. , 0.46087672],
[0.2254356 , 0.76541494, 0.12525111, 0. ],
[0. , 0.37064336, 0. , 0.43543748],
[2.63967324, 0. , 0.78363981, 0.02063025]])
通過一維布爾數組設置整行或列的值也很簡單:
In [157]: data[names!='Joe']=7
In [158]: data
Out[158]:
array([[7. , 7. , 7. , 7. ],
[0. , 0. , 0. , 0. ],
[7. , 7. , 7. , 7. ],
[7. , 7. , 7. , 7. ],
[7. , 7. , 7. , 7. ],
[0. , 0.37064336, 0. , 0.43543748],
[2.63967324, 0. , 0.78363981, 0.02063025]])
花式索引
花式索引是一個Numpy術語,它指的的是利用整數數組進行索引。假設我們有個8*4的數組:
In [162]: arr=np.empty((8,4))
In [163]: for i in range(8):
...: arr[i]=i
...:
In [164]: arr
Out[164]:
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.]])
為了以特定順序選取子集,只需傳入一個用於指定順序的整數列表或ndarry即可:
In [165]: arr[[4,3,0,6]]
Out[165]:
array([[4., 4., 4., 4.],
[3., 3., 3., 3.],
[0., 0., 0., 0.],
[6., 6., 6., 6.]])
這段代碼確實達到了我們的要求!使用負數索引將會從末尾開始選取行:
In [167]: arr[[-3,-5,-7]]
Out[167]:
array([[5., 5., 5., 5.],
[3., 3., 3., 3.],
[1., 1., 1., 1.]])
一次傳入多個索引數組會有一點特別。它返回的是一個一維數組,其中的元素對應哥哥索引元組:
In [169]: arr=np.arange(32).reshape((8,4))
In [170]: arr
Out[170]:
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 [171]: arr[[1,5,7,2],[0,3,1,2]]
Out[171]: array([ 4, 23, 29, 10])
代碼中最終選取的䛾(1,0),(5,3),(7,1)和(2,2)。這些花式索引的行為可能跟某些用戶的預期不一樣,選取矩陣的行列子集應該是矩形區域的形式才對,於是:
In [173]: arr[[1,5,7,2]][:,[0,3,1,2]]
Out[173]:
array([[ 4, 7, 5, 6],
[20, 23, 21, 22],
[28, 31, 29, 30],
[ 8, 11, 9, 10]])
花式索引跟切片不一樣,它總是將數據復制到新數組中。
數組轉置和軸對換
轉置是重塑的一種特殊方式,它返回的是源數據的視圖(不會進行任何復制操作)。數組不僅有transpose方法還有一個特殊的T屬性:
In [174]: arr=np.arange(15).reshape(3,5)
In [175]: arr
Out[175]:
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
In [176]: arr.T
Out[176]:
array([[ 0, 5, 10],
[ 1, 6, 11],
[ 2, 7, 12],
[ 3, 8, 13],
[ 4, 9, 14]])
在進行矩陣計算時,經常需要用到該操作,比如利用np.dot計算矩陣的內積:
In [178]: arr=np.random.randn(6,3)
In [179]: np.dot(arr.T,arr)
Out[179]:
array([[ 4.10149347, 0.88541771, -0.47377443],
[ 0.88541771, 5.33214026, -1.6787001 ],
[-0.47377443, -1.6787001 , 6.31781659]])
對於高維度數組,transpose需要得到一個由編號組成的元組才能對這些軸進行轉置:
In [180]: arr=np.arange(16).reshape((2,2,4))
In [181]: arr
Out[181]:
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
In [182]: arr.transpose((1,0,2))
Out[182]:
array([[[ 0, 1, 2, 3],
[ 8, 9, 10, 11]],
[[ 4, 5, 6, 7],
[12, 13, 14, 15]]])
簡單的轉置可以使用.T,它其實就是進行軸對換而已。ndarry還有一個swapaxes方法,它需要接受一對軸編號:
In [183]: arr
Out[183]:
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
In [184]: arr.swapaxes(1,2)
Out[184]:
array([[[ 0, 4],
[ 1, 5],
[ 2, 6],
[ 3, 7]],
[[ 8, 12],
[ 9, 13],
[10, 14],
[11, 15]]])
swapaxes也是返回源數據的視圖(不會進行任何復制操作)。

