『Python数値計算ノート』ではアフィリエイトプログラムを利用して商品を紹介しています。

【NumPy】concatenate, vstack, hstack, dstack, split

配列の連結

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

numpy.concatenate()

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

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

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

# NUMPY_CONCATENATE

# In[1]

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次元配列同士を縦軸方向に連結
x = np.concatenate((a1, a2))

# 2次元配列同士を横軸方向に連結
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

# In[1]

import numpy as np

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

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

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

numpy.hstack()

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

# NUMPY_HSTACK

# In[1]

import numpy as np

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

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

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

numpy.dstack()

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

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

深さ方向 (公式ドキュメントでは “depth”) というと何だか曖昧な表現ですが、要するに axis=2 に沿って連結するという意味です。例として、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を深さ(axis=2)方向に連結
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]

# 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

# In[1]

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

# In[1]

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分割
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

# In[1]

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() は受け取った配列を深さ(axis=2)方向に分割します。

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 となっています。

コメント

  1. HNaito より:

    下記は誤植と思われますので、ご確認ください。
    NUMPY_CONCATENATEプログラムのコメントで、
    13行目:1次元配列同士を第1軸(縦軸)方向に連結
       → 2次元配列同士を第0軸(縦軸)方向に連結
    16行目:1次元配列同士を第1軸(縦軸)方向に連結
       → 2次元配列同士を第1軸(横軸)方向に連結

    • あとりえこばと より:

      ありがとうございます。
      直しておきました。m(_ _)m

      • HNaito より:

        私は axis=0(縦軸) は 第 0 軸、axis=1(横軸) は 第 1 軸と言ったほうがいいと思ったのですが、以降の説明文の中でも、縦軸は第 1 軸、横軸は第 2 軸、深さは第 3 軸というコメントや説明が出てきますので、図中に第1~3軸の方向を明示して元に戻したほうがいいと思います。第1~3軸という表現をなくして、縦、横、深さという表現だけでもいいかもしれません。

        • あとりえこばと より:

          確かに第1軸、第2軸 … という表現は混乱しやすいですね。私も時々混乱します。これはネストの外側から数えて一番目の軸、二番目の軸 … という意味なのですが、引数で指定するときは axis=0, axis=1, … となって、番号がずれているのもややこしいですね。少し悩みましたが、第1軸、第2軸 … のような表現はやめて、縦軸、横軸、あるいは axis=0, axis=1, .. で説明することにしました。色々考えてくださってありがとうございますm(_ _)m

  2. HNaito より:

    今まで、2次元配列の縦方向は axis = 0、横方向は axix = 1、(2次元配列が2つ重なったイメージの) 3次元配列の深さ方向は axis = 0、縦方向は axis = 1,横方向は axis = 2 と形式的に憶えていたので、だいぶ混乱しました。

    下記の基本に戻って、もう一度冷静に記事を読み直してdstack( )を理解し、dsplit( ) でしっかりと3次元配列のイメージが獲得できました。
    ・配列の形状は ndarray.shape で確認できる。
    ・配列の次元数は ndarray.shape の要素数である。
    ・axis は ndarray.shape のインデックスに対応する。

    • あとりえこばと より:

      記事を役立てていただけたら嬉しいです。(^_^)
      私も ndarray.shape を重宝しています。
      新たに配列が生成されたときは常に形状、次元、axis を確認する、その基本姿勢は配列のイメージを描くためにとても大切だと思います。