《Python資料科學手冊》第二章 Numpy入門2.1—2.3
2.1 理解Python中的資料型別
Python的使用者被其易用性所吸引,其中一個易用之處就在於動態輸入,即在Python中,型別是動態推斷的。這意味著可以將任何型別的資料指定給任何變數。但是這種型別靈活性也指出了一個事實: Python 變數不僅是它們的值,還包括了關於值的型別的一些額外資訊 。
C語言整型本質上是對應某個記憶體位置的標籤,裡面儲存的位元組會編碼成整型。而Python的整型其實是一個指標,只向包含這個Python物件所有資訊的某個記憶體位置,其中包括可以轉換成整型的位元組。Python 型別中的這些額外資訊也
Python中的標準可變多元素容器是列表。 可用如下方式建立一個整型值列表
1 L=list(range(10)) 2 print(L)
輸出結果為
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
或者建立一個字串列表
1 L=list(range(10)) 2 L2=[str(c) for c in L] 3 print(L2)
其輸出結果為
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
因為Python的動態型別特性,甚至可以建立一個異構的列表.
L3=[True,"2",3.0,4]
Python的array陣列模組提供了陣列型資料的有效儲存,而Numpy包中的ndarry物件為該資料加上了高效的操作。
建立Numpy陣列的方法,Numpy要求陣列必須包含同一型別資料。如果型別不匹配,Numpy將會向上轉換(如果可行)。
import numpy as np np.array([1,4,2,5,3])
如果要設定陣列的資料型別,可以用dtype關鍵字:
np.array([1,2,3,4],dtype='float32')
Numpy陣列可以被指定為多維的。以下是用列表的列表初始化多維陣列的一種方法
np.array([range(i,i+3) for i in [2,4,6]]) # range(start, stop[, step]) range(起,終,步長)
其輸出結果如下,內層列表被當做二維陣列的行
([2,3,4], [4,5,6], [6,7,8])
在面對大型陣列的時候,用Numpy內建的方法從頭建立陣列是一種更高效的方法。
# 建立一個長度為10的陣列,陣列的值均為0 np.zeros(10,dtype=int) #建立一個3×5的浮點陣列,陣列的值均為3.14,3行5列 np.full((3,5),3.14) #建立一個3×3的、[0,10)區間的隨機整型陣列 np.array.randint(0,10,(3,3)) #建立一個3×3的單位矩陣 np.eye(3)
當用Numpy構建一個數組時,可以用一個字串引數來指定資料型別
2.2Numpy陣列基礎
Python中的資料操作幾乎等同於Numpy陣列操作,甚至Pandas也是構建在Numpy陣列基礎之上的。
介紹以下幾類基本的陣列操作:
屬性:確定陣列的大小、形狀、儲存大小、資料型別 索引:獲取和設定陣列各個元素的值 切分:在大陣列中獲取或設定更小的陣列
變形:改變給定陣列的形狀 拼接和分裂:將多個數組合併為一個,以及將一個數組分裂成多個
2.2.1 Numpy陣列的屬性
我們將用Numpy的隨機數生成器設定一組種子值,以確保每次程式執行時都可以生成同樣的隨機陣列
import numpy as np np.random.seed(0)# 設定隨機數種子 x3=np.random.randint(10,size=(3,4,5))#三維陣列 #每個陣列有nidm(陣列維度)、shape(陣列每個維度的大小)、 #size(陣列的總大小)、dtype(陣列的資料型別) print("x3 nidm",x3.ndim) print("x3 shape",x3.shape) print("x3 size",x3.size) print("x3 dtype",x3.dtype) #x3 nidm: 3 x3陣列有3個維度 #x3 shape: (3,4,5) 第一個維度大小為3 #x3 size: 60 3×4×5=60 #x3 dtype: int64
2.2.2 陣列索引 獲取單個元素
在一維陣列中,你也可以通過中括號指定索引獲取第i個值(從0開始計數)。為了獲取陣列末尾的索引,可以用負值
import numpy as np L=np.array([1,2,3,4]) print(L) print(L[-1]) print(L[-4]) #輸出結果為 #[1 2 3 4] #4 #1
在多維陣列中,可以用逗號分隔的索引元組獲取元素:
import numpy as np np.random.seed(0) x2=np.random.randint(10,size=(3,4)) print(x2) #陣列為[[5 0 3 3] # [7 9 3 5] # [2 4 7 6]] print(x2[0,0]) #行、列均從0開始計數 prnit(x2[0,3]) #行、列均從0開始計數
用以上方式也可以修改元素值,請注意,和 Python 列表不同, NumPy 陣列是固定型別的。這意味著當你試圖將一個浮點值插入一個整型陣列時,浮點值會被截短成整型。並且這種截短是自動完成的,不會給你提示或警告,所以需要特別注意這一點!
2.2.3陣列切片:獲取子陣列
用切片(slice)獲取子陣列 x[start:stop:step],如果3個引數均未指定,其預設值start=0、stop=維度大小、step=1
import numpy as np x=np.arrange(10) # 陣列為[0 1 2 3 4 5 6 7 8 9] x[:5]#前五個元素 x[5:]#索引五之後的元素 x[4:7]#從索引4開始 到索引7之前 x[::2]#每隔一個元素 x[::-1]#所有元素 逆序的
對於多維子陣列,也用冒號分隔進行處理
x2[:2,:3]#前兩行 前三列 #子陣列維度也可以同時被逆序 x2[::-1,::-1]
一種常見的需求是獲取陣列的單行和單列。你可以將索引與切片組合起來實現這個功能,用一個冒號(:)表示空切片
#獲取x2的第一列 x2[:,0] #獲取x2的第一行 x2[0,:] #在獲取行時 出於語法簡介考慮 可以省略空切片 x2[0]
關於陣列切片有一點很重要也非常有用,那就是陣列切片返回的是陣列資料的檢視,而不是數值資料的副本。
這一點也是NumPy陣列切片和Python列表切片的不同之處:在Python 列表中,切片是值的副本,改變切片裡的值,原始陣列也會修改。
這種預設的處理方式實際上非常有用:它意味著在處理非常大的資料集時,可以獲取或處理這些資料集的片段,而不用複製底層的資料快取 。
建立陣列的副本,用copy()方法實現,修改子陣列,原始陣列不會被改變。
x2_sub_copy=x2[:2,:2].copy()
2.2.4陣列的變形
陣列變形最靈活的方式是通過reshape()函式來實現,但要求原始陣列的大小必須和變形後的陣列的大小一致
import numpy as np grid=np.arange(1,10).reshape((3,3)) print(grid)
另一個常見的變形模式是將一個一維陣列轉變為二維的行或列矩陣,可以用reshape,也可以在切片中用newaxis
import numpy as np x=np.array([1,2,3]) x.reshape((1,3)) #[[1 2 3]] #通過newaxis獲得行向量 x[np.newaxis,:] #通過newaxis獲得列向量 x[:,np.newaxis]
2.2.5陣列的拼接和分裂
拼接或連線NumPy中的兩個陣列主要由np.concatenate、 np.vstack和np.hstack例程實現 。
np.concatenate將陣列元組或陣列列表作為第一引數
x=np.array([1,2,3]) y=np.array([3,2,1]) z=[99,99,99] np.concatenate([x,y]) np.concatenate([x,y,z])
np.concatenate也可以用於二維陣列的拼接
import numpy as np grid=np.array([[1,2,3], [4,5,6]]) #沿著第一個軸拼接 np.concatenate([grid,grid]) #[[1, 2, 3], # [4, 5, 6], # [1, 2, 3], # [4, 5, 6]] #沿著第二個軸拼接(軸是從0開始索引的) np.concatenate([grid,grid],axis=1) # [[1, 2, 3, 1, 2, 3], # [4, 5, 6, 4, 5, 6]]
沿著固定維度處理陣列時,使用 np.vstack(垂直棧)和 np.hstack(水平棧)函式會更簡潔,np.dstack將沿著第三個維度拼接。
與拼接相反的過程是分裂。分裂可以通過 np.split、 np.hsplit(水平分裂) 和 np.vsplit(垂直分裂) 函式來實現。 索引列表作為引數,記錄的是分裂點的位置。
import numpy as numpy x=[1,2,34,6,99,56,26,11,22,33,444,50] x1,x2,x3=np.split(x,[3,6]) print(x1,x2,x3) #[ 1 2 34] [ 6 99 56] [ 26 11 22 33 444 50] #N個分裂點會得到N+1個數組
2.3Numpy陣列的計算:通用函式
Numpy提供了一個簡單靈活的介面來優化資料陣列的計算。使Numpy變快的關鍵是利用向量化操作,通常用Numpy的通用函式。
通用函式有兩種存在形式: 一元通用函式(unary ufunc)對單個輸入操作, 二元通用函式(binary ufunc)對兩個輸入操作。
Numpy的通用函式可以對陣列進行向量化操作,可以提高陣列元素的重複計算的效率。通用函式的主要目標是對Numpy陣列中的值執行更快的操作
import numpy as np np.arange(5)/np.arange(1,6) # array([0. , 0.5 , 0.66666667, 0.75 , 0.8 ]) x=np.arange(9).reshape((3,3)) 2**x #array([[ 1, 2, 4], # [ 8, 16, 32], # [ 64, 128, 256]], dtype=int32)
通過通用函式用向量的方式進行計算幾乎總比用 Python 迴圈實現的計算更加有效,尤其是當陣列很大時。
只要你看到 Python 指令碼中有這樣的迴圈,就應該考慮能否用向量方式替換這個迴圈。
(1)加減乘除,邏輯非,**表示的指數運算子和%表示的模運算子 都是一元通用函式
(2)絕對值函式。直接abs(),括號內為一個Numpy陣列
(3)三角函式。np.sin() np.cos() np.tan() np.arcsin() np.arccos() np.arctan() 括號裡直接是一個Numpy陣列
(4)指數和對數。np.exp(x)表示 e^x np.exp2(x)表示2^x np.power(3,x)表示3^x。
最基本的 np.log 給出的是以自然數為底數的對數。如果你希望計算以2為底數或者以10為底數的對數 np.log2(),np.log10()
2.3.4高階通用函式的特性
指定輸出:所有的通用函式都可以通過out引數來指定計算結果的存放位置
import numpy as np x=np.arange(5) y=np.empty(5) np.multiply(x,10,out=y) print(y) #[ 0. 10. 20. 30. 40.]
聚合:一個reduce方法會對給定的元素和操作重複執行,直至得到單個的結果 。
import numpy as np x=np.arange(10) np.add.reduce(x) # 45
np.add.reduce()#返回陣列中所有元素的和
np.multiply.reduce()#返回陣列中所有元素的積
如果需要儲存中間結果,可以使用accumulate
外積:任何通用函式都可以用 outer 方法獲得兩個不同輸入陣列所有元素對的函式運算結果。這意味著你可以用一行程式碼實現一個乘法表:
import numpy as np x=np.arange(1,6) np.multiply.outer(x,x) Out[17]: array([[ 1, 2, 3, 4, 5], [ 2, 4, 6, 8, 10], [ 3, 6, 9, 12, 15], [ 4, 8, 12, 16, 20], [ 5, 10, 15, 20, 25]])
有關通用函式的更多資訊(包括可用的通用函式的完整列表)可以在 NumPy(http://www.numpy.org)和 SciPy(http://www.scipy.org)文件的網站找到。
前面的章節介紹過,可直接在 IPython 中通過匯入相應的包,然後利用 IPython 的 Tab 鍵補全和幫助(?)功能獲取資訊 。