Data Science — NumPy 資料科學程式馬拉松

D01 Numpy 陣列的定義與屬性

各屬性的意義:

  • ndarray.ndim: 陣列有多少維度
  • ndarray.shape: 每個維度的大小
  • ndarray.size: 陣列當中有幾個元素
  • ndarray.dtype: 陣列中的資料型態
  • ndarray.itemsize: 陣列中每個元素佔用的空間
  • ndarray.data: 陣列所存在的記憶體位置

列表 vs. 陣列

  • Python 內建的列表(List)是由獨立的元素所組成的容器,元素的型態可以不同。而 NumPy 陣列是同質性的(homogeneous)的物件,也就是每個元素的型態都必須相同。因此在底層定義與操作略有差異。
  • 陣列的元素會配置在連續的記憶體位置,而列表則沒有這樣的限制。

D02 Numpy 陣列中不同的資料型態

  • NumPy 提供了一個同類型元素的多維容器型態,稱為是數組或陣列。陣列的全名是 N-dimensional Array,習慣簡寫為 NdArray 或 Array。

在陣列的屬性當中,有兩個跟型態有關:

  • ndarray.dtype: 陣列中的資料型態
  • ndarray.itemsize: 陣列中每個元素佔用的空間

注意的點:

  • type() != dtype() 一個是陣列本身的資料型態,一個是陣列中元素的資料型態
  • is 是強比較,需要型態、物件、規格都一樣才會回傳True

D03 Numpy 陣列的初始化

  • NumPy 提供了一個同類型元素的多維容器型態,稱為是數組或陣列。陣列的全名是 N-dimensional Array,習慣簡寫為 NdArray 或 Array。

一般來說,在 NumPy 當中建立/初始化陣列有四種方法:

  1. 從內建型態作轉換
  2. 從固定大小的初始值開始
  3. 從固定大小的序列值開始
  4. 從固定大小的亂數值開始
numpy.array(object, dtype=None, *, copy=True, order=’K’, subok=False, ndmin=0, like=None)
  • object:必填,任何 array_like 物件
  • dtype:指定轉成陣列後的元素型態
  • copy:預設為 True,是否產生一個新的物件
  • order:指定元素在記憶體中的儲存方式

從內建型態做轉換

np.array(list({0: 123, 1: 456}.items()))
# array([[ 0 123] [ 1 456]])

從固定大小的初始值開始

np.zeros((2, 3))
# 建立由 0 組成的 2x3 陣列
np.ones((2, 3))
# 建立由 1 組成的 2x3 陣列
np.full((2, 3), 9)
# 建立由 9 組成的 2x3 陣列
np.empty((2, 3))
#zero vs. empty: 完全皆為零/極小的數

從固定大小的序列值開始

np.arange( 10, 30, 5 )
# array([10, 15, 20, 25])
np.linspace( 0, 2, 3 )
# array([0. 1. 2.])
np.logspace( 0, 2, 3 )
# array([1. 10. 100.])

從固定大小的亂數值開始

from numpy.random import default_rng
rng = default_rng()
normal = rng.standard_normal((3,2))
random = rng.random((3,2))
integers = rng.integers(0, 10, size=(3,2))

D04 NumPy 陣列的算術運算

  • +, np.add()
  • -, np.substract(a, b)
  • *, np.multiply(a, b)
  • /, np.divide(a, b)
  • **, np.power(a, b)

NumPy 陣列運算 — 四則運算

運算的時候,陣列的形狀 (shape) 必須相同,或是遵循廣播 (broadcasting) 規則,才能正確進行 element-wise 運算。規則如下:

  • 兩個陣列形狀完全相同
  • 比較兩個陣列的維度,如果維度的形狀相同的話,可以進行廣播
  • 比較兩個陣列的維度,其中一個維度為1的話,可以進行廣播

常數與陣列運算

常數與陣列運算會符合「廣播(Broadcast)」運算的特性,廣播會將常數補齊成多維的陣列。

import numpy as np
a = np.array( [20,30,40,50] )
print(a - 2)
# [18 28 38 48]
print(a / 10)
# [2. 3. 4. 5.]

不同大小的陣列運算

import numpy as npdata = np.array([[1, 2], [3, 4], [5, 6]])
ones_row = np.array([[1, 1]])
print(data + ones_row)
# array([[2, 3],
# [4, 5],
# [6, 7]])
示意圖

陣列運算與容器運算的差異

陣列有「對齊」跟「廣播」兩種重要的運算特性,這個其實就是我們講的向量運算特性。換句話說,在向量的運算中,是以「整組」為單位在進行運算,這是和 Python 容器最大的差異之一。

a = np.array( [20,30,40,50] )
print(a + 1)
# array([21 31 41 51]
a = [20, 30, 40, 50]
b = []
for i in a:
b.append(i+1)
print(b)
# [21, 31, 41, 51]

D05 NumPy 陣列的邏輯、比較運算

陣列中的比較運算

import numpy as npa = np.array( [20,30,40,50] )
b = np.arange( 4 )
print(a > b) # [ True True True True]
print(a < b) # [False False False False]
print(a == b) # [False False False False]
print(a != b) # [ True True True True]

陣列中的邏輯運算(and, or, not)

需要特別注意的是,在 NumPy 陣列中沒有邏輯運算,可以使用位元運算或是邏輯運算的函式方法取代:

import numpy as npa = np.array( [True, True, False, False] )
b = np.array( [True, False, True, False] )
print(a and b)
Traceback (most recent call last):
...
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

利用布林值作為篩選的條件:遮罩

可以用一組 True/False 做為每一個位置的篩選條件,這種方法稱為遮罩(Mask):

import numpy as npa = np.array( [10, 20, 30, 40] )print(a[ [True, True, True, True] ])
# [10 20 30 40]
print(a[ [True, False, True, False] ])
# [10 30]
print(a[ [False, False, False, False] ])
# []

D06 Numpy 中常見的陣列方法與函式

numpy.fmax(), numpy.fmin()

  • 以 element-wise 比較 2 個陣列並回傳各元素的最大值或最小值。與 maximum() / minimum() 不同的是,如果比較的元素中只有一個是 nan 的話,回傳非 nan 的值,如果兩個元素都是 nan 則回傳 nan。
  • 同樣在進行比較時,若有需要會利用到廣播 (bradcasting)。

numpy.nanmax(), numpy.nanmin()

  • 回傳陣列中有非 nan 元素值的最大值或最小值。
  • 可以指定要比較的軸(axis),以及回傳值是否要保留維度。

百分位數:percentile(), nanpercentile()

  • 計算百分位數,percentile() 與 nanpercentile() 不同的地方在於後者會忽略 nan。
  • 欲取得的百分位數引數,可以傳入純量或是陣列的值 (介於0–100 之間),也可以指定要比較的軸,以及回傳值是否要保留維度。

分位數:quantile(), nanquantile()

  • 計算分位數,quantile() 與 nan quantile() 不同的地方在於後者會忽略 nan。如果元素中包含 nan 的話,則 quantile() 會回傳 nan。
  • 欲取得的分位數引數,可以傳入純量或是陣列的值 (介於0–1 之間),也可以指定要比較的軸,以及回傳值是否要保留維度。

平均值:mean(), nanmean()

  • mean() 和 nanmean() 不同的地方在於後者會忽略 nan。如果元素中包含 nan 的話,則 mean() 會回傳 nan。下面的例子使用 np.isnan() 判斷陣列中是否包含 nan,如果無 nan 的話就呼叫 mean() 計算平均值,反之則呼叫 nanmean() 進行計算。
  • 可以指定要計算平均數的軸,以及回傳值是否要保留維度。dtype 引數是計算使用的型別,若輸入陣列是整數的話,則會用 float64 型別計算,若輸入的是浮點數的話,則是依輸入陣列的型別做為 dtype。

平均值:average()

  • 使用 average() 計算平均值的話,可以輸入權重值做為引數。
  • 須注意權重的總和不能為 0,否則會產生錯誤。

計算中位數:median(), nanmedian()

  • median() 和 nanmedian() 不同的地方在於後者會忽略 nan。如果元素中包含 nan 的話,則 median() 會回傳 nan。
  • 可以指定要計算中位數的軸,以及回傳值是否要保留維度。要留意的是,如果軸或是陣列總數不是單數的話,中位數的值會是中間 2 個元素值相加除以 2。

計算標準差:std(), nanstd()

  • std() 和 nanstd() 不同的地方在於後者會忽略 nan。如果元素中包含 nan 的話,則 std() 會回傳 nan。
  • 可以指定要計算標準差的軸,以及回傳值是否要保留維度。若是對於精度可能造成的誤差影響,可以改變 dtype 提高精度。
  • 如果要計算樣本標準差的話,可將 ddof (自由度) 引數傳入 1,在計算平均方差 (mean squared deviation) 時分母就會以 N — ddof 做計算

計算變異數:var(), nanvar()

  • var() 和 nanvar() 不同的地方在於後者會忽略 nan。如果元素中包含 nan 的話,則 var() 會回傳 nan。
  • 可以指定要計算變異數的軸,以及回傳值是否要保留維度。若是對於精度可能造成的誤差影響,可以改變 dtype 提高精度。
  • 如果要計算樣本變異數的話,可將 ddof (自由度) 引數傳入 1,在計算平均方差 (mean squared deviation) 時分母就會以 N — ddof 做計算。

相關係數:corrcoef()

  • corrcoef() 計算 Pearson 積差相關係數。引數 rowvar 預設值為 True,代表將每一個 row 當做是一筆變數。
np.sort(array)
array.sort()

numpy.sort(a, axis=-1, kind=None, …)

  • a:必填,任何需要被排序的 array_like 物件
  • axis:預設 = -1,指的是排序的方向
  • kind:有四種排序算法可以選(quicksort, mergesort, heapsort, stable),預設為 quicksort 演算法

ndarray.sort(axis=-1, kind=None, …)

  • axis:預設 = -1,指的是排序的方向
  • kind:有四種排序算法可以選(quicksort, mergesort, heapsort, stable),預設為 quicksort 演算法
# 搜尋與排序方法np.searchsorted([1,2,3,4,5], 3)
# 2
  • 與 np.sort() 不同的是,陣列物件.sort() 的語法會進行 in-place 排序,也就是原本的陣列內容會跟著改變。
  • 多維陣列在排序時可以指定要依據的軸。
  • 排序支援多種不同的排序算法,包括 quicksort (預設)、heapsort、mergesort、timesort,在 kind 引數指定即可。依照官網文件指出排序速度是以 quicksort 最快,mergesort / timesort 其次,之後是 heapsort。
# 攤平
import numpy as np
a = np.arange(6).reshape((3, 2))print(a.ravel() )
print(a.flatten())
print(a.flat)
  • 透過 flatten()ravel() 均可將多維陣列轉形為一維陣列,flatten() 與 ravel() 的使用透過下列兩種方法,得到的結果都是完全一樣的。
  • 不同的是,ravel() 建立的是原來陣列的 view,所以在 ravel() 回傳物件中做的元素值變更,將會影響原陣列的元素值。
  • 預設展開的順序是 C-style,展開時是以 row 為主的順序展開。

NumPy 陣列重塑 — reshape()

  • 呼叫 reshape() 時指定新的形狀 (shape),可將陣列重塑為該形狀,但如果新的總數與原先 shape 總數不一致的話,則會產生錯誤。
  • Reshape 時,新的形狀可以採用模糊指定為 -1,讓 NumPy 自動計算,例如:a.reshape((5, -1))。
  • 若 reshape 後的陣列元素值改變,會影響原陣列對應的元素值也跟著改變。

NumPy 陣列重塑 — resize()

  • 與 reshape() 不同的地方在於,如果 resize 的大小超過總元素值,則會在後面的元素值的指定為 0。
  • 如果 resize 的大小小於總元素值,則會依照 C-style 的順序,取得 resize 後的陣列元素。

NumPy 陣列的合併 — concatenate()

使用 concatenate() 進行陣列的合併時,須留意除了指定的軸之外 (預設為 axis 0),其他軸的形狀必須完全相同,合併才不會發生錯誤。

NumPy 陣列的合併 — stack(), hstack(), vstack()

  • stack(), hstack(), vstack() 的觀念及用法類似,不同點在於 stack() 回傳的陣列維度會是合併前的維度 +1,而 hstack() 與 vstack() 回傳的陣列維度則是依合併的陣列而定。
  • 至於是否可以合併,stack() 必須要所有陣列的形狀都一樣;而 hstack() 與 vstack() 則跟上述的規則一樣,除了指定的軸之外,其他軸的形狀必須完全相同才可以合併。

補充資料:stack(),hstack(),vstack()差異說明

NumPy 陣列的分割 — split()、hsplit()、vsplit()

  • 呼叫 split() 時 indices_or_sections 引數如果給定單一整數的話,那就會按照軸把陣列等分;如果給定一個 List 的整數值的話,就會按照區段去分割。
  • hsplit() 與 vsplit(),分別是依照水平軸和垂直軸去做分割。

D07 NumPy 陣列的索引、切片和迭代

import numpy as npdata = np.array([1, 2, 3])print(data[0]) # 取出第 0 個
print(data[1]) # 取出第 1 個
print(data[0:2]) # 第 0 - 1 個
print(data[1:]) # 第 1 到最後一個
print(data[-2:]) # 倒數第二到最後一個
  • 攤平後再迭代多維陣列

NumPy陣列的索引和切片 (Slicing)

  • 透過索引存取陣列元素或進行切片 (slicing),可以使用索引值,或是 [start:stop:step] 語法取得範圍內的元素,要留意的是起始-結束範圍仍是 half-open 的,所以回傳的元素將不包含結束索引的元素。
  • 索引 -1 表示取得最後一個元素。切片如果只有給定 step 值為 -1 的話,則代表是反向取出,元素值是從最後一筆開始取出。
  • 若沒有給定 start 或 stop 值的話則代表是取出該索引之前或之後的所有元素。若 start 和 stop 值都沒有給定的話,就是取出所有元素值。

--

--

理科與藝術交織成靈魂的會計人,喜愛戲劇與攝影,但也喜歡資料科學。

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Joe Chao

Joe Chao

理科與藝術交織成靈魂的會計人,喜愛戲劇與攝影,但也喜歡資料科學。