python

numpyの2次元配列を横方向に結合する方法

2021-05-03

numpyは強力ですが、機能が豊富なだけに単純なオペレーションでもなかなかドキュメントからたどり着くことができなかったりしますよね。機械学習の本を読んでいたらサンプルソースのなかにnumpy配列を横方向に結合する操作が出てきたので、少し深掘りして調べてみました。

ざっと調べたところ、横結合に使える書き方は3種類ありました。順番に紹介します。

numpy.concatenate()関数を使った横方向結合

numpy.concatenate()関数は、関数名・使い方ともに、いちばん直感的に使えて覚えやすい関数です。

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), axis=1)

結果:
[['a00', 'a01', 'a02', 'b00', 'b01', 'b02'],
 ['a10', 'a11', 'a12', 'b10', 'b11', 'b12'],
 ['a20', 'a21', 'a22', 'b20', 'b21', 'B22']]

numpy.concatenate()関数の特徴

  • 関数名が直感的な英語名(concatenate=「繋げる」)で覚えやすく、ドキュメントからも探しやすい。
  • 引数にクセがない。( 第1引数=結合したい配列のタプル、第2引数=対象とする次元)

ちなみに、axis=1ではなくaxis=0を指定すると、縦方向の結合になります。

np.concatenate((a, b), axis=0)

結果:
[['a00', 'a01', 'a02'],
 ['a10', 'a11', 'a12'],
 ['a20', 'a21', 'a22'],
 ['b00', 'b01', 'b02'],
 ['b10', 'b11', 'b12'],
 ['b20', 'b21', 'B22']]

numpy.hstack()関数を使った横方向結合

numpy.hstack()関数は、numpy.concatenate()関数にaxis=1を指定したのと同様の結合をしてくれます。何も知らない状態からhstack()という関数にたどり着くのは難しいですが、horizontally stack(水平に重ねる)という意味からきていると考えれば覚えやすいです。

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.hstack((a, b))

結果:
[['a00', 'a01', 'a02', 'b00', 'b01', 'b02'],
 ['a10', 'a11', 'a12', 'b10', 'b11', 'b12'],
 ['a20', 'a21', 'a22', 'b20', 'b21', 'B22']]

numpy.hstack()関数の特徴

  • 初見ではhstackという関数名にたどり着くのは難しい
  • horizontally stackを意味した関数名だと考えると覚えやすい
  • axis=1の指定が不要なぶん簡潔に記述できる

ちなみに、横方向ではなく縦方向に結合したい場合にはvstackという関数が用意されています。これも、vertically stackを意味した関数名だと考えると覚えやすいですね。

np.vstack((a, b))

結果:
[['a00', 'a01', 'a02'],
 ['a10', 'a11', 'a12'],
 ['a20', 'a21', 'a22'],
 ['b00', 'b01', 'b02'],
 ['b10', 'b11', 'b12'],
 ['b20', 'b21', 'B22']]

numpy.c_[]インデクシングを使った横方向結合

numpy.c_[ ]インデクシングは、基本的にはnumpy.hstack()関数と同じ使い方ができます。名前が短いこと、結合したい配列も可変長引数として渡せるためタプルにするためのカッコも不要になり、さらに簡潔に記述することができます。

numpy.concatenate()関数やnumpy.hstack()関数がAPI Reference > Routines > Array manipulation routines > Joining arraysに属しているのに対して、numpy.c_[ ]インデクシングはAPI Reference > Routines > Indexing routines > Generating index arraysに属していて、配列を横方向に結合したいと思ってドキュメントを調べていてもなかなか見つけ出せないので、知らない人も多いと思います。

さらにc_という名前が何を意味しているのか分からず記憶に残りにくいという難点もあります。マニュアルの位置の違いを書いたのは、あなたが明日にはこのc_という名前を思い出せなくなっていたとしても、なんとかドキュメントに辿り着いてほしいという想いを込めてのことです。

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.c_[a, b]

結果:
[['a00', 'a01', 'a02', 'b00', 'b01', 'b02'],
 ['a10', 'a11', 'a12', 'b10', 'b11', 'b12'],
 ['a20', 'a21', 'a22', 'b20', 'b21', 'B22']]

np.c_[ ]インデクシングは、短い名前で簡潔に記述できるというだけではなく、さらにもう1つ、使いやすい特徴があります。numpy.concatenate()関数やnumpy.hstack()関数が結合できるのは同じサイズのn次元配列だけでしたが、np.c_[ ]インデクシングはn次元配列に1次元配列を結合する、というオペレーションにも対応しています。

conc_result = np.concatenate((a, b), axis=1) # エラーになる
hstk_result = np.hstack((a, b)) # エラーになる
c_result = np.c_[a, b] # エラーにならない

c_result:
[['a00', 'a01', 'a02', 'b00'],
 ['a10', 'a11', 'a12', 'b01'],
 ['a20', 'a21', 'a22', 'b02']]

この性質はnumpy配列やpandasの表などを使ってデータ集計などの操作をしているときに地味に便利だったりします。ロジック的には1次元配列は2次元配列に変換したうえで最後のaxisを対象にして結合を行う、という挙動なので、3次元以上の結合の挙動もnumpy.concatenate()関数やnumpy.hstack()関数と変わってきます。

numpy.c_[]インデクシングの特徴

  • 簡潔に記述できる(短い名前・引数は可変長)
  • 名前は短いが覚えにくい
  • マニュアルで探しづらい(Array manipulation routinesではなくIndexing routinesに記載)
  • 異なる次元の配列同士は最後のaxisを使用して結合できる

-python

© 2022 ヂまるBlog