Numpy 基礎
- Numpy 是 Python 科學計算的基礎,學會如何創建、讀取、更改向量數據。
創建向量有許多方法,舉例說明:
import numpy as np
print(np.array([2,3,4])) # 可以從列表轉換而來,np.array 會嘗試為數組推斷出一個較為合適的數據類型
[2 3 4]
print(np.zeros( (3,4) , dtype=np.int32)) # zeros 可以創建指定長度或形狀的全 0 數組
[[0 0 0 0]
[0 0 0 0]
[0 0 0 0]]
print(np.ones( (2,3,4), dtype=np.float )) # ones 可以創建指定長度或形狀的全 1 數組,可以指定 dtype
[[[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]]
[[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]]]
print(np.empty( (2,3) )) # empty 可以創建一個沒有任何具體值的數組,
[[ 0. 0. 0.]
[ 0. 0. 0.]]
print(np.arange( 10, 30, 5 )) # arange 是 Python 內置函數 range 的數組版,第三個參數是步長
[10 15 20 25]
print(np.linspace( 0, 2, 9 )) # 同 arrange 類似,只是第三個參數表示要生成的數組元素個數
[ 0. 0.25 0.5 0.75 1. 1.25 1.5 1.75 2. ]
np.zeros((2,3)) # 創建一個二維數組,注意要加括號
array([[ 0., 0., 0.],
[ 0., 0., 0.]])
np.random.normal(10,3,(2,4)) # 二維數組元素來自於期望為 10,標准差為 3 的正態分布
array([[ 9.05422825, 15.64840644, 8.10973337, 14.19151934],
[ 7.25610522, 13.53550744, 8.62793042, 16.15319148]])
np.logspace 這種比較少用的就不提了,感興趣的可自行查詢。隨機數生成的方法是比較常用的:
np.random.randn(5) # 從標准正態分布中生成 5 個元素
array([ 0.12789087, 0.82489732, -1.86115425, 0.50760795, -0.3306407 ])
norm10 = np.random.normal(10,3,5) # 從期望為 10,標准差為 3 的正態分布中生成 5 個元素
norm10
array([ 9.60796679, 11.27292882, 10.80855564, 12.13837214, 10.25883022])
np.arange(8).reshape(2,4) # 一維數組可以通過 reshape 轉成二維數組
array([[0, 1, 2, 3],
[4, 5, 6, 7]])
讀取向量:
arr = np.arange(10) # 一維數組
print(arr)
[0 1 2 3 4 5 6 7 8 9]
print(arr[2])
2
print(arr[2:4])
[2 3]
arr2d = np.array([[1,2,3],[4,5,6],[7,8,9]]) # 二維數組
print(arr2d[2])
[7 8 9]
print(arr2d[0][2])
3
print(arr2d[0, 2]) # 該訪問方式同上面是等價的
3
print(arr2d[arr2d<5]) # 還可以指定截取符合條件的數據,也叫邏輯索引
[1 2 3 4]
更改向量:
arr = np.arange(10)
arr[5:8] = 12 # 可以直接修改
print(arr)
[ 0 1 2 3 4 12 12 12 8 9]
arr_slice = arr[5:8]
arr_slice[1] = 12345 # 也可以通過切片來修改
print(arr)
[ 0 1 2 3 4 12 12345 12 8 9]
arr[arr>10] = 0 # 通過邏輯索引修改
print arr
[0 1 2 3 4 0 0 0 8 9]
- 思考 np.array 和 list 有什么不同。
第一個不同,在高維數組中,numpy.array 支持比 list 更多的索引方式。舉例說明:
a = [[1, 2 , 3],[4, 5, 6]]
import numpy as np
b = np.array(a)
print type(a)
print a
print type(b)
print b
<type 'list'>
[[1, 2, 3], [4, 5, 6]]
<type 'numpy.ndarray'>
[[1 2 3]
[4 5 6]]
print a[1][2]
print a[1][:]
6
[4, 5, 6]
print a[1,2] # Wrong
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-60-9da16148905d> in <module>()
----> 1 print a[1,2]
TypeError: list indices must be integers, not tuple
print a[1, :] # Wrong
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-61-f921c5f2e22f> in <module>()
----> 1 print a[1, :]
TypeError: list indices must be integers, not tuple
print b[1][2]
print b[1][:]
print b[1, 2]
print b[1, :]
6
[4 5 6]
6
[4 5 6]
第二個不同,list 的元素可以是任何對象,而 np.array 的所有元素必須是相同類型的。比如當 list 和 np.array 存儲的都是數值元素時,list 可以修改其中元素為字符串,但 np.array 就不行,要報錯。
lst = [1,2,3,4,5,6]
arr = np.array(lst)
lst[-1] = 'openmind'
lst
[1, 2, 3, 4, 5, 'openmind']
arr[-1] = 'openmind'
arr
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-121-da05f2764aba> in <module>()
----> 1 arr[-1] = 'openmind'
2 arr
ValueError: invalid literal for long() with base 10: 'openmind'
list 所保存的是對象的指針。這樣為了保存一個簡單的[1,2,3],需要有3個指針和三個整數對象。對於數值運算來說這種結構顯然比較浪費內存和CPU計算時間。np.array 是存儲單一數據類型的多維數組,運算效率要高。
第三個不同,np.array 的大小是創建時就指定的,不能改變大小,而 list 可以隨時添加元素進去,list 有 append 函數。在實際使用中會經常遇到運行前不知道數組大小的情況,這時候就可以初始化 list 為空,然后在運行中根據需要添加元素進去,最后計算時可把 list 轉為 np.array 以提高效率。可見,list 和 np.array 有合作的空間。
- 如何使用 mask 方法快速截取數據。
mask 是一種按條件提取數組中數據的方法。舉例即可說明,如下例要提取向量中的偶數。
import numpy.ma as ma
import numpy as np
a = np.arange(10)
a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
mask = a%2==0
mask
array([ True, False, True, False, True, False, True, False, True, False], dtype=bool)
a[mask]
array([0, 2, 4, 6, 8])
Array 高維數組操作
- 思考 view 和 copy 的區別。
view 是淺復制,copy 是深復制。什么意思呢?
淺復制就是不復制,view 創建的新的數組對象指向同一數據,對 view 上的任何修改都會直接反映到源數組上。數組切片就是原始數組的視圖。舉個例子就明白了。
arr = np.arange(10)
arr_slice = arr[5:8]
arr_slice[1] = 12345
arr
array([ 0, 1, 2, 3, 4, 5, 12345, 7, 8, 9])
看到了吧,對 arr_slice[1] 的修改就是對 arr 的修改,因為 arr_slice 是 arr 的視圖。
reshape 也是一種 view,修改 reshape 后的數組內容會影響到原數組。
arr1 = np.arange(8)
arr2 = arr1.reshape(2,4)
arr2[0,0]=1234
arr1
array([1234, 1, 2, 3, 4, 5, 6, 7])
深復制才是真正的復制,copy 就是生成了數組切片的一個份副本。對該副本的操作就不會影響到原數組的值。
arr_copy = arr.copy()
arr_copy[1] = 56789
arr
array([ 0, 1, 2, 3, 4, 5, 12345, 7, 8, 9])
可見,對 copy 值的修改並沒有影響原數組。
- np.array 還有哪些屬性和方法?
屬性:dtype, size, ndim, shape, nbytes
創建 np.array 時,如果沒有顯式指定,np.array 會為新建的數組推斷出一個較為合適的數據類型,中途如果要修改 dtype 就只有通過 astype 方法顯式轉換 dtype。
arr = np.array([1, 2, 3, 4, 5])
print arr.dtype
arr[0] = 3.1415
print arr.dtype
float_arr = arr.astype(np.float64)
print float_arr.dtype
int64
int64
float64
一元通用函數:abs, fabs, sqrt, square, exp, log, log10, sign, ceil, floor, cos, cosh, sin, sinh, tan, tanh……
二元通用函數:add, subtract, multiply, divide, power, maximum, minimum, mod, copysign, greater, less, equal……
arr1 = np.arange(4)
arr2 = np.arange(10,14)
print(arr1,'+',arr2,'=',arr1+arr2) # 對應元素相加
(array([0, 1, 2, 3]), '+', array([10, 11, 12, 13]), '=', array([10, 12, 14, 16]))
print(arr1,'*',arr2,'=',arr1*arr2) # 注意這里的乘是兩數組中對位元素相加,跟線性代數中的矩陣乘法不同
(array([0, 1, 2, 3]), '*', array([10, 11, 12, 13]), '=', array([ 0, 11, 24, 39]))
基本數組統計方法:sum, mean, std, var, min, max, argmin, argmax, cumsum, cumprod
arr = np.arange(12).reshape(3,4) # 看下二維數組,可以按行求和,可以按列求和
print arr
print arr.sum(axis = 0) # 求每一列的和,即按列求和,相當於抹掉了行維度
print arr.sum(axis = 1) # 求每一行的和,即按行求和,相當於抹掉了列維度
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[12 15 18 21]
[ 6 22 38]
數組集合運算:unique(x), intersect1d(x), union1d(x,y), in1d(x,y), setdiff1d(x,y), setxor1d(x,y)
數組文件輸入輸出:save, load, loadtxt, genfromtxt
線性代數:diag, dot, trace, det, eig, inv, pinv, qr, svd, solve, lstsq
隨機數生成:seed, permutation, shuffle, rand, randint, randn, binomial, normal, beta, chisquare, gamma, uniform
- 理解什么是 broadcasting?如何使用?
廣播(broadcastring)是指不同形狀的數組之間算術運算的執行方式。例如,將標量值跟數組合並時就會發生最簡單的廣播。
arr = np.arange(5)
4 * arr
array([ 0, 4, 8, 12, 16])
低維變量遇到高維變量會自動補全,如上面就是把 4 復制成一個同 arr 一樣的含 5 個元素的一維向量,然后對位元素相乘,也可以說標量值 4 被廣播到了 arr 的所有元素上。
再比如數據分析中經常用到的距平化處理,對數組的每一列減去平均值。
arr = np.random.randn(4,3)
arr - arr.mean(0)
array([[-0.00186826, 0.36242026, 1.20839815],
[ 1.47094699, 1.02999549, -0.79207756],
[-1.55548229, 0.27482805, -1.05206272],
[ 0.08640356, -1.6672438 , 0.63574213]])
可見,所謂廣播,也就是把對矩陣的操作廣播到每個元素的操作。
如果列向量遇到行向量會如何呢?
np.arange(3).reshape((3, 1)) + np.arange(3)
array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]])
一圖勝千言,看了下圖,廣播的規則就一目了然了。
只要遵循一定的規則,低維度的值是可以被廣播到數組的任意維度的。
只要記住,遇到不同維度的運算,先對低維度補全,補全到相同的維度再計算,就不會迷惑。
Numpy 中的線性代數
- 如何計算向量、矩陣相乘?
Numpy 提供了一個用於矩陣乘法的 dot 函數。先來看下向量相乘:
v1 = np.array([1,2,3]) # 先來看下向量相乘
v2 = np.array([2,3,4])
print np.dot(v1,v2)
print np.dot(v1.T,v2)
print np.dot(v1,v2.T)
print np.dot(v1.T,v2.T)
print v1
print v1.T
20
20
20
20
[1 2 3]
[1 2 3]
由上可見,在 Numpy 中,向量是不分行向量和列向量的,轉置對向量不起作用,這點跟預想的不一樣。要注意的是,reshape 不能轉置向量,reshape 只是改變數組的形狀。
print v1.reshape(3,1) # 試圖通過 reshape 轉置向量
print v1
np.dot(v1.reshape(3,1),v1) # 失敗的線性相乘
[[1]
[2]
[3]]
[1 2 3]
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-174-ed17a869fdc6> in <module>()
1 print v1.reshape(3,1)
2 print v1
----> 3 np.dot(v1.reshape(3,1),v1)
ValueError: shapes (3,1) and (3,) not aligned: 1 (dim 1) != 3 (dim 0)
再來看向量和矩陣相乘。
v1 = np.array([2, 3, 4])
A = np.arange(6).reshape(2,3)
print np.dot(A, v1) # 2 行 3 列的矩陣乘 3 行 1 列的向量應等於 2 行 1 列的矩陣
print np.dot(A, v1.T) # 看結果再次證明,Numpy 是不分行向量和列向量的
B = np.arange(6).reshape(3,2)
print np.dot(v1, B) # 1 行 3 列的向量乘 3 行 2 列的向量等於 1 行 2 列的向量
[11 38]
[11 38]
[22 31]
可以這么看,當矩陣乘向量,即向量在后時,那么向量就是列向量;當向量乘矩陣,即向量在前時,那么向量就是行向量。
矩陣和矩陣相乘,只要符合維度要求即可。
x = np.array([[1,2,3],[4,5,6]]) # 2 行 3 列的矩陣
y = np.array([[6.,23],[-1,7],[8,9]]) # 3 行 2 列的矩陣
x.dot(y) # 得 2 行 2 列的矩陣
[[1 2 3]
[4 5 6]]
array([[ 28., 64.],
[ 67., 181.]])
- 如何從文件中讀取數據?
前面都是在內存空間中計算 np.array,那么它怎么和磁盤空間進行輸入輸出交互呢?有兩種讀寫方式,一種是 Text 模式,一種是 Binary 模式。
Text 模式是可以把數組以字符串的方式存到文本文件中,人們用編輯器打開文件讀得懂,也可以手動修改。只能用於一維和二維數組。
Binary 模式是以二進制存儲,跟內存中格式一模一樣,包含數組的大小和維度,沒有信息損失,原模原樣存儲自然效率高。但人們不能讀懂,無法編輯。
arr = np.arange(10).reshape(2,5)
np.savetxt('test.out',arr,fmt='%2e',header='My dataset') # 以 Text 模式存儲
!cat test.out
# My dataset
0.000000e+00 1.000000e+00 2.000000e+00 3.000000e+00 4.000000e+00
5.000000e+00 6.000000e+00 7.000000e+00 8.000000e+00 9.000000e+00
arr2 = np.loadtxt('test.out') # 以 Text 模式讀取
print(arr2)
[[ 0. 1. 2. 3. 4.]
[ 5. 6. 7. 8. 9.]]
np.save('test.npy', arr2) # 以 Binary 模式存儲
!cat test.npy # 可以看到 Binary 模式存儲的內容好像一團亂碼
�NUMPY F {'descr': '<f8', 'fortran_order': False, 'shape': (2, 5), }
�? @ @ @ @ @ @ @ "@
arr2n = np.load('test.npy') # 以 Binary 模式讀取
print arr2n
[[ 0. 1. 2. 3. 4.]
[ 5. 6. 7. 8. 9.]]
通過 np.savez 可以將多個數組保存到一個壓縮文件中,將數組以關鍵字參數的形式傳入即可。
np.savez('test.npz', arr, arr2) # 以 Binary 模式存儲多個數組
arrays = np.load('test.npz')
arrays.files
!cat test.npz # 一團亂碼
� � arr_1.npy�NUMPY F {'descr': '<f8', 'fortran_order': False, 'shape': (2, 5), }
�? @ @ @ @ @ @ @ "@PK l��Hx�� � arr_0.npy�NUMPY F {'descr': '<i8', 'fortran_order': False, 'shape': (2, 5), }
� � �� arr_1.npyPK l��Hx�� � ��� arr_0.npyPK n �
讀取文件還有 pandas 的 read_csv 和 read_table 函數,后面的課程會講到。
小結
Numpy 提供了 Array 這種數據結構,提供了所有 Python 環境中數值計算的底層支持,Array 使向量化計算更為容易,Array 有大量方便的內置函數。
補充閱讀
-
利用 Python 進行數據分析 第 4 章
-
Numerical Python 第 2 章
-
Scipy Lecture 第 3 章