NumPy 陣列組合與拆解


如果你有幾個 NumPy 陣列,可能會想要對它們進行串接、堆疊、拆開等,這類動作是蠻常見的需求,只不過因為 NumPy 陣列有軸的觀念,讓這類動作可以有更豐富的處理方式。

例如,對於一維陣列,串接的結果很簡單就能想像:

>>> import numpy as np
>>> a = np.array([1, 2, 3])
>>> b = np.array([4, 5, 6])
>>> np.concatenate([a, b])
array([1, 2, 3, 4, 5, 6])
>>>

concatenate 可以用來串接 NumPy 陣列,要被串接的陣列得放在一個清單裡頭,如果是多維陣列,預設是將軸 0 方向上的每個元素串接起來,可以透過 axis 來指定軸:

>>> c = np.array([[10, 20, 30], [40, 50, 60]])
>>> d = np.array([[100, 200, 300], [400, 500, 600]])
>>> np.concatenate([c, d])
array([[ 10,  20,  30],
       [ 40,  50,  60],
       [100, 200, 300],
       [400, 500, 600]])
>>> np.concatenate([c, d], axis = 0)
array([[ 10,  20,  30],
       [ 40,  50,  60],
       [100, 200, 300],
       [400, 500, 600]])
>>> np.concatenate([c, d], axis = 1)
array([[ 10,  20,  30, 100, 200, 300],
       [ 40,  50,  60, 400, 500, 600]])
>>>

除了串接之外,還可以對陣列進行堆疊,例如垂直堆疊:

>>> a = np.array([1, 2, 3])
>>> b = np.array([4, 5, 6])
>>> np.vstack([a, b])
array([[1, 2, 3],
       [4, 5, 6]])
>>> c = np.array([
...     [7, 8, 9],
...     [10, 11, 12]
... ])
>>> np.vstack([a, b, c])
array([[ 1,  2,  3],
       [ 4,  5,  6],
       [ 7,  8,  9],
       [10, 11, 12]])
>>>

vstack 用來對陣列進行垂直堆疊,更精確的說法是,在軸 0 的方向進行堆疊;也可以使用 hstack 針對軸 1 的方向,也就是水平堆疊:

>>> c = np.array([
...     [1, 2, 3],
...     [4, 5, 6]
... ])
>>> d = np.array([
...     [7, 8, 9],
...     [10, 11, 12]
... ])
>>> c = np.array([
...     [1, 2, 3],
...     [4, 5, 6]
... ])
>>> d = np.array([
...     [8],
...     [9]
... ])
>>> np.hstack([c, d])
array([[1, 2, 3, 8],
       [4, 5, 6, 9]])
>>>

想針對軸 2 的方向,也就是以深度來堆疊的話,就使用 dstack,這或許是最常使用的堆疊,例如:

>>> x = np.array([10, 20, 30])
>>> y = np.array([40, 50, 60])
>>> np.dstack([x, y])
array([[[10, 40],
        [20, 50],
        [30, 60]]])
>>>

為什麼說是最常使用的堆疊呢?以二維繪圖為例,經常地,你會需要將 x 軸的資料與 y 軸的資料組合為座標,這時就可以如上組合,如果想取得座標清單,取結果的索引 0 就可以了:

>>> coord = np.dstack([x, y])[0]
>>> coord
array([[10, 40],
       [20, 50],
       [30, 60]])
>>>

這個作法並不限於座標,在更高的維度,也可以用來取得向量清單:

>>> x0 = np.array([1, 2, 3])
>>> x1 = np.array([10, 20, 30])
>>> x2 = np.array([100, 200, 300])
>>> x3 = np.array([1000, 2000, 3000])
>>> v = np.dstack([x0, x1, x2, x3])
>>> v
array([[[   1,   10,  100, 1000],
        [   2,   20,  200, 2000],
        [   3,   30,  300, 3000]]])
>>> v[0]
array([[   1,   10,  100, 1000],
       [   2,   20,  200, 2000],
       [   3,   30,  300, 3000]])
>>>

串接與堆疊都是在組合陣列,相對地,也有可以分離陣列的函式。嗯?透過陣列索引不就可以嗎?是沒錯,不過有時,我想會想要指定索引,一次分離出數個陣列。例如:

>>> a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> a1, a2 = np.split(a, [3])
>>> a1, a2
(array([1, 2, 3]), array([4, 5, 6, 7, 8, 9]))
>>> a1, a2, a3 = np.split(a, [3, 8])
>>> a1, a2, a3
(array([1, 2, 3]), array([4, 5, 6, 7, 8]), array([9]))
>>>

split 也可以指定 axis

>>> a = np.array([[1, 2, 3, 4], [6, 7, 8, 9]])
>>> a1, a2 = np.split(a, [2], axis = 1)
>>> a1
array([[1, 2],
       [6, 7]])
>>> a2
array([[3, 4],
       [8, 9]])
>>>

類似地,在分離時,也可以直覺地用垂直、水平或深度,對應的函式分別是 vsplithsplitdsplit,例如垂直分離:

>>> a = np.arange(16).reshape((4, 4))
>>> a
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])
>>> a1, a2 = np.split(a, [2])
>>> a1
array([[0, 1, 2, 3],
       [4, 5, 6, 7]])
>>> a2
array([[ 8,  9, 10, 11],
       [12, 13, 14, 15]])
>>>