2019年10月30日 星期三

Python 學習筆記 : Numpy (一) : 建立陣列

最近因為看計算物理學的書時, 發現要用到 Numpy 與 Scipy 這兩個計算套件, 前兩章靠之前的初學還看得下去, 但後面就吃力了, 於是下定決心認真學習資料科學, 這也是機器學習的蹲馬步基本功, 資料科學必須練得紮實, 機器學習才能學得好. 計算物理學就等資料科學學完再來吧!

Python 資料科學基本上包含三部分 :
  1. Numpy
  2. Pandas
  3. Matplotlib
這三個套件都是免費的開源軟體, 與 MATLAB 有相似度極高之 API 介面與運算效能, 其中 Numpy 是資料科學的第一課, 是用 Python 進行高維陣列大量運算的主要工具.

Python 內建資料型態 list 屬於動態型態 (C 語言的 struct 結構), 資料結構中含有指標群指向元素物件, 但可儲存異質資料這優點的代價是, 若使用 list 儲存同質性 (固定型態) 的數值資料, 在資料存取與運算上速度太慢缺乏效率. 雖然 Python 3.3 之後內建了 array 模組, 但它只支援一維陣列且陣列運算函數不多, 故 Python 內建資料型態均不適合用來做高維度陣列運算, 參考 :

Python 學習筆記 : 陣列模組 array 測試

Jim HuguninTravis Oliphant (Anaconda 的創辦人與 Scipy 開發者) 分別在 1996 年與 2005 年相繼開發了 Python 的開源數學運算擴充函式庫 Numeric 與 Numarray, 後來於 2006 年結合發展成 Numpy 套件. Numpy 採用 BLASLAPACK 線性代數函式庫 API, 透過底層以 C 與 Fortran 撰寫的程式碼來提升高維向量與陣列運算效能, 其函數介面與功能與 MATLAB 非常類似, 參考 :

# 維基 : Numpy

Numpy 套件以 ndarray 物件為核心, 由資料本身與元資料 (metadata, 陣列維度與型別 dtype 等) 資訊構成, 其資料均為同質性 (homogeneous, 即元素的資料型別都一樣), 每一個元素在記憶體中都是緊密配置在連續的記憶體空間, 因此存取元素的效率比串列高; 此外, Numpy 提供了大量以 C 語言撰寫的 ufunc 函數 (universal function) 以進行高效運算.

Numpy 是第三方套件, 必須先安裝才能使用, 安裝指令如下 :

pip3 install numpy

如果要做科學計算, 則可以直接安裝 Scipy, 它會自動安裝 Numpy, 因為 Scipy 是以 Numpy 為基礎而建立的 :

pip3 install scipy

Numpy 的官方文件參考 :

https://numpy.org/devdocs/user/index.html
https://numpy.org/devdocs/reference/index.html
https://docs.scipy.org/doc/numpy/reference/arrays.ndarray.html

本篇測試範例參考了如下書籍 :

# Python 程式設計經典 (吳昱禎, 碁峰)
# Python 資料運算與分析實戰 (中久喜見司, 旗標)
# Python 資料科學與人工智慧 (陳允傑, 旗標)
# Python 資料科學學習手冊 (何敏煌譯, 歐萊里)
# Deep learning from scratch (齋藤康毅, 歐萊里)
# Python 在大數據科學計算上的最佳實作 (張若愚, 博碩)


一. 載入 Numpy 套件 :

Numpy 是第三方套件, 因此使用前須載入, 通常會取 np 當作別名 :

import numpy as np 

這樣就可以用別名 np 作為名稱空間呼叫 numpy 的函數, 例如查詢版本或用 help() 查詢 Numpy 使用說明 :


 numpy 查詢指令 說明
 np.__version__ 查詢 Numpy 版本
 np.info(np) 查詢 Numpy 訊息
 help(np) 查詢 Numpy 使用說明, 可查詢函數用法例如 np.arange
 np.lookfo('函數名') 查詢函數用法文件


>>> import numpy as np    
>>> np.__version__               #查詢 Numpy 版本
'1.16.0'


二. 建立陣列 :

陣列是一種資料結構, 用來儲存具有相同資料型態的有序 (ordered) 元素, 這些元素都是配置在連續的記憶體中. 在資料科學中, 一維的數值陣列稱為向量 (vector); 二維數值陣列稱為矩陣 (matrix); 三維以上的數值陣列則稱為張量 (tensor). 如以張量作為數值序列的一般化型態名稱, 向量可稱為一維張量; 矩陣是二維張量, 三維陣列則是三維張量.

最常用的陣列是二維的矩陣, EXCEL 試算表就是矩陣結構. 對於矩陣而言, 其維度 i*j 表示 "列*行", 意即此二維陣列元素有 i 列 j 行 (注意, 台灣習慣是橫列直行, 但在看強國人出版的簡體書時要注意, 他們的用法是直列橫行), 在 Python 與 Numpy 中, 陣列的索引都是 0 起始的.

通常使用元組來表示陣列的形狀 (shape), 例如形狀為 (2, 3, 4) 的陣列表示這是一個三維陣列, 它有三個軸 (axis), 依序是軸 0, 軸 1, 與軸 3 (axis 也是 0 起始的), 長度分別是 2, 3, 4.

Numpy 提供如下常用函數可用來建立陣列, 這些函數都是 Numpy 的全域方法 (即類別方法), 呼叫這些函數都會傳回一個 numpy.ndarray 陣列物件 :


 np 建立陣列方法 說明
 array(串列或元組) 傳入串列或元組來建立陣列, 傳回 numpy.ndarray 物件
 arange(e) 建立元素值為 0, 1, 2 ... (b-1) 的陣列
 arange(b, e) 建立元素值為 b, b+1, b+2 ... (e-1) 的陣列
 arange(b, e, s) 建立元素值為 b, b+s, b+2s ... (e-1) 的陣列 (步階為 s)
 linspace(b, e) 建立 50 個等差數列元素之陣列 (含 e), 公差為 (e-b)/49
 linspace(b, e, n) 建立 n 個等差數列元素之陣列 (含 e), 公差為 (e-b)/(n-1) 
 logspace(b, e) 建立 50 個基底為 10 等比數列元素之陣列 (含 e)
 logspace(b, e, n) 建立 n 個基底為 10 等比數列元素之陣列 (含 e)
 logspace(b, e, n, base=k) 建立 n 個基底為 k 等比數列元素之陣列 (含 e)
 zeros(n) 建立 n 個元素的一維零陣列 (全部元素為 0)
 zeros((i, j)) 建立 i*j 維 (i 列 j 行) 的零陣列 (全部元素為 0)
 ones(n) 建立 n  個 元素的一維壹陣列 (全部元素為 1)
 ones((i, j)) 建立 i*j 維 (i 列 j 行) 的壹陣列 (全部元素為 1)
 full(n, k) 建立 n 個元素的一維陣列 (全部元素為 k)
 full((i, j), k) 建立 i*j 維 (i 列 j 行) 的陣列 (全部元素為 k)
 zeros_like(a) 建立與範本陣列 a 結構一樣的零陣列 (全部元素為 0)
 ones_like(a) 建立與範本陣列 a 結構一樣的壹陣列 (全部元素為 1)
 identity(n) 建立 n*n 維 (n 列 n 行) 的對角矩陣 (對角元素為 1, 其餘為 0)
 eye(n) 建立 n*n 維 (n 列 n 行) 的對角矩陣 (對角元素為 1, 其餘為 0)
 eye(n, k) 建立 n*n 維 (n 列 n 行) 的對角矩陣 (第 k 條對角元素為 1, 其餘為 0)
 diag(v, k) v=二維傳回 v 指定對角線向量; v=一維建立以 v 為對角線之方陣
 empty(n) 建立 n 個元素的未初始化一維陣列 
 empty((i, j)) 建立 i*j 維 (i 列 j 行) 的未初始化陣列
 random.rand() 傳回 0~1 之間的一個隨機浮點數 (純量)
 random.rand(n) 建立 n 個元素的一維隨機陣列 (均勻分布隨機浮點數)
 random.rand(i, j) 建立 i*j 維 (i 列 j 行) 的隨機陣列 (均勻分布隨機浮點數)
 random.randint(L, H, size) 建立 [L, H] 間形狀為 size (元組) 的隨機陣列 (常態分佈隨機整數)
 random.randn(n) 建立 n 個元素的一維常態分布隨機陣列 (常態分佈隨機浮點數)
 random.random((i, j)) 同 rand() 建立 i*j 維 (i 列 j 行) 的隨機陣列 (均勻分布隨機浮點數)
 fromfunction(func, shape) 依據指定之外形  shape 執行函數 func 建立陣列


其中 array(), arange(), linspace(), 用來建立特定元素值的陣列; random 模組的 rand() 等函式用來建立不特定 (隨機) 陣列, 其餘函數則用來建立預設內容之陣列.

上表中的函數都有一個備選參數 dtype 可用來指定元素之數值資料型態, 每一個函數有個別之預設值 :


 dtype 說明
 bool_ 布林值 (byte)
 int_ 預設之整數型態 (int32 或 int64)
 intc C 語言之 int (int32 或 int64)
 intp 做指標用之整數 (int32 或 int64)
 int8 有號 8 位元整數 (值 -128 至 127)
 int16 有號 16 位元整數 (值 -32768 至 32767)
 int32 有號 32 位元整數 (值 -2147483648 至 2147483647)
 int64 有號 64 位元整數 (值 -9223372036854775808 至 9223372036854775807)
 uint8 無號 8 位元整數 (值 0 至 255)
 uint16 無號 16 位元整數 (值 0 至 65535)
 uint32 無號 32 位元整數 (值 0 至 429497295)
 uint64 無號 64 位元整數 (值 0 至 18446744073709551615)
 float_ 同 float64
 float16 16 位元浮點數 (半精度), 含符號位元 1, 指數位元 5, 小數 10 位元
 float32 32 位元浮點數 (單精度), 含符號位元 1, 指數位元 8, 小數 23 位元
 float64 64 位元浮點數 (倍精度), 含符號位元 1, 指數位元 11, 小數 52 位元
 complex_ 同 complex128
 complex64 64 位元複數
 complext128 128 位元複數


dtype 是上面每一個建立陣列的函數都有的備選參數, 其設定方式可直接指定, 也可用字串, 也可以用匿名 np 存取 :
  1. dtype=float64
  2. dtype='float64'
  3. dtype=np.float64
numpy.ndarray 物件具有如下常用屬性 :


 ndarray 物件常用屬性 說明
 dtype 陣列元素之資料型態, 例如 int32/int64/float64 等
 shape 陣列的形狀, 型態為 tuple, 例如 (2, 3, 4)
 ndim 陣列的軸數或維 (D) 度
 size 陣列的元素總數
 itemsize 每個元素所佔之 bytes 數
 nbytes 整個陣列物件所佔之 bytes 數=itemsize*size


查詢形狀 (內部結構) 除了可用物件的 shape 屬性外, 也可以呼叫全域方法 numpy.shape(a) 函數, 只要將陣列當參數傳進去即可.

陣列的形狀可以呼叫物件方法 reshape() 並傳入一個元組來改變, 它會傳回一個新陣列參考, 但此新陣列與原陣列元素共用記憶體空間, 新陣列只是陣列外觀 (view) 不同而已, 元素仍然是儲存在原來的記憶體位址不變, 因此若更改新陣列的元素值連帶舊陣列元素也會改變.


1. 從元組或串列字面值建立陣列 :   

呼叫 np.array() 可建立一維或多維陣列, 其介面如下 :

numpy.array(object, dtype=None, copy=True, order='K', subok=False, ndmin=0)

可見至少必須傳入一個物件參數, 通常是元組或串列字面值, array() 會將其轉換成 numpy.ndarray 物件, 例如 :

>>> a=np.array([1,2,3])      #將串列轉成一維陣列
>>> type(a)
<class 'numpy.ndarray'>              #陣列維 numpy.ndarray 型別
>>> print(a)                          #顯示陣列 a 內容
array([1, 2, 3]) 
>>> b=np.array((1,2,3))      #將元組轉成一維陣列
>>> type(b)
<class 'numpy.ndarray'>   
>>> print(b)                         #顯示陣列 b 內容
array([1, 2, 3])
>>> a == b                            #依序比較陣列元素之值
array([ True,  True,  True])   
>>> c=np.array([[1,2,3],[4,5,6]])    #將二維串列轉成二維陣列
>>> type(c)                         
<class 'numpy.ndarray'>   
>>> print(c)                                      #顯示陣列 b 內容
array([[1, 2, 3],
       [4, 5, 6]])
>>> np.array(range(10))                 #呼叫 range() 產生串列再轉成陣列
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> np.array(range(1,10))              #呼叫 range() 產生串列再轉成陣列
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> np.array([range(i, i+3) for i in (2, 4, 6)])    #使用串列生成式產生串列再轉成陣列
array([[2, 3, 4],
       [4, 5, 6],
       [6, 7, 8]])

接著檢視 ndarray 物件的屬性 :

>>> a=np.array([1,2,3])    #一維整數陣列
>>> a.dtype                        #元素型態為 int32
dtype('int32')
>>> a.shape                        #形狀為 1*3
(3,)
>>> np.shape(a)                 #查詢形狀也可呼叫 numpy.shape() 傳入陣列
(3,)
>>> a.ndim                         #維度為一維
1
>>> a.size                            #有 3 個元素
3
>>> a.itemsize                     #每個元素佔 4 bytes (4*8=32)
4
>>> a.nbytes                        #整個陣列佔 3*4=12 bytes
12

呼叫陣列物件的 reshape() 方法並傳入一個元組會傳回指定形狀之陣列, 但這是原陣列的視圖 (view), 傳回的參考仍指向原陣列的記憶體空間 (即其元素與原陣列是共用記憶體), 兩者只是外觀不同而已, 因此改變傳回陣列之元素也會改變原陣列內容 :

>>> a=np.array(range(12))   #建立 1*12 的一維陣列
>>> print(a)   
[ 0  1  2  3  4  5  6  7  8  9 10 11]
>>> b=a.reshape((3, 4))         #改變形狀為 3*4
>>> print(b)   
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
>>> b[0][0]=1                          #改變 b 的第 0 元素
>>> print(b)                             #b[0][0] 變 1 了
[[ 1  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
>>> print(a)                              #a[0][0] 也變 1 了
[ 1  1  2  3  4  5  6  7  8  9 10 11]

Numpy 提供了 may_share_memory() 函數來檢驗兩個 ndarray 物件是否共用記憶體, 例如上面從陣列 a 變形為 b, 兩者共用記憶體, 呼叫 may_share_memory(a, b) 會傳回 True :

>>> a=np.array(range(12))   
>>> b=a.reshape((3, 4))   
>>> np.may_share_memory(a, b)     # a, b 陣列共用記憶體
True  

注意, 傳入 array() 的串列或元組結構必須一致 (即元素長度必須相同), 否則會變成結構性陣列, 其元素仍然是串列, 而非陣列元素, 例如 :

>>> a1=np.array([[1,2,3],[4,5,6]])   
>>> print(a1) 
[[1 2 3]
 [4 5 6]]
>>> a2=np.array([[1,2,3],[4,5]]) 
>>> print(a2)   
[list([1, 2, 3]) list([4, 5])] 

上面 a2 陣列的第二個串列少了 1 個元素, 因此 Numpy 會以串列物件儲存, 而不是全部轉成陣列.


2. 建立等差數列之陣列 :     

呼叫 arange() 與 linspace() 這兩個函數都可建立以等差數列為元素的一維陣列, 兩者的差別是 linspace() 預設有包含結束值, 而 arange() 則不包含, 即產生半開數列 [b, e); 其次是傳入三個參數時 arange(b, e, s) 第三個參數 s 是步階值 (即公差), 而 linspace(b, e, n) 的第三參數 n 則是指元素個數.


(1). arrange() 函數 :

函數 arrange() 是 Python 內建函數 range() 的 Numpy 陣列版, 它可傳入 1~3 個參數, 當只有一個參數時 arange(e) 會建立一維陣列, 其元素為 0~e (但不包含 e) 步階為 1 的等差數列 (即公差=1), 介面如下 :

numpy.arange([start, ]stop, [step, ]dtype=None)

例如 :

>>> np.arange(10)            #建立 [0, 10) 步階為 1 的一維陣列
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

如果傳入兩個參數, arange(b, e) 會建立一維陣列, 其元素為 b~e  (但不包含 e) 步階為 1 的等差數列, 例如 :

>>> np.arange(2, 10)         #建立 [2, 10) 步階為 1 的一維陣列
array([2, 3, 4, 5, 6, 7, 8, 9])

如果傳入三個參數, arange(b, e, s) 會建立一維陣列, 其元素為 b~e 但不包含 e, 步階為 s 的等差數列, 例如 :

>>>  np.arange(1, 10, 2)    #建立 [1, 10) 步階為 2 的一維陣列
array([1, 3, 5, 7, 9])

三個參數也可以是浮點數, 例如 :

>>> np.arange(1.2, 5.6, 0.8) 
array([1.2, 2. , 2.8, 3.6, 4.4, 5.2])
>>> np.arange(1.2, 2.4, 0.2)   
array([1.2, 1.4, 1.6, 1.8, 2. , 2.2])              #不含結束值 2.4

注意, 雖然在起訖是整數情況下, arrange() 所產生的等差數列不包含結束的 end 數值, 但若 end 是浮點數時因為捨入誤差的關係, 產生的有可能會包含 end 參數, 例如 :

>>> np.arange(0.1, 0.4, 0.1)    # 注意 0.4 有被包含在數列中
array([0.1, 0.2, 0.3, 0.4])
>>> np.arange(0.1, 0.5, 0.1)   
array([0.1, 0.2, 0.3, 0.4])
>>> np.arange(0.1, 0.6, 0.1)   
array([0.1, 0.2, 0.3, 0.4, 0.5])   
>>> np.arange(1, 1.3, 0.1)       # 注意 1.3 有被包含在數列中
array([1. , 1.1, 1.2, 1.3])  

參考 :



(2). linespace() 函數 :

函數 linspace() 也可以建立元素是等差數列的一維陣列, 它至少需傳入兩個參數, 分別為開始值與結束值, linspace(b, e) 會建立一個 50 個元素的一維陣列, 其元素為範圍 b~e (包含 e), 步階或公差為 (e-b)/49 的等差數列, 介面如下 :

numpy.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0)

例如 :

>>> np.linspace(0, 10)       #建立 [0, 10] 步階為 10/49 的一維陣列
array([ 0.        ,  0.20408163,  0.40816327,  0.6122449 ,  0.81632653,
        1.02040816,  1.2244898 ,  1.42857143,  1.63265306,  1.83673469,
        2.04081633,  2.24489796,  2.44897959,  2.65306122,  2.85714286,
        3.06122449,  3.26530612,  3.46938776,  3.67346939,  3.87755102,
        4.08163265,  4.28571429,  4.48979592,  4.69387755,  4.89795918,
        5.10204082,  5.30612245,  5.51020408,  5.71428571,  5.91836735,
        6.12244898,  6.32653061,  6.53061224,  6.73469388,  6.93877551,
        7.14285714,  7.34693878,  7.55102041,  7.75510204,  7.95918367,
        8.16326531,  8.36734694,  8.57142857,  8.7755102 ,  8.97959184,
        9.18367347,  9.3877551 ,  9.59183673,  9.79591837, 10.        ])

因為預設是 50 個元素, 中間有 49 段區間, 故步階值為 (10-0)/49=0.20408163.

如果傳入三個參數 linspace(b, e, n), 則第三參數 n 為元素個數, 它會將此 b~e 區段均分成 (n-1) 段 以產生元素, 因此步階值或公差為 (e-b)/(n-1), 例如 :

>>> np.linspace(0, 10, 9)      #[0~10] 分 8 段, 步階=10/8=1.25
array([ 0.  ,  1.25,  2.5 ,  3.75,  5.  ,  6.25,  7.5 ,  8.75, 10.  ])
>>> np.linspace(0, 10, 10)    #[0~10] 分 9 段, 步階=10/9=1.1111
array([ 0.        ,  1.11111111,  2.22222222,  3.33333333,  4.44444444,
        5.55555556,  6.66666667,  7.77777778,  8.88888889, 10.        ])
>>> np.linspace(0, 10, 11)    #[0~10] 分 10 段, 步階=10/10=1
array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10.])


3. 建立等比數列之陣列 :

呼叫 numpy.logspace() 可建立元素為等比數列的陣列, 其介面如下 :

numpy.logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None, axis=0)[source]

可見 logspace() 至少須傳入兩個參數 (整數, 浮點數, 或複數均可), 注意, 這兩個參數是指數, 不是數列的首項與末項, 預設基底為 10, 等比數列起始值為 base**start (即 base 的 start 次方), 結束值為 base**stop, 預設會建立 50 個元素的等比數列陣列 (含結束值). 例如 np(0, 2) 會建立 [0, 100] 之間 50 個等比數列元素之陣列 :

>>> np.logspace(0, 2)     #建立 0~100 間 50 個等比數列元素之陣列
array([  1.        ,   1.09854114,   1.20679264,   1.32571137,
         1.45634848,   1.59985872,   1.75751062,   1.93069773,
         2.12095089,   2.32995181,   2.55954792,   2.8117687 ,
         3.0888436 ,   3.39322177,   3.72759372,   4.09491506,
         4.49843267,   4.94171336,   5.42867544,   5.96362332,
         6.55128557,   7.19685673,   7.90604321,   8.68511374,
         9.54095476,  10.48113134,  11.51395399,  12.64855217,
        13.89495494,  15.26417967,  16.76832937,  18.42069969,
        20.23589648,  22.22996483,  24.42053095,  26.82695795,
        29.47051703,  32.37457543,  35.56480306,  39.06939937,
        42.9193426 ,  47.14866363,  51.79474679,  56.89866029,
        62.50551925,  68.6648845 ,  75.43120063,  82.86427729,
        91.0298178 , 100.])

可見 np.logspace(0, 2) 的公比為 1.09854114, 依據等比數列公式 :


np.logspace(0, 2) 的首項為 10**0=1, 末項為 10**2=100, 因此公比為末項除以首項後再開 49 次方根, 即 (100/1)**(1/49)=1.09854114.

可傳入第三參數 num 指定元素個數, 例如 :

>>> np.logspace(0, 2, 10)   #建立 [1, 100] 10 個元素等比陣列
array([  1.        ,   1.66810054,   2.7825594 ,   4.64158883,
         7.74263683,  12.91549665,  21.5443469 ,  35.93813664,
        59.94842503, 100.        ])

首項與末項同樣是 1 與 100, 但元素為 10 個, 故公比要開 9 次方為 (100/1)**(1/9)=1.66810054.

可傳入 base 參數指定基底, 例如 :

>>> np.logspace(0, 2, 10, base=2)    #建立基底=10 的等比陣列
array([1.        , 1.16652904, 1.36079   , 1.58740105, 1.85174942,
       2.16011948, 2.5198421 , 2.93946898, 3.42897593, 4.])

因基底為 2, 因此首項為 2**0=1, 末項=2**2=4, 所以公比為 (4/1)**(1/9)=1.16652904.


4. 建立預設內容陣列 : 

呼叫 zeros(), ones(), full(), zeros_like(), ones_like(), eye(), diag(), identity() 等函數可建立預設內容之陣列, 例如零陣列, 壹陣列, 與對角陣列等, 它們都可以傳入 dtype 參數指定資料型態.


(1). 零陣列 : 

zeros() 必須傳入一個參數, 當傳入一個正整數時, zeros(n) 表示要建立有 n 個元素的一維零陣列 (所有元素為 0, 若 n=0 則建立空陣列); 而傳入一個元組時 zeros((i, j)) 表示要建立一個 i 列 j 行的零陣列, 例如 :

>>> np.zeros(5)                      #建立 5 個元素的一維零陣列 
array([0., 0., 0., 0., 0.])
>>> np.zeros(5, dtype=int)    #指定元素資料型態為 int
array([0, 0, 0, 0, 0])
>>> np.zeros((2, 3))               #建立 i 列 j 行的零陣列
array([[0., 0., 0.],
       [0., 0., 0.]])
>>> np.zeros(0)                      #建立空陣列 (預設資料型態為 float64)
array([], dtype=float64)

zeros_like() 可依據傳入之陣列結構, 建立與其維度相同之零陣列, 例如 :

>>> a=np.array([[1, 2 , 3], [4, 5 ,6]])    #建立一個 2*3 陣列
>>> print(a)   
[[1 2 3]
 [4 5 6]]
>>> b=np.zeros_like(a)          #依據 a 陣列結構建立零陣列
>>> print(b)       
[[0 0 0]
 [0 0 0]]
>>> b.dtype            #zeros_like() 建立之陣列預設型態為整數
dtype('int32')

可見 zeros() 建立元素之預設型態為 float 浮點數, 而 zeros_like() 則是整數.


(2). 壹陣列 :

ones() 與 zeros() 一樣必須傳入一個參數, 當傳入一個正整數時, ones(n) 表示要建立有 n 個元素的一維壹陣列 (所有元素為 1, 若 n=0 建立空陣列); 而傳入一個元組時 ones((i, j)) 表示要建立一個 i 列 j 行的壹陣列, 例如 :

>>> np.ones(5)                        #建立 5 個元素的一維壹陣列
array([1., 1., 1., 1., 1.])
>>> np.ones(5, dtype=int)      #指定元素資料型態為 int
array([1, 1, 1, 1, 1])
>>> np.ones((2, 3))                  #建立 i 列 j 行的壹陣列
array([[1., 1., 1.],
       [1., 1., 1.]])
>>> np.ones(0)                        #建立空陣列
array([], dtype=float64)

ones_like() 可依據傳入之陣列結構, 建立與其維度相同之壹陣列, 例如 :

>>> a=np.array([[1, 2 , 3], [4, 5 ,6]])    #建立一個 2*3 陣列
>>> print(a)     
[[1 2 3]
 [4 5 6]]
>>> b=np.ones_like(a)         #依據 a 陣列結構建立壹陣列
>>> print(b) 
[[1 1 1]
 [1 1 1]]
>>> b.dtype            #ones_like() 建立之陣列預設型態為整數
dtype('int32')


(4). 填滿陣列 :

呼叫 numpy.full() 可建立元素全為指定值的填滿陣列, 其介面為 :

numpy.full(shape, fill_value, dtype=None, order='C')

第一個參數為陣列外形, 可傳入一個正整數, 元組或串列; 第二個參數是要填入的元素值. 當第一參數是正整數時, full(n, k) 會建立一個具有 n 個元素值為 k 的一維陣列; 當第一參數是元組或串列時, full(shape, k) 會依據外形 shape 建立元素值為 k 的高維陣列, 例如 :

>>> np.full(5, 3.14159)         #用 pi 填滿一維陣列
array([3.14159, 3.14159, 3.14159, 3.14159, 3.14159])
>>> np.full((2, 3), 3.14159)   #用 pi 填滿二維陣列
array([[3.14159, 3.14159, 3.14159],
       [3.14159, 3.14159, 3.14159]])
>>> np.full([2, 3], 3.14159)   #用 pi 填滿二維陣列   
array([[3.14159, 3.14159, 3.14159],
       [3.14159, 3.14159, 3.14159]])
>>> np.full([2, 3, 4], 9)          #用 9 填滿三維陣列
array([[[9, 9, 9, 9],
        [9, 9, 9, 9],
        [9, 9, 9, 9]],
       [[9, 9, 9, 9],
        [9, 9, 9, 9],
        [9, 9, 9, 9]]])


(5). 對角陣列 (矩陣) :

呼叫 identity() 會建立一個中央對角線元素值為 1 的對角方陣, 介面如下 :

numpy.identity(n, dtype=None)

至少須傳入一個正整數參數以建立一個 n*n 方陣 :

>>> np.identity(4)          #建立 4*4 對角方陣 (浮點數)
array([[1., 0., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.]])
>>> np.identity(4, dtype=int)      #指定型態為整數
array([[1, 0, 0, 0],
       [0, 1, 0, 0],
       [0, 0, 1, 0],
       [0, 0, 0, 1]])

呼叫 eye() 可建立一個指定對角線元素值為 1.0 (預設為中央對角線), 其餘元素為 0 的對角矩陣 (元素值預設為浮點數), 介面如下 :

numpy.eye(n, m=None, k=0, dtype=np.float, order='C')

第一參數 n 為矩陣的列數, 第二參數 m 為矩陣的行數, 第三參數 k 為元素值為 1 之對角線編號, 預設 0 為中央正對角線, k 為正整數時, 由中央往右上角第 k 條對角線元素全部為 1, 其餘為 0; k 為負整數時, 由中央往左下角第 k 條對角線元素全部為 1, 其餘為 0, 例如 :

>>> np.eye(4)            #建立 4*4 單位方陣
array([[1., 0., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.]])
>>> np.eye(4, k=1)   #建立 4*4 對角矩陣
array([[0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.],
       [0., 0., 0., 0.]])
>>> np.eye(4, k=-1)   #左下方第 1 條對角線填 1
array([[0., 0., 0., 0.],
       [1., 0., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.]])
>>> np.eye(4, k=2)     #右上方第 2 條對角線填 1
array([[0., 0., 1., 0.],
       [0., 0., 0., 1.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])
>>> np.eye(4, k=3)     #右上方第 3 條對角線填 1
array([[0., 0., 0., 1.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])
>>> np.eye(4, k=4)    #右上方第 4 條對角線填 1 (超過了)
array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])
>>> np.eye(4, 3)        #4*3 矩陣 (非方陣)
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.],
       [0., 0., 0.]])

可見當 k 超過對角線總數時會變成建立零陣列.

另外一個與對角陣列有關的函數是 diag(), 其介面如下 :

numpy.diag(v, k=0)

第一個函數是一維或二維陣列, 第二個參數與 eye() 中的 k 參數一樣, 中央對角線 k 為 0 (預設值), 往右上走之對角線依序為 1, 2, ... ; 往左下走之對角線依序為 -1, -2, ... 等等. 若第一參數 v 傳入一個二維陣列, 則以一維陣列形式將其第 k 的對角線元素傳回 (getter); 若 v 傳入一個一維陣列, 則以該陣列元素取代第 k 個對角線.

首先建立一個 5*5 方陣如下 :

>>> a=np.array(range(25))      #建立 [0, 1, 2, .... , 24] 一維陣列
>>> x=a.reshape((5,5))             #調整形狀建立 5*5 方陣
>>> print(x)     
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, 24]])

將此二維陣列傳給 diag() 會傳回其第 k 的對角線元素 :

>>> np.diag(x)                          #傳回方陣 x 的中央對角線
array([ 0,  6, 12, 18, 24])                           #傳回值為一維陣列
>>> np.diag(x, k=1)                 #傳回方陣 x 的 k=1 對角線
array([ 1,  7, 13, 19])
>>> np.diag(x, k=-1)                #傳回方陣 x 的 k=-1 對角線
array([ 5, 11, 17, 23])

如果傳入一維陣列, 則會傳回一個指定對角線是該一維陣列的方陣, 例如 :

>>> np.diag([1, 2, 3, 4, 5])       #建立指定中央對角線之方陣
array([[1, 0, 0, 0, 0],
       [0, 2, 0, 0, 0],
       [0, 0, 3, 0, 0],
       [0, 0, 0, 4, 0],
       [0, 0, 0, 0, 5]])
>>> np.diag([1, 2, 3, 4, 5], k=1)      #k=1 右上對角線方陣
array([[0, 1, 0, 0, 0, 0],
       [0, 0, 2, 0, 0, 0],
       [0, 0, 0, 3, 0, 0],
       [0, 0, 0, 0, 4, 0],
       [0, 0, 0, 0, 0, 5],
       [0, 0, 0, 0, 0, 0]])
>>> np.diag([1, 2, 3, 4, 5], k=-1)    #k=-1 左下對角線方陣
array([[0, 0, 0, 0, 0, 0],
       [1, 0, 0, 0, 0, 0],
       [0, 2, 0, 0, 0, 0],
       [0, 0, 3, 0, 0, 0],
       [0, 0, 0, 4, 0, 0],
       [0, 0, 0, 0, 5, 0]])


(6). 空陣列 :

呼叫 empty() 會建立一個指定形狀的未初始化陣列, 其元素值為所配置記憶體內原本的值, 其介面如下 :

numpy.empty(shape, dtype=float, order='C')

第一參數為陣列外形, 可以是正整數, 或元素為正整數之元組或串列 (通常用元組), 例如 :

>>> np.empty(4)              #建立 4 個元素的未初始化一維陣列
array([1., 1., 1., 1.])
>>> np.empty((4,))          #建立 4 個元素的未初始化一維陣列
array([1., 1., 1., 1.])
>>> np.empty((2, 3))       #建立 2*3 未初始化二維陣列
array([[3.14159, 3.14159, 3.14159],
       [3.14159, 3.14159, 3.14159]])
>>> np.empty((2, 3, 4))   #建立 2*3*4 未初始化三維陣列
array([[[0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.]],
       [[0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.]]])

可見傳入 (4, ) 相當於傳入整數 4, 都是建立一維陣列.


5. 建立隨機內容之陣列 :

Numpy 底下的 random 模組提供了幾個方法可用來建立隨機陣列, 不過如果要讓隨機數可預測, 可以透過呼叫 np.random.seed() 設定隨機種子, 這樣在同一隨機種子下重複呼叫時, 傳回的隨機數列值與順序是一樣的 :

np.random.seed(i)

傳入參數 i 須為正整數.


(1). rand() 與 random() 函數 :

此函數會傳回 [0, 1) 區間均勻分布隨機陣列, 介面如下 :

numpy.random.rand(d0, d1, ..., dn)

rand() 的參數必須是正整數, 都是可選的, 未傳入參數時會傳回一個 0~1 之間的均勻分布隨機浮點數 (純量); 傳入一個參數 n (須正整數) 時 random.rand(n) 會傳回 n 個元素的一維均勻分布隨機陣列, 這相當於 random.rand(1, n); 傳入兩個參數 (須正整數) 時 random.rand(i, j) 會傳回一個 i*j 維度的均勻分布隨機隨機陣列, 更多參數則產生更高維的陣列, random.rand(i, j, k) 會傳回三維陣列, 例如 :

>>> np.random.rand()       #傳回 0~1 間的隨機數 (float 純量)
0.19644093345544167
>>> type(np.random.rand())   #沒有傳入參數時傳回一個純量
<class 'float'>
>>> np.random.rand(5)      #傳回 1*5 一維隨機陣列
array([0.74998027, 0.78622133, 0.92885681, 0.18321391, 0.89496996])
>>> np.random.rand(3, 4)   #傳回 3*4 二維隨機陣列
array([[0.27912439, 0.73034461, 0.58826309, 0.59707398],
       [0.44257555, 0.01745328, 0.59587659, 0.68253437],
       [0.22404294, 0.19438719, 0.99749655, 0.49556471]])
>>> np.random.rand(2, 3, 4)   #傳回 2*3*4 三維隨機陣列
array([[[0.27131017, 0.40616805, 0.36128135, 0.75666407],
        [0.40549157, 0.41149331, 0.30765576, 0.39208383],
        [0.82069598, 0.60894839, 0.4239132 , 0.92491804]],
       [[0.38630791, 0.36035005, 0.56409681, 0.15552691],
        [0.74267144, 0.81580992, 0.8991233 , 0.34849928],
        [0.37512254, 0.56358772, 0.60626021, 0.11272833]]])

可見 random.rand() 的元素值都是介於 0~1 之間.

另外 random.random() 功能與 random.rand() 一樣, 差別只是 random.random() 之參數為表示陣列形狀的物件, 通常是元組, 其介面如下 :

numpy.random.randdom(shape)

例如 :

>>> np.random.random((2, 3, 4))     #傳回 2*3*4 三維隨機陣列
array([[[0.8636309 , 0.85336527, 0.17550813, 0.11426248],
        [0.84716226, 0.7543792 , 0.24974123, 0.67595818],
        [0.81296995, 0.5708457 , 0.35645387, 0.46249463]],
       [[0.86340479, 0.20525588, 0.86448148, 0.67061865],
        [0.78973599, 0.87887531, 0.85816169, 0.37024847],
        [0.06263618, 0.10129474, 0.7355295 , 0.30408336]]])

參考 :

np.random.rand vs np.random.random


(2). randn() 函數 :

此函數與 rand() 一樣會傳回 [0, 1) 區間指定形狀之隨機陣列, 不同之處為 randn() 的隨機數為常態分布 (結尾的 n 表示 normal distribution), 而 rand() 為均勻分布, 介面如下 :

numpy.random.rand(d0, d1, ..., dn)

例如 :

>>> np.random.randn()     #傳回 0~1 間的隨機數 (float 純量)
0.3856502495158207
>>> np.random.randn(5)     #傳回 1*5 一維隨機陣列
array([ 0.1719701 ,  0.30347725, -1.62440603, -0.2287934 ,  0.8188689 ])
>>> np.random.randn(3, 4)     #傳回 3*4 二維隨機陣列
array([[ 0.42688578,  0.39268864, -0.3210757 , -0.59786833],
       [ 0.62772359,  0.95096534,  0.47403366,  1.07871119],
       [-0.46120767,  0.02107573, -1.12275642, -0.44598128]])
>>> np.random.randn(2, 3, 4)     #傳回 2*3*4 三維隨機陣列
array([[[ 0.65388739, -1.57062685,  1.29388902, -0.10002825],
        [-1.34442224, -1.8373028 ,  0.68837754,  0.13592657],
        [-0.1309182 , -1.41535012,  0.2141069 ,  1.30175303]],
       [[ 1.83388743,  0.18719303, -1.58136535,  0.69444909],
        [-1.20114114, -1.32826911,  0.16630603, -1.48900367],
        [ 0.77561497,  0.18556015,  0.12806819, -0.70700624]]])


(3). randint() 函數 :

顧名思義, 此函數名結尾的 int 表示它會建立一個隨機整數陣列, 介面如下 :

numpy.random.randint(low, high=None, size=None, dtype='l')

可見 randint() 至少必須傳入一個參數 low, 表示傳回的隨機數最小值, 第二參數 high 為傳回的隨機數最大值 (不含), 第三參數為陣列維度, 可用元組或串列指定 (通常用元組), 例如 size=(2, 3) 表示建立 2*3 維度陣列, 預設傳回值型態為整數. 資料型態預設為  32 位元整數 'I', 也可以用其它整數型態例如 64 位元整數 'i8', 但不可指定為非整數型態.

總之, 此函數會傳回一個元素值大於等於 low, 小於 high, 維度為 size 的均勻分布隨機數, 但實際測試發現, 當只傳入一個參數時, 它會被視為是 high 而不是 low, 例如 randint(4) 會傳回 [0, 4) 間隨機數, 必須同時傳入兩個參數時才會被解讀為前一個 low, 後一個 high, 例如 :

>>> np.random.randint(4)            #傳回小於 4 的隨機數
2
>>> np.random.randint(low=4)    #傳回小於 4 的隨機數
1
>>> np.random.randint(1, 4)        #傳回 [1, 4) 間的隨機數
3
>>> np.random.randint(2, 31)       #傳回 [2, 31) 間的隨機數
21
>>> np.random.randint(2,10, [2,3])   #傳回 [2, 10) 間的隨機陣列
array([[5, 4, 8],
       [2, 3, 9]])

參考 :

为什么你用不好Numpy的random函数?


6. 呼叫函數建立陣列 :

呼叫 numpy.fromfunction() 可透過函數呼叫來建立陣列, 介面如下 :

numpy.fromfunction(function, shape, **kwargs)

第一參數為建立陣列要呼叫的函數 (可用 lamda), 第二參數為輸入陣列的外形 (也是輸出陣列的外形), 通常是正整數元組 (tuple). 注意, shape 並非傳入函數 function() 之參數, 傳入函數的參數是符合此外形的座標陣列 (coordinate array), 例如 shape=(2, 2) 時, 傳入參數為 [[0, 0], [1, 1]] 與 [[0, 1], [0, 1]] 這兩個陣列, 例如 :

>>> def func(i, j):         #呼叫的函數傳回兩陣列加 1 之乘積
...     return (i+1)*(j+1) 
...
>>> np.fromfunction(func, (2, 2))   
array([[1., 2.],
       [2., 4.]])

此處傳入函數 func(i, j) 的參數 i, j 分別為 [[0, 0], [1, 1]] 與 [[0, 1], [0, 1]] 這兩個陣列, 所以傳回 i+1=[[1, 1], [2, 2]] 與 j+1=[[1, 2], [1, 2]] 的陣列乘積為 [[1, 2], [2, 4]], 此可由下列陣列乘積印證 :

>>> i=np.array([[1, 1], [2, 2]])     #二維陣列
>>> j=np.array([[1, 2], [1, 2]])     #二維陣列
>>> i*j 
array([[1, 2],
       [2, 4]])

與上面呼叫 fromfunction() 結果是一樣的. 如果傳入外形 shape=(9, 9) 就可以建立九九乘法表陣列, 例如 :

>>> def func(i, j): 
...     return (i+1)*(j+1) 
...
>>> np.fromfunction(func, (9, 9))   
array([[ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.],
       [ 2.,  4.,  6.,  8., 10., 12., 14., 16., 18.],
       [ 3.,  6.,  9., 12., 15., 18., 21., 24., 27.],
       [ 4.,  8., 12., 16., 20., 24., 28., 32., 36.],
       [ 5., 10., 15., 20., 25., 30., 35., 40., 45.],
       [ 6., 12., 18., 24., 30., 36., 42., 48., 54.],
       [ 7., 14., 21., 28., 35., 42., 49., 56., 63.],
       [ 8., 16., 24., 32., 40., 48., 56., 64., 72.],
       [ 9., 18., 27., 36., 45., 54., 63., 72., 81.]]

參考 :

Numpy里的fromfunction()的问题?
# Python資料分析(三)NumPy 

沒有留言 :