無燈可看。雨水從教正月半。探繭推盤。探得千秋字字看。
銅駝故老。說著宣和似天寶。五百年前。曾向杭州看上元。
ufunc是universal function的縮寫,他是一種對數組的每個元素進行運算的函數。NumPy的內置許多函數都是用C語言實現的因此,他們的計算速度十分的快。
>>> x = np.linspace(0,2*np.pi,10) >>> x array([ 0. , 0.6981317 , 1.3962634 , 2.0943951 , 2.7925268 , 3.4906585 , 4.1887902 , 4.88692191, 5.58505361, 6.28318531]) >>> y = np.sin(x) >>> y array([ 0.00000000e+00, 6.42787610e-01, 9.84807753e-01, 8.66025404e-01, 3.42020143e-01, -3.42020143e-01, -8.66025404e-01, -9.84807753e-01, -6.42787610e-01, -2.44929360e-16])
可以查看函數的使用方法,得到使用方法。
下面我們比較一下np.sin()和math.sin()的時間復雜度。
#coding:utf-8 import math import datetime import numpy as np x = np.array([i* 0.001 for i in range(3000000)]) def sin_math(x): beg = datetime.datetime.now() for i,t in enumerate(x): # 返回下標和元素。 x[i] = math.sin(t) end = datetime.datetime.now() return end-beg def sin_np(x): beg = datetime.datetime.now() np.sin(x,x) end = datetime.datetime.now() return end-beg print(sin_math(x),sin_np(x))
最后得到的結構大概 np.sin()的速度比math.sin()快5倍。這得益於np.sin()的C語言級別的計算。
實際上標准的Python中可以用列表推導式的方法得到比for循環更快的計算效果。x = [math.sin(t) for t in x] ,但是列表推導式將產生一個新的列表,而不是修改原來的列表,相當於用 空間換了時間。
四則運算
NumPy提供了許多的ufunc函數,例如計算兩個數組之和的add()函數。
>>> a+b array([ 5, 7, 9, 11]) >>> a = np.arange(0,4) >>> b = np.arange(5,9) >>> a+b array([ 5, 7, 9, 11]) >>> np.add(a,b) array([ 5, 7, 9, 11])
add()將返回一個數組,這個同樣可以指定out參數,如果沒有指定則創建一個新的數組來保存計算結果。
>>> np.add(a,b,a) array([ 5, 7, 9, 11]) >>> a array([ 5, 7, 9, 11])
NumPy位數組定義了各種數學運算操作符,因此計算兩個數組想家可以簡單的寫成a+b而,而np.add(a,b,a)則可以用a += b來表示。下標列出了數組的運算符,以及與之對應的ufunc
數組對象支持操作符,極大地簡化了算式的編寫,但是需要注意,如果算式很復雜,要產生大量的中間結果這樣就會降低程序的運算速度,例如堆a,b,c,三個數組采用算法進行 x = a * b + c 它相當於
t = a * b x = t + c del t
我們可以通過分解算式來將上述的一句話,變成兩句話,且減少一次內存分配。
x = a * b
x += c
使用比較運算符堆兩個數組進行運算的時候,將返回一個Bool 數組, 他的每個元素值都是兩個數組對應元素的比較結果 eg:
>>> np.array([1,2,3,4]) < np.array([4,3,2,1])
array([ True, True, False, False], dtype=bool)
每個比較運算符都有一個ufunc 函數對應, 表2-2是比較運算符與ufunc 函數的對照表。
因為Python中的布爾運算使用,and,or和not等關鍵字,他們無法被重載,因此數組的布爾運算只能通過相應的ufunc函數進行,這些函數都以logical_開頭,可以通過自動補齊來找到相應函數。
>>> a = np.arange(5) >>> a array([0, 1, 2, 3, 4]) >>> b = np.arange(4,-1,-1) >>> b array([4, 3, 2, 1, 0]) >>> print (a == b) [False False True False False] >>> print(a>b) [False False False True True] >>> print(np.logical_and(a,b)) [False True True True False] >>> print(a == b,a>b) [False False True False False] [False False False True True]
自定義ufunc函數
通過NumPy提供的標准ufunc函數,可以組合出復雜的表達式,在C語言級別堆數組的每個元素進行計算。但是這種表達式不易編寫,而對元素進行計算的程序卻很容易用Python實現,這時候可以通過frompyfunc()將計算單個元素的函數轉換成ufunc函數,這樣就可以很方便的用所產生的ufunc函數對數組進行計算了。
例如我們可以通過一個分段函數描述三角波,三角波的形狀如圖所示,它分為三段:上升段,下降段和平坦段。
根據圖2-5,我們可以很容易的寫出計算三角波上某點的Y坐標函數,顯然triangle_wave()只能計算單個數值,不能直接堆數組進行處理。
import numpy as np import matplotlib.pyplot as plt def triangle_wave(x,c,c0,hc): x = x - int(x) #三角波的周期位1,因此只取x坐標的小數部分進行計算。 if x > c: r = 0.0 elif x < c0: r = x / c0 * hc else: r = (c - x)/(c - c0) * hc return r x = np.linspace(0,2,1000) y1 = np.array([triangle_wave(t,0.6,0.4,1.0) for t in x]) plt.plot(x,y1) plt.show()
通過frompyfunc()可以將計算單個值的函數轉換為能對數組的每個元素進行計算的ufunc函數。frompyfunc的調用格式為:
frompyfunc(func,nin,nout)
其中func是計算單個元素的函數,nin是func的輸入參數的個數,nout是func的返回值的個數。下面的程序使用frompyfunc()將triangle_wave()轉換為ufunc函數對象triangle_ufunc1:
# -*- coding: utf-8 -*- """ Created on Tue Mar 14 13:24:50 2017 @author: x-power """ import numpy as np import matplotlib.pyplot as plt def triangle_wave(x,c,c0,hc): x = x - int(x) #三角波的周期位1,因此只取x坐標的小數部分進行計算。 if x > c: r = 0.0 elif x < c0: r = x / c0 * hc else: r = (c - x)/(c - c0) * hc return r x = np.linspace(0,2,1000) y1 = np.array([triangle_wave(t,0.6,0.4,1.0) for t in x]) triangle_ufunc = np.frompyfunc(triangle_wave,4,1) y2 = triangle_ufunc(x,0.6,0.3,1) print("y1的類型是:" + str(y1.dtype)) print("y2的類型是:" + str(y2.dtype)) y2 = y2.astype(np.float) print("轉換之后的y2類型是:" + str(y2.dtype)) print("如果使用np.frompyfunc的話,返回值的類型是object很尷尬,如果需要方便的使用的話,需要轉換類型。") #plt.plot(x,y1) #plt.plot(x,y2) # #plt.show()
廣播
當使用ufunc函數堆兩個數組進行計算的時候,ufunc函數會對這兩個數組的對應元素進行計算,因此其要求兩個數組的shape相同,不同的話會進行廣播處理( broadcasting )處理:
1)讓所有的輸入數組都向其中維數最多的數組看齊,shape屬性中不足的部分都通過向前面加1 的當時補齊。
2)輸出的數組的shape屬性是輸入數組的shape屬性的各個軸上的最大值。
3)如果輸入數組的某個軸長度為 1 時,沿着此軸運算時都用此軸上的第一組值。
talk is cheap , show me your code !
import numpy as np import matplotlib.pyplot as plt a = np.arange(0,60,10).reshape(-1,1) b = np.arange(0,5) c = a + b print("a的形狀是:" + str(a.shape)) print(a) print("b的形狀是:" + str(b.shape)) print(a) print("c的形狀是:" + str(c.shape)) print(c)
a的形狀是:(6, 1) [[ 0] [10] [20] [30] [40] [50]] b的形狀是:(5,) [[ 0] [10] [20] [30] [40] [50]] c的形狀是:(6, 5) [[ 0 1 2 3 4] [10 11 12 13 14] [20 21 22 23 24] [30 31 32 33 34] [40 41 42 43 44] [50 51 52 53 54]]
由於a和b的維數不同,根據規則1),需要讓b的shape屬性向a 對齊,於是在b的 shape屬性前 加 1,補齊位(1,5).相當於做了如下計算:
b.shape = 1,5
這樣加法運算的兩個輸入數組的shape屬性分別為(6,1)和(1,5),根據規則2)輸出數組的各個軸的長度位輸入數組的各個軸長度的最大值,可知輸出數組的shape屬性為(6,5)。
由於b的第0軸長度為1,而a的第0軸長度為6,為了讓他們在第0軸上能夠相加,需要將b的第0軸的長度擴展為6,這相當於:
b Out[16]: array([[0, 1, 2, 3, 4]]) b = b.repeat(6,0) b Out[18]: array([[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]])
這里的repeat()方法沿着axis參數指定的軸復制數組中的各個元素的值。由於a的第 1 軸的長度為 1 而b的第1軸的長度為5,為了能夠讓他們相加,需要將a的第一軸的長度擴展為5 這相當於:
a Out[19]: array([[ 0], [10], [20], [30], [40], [50]]) a = a.repeat(5,1) a Out[21]: array([[ 0, 0, 0, 0, 0], [10, 10, 10, 10, 10], [20, 20, 20, 20, 20], [30, 30, 30, 30, 30], [40, 40, 40, 40, 40], [50, 50, 50, 50, 50]])
經過上述處理就可以讓 a 和 b進行相加計算了。可是這樣不是很浪費內存么?由於這種廣播計算使用頻率比較高,所以一定是要有解決辦法的。因此NumPy提供了ogrid對象,用於創建廣播運算用的數組。
x,y = np.ogrid[:5,:5] print(x) print(y)
[[0] [1] [2] [3] [4]] [[0 1 2 3 4]]
此外,NumPy還提供了mgrid對象,它的用法和ogrid對象類似,但是它所返回的是進行廣播之后的數組:
x,y = np.mgrid[:5,:5] print(x) print(y)
[[0 0 0 0 0] [1 1 1 1 1] [2 2 2 2 2] [3 3 3 3 3] [4 4 4 4 4]] [[0 1 2 3 4] [0 1 2 3 4] [0 1 2 3 4] [0 1 2 3 4] [0 1 2 3 4]]
ogrid是一個很有趣的對象,它像是多維數組一樣,用切片元祖作為下標,返回的是一組可以用來廣播計算的數組,其切片下標有兩種形式:
- 開始值:結束值:步長,和np.arange(開始,結束,步長)類似
- 開始值:結束值:長度j,當第三個參數為虛數的時候,它表示所返回數組的長度和np.linspace(開始值,結束值,長度)類似。
x = np.arange(0,1,0.1) y = np.linspace(0,1,10) print(x) print(y)
x,y = np.ogrid[:1:4j,:1:3j] print(x) print(y)
x,y = np.ogrid[-2:2:20j,-2:2:20j]
z = x + np.exp(-x**2 - y**2)
下圖則位使用ogrid()函數,計算二元函數在等間隔網格上的值,下面是繪制三維曲面(x,y)= xex^2-y^2的圖形。
為了充分利用ufunc的廣播功能,我們經常需要調整數組的形狀,因此數組支持特殊的下標對象None,它表示在None對應的位置創建一個長度為1的新軸。例如堆一維數組a,a[None,:]和a.reshape(1,-1)等效,而a[:,None]和a.shape(-1,1)等效:
a = np.arange(4) print(a[:,None]) print("\n\n\n") print(a[None,:])
runfile('/home/x-power/untitled0.py', wdir='/home/x-power') [[0] [1] [2] [3]] [[0 1 2 3]]
下面的例子以None作為下標,實現廣播運算。
x = np.array([0,1,4,10]) y = np.array([2,3,8]) print(x[None,:]) print(y[:,None]) print(x[None,:]+y[:,None])
[[ 0 1 4 10]] [[2] [3] [8]] [[ 2 3 6 12] [ 3 4 7 13] [ 8 9 12 18]]
真正的英雄都是在最后力挽狂瀾的,這句話說的真他么的對,狗日的。
x = np.array([0,1,4,10]) y = np.array([2,3,8]) gy,gx = np.ix_(y,x) print(gy + gx)
[[ 2 3 6 12] [ 3 4 7 13] [ 8 9 12 18]]
在上面的例子中,通過ix_()將數組x和y轉換成能進行廣播運算的二維數組,注意數組y對應廣播運算結果中的0軸,而數組x與第1軸相對應。