收藏|Numpy詳細教程

技術小能手發表於2018-12-05

NumPy 是一個 Python 包。 它代表 “Numeric Python”。 它是一個由多維陣列物件和用於處理陣列的例程集合組成的庫。

Numeric,即 NumPy 的前身,是由 Jim Hugunin 開發的。 也開發了另一個包 Numarray ,它擁有一些額外的功能。 2005年,Travis Oliphant 通過將 Numarray 的功能整合到 Numeric 包中來建立 NumPy 包。 這個開源專案有很多貢獻者。

Numpy基礎

NumPy的主要物件是同種元素的多維陣列。這是一個所有的元素都是一種型別、通過一個正整數元組索引的元素表格(通常是元素是數字)。在NumPy中維度(dimensions)叫做軸(axes),軸的個數叫做秩(rank)。

例如,在3D空間一個點的座標[1, 2, 3]是一個秩為1的陣列,因為它只有一個軸。那個軸長度為3.又例如,在以下例子中,陣列的秩為2(它有兩個維度).第一個維度長度為2,第二個維度長度為3.

[[ 1., 0., 0.],
[ 0., 1., 2.]]

NumPy的陣列類被稱作ndarray。通常被稱作陣列。注意numpy.array和標準Python庫類array.array並不相同,後者只處理一維陣列和提供少量功能。更多重要ndarray物件屬性有:

ndarray.ndim: 陣列軸的個數,在python的世界中,軸的個數被稱作秩

ndarray.shape:陣列的維度。這是一個指示陣列在每個維度上大小的整數元組。例如一個n排m列的矩陣,它的shape屬性將是(2,3),這個元組的長度顯然是秩,即維度或者ndim屬性

ndarray.size 陣列元素的總個數,等於shape屬性中元組元素的乘積。

ndarray.dtype 一個用來描述陣列中元素型別的物件,可以通過創造或指定dtype使用標準Python型別。另外NumPy提供它自己的資料型別。

ndarray.itemsize 陣列中每個元素的位元組大小。例如,一個元素型別為float64的陣列itemsiz屬性值為8(=64/8),又如,一個元素型別為complex32的陣列item屬性為4(=32/8).

ndarray.data 包含實際陣列元素的緩衝區,通常我們不需要使用這個屬性,因為我們總是通過索引來使用陣列中的元素。

例如:

>>> from numpy  import *
>>> a = arange(15).reshape(3, 5)
>>> a
array([[ 0,  1,  2,  3,  4],
[ 5,  6,  7,  8,  9],
[10, 11, 12, 13, 14]])
>>> a.shape
(3, 5)
>>> a.ndim
2
>>> a.dtype.name
`int32`
>>> a.itemsize
4
>>> a.size
15
>>> type(a)
numpy.ndarray
>>> b = array([6, 7, 8])
>>> b
array([6, 7, 8])
>>> type(b)
numpy.ndarray

建立陣列

有好幾種建立陣列的方法。

例如,你可以使用 array函式從常規的Python列表和元組創造陣列。所建立的陣列型別由原序列中的元素型別推導而來。

>>> from numpy  import *
>>> a = array( [2,3,4] )
>>> a
array([2, 3, 4])
>>> a.dtype
dtype(`int32`)
>>> b = array([1.2, 3.5, 5.1])
>>> b.dtype
dtype(`float64`)  一個常見的錯誤包括用多個數值引數呼叫`array`而不是提供一個由數值組成的列表作為一個引數。
>>> a = array(1,2,3,4)    # WRONG
>>> a = array([1,2,3,4])  # RIGHT

陣列將序列包含序列轉化成二維的陣列,序列包含序列包含序列轉化成三維陣列等等。

>>> b = array( [ (1.5,2,3), (4,5,6) ] )
>>> b
array([[ 1.5,  2. ,  3. ],
[ 4. ,  5. ,  6. ]])

陣列型別可以在建立時顯示指定

>>> c = array( [ [1,2], [3,4] ], dtype=complex )
>>> c
array([[ 1.+0.j,  2.+0.j],
[ 3.+0.j,  4.+0.j]])

通常,陣列的元素開始都是未知的,但是它的大小已知。因此,NumPy提供了一些使用佔位符建立陣列的函式。這最小化了擴充套件陣列的需要和高昂的運算代價。

函式 function建立一個全是0的陣列,函式 ones建立一個全1的陣列,函式 empty建立一個內容隨機並且依賴與記憶體狀態的陣列。預設建立的陣列型別(dtype)都是float64。

>>> zeros( (3,4) )
array([[0.,  0.,  0.,  0.],
[0.,  0.,  0.,  0.],
[0.,  0.,  0.,  0.]])
>>> ones( (2,3,4), dtype=int16 )                # dtype can also be specified
array([[[ 1, 1, 1, 1],
[ 1, 1, 1, 1],
[ 1, 1, 1, 1]],
[[ 1, 1, 1, 1],
[ 1, 1, 1, 1],
[ 1, 1, 1, 1]]], dtype=int16)
>>> empty( (2,3) )
array([[  3.73603959e-262,   6.02658058e-154,   6.55490914e-260],
[  5.30498948e-313,   3.14673309e-307,   1.00000000e+000]])

為了建立一個數列,NumPy提供一個類似arange的函式返回陣列而不是列表:

>>> arange( 10, 30, 5 )
array([10, 15, 20, 25])
>>> arange( 0, 2, 0.3 )                 # it accepts float arguments
array([ 0. ,  0.3,  0.6,  0.9,  1.2,  1.5,  1.8])

當 arange使用浮點數引數時,由於有限的浮點數精度,通常無法預測獲得的元素個數。因此,最好使用函式 linspace去接收我們想要的元素個數來代替用range來指定步長。

列印陣列

當你列印一個陣列,NumPy以類似巢狀列表的形式顯示它,但是呈以下佈局:

最後的軸從左到右列印

次後的軸從頂向下列印

剩下的軸從頂向下列印,每個切片通過一個空行與下一個隔開

一維陣列被列印成行,二維陣列成矩陣,三維陣列成矩陣列表。

>>> a = arange(6)                         # 1d array
>>> print a
[0 1 2 3 4 5]
>>>
>>> b = arange(12).reshape(4,3)           # 2d array
>>> print b
[[ 0  1  2]
[ 3  4  5]
[ 6  7  8]
[ 9 10 11]]
>>>
>>> c = arange(24).reshape(2,3,4)         # 3d array
>>> print c
[[[ 0  1  2  3]
[ 4  5  6  7]
[ 8  9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]

如果一個陣列用來列印太大了,NumPy自動省略中間部分而只列印角落

>>> print arange(10000)
[   0    1    2 ..., 9997 9998 9999]
>>>
>>> print arange(10000).reshape(100,100)
[[   0    1    2 ...,   97   98   99]
[ 100  101  102 ...,  197  198  199]
[ 200  201  202 ...,  297  298  299]
...,
[9700 9701 9702 ..., 9797 9798 9799]
[9800 9801 9802 ..., 9897 9898 9899]
[9900 9901 9902 ..., 9997 9998 9999]]

禁用NumPy的這種行為並強制列印整個陣列,你可以設定 printoptions引數來更改列印選項。

>>> set_printoptions(threshold=`nan`)

基本運算

陣列的算術運算是按元素的。新的陣列被建立並且被結果填充。

>>> a = array( [20,30,40,50] )
>>> b = arange( 4 )
>>> b
array([0, 1, 2, 3])
>>> c = a-b
>>> c
array([20, 29, 38, 47])
>>> b**2
array([0, 1, 4, 9])
>>> 10*sin(a)
array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
>>> a<35
array([True, True, False, False], dtype=bool)

不像許多矩陣語言,NumPy中的乘法運算子*指示按元素計算,矩陣乘法可以使用dot函式或建立矩陣物件實現

>>> A = array( [[1,1],
...             [0,1]] )
>>> B = array( [[2,0],
...             [3,4]] )
>>> A*B                         # elementwise product
array([[2, 0],
[0, 4]])
>>> dot(A,B)                    # matrix product
array([[5, 4],
[3, 4]])

有些操作符像 +=和 *=被用來更改已存在陣列而不建立一個新的陣列。

>>> a = ones((2,3), dtype=int)
>>> b = random.random((2,3))
>>> a *= 3
>>> a
array([[3, 3, 3],
[3, 3, 3]])
>>> b += a
>>> b
array([[ 3.69092703,  3.8324276 ,  3.0114541 ],
[ 3.18679111,  3.3039349 ,  3.37600289]])
>>> a += b                                  # b is converted to integer type
>>> a
array([[6, 6, 6],
[6, 6, 6]])

當運算的是不同型別的陣列時,結果陣列和更普遍和精確的已知(這種行為叫做upcast)。

>>> a = ones(3, dtype=int32)
>>> b = linspace(0,pi,3)
>>> b.dtype.name
`float64`
>>> c = a+b
>>> c
array([ 1.        ,  2.57079633,  4.14159265])
>>> c.dtype.name
`float64`
>>> d = exp(c*1j)
>>> d
array([ 0.54030231+0.84147098j, -0.84147098+0.54030231j,
-0.54030231-0.84147098j])
>>> d.dtype.name
`complex128` 許多非陣列運算,如計算陣列所有元素之和,被作為ndarray類的方法實現
>>> a = random.random((2,3))
>>> a
array([[ 0.6903007 ,  0.39168346,  0.16524769],
[ 0.48819875,  0.77188505,  0.94792155]])
>>> a.sum()
3.4552372100521485
>>> a.min()
0.16524768654743593
>>> a.max()
0.9479215542670073

這些運算預設應用到陣列好像它就是一個數字組成的列表,無關陣列的形狀。然而,指定 axis引數你可以吧運算應用到陣列指定的軸上:

>>> b = arange(12).reshape(3,4)
>>> b
array([[ 0,  1,  2,  3],
[ 4,  5,  6,  7],
[ 8,  9, 10, 11]])
>>>
>>> b.sum(axis=0)                            # sum of each column
array([12, 15, 18, 21])
>>>
>>> b.min(axis=1)                            # min of each row
array([0, 4, 8])
>>>
>>> b.cumsum(axis=1)                         # cumulative sum along each row
array([[ 0,  1,  3,  6],
[ 4,  9, 15, 22],
[ 8, 17, 27, 38]])
通用函式(ufunc)
NumPy提供常見的數學函式如 sin, cos和 exp。在NumPy中,這些叫作“通用函式”(ufunc)。在NumPy裡這些函式作用按陣列的元素運算,產生一個陣列作為輸出.
>>> B = arange(3)
>>> B
array([0, 1, 2])
>>> exp(B)
array([ 1.        ,  2.71828183,  7.3890561 ])
>>> sqrt(B)
array([ 0.        ,  1.        ,  1.41421356])
>>> C = array([2., -1., 4.])
>>> add(B, C)
array([ 2.,  0.,  6.])

索引,切片和迭代

一維陣列可以被索引、切片和迭代,就像列表和其它Python序列。

>>> a = arange(10)**3
>>> a
array([  0,   1,   8,  27,  64, 125, 216, 343, 512, 729])
>>> a[2]
8
>>> a[2:5]
array([ 8, 27, 64])
>>> a[:6:2] = -1000    # equivalent to a[0:6:2] = -1000; from start to position 6, exclusive, set every 2nd element to -1000
>>> a
array([-1000,     1, -1000,    27, -1000,   125,   216,   343,   512,   729])
>>> a[ : :-1]                                 # reversed a
array([  729,   512,   343,   216,   125, -1000,    27, -1000,     1, -1000])
>>> for i in a:
...         print i**(1/3.),
...
nan 1.0 nan 3.0 nan 5.0 6.0 7.0 8.0 9.0

多維陣列可以每個軸有一個索引。這些索引由一個逗號分割的元組給出。

>>> def f(x,y):
...         return 10*x+y
...
>>> b = fromfunction(f,(5,4),dtype=int)
>>> b
array([[ 0,  1,  2,  3],
[10, 11, 12, 13],
[20, 21, 22, 23],
[30, 31, 32, 33],
[40, 41, 42, 43]])
>>> b[2,3]
23
>>> b[0:5, 1]                       # each row in the second column of b
array([ 1, 11, 21, 31, 41])
>>> b[ : ,1]                        # equivalent to the previous example
array([ 1, 11, 21, 31, 41])
>>> b[1:3, : ]                      # each column in the second and third row of b
array([[10, 11, 12, 13],
[20, 21, 22, 23]])

當少於軸數的索引被提供時,確失的索引被認為是整個切片:

>>> b[-1]                                  # the last row. Equivalent to b[-1,:]
array([40, 41, 42, 43])

b[i]中括號中的表示式被當作i和一系列:,來代表剩下的軸。NumPy也允許你使用“點”像 b[i,…]。

點(…)代表許多產生一個完整的索引元組必要的分號。如果x是秩為5的陣列(即它有5個軸),那麼:

x[1,2,…] 等同於 x[1,2,:,:,:],
x[…,3] 等同於 x[:,:,:,:,3]
x[4,…,5,:] 等同 x[4,:,:,5,:].
>>> c = array( [ [[  0,  1,  2],      # a 3D array (two stacked 2D arrays) ...               [ 10, 12, 13]], ... ...              [[100,101,102], ...               [110,112,113]] ] ) >>> c.shape (2, 2, 3) >>> c[1,...]                          # same as c[1,:,:] or c[1] array([[100, 101, 102],        [110, 112, 113]]) >>> c[...,2]                          # same as c[:,:,2] array([[  2,  13],        [102, 113]]) 

迭代多維陣列是就第一個軸而言的:

>>> for row in b:
...         print row
...
[0 1 2 3]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
[40 41 42 43]

然而,如果一個人想對每個陣列中元素進行運算,我們可以使用flat屬性,該屬性是陣列元素的一個迭代器:

>>> for element in b.flat:
...         print element,
...
0 1 2 3 10 11 12 13 20 21 22 23 30 31 32 33 40 41 42 43

形狀操作

更改陣列的形狀一個陣列的形狀由它每個軸上的元素個數給出:

>>> a = floor(10*random.random((3,4)))
>>> a
array([[ 7.,  5.,  9.,  3.],
[ 7.,  2.,  7.,  8.],
[ 6.,  8.,  3.,  2.]])
>>> a.shape
(3, 4)

一個陣列的形狀可以被多種命令修改:

>>> a.ravel() # flatten the array
array([ 7.,  5.,  9.,  3.,  7.,  2.,  7.,  8.,  6.,  8.,  3.,  2.])
>>> a.shape = (6, 2)
>>> a.transpose()
array([[ 7.,  9.,  7.,  7.,  6.,  3.],
[ 5.,  3.,  2.,  8.,  8.,  2.]])

由 ravel()展平的陣列元素的順序通常是“C風格”的,就是說,最右邊的索引變化得最快,所以元素a[0,0]之後是a[0,1]。如果陣列被改變形狀(reshape)成其它形狀,陣列仍然是“C風格”的。NumPy通常建立一個以這個順序儲存資料的陣列,所以 ravel()將總是不需要複製它的引數3。但是如果陣列是通過切片其它陣列或有不同尋常的選項時,它可能需要被複制。函式 reshape()和 ravel()還可以被同過一些可選引數構建成FORTRAN風格的陣列,即最左邊的索引變化最快。 reshape函式改變引數形狀並返回它,而resize函式改變陣列自身。

>>> a
array([[ 7.,  5.],
[ 9.,  3.],
[ 7.,  2.],
[ 7.,  8.],
[ 6.,  8.],
[ 3.,  2.]])
>>> a.resize((2,6))
>>> a
array([[ 7.,  5.,  9.,  3.,  7.,  2.],
[ 7.,  8.,  6.,  8.,  3.,  2.]])

如果在改變形狀操作中一個維度被給做-1,其維度將自動被計算

組合(stack)不同的陣列

幾種方法可以沿不同軸將陣列堆疊在一起:

>>> a = floor(10*random.random((2,2)))
>>> a
array([[ 1.,  1.],
[ 5.,  8.]])
>>> b = floor(10*random.random((2,2)))
>>> b
array([[ 3.,  3.],
[ 6.,  0.]])
>>> vstack((a,b))
array([[ 1.,  1.],
[ 5.,  8.],
[ 3.,  3.],
[ 6.,  0.]])
>>> hstack((a,b))
array([[ 1.,  1.,  3.,  3.],
[ 5.,  8.,  6.,  0.]])

函式 column_stack以列將一維陣列合成二維陣列,它等同與 vstack對一維陣列。

>>> column_stack((a,b))   # With 2D arrays
array([[ 1.,  1.,  3.,  3.],
[ 5.,  8.,  6.,  0.]])
>>> a=array([4.,2.])
>>> b=array([2.,8.])
>>> a[:,newaxis]  # This allows to have a 2D columns vector
array([[ 4.],
[ 2.]])
>>> column_stack((a[:,newaxis],b[:,newaxis]))
array([[ 4.,  2.],
[ 2.,  8.]])
>>> vstack((a[:,newaxis],b[:,newaxis])) # The behavior of vstack is different
array([[ 4.],
[ 2.],
[ 2.],
[ 8.]])

row_stack函式,另一方面,將一維陣列以行組合成二維陣列。

對那些維度比二維更高的陣列, hstack沿著第二個軸組合, vstack沿著第一個軸組合, concatenate允許可選引數給出組合時沿著的軸。

在複雜情況下, r_[]和 c_[]對建立沿著一個方向組合的數很有用,它們允許範圍符號(“:”):

>>> r_[1:4,0,4]
array([1, 2, 3, 0, 4])

當使用陣列作為引數時,r和c的預設行為和vstack和hstack很像,但是允許可選的引數給出組合所沿著的軸的代號。

將一個陣列分割(split)成幾個小陣列

使用 hsplit你能將陣列沿著它的水平軸分割,或者指定返回相同形狀陣列的個數,或者指定在哪些列後發生分割:

>>> a = floor(10*random.random((2,12)))
>>> a
array([[ 8.,  8.,  3.,  9.,  0.,  4.,  3.,  0.,  0.,  6.,  4.,  4.],
[ 0.,  3.,  2.,  9.,  6.,  0.,  4.,  5.,  7.,  5.,  1.,  4.]])
>>> hsplit(a,3)   # Split a into 3
[array([[ 8.,  8.,  3.,  9.],
[ 0.,  3.,  2.,  9.]]), array([[ 0.,  4.,  3.,  0.],
[ 6.,  0.,  4.,  5.]]), array([[ 0.,  6.,  4.,  4.],
[ 7.,  5.,  1.,  4.]])]
>>> hsplit(a,(3,4))   # Split a after the third and the fourth column
[array([[ 8.,  8.,  3.],
[ 0.,  3.,  2.]]), array([[ 9.],
[ 9.]]), array([[ 0.,  4.,  3.,  0.,  0.,  6.,  4.,  4.],
[ 6.,  0.,  4.,  5.,  7.,  5.,  1.,  4.]])]

vsplit沿著縱向的軸分割, array split允許指定沿哪個軸分割。

複製和檢視當運算和處理陣列時,它們的資料有時被拷貝到新的陣列有時不是。這通常是新手的困惑之源。這有三種情況:完全不拷貝簡單的賦值不拷貝陣列物件或它們的資料。

>>> a = arange(12)
>>> b = a            # no new object is created
>>> b is a           # a and b are two names for the same ndarray object
True
>>> b.shape = 3,4    # changes the shape of a
>>> a.shape
(3, 4)

Python 傳遞不定物件作為參考,所以函式呼叫不拷貝陣列。

>>> def f(x):
...     print id(x)
...
>>> id(a)                           # id is a unique identifier of an object
148293216
>>> f(a)
148293216

檢視(view)和淺複製

不同的陣列物件分享同一個資料。檢視方法創造一個新的陣列物件指向同一資料。

>>> c = a.view()
>>> c is a
False
>>> c.base is a                        # c is a view of the data owned by a
True
>>> c.flags.owndata
False
>>>
>>> c.shape = 2,6                      # a`s shape doesn`t change
>>> a.shape
(3, 4)
>>> c[0,4] = 1234                      # a`s data changes
>>> a
array([[   0,    1,    2,    3],
[1234,    5,    6,    7],
[   8,    9,   10,   11]])

切片陣列返回它的一個檢視:

>>> s = a[ : , 1:3]     # spaces added for clarity; could also be written "s = a[:,1:3]"
>>> s[:] = 10           # s[:] is a view of s. Note the difference between s=10 and s[:]=10
>>> a
array([[   0,   10,   10,    3],
[1234,   10,   10,    7],
[   8,   10,   10,   11]])

深複製

這個複製方法完全複製陣列和它的資料。

>>> d = a.copy()                          # a new array object with new data is created
>>> d is a
False
>>> d.base is a                           # d doesn`t share anything with a
False
>>> d[0,0] = 9999
>>> a
array([[   0,   10,   10,    3],
[1234,   10,   10,    7],
[   8,   10,   10,   11]])

函式和方法(method)總覽

建立陣列

arange, array, copy, empty, empty_like, eye, fromfile, fromfunction, identity, linspace, logspace, mgrid, ogrid, ones, ones_like, r , zeros, zeros_like 

轉化

astype, atleast 1d, atleast 2d, atleast 3d, mat 

操作

array split, column stack, concatenate, diagonal, dsplit, dstack, hsplit, hstack, item, newaxis, ravel, repeat, reshape, resize, squeeze, swapaxes, take, transpose, vsplit, vstack 

詢問

all, any, nonzero, where 

排序

argmax, argmin, argsort, max, min, ptp, searchsorted, sort 

運算

choose, compress, cumprod, cumsum, inner, fill, imag, prod, put, putmask, real, sum 

基本統計

cov, mean, std, var 

基本線性代數

cross, dot, outer, svd, vdot

進階

廣播法則(rule)

廣播法則能使通用函式有意義地處理不具有相同形狀的輸入。

廣播第一法則是,如果所有的輸入陣列維度不都相同,一個“1”將被重複地新增在維度較小的陣列上直至所有的陣列擁有一樣的維度。

廣播第二法則確定長度為1的陣列沿著特殊的方向表現地好像它有沿著那個方向最大形狀的大小。對陣列來說,沿著那個維度的陣列元素的值理應相同。

應用廣播法則之後,所有陣列的大小必須匹配。更多細節可以從這個文件找到。

花哨的索引和索引技巧

NumPy比普通Python序列提供更多的索引功能。除了索引整數和切片,正如我們之前看到的,陣列可以被整數陣列和布林陣列索引。

通過陣列索引

>>> a = arange(12)**2                          # the first 12 square numbers
>>> i = array( [ 1,1,3,8,5 ] )                 # an array of indices
>>> a[i]                                       # the elements of a at the positions i
array([ 1,  1,  9, 64, 25])
>>>
>>> j = array( [ [ 3, 4], [ 9, 7 ] ] )         # a bidimensional array of indices
>>> a[j]                                       # the same shape as j
array([[ 9, 16],
[81, 49]])

當被索引陣列a是多維的時,每一個唯一的索引數列指向a的第一維。以下示例通過將圖片標籤用調色版轉換成色彩影像展示了這種行為。

>>> palette = array( [ [0,0,0],                # black
...                    [255,0,0],              # red
...                    [0,255,0],              # green
...                    [0,0,255],              # blue
...                    [255,255,255] ] )       # white
>>> image = array( [ [ 0, 1, 2, 0 ],           # each value corresponds to a color in the palette
...                  [ 0, 3, 4, 0 ]  ] )
>>> palette[image]                            # the (2,4,3) color image
array([[[  0,   0,   0],
[255,   0,   0],
[  0, 255,   0],
[  0,   0,   0]],
[[  0,   0,   0],
[  0,   0, 255],
[255, 255, 255],
[  0,   0,   0]]])

我們也可以給出不不止一維的索引,每一維的索引陣列必須有相同的形狀。

>>> a = arange(12).reshape(3,4)
>>> a
array([[ 0,  1,  2,  3],
[ 4,  5,  6,  7],
[ 8,  9, 10, 11]])
>>> i = array( [ [0,1],                        # indices for the first dim of a
...              [1,2] ] )
>>> j = array( [ [2,1],                        # indices for the second dim
...              [3,3] ] )
>>>
>>> a[i,j]                                     # i and j must have equal shape
array([[ 2,  5],
[ 7, 11]])
>>>
>>> a[i,2]
array([[ 2,  6],
[ 6, 10]])
>>>
>>> a[:,j]                                     # i.e., a[ : , j]
array([[[ 2,  1],
[ 3,  3]],
[[ 6,  5],
[ 7,  7]],
[[10,  9],
[11, 11]]])

自然,我們可以把i和j放到序列中(比如說列表)然後通過list索引。

>>> l = [i,j]
>>> a[l]                                       # equivalent to a[i,j]
array([[ 2,  5],
[ 7, 11]])

然而,我們不能把i和j放在一個陣列中,因為這個陣列將被解釋成索引a的第一維。

>>> s = array( [i,j] )
>>> a[s]                                       # not what we want
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-100-b912f631cc75> in <module>()
----> 1 a[s]
IndexError: index (3) out of range (0<=index<2) in dimension 0
>>>
>>> a[tuple(s)]                                # same as a[i,j]
array([[ 2,  5],
[ 7, 11]])

另一個常用的陣列索引用法是搜尋時間序列最大值。

>>> time = linspace(20, 145, 5)                 # time scale
>>> data = sin(arange(20)).reshape(5,4)         # 4 time-dependent series
>>> time
array([  20.  ,   51.25,   82.5 ,  113.75,  145.  ])
>>> data
array([[ 0.        ,  0.84147098,  0.90929743,  0.14112001],
[-0.7568025 , -0.95892427, -0.2794155 ,  0.6569866 ],
[ 0.98935825,  0.41211849, -0.54402111, -0.99999021],
[-0.53657292,  0.42016704,  0.99060736,  0.65028784],
[-0.28790332, -0.96139749, -0.75098725,  0.14987721]])
>>>
>>> ind = data.argmax(axis=0)                   # index of the maxima for each series
>>> ind
array([2, 0, 3, 1])
>>>
>>> time_max = time[ ind]                       # times corresponding to the maxima
>>>
>>> data_max = data[ind, xrange(data.shape[1])] # => data[ind[0],0], data[ind[1],1]...
>>>
>>> time_max
array([  82.5 ,   20.  ,  113.75,   51.25])
>>> data_max
array([ 0.98935825,  0.84147098,  0.99060736,  0.6569866 ])
>>>
>>> all(data_max == data.max(axis=0))
True

你也可以使用陣列索引作為目標來賦值:

>>> a = arange(5)
>>> a
array([0, 1, 2, 3, 4])
>>> a[[1,3,4]] = 0
>>> a
array([0, 0, 2, 0, 0])

然而,當一個索引列表包含重複時,賦值被多次完成,保留最後的值:

>>> a = arange(5)
>>> a[[0,0,2]]=[1,2,3]
>>> a
array([2, 1, 3, 3, 4])

這足夠合理,但是小心如果你想用Python的+=結構,可能結果並非你所期望:

>>> a = arange(5)
>>> a[[0,0,2]]+=1
>>> a
array([1, 1, 3, 3, 4])

即使0在索引列表中出現兩次,索引為0的元素僅僅增加一次。這是因為Python要求a+=1和a=a+1等同。

通過布林陣列索引

當我們使用整數陣列索引陣列時,我們提供一個索引列表去選擇。通過布林陣列索引的方法是不同的我們顯式地選擇陣列中我們想要和不想要的元素。

我們能想到的使用布林陣列的索引最自然方式就是使用和原陣列一樣形狀的布林陣列。

>>> a = arange(12).reshape(3,4)
>>> b = a > 4
>>> b                                          # b is a boolean with a`s shape
array([[False, False, False, False],
[False, True, True, True],
[True, True, True, True]], dtype=bool)
>>> a[b]                                       # 1d array with the selected elements
array([ 5,  6,  7,  8,  9, 10, 11])

這個屬性在賦值時非常有用:

>>> a[b] = 0                                   # All elements of `a` higher than 4 become 0
>>> a
array([[0, 1, 2, 3],
[4, 0, 0, 0],
[0, 0, 0, 0]])

第二種通過布林來索引的方法更近似於整數索引;對陣列的每個維度我們給一個一維布林陣列來選擇我們想要的切片。

>>> a = arange(12).reshape(3,4)
>>> b1 = array([False,True,True])             # first dim selection
>>> b2 = array([True,False,True,False])       # second dim selection
>>>
>>> a[b1,:]                                   # selecting rows
array([[ 4,  5,  6,  7],
[ 8,  9, 10, 11]])
>>>
>>> a[b1]                                     # same thing
array([[ 4,  5,  6,  7],
[ 8,  9, 10, 11]])
>>>
>>> a[:,b2]                                   # selecting columns
array([[ 0,  2],
[ 4,  6],
[ 8, 10]])
>>>
>>> a[b1,b2]                                  # a weird thing to do
array([ 4, 10])

注意一維陣列的長度必須和你想要切片的維度或軸的長度一致,在之前的例子中,b1是一個秩為1長度為三的陣列(a的行數),b2(長度為4)與a的第二秩(列)相一致.

ix_()函式

ix_函式可以為了獲得多元組的結果而用來結合不同向量。例如,如果你想要用所有向量a、b和c元素組成的三元組來計算 a+b*c:

>>> a = array([2,3,4,5])
>>> b = array([8,5,4])
>>> c = array([5,4,6,8,3])
>>> ax,bx,cx = ix_(a,b,c)
>>> ax
array([[[2]],
[[3]],
[[4]],
[[5]]])
>>> bx
array([[[8],
[5],
[4]]])
>>> cx
array([[[5, 4, 6, 8, 3]]])
>>> ax.shape, bx.shape, cx.shape
((4, 1, 1), (1, 3, 1), (1, 1, 5))
>>> result = ax+bx*cx
>>> result
array([[[42, 34, 50, 66, 26],
[27, 22, 32, 42, 17],
[22, 18, 26, 34, 14]],
[[43, 35, 51, 67, 27],
[28, 23, 33, 43, 18],
[23, 19, 27, 35, 15]],
[[44, 36, 52, 68, 28],
[29, 24, 34, 44, 19],
[24, 20, 28, 36, 16]],
[[45, 37, 53, 69, 29],
[30, 25, 35, 45, 20],
[25, 21, 29, 37, 17]]])
>>> result[3,2,4]
17
>>> a[3]+b[2]*c[4]
17

你也可以實行如下簡化:

def ufunc_reduce(ufct, *vectors):
vs = ix_(*vectors)
r = ufct.identity
for v in vs:
r = ufct(r,v)
return r

然後這樣使用它:

>>> ufunc_reduce(add,a,b,c)
array([[[15, 14, 16, 18, 13],
[12, 11, 13, 15, 10],
[11, 10, 12, 14,  9]],
[[16, 15, 17, 19, 14],
[13, 12, 14, 16, 11],
[12, 11, 13, 15, 10]],
[[17, 16, 18, 20, 15],
[14, 13, 15, 17, 12],
[13, 12, 14, 16, 11]],
[[18, 17, 19, 21, 16],
[15, 14, 16, 18, 13],
[14, 13, 15, 17, 12]]])

這個reduce與ufunc.reduce(比如說add.reduce)相比的優勢在於它利用了廣播法則,避免了建立一個輸出大小乘以向量個數的引數陣列。

線性代數

簡單陣列運算

>>> from numpy import *
>>> from numpy.linalg import *
>>> a = array([[1.0, 2.0], [3.0, 4.0]])
>>> print a
[[ 1.  2.]
[ 3.  4.]]
>>> a.transpose()
array([[ 1.,  3.],
[ 2.,  4.]])
>>> inv(a)
array([[-2. ,  1. ],
[ 1.5, -0.5]])
>>> u = eye(2) # unit 2x2 matrix; "eye" represents "I"
>>> u
array([[ 1.,  0.],
[ 0.,  1.]])
>>> j = array([[0.0, -1.0], [1.0, 0.0]])
>>> dot (j, j) # matrix product
array([[-1.,  0.],
[ 0., -1.]])
>>> trace(u)  # trace
2.0
>>> y = array([[5.], [7.]])
>>> solve(a, y)
array([[-3.],
[ 4.]])
>>> eig(j)
(array([ 0.+1.j,  0.-1.j]),
array([[ 0.70710678+0.j,  0.70710678+0.j],
[ 0.00000000-0.70710678j,  0.00000000+0.70710678j]]))
Parameters:
square matrix
Returns
The eigenvalues, each repeated according to its multiplicity.
The normalized (unit "length") eigenvectors, such that the
column ``v[:,i]`` is the eigenvector corresponding to the
eigenvalue ``w[i]`` .

矩陣類

這是一個關於矩陣類的簡短介紹。

>>> A = matrix(`1.0 2.0; 3.0 4.0`)
>>> A
[[ 1.  2.]
[ 3.  4.]]
>>> type(A)  # file where class is defined
<class `numpy.matrixlib.defmatrix.matrix`>
>>> A.T  # transpose
[[ 1.  3.]
[ 2.  4.]]
>>> X = matrix(`5.0 7.0`)
>>> Y = X.T
>>> Y
[[5.]
[7.]]
>>> print A*Y  # matrix multiplication
[[19.]
[43.]]
>>> print A.I  # inverse
[[-2.   1. ]
[ 1.5 -0.5]]
>>> solve(A, Y)  # solving linear equation
matrix([[-3.],
[ 4.]])

索引:比較矩陣和二維陣列

注意NumPy中陣列和矩陣有些重要的區別。NumPy提供了兩個基本的物件:一個N維陣列物件和一個通用函式物件。其它物件都是建構在它們之上 的。特別的,矩陣是繼承自NumPy陣列物件的二維陣列物件。對陣列和矩陣,索引都必須包含合適的一個或多個這些組合:整數標量、省略號 (ellipses)、整數列表;布林值,整數或布林值構成的元組,和一個一維整數或布林值陣列。矩陣可以被用作矩陣的索引,但是通常需要陣列、列表或者 其它形式來完成這個任務。

像平常在Python中一樣,索引是從0開始的。傳統上我們用矩形的行和列表示一個二維陣列或矩陣,其中沿著0軸的方向被穿過的稱作行,沿著1軸的方向被穿過的是列。

讓我們建立陣列和矩陣用來切片

>>> A = arange(12)
>>> A
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
>>> A.shape = (3,4)
>>> M = mat(A.copy())
>>> print type(A),"  ",type(M)
<type `numpy.ndarray`>    <class `numpy.core.defmatrix.matrix`>
>>> print A
[[ 0  1  2  3]
[ 4  5  6  7]
[ 8  9 10 11]]
>>> print M
[[ 0  1  2  3]
[ 4  5  6  7]
[ 8  9 10 11]]

現在,讓我們簡單的切幾片。基本的切片使用切片物件或整數。例如, A[:]和 M[:]的求值將表現得和Python索引很相似。然而要注意很重要的一點就是NumPy切片陣列不建立資料的副本;切片提供統一資料的檢視。

>>> print A[:]; print A[:].shape
[[ 0  1  2  3]
[ 4  5  6  7]
[ 8  9 10 11]]
(3, 4)
>>> print M[:]; print M[:].shape
[[ 0  1  2  3]
[ 4  5  6  7]
[ 8  9 10 11]]
(3, 4)

現在有些和Python索引不同的了:你可以同時使用逗號分割索引來沿著多個軸索引。

>>> print A[:,1]; print A[:,1].shape
[1 5 9]
(3,)
>>> print M[:,1]; print M[:,1].shape
[[1]
[5]
[9]]
(3, 1)

注意最後兩個結果的不同。對二維陣列使用一個冒號產生一個一維陣列,然而矩陣產生了一個二維矩陣。10例如,一個M[2,:]切片產生了一個形狀為(1,4)的矩陣,相比之下,一個陣列的切片總是產生一個最低可能維度11的陣列。例如,如果C是一個三維陣列,C[…,1]產生一個二維的陣列而C[1,:,1]產生一個一維陣列。從這時開始,如果相應的矩陣切片結果是相同的話,我們將只展示陣列切片的結果。

假如我們想要一個陣列的第一列和第三列,一種方法是使用列表切片

>>> A[:,[1,3]]
array([[ 1,  3],
[ 5,  7],
[ 9, 11]])

稍微複雜點的方法是使用 take()方法(method):

>>> A[:,].take([1,3],axis=1)
array([[ 1,  3],
[ 5,  7],
[ 9, 11]])

如果我們想跳過第一行,我們可以這樣:

>>> A[1:,].take([1,3],axis=1)
array([[ 5,  7],
[ 9, 11]])

或者我們僅僅使用 A[1:,[1,3]]。還有一種方法是通過矩陣向量積(叉積)。

>>> A[ix_((1,2),(1,3))]
array([[ 5,  7],
[ 9, 11]])

為了讀者的方便,在次寫下之前的矩陣:

>>> A[ix_((1,2),(1,3))]
array([[ 5,  7],
[ 9, 11]])

現在讓我們做些更復雜的。比如說我們想要保留第一行大於1的列。一種方法是建立布林索引:

>>> A[0,:]>1
array([False, False, True, True], dtype=bool)
>>> A[:,A[0,:]>1]
array([[ 2,  3],
[ 6,  7],
[10, 11]])

就是我們想要的!但是索引矩陣沒這麼方便。

>>> M[0,:]>1
matrix([[False, False, True, True]], dtype=bool)
>>> M[:,M[0,:]>1]
matrix([[2, 3]])

這個過程的問題是用“矩陣切片”來切片產生一個矩陣,但是矩陣有個方便的A屬性,它的值是陣列呈現的。所以我們僅僅做以下替代:

>>> M[:,M.A[0,:]>1]
matrix([[ 2,  3],
[ 6,  7],
[10, 11]])

如果我們想要在矩陣兩個方向有條件地切片,我們必須稍微調整策略,代之以:

>>> A[A[:,0]>2,A[0,:]>1]
array([ 6, 11])
>>> M[M.A[:,0]>2,M.A[0,:]>1]
matrix([[ 6, 11]])

我們需要使用向量積 ix_:

>>> A[ix_(A[:,0]>2,A[0,:]>1)]
array([[ 6,  7],
[10, 11]])
>>> M[ix_(M.A[:,0]>2,M.A[0,:]>1)]
matrix([[ 6,  7],
[10, 11]])

技巧和提示

下面我們給出簡短和有用的提示。

“自動”改變形狀

更改陣列的維度,你可以省略一個尺寸,它將被自動推匯出來。

>>> a = arange(30)
>>> a.shape = 2,-1,3  # -1 means "whatever is needed"
>>> a.shape
(2, 5, 3)
>>> a
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]]])

向量組合(stacking)

我們如何用兩個相同尺寸的行向量列表構建一個二維陣列?在MATLAB中這非常簡單:如果x和y是兩個相同長度的向量,你僅僅需要做 m=[x;y]。在NumPy中這個過程通過函式 column_stack、 dstack、 hstack和 vstack來完成,取決於你想要在那個維度上組合。例如:

x = arange(0,10,2)                     # x=([0,2,4,6,8])
y = arange(5)                          # y=([0,1,2,3,4])
m = vstack([x,y])                      # m=([[0,2,4,6,8],
#     [0,1,2,3,4]])
xy = hstack([x,y])                     # xy =([0,2,4,6,8,0,1,2,3,4])

直方圖(histogram)

NumPy中histogram函式應用到一個陣列返回一對變數:直方圖陣列和箱式向量。注意:matplotlib也有一個用來建立直方圖的函式(叫作hist,正如matlab中一樣)與NumPy中的不同。主要的差別是pylab.hist自動繪製直方圖,而numpy.histogram僅僅產生資料。

import numpy
import pylab
# Build a vector of 10000 normal deviates with variance 0.5^2 and mean 2
mu, sigma = 2, 0.5
v = numpy.random.normal(mu,sigma,10000)
# Plot a normalized histogram with 50 bins
pylab.hist(v, bins=50, normed=1)       # matplotlib version (plot)
pylab.show()
# Compute the histogram with numpy and then plot it
(n, bins) = numpy.histogram(v, bins=50, normed=True)  # NumPy version (no plot)
pylab.plot(.5*(bins[1:]+bins[:-1]), n)
pylab.show()

原文釋出時間為:2018-12-5

本文作者:機器學習演算法與Python學習

本文來自雲棲社群合作伙伴“ 機器學習演算法與Python學習”,瞭解相關資訊可以關注“guodongwei1991”微信公眾號


相關文章