今回は、配列を結合する方法の1つとして、オブジェクトであるnp.r_np.c_を用いた方法と使い方を解説します。

np.c_np.r_の特殊な場合を抜き出したものなので、np.r_でもnp.c_と同じことができます。

特に何も指定しない場合は、np.r_hstacknp.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部分に値を入れていくことで連番であったり、等差数列を作ります。startstop は始点と終点になっており、stepは差になっています。これはnp.arange(start, stop, step)と同じ意味合いを持ちます。

デフォルトの値として、start=1step=1となっており、stopの値だけ指定すればその値未満までの0から始まる整数の連番が生成されます。また、stepは虚数を指定することができ、その場合はstep=stepjだった場合、startからstopの区間(stopも区間に含まれる)をstep-1等分する数列を生成します。

すなわち、np.linspace(start, stop, step, endpoint=1)と同じ意味合いを持つ表記になります。また、カンマで区切ることで他の数値単体や、一次元配列を結合することもできます。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 [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]])

参考