すでに1次元配列(ベクトル)や2次元配列(行列)で使用したとおり,numpy.array()
関数の引数に Python のリストやタプルを指定することで NumPy 配列を作成できます.
numpy.array
import numpy as np
x = np.array([1,2,3,4])
print(x)
X = np.array([[1, 2], [3, 4]])
print(X)
[1 2 3 4] [[1 2] [3 4]]
numpy.arange()
関数では,numpy.arange(開始値, 終了値, 刻み幅)
のように引数を指定して連続した値の配列を生成できます.numpy.arange(終了値)
のように開始値 (標準の0) と刻み幅 (標準の1) を省略することもできます.
numpy.arange
x = np.arange(4)
print(x)
[0 1 2 3]
numpy.arange
x = np.arange(1,5,1)
print(x)
[1 2 3 4]
numpy.arange
x = np.arange(0, 1, 0.1)
print(x)
[0. 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9]
numpy.linspace()
関数は,numpy.linspace(開始値, 終了値, 分割数)
のように指定して連続した値の配列を生成できます.
numpy.linspace
x = np.linspace(0, 1, 5)
print(x)
[0. 0.25 0.5 0.75 1. ]
numpy.linspace
x = np.linspace(0, 1, 11)
print(x)
[0. 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ]
numpy.ones()
関数は,すべての要素が1になるような配列を生成できます.このとき numpy.ones(n)
では長さ n の1次元配列が,numpy.ones((m, n))
では m × n の2次元配列が作成されます.
numpy.ones
x = np.ones(5)
print(x)
[1. 1. 1. 1. 1.]
numpy.ones
X = np.ones((2, 3))
print(X)
[[1. 1. 1.] [1. 1. 1.]]
numpy.zeros()
関数は,すべての要素が0になるような配列を生成できます.このとき numpy.zeros(n)
では長さ n の1次元配列が,numpy.zeros((m, n))
では m × n の2次元配列が作成されます.
numpy.zeros
x = np.zeros(5)
print(x)
[0. 0. 0. 0. 0.]
numpy.zeros
X = np.zeros((2, 3))
print(X)
[[0. 0. 0.] [0. 0. 0.]]
なお行列のすべての要素を欠損値 (np.nan) で初期化したい場合は np.zeros()
によってゼロで初期化したあとすべての要素に np.nan
を代入することで実現できます.
numpy.zeros
X = np.zeros((2, 3))
X[:] = np.nan
print(X)
[[nan nan nan] [nan nan nan]]
Python リストの複製の場合と同様に,変数と同じような感覚で NumPy 配列に NumPy 配列を代入すると思わぬ結果になる可能性があるので注意が必要です.例えば, NumPy 配列に NumPy 配列を代入したあと,一方の値を変更すると他方に影響が及びます.つまり,NumPy 配列の代入では新たな NumPy 配列ができているわけではありません.
b の書き換えの影響が a にも及ぶ
a = np.array([1, 2, 3, 4, 5])
b = a
b[2] = 10 # b の値を変更
print(a)
print(b)
[ 1 2 10 4 5] [ 1 2 10 4 5]
NumPy 配列を複製して新たな NumPy 配列を作りたい場合は copy()
メソッドを使います.この場合は一方の値を変更しても他方に影響は及びません.
b の書き換えは a に影響しない
a = np.array([1, 2, 3, 4, 5])
b = a.copy()
b[2] = 10 # b の値を変更
print(a)
print(b)
[1 2 3 4 5] [ 1 2 10 4 5]
concatenate は「連結する」とか「鎖状につなぐ」という意味です.つまり,NumPy 配列を連結する操作が可能です.まず,1次元配列を連結するには次のように記述します.
a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
b = np.concatenate((a, a))
print(b)
[1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9]
このとき,上のように連結したい配列を ( )
で囲う必要があることに注意してください.つまり,次のように3つ以上の配列を連結することもできます.
a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
b = np.concatenate((a, a, a))
print(b)
[1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9]
2次元配列を連結すると次のような結果になります.
a = np.array([[1, 2, 3],[4, 5, 6], [7, 8, 9]])
b = np.concatenate((a, a))
print(b)
[[1 2 3] [4 5 6] [7 8 9] [1 2 3] [4 5 6] [7 8 9]]
オプショナル引数 axis=0
を与えた場合も同じ結果になります.
a = np.array([[1, 2, 3],[4, 5, 6], [7, 8, 9]])
b = np.concatenate((a, a), axis=0)
print(b)
[[1 2 3] [4 5 6] [7 8 9] [1 2 3] [4 5 6] [7 8 9]]
オプショナル引数 axis=1
を与えた場合は連結の方向が次のように変化します.
a = np.array([[1, 2, 3],[4, 5, 6], [7, 8, 9]])
b = np.concatenate((a, a), axis=1)
print(b)
[[1 2 3 1 2 3] [4 5 6 4 5 6] [7 8 9 7 8 9]]
ブロードキャストは,異なるサイズの NumPy 配列に対して,ユニバーサル関数などを適用することができる機能です.これにより,低速な Python の繰り返し処理を使わずに高速な処理が可能になります.
まず,2つの配列の次元とサイズが同じ場合は,要素ごとの演算が行われます.
a = np.array([0, 1, 2, 3])
b = np.array([4, 5, 6, 7])
print(a + b)
print(a - b)
print(a * b)
print(a / b)
[ 4 6 8 10] [-4 -4 -4 -4] [ 0 5 12 21] [0. 0.2 0.33333333 0.42857143]
例えばスカラー(0次元配列)と配列のブロードキャストを行います.このとき,値 2 がコピーされて [2, 2, 2, 2, 2, 2, 2, 2, 2, 2] が生成された後に a との演算が行われていると考えることができます.
a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
print(a)
print(a + 2)
print(a - 2)
print(a * 2)
print(a / 2)
[1 2 3 4 5 6 7 8 9] [ 3 4 5 6 7 8 9 10 11] [-1 0 1 2 3 4 5 6 7] [ 2 4 6 8 10 12 14 16 18] [0.5 1. 1.5 2. 2.5 3. 3.5 4. 4.5]
配列のサイズが一致せず,どちらも長さが1でない場合はブロードキャストできないためにエラーになります.
a = np.array([0, 1, 2, 3])
b = np.array([4, 5, 6])
print(a + b)
ValueError
ブロードキャストを使うとすべてのビットの反転を行うことも可能です.
a = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
print(a)
print(1-a)
[[1 0 0] [0 1 0] [0 0 1]] [[0 1 1] [1 0 1] [1 1 0]]
マスキングは何らかの条件で配列内の値を抽出するときなどに利用できます.例えば 5 未満であるかどうかをチェックします.
a = np.array([1,3,8,4,2,6,7,9,5])
print(a < 5)
[ True True False True True False False False False]
5 と等しいかどうかをチェックします.
a = np.array([1,3,8,4,2,6,7,9,5])
print(a == 5)
[False False False False False False False False True]
次は,偶数であるかどうかをチェックします.
a = np.array([1,3,8,4,2,6,7,9,5])
print(a % 2 == 0)
[False False True True True True False False False]
条件を満たした要素数を数えたい場合には count_nonzero()
を使えます.
a = np.array([1,3,8,4,2,6,7,9,5])
np.count_nonzero(a < 5)
4
5 と一致した要素数も数えてみます.
a = np.array([1,3,8,4,2,6,7,9,5])
np.count_nonzero(a == 5)
1
np.sum()
を使った場合も条件を満たした要素数を数えます.ここで,5未満のデータの合計ではなく,個数を数えていることに注意してください.つまり,[ True True False True True False False False False]
の合計を計算しています.
a = np.array([1,3,8,4,2,6,7,9,5])
np.sum(a < 5)
4
条件を満たす要素だけ取得して新たな配列を作成するには次のように記述します.
a = np.array([1,3,8,4,2,6,7,9,5])
a[a < 5]
array([1, 3, 4, 2])
よって,5未満のデータだけ取り出して合計を計算し,1 + 3 + 4 + 2 = 10 を求めたければ次のようになります.
a = np.array([1,3,8,4,2,6,7,9,5])
np.sum(a[a < 5])
10
where を使うと NumPy 配列に対して,条件を満たす要素にだけ何らかの処理を行うことが可能です.これは if による条件分岐や Excel の IF 関数と考え方が似ていますが,非常に高速に動作します.
例えば,要素の値が 5 未満のものについては 10 倍し,それ以外は値を変更しない処理は次のように書くことができます.
a = np.array([1,3,8,4,2,6,7,9,5])
print(a)
a = np.where(a < 5, a * 10, a)
print(a)
[1 3 8 4 2 6 7 9 5] [10 30 8 40 20 6 7 9 5]
次に,配列 a
の要素の値が 5 未満のものについては配列 b
にある値を格納し,それ以外は値を変更しない処理は次のように書くことができます.ただし,配列 a
と b
の長さが異なる場合にはエラーとなって動作しないことに注意してください.
a = np.array([1,3,8,4,2,6,7,9,5])
b = np.array([99,98,97,96,95,94,93,92,91])
print(a)
print(b)
a = np.where(a < 5, b, a)
print(a)
[1 3 8 4 2 6 7 9 5] [99 98 97 96 95 94 93 92 91] [99 98 8 96 95 6 7 9 5]
さらに,偶数要素のみ 10 を加えて,奇数要素は 0 に書き換える処理は次のようになります.
a = np.array([1,3,8,4,2,6,7,9,5])
print(a)
a = np.where(a%2 == 0, a + 10, 0)
print(a)
[1 3 8 4 2 6 7 9 5] [ 0 0 18 14 12 16 0 0 0]
ここで Python のリストについて for
と enumerate
を用いた繰り返し処理と NumPy の where
でどの程度パフォーマンスに差が出るのかを検証してみましょう.まず,1000万個の乱数のリストを生成し,NumPy 配列にコピーします.この段階で Python リスト scores
と NumPy 配列 npscores
には同じ値が格納されています.
import random
scores = random.sample(range(1,100000000), 10000000)
npscores = np.array(scores)
Jupyter Notebook のマジックコマンドを使って,for
と enumerate
を用いた繰り返し処理の処理時間を計測します.この結果,平均で0.841秒かかったことがわかりました.
%%timeit
for i, s in enumerate(scores):
if s % 2 == 0:
x = s * 10
else:
x = 0
841 ms ± 3.03 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
次に,NumPy の where
で記述した場合の処理時間を計測します.この結果,平均で0.171秒となり,5倍近くの速度で処理できていることがわかりました(この結果はPC環境に依存します).
%%timeit
x = np.where(npscores%2 == 0, npscores * 10, 0)
171 ms ± 1.28 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
NumPy 配列から条件を満たすデータを検索し,そのインデックスを取得するには上で説明した where を使うと可能です.例えば NumPy 配列から偶数を検索し,そのインデックスを取得してみます.
a = np.array([1,3,8,4,2,6,7,2,5])
idx = np.where(a%2 == 0)
print(idx)
print(type(idx))
(array([2, 3, 4, 5, 7]),) <class 'tuple'>
上の方法では結果が NumPy 配列が含まれたタプル形式になっていることがわかります.よって,タプルから NumPy 配列を取り出すためには,次のように記述します.
a = np.array([1,3,8,4,2,6,7,2,5])
idx = np.where(a%2 == 0)[0]
print(idx)
print(type(idx))
[2 3 4 5 7] <class 'numpy.ndarray'>
次は,値が2と等しいデータをすべて検索し,そのインデックスを NumPy 配列形式で取得します.
a = np.array([1,3,8,4,2,6,7,2,5])
idx = np.where(a == 2)[0]
print(idx)
print(type(idx))
[4 7] <class 'numpy.ndarray'>
値が8と等しいデータを検索し,そのインデックスを取得します.検索結果が1個に限られる場合は次のように整数型で取得することも可能です.
a = np.array([1,3,8,4,2,6,7,2,5])
idx = np.where(a == 8)[0][0]
print(idx)
print(type(idx))
2 <class 'numpy.int64'>
ゼロで初期化された配列の一部の要素だけに1を代入するには次のような方法が考えられるでしょう.
a = np.zeros(10)
a[3] = 1
a[6] = 1
a[8] = 1
print(a)
[0. 0. 0. 1. 0. 0. 1. 0. 1. 0.]
しかしながら,書き換えたい要素のインデックスをリストとして保持しておくと,より簡単に書き換えることができます.これはスカラーと配列のブロードキャストが行われていると理解するとよいでしょう.
a = np.zeros(10)
idx = [3, 6, 8]
a[idx] = 1
print(a)
[0. 0. 0. 1. 0. 0. 1. 0. 1. 0.]
したがって,ブロードキャストを利用して,一部の要素にだけ別の値をまとめて代入することもできるようになります.
a = np.zeros(10)
idx = [3, 6, 8]
a[idx] = [1, 2, 3]
print(a)
[0. 0. 0. 1. 0. 0. 2. 0. 3. 0.]
もちろん,要素数が一致しない場合にはエラーになることに注意してください.
a = np.zeros(10)
idx = [3, 6, 8]
a[idx] = [1, 2, 3, 4]
print(a)
ValueError: shape mismatch: value array of shape (4,) could not be broadcast to indexing result of shape (3,)