Pythonのlistはappendメソッドを使用すると、末尾に要素を追加することができます。例えば、以下のようにlistのメソッドで使用することができます。

In [1]: a = [1, 2, 3]

In [2]: a.append(2)

In [3]: a
Out[3]: [1, 2, 3, 2]

また、Pythonのlistにはextendというメソッドもありました。extendは引数に配列を指定すると、元のリストに指定した配列の要素がマージされます。

In [4]: a = [1, 2, 3]

In [5]: a.extend([4, 5, 6])

In [6]: a
Out[6]: [1, 2, 3, 4, 5, 6]

一方で、NumPyにもnp.appendという同じ名前の関数が備わっていますが、その挙動は引数の指定が配列でなければならないという面において、どちらかというとextendに近いものです。さらに、要素のコピーが発生するので、思ったよりも遅く、混乱しやすいのでコードを読む際にも書く際にも注意が必要になります。

np.append

np.appendは、配列の末尾に要素を追加した新しい配列を生成する関数です。 APIドキュメントは以下のようになっています。

numpy.append(arr, values, axis = None)

params:

パラメータ名 概要
arr array_like 要素を追加される配列を指定します。
values array_like 追加する要素または配列を指定します。
axis int ここで指定した軸パラメータに沿ってappend演算を適用します。

returns:

要素が追加されたndarrayが返されます。

np.appendの引数には、第一引数として元の配列、第二引数として追加する要素の配列、第三引数に配列として要素を追加する軸パラメータを指定できます。第三引数を指定しないと第一、第二引数で指定された配列のshapeとは関係なく一次元配列が生成されます。

以下のコードで確認してみましょう。

In [1]: import numpy as np

In [2]: a = np.arange(12)

In [3]: np.append(a, [6, 4, 2])  # aの末尾に要素を追加。
Out[3]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,  6,  4,  2])

In [4]: b = np.arange(12).reshape((3, 4))

In [5]: b
Out[5]:
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

In [6]: np.append(b, [1, 2, 3, 4])  # axisを指定しないと一次元配列が返ってくる。
Out[6]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,  1,  2,  3,  4])

第三引数のaxisを指定した場合、指定した軸方向に沿って要素が追加されます。追加前の配列と追加後の配列のshapeが一致していないとエラーが発生します。

In [7]: b
Out[7]:
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

In [8]: np.append(b, [[12, 13, 14, 15]], axis=0)
Out[8]:
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])

In [8]: np.append(b, [12, 13, 14, 15], axis=0) # shapeが一致していないときはエラーが発生する
---------------------------------------------------------------------------
ValueError: all the input arrays must have same number of dimensions

さらにaxisの使い方を見ていきます。

In [9]: c = np.arange(12).reshape((3, 4))

In [10]: c
Out[10]:
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

In [11]: d = np.linspace(0, 26, 12).reshape(3,4) # 今度はcと同じ`shape`の配列を生成。

In [12]: d
Out[12]:
array([[  0.        ,   2.36363636,   4.72727273,   7.09090909],
       [  9.45454545,  11.81818182,  14.18181818,  16.54545455],
       [ 18.90909091,  21.27272727,  23.63636364,  26.        ]])

In [13]: np.append(c,d, axis = 0) # axisに0を指定すると行方向に追加される
Out[13]:
array([[  0.        ,   1.        ,   2.        ,   3.        ],
       [  4.        ,   5.        ,   6.        ,   7.        ],
       [  8.        ,   9.        ,  10.        ,  11.        ],
       [  0.        ,   2.36363636,   4.72727273,   7.09090909],
       [  9.45454545,  11.81818182,  14.18181818,  16.54545455],
       [ 18.90909091,  21.27272727,  23.63636364,  26.        ]])

In [14]: np.append(c, d, axis = 1) # axisに1を指定すると列方向に要素が追加される。
Out[14]:
array([[  0.        ,   1.        ,   2.        ,   3.        ,
          0.        ,   2.36363636,   4.72727273,   7.09090909],
       [  4.        ,   5.        ,   6.        ,   7.        ,
          9.45454545,  11.81818182,  14.18181818,  16.54545455],
       [  8.        ,   9.        ,  10.        ,  11.        ,
         18.90909091,  21.27272727,  23.63636364,  26.        ]])

速度比較

NumPyのndarrayは多次元配列を扱うことを目的としたクラスで、事前にメモリ確保しています。np.append関数を使用すると、元のshapeが破壊されてしまうため、要素のコピーが発生して遅くなることがあります。一方で、Pythonのlistは可変長の要素を保持するベクタ型です。少し大きめの要素を事前に確保するので、毎回のコピーは発生しません。

同様のコードを書いて、Pythonのリストと速度比較してみます。

In [21]: def np_append():
    ...:     a = np.array([1, 2, 3])
    ...:     for i in range(10000):
    ...:         a = np.append(a, [i])
    ...:     return a
    ...:

In [22]: def list_append():
    ...:     a = [1, 2, 3]
    ...:     for i in range(10000):
    ...:         a.append(i)
    ...:     return np.array(a)
    ...:

In [23]: %timeit np_append()
10 loops, best of 3: 69.6 ms per loop

In [24]: %timeit list_append()
1000 loops, best of 3: 1.35 ms per loop

実行速度は、Pythonのlistの方が高速になります。データ構造を意識したコードを心がけると、何倍も高速にすることができ、ソースコードの効率化の見直しになるはずです。