第四章 Numpy基礎:數組和矢量計算
第一部分:numpy的ndarray:一種多維數組對象
實話說,用numpy的主要目的在於應用矢量化運算。Numpy並沒有多么高級的數據分析功能,理解Numpy和面向數組的計算能有助於理解后面的pandas.按照課本的說法,作者關心的功能主要集中於:
-
用於數據整理和清理、子集構造和過濾、轉換等快速的矢量化運算
-
常用的數組解法,如排序、唯一化、集合運算等
-
高效的描述統計和數據聚合/摘要運算
-
用於異構數據集的合並/連接運算的數據對齊和關系型數據運算
-
將條件邏輯表述為數組表達式(而不是帶有if-elif-else分支的循環)
-
數據的分組運算(聚合、轉換、函數應用等)。
作者說了,可能還是pandas更好一些,我感覺顯然pandas更高級,其中的函數真是太方便了,數據框才是最好的數據結構。只是,Numpy中的函數之類的是基礎,需要熟悉。
NumPy的ndarray:一種多維數組對象
ndarray對象是NumPy最重要的對象,特點是矢量化。ndarray每個元素的數據類型必須相同,每個數組有兩個屬性:shape和dtype.
#-*- encoding:utf-8 -*- import numpy as np import pandas as pd import matplotlib.pyplot as plt data = [[1,2,5.6],[21,4,2]] data = np.array(data) print data.shape print data.dtype print data.ndim
>>>
(2, 3)
float64
2
array函數接受一切序列型的對象(包括其他數組),然后產生新的含有傳入數據的NumPy數組,array會自動推斷出一個合適的數據類型。還有一個方法是ndim:這個翻譯過來叫維度,標明數據的維度。上面的例子是兩維的。zeros和ones可以創建指定長度或形狀全為0或1的數組。empty可以創建一個沒有任何具體值的數組,arange函數是python內置函數range的數組版本。
#-*- encoding:utf-8 -*- import numpy as np import pandas as pd import matplotlib.pyplot as plt data = [[1,2,5.6],[21,4,2],[2,5,3]] data1 = [[2,3,4],[5,6,7,3]] data = np.array(data) data1 = np.array(data1) arr1 = np.zeros(10) arr2 = np.ones((2,3)) arr3 = np.empty((2,3,4)) print arr1 print arr2 print arr3 print arr3.ndim
>>>
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[[ 1. 1. 1.]
[ 1. 1. 1.]]
[[[ 3.83889007e-321 0.00000000e+000 0.00000000e+000 0.00000000e+000]
[ 0.00000000e+000 0.00000000e+000 0.00000000e+000 0.00000000e+000]
[ 0.00000000e+000 0.00000000e+000 0.00000000e+000 0.00000000e+000]]
[[ 0.00000000e+000 0.00000000e+000 0.00000000e+000 0.00000000e+000]
[ 0.00000000e+000 0.00000000e+000 0.00000000e+000 0.00000000e+000]
[ 0.00000000e+000 0.00000000e+000 0.00000000e+000 0.00000000e+000]]]
3
上面是常用的生成數組的函數。
ndarray的數據類型
dtype(數據類型)是一個特殊的對象。它含有ndarray將一塊內存解釋為指定數據類型所需的信息。他是NumPy如此靈活和強大的原因之一。多數情況下,它們直接映射到相應的機器表示,這使得“讀寫磁盤上的二進制數據流”以及“集成低級語言代碼(C\Fortran)”等工作變得更加簡單。dtype命名方式為,類型名+表示元素位長的數字。標准雙精度浮點型數據需要占用8字節(64位)。記作float64.常見的數據類型為:
我終於找到了f4,f8的含義了……布爾型數據的代碼倒是很有個性。函數astype可以強制轉換數據類型。
#-*- encoding:utf-8 -*- import numpy as np import pandas as pd import matplotlib.pyplot as plt arr = np.array([1,2,3,4,5]) print arr.dtype float_arr = arr.astype(np.float64) print float_arr.dtype arr1 = np.array([2.3,4.2,32.3,4.5]) #浮點型會被整型截斷 print arr1.astype(np.int32) #一個全是數字的字符串也可以轉換為數值類型 arr2 = np.array(['2323.2','23']) print arr2.astype(float) #數組的dtype還有一個用法 int_array = np.arange(10) calibers = np.array([.22,.270,.357,.44,.50],dtype = np.float64) print int_array.astype(calibers.dtype) print np.empty(10,'u4')
調用astype總會創建一個新的數組(原始數組的一個拷貝),即使和原來的數據類型相同。警告:浮點數只能表示近似數,比較小數的時候要注意。
數組與標量之間的運算
矢量化(vectorization)是數組最重要的特點了。可以避免(顯示)循環。注意加減乘除的向量化運算。不同大小的數組之間的運算叫廣播(broadcasting)。
索引和切片,不再贅述,注意的是 廣播的存在使得數組即使只賦一個值也會被廣播到所有數組元素上,其實和R語言中自動補齊功能相同。下面的性質有點蛋疼:跟列表最重要的區別在於,數組切片是原始數組的視圖,對視圖的任何修改都會反映到源數據上。即使是下面的情況:
#-*- encoding:utf-8 -*- import numpy as np import pandas as pd import matplotlib.pyplot as plt arr = np.array([1,2,3,4,5,6,7,8,9]) arr1 = arr[1:2] arr1[0] = 10 print arr #如果想得到拷貝,需要顯示地復制一份 arr2 = arr[3:4].copy() arr2[0] = 10 print arr arr2d = np.array([[1,2,3],[4,5,6],[7,8,9]]) #下面兩種索引方式等價 print arr2d[0][2] print arr2d[0,2] print arr2d[:,1] #注意這里的方式和下面的方式 print arr2d[:,:1] arr3d = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[[10,11,12]]]]) print arr3d[(1,0)]
>>>
[ 1 10 3 4 5 6 7 8 9]
[ 1 10 3 4 5 6 7 8 9]
3
3
[2 5 8] #注意這里的方式和下面的方式
[[1]
[4]
[7]]
[7, 8, 9]
布爾型索引
這里的布爾型索引就是TRUE or FALSE索引。==、!=、-(表示否定)、&(並且)、|(或者)。注意布爾型索引選取數組中的數據,將創建數據的副本。python關鍵字and、or無效。
花式索引(Fancy indexing)
花式索引指的是利用整數數組進行索引。
#-*- encoding:utf-8 -*- import numpy as np import pandas as pd import matplotlib.pyplot as plt arr = np.arange(32).reshape(8,4) print arr #注意這里的向量式方式 print arr[[1,5,7,2],[0,3,1,2]] print arr[[1,5,7,2]][:,[0,3,1,2]] #也可以使用np.ix_函數,將兩個一維整數數組組成選取方形區域的索引器 print arr[np.ix_([1,5,7,2],[0,3,1,2])] >>> [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11] [12 13 14 15] [16 17 18 19] [20 21 22 23] [24 25 26 27] [28 29 30 31]] [ 4 23 29 10] [[ 4 7 5 6] [20 23 21 22] [28 31 29 30] [ 8 11 9 10]] [[ 4 7 5 6] [20 23 21 22] [28 31 29 30] [ 8 11 9 10]]
花式索引總是將數據復制到新數組中,跟切片不同,一定要注意下面的區別:
#-*- encoding:utf-8 -*- import numpy as np import pandas as pd import matplotlib.pyplot as plt arr = np.arange(32).reshape(8,4) arr1 = np.arange(32).reshape(8,4) #注意下面得到的結果是一樣的 arr3 = arr[[1,2,3]][:,[0,1,2,3]] arr3_1 = arr1[1:4][:] #注意下面是區別了 arr3[0,1] = 100 #花式索引得到的是復制品,重新賦值以后arr不變化 arr3_1[0,1] = 100 #切片方式得到的是一個視圖,重新賦值后arr1會變化 print arr3 print arr3_1 print arr print arr1 >>> [[ 4 100 6 7] [ 8 9 10 11] [ 12 13 14 15]] [[ 4 100 6 7] [ 8 9 10 11] [ 12 13 14 15]] [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11] [12 13 14 15] [16 17 18 19] [20 21 22 23] [24 25 26 27] [28 29 30 31]] [[ 0 1 2 3] [ 4 100 6 7] [ 8 9 10 11] [ 12 13 14 15] [ 16 17 18 19] [ 20 21 22 23] [ 24 25 26 27] [ 28 29 30 31]]
數組轉置和軸轉換
轉置transpose,是一種對源數據的視圖,不會進行復制。調用T就可以。np中的矩陣乘積函數為np.dot。
比較復雜的是高維數組:
#-*- encoding:utf-8 -*- import numpy as np import pandas as pd import matplotlib.pyplot as plt arr = np.arange(24).reshape((2,3,4)) #下面解釋一下transpose: #(1,0,2) 是將reshape中的參數 (2,3,4) 進行變化 ,變為(3,2,4) #但是由於是轉置,所以是將所有元素的下標都進行了上述變化,比如 12這個元素,原來索引為 (1,0,0) ,現在為 (0,1,0) arr1 = arr.transpose((1,0,2)) arr2 = arr.T #直接用T是變為了(4,3,2)的形式 #arr3 = np.arange(120).reshape((2,3,4,5)) #arr4 = arr3.T #直接用T就是將形式變為 (5,4,3,2) #ndarray還有swapaxes方法,接受一對軸編號 arr5 = arr.swapaxes(1,2) #print arr #print arr1 #print arr2 #print arr3 #print arr4 print arr5 >>> [[[ 0 4 8] [ 1 5 9] [ 2 6 10] [ 3 7 11]] [[12 16 20] [13 17 21] [14 18 22] [15 19 23]]]
第二部分是關於一些元素級函數:即作用於數組每個元素上的函數,用過R語言之后就覺得其實沒什么了。
下面是一些常見的矢量化函數(姑且這么叫吧)。
下面是幾個例子:
#-*- encoding:utf-8 -*- import numpy as np import numpy.random as npr import pandas as pd #接收兩個數組的函數,對應值取最大值 x = npr.randn(8) y = npr.randn(8) #注意不是max函數 z = np.maximum(x,y) print x,y,z #雖然並不常見,但是一些ufunc函數的確可以返回多個數組。modf函數就是一例,用來分隔小數的整數部分和小數部分,是python中divmod的矢量化版本 arr = npr.randn(8) print np.modf(arr) #ceil函數取天花板,不小於這個數的最小整數 print np.ceil(arr) #concatenate函數是將兩個numpy數組連接,注意要組成元組方式再連接 #arr = np.concatenate((arr,np.array([0,0]))) #logical_not函數, 非 函數 #print np.logical_not(arr) print np.greater(x,y) print np.multiply(x,y)
第三部分:利用數組進行數據處理
作者說矢量化數組運算比純pyhton方式快1-2個數量級(or more),又一次強調了broadcasting作用很強大。
#-*- encoding:utf-8 -*- import numpy as np import pandas as pd import matplotlib.pyplot as plt #假設想在一個二維網格上計算一個 sqrt(x^2 + y^2) #生成-5到5的網格,間隔0.01 points = np.arange(-5,5,0.01) #meshgrid返回兩個二維矩陣,描述出所有(-5,5)* (-5,5)的點對 xs,ys = np.meshgrid(points,points) z = np.sqrt(xs ** 2 + ys ** 2) #print xs #print ys #不做個圖都對不起觀眾 #imshow函數,展示z是一個矩陣,cmap就是colormap,用的時候值得研究 plt.imshow(z,cmap=plt.cm.gray) plt.colorbar() plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of values") plt.show()
上面的畫圖語句在用的時候還需要好好研究一下。
下面的一個例子是np.where函數,簡潔版本的if-else。
''' #np.where函數通常用於利用已有的數組生產新的數組 arr = npr.randn(4,4) #正值賦成2,負值為-2 print np.where(arr > 0,2,-2) #注意這里的用法 print np.where(arr > 0,2,arr) #可以用where表示更為復雜的邏輯表達 #兩個布爾型數組cond1和cond2,4種不同的組合賦值不同 #注意:按照課本上的說法,下面的語句是從左向右運算的,不是從做內層括號計算起的;這貌似與python的語法不符 np.where(cond1 & cond2,0,np.where(cond1,1,np.where(cond2,2,3))) #不過感覺沒有更好的寫法了。 #書上“投機取巧”的式子,前提是True = 1,False = 0 result = 1 * (cond1 - cond2) + 2 * (cond2 & -cond1) + 3 * -(cond1 | cond2) '''
#-*- encoding:utf-8 -*- import numpy as np import pandas as pd import matplotlib.pyplot as plt import numpy.random as npr #值得注意的是,mean、sum這樣的函數,會有一個參數axis表示對哪個維度求值 arr = np.array([[0,1,2],[3,4,5],[6,7,8]]) #cumsum不是聚合函數,維度不會減少 print arr.cumsum(0)
下面是常用的數學函數:
用於布爾型數組的方法
sum經常用於True的加和;any和all分別判斷是否存在和是否全部為True。
排序及唯一化
#-*- encoding:utf-8 -*- import numpy as np import pandas as pd import matplotlib.pyplot as plt import numpy.random as npr #sort函數是就地排序 arr = npr.randn(10) print arr arr.sort() print arr #多維數組可以按照維度排序,把軸編號傳遞給sort即可 arr = npr.randn(5,3) print arr #sort傳入1,就是把第1軸排好序,即按列 arr.sort(1) print arr #np.sort返回的是排序副本,不是就地排序 #輸出5%分位數 arr_npr = npr.randn(1000) arr_npr.sort() print arr_npr[int(0.05 * len(arr_npr))] #pandas中有更多排序、分位數之類的函數,直接可以取分位數的,第二章的例子中就有 #numpy中有unique函數,唯一化函數,R語言中也有 names = np.array(['Bob','Joe','Will','Bob','Will']) print sorted(set(names)) print np.unique(names) values = np.array([6,0,0,3,2,5,6]) #in1d函數用來查看一個數組中的元素是否在另一個數組中,名字挺好玩,注意返回的長度與第一個數組相同 print np.in1d(values,[6,2,3])
下面是常用集合運算
用於數組的文件輸入輸出
NumPy能夠讀寫磁盤上的文本數據或二進制數據。后面的章節將會給出一些pandas中用於將表格型數據讀取到內存的工具。
np.save 和 np.load是讀寫磁盤數據的兩個主要函數。默認情況下,數組是以未壓縮的原始二進制文件格式保存在擴展名為.npy的文件中。
#-*- encoding:utf-8 -*- import numpy as np import pandas as pd import matplotlib.pyplot as plt import numpy.random as npr ''' arr = np.arange(10) np.save('some_array',arr) np.savez('array_archive.npz',a = arr,b = arr) arr1 = np.load('some_array.npy') arch = np.load('array_archive.npz') print arr1 print arch['a'] ''' #下面是存取文本文件,pandas中的read_csv和read_table是最好的了 #有時需要用np.loadtxt或者np.genfromtxt將數據加載到普通的NumPy數組中 #這些函數有許多選項使用:指定各種分隔符,針對特定列的轉換器函數,需要跳過的行數等 #np.savetxt執行的是相反的操作:將數組寫到以某種分隔符隔開的文本文件中 #genfromtxt跟loadtxt差不多,只不過它面向的是結構化數組和缺失數據處理
線性代數
關於線性代數的一些函數,NumPy的linalg中有很多關於矩陣的函數,與MATLAB、R使用的是相同的行業標准級Fortran庫。
隨機數生成
NumPy.random模塊對Python內置的random進行了補充,增加了一些用於高效生成多種概率分布的樣本值的函數。
#-*- encoding:utf-8 -*- import numpy as np import numpy.random as npr from random import normalvariate #生成標准正態4*4樣本數組 samples = npr.normal(size = (4,4)) print samples #從下面的例子中看出,如果產生大量樣本值,numpy.random快了不止一個數量級 N = 1000000 #xrange()雖然也是內置函數,但是它被定義成了Python里一種類型(type),這種類型就叫做xrange. #下面的循環中,for _ in xrange(N) 非常good啊,查了一下和range的關系,兩者都用於循環,但是在大型循環時,xrange好得多 %timeit samples = [normalvariate(0,1) for _ in xrange(N)] %timeit npr.normal(size = N)
范例:隨機漫步
#-*- encoding:utf-8 -*- import numpy as np import random #這里的random是python內置的模塊 import matplotlib.pyplot as plt position = 0 walk = [position] steps = 1000 for i in xrange(steps): step = 1 if random.randint(0,1) else -1 position += step walk.append(position) plt.plot(walk) plt.show() #下面看看簡單的寫法 nsteps = 1000 draws = np.random.randint(0,2,size = nsteps) steps = np.where(draws > 0,1,-1) walk = steps.cumsum() plt.plot(walk) plt.show() #argmax函數返回數組第一個最大值的索引,但是在這argmax不高效,因為它會掃描整個數組 print (np.abs(walk) >= 10).argmax() nwalks = 5000 nsteps = 1000 draws = np.random.randint(0,2,size = (nwalks,nsteps)) steps = np.where(draws > 0,1,-1) walks = steps.cumsum(1) print walks.max() print walks.min() #這里的any后面的參數1表示每行(軸為1)是否存在true hist30 = (np.abs(walks) >= 30).any(1) print hist30 print hist30.sum() #這就是有多少行超過了30 #這里argmax的參數1就是 crossing_time = (np.abs(walks[hist30]) >= 30).argmax(1) print crossing_time.mean() X = range(1000) plt.plot(X,walks.T) plt.show()
NumPy寫完了,接下來寫pandas.NumPy寫的還好,比較順利。