5.3. 配列操作#

本節では、複数の配列を連結したり、既存の配列の次元数や形状を変更したりする方法について説明します。なお、これらの操作によって生成される配列は、ビューとして生成されます。ビューは元の配列とメモリを共有しているため、生成元または生成後のいずれかの配列の値を変更すると、もう一方の配列の値も連動して変化します。予期せぬデータの変更を避けるためにも、取り扱いには十分に注意してください。

5.3.1. 連結#

複数の配列を連結するには、np.concatenatenp.stack 関数を使用します。これらの関数は似ていますが、動作に明確な違いがあります。np.concatenate は、既存の次元に沿って配列を結合します。一方、np.stack は新しい次元(軸)を追加して連結を行います。そのため、連結後の配列は元の配列より次元が 1 つ増えます。

では、それぞれの使い方を具体的に見ていきましょう。

2 行 4 列の行列 a および b を用意し、両者を np.concatenate 関数で連結します。axis オプションを指定しない場合は、axis=0 として、行方向に連結が行われます。つまり、行数が増える形になります。

a = np.array([[1, 2, 3, 4],
              [5, 6, 7, 8]])
b = np.array([[0, 0, 1, 1],
              [1, 1, 0, 0]])
x = np.concatenate([a, b])
x
array([[1, 2, 3, 4],
       [5, 6, 7, 8],
       [0, 0, 1, 1],
       [1, 1, 0, 0]])

np.concatenateaxis=1 を指定すると、第二次元方向に向かって連結されます。つまり、列数が増える方向で連結が行われます。

x = np.concatenate([a, b], axis=1)
x
array([[1, 2, 3, 4, 0, 0, 1, 1],
       [5, 6, 7, 8, 1, 1, 0, 0]])

Tip

連結を行う関数に np.vstacknp.hstack もあります。np.vstack([a, b])np.concatenate([a, b], axis=0)np.hstack([a, b])np.concatenate([a, b], axis=1) と同じ動作をします。これらは np.concatenate よりも簡潔に記述できるため、よく使われます。

np.stack を使うと、新しい次元(軸)が追加されて連結されます。この関数にも axis オプションが用意されています。オプションを指定しない場合、axis=0 として扱われ、配列が縦方向に積み重ねられた形になり、2 行 4 列の行列になります。

a = np.array([1, 2, 3, 4])
b = np.array([5, 6, 7, 8])
x = np.stack([a, b])
x
array([[1, 2, 3, 4],
       [5, 6, 7, 8]])

axis=1 を指定すると、配列は横方向に積み重ねられ、4 行 2 列の行列になります。

x = np.stack([a, b], axis=1)
x
array([[1, 5],
       [2, 6],
       [3, 7],
       [4, 8]])

5.3.2. 形状変更#

一度に作成した配列は、必要に応じてその形を変更することができます。二次元配列(行列)の行と列を入れ替える転置はもちろんのこと、二次元配列を一次元配列に変換したり、一次元配列を三次元配列に変換したりすることも可能です。

多次元配列を一次元配列に変換するには、np.ravel 関数を利用すると便利です。例えば、次のように二次元配列を一次元配列に変換できますが、多次元配列でも同様の変換が可能です。

x = np.array([[1, 2, 3, 4],
              [5, 6, 7, 8]])
a = np.ravel(x)
a
array([1, 2, 3, 4, 5, 6, 7, 8])

配列を一次元に展開する際には、order オプションでその順序を指定できます。何も指定しなければ、デフォルトで order='C' が適用され、行方向を優先して展開されます。一方で、order='F' を指定すると、列方向を優先して展開されます。配列の次元数が高くなると、展開順序が直感的に理解しにくくなることもあるため、常に print 関数などで展開後の内容を確認しながら処理を進めると安心です。また、order には 'A''K' も指定できますが、実際にはデフォルトのままで使うことがほとんどです。

b = np.ravel(x, order='F')
b
array([1, 5, 2, 6, 3, 7, 4, 8])

配列の形を任意に変換したいときには、np.reshape 関数を利用すると便利です。この関数では、対象となる配列とともに新しい各次元の要素数を指定することで、柔軟に形状の変換を行うことができます。np.reshape は、内部的にはまず配列を一次元に変換してから、指定された形に再構築するような動作をしており、その順序を制御するための order オプションも用意されています。

まず、一次元配列を二次元配列に変換する例を見てみましょう。たとえば、要素が 8 個ある一次元配列を 2 行 4 列の二次元配列に変換するには、次のようにします。

x = np.array([1, 2, 3, 4, 5, 6, 7, 8])
a = np.reshape(x, [2, 4])
a
array([[1, 2, 3, 4],
       [5, 6, 7, 8]])

もう一つ例を挙げてみます。24 個の要素を持つ一次元配列を三次元配列に変換してみましょう。ここでは、一次元目に 4 個、二次元目に 2 個、三次元目に 3 個の要素数を持たせるように指定します。

x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
              11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
              21, 22, 23, 24])
a = np.reshape(x, [4, 2, 3])
a
array([[[ 1,  2,  3],
        [ 4,  5,  6]],

       [[ 7,  8,  9],
        [10, 11, 12]],

       [[13, 14, 15],
        [16, 17, 18]],

       [[19, 20, 21],
        [22, 23, 24]]])

np.reshape 関数には、変換元と変換先の配列の次元数に制限がありません。そのため、たとえば三次元配列を二次元配列に変換するといった操作も問題なく行うことができます。

b = np.reshape(a, [3, 8])
b
array([[ 1,  2,  3,  4,  5,  6,  7,  8],
       [ 9, 10, 11, 12, 13, 14, 15, 16],
       [17, 18, 19, 20, 21, 22, 23, 24]])

5.3.3. 次元転置#

配列の次元数を変えずに、各次元の順序だけを入れ替えたい場合には、np.transpose 関数を使うと便利です。これは、行列における行と列の入れ替え、つまり転置に相当する操作です。

具体例を見てみましょう。まず、2 行 4 列の行列を作成します。

x = np.array([[1, 2, 3, 4],
              [5, 6, 7, 8]])
x
array([[1, 2, 3, 4],
       [5, 6, 7, 8]])

次に、np.transpose に対象の配列 x と、変更後の次元の並び [1, 0] を指定します。ここで [1, 0] は、元の配列の二次元目(列)を新しい配列の一次元目に、一次元目(行)を新しい配列の二次元目に移動させることを意味します。

a = np.transpose(x, [1, 0])
a
array([[1, 5],
       [2, 6],
       [3, 7],
       [4, 8]])

この関数は次元数が増えた場合でも同様に使えます。たとえば、三次元配列において、立体的な座標軸(X, Y, Z)を Y-Z-X の順に入れ替えるような操作も可能です。

x = np.reshape([1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
                11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
                21, 22, 23, 24],
                [3, 2, 4])
x.shape
(3, 2, 4)
y = np.transpose(x, [1, 2, 0])
y.shape
(2, 4, 3)

このように、np.transpose を使えば、配列の形状そのものは変えずに、軸の順序だけを柔軟に入れ替えることができます。多次元配列を扱う際には非常に有用な機能です。