NumPy之一:基本操作

  NumPy的主要對象是同類多維數組,這是一個相同類型的元素(通常是數字)組成的表。在NumPy中,維度稱爲axis,axis的數量叫做rank

  例如,三維空間中的一個座標爲[1, 2, 1]的點,即爲rank=1的數組,因爲這個數組只有一個axis。有些人可能會有疑惑,不是說三維空間麼,爲什麼說它只有一個axis呢?axis可以理解成軸,拋開這個點,單看這個數組[1, 2, 1],它確實只需要一個axis即可表示。這個axis長度爲3。

  又如,[[ 1., 0., 0.],[ 0., 1., 2.]],這個數組的rank=2(2維的)。第一維(axis)長度爲2,第二維長度爲3。
  
  也可以這麼理解,數組是由2個行向量組成,所以第一維長度爲2,每個行向量由3個向量(點)組成,所以第二維長度爲3。

  Numpy的數組類稱爲ndarray。numpy.array和Python標準庫中的array.array是兩回事,標準庫中的array.array只能處理一維數組且只提供了少數的功能。ndarray對象中更加重要的特性如下:

  • ndarray.ndim
    數組的axis(維)的數量。在Python中,維的數量被稱作rank。

  • ndarray.shape
    數組的各個維(注意和維和維數要區分開)。它是一個數組各個維的長度構成的整數元組。對n行m列矩陣而言,shape將是(n,m)。因此,shape元組的長度也就是rank,也是維數ndim。

  • ndarray.size
    數組所有元素總數量。等於shpe元組各元素的乘積。

  • ndarray.dtype
    一個描述數組中元素類型的對象。用戶可以使用Python標準類型創建或指定dtype。此外,NumPy還提供了其自有的類型,比如numpy.int32, numpy.int16, numpy.float64。

  • ndarray.itemsize
    數組各元素的佔多少字節。比如,一個元素類型是float64的數組,其itemsize爲8(=64/8,64位除以8)。同理,元素類型是complex32的數組的itemsize爲4(=32/8)。ndarray.itemsize等於ndarray.dtype.itemsize。

  • ndarray.data
    裝載數組真實元素的緩衝區。通常,我們用不到這個屬性,因爲我們一般使用索引訪問數組元素。

1. 一個例子

>>> 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'>

2. 創建數組

  有多種方法來創建數組。
  你可以利用常規的Python列表或元組同夥array函數來創建一個數組。創建出的數組類型和序列元素類型是一致的。

>>> import numpy as np
>>> a = np.array([2,3,4])
>>> a
array([2, 3, 4])
>>> a.dtype
dtype('int64')
>>> b = np.array([1.2, 3.5, 5.1])
>>> b.dtype
dtype('float64')
>>> c = np.array(('a','b','c'))
>>> c
array(['a', 'b', 'c'],
      dtype='|S1')
>>> c.shape
(3,)
>>> c.size
3
>>> c.ndim
1
>>> c.dtype
dtype('S1')

  注意,array函數只接收一個參數,且這個參數是一個python序列。常見的錯誤是給array函數傳遞多個數字作爲參數,如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]])

  一般情況是,數組元素最初是未知的,但其size是已知的。因此,NumPy提供了一些函數,利用初始的佔位符來創建數組。這些函數減少了費時費力的手動填充數組的成本。

  函數zeros可以創建一個全部填充0的數組;函數ones創建的數組全部填充爲1;函數empty創建的函數的初始內容是隨機的,依賴於內存狀態。默認情況下,這些函數創建的數組的dtype是float64。

>>> a = np.zeros((3,4))
>>> a
array([[ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.]])
>>> a.dtype
dtype('float64')
>>> np.ones((2,3,4), dtype = np.int16)
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))
array([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])

  Numpy提供了一個類似於range的函數,可以返回一列數字,用以代替Python序列來創建數組。

>>> np.arange( 10, 30, 5 )
array([10, 15, 20, 25])
>>> np.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,其接收一個我們想要的元素數量作爲參數,而不是step:

>>> from numpy import pi
>>> np.linspace( 0, 2, 9 )   # 9 numbers from 0 to 2
array([ 0.  ,  0.25,  0.5 ,  0.75,  1.  ,  1.25,  1.5 ,  1.75,  2.  ])
>>> x = np.linspace( 0, 2*np.pi, 100 )  # useful to evaluate function at lots of points
>>> f = np.sin(x)

3. 打印數組

  打印數組時,NumPy通過類似嵌套列表的方式將數組展現出來,但會遵循以下層次佈局:

  • 最後的那個axis從左往右依次打印
    比如數組有4維,那麼從左到右打印的是第4維(第4個axis)

  • 倒數第二個axis從上到下打印

  • 其餘的axis也從上到下打印,每個切片由一個空行分開
      
    基於此,一維數組將打印成一行;二維數組將打印成一個矩陣;三維數組將打印成一個矩陣列表。

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

  如果不想省略中間部分,可以通過set_printoptions來強制NumPy打印所有數據。

>>> np.set_printoptions(threshold='nan')

4. 基本運算

  在數組上的算術運算作用於每個元素。運算結果將填充到一個新創建的數組中。

>>> 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函數或方法來實現:

>>> A = np.array( [[1,1],
...             [0,1]] )
>>> B = np.array( [[2,0],
...             [3,4]] )
>>> A*B                         # elementwise product
array([[2, 0],
       [0, 4]])
>>> A.dot(B)                    # matrix product
array([[5, 4],
       [3, 4]])
>>> np.dot(A, B)                # another matrix product
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 is not automatically converted to integer type
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,np.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

  默認情況下,這些應用於數組的運算表現的好像數組是一列數字一樣,並不考慮數組的形狀。但是,通過指定axis參數,可以將運算應用於指定的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)                            # 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]])
>>> b.cumsum(axis = 0)
array([[ 0,  1,  2,  3],
       [ 4,  6,  8, 10],
       [12, 15, 18, 21]])

5. 通用函數

  NumPy提供了常見的數學函數,如sin,cos,exp等。在NumPy中,這些函數稱爲“通用函數”(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.])

6. 索引、切片和迭代

  一維數組可以像Python中的列表等一樣被索引、切片和迭代。

>>> 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    # 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

  多維數組的每個axis都有一個索引。這些索引由一組逗號分隔的數字給出。

>>> def f(x,y):
...     return 10*x+y
...
>>> 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]                       # 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]])

  如果給出的索引少於axis的數量,那麼缺失的索引被認爲是整體切片:

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

  b[i]的括號中的表達式被解析成一個i後面跟着足夠多的冒號“:”,其足以代表其餘的axis。NumPy中還可以使用點“.”寫成b[i,…]的形式。

  (…)表示足夠的冒號來產生一個完整的索引元組。比如,如果x是一個5維數組(就是說它有5個axis),那麼

  • x[1,2,…] 等價於 x[1,2,:,:,:]
  • x[…,3] 等價於 x[:,:,:,:,3]
  • x[4,…,5,:] 等價於 x[4,:,:,5,:]
>>> c = np.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]])

  對多維數組迭代是就第一個axis而言的:

>>> 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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章