NumPy - ikymrkw/pydepot GitHub Wiki

import numpy as np とするのが通例。

NumPy は np.ndarray という型(クラス)で多次元配列(ベクトル、行列、テンソル)を扱う。

Python の sequence 型(リストなど)との主な違いは、

  • 要素の型が一様であること、
  • サイズ(各次元・軸の要素数)が固定されていること、
  • 配列への演算がすべての要素に対して行われること(ブロードキャスト)。

配列の生成

基本は、np.array または np.asarray で配列を生成する。

L = [1, 2, 3]
a = np.array(L)
b = np.asarray(L, dtype=np.int32)  # データ型を指定

このように、Pythonのリストなどから nd.array を作るときは、 np.array でも np.asarray でも同じ。 a や b は L とは切り離されているので、 a や b の要素として別の値を代入しても L の要素は変わらない。

多次元のリストから多次元配列を作ることができる。

a2d = np.array([[1,2,3], [4,5,6], [7,8,9])  # 2次元配列

np.array と np.asarray の違いは、引数が ndarray の場合に現れる。

a = np.array([1,2,3])
b, c = np.array(a), np.asarray(a)
print(a, b, c)  # => '[1 2 3] [1 2 3] [1 2 3]'
b[0], c[1] = 8, 9
print(a, b, c)  # => '[1 9 3] [8 2 3] [1 9 3]'
  • np.array は新たな配列を作るので、元の配列とは切り離される。
  • np.asarray は引数が ndarray (かつ型も合う)なら引数そのものを返すので、実態として同じ配列を指す。

0 や 1 のみ、連続値、乱数など特殊な配列を生成することもできる。

# 0、1、特定の値のみ
np.zeros(dim)
np.zeros( (dim1, dim2) )
np.ones(dim)
np.ones( (dim1, dim2) )
np.full(dim, value)
np.ones( (dim1, dim2), value )
# 既存の配列と同じ形(shape)で作ることもできる
np.zeros_like(arr)
np.ones_like(arr)
np.full_like(arr, value)

# 連続値
np.arange(stop)           # range() とほぼ同じ
np.linspace(start, stop)  # [start, stop] を 50区間に均等分割する 51個の点を返す

# 乱数
np.random.rand()     # 0~1のランダムな実数を返す
np.random.rand(100)  # 0~1の乱数100個の配列を返す
# 範囲を変えるには、引数で指定するのではなく、ブロードキャストする
rand(10) * 3 + 1     # 1.0~4.0 の 10 次元の配列
# 補足:randn はガウス分布

配列の情報と型・形の変換

a = np.array([1,2,3], [4,5,6](/ikymrkw/pydepot/wiki/1,2,3],-[4,5,6))  # 2行3列の行列(2次元配列)
a.ndim   # = 2; 次元数(テンソルの階数)
a.shape  # = (2, 3); 各次元のサイズ(テンソルの各階の次元数)
a.size   # = 6; 要素の総数
a.dtype  # = dtype('int32'); データ型

a.astype(np.float64)  # データ型を変換した配列を返す

if a.dtype == np.int32: pass  # 実際に入っている型は通常 np.int64 や np.float64
if np.issubdtype(a.dtype, np.integer): pass  # 整数なら何でもいい場合は np.integer と np.issubdtype(.) を使う

a.reshape((6,))  # 新たに1次元に再構成した配列を返す
a.reshape((3,2)) # 新たに3行2列に再構成した配列を返す

# 軸の交換(ピボット)
#   a[i] とアクセスするとき i が指すのが axis 0 (行列なら行)、
#   a[i][j] とアクセスするとき j が指すのが axis 1 (行列なら列)
a.swapaxes( m, n )  # axis m と axis n を入れ替えた配列を作って返す
#   2次元配列ならわかるが、多次元になると理解が難しい

配列の要素へのアクセス

リストと同様に [...] で添え字によるアクセスが可能。スライスも可能。 ただしリストと違って、添え字の列を与えることもできる。

a = np.arange(10)  # = array([0,1,2,3,4,5,6,7,8,9])
a[0]      # 参照
a[1] = 11  # 代入

# スライス
a[:], a[3:], a[:7], a[3:7], a[:-2]  # 部分配列
a[::-1]  # 逆順
a[ [1,3,5] ]  # => array([11,3,5])  # 添え字のリストによるアクセス(リストはndarrayでもよい)

多次元の場合、スライス内でコンマを使って各次元・軸にアクセスする。

b = np.arange(10).reshape((2,5))  # = [ [0,1,2,3,4], [5,6,7,8,9] ]
b[1,3]   # = 8; 0次元配列になったら要素そのものを返す
b[1]     # = array([5,6,7,8,9])
b[:,3]   # = array([3,8])
# b[:][3] とは意味が違うことに注意。
# b[:]はbと同じものを返すので、b[:][3] は b[3]と同じだが、axis 0には3個も値がないのでエラー。
# b[:][1] は b[1] と同じ。

配列の基本演算とブロードキャスト

b = np.array([11,1,21], [2,22,12])

# 配列を代表する値
b.min(), b.max(), b.sum(), b.mean(), b.var(), b.std()  # 全要素に対する最小値・最大値・合計・平均・分散・標準偏差を返す
b.argmin(), b.argmax()  # 最小値・最大値をとる添え字を返す(多次元の場合は1次元にreshapeしたときの添え字)

# minやargminにaxis=0を指定すると、軸0の添え字を変えたときの最小値(となる他の軸の添え字)の列を返す
# 行列で考えるとわかりやすい。軸0は行なので、各列について行を変えたときの最小値を求めている。
b.min(axis=0)    # = [2,1,12]  # [b[:,0].min(), b[:,1].min(), b[:,2].min()] と同じ
b.argmin(axis=0) # = [1,0,1]   # [b[:,0].argmin(), b[:,1].argmin(), b[:,2].argmin()] と同じ

# ブロードキャスト:配列への演算は全要素に対して一斉に行われる
b + 4  # 全要素に4を足した配列を新たに作り、返す
b * 2  # 全要素に2をかけた配列を新たに作り、返す
b==1   # array([False,True,False], [False,False,False](/ikymrkw/pydepot/wiki/False,True,False],-[False,False,False)) を返す(比較演算子もブロードキャストされる)

c = np.array([1,2,3], [1,2,3]])
b + c  # 添え字が対応する各要素同士を足した配列を新たに作り、返す

配列の結合

# np.r_[...] は要素を結合する。配列を指定してもよいし、要素をそのまま指定してもよい。混じっていてもよい。
np.r_[ np.array([1,2,3]), np.array([7,8,9]) ]  # => array([1,2,3,7,8,9]) 

# np.c_[...] は要素をつないで多次元配列を作る
np.c_[ np.array([1,2,3]), np.array([7,8,9]) ]  # => array([1,7], [2,8], [3,9](/ikymrkw/pydepot/wiki/1,7],-[2,8],-[3,9))

本当は np.r_['0,0,-1', ...] のように第1引数に文字列で結合規則を書くことができ、np.c_[...] は np.r_['-1,2,0'] と同じ意味になる。

配列のソート

ndarray.sort() は自身をソートされた順序に更新し、 np.sort(a) はソートされた新しい配列を返す。

a = np.array([3,5,1,4,2])
aa = np.sort(a)
a.sort()

多次元配列の場合は、axis=N で軸を指定するが、デフォルトでは最後の軸(-1)を動かしてソートする。

2次元配列(行列)の場合、最後の軸は列なので、各値は行方向には移動せず、列方向にだけ移動して、行の中でソートされた状態になる。各行は独立してソートされる(元の列にあった組み合わせが維持されるわけではない)。

a = np.array([16,3,1], [2,14,28], [27,29,15](/ikymrkw/pydepot/wiki/16,3,1],-[2,14,28],-[27,29,15))
np.sort(a)  # たとえば第0行は [16, 3, 1] なので [1, 3, 16] にソートされる
# => [1,3,16], [2,14,28], [15,27,29](/ikymrkw/pydepot/wiki/1,3,16],-[2,14,28],-[15,27,29)
np.sort(a, axis=0)
# => [2,3,1], [16,14,15], [27,29,28](/ikymrkw/pydepot/wiki/2,3,1],-[16,14,15],-[27,29,28)

逆順にする引数はないので、結果をスライス [::-1] で逆順にする。

表計算ソフトでやるような、行や列を維持したまま特定の列/行を見てソートするには、 argsort を使う。

argsort は sort と同様だが、ソート前の添え字を並び替えた順列を返す。添え字の順列をスライスに入れればその順番に並び替えた多次元配列になる。たとえば、列0だけを見て行をソートする場合:

a = np.array([16,3,1], [2,14,28], [27,29,15](/ikymrkw/pydepot/wiki/16,3,1],-[2,14,28],-[27,29,15))
idx = np.argsort(a[:,0])
aa = a[idx]

ヒストグラムと数え上げ

hist, bins = np.histogram(arr)

histはi番目の区間(bin)の個数の配列(n次元)、binsはi番目の区間の始点=i+1番目の区間の終点の配列(n+1次元)

np.histogram(arr, bins=100)  # 100区間(bin)に分ける。デフォルトは10。
np.histogram(arr, range=(1.0, 6.0))  # binsの範囲。デフォルトは(arr.min(), arr.max())
np.histogram(arr, density=True)  # hist を個数の配列でなく濃度の配列にする(合計が1.0になるように正規化される)

離散値・カテゴリカルデータを数え上げるだけであれば、unique()でよい。

np.unique(arr, return_counts=True)
# (arr1, arr2) が返る。arr1はユニークな値のリスト、arr2はarr1の各要素の登場回数。

条件を満たす要素のみから成る配列を作る

arr2[ np.where(arr1 > 3) ]
  • arr1>3 は、arr1 の各要素について > 3 かどうかによって True または False が入った配列を返す。
  • np.where(conditions) は、conditions が True な要素の添え字のみから成る(短い)配列を返す。
  • arr2[indexes] は、indexesの各要素を添え字とみなして、その添え字に対応するarr2の要素のみから成る配列を返す。

これらを組み合わせると、条件を満たす要素のみから成る部分配列を得ることができる。

X[ np.where( Y==3 ) ]
# 説明変数ベクトルの列 X と目的変数(ラベル)列 Y があるとき、
# ラベルが3であるXの要素のみから成る配列を返す

データの読み込みと保存

NumPy配列をバイナリとして保存するには、np.saveを使う。

np.save("foo", arr)  # 自動的に foo.npy というファイルに保存される
arr2 = np.load("foo.npy")

実は save には Python 2 互換オプション(デフォルト True)があり、互換だと npy、非互換だと npz が拡張子になるようだが、通常は無視していいだろう。