Numpy 是一個開源的 Python 科學計算庫,用於快速處理任意維度的陣列。Numpy 支援常見的陣列和矩陣操作,對於同樣的數值計算任務,使用 NumPy 不僅程式碼要簡潔的多,而且 NumPy 的效能遠遠優於原生 Python,基本是一個到兩個數量級的差距,而且資料量越大,NumPy 的優勢就越明顯。
Numpy 最為核心的資料型別是ndarray
,使用ndarray
可以處理一維、二維和多維陣列,該物件相當於是一個快速而靈活的大資料容器。NumPy 底層程式碼使用 C 語言編寫,解決了 GIL 的限制,ndarray
在存取資料的時候,資料與資料的地址都是連續的,這確保了可以進行高效率的批次操作,遠遠優於 Python 中的list
;另一方面ndarray
物件提供了更多的方法來處理資料,尤其是和統計相關的方法,這些方法也是 Python 原生的list
沒有的。
-
啟動Notebook
jupyter notebook
提示:在啟動Notebook之前,建議先安裝好資料分析相關依賴項,包括之前提到的三大神器以及相關依賴項,包括:
numpy
、pandas
、matplotlib
、openpyxl
等。如果使用Anaconda,則無需單獨安裝。 -
匯入
import numpy as np import pandas as pd import matplotlib.pyplot as plt
說明:如果已經啟動了 Notebook 但尚未安裝相關依賴庫,例如尚未安裝
numpy
,可以在 Notebook 的單元格中輸入!pip install numpy
並執行該單元格來安裝 NumPy,也可以一次性安裝多個三方庫,需要在單元格中輸入%pip install numpy pandas matplotlib
。注意上面的程式碼,我們不僅匯入了NumPy,還將 pandas 和 matplotlib 庫一併匯入了。
建立ndarray
物件有很多種方法,下面就如何建立一維陣列、二維陣列和多維陣列進行說明。
-
方法一:使用
array
函式,透過list
建立陣列物件程式碼:
array1 = np.array([1, 2, 3, 4, 5]) array1
輸出:
array([1, 2, 3, 4, 5])
-
方法二:使用
arange
函式,指定取值範圍建立陣列物件程式碼:
array2 = np.arange(0, 20, 2) array2
輸出:
array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18])
-
方法三:使用
linspace
函式,用指定範圍均勻間隔的數字建立陣列物件程式碼:
array3 = np.linspace(-5, 5, 101) array3
輸出:
array([-5. , -4.9, -4.8, -4.7, -4.6, -4.5, -4.4, -4.3, -4.2, -4.1, -4. , -3.9, -3.8, -3.7, -3.6, -3.5, -3.4, -3.3, -3.2, -3.1, -3. , -2.9, -2.8, -2.7, -2.6, -2.5, -2.4, -2.3, -2.2, -2.1, -2. , -1.9, -1.8, -1.7, -1.6, -1.5, -1.4, -1.3, -1.2, -1.1, -1. , -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1, 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. , 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2. , 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3. , 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 4. , 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, 5. ])
-
方法四:使用
numpy.random
模組的函式生成隨機數建立陣列物件產生10個$[0, 1)$範圍的隨機小數,程式碼:
array4 = np.random.rand(10) array4
輸出:
array([0.45556132, 0.67871326, 0.4552213 , 0.96671509, 0.44086463, 0.72650875, 0.79877188, 0.12153022, 0.24762739, 0.6669852 ])
產生10個$[1, 100)$範圍的隨機整數,程式碼:
array5 = np.random.randint(1, 100, 10) array5
輸出:
array([29, 97, 87, 47, 39, 19, 71, 32, 79, 34])
產生20個$\mu=50$,$\sigma=10$的正態分佈隨機數,程式碼:
array6 = np.random.normal(50, 10, 20) array6
輸出:
array([55.04155586, 46.43510797, 20.28371158, 62.67884053, 61.23185964, 38.22682148, 53.17126151, 43.54741592, 36.11268017, 40.94086676, 63.27911699, 46.92688903, 37.1593374 , 67.06525656, 67.47269463, 23.37925889, 31.45312239, 48.34532466, 55.09180924, 47.95702787])
說明:建立一維陣列還有很多其他的方式,比如透過讀取字串、讀取檔案、解析正則表示式等方式,這裡我們暫不討論這些方式,有興趣的讀者可以自行研究。
-
方法一:使用
array
函式,透過巢狀的list
建立陣列物件程式碼:
array7 = np.array([[1, 2, 3], [4, 5, 6]]) array7
輸出:
array([[1, 2, 3], [4, 5, 6]])
-
方法二:使用
zeros
、ones
、full
函式指定陣列的形狀建立陣列物件使用
zeros
函式,程式碼:array8 = np.zeros((3, 4)) array8
輸出:
array([[0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.]])
使用
ones
函式,程式碼:array9 = np.ones((3, 4)) array9
輸出:
array([[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]])
使用
full
函式,程式碼:array10 = np.full((3, 4), 10) array10
輸出:
array([[10, 10, 10, 10], [10, 10, 10, 10], [10, 10, 10, 10]])
-
方法三:使用eye函式建立單位矩陣
程式碼:
array11 = np.eye(4) array11
輸出:
array([[1., 0., 0., 0.], [0., 1., 0., 0.], [0., 0., 1., 0.], [0., 0., 0., 1.]])
-
方法四:透過
reshape
將一維陣列變成二維陣列程式碼:
array12 = np.array([1, 2, 3, 4, 5, 6]).reshape(2, 3) array12
輸出:
array([[1, 2, 3], [4, 5, 6]])
提示:
reshape
是ndarray
物件的一個方法,使用reshape
方法時需要確保調形後的陣列元素個數與調形前陣列元素個數保持一致,否則將會產生異常。 -
方法五:透過
numpy.random
模組的函式生成隨機數建立陣列物件產生$[0, 1)$範圍的隨機小數構成的3行4列的二維陣列,程式碼:
array13 = np.random.rand(3, 4) array13
輸出:
array([[0.54017809, 0.46797771, 0.78291445, 0.79501326], [0.93973783, 0.21434806, 0.03592874, 0.88838892], [0.84130479, 0.3566601 , 0.99935473, 0.26353598]])
產生$[1, 100)$範圍的隨機整數構成的3行4列的二維陣列,程式碼:
array14 = np.random.randint(1, 100, (3, 4)) array14
輸出:
array([[83, 30, 64, 53], [39, 92, 53, 43], [43, 48, 91, 72]])
-
使用隨機的方式建立多維陣列
程式碼:
array15 = np.random.randint(1, 100, (3, 4, 5)) array15
輸出:
array([[[94, 26, 49, 24, 43], [27, 27, 33, 98, 33], [13, 73, 6, 1, 77], [54, 32, 51, 86, 59]], [[62, 75, 62, 29, 87], [90, 26, 6, 79, 41], [31, 15, 32, 56, 64], [37, 84, 61, 71, 71]], [[45, 24, 78, 77, 41], [75, 37, 4, 74, 93], [ 1, 36, 36, 60, 43], [23, 84, 44, 89, 79]]])
-
將一維二維的陣列調形為多維陣列
一維陣列調形為多維陣列,程式碼:
array16 = np.arange(1, 25).reshape((2, 3, 4)) array16
輸出:
array([[[ 1, 2, 3, 4], [ 5, 6, 7, 8], [ 9, 10, 11, 12]], [[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]]])
二維陣列調形為多維陣列,程式碼:
array17 = np.random.randint(1, 100, (4, 6)).reshape((4, 3, 2)) array17
輸出:
array([[[60, 59], [31, 80], [54, 91]], [[67, 4], [ 4, 59], [47, 49]], [[16, 4], [ 5, 71], [80, 53]], [[38, 49], [70, 5], [76, 80]]])
-
讀取圖片獲得對應的三維陣列
程式碼:
array18 = plt.imread('guido.jpg') array18
輸出:
array([[[ 36, 33, 28], [ 36, 33, 28], [ 36, 33, 28], ..., [ 32, 31, 29], [ 32, 31, 27], [ 31, 32, 26]], [[ 37, 34, 29], [ 38, 35, 30], [ 38, 35, 30], ..., [ 31, 30, 28], [ 31, 30, 26], [ 30, 31, 25]], [[ 38, 35, 30], [ 38, 35, 30], [ 38, 35, 30], ..., [ 30, 29, 27], [ 30, 29, 25], [ 29, 30, 25]], ..., [[239, 178, 123], [237, 176, 121], [235, 174, 119], ..., [ 78, 68, 56], [ 75, 67, 54], [ 73, 65, 52]], [[238, 177, 120], [236, 175, 118], [234, 173, 116], ..., [ 82, 70, 58], [ 78, 68, 56], [ 75, 66, 51]], [[238, 176, 119], [236, 175, 118], [234, 173, 116], ..., [ 84, 70, 61], [ 81, 69, 57], [ 79, 67, 53]]], dtype=uint8)
說明:上面的程式碼讀取了當前路徑下名為
guido.jpg
的圖片檔案,計算機系統中的圖片通常由若干行若干列的畫素點構成,而每個畫素點又是由紅綠藍三原色構成的,所以能夠用三維陣列來表示。讀取圖片用到了matplotlib
庫的imread
函式。
-
size
屬性:陣列元素個數程式碼:
array19 = np.arange(1, 100, 2) array20 = np.random.rand(3, 4) print(array19.size, array20.size)
輸出:
50 12
-
shape
屬性:陣列的形狀程式碼:
print(array19.shape, array20.shape)
輸出:
(50,) (3, 4)
-
dtype
屬性:陣列元素的資料型別程式碼:
print(array19.dtype, array20.dtype)
輸出:
int64 float64
ndarray
物件元素的資料型別可以參考如下所示的表格。 -
ndim
屬性:陣列的維度程式碼:
print(array19.ndim, array20.ndim)
輸出:
1 2
-
itemsize
屬性:陣列單個元素佔用記憶體空間的位元組數程式碼:
array21 = np.arange(1, 100, 2, dtype=np.int8) print(array19.itemsize, array20.itemsize, array21.itemsize)
輸出:
8 8 1
說明:在使用
arange
建立陣列物件時,透過dtype
引數指定元素的資料型別。可以看出,np.int8
代表的是8位有符號整數,只佔用1個位元組的記憶體空間,取值範圍是$[-128,127]$。 -
nbytes
屬性:陣列所有元素佔用記憶體空間的位元組數程式碼:
print(array19.nbytes, array20.nbytes, array21.nbytes)
輸出:
400 96 50
-
flat
屬性:陣列(一維化之後)元素的迭代器程式碼:
from typing import Iterable print(isinstance(array20.flat, np.ndarray), isinstance(array20.flat, Iterable))
輸出:
False True
-
base
屬性:陣列的基物件(如果陣列共享了其他陣列的記憶體空間)程式碼:
array22 = array19[:] print(array22.base is array19, array22.base is array21)
輸出:
True False
說明:上面的程式碼用到了陣列的切片操作,它類似於 Python 中
list
型別的切片,但在細節上又不完全相同,下面會專門講解這個知識點。透過上面的程式碼可以發現,ndarray
切片後得到的新的陣列物件跟原來的陣列物件共享了記憶體中的資料,因此array22
的base
屬性就是array19
對應的陣列物件。
和 Python 中的列表類似,NumPy 的ndarray
物件可以進行索引和切片操作,透過索引可以獲取或修改陣列中的元素,透過切片可以取出陣列的一部分。
-
索引運算(普通索引)
一維陣列,程式碼:
array23 = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9]) print(array23[0], array23[array23.size - 1]) print(array23[-array23.size], array23[-1])
輸出:
1 9 1 9
二維陣列,程式碼:
array24 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) print(array24[2]) print(array24[0][0], array24[-1][-1]) print(array24[1][1], array24[1, 1])
輸出:
[7 8 9] 1 9 5 5 [[ 1 2 3] [ 4 10 6] [ 7 8 9]]
程式碼:
array24[1][1] = 10 print(array24) array24[1] = [10, 11, 12] print(array24)
輸出:
[[ 1 2 3] [ 4 10 6] [ 7 8 9]] [[ 1 2 3] [10 11 12] [ 7 8 9]]
-
切片運算(切片索引)
切片是形如
[開始索引:結束索引:步長]
的語法,透過指定開始索引(預設值無窮小)、結束索引(預設值無窮大)和步長(預設值1),從陣列中取出指定部分的元素並構成新的陣列。因為開始索引、結束索引和步長都有預設值,所以它們都可以省略,如果不指定步長,第二個冒號也可以省略。一維陣列的切片運算跟 Python 中的list
型別的切片非常類似,此處不再贅述,二維陣列的切片可以參考下面的程式碼,相信非常容易理解。程式碼:
print(array24[:2, 1:])
輸出:
[[2 3] [5 6]]
程式碼:
print(array24[2]) print(array24[2, :])
輸出:
[7 8 9] [7 8 9]
程式碼:
print(array24[2:, :])
輸出:
[[7 8 9]]
程式碼:
print(array24[:, :2])
輸出:
[[1 2] [4 5] [7 8]]
程式碼:
print(array24[1, :2]) print(array24[1:2, :2])
輸出:
[4 5] [[4 5]]
程式碼:
print(array24[::2, ::2])
輸出:
[[1 3] [7 9]]
程式碼:
print(array24[::-2, ::-2])
輸出:
[[9 7] [3 1]]
關於陣列的索引和切片運算,大家可以透過下面的兩張圖來增強印象,這兩張圖來自《利用Python進行資料分析》一書,它是
pandas
庫的作者 Wes McKinney 撰寫的 Python 資料分析領域的經典教科書,有興趣的讀者可以購買和閱讀原書。 -
花式索引(fancy index)
花式索引(Fancy indexing)是指利用整數陣列進行索引,這裡所說的整數陣列可以是 NumPy 的
ndarray
,也可以是 Python 中list
、tuple
等可迭代型別,可以使用正向或負向索引。一維陣列的花式索引,程式碼:
array25 = np.array([50, 30, 15, 20, 40]) array25[[0, 1, -1]]
輸出:
array([50, 30, 40])
二維陣列的花式索引,程式碼:
array26 = np.array([[30, 20, 10], [40, 60, 50], [10, 90, 80]]) # 取二維陣列的第1行和第3行 array26[[0, 2]]
輸出:
array([[30, 20, 10], [10, 90, 80]])
程式碼:
# 取二維陣列第1行第2列,第3行第3列的兩個元素 array26[[0, 2], [1, 2]]
輸出:
array([20, 80])
程式碼:
# 取二維陣列第1行第2列,第3行第2列的兩個元素 array26[[0, 2], 1]
輸出:
array([20, 90])
-
布林索引
布林索引就是透過布林型別的陣列對陣列元素進行索引,布林型別的陣列可以手動構造,也可以透過關係運算來產生布爾型別的陣列。
程式碼:
array27 = np.arange(1, 10) array27[[True, False, True, True, False, False, False, False, True]]
輸出:
array([1, 3, 4, 9])
程式碼:
array27 >= 5
輸出:
array([False, False, False, False, True, True, True, True, True])
程式碼:
# ~運算子可以實現邏輯變反,看看執行結果跟上面有什麼不同 ~(array27 >= 5)
輸出:
array([ True, True, True, True, False, False, False, False, False])
程式碼:
array27[array27 >= 5]
輸出:
array([5, 6, 7, 8, 9])
提示:切片操作雖然建立了新的陣列物件,但是新陣列和原陣列共享了陣列中的資料,簡單的說,如果透過新陣列物件或原陣列物件修改陣列中的資料,其實修改的是同一塊資料。花式索引和布林索引也會建立新的陣列物件,而且新陣列複製了原陣列的元素,新陣列和原陣列並不是共享資料的關係,這一點透過前面講的陣列的
base
屬性也可以瞭解到,在使用的時候要引起注意。
學習基礎知識總是比較枯燥且沒有成就感的,所以我們還是來個案例為大家演示下上面學習的陣列索引和切片操作到底有什麼用。前面我們說到過,可以用三維陣列來表示影象,那麼透過影象對應的三維陣列進行操作,就可以實現對影象的處理,如下所示。
讀入圖片建立三維陣列物件。
guido_image = plt.imread('guido.jpg')
plt.imshow(guido_image)
對陣列的0軸進行反向切片,實現影象的垂直翻轉。
plt.imshow(guido_image[::-1])
對陣列的1軸進行反向切片,實現影象的水平翻轉。
plt.imshow(guido_image[:,::-1])
將 Guido 的頭切出來。
plt.imshow(guido_image[30:350, 90:300])
統計方法主要包括:sum()
、mean()
、std()
、var()
、min()
、max()
、argmin()
、argmax()
、cumsum()
等,分別用於對陣列中的元素求和、求平均、求標準差、求方差、找最大、找最小、求累積和等,請參考下面的程式碼。
array28 = np.array([1, 2, 3, 4, 5, 5, 4, 3, 2, 1])
print(array28.sum())
print(array28.mean())
print(array28.max())
print(array28.min())
print(array28.std())
print(array28.var())
print(array28.cumsum())
輸出:
30
3.0
5
1
1.4142135623730951
2.0
[ 1 3 6 10 15 20 24 27 29 30]
-
all()
/any()
方法:判斷陣列是否所有元素都是True
/ 判斷陣列是否有為True
的元素。 -
astype()
方法:複製陣列,並將陣列中的元素轉換為指定的型別。 -
dump()
方法:儲存陣列到檔案中,可以透過 NumPy 中的load()
函式從儲存的檔案中載入資料建立陣列。程式碼:
array31.dump('array31-data') array32 = np.load('array31-data', allow_pickle=True) array32
輸出:
array([[1, 2], [3, 4], [5, 6]])
-
fill()
方法:向陣列中填充指定的元素。 -
flatten()
方法:將多維陣列扁平化為一維陣列。程式碼:
array32.flatten()
輸出:
array([1, 2, 3, 4, 5, 6])
-
nonzero()
方法:返回非0元素的索引。 -
round()
方法:對陣列中的元素做四捨五入操作。 -
sort()
方法:對陣列進行就地排序。程式碼:
array33 = np.array([35, 96, 12, 78, 66, 54, 40, 82]) array33.sort() array33
輸出:
array([12, 35, 40, 54, 66, 78, 82, 96])
-
swapaxes()
和transpose()
方法:交換陣列指定的軸。程式碼:
# 指定需要交換的兩個軸,順序無所謂 array32.swapaxes(0, 1)
輸出:
array([[1, 3, 5], [2, 4, 6]])
程式碼:
# 對於二維陣列,transpose相當於實現了矩陣的轉置 array32.transpose()
輸出:
array([[1, 3, 5], [2, 4, 6]])
-
tolist()
方法:將陣列轉成Python中的list
。