Skip to content

Latest commit

 

History

History
971 lines (676 loc) · 21.7 KB

68.NumPy的应用-1.md

File metadata and controls

971 lines (676 loc) · 21.7 KB

NumPy的應用-1

Numpy 是一個開源的 Python 科學計算庫,用於快速處理任意維度的陣列。Numpy 支援常見的陣列和矩陣操作,對於同樣的數值計算任務,使用 NumPy 不僅程式碼要簡潔的多,而且 NumPy 的效能遠遠優於原生 Python,基本是一個到兩個數量級的差距,而且資料量越大,NumPy 的優勢就越明顯。

Numpy 最為核心的資料型別是ndarray,使用ndarray可以處理一維、二維和多維陣列,該物件相當於是一個快速而靈活的大資料容器。NumPy 底層程式碼使用 C 語言編寫,解決了 GIL 的限制,ndarray在存取資料的時候,資料與資料的地址都是連續的,這確保了可以進行高效率的批次操作,遠遠優於 Python 中的list;另一方面ndarray物件提供了更多的方法來處理資料,尤其是和統計相關的方法,這些方法也是 Python 原生的list沒有的。

準備工作

  1. 啟動Notebook

    jupyter notebook

    提示:在啟動Notebook之前,建議先安裝好資料分析相關依賴項,包括之前提到的三大神器以及相關依賴項,包括:numpypandasmatplotlibopenpyxl等。如果使用Anaconda,則無需單獨安裝。

  2. 匯入

    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]])
    
  • 方法二:使用zerosonesfull函式指定陣列的形狀建立陣列物件

    使用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]])
    

    提示reshapendarray物件的一個方法,使用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函式。

陣列物件的屬性

  1. size屬性:陣列元素個數

    程式碼:

    array19 = np.arange(1, 100, 2)
    array20 = np.random.rand(3, 4)
    print(array19.size, array20.size)

    輸出:

    50 12
    
  2. shape屬性:陣列的形狀

    程式碼:

    print(array19.shape, array20.shape)

    輸出:

    (50,) (3, 4)
    
  3. dtype屬性:陣列元素的資料型別

    程式碼:

    print(array19.dtype, array20.dtype)

    輸出:

    int64 float64
    

    ndarray物件元素的資料型別可以參考如下所示的表格。

  4. ndim屬性:陣列的維度

    程式碼:

    print(array19.ndim, array20.ndim)

    輸出:

    1 2
    
  5. 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]$。

  6. nbytes屬性:陣列所有元素佔用記憶體空間的位元組數

    程式碼:

    print(array19.nbytes, array20.nbytes, array21.nbytes)

    輸出:

    400 96 50
    
  7. flat屬性:陣列(一維化之後)元素的迭代器

    程式碼:

    from typing import Iterable
    
    print(isinstance(array20.flat, np.ndarray), isinstance(array20.flat, Iterable))

    輸出:

    False True
    
  8. base屬性:陣列的基物件(如果陣列共享了其他陣列的記憶體空間)

    程式碼:

    array22 = array19[:]
    print(array22.base is array19, array22.base is array21)

    輸出:

    True False
    

    說明:上面的程式碼用到了陣列的切片操作,它類似於 Python 中list型別的切片,但在細節上又不完全相同,下面會專門講解這個知識點。透過上面的程式碼可以發現,ndarray切片後得到的新的陣列物件跟原來的陣列物件共享了記憶體中的資料,因此array22base屬性就是array19對應的陣列物件。

陣列的索引和切片

和 Python 中的列表類似,NumPy 的ndarray物件可以進行索引和切片操作,透過索引可以獲取或修改陣列中的元素,透過切片可以取出陣列的一部分。

  1. 索引運算(普通索引)

    一維陣列,程式碼:

    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]]
    
  2. 切片運算(切片索引)

    切片是形如[開始索引:結束索引:步長]的語法,透過指定開始索引(預設值無窮小)、結束索引(預設值無窮大)和步長(預設值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 資料分析領域的經典教科書,有興趣的讀者可以購買和閱讀原書。

  3. 花式索引(fancy index)

    花式索引(Fancy indexing)是指利用整數陣列進行索引,這裡所說的整數陣列可以是 NumPy 的ndarray,也可以是 Python 中listtuple等可迭代型別,可以使用正向或負向索引。

    一維陣列的花式索引,程式碼:

    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])
    
  4. 布林索引

    布林索引就是透過布林型別的陣列對陣列元素進行索引,布林型別的陣列可以手動構造,也可以透過關係運算來產生布爾型別的陣列。

    程式碼:

    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]

其他方法

  1. all() / any()方法:判斷陣列是否所有元素都是True / 判斷陣列是否有為True的元素。

  2. astype()方法:複製陣列,並將陣列中的元素轉換為指定的型別。

  3. dump()方法:儲存陣列到檔案中,可以透過 NumPy 中的load()函式從儲存的檔案中載入資料建立陣列。

    程式碼:

    array31.dump('array31-data')
    array32 = np.load('array31-data', allow_pickle=True)
    array32

    輸出:

    array([[1, 2],
           [3, 4],
           [5, 6]])
    
  4. fill()方法:向陣列中填充指定的元素。

  5. flatten()方法:將多維陣列扁平化為一維陣列。

    程式碼:

    array32.flatten()

    輸出:

    array([1, 2, 3, 4, 5, 6])
    
  6. nonzero()方法:返回非0元素的索引。

  7. round()方法:對陣列中的元素做四捨五入操作。

  8. sort()方法:對陣列進行就地排序。

    程式碼:

    array33 = np.array([35, 96, 12, 78, 66, 54, 40, 82])
    array33.sort()
    array33

    輸出:

    array([12, 35, 40, 54, 66, 78, 82, 96])
    
  9. swapaxes()transpose()方法:交換陣列指定的軸。

    程式碼:

    # 指定需要交換的兩個軸,順序無所謂
    array32.swapaxes(0, 1)

    輸出:

    array([[1, 3, 5],
          [2, 4, 6]])
    

    程式碼:

    # 對於二維陣列,transpose相當於實現了矩陣的轉置
    array32.transpose()

    輸出:

    array([[1, 3, 5],
          [2, 4, 6]])
    
  10. tolist()方法:將陣列轉成Python中的list