Python入門トップページ


目次

  1. NumPy とは
  2. Python リストの場合
  3. NumPy の使用例
    1. 1次元配列(ベクトル)
    2. 2次元配列(行列)
    3. 要素の取り出し
    4. ベクトルと行列の変換
    5. NumPy のデータ型
    6. NumPy 配列の作成と操作
    7. ユニバーサル関数と集約関数
    8. NumPy 配列のソート
    9. NumPy の構造化配列
    10. NumPy 配列の保存と読み込み
  4. NumPy で線形代数
  5. NumPy の乱数生成

NumPy

NumPy の使用例

NumPy 配列の作成と操作

array

すでに1次元配列(ベクトル)2次元配列(行列)で使用したとおり,numpy.array() 関数の引数に Python のリストやタプルを指定することで NumPy 配列を作成できる.

numpy.arrayimport 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]]

目次に戻る

arange

numpy.arange() 関数では,numpy.arange(開始値, 終了値, 刻み幅) のように引数を指定して連続した値の配列を生成できます.numpy.arange(終了値) のように開始値 (標準の0) と刻み幅 (標準の1) を省略することもできます.

numpy.arangex = np.arange(4)
print(x)
[0 1 2 3]
numpy.arangex = np.arange(1,5,1)
print(x)
[1 2 3 4]
numpy.arangex = 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]

目次に戻る

linspace

numpy.linspace() 関数は,numpy.linspace(開始値, 終了値, 分割数) のように指定して連続した値の配列を生成できます.

numpy.linspacex = np.linspace(0, 1, 5)
print(x)
[0.   0.25 0.5  0.75 1.  ]
numpy.linspacex = 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. ]

目次に戻る

ones

numpy.ones() 関数は,すべての要素が1になるような配列を生成できます.このとき numpy.ones(n) では長さ n の1次元配列が,numpy.ones((m, n)) では m × n の2次元配列が作成されます.

numpy.onesx = np.ones(5)
print(x)
[1. 1. 1. 1. 1.]
numpy.onesX = np.ones((2, 3))
print(X)
[[1. 1. 1.]
 [1. 1. 1.]]

目次に戻る

zeros

numpy.zeros() 関数は,すべての要素が0になるような配列を生成できます.このとき numpy.zeros(n) では長さ n の1次元配列が,numpy.zeros((m, n)) では m × n の2次元配列が作成されます.

numpy.zerosx = np.zeros(5)
print(x)
[0. 0. 0. 0. 0.]
numpy.zerosX = np.zeros((2, 3))
print(X)
[[0. 0. 0.]
 [0. 0. 0.]]

なお行列のすべての要素を欠損値 (np.nan) で初期化したい場合は np.zeros() によってゼロで初期化したあとすべての要素np.nan を代入することで実現できます.

numpy.zerosX = np.zeros((2, 3))
X[:] = np.nan
print(X)
[[nan nan nan]
 [nan nan nan]]

目次に戻る

copy

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

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

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]

次に,偶数要素のみ 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 のリストについて forenumerate を用いた繰り返し処理と NumPy の where でどの程度パフォーマンスに差が出るのかを検証してみましょう.まず,1000万個の乱数のリストを生成し,NumPy 配列にコピーします.この段階で Python リスト scores と NumPy 配列 npscores には同じ値が格納されています.

import random
scores = random.sample(range(1,100000000), 10000000)
npscores = np.array(scores)

Jupyter Notebook のマジックコマンドを使って,forenumerate を用いた繰り返し処理の処理時間を計測します.この結果,平均で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'>

目次に戻る