NumPy 配列 (ndarray) とは
Python の NumPy ライブラリ (numpy
) は、多次元配列を高速かつ便利に扱うための ndarray
クラスを提供しています(n-dimensional array の略)。
ndarray
は Python 標準のリストと比べて次のような違いがあります。
- 内部実装に C 言語などの低レベル言語が利用されており、処理が高速でメモリ効率がよい
- C 言語の配列と同様、単一のデータタイプ で 固定サイズ の配列である(サイズ変更は新しい
ndarray
の生成になる) - 全要素に対するブロードキャスト演算や、行列(ベクトル)演算など、便利な演算方法 が提供されている
NumPy はこのような特徴を持つため、次のように様々なライブラリで利用されています。
- データ解析ライブラリ: Pandas
- 画像処理ライブラリ: OpenCV
- 科学計算ライブラリ: SciPy
- 機械学習ライブラリ: TensorFlow、scikit-learn
- プロットライブラリ: Matplotlib
つまり、ほとんどの数学的、科学的な処理を行うライブラリは NumPy を利用していると考えられます。
NumPy 配列を生成する(numpy.array 関数)
ndarray
インスタンスは、numpy.array()
関数で生成することができます。
実際には、numpy
ライブラリは np
という別名でインポートするのが慣例となっているので、コード中では numpy.array()
ではなく np.array()
と記述されることが多いです。
1 次元の ndarray
次の例では、1 次元の ndarray
インスタンスを生成しています。
>>> import numpy as np
>>> a = np.array([1, 2, 3])
>>> a
array([1, 2, 3])
>>> type(a)
<class 'numpy.ndarray'>
多次元の ndarray
多次元の ndarray
インスタンスも同様に生成できます。
>>> a = np.array([[1, 2, 3], [4, 5, 6]])
>>> a
array([[1, 2, 3],
[4, 5, 6]])
NumPy 配列の形状(各次元のサイズ)は、shape
属性を参照することで調べることができます。
>>> a.shape
(2, 3) # 2 行 3 列を表すタプルが返される
行列として扱うには、各行のサイズ(列数)は揃えておく必要があります。 次のように、各行のサイズが異なるとエラーになります。
a = np.array([[1, 2, 3], [4, 5]]) # ValueError!
NumPy 配列は 1 つのデータタイプ (dtype) の要素のみを持つ
効率化のため、NumPy 配列に格納される要素の型(データタイプ)は統一されます。
データタイプは ndarray
インスタンスの初期化時の要素の値によって自動的に設定されます。
ndarray
インスタンスが保持している要素のデータタイプを調べるには、dtype
属性を参照します。
>>> a = np.array([[1, 2, 3], [4, 5, 6]]) # 整数値のみで初期化
>>> a.dtype
dtype('int64')
>>> a = np.array([[1, 2, 3], [1.5, 2.5, 3.5]]) # 浮動小数点数を含む値で初期化
>>> a.dtype
dtype('float64')
データタイプは、NumPy 配列の生成時に dtype
オプションで明示的に指定することができます。
次の例では、整数値のみで NumPy 配列を初期化していますが、浮動小数点数の要素として扱うように指定しています。
>>> a = np.array([[1, 2, 3], [4, 5, 6]], dtype='float64')
>>> a.dtype
dtype('float64')
>>> a
array([[ 1., 2., 3.],
[ 4., 5., 6.]])
NumPy 配列のインデックスアクセスとスライス
NumPy 配列 (ndarray
) の各要素は、リストと同様にインデックスでアクセスできます。
a = np.array([[1, 2, 3], [4, 5, 6]])
print(a[0][2]) # 3
a[0][2] = 100 # 値の変更
print(a[0]) # [1 100 3]
print(len(a)) # 2
print(len(a[0])) # 3
ndarray
はリストよりも柔軟なスライスを行うことができます。
a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# 2 行目だけを取得
print(a[1]) # [4, 5, 6]
print(a[1, :]) # 同上
# 2 行目以降を取得
print(a[1:]) # [[4, 5, 6], [7, 8, 9]]
print(a[1:, :]) # 同上
# すべての行の 2 列目までを取得
print(a[:, :2]) # [[1, 2], [4, 5], [7, 8]]
print(a[:, :-1]) # 同上
# すべての行の列を 1 つ飛びで取得
print(a[:, ::2]) # [[1, 3], [4, 6], [7, 9]]
ndarray
のスライスは、元の ndarray
のデータの参照であることに注意してください。
スライス経由での値の変更は、スライス元の ndarray
の変更を意味します。
一方で、Python 標準のリストのスライスは、Shallow Copy(一階層目だけコピー)です。
# NumPy 配列のスライスは元のデータを参照する
np_arr = np.array([1, 2, 3])
np_slice = np_arr[:]
np_slice[0] = 100
print(np_arr) # [100, 2, 3]
# リストのスライスは Shallow Copy(一階層目はコピーによって作られた新しいデータ)
py_arr = [1, 2, 3]
py_slice = py_arr[:]
py_slice[0] = 100
print(py_arr) # [1, 2, 3]
NumPy 配列に対する演算(ブロードキャスト)
NumPy 配列 (ndarray
) やそのスライスに対してスカラー値(単一の値)の四則演算や代入操作を行うと、全ての要素に対してその演算 が実行されます(この仕組みを ブロードキャスト と呼びます)。
これは非常に強力な仕組みで、この仕組みをうまく活用することで、多くの計算をループを記述せずに実装することができます。
a = np.array([[1, 2, 3], [4, 5, 6]])
a[0] = 10 # 1 行目の要素をすべて 10 にする
print(a) # [[10, 10, 10], [4, 5, 6]]
a -= 1 # すべての要素をマイナス 1 する
print(a) # [[9, 9, 9], [3, 4, 5]]
a[1] **= 2 # 2 行目の要素をすべて 2 乗する
print(a) # [[9, 9, 9], [9, 16, 25]]
a[:, 1] = 0 # 全ての行の 2 列目の要素を 0 にする
print(a) # [[9, 0, 9], [9, 0, 25]]
NumPy 配列 (ndarray
) 同士の四則演算も、それぞれ対応する要素に対して演算が行われます。
ただし、形状の異なる NumPy 配列同士で演算しようとすると ValueError
が発生します。
a = np.array([[1, 2, 3], [4, 5, 6]])
b = np.array([[5, 5, 5], [2, 2, 2]])
c = a + b # [[6, 7, 8], [6, 7, 8]]
c = a - b # [[-4, -3, -2], [2, 3, 4]]
c = a * b # [[5, 10, 15], [8, 10, 12]]
c = a / b # [[0.2, 0.4, 0.6], [2.0, 2.5, 3.0]]
c = a // b # [[0, 0, 0], [2, 2, 3]]
c = a % b # [[1, 2, 3], [0, 1, 0]]
c = a ** b # [[1, 32, 243], [16, 25, 36]]
特に、除算 (a / b
) の結果は、float64
型のデータタイプになることに注意してください。
多次元の NumPy 配列と、1 次元の NumPy 配列の演算でもブロードキャストの仕組みが働きます。 列のサイズは等しくないといけません。
a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b = np.array([1, 2, 3])
c = a + b # [[2, 4, 6], [5, 7, 9], [8, 10, 12]]
c = a - b # [[0, 0, 0], [3, 3, 3], [6, 6, 6]]
c = a * b # [[1, 4, 9], [4, 10, 18], [7, 16, 27]]
c = a / b # [[1.0, 1.0, 1.0], [4.0, 2.5, 2.0], [7.0, 4.0, 3.0]]
c = a // b # [[1, 1, 1], [4, 2, 2], [7, 4, 3]]
c = a % b # [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
c = a ** b # [[1, 4, 27], [4, 25, 216], [7, 64, 729]]
行列演算(内積、外積、転置)
NumPy 配列 (ndarray
) には、行列の内積、外積、転置などの演算を行うためのメソッドが用意されています。
a = np.array([[1, 2, 3], [4, 5, 6]])
b = np.array([1, 2, 3])
result = a.dot(b) # array([14, 32])
a = np.array([1, 2, 3])
b = np.array([10, 10, 10])
result = np.cross(a, b) # array([-10, 20, -10])
a = np.array([[1, 2, 3], [4, 5, 6]])
result = a.transpose() # array([[1, 4], [2, 5], [3, 6]])
ちなみに、各メソッドの引数としては、ndarray
インスタンスではなく、リストやタプルも渡せるようになっていますが、戻り値は結局 ndarray
になります。
result = np.cross([1, 2, 3], (10, 10, 10)) # array([-10, 20, -10])