numpyの2次元配列を横方向に結合する方法について調べて記事にしたのがつい先日。縦方向も同じような感じなのかな?といった感じでふと気になったので、こちらも調べてまとめておきました。
結論としては、横方向の結合も縦方向の結合も、考え方としてはほぼ同じでした。
numpy.append()関数を使った縦方向結合
numpy.append()関数は標準ライブラリのlist.append()関数と同じ名前なので、関数を探すときにアタリがつけやすいです。使い方も直感的で、クセが無いです。
ドキュメントの記述がある場所も、API Reference > Routines > Array manipulation routines > Adding and removing elementsと直感的な場所で見つけやすいです。
import numpy as np
a = [['a00', 'a01', 'a02'],
['a10', 'a11', 'a12'],
['a20', 'a21', 'a22']]
b = [['b00', 'b01', 'b02'],
['b10', 'b11', 'b12'],
['b20', 'b21', 'b22']]
np.append(a, b, axis=0)
結果:
[['a00', 'a01', 'a02'],
['a10', 'a11', 'a12'],
['a20', 'a21', 'a22'],
['b00', 'b01', 'b02'],
['b10', 'b11', 'b12'],
['b20', 'b21', 'b22']]
ちなみに、axis=0ではなくaxis=1を指定すると、横方向の結合になります。
np.append(a, b, axis=1)
結果:
[['a00', 'a01', 'a02', 'b00', 'b01', 'b02'],
['a10', 'a11', 'a12', 'b10', 'b11', 'b12'],
['a20', 'a21', 'a22', 'b20', 'b21', 'b22']]
また、axisを省略してしまうと、1次元配列として結合してしまうので、2次元配列の結合ではこの引数を省略することはできないところが残念ですが、次に紹介するnumpy.concatenate()関数だとaxis=0を省略することができます。
np.append(a, b) # axis引数を省略すると1次元配列に展開した結合に
結果:
['a00', 'a01', 'a02', 'a10', 'a11', 'a12', 'a20', 'a21', 'a22',
'b00', 'b01', 'b02', 'b10', 'b11', 'b12', 'b20', 'b21', 'b22']
さらに、このnumpy.append()関数は、1度の呼び出しで結合できるのは、2つの配列までです。3つ以上の配列をいっきに結合したい場合はこの後紹介するnumpy.concatenate()関数・numpy.vstack()関数、numpy.r_[ ]インデクシングを使う必要があるので、このnumpy.append()関数は2次元配列の結合という面では汎用性が低いといえます。
numpy.append()関数の特徴
- 標準ライブラリのlist.append()関数と同じ名前で調べるときにアタリがつけやすい
- 関数名が直感的(append=「追加する」)で覚えやすい
- 引数にクセがない。(第1引数=結合される配列、第2引数=結合したい配列、第3引数=結合する次元としてaxis=0)
- 一度の関数呼び出しで3つ以上の配列を一気に結合することはできない
numpy.concatenate()関数を使った縦方向結合
numpy.concatenate()関数は、直感的に探せる関数名であり、引数も縦方向に結合したい配列のタプルを指定するだけとシンプルです。
ドキュメントの記述がある場所はnumpy.append()関数とは違ってAPI Reference > Routines > Array manipulation routines > Joining arraysですが、「追加する」ではなく「結合する」という性質の関数だということを考えると、スッと頭に入ってきます。
import numpy as np
a = [['a00', 'a01', 'a02'],
['a10', 'a11', 'a12'],
['a20', 'a21', 'a22']]
b = [['b00', 'b01', 'b02'],
['b10', 'b11', 'b12'],
['b20', 'b21', 'b22']]
np.concatenate((a, b))
結果:
[['a00', 'a01', 'a02'],
['a10', 'a11', 'a12'],
['a20', 'a21', 'a22'],
['b00', 'b01', 'b02'],
['b10', 'b11', 'b12'],
['b20', 'b21', 'b22']]
ちなみに、axis=1を指定すると、numpy.append()関数と同様に横方向の結合になります。
np.concatenate((a, b), axis=1)
結果:
[['a00', 'a01', 'a02', 'b00', 'b01', 'b02'],
['a10', 'a11', 'a12', 'b10', 'b11', 'b12'],
['a20', 'a21', 'a22', 'b20', 'b21', 'b22']]
横方向の結合だけ見ると、(a, b)とカッコで囲ってタプルにする必要があるぶん、numpy.append()関数を使った方がスッキリしますね。
ただ、numpy.append()関数にはない強みとして、3つ以上の配列を1度の関数呼び出しでいっきに結合できるという強みがあります。
a = [['a00', 'a01', 'a02'],
['a10', 'a11', 'a12'],
['a20', 'a21', 'a22']]
b = [['b00', 'b01', 'b02'],
['b10', 'b11', 'b12'],
['b20', 'b21', 'b22']]
c = [['c00', 'c01', 'c02'],
['c10', 'c11', 'c12'],
['c20', 'c21', 'c22']]
np.concatenate((a, b, c))
結果:
[['a00', 'a01', 'a02'],
['a10', 'a11', 'a12'],
['a20', 'a21', 'a22'],
['b00', 'b01', 'b02'],
['b10', 'b11', 'b12'],
['b20', 'b21', 'b22'],
['c00', 'c01', 'c02'],
['c10', 'c11', 'c12'],
['c20', 'c21', 'c22']]
numpy.concatenate()関数の特徴
- 関数名が直感的(concatenate=「結合する」)で覚えやすい
- 引数にクセが無い(第1引数=結合したい配列のタプル、第2引数=結合する次元としてaxis=0)
- 縦方向の結合の場合、引数axis=0は省略することができる
- 3つ以上の配列を1度の関数呼び出しでいっきに結合できる
numpy.vstack()関数を使った縦方向結合
numpy.vstack()関数は、numpy.concatenate()関数にaxis=0を指定したのと同様の結合をしてくれます。何も知らない状態からvstack()という関数にたどり着くのは難しいですが、vertically stack(垂直に重ねる)という意味からきていると考えれば覚えやすいです。
numpy.concatenate()関数に対して関数名が短い点、関数名だけ見てもnumpy.vstack()は縦方向の結合なんだなという意図が伝わりやすい点などが、地味にありがたい要素かなと思います。
import numpy as np
a = [['a00', 'a01', 'a02'],
['a10', 'a11', 'a12'],
['a20', 'a21', 'a22']]
b = [['b00', 'b01', 'b02'],
['b10', 'b11', 'b12'],
['b20', 'b21', 'b22']]
np.vstack((a, b))
結果:
[['a00', 'a01', 'a02'],
['a10', 'a11', 'a12'],
['a20', 'a21', 'a22'],
['b00', 'b01', 'b02'],
['b10', 'b11', 'b12'],
['b20', 'b21', 'b22']]
また、numpy.concatenate()関数と同様、1度の関数呼び出しで3つ以上の配列をいっきに結合可能です。
また、numpy.row_stack()という別名関数があります。(処理は全く同じ)
numpy.vstack()関数の特徴
- 初見ではvstackという関数名にたどり着くのは難しい
- vertically stackを意味した関数名だと考えると覚えやすい
- axis=0の指定が不要で、numpy.concatenate()関数より縦方向の結合だという意図が関数名だけでも伝わる
- 3つ以上の配列を1度の関数呼び出しでいっきに結合できる
numpy.r_[ ]インデクシングを使った縦方向結合
numpy.r_[ ]インデクシングは、基本的にはnumpy.vstack()関数と同じ使い方ができます。名前が短いこと、結合したい配列もタプルで括る必要はなく可変長引数として渡せるため、さらに簡潔に記述することができます。
numpy.append()関数・numpy.concatenate()関数・numpy.vstack()関数がAPI Reference > Routines > Array manipulation routinesに記述があるのに対して、numpy.r_[ ]インデクシングはAPI Reference > Routines > Indexing routinesに記述があるため、配列を縦方向に結合したいと思ってドキュメントを調べていてもなかなか見つけ出せない人も多いと思います。
さらにr_という名前が何を意味しているのか分からず記憶に残りにくいという難点もあります。マニュアルの位置の違いを書いたのは、あなたが明日にはr_という名前を思い出せなくなっていたとしても、なんとかドキュメントにたどり着いてほしいという想いを込めての考えです。
import numpy as np
a = [['a00', 'a01', 'a02'],
['a10', 'a11', 'a12'],
['a20', 'a21', 'a22']]
b = [['b00', 'b01', 'b02'],
['b10', 'b11', 'b12'],
['b20', 'b21', 'b22']]
np.r_[a, b]
結果:
[['a00', 'a01', 'a02'],
['a10', 'a11', 'a12'],
['a20', 'a21', 'a22'],
['b00', 'b01', 'b02'],
['b10', 'b11', 'b12'],
['b20', 'b21', 'b22']]
配列の作成にも使えるnumpy.r_[ ]インデクシング
numpy.r_[ ]インデクシングの存在を知ったところで、ついでに配列の生成機能についても知っておいて損はしないと思うので紹介しておきます。
引数に配列ではなくスライス表記で数字を指定すると、配列の結合ではなく配列の生成を行う挙動になります。
np.r_[5:12] # 結果:[ 5, 6, 7, 8, 9, 10, 11]
np.r_[:5] # 結果:[0, 1, 2, 3, 4]
np.r_[6:18:3] # 結果:[ 6, 9, 12, 15]
numpy.arrange()関数と同じことができるのですが、こっちのほうがクロウトぽくてカッコイイ!と感じる人にオススメです。
また、numpy.linspace()関数と同じようなこともできます。
np.r_[:10:3j] # 結果:[ 0., 5., 10.]
np.r_[1:2:5j] # 結果:[1. , 1.25, 1.5 , 1.75, 2. ]
汎用的なプログラミングをする人間からすると、ぱっと見意図が伝わりづらい記法だなぁと感じてしまい、あまり好きにはなれません。とはいえデータサイエンティストの方たちからすると、数式っぽく記述できたほうが理解しやすいのかもしれませんね。
numpy.r_[ ]インデクシングの特徴
- 簡潔に記述できる(短い名前・可変調引数)
- 名前は短いが覚えにくい
- マニュアルで見つけづらい(Array manipulation routinesではなくIndexing routinesに記載がある)
- スライス表記をすると1次元配列を生成できる
- 3つ以上の配列を1度の関数呼び出しでいっきに結合できる