配列の連結
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 次元配列を下の図のように「重ね合わせて」います。
このように重ね合わせた配列の形状は 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() に渡す配列 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 となっています。
下記は誤植と思われますので、ご確認ください。
NUMPY_CONCATENATEプログラムのコメントで、
13行目:1次元配列同士を第1軸(縦軸)方向に連結
→ 2次元配列同士を第0軸(縦軸)方向に連結
16行目:1次元配列同士を第1軸(縦軸)方向に連結
→ 2次元配列同士を第1軸(横軸)方向に連結
ありがとうございます。
直しておきました。m(_ _)m
私は 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次元配列の縦方向は 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 を確認する、その基本姿勢は配列のイメージを描くためにとても大切だと思います。