1. 程式人生 > >Numpy快速上手指南 --- 基礎篇

Numpy快速上手指南 --- 基礎篇

文章目錄

歡迎點選

原文連結,Fork到自己的工作區進行實現和除錯。

閱讀過本文的讀者可以點選這100道練習,帶你玩轉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
>>>'int64'

a.itemsize  
>>>8

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('int64')
b = array([1.2, 3.5, 5.1])

b.dtype   # 一個常見的錯誤包括用多個數值引數呼叫 array 而不是提供一個由數值組成的列表作為一個引數。
>>>dtype('float64')

正確建立方式與錯誤建立方式對比

a = array(1,2,3,4)            # 錯誤的建立方式
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)
<ipython-input-194-501541f17041> in <module>()
----> 1 a = array(1,2,3,4)    # WRONG

ValueError: only 2 non-keyword arguments accepted
a = array([1,2,3,4])          # 正確的建立方式

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

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([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])

為了建立一個數列,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來指定步長。

其它函式array, zeros, zeros_like, ones, ones_like, empty, empty_like, arange, linspace, rand, randn, fromfunction, fromfile請大家自己動手查詢官方文件。

列印陣列

當你列印一個數組,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]]]

檢視形狀操作一節獲得有關reshape的更多細節

如果一個數組用來列印太大了,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')

基本運算

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

from numpy import *
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.04610006,  3.25068416,  3.39458014],
       [ 3.34956036,  3.30907687,  3.05677917]])

錯誤的方式

a += b                                  # b is converted to integer type
a
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-219-2b1a0b731705> in <module>()
----> 1 a += b                                  # b is converted to integer type
      2 a

TypeError: Cannot cast ufunc add output from dtype('float64') to dtype('int64') with casting rule 'same_kind'

當運算的是不同型別的陣列時,結果陣列和更普遍和精確的已知,這種行為叫做 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          # 許多非陣列運算,如計算陣列所有元素之和,被作為ndarray類的方法實現
>>>'complex128'

a = random.random((2,3))
a
>>>array([[ 0.58120218,  0.65134515,  0.86005124],
       [ 0.47042799,  0.59048742,  0.84672098]])

a.sum() 
>>>4.0002349566329114

a.min() 
>>>0.47042798669229968

a.max() 
>>>0.86005123623089985

這些運算預設應用到陣列好像它就是一個數字組成的列表,無關陣列的形狀。然而,指定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.])

更多函式

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

索引,切片和迭代

一維陣列可以被索引、切片和迭代,就像列表和其它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; 
a                # from start to position 6, exclusive, set every 2nd element to -1000
>>>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.),end=",")
>>>[ 0.          2.15443469  2.15443469  1.44224957],[ 10.72601467   2.15443469   2.15443469   1.91293118],[ 2.          2.15443469  2.15443469  2.22398009],

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

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,end=",")   
>>>
0,1,2,3,10,11,12,13,20,21,22,23,30,31,32,33,40,41,42,43,

更多newaxis, ndenumerate, indices, index exp 參考Numpy官方文件。

形狀操作

更改陣列的形狀

一個數組的形狀由它每個軸上的元素個數給出:

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

a.shape 
>>>
(3, 4)

一個數組的形狀可以被多種命令修改:

a.ravel() # flatten the array
>>>array([ 6.,  7.,  4.,  3.,  0.,  0.,  2.,  6.,  7.,  7.,  8.,  6.])

a.shape = (6, 2)
a.transpose()
>>>array([[ 6.,  4.,  0.,  2.,  7.,  8.],
       [ 7.,  3.,  0.,  6.,  7.,  6.]])

由ravel()展平的陣列元素的順序通常是 C風格 的,就是說,最右邊的索引變化得最快,所以元素a[0,0]之後是a[0,1]。如果陣列被改變形狀(reshape)成其它形狀,陣列仍然是 C風格 的。

Numpy通常建立一個以這個順序儲存資料的陣列,所以ravel()將總是不需要複製它的引數。但是如果陣列是通過切片其它陣列或有不同尋常的選項時,它可能需要被複制。函式reshape()和ravel()還可以被同過一些可選引數構建成 FORTRAN風格 的陣列,即最左邊的索引變化最快。

reshape函式改變引數形狀並返回它,而resize函式改變陣列自身。

a
>>>
array([[ 6.,  7.],
       [ 4.,  3.],
       [ 0.,  0.],
       [ 2.,  6.],
       [ 7.,  7.],
       [ 8.,  6.]])

a.resize((2,6))
a
>>>
array([[ 6.,  7.,  4.,  3.,  0.,  0.],
       [ 2.,  6.,  7.,  7.,  8.,  6.]])

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

更多 shape, reshape, resize, ravel 參考Numpy官方文件

組合(stack)不同的陣列

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

a = floor(10*random.random((2,2)))
a
>>>
array([[ 1.,  5.],
       [ 6.,  7.]])

b = floor(10*random.random((2,2)))
b
>>>
array([[ 5.,  5.],
       [ 4.,  5.]])

vstack((a,b))
>>>
array([[ 1.,  5.],
       [ 6.,  7.],
       [ 5.,  5.],
       [ 4.,  5.]])

hstack((a,b))
>>>
array([[ 1.,  5.,  5.,  5.],
       [ 6.,  7.,  4.,  5.]])

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

column_stack((a,b))   # With 2D arrays
>>>
array([[ 1.,  5.,  5.,  5.],
       [ 6.,  7.,  4.,  5.]])

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很像,但是允許可選的引數給出組合所沿著的軸的代號。

更多函式hstack, vstack, column_stack, row_stack, concatenate, c_, r_ 參見Numpy示例.

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

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

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

hsplit(a,3)   # Split a into 3
>>>
[array([[ 3.,  7.,  0.,  6.],
        [ 7.,  7.,  2.,  3.]]), array([[ 0.,  6.,  8.,  5.],
        [ 4.,  8.,  8.,  5.]]), array([[ 8.,  2.,  7.,  1.],
        [ 2.,  5.,  5.,  4.]])]

hsplit(a,(3,4))   # Split a after the third and the fourth column
>>>
[array([[ 3.,  7.,  0.],
        [ 7.,  7.,  2.]]), array([[ 6.],
        [ 3.]]), array([[ 0.,  6.,  8.,  5.,  8.,  2.,  7.,  1.],
        [ 4.,  8.,  8.,  5.,  2.,  5.,  5.,  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
>>>4538177536

f(a)
>>>4538177536

檢視(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 changesa
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總覽

這是個Numpy函式和方法分類排列目錄。這些名字連結到Numpy示例,你可以看到這些函式起作用

建立陣列

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

上述文中所有程式碼部分都可以使用線上資料分析協作工具K-Lab復現,點選連結一鍵直達~

轉載本文請聯絡 科賽網知乎文章原作者 取得授權。

K-Lab提供基於Jupyter Notebook的線上資料分析服務,涵蓋Python&R等主流程式語言,可滿足資料科學家、人工智慧工程師、商業分析師等資料工作者線上完成資料處理、模型搭建、程式碼除錯、撰寫分析報告等資料分析全過程。