はじめに #
NumPyのempty
関数を用いて、np.empty(0)
, np.empty([0, 0])
, …とすることで、空の(要素を持たない)任意の次元の配列を作成できる。本記事では、空の配列の作り方、使い方について簡単に考察する。
環境 #
- NumPy 1.19
本記事では、以下の通りライブラリをインポートしていることを前提とする。
import numpy as np
np.emptyについて #
np.empty
は中身を初期化せずに配列を作成する関数である。主な引数は次の通り。
np.empty(shape, dtype=float)
shape
は配列の形状を決める引数であり、
- int
- intを格納するタプル
- intを格納するリスト
のいずれかをとる。
dtype
は配列の数値の型を決める引数であり、
int
float
np.float32
np.int8
などを指定する(デフォルトはfloat
)。
要素数が5つの1次元配列を生成するには、shape
を5
、または(5)
、または[5]
とする。ただし、配列の中身は変わる場合がある。
>>> np.empty(5)
array([0. , 0.25, 0.5 , 0.75, 1. ])
>>> np.empty((5))
array([0. , 0.25, 0.5 , 0.75, 1. ])
>>> np.empty([5])
array([0. , 0.25, 0.5 , 0.75, 1. ])
要素数が2×3の2次元配列を生成するには、shape
を(2, 3)
、または[2, 3]
とする。
>>> np.empty((2, 3))
array([[0.93137112, 0.92761264, 0.98531337],
[0.28813119, 0.49620105, 0.41308828]])
>>> np.empty([2, 3])
array([[0.93137112, 0.92761264, 0.98531337],
[0.28813119, 0.49620105, 0.41308828]])
np.empty()
は、中身が全て0の配列を生成するnp.zeros()
や、中身が全て1の配列を生成するnp.ones()
と比較すると、配列を高速に生成できる利点がある。
空の配列の生成 #
冒頭に記したように、np.empty()
のshape
を0
や[0, 0]
, …とすると、任意の次元の空の配列を生成できる。
1次元配列の場合
>>> a1 = np.empty(0)
>>> a1
array([], dtype=float64)
>>> a1.ndim # 次元数
1
2次元配列の場合
>>> a2 = np.empty([0, 0])
>>> a2
array([], shape=(0, 0), dtype=float64)
>>> a2.ndim # 次元数
2
同様に、
np.empty([0, 0, 0])
で3次元の空の配列np.empty([0, 0, 0, 0])
で4次元の空の配列
がそれぞれ生成できる。
なお、np.zeros()
関数やnp.ones()
関数を使って、
np.zeros(0)
np.ones([0, 0])
としても、同様に空の配列を作成できる。しかし、関数名と異なり要素に0や1を含まない配列が生成されて紛らわしいため、np.empty()
関数を使うべきだと個人的に思う。
空の配列の使い方 #
空の配列は、「配列を結合したいが、最終的に得られる配列のサイズが分からない場合」、あるいは「計算の規模が小さく、かつプログラムの可読性を重視したい場合」に用いるべきである。
配列を逐次的に結合すると、その都度メモリを確保する必要があるため、実行速度が低下する。 そのため、最終的な配列のサイズが分かっている場合には、初めに必要なサイズの配列を確保して、逐次的に配列を代入した方が速く実行できる。
比較のため、配列を結合する関数stack()
と、最初に配列を確保する関数substitute()
を作成した。両者とも、長さが1000で要素が全て1の配列を返す。関数stack()
では長さ1の配列を1000回結合し、関数substitute()
では最初に確保した配列に長さ1の配列を1000回代入している。
def stack():
a = np.empty(0)
for i in range(1000):
a = np.hstack([a, np.array(1)])
return a
def substitute():
a = np.empty(1000)
for i in range(1000):
a[i] = np.array(1)
return a
IPythonの%timeitマジックコマンドで実行速度を測定する。
>>> %timeit stack()
5.93 ms ± 147 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit substitute()
436 µs ± 17.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
stack()
関数とsubstitute()
関数の平均実行時間はそれぞれ5.93ミリ秒と0.436ミリ秒であり、substitute()
の方が約14倍高速である。すなわち、処理速度を重視する場合、最初に配列を確保した方が良い。