本文摘自《用Python做科學計算》,版權歸原作者所有。
1. NumPy-快速處理數據--ndarray對象--數組的創建和存取
2. NumPy-快速處理數據--ndarray對象--多維數組的存取、結構體數組存取、內存對齊、Numpy內存結構
接下來介紹ufunc運算、廣播、ufunc方法
ufunc是universal function的縮寫,它是一種能對數組的每個元素進行操作的函數。NumPy內置的許多ufunc函數都是在C語言級別實現的,因此它們的計算速度非常快。
1 >>> import numpy as np 2 >>> x = np.linspace(0, 2*np.pi, 10)#從0到2*PI的等距離的10個數 3 >>> x 4 array([ 0. , 0.6981317 , 1.3962634 , 2.0943951 , 2.7925268 , 5 3.4906585 , 4.1887902 , 4.88692191, 5.58505361, 6.28318531]) 6 >>> y = np.sin(x)#np.sin是一個ufunc函數,對x中的每個元素求正弦值,然后將結果返回,並且賦值給y 7 #注意:計算之后x中的值並沒有改變,而是新創建了一個數組y保存結果。 8 >>> y 9 array([ 0.00000000e+00, 6.42787610e-01, 9.84807753e-01, 10 8.66025404e-01, 3.42020143e-01, -3.42020143e-01, 11 -8.66025404e-01, -9.84807753e-01, -6.42787610e-01, 12 -2.44929360e-16]) 13 >>> t = np.sin(x, x) 14 #sin(x, x)第二個參數表示計算結果要覆蓋的數組,即對x中每個元素求sin,然后結果存放在x中。 15 >>> x 16 array([ 0.00000000e+00, 6.42787610e-01, 9.84807753e-01, 17 8.66025404e-01, 3.42020143e-01, -3.42020143e-01, 18 -8.66025404e-01, -9.84807753e-01, -6.42787610e-01, 19 -2.44929360e-16]) 20 >>> id(t) == id(x)#t和x id相同,表示共享一塊內存 21 True
計算sin有兩種途徑:numpy.sin和math.sin。應當注意的是,math.sin只支持對單個數值求正弦,而numpy.sin既支持單個數值也支持數組。在計算單個數值時,math.sin速度快,而在計算數組時numpy.sin運算速度快。因此在導入numpy時采用import numpy as np的方法,不建議使用*號全部載入,否則后載入的sin會覆蓋掉先載入的sin。在使用時根據情況選擇使用numpy.sin還是math.sin
此外,numpy.sin返回的數的類型和math.sin返回的類型有所不同,math.sin返回的是Python的標准float類型,而numpy.sin則返回一個numpy.float64類型
1 >>> type(math.sin(0.5))# math.sin返回的是Python的標准float類型 2 <type 'float'> 3 >>> type(np.sin(0.5)) # numpy.sin則返回一個numpy.float64類型 4 <type 'numpy.float64'>
add()函數:計算兩個數組相加
>>> a = np.arange(0, 4) >>> a array([0, 1, 2, 3]) >>> b = np.arange(1, 5) >>> b array([1, 2, 3, 4]) >>> np.add(a, b) #數組a + b帶返回結果 array([1, 3, 5, 7]) >>> np.add(a, b, a)#第三個參數用來存放計算結果 array([1, 3, 5, 7]) >>> a array([1, 3, 5, 7])
由於Python的操作符重載功能,計算兩個數組相加可以簡單地寫為a+b,而np.add(a,b,a)則可以用a+=b來表示。下面是數組的運算符和其對應的ufunc函數的一個列表,注意除號"/"的意義根據是否激活__future__.division有所不同。
y = x1 + x2 | add(x1, x2 [, y]) |
y = x1 - x2 | subtract(x1, x2 [, y]) |
y = x1 * x2 | multiply (x1, x2 [, y]) |
y = x1 / x2 | divide (x1, x2 [, y]), 如果兩個數組的元素為整數,那么用整數除法 |
y = x1 / x2 | true divide (x1, x2 [, y]), 總是返回精確的商 |
y = x1 // x2 | floor divide (x1, x2 [, y]), 總是對返回值取整 |
y = -x | negative(x [,y]) |
y = x1 ** x2 | power(x1, x2 [, y]) |
y = x1 % x2 | remainder(x1, x2 [, y]), mod(x1, x2, [, y]) |
數組對象支持這些操作符,極大地簡化了算式的編寫。
廣播
當我們使用ufunc函數對兩個數組進行計算時,ufunc函數會對這兩個數組的對應元素進行計算,因此它要求這兩個數組有相同的大小(shape相同)。如果兩個數組的shape不同的話,會進行如下的廣播(broadcasting)處理:
- 讓所有輸入數組都向其中shape最長的數組看齊,shape中不足的部分都通過在前面加1補齊
- 輸出數組的shape是輸入數組shape的各個軸上的最大值
- 如果輸入數組的某個軸和輸出數組的對應軸的長度相同或者其長度為1時,這個數組能夠用來計算,否則出錯
- 當輸入數組的某個軸的長度為1時,沿着此軸運算時都用此軸上的第一組值
1 >>> a = np.arange(0, 60, 10).reshape(-1, 1)#產生0~60,間距為10的數組,行(第0軸)為-1則根據列自動匹配 2 >>> a 3 array([[ 0], 4 [10], 5 [20], 6 [30], 7 [40], 8 [50]]) 9 >>> a.shape#a是二維數組 10 (6, 1) 11 >>> b = np.arange(0, 5) 12 >>> b 13 array([0, 1, 2, 3, 4]) 14 >>> b.shape#b是一維數組 15 (5,) 16 >>> c = a + b 17 #由於a和b的shape長度(也就是ndim屬性)不同,根據規則1,需要讓b的shape向a對齊,於是將b的shape補齊為(1,5)。 18 #兩個輸入數組的shape分別為(6,1)和(1,5),根據規則2,可知輸出數組的shape為(6,5)。 19 #規則3,當輸入數組的某個軸的長度為1時,沿着此軸運算時都用此軸上的第一組值。 20 >>> c 21 array([[ 0, 1, 2, 3, 4], 22 [10, 11, 12, 13, 14], 23 [20, 21, 22, 23, 24], 24 [30, 31, 32, 33, 34], 25 [40, 41, 42, 43, 44], 26 [50, 51, 52, 53, 54]]) 27 >>> c.shape 28 (6, 5)
由於這種廣播計算很常用,因此numpy提供了一個快速產生如上面a,b數組的方法: ogrid對象:
ogrid是一個很有趣的對象,它像一個多維數組一樣,用切片組元作為下標進行存取,返回的是一組可以用來廣播計算的數組。其切片下標有兩種形式:
-
開始值:結束值:步長,和np.arange(開始值, 結束值, 步長)類似
- 開始值:結束值:長度j,當第三個參數為虛數時,它表示返回的數組的長度,和np.linspace(開始值, 結束值, 長度)類似:
1 >>> x,y = np.ogrid[0:5,0:5] #開始值:結束值:步長 2 >>> x 3 array([[0], 4 [1], 5 [2], 6 [3], 7 [4]]) 8 >>> y 9 array([[0, 1, 2, 3, 4]]) 10 >>> x, y = np.ogrid[0:1:4j, 0:1:3j]#開始值:結束值:長度j 11 >>> x 12 array([[ 0. ], 13 [ 0.33333333], 14 [ 0.66666667], 15 [ 1. ]]) 16 >>> y 17 array([[ 0. , 0.5, 1. ]])
注意:ogrid不是函數!!!
根據Python的語法,只有在中括號中才能使用用冒號隔開的切片語法,如果ogrid是函數的話,那么這些切片必須使用slice函數創建,這顯然會增加代碼的長度。
ufunc的方法
ufunc函數本身還有些方法,這些方法只對兩個輸入一個輸出(如加法)的ufunc函數有效,其它的ufunc對象調用這些方法時會拋出ValueError異常。
reduce 方法和Python的reduce函數類似,語法為:
1 <op>.reduce (array=, axis=0, dtype=None) 2 #沿着axis軸對array進行操作,將<op>運算符插入到沿axis軸的所有子數組或者元素當中
1 >>> np.add.reduce([1,2,3]) # 1 + 2 + 3 2 6 3 >>> np.add.reduce([[1,2,3],[4,5,6]], axis=1) # 1,4 + 2,5 + 3,6 4 array([ 6, 15])
accumulate 方法和reduce方法類似,只是它返回的數組和輸入的數組的shape相同,保存所有的中間計算結果:
1 >>> np.add.accumulate([1,2,3]) 2 array([1, 3, 6]) 3 >>> np.add.accumulate([[1,2,3],[4,5,6]], axis=1) 4 array([[ 1, 3, 6], 5 [ 4, 9, 15]])
reduceat 方法計算多組reduce的結果,通過indices參數指定一系列reduce的起始和終了位置。
1 >>> a = np.array([1,2,3,4]) 2 >>> result = np.add.reduceat(a,indices=[0,1,0,2,0,3,0]) 3 >>> result 4 array([ 1, 2, 3, 3, 6, 4, 10])
outer 方法,<op>.outer(a,b)方法的計算等同於如下程序:
1 >>> np.multiply.outer([1,2,3,4,5],[2,3,4])#乘法表最終是通過廣播的方式計算出來的 2 array([[ 2, 3, 4], 3 [ 4, 6, 8], 4 [ 6, 9, 12], 5 [ 8, 12, 16], 6 [10, 15, 20]]) 7 #>>> np.multiply([1,2,3,4,5],[2,3,4])#ValueError