1. 程式人生 > >NumPy學習(一)

NumPy學習(一)

基礎

NumPy的主要物件是齊次多維陣列。表由相同型別的元素組成(通常為數字),由一個正整數元組索引。在NumPy中維數被稱為軸,軸數稱為秩。例如,一個三維空間中點的座標[1,2,1]是一個秩為1的陣列,因為其軸數為1,軸的長度為3。在下圖中,陣列的秩為2(2維),第二維的長度為3。

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

NumPy的陣列類是ndarray,也稱array。需要注意numpy.array和標準Python庫類array.array不同,後者只能處理一維陣列,提供的功能較少。
ndarray物件的主要屬性如下所示:
ndarray.ndim


陣列的軸的數量(維度的數量)。也稱為秩。
ndarray.shape
陣列的維度。這是用一個整數元組表示每個維度的陣列的大小。比如一個n行m列的矩陣,shape為(n,m)。shape元組的長度即為秩
或者稱為維度的數量,即ndim。
ndarray.size
陣列中元素的個數總和。等於shape元組中元素的乘積。
ndarray.dtype
描述陣列中的元素型別。可以使用標準的額Python型別生成或指定dtype。除此之外NumPy提供可一些獨有的型別,如numpy.int32
,numpy.int16, numpy.float64等。
ndarray.itemsize

陣列中每個元素的位元組數的大小。例如,float64型別元素的itemsize值為8=(64/8),complex32型別元素的itemsize值為4=(32/8)。等同於ndarray.dtype.itemsize。
ndarray.data
包含實際陣列元素的緩衝區。通常我們不會使用這個屬性,因為我們將會使用索引來獲取陣列中的元素。
例項:

>>> import numpy as np
>>> a = np.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 'int64' >>> a.itemsize 8 >>> a.size 15 >>> type(a) <type 'numpy.ndarray'> >>> b = np.array([6, 7, 8]) >>> b array([6, 7, 8]) >>> type(b) <type 'numpy.ndarray'>

建立陣列

有許多方法可以用來建立陣列,比如利用numpy的array方法從Python的list或tuple建立陣列。如上圖所示。
但是array方法不能直接接收一串數字,如下圖所示:

>>> a = np.array(1,2,3,4)    # 錯
>>> a = np.array([1,2,3,4])  # 對

將二維序列轉換為二維陣列:

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

也可以在建立陣列時指定元素型別:

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

經常會碰到陣列的元素實現不知道,僅直到陣列的大小。因此,numpy提供一些功能建立擁有初始佔位符的陣列。儘可能減少陣列的增長,這是個昂貴的操作。
zero方法建立一個元素全為0的陣列,ones方法建立一個元素全為1的陣列,empty方法建立一個元素內容隨機且依賴記憶體狀態的陣列。預設情況下,建立的陣列的元素型別為float64。

>>> np.zeros( (3,4) )
array([[ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.]])
>>> np.ones( (2,3,4), dtype=np.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)
>>> np.empty( (2,3) )                                 # uninitialized, output may vary
array([[  3.73603959e-262,   6.02658058e-154,   6.55490914e-260],
       [  5.30498948e-313,   3.14673309e-307,   1.00000000e+000]])

為了建立數字序列,numpy提供了類似Python中range的方法,arange返回的是陣列而不是列表。

>>> np.arange( 10, 30, 5 )    # 起始10,終止30,步長為5
array([10, 15, 20, 25])
>>> np.arange( 0, 2, 0.3 )
array([ 0. ,  0.3,  0.6,  0.9,  1.2,  1.5,  1.8])

當arange接收浮點引數時,由於有限的浮點精度,通常不能預測可獲得的元素數量。因此,更好的選擇是用linspace接收指定生成的元素數量作為引數,替代arange中的步長。

>>> from numpy import pi
>>> np.linspace( 0, 2, 9 )                 # 返回從0到2中間的9個數,包括2
array([ 0.  ,  0.25,  0.5 ,  0.75,  1.  ,  1.25,  1.5 ,  1.75,  2.  ])
>>> x = np.linspace( 0, 2*pi, 100 )        # 可以利用多個點評價函式
>>> f = np.sin(x)

列印陣列

當列印陣列時,numpy提供了一個類似Python巢狀列表的方法,但是遵守以下佈局:
* 最後的維度(軸)從左至右列印
* 倒數第二維從上至下列印
* 其他維也從上至下列印,每個切片下由一個空行分隔

>>> a = np.arange(6)                         # 1d 維
>>> print(a)
[0 1 2 3 4 5]
>>>
>>> b = np.arange(12).reshape(4,3)           # 2d 維
>>> print(b)
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
>>>
>>> c = np.arange(24).reshape(2,3,4)         # 3d 維
>>> 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]]]

reshape方法指定陣列的行列布局:

>>> print(np.arange(10000))
[   0    1    2 ..., 9997 9998 9999]
>>>
>>> print(np.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會自動忽略中間的元素以省略號表示,僅列印兩邊的元素。
如果想強制陣列列印所有元素,可以設定set_printoptions引數:
>>> np.set_printoptions(threshold=10000)

基本操作

陣列的算數運算適用於元素方式,即運算執行在每個元素上:

>>> a = np.array( [20,30,40,50] )
>>> b = np.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*np.sin(a)
array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
>>> a<35
>>> 
array([ True, True, False, False], dtype=bool)

不同於其他矩陣運算,在numpy陣列中,*乘法運算子操作與每個元素。而矩陣的乘積可以使用dot函式或dot方法執行:

>>> A = np.array( [[1,1],
...             [0,1]] )
>>> B = np.array( [[2,0],
...             [3,4]] )
>>> A*B                         # 元素相乘
array([[2, 0],
       [0, 4]])
>>> A.dot(B)                    # 矩陣乘積
array([[5, 4],
       [3, 4]])
>>> np.dot(A, B)                # 矩陣乘積
array([[5, 4],
       [3, 4]])

+=*=等操作符,在操作執行的地方替代陣列元素而不是產生一個新的陣列:

>>> a = np.ones((2,3), dtype=int)
>>> b = np.random.random((2,3))
>>> a *= 3
>>> a
array([[3, 3, 3],
       [3, 3, 3]])
>>> b += a
>>> b
array([[ 3.417022  ,  3.72032449,  3.00011437],
       [ 3.30233257,  3.14675589,  3.09233859]])
>>> a += b                  # b不能自動轉換為int型別
Traceback (most recent call last):
  ...
TypeError: Cannot cast ufunc add output from dtype('float64') to dtype('int64') with casting rule 'same_kind'

若運算的陣列時不同的資料型別,則遵循向上轉型,即結果陣列的元素型別為更一般或更精確的資料型別:

>>> a = np.ones(3, dtype=np.int32)
>>> b = np.linspace(0,pi,3)
>>> b.dtype.name
'float64'
>>> c = a+b
>>> c
array([ 1.        ,  2.57079633,  4.14159265])
>>> c.dtype.name
'float64'
>>> d = np.exp(c*1j)
>>> d
array([ 0.54030231+0.84147098j, -0.84147098+0.54030231j,
       -0.54030231-0.84147098j])
>>> d.dtype.name
'complex128'

ndarray類提供了許多一元操作方法,比如元素求和,求最小元素等:

>>> a = np.random.random((2,3))
>>> a
array([[ 0.18626021,  0.34556073,  0.39676747],
       [ 0.53881673,  0.41919451,  0.6852195 ]])
>>> a.sum()
2.5718191614547998
>>> a.min()
0.1862602113776709
>>> a.max()
0.6852195003967595

通常上述操作將陣列視為包含數字的列表,忽略其shape型別。進一步,通過制定軸引數axis ,你可以在指定的維度上操作這些運算:

>>> b = np.arange(12).reshape(3,4)
>>> b
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> b.sum(axis=0)                            # 每一列的求和
array([12, 15, 18, 21])
>>>
>>> b.min(axis=0)                            # 每列的最小值
array([0 1 2 3])
>>>
>>> b.min(axis=1)                            # 每行的最小值
array([0, 4, 8])
>>>
>>> b.cumsum(axis=1)                         # 沿著行累積求和
array([[ 0,  1,  3,  6],
       [ 4,  9, 15, 22],
       [ 8, 17, 27, 38]])

常用函式

numpy提供了我們常見的數學公式如:sin、cos、exp,稱為“universal functions”(ufunc)。這些函式作用在陣列元素上:

>>> B = np.arange(3)
>>> B
array([0, 1, 2])
>>> np.exp(B)
array([ 1.        ,  2.71828183,  7.3890561 ])
>>> np.sqrt(B)
array([ 0.        ,  1.        ,  1.41421356])
>>> C = np.array([2., -1., 4.])
>>> np.add(B, C)
array([ 2.,  0.,  6.])

其他函式還有:

all, any, apply_along_axis, argmax, argmin, argsort, average, bincount, ceil, clip, conj, corrcoef, cov, cross, cumprod, cumsum, diff, dot, floor, inner, inv, lexsort, max, maximum, mean, median, min, minimum, nonzero, outer, prod, re, round, sort, std, sum, trace, transpose, var, vdot, vectorize, where

索引、切片、迭代

一維陣列的這些操作和Python的list操作一樣:

>>> a = np.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    # 等於 a[0:6:2] = -1000; 表示從初始位置到第六個之前, 以2為步長將元素設為-1000
>>> a
array([-1000,     1, -1000,    27, -1000,   125,   216,   343,   512,   729])
>>> a[ : :-1]                                 # 翻轉 a的元素順序
array([  729,   512,   343,   216,   125, -1000,    27, -1000,     1, -1000])
>>> for i in a:
...     print(i**(1/3.))
...
0.0
1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
9.0

多維陣列可以每軸有一個索引,這些指數存在於一個元組中,以逗號分離:

>>> def f(x,y):
...     return 10*x+y
...
# fromfunction(函式,shape維度,元素型別),其中函式的引數個數由shape維度的秩決定決定,函式引數的值為陣列中元素的下標。比如shape為(2,2),函式引數有2個,則函式的引數值分別為:(0,0)、(0,1)、(1,0)、(1,1)。
>>> b = np.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]                       # 第二列的元素
array([ 1, 11, 21, 31, 41])
>>> b[ : ,1]                        # 第二列的元素
array([ 1, 11, 21, 31, 41])
>>> b[1:3, : ]                      # 第二行和第三行的元素
array([[10, 11, 12, 13],
       [20, 21, 22, 23]])
>>> b[-1]                                  # 最後一行的元素,等於b[-1,:]
array([40, 41, 42, 43])

b[i]被認為i後面有:所有例項需要去表示這個軸。numpy也允許以b[i,...]的形式表示。
dots (...)函式表示更具需要產生一個完整的索引元組表示儘可能多的冒號。例如,如果陣列x的秩為5,即有5個軸(維度),那麼:
* x[1,2,...]即等於x[1,2,:,:,:]
* x[...,3]即等於x[:,:,:,:,3]
* x[4,...,5,:]即等於x[4,:,:,5,:]

>>> c = np.array( [[[  0,  1,  2],    # (2,2,3)兩個堆疊的二維陣列
...                 [ 10, 12, 13]],
...                [[100,101,102],
...                 [110,112,113]]])
>>> c.shape
(2, 2, 3)
>>> c[1,...]                           #  c[1,:,:] 等於 c[1]
array([[100, 101, 102],
       [110, 112, 113]])
>>> c[...,2]                           # 等於 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

shape維數操作

改變陣列的shape形狀

一個數組的shape維數值等於其每一軸的元素的個數:

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

一個數組的shape維度可以被很多命令改變,例如:

>>> a.ravel() # 扁平化陣列,轉換為一維陣列
array([ 2.,  8.,  0.,  6.,  4.,  5.,  1.,  1.,  8.,  9.,  3.,  6.])
>>> a.shape = (6, 2)
>>> a.T    # 陣列轉置
array([[ 2.,  0.,  4.,  1.,  8.,  3.],
       [ 8.,  6.,  5.,  1.,  9.,  6.]])

ravel()方法後陣列元素的順序通常是“C-style”,也就是說,最右邊的索引值先變化,所以元素a[0,0]後緊跟的是a[0,1]。如果陣列被reshape為其他維度形狀,依然保持“C-style”。numpy通常將產生的陣列以這種順序儲存,所以ravel()方法不需要複製他的引數,但是如果陣列是由其他陣列的切片產生,或者有不尋常的選項建立,ravel()方法將會覆蓋掉陣列之前的值。函式ravel()reshape()也可以指定一個可選引數,使用FORTRAN-style陣列,即最左邊的索引最先變化。
reshape函式返回其引數指定的重塑的維度形狀,不改變陣列本身;然而ndarray.resize方法返回修改陣列本身:

>>> a
array([[ 2.,  8.],
       [ 0.,  6.],
       [ 4.,  5.],
       [ 1.,  1.],
       [ 8.,  9.],
       [ 3.,  6.]])
>>> a.reshape(2, 6)
array([[ 2.,  8.,  0.,  6.,  4.,  5.],
       [ 1.,  1.,  8.,  9.,  3.,  6.]])
>>> a    # 陣列a沒有改變
array([[ 2.,  8.],
       [ 0.,  6.],
       [ 4.,  5.],
       [ 1.,  1.],
       [ 8.,  9.],
       [ 3.,  6.]])
>>> a.resize((2,6))
>>> a    # 陣列a被改變
array([[ 2.,  8.,  0.,  6.,  4.,  5.],
       [ 1.,  1.,  8.,  9.,  3.,  6.]])

如果reshape中有維度引數為“-1”,則其維度的個數會自動計算:

>>> a.reshape(3,-1)
array([[ 2.,  8.,  0.,  6.],
       [ 4.,  5.,  1.,  1.],
       [ 8.,  9.,  3.,  6.]])

將不同的陣列疊加

陣列可以沿著不同的軸被疊加在一起

>>> a = np.floor(10*np.random.random((2,2)))
>>> a
array([[ 8.,  8.],
       [ 0.,  0.]])
>>> b = np.floor(10*np.random.random((2,2)))
>>> b
array([[ 1.,  8.],
       [ 0.,  4.]])
>>> np.vstack((a,b))    # 垂直按行疊加
array([[ 8.,  8.],
       [ 0.,  0.],
       [ 1.,  8.],
       [ 0.,  4.]])
>>> np.hstack((a,b))    # 平行按列疊加
array([[ 8.,  8.,  1.,  8.],
       [ 0.,  0.,  0.,  4.]])

函式column_stack等同於hstack方法:

>>> from numpy import newaxis
>>> np.column_stack((a,b))   # With 2D arrays
array([[ 8.,  8.,  1.,  8.],
       [ 0.,  0.,  0.,  4.]])
>>> a = np.array([4.,2.])
>>> b = np.array([2.,8.])
>>> a[:,newaxis]  # 產生一個二維列向量
array([[ 4.],
       [ 2.]])
>>> np.column_stack((a[:,newaxis],b[:,newaxis]))
array([[ 4.,  2.],
       [ 2.,  8.]])
>>> np.vstack((a[:,newaxis],b[:,newaxis]))
array([[ 4.],
       [ 2.],
       [ 2.],
       [ 8.]])

對於超過兩維的陣列,hstack沿著第二軸疊加,vstack沿著第一軸疊加,concatenate提供一個可選的軸數引數使沿著這個軸數疊加。
說明:
在複雜的情況下,r_c_對於建立沿著某一軸疊加的陣列很有用。允許使用範圍字元“:”。

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

當使用陣列作為引數時,r_c_方法和vstackhstack功能相似,但是前者可以指定引數是陣列沿著指定的軸數連結。r_c_詳讀:r_c_

將陣列劃分為幾個小陣列

使用hsplit方法可以將陣列沿著水平方向切分,返回制定shape維度的子陣列,或者在切分後製定列數:

>>> a = np.floor(10*np.random.random((2,12)))
>>> a
array([[ 9.,  5.,  6.,  3.,  6.,  8.,  0.,  7.,  9.,  7.,  2.,  7.],
       [ 1.,  4.,  9.,  2.,  2.,  1.,  0.,  6.,  2.,  2.,  4.,  0.]])
>>> np.hsplit(a,3)   # 分割3份
[array([[ 9.,  5.,  6.,  3.],
       [ 1.,  4.,  9.,  2.]]), array([[ 6.,  8.,  0.,  7.],
       [ 2.,  1.,  0.,  6.]]), array([[ 9.,  7.,  2.,  7.],
       [ 2.,  2.,  4.,  0.]])]
>>> np.hsplit(a,(3,4))   # 將第三列切分出來
[array([[ 9.,  5.,  6.],
       [ 1.,  4.,  9.]]), array([[ 3.],
       [ 2.]]), array([[ 6.,  8.,  0.,  7.,  9.,  7.,  2.,  7.],
       [ 2.,  1.,  0.,  6.,  2.,  2.,  4.,  0.]])]

vsplit方法沿著垂直方向切分,array_split可以指定沿著哪個軸切分。

複製和檢視

當運算元組時,有時候陣列會複製產生一個新的陣列有時候則不會,新手對此肯定會很困惑,一下有三個例項:

不復制

簡單的分配不復制物件及其資料:

>>> a = np.arange(12)
>>> b = a            # 不產生新的物件
>>> b is a           # a、b是相同的ndarray物件的兩個不同名稱
True
>>> b.shape = 3,4    # 改變b的維度也將改變a的維度
>>> a.shape
(3, 4)

Python傳遞可變物件作為引用,因此函式呼叫沒有複製新的物件:

>>> def f(x):
...     print(id(x))
...
>>> id(a)                           # id是一個物件的唯一標識
148293216
>>> f(a)
148293216

檢視或淺層拷貝

不同的陣列物件可以共享相同的資料,view方法產生一個有相同資料的陣列物件:

>>> c = a.view()
>>> c is a
False
>>> c.base is a                        # c是a的一個數據檢視
True
>>> c.flags.owndata
False
>>>
>>> c.shape = 2,6                      # a的shape維度沒有改變
>>> a.shape
(3, 4)
>>> c[0,4] = 1234                      # a的資料發生改變
>>> a
array([[   0,    1,    2,    3],
       [1234,    5,    6,    7],
       [   8,    9,   10,   11]])

陣列的切片也是陣列的一個檢視:

>>> s = a[ : , 1:3]     # 切片產生陣列a的第二、第三列,s是一個檢視
>>> s[:] = 10           # s[:]是s的檢視。注意s=10是將將s例項指向10,s[:]=10才是將切片陣列s中的每個元素都設為10
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

深層拷貝

方法copy實現陣列及其資料的複製。

>>> d = a.copy()              # 一個包含資料的新陣列物件被建立
>>> d is a
False
>>> d.base is a                  # 陣列d不共享陣列a的任何資料
False
>>> d[0,0] = 9999
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

函式、方法概覽

下面列出一下常用的NumPy函式或方法,全而細的方法和函式見Routines

建立陣列:
   arange, array, copy, empty, empty_like, eye, fromfile, fromfunction, identity, linspace, logspace, mgrid, ogrid, ones, ones_like, r, zeros, zeros_like
轉換:
  ndarray.astype, atleast_1d, atleast_2d, atleast_3d, mat
操作:
  array_split, column_stack, concatenate, diagonal, dsplit, dstack, hsplit, hstack, ndarray.item, newaxis, ravel, repeat, reshape, resize, squeeze, swapaxes, take, transpose, vsplit, vstack
Questions:
  all, any, nonzero, where
排序:
  argmax, argmin, argsort, max, min, ptp, searchsorted, sort
Operations:
  choose, compress, cumprod, cumsum, inner, ndarray.fill, imag, prod, put, putmask, real, sum
基本統計:
  cov, mean, std, var
基本線性代數:
  cross, dot, outer, linalg.svd, vdot