[NumPy] 配列の連結・分割

[NumPy] 配列の連結・分割

配列の連結

 NumPy の配列を連結(結合)する関数の一覧です。

numpy.concatenate():配列の連結

 numpy.concatenate()を使うと、複数の配列を連結することができます。

numpy.concatenate((a1, a2, ...), axis=0, out=None)

 a1, a2, a3, ... には配列を渡します。
 2 次元以上の配列を渡す場合は axis を指定できます (デフォルトは axis = 0)。

# NUMPY_CONCATENATE

import numpy as np

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

a2 = np.array([[7, 8, 9]])

a3 = np.array([[1, 1],
               [2, 2]])

# 2次元配列同士を第0軸(縦軸)方向に連結
x = np.concatenate((a1, a2))

# 2次元配列同士を第1軸(横軸)方向に連結
y = np.concatenate((a1, a3), axis = 1)

print("配列x\n{}\n\n".format(x))
print("配列y\n{}".format(y))
配列x
[[1 2 3]
 [4 5 6]
 [7 8 9]]
 
配列y
[[1 2 3 1 1]
 [4 5 6 2 2]]

 

numpy.vstack():縦軸方向に連結

 numpy.vstack()は、受け取った配列を縦積みします。引数には配列のシーケンス(リストやタプル)を渡します。この関数は numpy.concatenate() で axis に 0 を渡した場合と同じ機能をもちます。

# NUMPY_VSTACK

import numpy as np

a1 = np.array([[1, 1, 1]])
a2 = np.array([[2, 2, 2]])

# 1次元配列同士を第1軸(縦軸)方向に連結
x = np.vstack((a1, a2))

print(x)
[[1 1 1]
 [2 2 2]]

 

numpy.hstack():横軸方向に連結

 numpy.hstack()は、受け取った配列を横軸方向に連結します。引数には配列のシーケンス(リストやタプル)を渡します。この関数は numpy.concatenate() で axis に 1 を渡したときと同じ機能をもちます。

# NUMPY_HSTACK

import numpy as np

a1 = np.array([[1, 1]])
a2 = np.array([[2, 2]])

# 1次元配列同士を第2軸(横軸)方向に連結
x = np.hstack((a1, a2))

print(x)
[[1 1 2 2]]

 

numpy.dstack():深さ方向に連結

 numpy.dstack() は受け取った配列 a1, a2, ... を深さ方向に連結します。

numpy.dstack((a1, a2, ...))

 深さ方向 (公式ドキュメントでは "depth") というと何だか曖昧な表現ですが、要するに axis=2 (第 3 軸) に沿って連結するという意味です。例として、2 次元配列を numpy.dstack() で結合してみます。

# NUMPY_DSTACK

# In[1]

import numpy as np

# 全ての要素が0の3×3配列
a = np.zeros((3, 3))

# 全ての要素が1の3×3配列
b = np.ones((3, 3))

# aとbを深さ(第3軸)方向に連結
stack_ab = np.dstack((a, b))

print(stack_ab)
[[[0. 1.]
  [0. 1.]
  [0. 1.]]

 [[0. 1.]
  [0. 1.]
  [0. 1.]]

 [[0. 1.]
  [0. 1.]
  [0. 1.]]]

 上の例では同じ形状の 2 次元配列を下の図のように「重ね合わせて」います。

numpy.dstackで配列を奥行き(深さ)方向に連結する

 このように重ね合わせた配列の形状は 3 次元となるので、戻り値は 3 次元配列です。実行結果を見ても、3 次元配列を頭の中でイメージし難いのですが、shape 属性で形状を確かめてみましょう。

# In[2]

print(stack_ab.shape)
(3, 3, 2)

 3×3 の配列が 2 つ重なっていることがわかります。引数に 1 次元配列を渡すこともできます。試してみましょう。

# In[3]

c = [0, 0, 0]
d = [1, 1, 1]
e = [2, 2, 2]

# a,b,cを奥行き方向に連結
stack_cde = np.dstack((c, d, e))

print(stack_cde)
[[[0 1 2]
  [0 1 2]
  [0 1 2]]]

 渡した配列の形状が 1 次元であっても、下図のように縦でも横でもなく、深さ方向に重ねているので、戻り値はやはり 3 次元配列となっています。

numpy.dstack 1次元配列を奥行き(深さ)方向に連結

 numpy.dstack() に渡す配列 a1, a2, ... は、それぞれ縦横 (axis=0, axis=1) のサイズが揃っていなければなりませんが、深さ方向 (axis=2) のサイズに関しては制限がありません。たとえば、In[1] で作成した配列 stack_ab に 2 次元配列 a をさらに重ねて、深さ 3 の 3 次元配列を生成することもできます。

# In[4]

# stack_abとaを深さ方向に連結
stack_aba = np.dstack((stack_ab, a))

print(stack_aba)
[[[0. 1. 0.]
  [0. 1. 0.]
  [0. 1. 0.]]

 [[0. 1. 0.]
  [0. 1. 0.]
  [0. 1. 0.]]

 [[0. 1. 0.]
  [0. 1. 0.]
  [0. 1. 0.]]]

 

配列の分割

 NumPy の配列を分割する関数の一覧です。

numpy.split():均等分割

 numpy.split() は受け取った配列を 均等分割 します。

numpy.split(array, indices_or_sections, axis=0)

 array には配列オブジェクトを渡します。第 2 引数に整数 N を渡すと、指定した axis (デフォルトは 0) に沿って配列を N 等分します。

# NUMPY_SPLIT

# In[1]

import numpy as np

# 9個の要素をもつ1次元配列を定義
x = np.ones(9, dtype = "int32")

# xを3つの配列に分割
y1, y2, y3 = np.split(x, 3)

print(y1, y2, y3)
[1 1 1] [1 1 1] [1 1 1]

 第 2 引数に分割点インデックスのリストを渡すこともできます。

# In[2]

import numpy as np

# x = [1 2 3 4 5 6 7 8 9]
x = np.arange(1, 10)

# xをインデックス3,5の位置で分割
y1, y2, y3 = np.split(x, [3, 5])

print(y1, y2, y3)
[1 2 3] [4 5] [6 7 8 9]

 

numpy.array_split():なるべく均等に分割

 numpy.array_split() は受け取った配列を分割します。

numpy.array_split(array, indices_or_sections, axis=0)

array には配列オブジェクトを渡します。第 2 引数に整数 N を渡すと、指定した axis (デフォルトは 0) に沿って配列を N 等分します。
 numpy.split(x, N) は配列 x が N で割り切れないとエラーを返しますが、numpy.array_split(x, N) は、x が N で割り切れないときには、余りの要素数を先頭の配列から順に割り振ります。たとえば、11 個の要素で構成される配列を 3 分割する場合、4 個、4 個、3 個の要素数をもつ配列に分割します。

# NUMPY_ARRAY_SPLIT

import numpy as np

# x = [0 1 2 3 4 5 6 7 8 9 10]
x = np.arange(11)

# 配列xを3等分
y = np.array_split(x, 3)

print(y)
[array([0, 1, 2, 3]), array([4, 5, 6, 7]), array([ 8,  9, 10])]

 第 2 引数に分割点インデックスのリストを渡すこともできます。
 その場合は numpy.split() と同じはたらきをします。
 

numpy.vsplit():縦軸方向に分割

 numpy.vsplit() は配列を縦軸方向に分割します。

# NUMPY_VSPLIT

import numpy as np

# 乱数を固定
np.random.seed(0)

# 1~9の乱数を要素にもつ4×3配列
x = np.random.randint(1, 10, (4, 3))

print("配列x\n{}\n".format(x))

# 縦軸(第2軸)に沿って配列を2分割
y1, y2 = np.vsplit(x, 2)

print("配列y1\n{}\n".format(y1))
print("配列y2\n{}".format(y2))
配列x
[[6 1 4]
 [4 8 4]
 [6 3 5]
 [8 7 9]
 
配列y1
[[6 1 4]
 [4 8 4]]
 
配列y2
[[6 3 5]
 [8 7 9]]

 

numpy.hsplit():横軸方向に分割

 numpy.hsplit() は配列を横軸方向に分割します。

# NUMPY_HSPLIT

import numpy as np

# 2×2の2次元配列
x = np.array([[1, 2],
              [3, 4]])

# xを2個の配列に水平分割
y1, y2 = np.hsplit(x, 2)

print(y1, "\n")
print(y2)
[[1]
 [3]]
 
[[2]
 [4]

 

numpy.dsplit():深さ方向に分割

 numpy.dsplit() は受け取った配列を深さ (第 3 軸) 方向に分割します。

numpy.dsplit(ary, indices_or_sections)

 第 2 引数に整数 k を渡すと、配列を深さ方向に k 等分します (割り切れなければエラーとなります)。準備として、まずは 2 次元配列を numpy.dstack() で 3 つ重ねてみます。

# NUMPY_DSPLIT

# In[1]

import numpy as np

a = np.zeros((3, 3))
b = np.ones((3, 3))
c = np.full((3, 3), 2)

# 配列a,b,cを深さ方向に連結
stack_abc = np.dstack((a, b, c))

print(stack_abc)

 今度は逆に numpy.dsplit() を使って、stack_abc を深さ方向に 3 等分してみます。

# In[2]

# stack_abcを深さ方向に3等分
d, e, f = np.dsplit(stack_abc, 3)

 変数 d に格納された配列を確認してみます。

# In[3]

print("配列d:\n{}\n".format(d))
print("配列dの形状:\n{}".format(d.shape))
配列d:
[[[0.]
  [0.]
  [0.]]

 [[0.]
  [0.]
  [0.]]

 [[0.]
  [0.]
  [0.]]]

配列dの形状:
(3, 3, 1)

 numpy.dsplit() によって、3×3×1 の 3 次元配列にスライスされていることがわかります (配列 e, f についても確認してみてください)。重ね合わせる前の配列 a より次元が 1 つ増えていますが、numpy.dsplit() の戻り値は必ず 3 次元配列となります。

 第 2 引数には配列を切り分ける位置をシーケンスで指定することもできます。stack_abc を 2 つ重ねて深さ 6 の 3 次元配列を作ってみます。

# In[4]

# stack_abcを2つ重ねる
stack_abcabc = np.dstack((stack_abc, stack_abc))

print(stack_abcabc)
[[[0. 1. 2. 0. 1. 2.]
  [0. 1. 2. 0. 1. 2.]
  [0. 1. 2. 0. 1. 2.]]

 [[0. 1. 2. 0. 1. 2.]
  [0. 1. 2. 0. 1. 2.]
  [0. 1. 2. 0. 1. 2.]]

 [[0. 1. 2. 0. 1. 2.]
  [0. 1. 2. 0. 1. 2.]
  [0. 1. 2. 0. 1. 2.]]]

 深さ方向に [1, 3] の位置で切り分けてみます (インデクス 0 と 1 の間、および 3 と 4 の間に切れ目を入れるイメージです)。

# In[5]

# stack_abcabcを深さの方向にインデクス1,3の位置でスライス
g, h, i = np.dsplit(stack_abcabc, [1, 3])

 配列 g の形状を確かめてみます。

# In[6]

print("配列g:\n{}\n".format(g))
print("配列gの形状:\n{}".format(g.shape))
配列g:
[[[0.]
  [0.]
  [0.]]

 [[0.]
  [0.]
  [0.]]

 [[0.]
  [0.]
  [0.]]]

配列gの形状:
(3, 3, 1)

 g は深さ 1 の 3 次元配列ですね。同様に h についても調べてみましょう。

# In[7]

print("配列h:\n{}\n".format(h))
print("配列hの形状:\n{}".format(h.shape))
配列h:
[[[1. 2.]
  [1. 2.]
  [1. 2.]]

 [[1. 2.]
  [1. 2.]
  [1. 2.]]

 [[1. 2.]
  [1. 2.]
  [1. 2.]]]

配列hの形状:
(3, 3, 2)

 h は深さ方向にインデクス 0 と 3 の間にある配列なので厚みは 2 となっています。