科學(xué)計算基礎(chǔ)軟件包Numpy入門講座(4):操作數(shù)組(科學(xué)計算庫Numpy)
文章目錄
1. 索引和切片
2. 改變結(jié)構(gòu)
3. 合并與拆分
4. 復(fù)制
5. 排序
6. 查找和篩選
6.1 查找
6.2 篩選
7. 數(shù)組I/O
1. 索引和切片
NumPy數(shù)組對象的內(nèi)容可以通過索引或切片來訪問和修改。對于一維數(shù)組的索引和切片,NumPy數(shù)組和Python的列表一樣靈活。
a = np.arange(9) >>> a[-1] # 最后一個元素 8 >>> a[2:5] # 返回第2到第5個元素 array([2, 3, 4]) >>> a[:7:3] # 返回第0到第7個元素,步長為3 array([0, 3, 6]) >>> a[::-1] # 返回逆序的數(shù)組 array([8, 7, 6, 5, 4, 3, 2, 1, 0])
1
2
3
4
5
6
7
8
9
對于多維數(shù)組操作,NumPy數(shù)組比 Python的列表更加靈活、強大。假設(shè)有一棟2層樓,每層樓內(nèi)的房間都是3行4列,那我們可以用一個三維數(shù)組來保存每個房間的居住人數(shù)(當(dāng)然,也可以是房間面積等其他數(shù)值信息)。
>>> a = np.arange(24).reshape(2,3,4) # 2層3行4列 >>> a array([[[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]]) >>> a[1][2][3] # 雖然可以這樣 23 >>> a[1,2,3] # 但這才是規(guī)范的用法 23 >>> a[:,0,0] # 所有樓層的第1排第1列 array([ 0, 12]) >>> a[0,:,:] # 1樓的所有房間,等價與a[0]或a[0,...] array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) >>> a[:,:,1:3] # 所有樓層所有排的第2到4列 array([[[ 1, 2], [ 5, 6], [ 9, 10]], [[13, 14], [17, 18], [21, 22]]]) >>> a[1,:,-1] # 2層每一排的最后一個房間 array([15, 19, 23])
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
提示:
對多維數(shù)組切片或索引得到的結(jié)果,維度不是確定的;
切片返回的數(shù)組不是原始數(shù)據(jù)的副本,而是指向與原始數(shù)組相同的內(nèi)存區(qū)域。數(shù)組切片不會復(fù)制內(nèi)部數(shù)組數(shù)據(jù),只是產(chǎn)生了原始數(shù)據(jù)的一個新視圖。
>>> a = np.arange(12).reshape(3,4) >>> a array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) >>> b = a[1:,2:] # 數(shù)組b是數(shù)組a的切片 >>> b array([[ 6, 7], [10, 11]]) >>> b[:,:] = 99 # 改變數(shù)組b的值,也會同時影響數(shù)組a >>> b array([[99, 99], [99, 99]]) >>> a array([[ 0, 1, 2, 3], [ 4, 5, 99, 99], [ 8, 9, 99, 99]])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2. 改變結(jié)構(gòu)
NumPy數(shù)組的存儲順序和數(shù)組的視圖是相互獨立的,因此改變數(shù)組的維度是非常便捷的操作,這一類操作不會改變所操作的數(shù)組本身的存儲順序, resize() 除外。
reshape() - 按照指定的結(jié)構(gòu)(形狀)返回數(shù)組的新視圖,但不會改變數(shù)組
resize() - 按照指定的結(jié)構(gòu)(形狀)改變數(shù)組,無返回值
ravel() - 返回多維數(shù)組一維化的視圖,但不會改變原數(shù)組
transpose() - 返回行變列的視圖,但不會改變原數(shù)組
rollaxis() - 翻滾軸,返回新的視圖
>>> a = np.arange(12) >>> b = a.reshape((3,4)) # reshape()返回數(shù)組a的一個新視圖,但不會改變數(shù)組a >>>> a.shape (12,) >>> b.shape (3, 4) >>> b is a False >>> b.base is a True a.resize([4,3]) # resize()則真正改變了數(shù)組a的結(jié)構(gòu) >>> a.shape (4, 3) >>> a.ravel() # 返回多維數(shù)組一維化的視圖,但不會改變原數(shù)組 array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) >>> a.transpose() # 返回行變列的視圖,但不會改變原數(shù)組 array([[ 0, 3, 6, 9], [ 1, 4, 7, 10], [ 2, 5, 8, 11]]) >>> a.T # 返回行變列的視圖,等價于transpose() array([[ 0, 3, 6, 9], [ 1, 4, 7, 10], [ 2, 5, 8, 11]]) >>> np.rollaxis(a, 1, 0) # 翻滾軸,1軸變0軸 array([[ 0, 3, 6, 9], [ 1, 4, 7, 10], [ 2, 5, 8, 11]])
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
3. 合并與拆分
NumPy數(shù)組一旦創(chuàng)建就不能再改變其元素數(shù)量了。如果要動態(tài)改變數(shù)組元素數(shù)量,只能通過合并或者拆分的方法,生成新的數(shù)組。對于剛剛上手NumPy的程序員來說,最大的困惑就是不能使用append() 方法向數(shù)組內(nèi)添加元素,甚至連 append() 方法都找不到了。其實,NumPy仍然保留了append() 方法,只不過這個方法不再是NumPy數(shù)組的方法,而是是升級到最外層的NumPy命名空間,并且該方法的功能不再是追加元素,而是合并數(shù)組。
>>> np.append([[1, 2, 3]], [[4, 5, 6]]) array([1, 2, 3, 4, 5, 6]) >>> np.append([[1, 2, 3]], [[4, 5, 6]], axis=0) array([[1, 2, 3], [4, 5, 6]]) >>> np.append([[1, 2, 3]], [[4, 5, 6]], axis=1) array([[1, 2, 3, 4, 5, 6]])
1
2
3
4
5
6
7
8
9
不過,這個append()委實不夠好用,我給大家推薦的是stack()方法。
>>> a = np.arange(4).reshape(2,2) >>> b = np.arange(4,8).reshape(2,2) >>> np.hstack((a,b)) # 水平合并 array([[0, 1, 4, 5], [2, 3, 6, 7]]) >>> np.vstack((a,b)) # 垂直合并 array([[0, 1], [2, 3], [4, 5], [6, 7]]) >>> np.dstack((a,b)) # 深度合并 array([[[0, 4], [1, 5]], [[2, 6], [3, 7]]])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
stack 函數(shù)原型為 stack(arrays, axis=0),請注意體會下面例子中的 axis 的用法。
>>> a = np.arange(60).reshape(3,4,5) >>> b = np.arange(60).reshape(3,4,5) >>> a.shape, b.shape >>> np.stack((a,b), axis=0).shape (2, 3, 4, 5) >>> np.stack((a,b), axis=1).shape (3, 2, 4, 5) >>> np.stack((a,b), axis=2).shape (3, 4, 2, 5) >>> np.stack((a,b), axis=3).shape (3, 4, 5, 2)
1
2
3
4
5
6
7
8
9
10
11
因為數(shù)組切片非常簡單,所以數(shù)組拆分應(yīng)用較少。拆分是合并的逆過程,最常用的方法是split()。
>>> a = np.arange(8).reshape(2,4) >>> np.vsplit(a, 2) # 垂直方向拆分成2部分 [array([[0, 1, 2, 3]]), array([[4, 5, 6, 7]])] >>> np.hsplit(a, 2) # 水平方向拆分成2部分 [array([[0, 1], [4, 5]]), array([[2, 3], [6, 7]])]
1
2
3
4
5
6
7
4. 復(fù)制
改變數(shù)組結(jié)構(gòu)返回的是原元數(shù)據(jù)的一個新視圖,而不是原元數(shù)據(jù)的副本。淺復(fù)制(view)和深復(fù)制(copy)則是創(chuàng)建原數(shù)據(jù)的副本,但二者之間也有細(xì)微差別:淺復(fù)制(view)是共享內(nèi)存,深復(fù)制(copy)則是獨享。
>>> a = np.arange(6).reshape((2,3)) >>> b = a.view() >>> b is a False >>> b.base is a False >>> b.flags.owndata False >>> c = a.copy() >>> c is a False >>> c.base is a False >>> c.flags.owndata True
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
5. 排序
NumPy 數(shù)組排序函數(shù)有兩個,一個是sort(),一個是argsort()。sort()返回輸入數(shù)組的排序副本,argsort()返回的是數(shù)組值從小到大的索引號。從函數(shù)原型看,這兩個函數(shù)的參數(shù)是完全一樣的。
numpy.sort(a, axis=-1, kind=‘quicksort’, order=None)
numpy.argsort(a, axis=-1, kind=‘quicksort’, order=None)
a - 要排序的數(shù)組
axis - 沿著它排序數(shù)組的軸,如果沒有,則沿著最后的軸排序
kind - 排序方法,默認(rèn)為’quicksort’(快速排序),其他選項還有 ‘mergesort’(歸并排序)和 ‘heapsort’(堆排序)
order - 如果數(shù)組包含字段,則是要排序的字段
>>> a = np.random.random((2,3)) >>> a array([[0.79658569, 0.14507096, 0.63016223], [0.24983103, 0.98368325, 0.71092079]]) >>> np.argsort(a) # 返回行內(nèi)從小到大排序的索引序號(列排序),相當(dāng)于axis=1(最后的軸) array([[1, 2, 0], [0, 2, 1]], dtype=int64) >>> np.sort(a) # 返回行內(nèi)從小到大排序的一個新數(shù)組(列排序) array([[0.14507096, 0.63016223, 0.79658569], [0.24983103, 0.71092079, 0.98368325]]) >>> np.sort(a,axis=0) # 返回列內(nèi)每一行都是從小到大排序(行排序) array([[0.24983103, 0.14507096, 0.63016223], [0.79658569, 0.98368325, 0.71092079]])
1
2
3
4
5
6
7
8
9
10
11
12
13
我們再看看排序字段的使用。先定義一個新的數(shù)據(jù)類型dt:dt類似于一個字典,有兩個鍵值對,一個是姓名name,一個是年齡age,姓名長度10個字符,年齡是整型。
>>> dt = np.dtype([('name', 'S10'),('age', int)]) >>> a = np.array([("zhang",21),("wang",25),("li", 17), ("zhao",27)], dtype = dt) >>> np.sort(a, order='name') # 如果指定姓名排序,結(jié)果是李王張趙 array([(b'li', 17), (b'wang', 25), (b'zhang', 21), (b'zhao', 27)], dtype=[('name', 'S10'), ('age', ' 1 2 3 4 5 6 7 8 6. 查找和篩選 這里,我們約定查找是返回符合條件的元素的索引號,篩選是返回符合條件的元素。查找和篩選,是 NumPy 數(shù)組最令人心動的功能,也是相對比較燒腦的操作。 6.1 查找 下面的代碼演示了返回數(shù)組中最大值和最小值的索引(對于多維數(shù)組,這個索引是數(shù)組轉(zhuǎn)成一維之后的索引): >>> a = np.random.random((2,3)) >>> a array([[0.47881615, 0.55682904, 0.29173085], [0.41107703, 0.91467593, 0.88852535]]) >>> np.argmax(a) 4 >>> np.argmin(a) 2 1 2 3 4 5 6 7 8 下面的代碼演示了返回數(shù)組中非零元素的索引: >>> a = np.random.randint(0, 2, (2,3)) >>> a array([[0, 0, 0], [0, 1, 1]]) >>> np.nonzero(a) (array([1, 1], dtype=int64), array([1, 2], dtype=int64)) 1 2 3 4 5 6 numpy.where() 用于返回數(shù)組中滿足給定條件的元素的索引,還可以用于替換符合條件的元素: numpy.where(condition[, x, y]) >>> a = np.arange(10) >>> a array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> np.where(a < 5) (array([0, 1, 2, 3, 4], dtype=int64),) >>> a = a.reshape((2, -1)) >>> a array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]) >>> np.where(a < 5) (array([0, 0, 0, 0, 0], dtype=int64), array([0, 1, 2, 3, 4], dtype=int64)) >>> np.where(a < 5, a, 10*a) # 滿足條件的元素不變,其他元素乘以10 array([[ 0, 1, 2, 3, 4], [50, 60, 70, 80, 90]]) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 6.2 篩選 篩選有3種方式,一是使用np.where()返回的python元組,二是使用邏輯表達(dá)式返回的布爾型數(shù)組,三是使用整型數(shù)組。 >>> a = np.random.random((3,4)) >>> a array([[0.41551063, 0.38984904, 0.01204226, 0.72323978], [0.82425869, 0.64216573, 0.41475495, 0.21351508], [0.30104819, 0.52046164, 0.58286043, 0.66749564]]) >>> a[np.where(a>0.5)] # 返回大于0.5的元素(使用np.where()返回的python元組) array([0.72323978, 0.82425869, 0.64216573, 0.52046164, 0.58286043, 0.66749564]) >>> a[(a>0.3)&(a<0.7)] # 返回大于0.3且小于0.7的元素(使用邏輯表達(dá)式返回的布爾型數(shù)組) array([0.41551063, 0.38984904, 0.64216573, 0.41475495, 0.30104819, 0.52046164, 0.58286043, 0.66749564]) >>> a[np.array([2,1])] # 返回整形數(shù)組指定的項(使用整型數(shù)組) array([[0.30104819, 0.52046164, 0.58286043, 0.66749564], [0.82425869, 0.64216573, 0.41475495, 0.21351508]]) >>> a = a.ravel() >>> a[np.array([3,5,7,11])] # 返回整形數(shù)組指定的項(使用整型數(shù)組) array([0.72323978, 0.64216573, 0.21351508, 0.66749564]) >>> a[np.array([[3,5],[7,11]])] # 返回整形數(shù)組指定的項(使用整型數(shù)組) array([[0.72323978, 0.64216573], [0.21351508, 0.66749564]]) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 使用np.where()或者直接使用邏輯表達(dá)式來篩選數(shù)組元素,很容易想象到這樣的做的目的,使用整形數(shù)組來篩選數(shù)組元素的用途是什么呢?看似不起眼的一個功能,卻蘊含著無窮的想象空間。下面用一個例子來演示通過整型數(shù)組篩選數(shù)組元素的神奇魔法。 上圖是用字符表現(xiàn)像素灰度的效果圖。一般而言,灰度圖像每個像素的值域范圍是 [0, 255]。假如用于表現(xiàn)不同灰度的字符集是[’ ‘, ‘.’, ‘-’, ‘+’, ‘=’, ‘*’, ‘#’, ‘@’],從 ’ ’ 到 ‘@’ 表示從白到黑的 8 個灰度等級。我們需要將每個像素的灰度值分段轉(zhuǎn)換成相應(yīng)的字符。例如,灰度值小于32的像素用 ‘@’ 表示,大于或等于32且小于64的像素用 ‘#’ 表示,依次類推直至大于或等于224的像素用’ '表示。 如何實現(xiàn)圖像數(shù)組從灰度值到對應(yīng)字符的轉(zhuǎn)換呢?乍一看,好像只有用循環(huán)的方式遍歷所有像素才能實現(xiàn)。但是,下面的代碼卻用“整型數(shù)組篩選數(shù)組元素”的方法完成了這個轉(zhuǎn)換,不但代碼簡潔,而且代碼的執(zhí)行速度也非常快。 >>> img = np.random.randint(0, 256, (5, 10), dtype=np.uint8) # 生成10x5的灰度圖 >>> img array([[145, 95, 60, 14, 66, 150, 221, 43, 184, 66], [229, 138, 76, 90, 179, 217, 2, 20, 154, 191], [165, 120, 77, 117, 42, 108, 156, 5, 208, 50], [164, 196, 227, 111, 82, 84, 19, 208, 124, 16], [146, 50, 107, 26, 34, 229, 137, 104, 93, 223]], dtype=uint8) >>> img = (img/32).astype(np.uint8) # 將256級灰度值轉(zhuǎn)為8級灰度值 >>> img array([[0, 3, 3, 3, 1, 7, 7, 4, 2, 7], [6, 3, 4, 5, 4, 5, 4, 7, 3, 4], [6, 7, 1, 2, 2, 2, 2, 4, 7, 7], [5, 7, 1, 2, 0, 2, 7, 0, 7, 5], [3, 5, 0, 7, 0, 4, 6, 2, 5, 0]], dtype=uint8) >>> chs = np.array([' ', '.', '-', '+', '=', '*', '#', '@']) # 灰度字符集 >>> chs[img] # 用整型數(shù)組篩選數(shù)組元素(我認(rèn)為這是NumPy最精彩之處!) array([[' ', '+', '+', '+', '.', '@', '@', '=', '-', '@'], ['#', '+', '=', '*', '=', '*', '=', '@', '+', '='], ['#', '@', '.', '-', '-', '-', '-', '=', '@', '@'], ['*', '@', '.', '-', ' ', '-', '@', ' ', '@', '*'], ['+', '*', ' ', '@', ' ', '=', '#', '-', '*', ' ']], dtype=' 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 7. 數(shù)組I/O 所謂數(shù)組I/O,就是討論如何分發(fā)、交換數(shù)據(jù)。在機器學(xué)習(xí)算法模型的例子中,海量的訓(xùn)練數(shù)據(jù)通常都是從數(shù)據(jù)文件中讀出來的,而數(shù)據(jù)文件一般是csv格式,NumPy 自帶的csv文件讀寫函數(shù),可以很方便的讀寫csv格式的數(shù)據(jù)文件。除了支持通用的csv格式的數(shù)據(jù)文件, NumPy 為數(shù)組對象引入了新的二進(jìn)制文件格式,用于數(shù)據(jù)交換。后綴名為.npy 文件用于存儲單個數(shù)組,后綴名為.npz 文件用于存取多個數(shù)組。 下面的代碼演示了NumPy讀寫CSV格式的數(shù)據(jù)文件的方法。實際操作下面的代碼時,請注意結(jié)合實際情況替換對應(yīng)的文件路徑和文件名。 >>> a = np.random.random((15,5)) >>> np.savetxt('demo.csv', a, delimiter=',') # 將數(shù)組a保存成CSV格式的數(shù)據(jù)文件 >>> data = np.loadtxt('demo.csv', delimiter=',') # 打開CSV格式的數(shù)據(jù)文件 >>> data.shape, data.dtype ((15, 5), dtype('float64')) 1 2 3 4 5 NumPy 自定義的數(shù)據(jù)交換格式也是一個非常好用的數(shù)據(jù)交換方式,使用它保存 NumPy 數(shù)組時不會丟失任何信息,特別是數(shù)據(jù)類型的信息。實際操作下面的代碼時,請注意結(jié)合實際情況替換對應(yīng)的文件路徑和文件名。 >>> single_arr_fn = 'single_arr.npy' # 存儲單個數(shù)組文件名 >>> multi_arr_fn = 'multi_arr.npz' # 存儲多個數(shù)組文件名 >>> lon = np.linspace(10,90,9) >>> lat = np.linspace(20,60,5) >>> np.save(single_arr_fn, lon) # 用save()函數(shù)把經(jīng)度數(shù)組保存成.npy文件 >>> lon = np.load(single_arr_fn) # 接著用load()函數(shù)讀出來 >>> np.savez(multi_arr_fn, longitude=lon, latitude=lat) #保存兩個數(shù)組到一個文件 >>> data = np.load(multi_arr_fn) # 用load()函數(shù)把這個.npz文件讀成一個結(jié)構(gòu)data >>> data.files # 查看所有的數(shù)組名 >>> data['longitude'] # 使用data[數(shù)組名],就可以取得想要的數(shù)據(jù) >>> data['latitude'] # 使用data[數(shù)組名],就可以取得想要的數(shù)據(jù) 1 2 3 4 5 6 7 8 9 10 11 Numpy 數(shù)據(jù)結(jié)構(gòu)
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。