今回は、配列を結合する方法の1つとして、オブジェクトであるnp.r_
とnp.c_
を用いた方法と使い方を解説します。
np.c_
はnp.r_
の特殊な場合を抜き出したものなので、np.r_
でもnp.c_
と同じことができます。
特に何も指定しない場合は、np.r_
はhstack
、np.c_
はvstack
と同様の使い方をすることができます。
大きな特徴としてはスライス表記で配列を作ることができること、配列でないただの数値も配列として結合できることが挙げられます。どちらも関数ではなく、オブジェクトなので全て[]
の中に配列や値を入れていくことで操作をしていきます。
np.r_
まずは、np.r_
の方からみていきます。関数ではないので、引数とかを指定するのではなく、[ ]
の中に結合したい配列や結合の仕方について指定することができます。
配列同士の結合(特に指定なし)
特に結合の様式を指定しない場合、np.r_
に含まれる配列はaxis=0
の方向に結合することになります。これが最もよく使われるかたちとなっています。
これはnp.hstack
関数と同じ機能を持ちます。np.hstack
関数については以下の記事で詳しく解説していますので参照してみてください。
配列同士を連結する、NumPyのvstackとhstack関数の使い方 /features/numpy-stack.html
実際のコードでみてみましょう。
In [1]: import numpy as np
In [2]: a = np.array([1, 2, 3])
In [3]: b = np.array([4, 5, 6]) # まずは1次元配列で
In [4]: np.r_[a,b]
Out[4]: array([1, 2, 3, 4, 5, 6])
In [5]: np.r_[2, 5, 3, np.array([2,3,]), 4.2]
Out[5]: array([ 2. , 5. , 3. , 2. , 3. , 4.2])
In [6]: c = np.zeros((2,3)) # 2次元配列を結合する。axis=1方向の要素数は一致していないと結合できないことに注意。
In [7]: d = np.ones((3, 3)) # cのaxis=1方向の要素数は3なので、それに合わせる。axis=0方向の要素数はcと同じ2である必要はない。
In [8]: np.r_[c, d]
Out[8]:
array([[ 0., 0., 0.],
[ 0., 0., 0.],
[ 1., 1., 1.],
[ 1., 1., 1.],
[ 1., 1., 1.]])
In [9]: d = np.ones((3, 4))
In [10]: np.r_[c, d]
----------------------------------------------------------------------
(エラーメッセージが表示される)
ValueError: all the input array dimensions except for the concatenation axis must match exactly
ここでの使い方はnp.hstack
と全く一緒になります。
スライス表記
np.r_
を使うことで、スライス表記によって1次元配列を作ることができます。
start:stop:step
のstart,stop,step
部分に値を入れていくことで連番であったり、等差数列を作ります。start
とstop
は始点と終点になっており、step
は差になっています。これはnp.arange(start, stop, step)
と同じ意味合いを持ちます。
デフォルトの値として、start=1
、step=1
となっており、stop
の値だけ指定すればその値未満までの0から始まる整数の連番が生成されます。また、step
は虚数を指定することができ、その場合はstep=stepj
だった場合、start
からstop
の区間(stopも区間に含まれる)をstep-1
等分する数列を生成します。
すなわち、Pythonのビルトイン関数のrange
を使用したnp.array(range(start, stop, step))
と同じ意味合いを持つ表記になります。また、カンマで区切ることで他の数値単体や、一次元配列を結合することもできます。np.arange
関数とnp.linspace
関数は以下の記事で詳細な説明をしていますので気になる方はみてみてください。
連番や等差数列を生成するnumpy.arange関数の使い方 /features/numpy-arange.html 線形に等間隔な数列を生成するnumpy.linspace関数の使い方 /features/numpy-linspace.html
では、スライス表記を交えてnp.r_
を使ってみます。
In [10]: np.r_[0:10]
Out[10]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [11]: np.r_[:10]
Out[11]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [12]: np.r_[-10:]
Out[12]: array([], dtype=int64)
In [13]: np.r_[0:10:2] # 0~9までを2刻みで
Out[13]: array([0, 2, 4, 6, 8])
In [15]: np.r_[10:0:-1] # 逆順もできる
Out[15]: array([10, 9, 8, 7, 6, 5, 4, 3, 2, 1])
In [16]: np.r_[0:10:10j] # 10等分
Out[16]:
array([ 0. , 1.11111111, 2.22222222, 3.33333333,
4.44444444, 5.55555556, 6.66666667, 7.77777778,
8.88888889, 10. ])
In [17]: np.r_[0:9:20j] # 20等分
Out[17]:
array([ 0. , 0.47368421, 0.94736842, 1.42105263, 1.89473684,
2.36842105, 2.84210526, 3.31578947, 3.78947368, 4.26315789,
4.73684211, 5.21052632, 5.68421053, 6.15789474, 6.63157895,
7.10526316, 7.57894737, 8.05263158, 8.52631579, 9. ])
In [18]: np.r_[0:10,0,4, np.array([3,3])] # 最後に数字や一次元配列を付け加えることもできる。
Out[18]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 4, 3, 3])
数値の文字列(string)による軸や次元の指定
np.r_
ではどの軸方向に結合するのか、次元の最小数はどうするのか、形状(shape)を揃える際、どの軸番号に1
を付け加えればいいのかといったことを
'a,b,c'
といった数値の文字列で表すことができます(a, b, cはそれぞれ整数)。デフォルトでは'0, 0, -1'
となっています。
-
a
は、どの軸(axis)の方向に沿って配列を結合するのかを指定します。 -
b
は、できあがる配列の次元数の最小値を指定します。 -
c
は、次元数の少ない配列の次元数の拡張を行った際、形状(shape)の表記として、どこに配列の最後の次元が置かれるべきかを指定します。
c
については、例えば (2,3)
の形状(shape)を持つ二次元配列があったとします。この配列を他の(2,2,3)
の形状(shape)を持つ三次元配列と結合したいとします。このとき、二次元配列は三次元に拡張しなければ結合できません。
c=-1
またはc=1
であれば、先頭に1が追加され、(1, 2, 3)
の形状で結合することができます。c=0
であれば、元の配列の形状が先頭に詰められるので、(2,3,1)
となります。
少し複雑ですね。c
が示しているのは拡張前の形状(shape)がどこに配置されるかであることとなっています。ややこしくはなっていますが、マスターできれば次元の違う配列でもうまく結合することが可能となるので使いこなせるようになれば、かなり便利なオブジェクトになるかと思います。
まずは、a
の部分についてみていきます。a
は結合する軸(axis)を指定します。ここでの軸番号は、できあがる配列における軸(axis)番号となっていることに注意してください。
また、a
のデフォルトの値は0
です。
In [102]: a = np.ones((2, 2))
In [103]: b = np.zeros((2, 2))
In [104]: np.r_['1', a, b] # axis=1(列)方向に結合
Out[104]:
array([[ 1., 1., 0., 0.],
[ 1., 1., 0., 0.]])
In [105]: np.r_['1', a, b].shape # これのshapeを確認すると、axis=1の要素数が加算されていることがわかる。
Out[105]: (2, 4)
In [106]: np.r_['0', a, b] # 今度は行方向に
Out[106]:
array([[ 1., 1.],
[ 1., 1.],
[ 0., 0.],
[ 0., 0.]])
In [107]: np.r_['0', a, b].shape # shapeを確認。
Out[107]: (4, 2)
In [121]: np.r_[a,b].shape # aを指定しない場合はa=0となっている。
Out[121]: (4, 2)
In [108]: c = np.ones((2, 2, 2)) # 次は三次元で。
In [109]: d = np.zeros((2,2,2))
In [110]: c
Out[110]:
array([[[ 1., 1.],
[ 1., 1.]],
[[ 1., 1.],
[ 1., 1.]]])
In [111]: d
Out[111]:
array([[[ 0., 0.],
[ 0., 0.]],
[[ 0., 0.],
[ 0., 0.]]])
In [114]: np.r_['0', c, d] # まずはaxis=0の方向に
Out[114]:
array([[[ 1., 1.],
[ 1., 1.]],
[[ 1., 1.],
[ 1., 1.]],
[[ 0., 0.],
[ 0., 0.]],
[[ 0., 0.],
[ 0., 0.]]])
In [115]: np.r_['1', c, d]
Out[115]:
array([[[ 1., 1.],
[ 1., 1.],
[ 0., 0.],
[ 0., 0.]],
[[ 1., 1.],
[ 1., 1.],
[ 0., 0.],
[ 0., 0.]]])
In [116]: np.r_['2', c, d]
Out[116]:
array([[[ 1., 1., 0., 0.],
[ 1., 1., 0., 0.]],
[[ 1., 1., 0., 0.],
[ 1., 1., 0., 0.]]])
In [117]: np.r_['0', c, d].shape # これらの形状(shape)を一通り確認しておく。全て番号に対応する箇所の要素数が加算されていることがわかる。
Out[117]: (4, 2, 2)
In [118]: np.r_['1', c, d].shape
Out[118]: (2, 4, 2)
In [120]: np.r_['2', c, d].shape
Out[120]: (2, 2, 4)
次に、b
の値を変更していきます。b
を変更することで、出力される配列の次元の最小数を指定することができます。b = 2
なら次元数(ndim)>=2となります。b = 3
であれば次元数(ndim)>=3となります。
b
の次元数より少ない配列はb
次元まで拡張されてから結合の操作に入ります(拡張の仕方をc
で指定しますが、デフォルトでは形状(shape)表記の先頭に1を足りない次元分だけ付け足していくことで拡張を行います)。
In [122]: np.r_['0,2', [0,1,2],[3,3,3]] # 2次元配列でaxis=0の方向に結合
Out[122]:
array([[0, 1, 2],
[3, 3, 3]])
In [123]: np.r_['0,2', [0,1,2],[3,3,3]].shape
Out[123]: (2, 3)
In [124]: np.r_['0,3', [0,1,2],[3,3,3]] # 3次元配列でaxis=0の方向に結合
Out[124]:
array([[[0, 1, 2]],
[[3, 3, 3]]])
In [125]: np.r_['0,3', [0,1,2],[3,3,3]].shape
Out[125]: (2, 1, 3)
In [126]: np.r_['-1,4', [0,1,2],[3,3,3]] # 4次元でも。今度は最低次元で結合させる
Out[126]: array([[[[0, 1, 2, 3, 3, 3]]]])
In [127]: np.r_['-1,4', [0,1,2],[3,3,3]].shape
Out[127]: (1, 1, 1, 6)
最後にc
の値を変更していきます。このデフォルトの値は-1
となっており、形状(shape)は先頭に1が続いていくかたちになります。このため、先ほどb
の値を変えたとき、拡張後の配列の形状(shape)は全て(1, 1, 3)
や(1, 1, 1, 3)
のようになっていました。
c
が一番理解しにくいところだと思います。よくわからなかったら、最初からreshape
関数の機能を使って結合したい配列の形状(shape)を変更してから結合するのもできますので、無理に理解する必要はありません。また、コードが読みにくくなるのであまりオススメしません。
In [128]: np.r_['0,2,-1', [0, 1, 2], [3,3,3]] # (3,)→(1,3) になってから、axis=0方向に結合
Out[128]:
array([[0, 1, 2],
[3, 3, 3]])
In [129]: np.r_['0,2,-1', [0, 1, 2], [3,3,3]].shape
Out[129]: (2, 3)
In [130]: np.r_['0,2,0', [0, 1, 2], [3,3,3]] # (3,)→(3,1)になってから、axis=0の方向に結合
Out[130]:
array([[0],
[1],
[2],
[3],
[3],
[3]])
In [131]: np.r_['0,2,0', [0, 1, 2], [3,3,3]].shape
Out[131]: (6, 1)
In [132]: np.r_['0,3,0', [0, 1, 2], [3,3,3]] # 3次元でも同様にできる。 (3,)→(3,1,1)。
Out[132]:
array([[[0]],
[[1]],
[[2]],
[[3]],
[[3]],
[[3]]])
In [133]: np.r_['0,3,0', [0, 1, 2], [3,3,3]].shape
Out[133]: (6, 1, 1)
行列(matrix)の指定
2次元の行列(matrix)をnp.r_
を使って生成する際は、文字列(string)として、'r'
または'c'
を最初に指定します。2次元行列の場合は、どちらを指定しても結果は同じですが、1次元で横ベクトルを生成したいときはr
を、縦ベクトルを生成したいときはc
を指定する必要があります。
コードをみながら確認していきます。
In [19]: a = np.array([1, 4, 6])
In [20]: b = np.array([2, 2, 2])
In [21]: np.r_['r', a, b]
Out[21]: matrix([[1, 4, 6, 2, 2, 2]])
In [22]: np.r_['c', a, b] # 縦ベクトルになる
Out[22]:
matrix([[1],
[4],
[6],
[2],
[2],
[2]])
In [23]: c = np.ones((4,5))
In [24]: d = np.zeros((2, 5))
In [25]: np.r_['r', c, d] # 2次元行列
Out[25]:
matrix([[ 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.]])
In [26]: np.r_['c', c, d] # 'c'でも結果は一緒
Out[26]:
matrix([[ 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.]])
numpy.c_について
ここまでnp.r_
についてみてきたわけですが、本記事の冒頭に述べたとおり、np.c_
はnp.r_
の特殊な場合について抜き出したものです。
これは、np.c_[] == np.r_['-1, 2, 0']
の関係が成り立ちます。
2次元以上の配列で、最も最低の次元(axisの番号が一番大きい)の方向で配列を結合します。また、次元の拡張を各配列で行っている段階では、c=0
となっているので、形状(shape)表記で元々の要素が先頭にあり、末尾に1を追加する形で拡張されます。
In [143]: a = np.ones((3, 2))
In [144]: b = np.zeros((3, 3))
In [145]: a
Out[145]:
array([[ 1., 1.],
[ 1., 1.],
[ 1., 1.]])
In [146]: b
Out[146]:
array([[ 0., 0., 0.],
[ 0., 0., 0.],
[ 0., 0., 0.]])
In [147]: np.c_[a,b]
Out[147]:
array([[ 1., 1., 0., 0., 0.],
[ 1., 1., 0., 0., 0.],
[ 1., 1., 0., 0., 0.]])
In [159]: c = np.zeros(3)
In [160]: c
Out[160]: array([ 0., 0., 0.])
In [161]: np.c_[a,c]
Out[161]:
array([[ 1., 1., 0.],
[ 1., 1., 0.],
[ 1., 1., 0.]])
In [162]: np.c_[a,c].shape
Out[162]: (3, 3)
In [163]: np.c_[[1,2,3],[4,5,6]] # 1次元配列だと2次元配列として結合される
Out[163]:
array([[1, 4],
[2, 5],
[3, 6]])
In [165]: np.c_[[[1,2,3]],[[4,5,6]], 2, 3] # 数値だけと結合も可能。前半の2つの配列が2次元配列とならないとできないことを注意する。
Out[165]: array([[1, 2, 3, 4, 5, 6, 2, 3]])