Pythonで高速な数値計算や多次元配列を扱うならNumPyが定番です。
配列の作成から整形、演算、統計、行列計算、NaNの扱いまで、よく使う操作を初心者向けに体系立てて解説します。
サンプルコードは最小限で実行可能な形にし、出力例も併記して理解を助けます。
NumPyとは何か
NumPyはPythonで数値計算を高速に行うためのライブラリです。
Pythonのリストよりもメモリ効率が良く、C言語に近い速度でベクトル化された演算ができます。
データ分析や機械学習の土台として、pandasやscikit-learn、TensorFlowなどの内部でも広く使われています。
導入方法
pipでインストールします。
一般にnumpy
はnp
という別名でインポートします。
# NumPyのインストール
pip install numpy
# バージョンとエイリアスの確認
import numpy as np
print(np.__name__)
print(np.__version__) # 実行環境により値は異なります
numpy
(例) 2.3.3
ndarrayの基本
NumPyの中心はndarray
と呼ばれるN次元配列です。
要素の型(cst-code>dtype)が配列全体で統一され、固定長かつ連続領域に配置されます。
これにより、低レベル最適化が効き、高速に処理できます。
以下はPythonのリストとNumPy配列の挙動の違いを示す例です。
# リストとNumPy配列の*演算の違い
import numpy as np
py_list = [1, 2, 3]
np_arr = np.array([1, 2, 3], dtype=np.int64)
# リストは要素の複製、NumPy配列は数値のスカラー倍
print(py_list * 2)
print(np_arr * 2)
[1, 2, 3, 1, 2, 3]
[2 4 6]
よく使う属性
ndarray
には配列の形や型を知るための属性が用意されています。
代表的なものをまとめます。
属性 | 意味 | 例 |
---|---|---|
shape | 各次元のサイズ(タプル) | (3, 4) なら3行4列 |
ndim | 次元数 | 2は2次元配列 |
size | 要素総数 | shapeの積 |
dtype | 要素のデータ型 | int64やfloat64など |
itemsize | 要素1つのバイト数 | int64なら8 |
nbytes | 配列全体のバイト数 | itemsize×size |
strides | 次元ごとのバイトステップ | C順序なら(列数×itemsize, itemsize) |
import numpy as np
a = np.arange(12, dtype=np.int64).reshape(3, 4)
print(a)
print("shape:", a.shape)
print("ndim:", a.ndim)
print("size:", a.size)
print("dtype:", a.dtype)
print("itemsize:", a.itemsize)
print("nbytes:", a.nbytes)
print("strides:", a.strides)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
shape: (3, 4)
ndim: 2
size: 12
dtype: int64
itemsize: 8
nbytes: 96
strides: (32, 8)
配列の作成
NumPyでは、Pythonのリストやタプルから作る方法に加え、ゼロ配列や連番、乱数、型指定など多彩な作成手段があります。
リストから配列を作る
最も基本的なのはnp.array
です。
多次元は入れ子のリストで表現します。
import numpy as np
arr1d = np.array([1, 2, 3], dtype=np.int64)
arr2d = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32)
print(arr1d)
print(type(arr1d))
print(arr2d)
print("shape:", arr2d.shape, "dtype:", arr2d.dtype)
[1 2 3]
<class 'numpy.ndarray'>
[[1. 2. 3.]
[4. 5. 6.]]
shape: (2, 3) dtype: float32
ゼロ/ワン/定数配列
np.zeros
、np.ones
、np.full
で初期化した配列を作れます。
np.emptyは中身が未初期化なので注意します。
import numpy as np
z = np.zeros((2, 3), dtype=np.int32)
o = np.ones(4, dtype=np.float64)
f = np.full((2, 2), fill_value=7, dtype=np.int64)
print(z)
print(o)
print(f)
[[0 0 0]
[0 0 0]]
[1. 1. 1. 1.]
[[7 7]
[7 7]]
連番と範囲
整数の等間隔にはnp.arange
、指定した区間を分割した浮動小数にはnp.linspace
が便利です。
浮動小数の等差でarangeを使うと誤差で端がズレるため、区間分割が目的ならlinspace
を推奨します。
import numpy as np
a = np.arange(0, 10, 2, dtype=np.int64) # 0,2,4,6,8
b = np.linspace(0.0, 1.0, 5, dtype=np.float64) # 0.0から1.0を5分割
c = np.arange(1, 13, dtype=np.int64).reshape(3, 4) # 3行4列に整形
print(a)
print(b)
print(c)
[0 2 4 6 8]
[0. 0.25 0.5 0.75 1. ]
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
乱数配列を作る
新しい推奨APIはnp.random.default_rng
で生成器を作る方法です。
シードを固定すると再現性が得られます。
import numpy as np
rng = np.random.default_rng(seed=42)
# 一様乱数(0以上1未満)
u = rng.random((2, 3)) # float64が既定
# 整数乱数(0〜9)
ri = rng.integers(0, 10, size=(2, 3), endpoint=False)
# 正規分布(平均0, 標準偏差1)
g = rng.normal(loc=0.0, scale=1.0, size=(3,))
print("u shape/dtype:", u.shape, u.dtype)
print("ri shape/dtype:", ri.shape, ri.dtype)
print("g shape/dtype:", g.shape, g.dtype)
u shape/dtype: (2, 3) float64
ri shape/dtype: (2, 3) int64
g shape/dtype: (3,) float64
型の指定と変換
配列全体で同じdtype
を持ちます。
作成時にdtype=...
で指定、変換はastype
です。
import numpy as np
x = np.array([1, 2, 3], dtype=np.int32)
xf = x.astype(np.float64) # 安全な型拡張
y = np.array([1.2, 2.7, 3.5], dtype=np.float64)
y_trunc = y.astype(np.int64) # 小数部は切り捨て
y_round = np.round(y).astype(np.int64) # 四捨五入してから整数化
wrap = np.array([300]) # 256で循環(オーバーフローに注意)
print(x.dtype, xf.dtype)
print(y_trunc, y_round)
print(wrap, wrap.dtype)
int32 float64
[1 2 3] [1 3 4]
[44] uint8
注意: 整数型でのオーバーフローは例外ではなく桁あふれになります。
必要に応じて十分なビット幅の型を選んでください。
配列の整形と取り出し
配列から必要な部分を取り出し、形状を柔軟に変える操作はNumPyの要です。
インデックスとスライス
1次元ではPythonのスライスと同様に動作します。
2次元以上は[行, 列]
の順です。
import numpy as np
v = np.arange(10, dtype=np.int64)
print(v)
print(v[0], v[-1]) # 先頭と末尾
print(v[2:8:2]) # 2から7まで2刻み
print(v[:3], v[7:]) # 先頭3つと後ろ3つ
m = np.arange(1, 13, dtype=np.int64).reshape(3, 4)
print(m)
print(m[1, 2]) # 2行目3列目(0始まり)
print(m[0, :]) # 1行目
print(m[:, -1]) # 最終列
print(m[0:2, 2:]) # 左上の部分切り出し
[0 1 2 3 4 5 6 7 8 9]
0 9
[2 4 6]
[0 1 2] [7 8 9]
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
7
[1 2 3 4]
[ 4 8 12]
[[3 4]
[7 8]]
形状の変更
reshape
で形を変えられます。
-1は自動計算です。
import numpy as np
a = np.arange(12, dtype=np.int64)
print(a.reshape(3, 4))
print(a.reshape(2, 3, 2))
print(a.reshape(-1, 6)) # 要素数が一致する限り-1で自動補完
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[[ 0 1]
[ 2 3]
[ 4 5]]
[[ 6 7]
[ 8 9]
[10 11]]]
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]]
次元の追加/削除
次元の追加はnp.newaxis
(またはNone
)やnp.expand_dims
、削除はnp.squeeze
です。
import numpy as np
v = np.array([10, 20, 30], dtype=np.int64)
row = v[np.newaxis, :] # (1, 3)
col = v[:, np.newaxis] # (3, 1)
sqz = np.squeeze(np.array([[[1], [2], [3]]], dtype=np.int64)) # (3,)
print(row, row.shape)
print(col, col.shape)
print(sqz, sqz.shape)
[[10 20 30]] (1, 3)
[[10]
[20]
[30]] (3, 1)
[1 2 3] (3,)
転置と軸の入れ替え
2次元は.T
で転置、n次元はnp.transpose
やnp.swapaxes
が使えます。
import numpy as np
M = np.arange(6, dtype=np.int64).reshape(2, 3)
print(M)
print(M.T) # 転置(3, 2)
A = np.arange(24, dtype=np.int64).reshape(2, 3, 4)
B = np.transpose(A, (1, 0, 2)) # 軸0と1を入れ替え
print("A shape:", A.shape, "B shape:", B.shape)
[[0 1 2]
[3 4 5]]
[[0 3]
[1 4]
[2 5]]
A shape: (2, 3, 4) B shape: (3, 2, 4)
配列の結合/分割
np.concatenate
やnp.stack
で結合、np.split
やnp.array_split
で分割します。
import numpy as np
a = np.arange(6, dtype=np.int64).reshape(2, 3)
b = np.arange(6, 12, dtype=np.int64).reshape(2, 3)
vcat = np.concatenate([a, b], axis=0) # 縦結合(行方向)
hcat = np.concatenate([a, b], axis=1) # 横結合(列方向)
stk = np.stack([a, b], axis=0) # 新しい次元を追加して結合
s1, s2 = np.array_split(vcat, 2, axis=0) # 行方向に2分割
print(vcat)
print(hcat)
print(stk.shape) # (2, 2, 3)
print(s1)
print(s2)
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
[[ 0 1 2 6 7 8]
[ 3 4 5 9 10 11]]
(2, 2, 3)
[[0 1 2]
[3 4 5]]
[[ 6 7 8]
[ 9 10 11]]
配列の演算と集計
NumPyのベクトル化により、ループを書かずに全要素へ演算を適用できます。
要素ごとの演算とufunc
四則演算や三角関数などはufunc
と呼ばれる高速関数で実装されています。
import numpy as np
x = np.array([1, 2, 3], dtype=np.int64)
y = np.array([10, 20, 30], dtype=np.int64)
print(x + y) # 要素ごとの加算
print(np.add(x, y)) # 同じ意味
out = np.empty_like(x)
np.multiply(x, y, out=out) # 結果を書き込み(メモリ再利用)
print(out)
print(np.sqrt(np.array([1.0, 4.0, 9.0], dtype=np.float64))) # べき乗や根も高速
[11 22 33]
[11 22 33]
[10 40 90]
[1. 2. 3.]
ブロードキャストの基本
サイズの異なる配列でも形状が互換なら自動的に拡張して演算します。
右側から次元を照合し、1または同じサイズなら一致とみなします。
import numpy as np
A = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int64) # (2, 3)
b = np.array([10, 20, 30], dtype=np.int64) # (3,)
c = np.array([[100], [200]], dtype=np.int64) # (2, 1)
s = 5 # スカラー
print(A + b) # (2,3)+(3,) -> (2,3)
print(A + c) # (2,3)+(2,1) -> (2,3)
print(A + s) # (2,3)+() -> (2,3)
[[11 22 33]
[14 25 36]]
[[101 102 103]
[204 205 206]]
[[ 6 7 8]
[ 9 10 11]]
互換でない形状はValueErrorになります。
必要ならnp.newaxis
で次元を追加して整えます。
比較/論理/マスク抽出
条件で要素を選ぶ場合、比較演算とブール配列(マスク)を使います。
import numpy as np
A = np.arange(1, 10, dtype=np.int64).reshape(3, 3)
mask_even = (A % 2 == 0)
print(A)
print(mask_even) # 偶数の位置
print(A[mask_even]) # マスク抽出
mask_range = (A > 3) & (A < 8) # 複数条件(論理積)
print(A[mask_range]) # 条件に合う要素のみ
print(np.where(mask_even, A, -1)) # 条件式の分岐
print((A > 5).any(axis=1)) # 行ごとに1つでもTrueがあるか
[[1 2 3]
[4 5 6]
[7 8 9]]
[[False True False]
[ True False True]
[False True False]]
[2 4 6 8]
[4 5 6 7]
[[-1 2 -1]
[ 4 -1 6]
[ 7 8 9]]
[False True True]
基本統計
和や平均、標準偏差などはaxis
で方向を指定できます。
import numpy as np
data = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float64)
print("sum:", data.sum())
print("mean:", data.mean())
print("std:", data.std()) # 母標準偏差(ddof=0)
print("sum axis=0:", data.sum(axis=0)) # 列ごと
print("sum axis=1:", data.sum(axis=1)) # 行ごと
print("argmax axis=0:", data.argmax(axis=0)) # 列ごとの最大の位置
sum: 21.0
mean: 3.5
std: 1.707825127659933
sum axis=0: [5. 7. 9.]
sum axis=1: [ 6. 15.]
argmax axis=0: [1 1 1]
平均や標準偏差は浮動小数の丸め誤差の影響を受けるため、必要に応じてdtype=np.float64
を用いましょう。
行列計算
行列積は@
演算子またはnp.matmul
が使えます。
線形代数はnp.linalg
にまとまっています。
import numpy as np
A = np.array([[1, 2], [3, 4]], dtype=np.float64)
B = np.array([[5, 6], [7, 8]], dtype=np.float64)
print("A@B =\n", A @ B) # 行列積
print("det(A) =", np.linalg.det(A)) # 行列式
print("inv(A) =\n", np.linalg.inv(A)) # 逆行列
b = np.array([1.0, 0.0], dtype=np.float64)
x = np.linalg.solve(A, b) # 連立一次方程式 A x = b
print("solve(A, b) =", x)
A@B =
[[19. 22.]
[43. 50.]]
det(A) = -2.0000000000000004
inv(A) =
[[-2. 1. ]
[ 1.5 -0.5]]
solve(A, b) = [-2. 1.5]
特異行列(逆行列がない)に対してinvやsolveを呼ぶとエラーになります。
条件数が大きい場合は数値的に不安定なこともあります。
NaNへの対処
欠損や未定義の値はnp.nan
で表現されます。
通常の統計関数はNaNを伝播させるため、np.nanmean
などNaN無視版を使うか、事前に埋めます。
import numpy as np
a = np.array([1.0, np.nan, 3.0, np.nan, 5.0], dtype=np.float64)
print("isnan:", np.isnan(a))
print("nanmean:", np.nanmean(a)) # 1,3,5の平均=3.0
filled = np.where(np.isnan(a), np.nanmean(a), a) # 平均で埋める
print("filled:", filled)
# 無限大やNaNを有限値に置換
b = np.array([np.nan, np.inf, -np.inf, 2.0], dtype=np.float64)
print(np.nan_to_num(b, nan=0.0, posinf=1e9, neginf=-1e9))
isnan: [False True False True False]
nanmean: 3.0
filled: [1. 3. 3. 3. 5.]
[ 0.e+00 1.e+09 -1.e+09 2.e+00]
NaN判定にはnp.isnan
、有限判定にはnp.isfinite
が有用です。
まとめ
本記事ではNumPyの基礎として、配列(ndarray)の作成、整形、取り出し、要素演算、ブロードキャスト、条件抽出、統計、行列計算、NaN対処までを通して解説しました。
Pythonのリストでは難しい大規模データの高速処理も、NumPyならベクトル化とaxisの理解で簡潔に書けます。
はじめはnp.array
、reshape
、sum/mean
、@
、where
、nanmean
といった基本から手を動かし、必要に応じて型(dtype)やブロードキャストの整合を意識するとつまずきにくくなります。
次のステップとしてはpandasや可視化(Matplotlib/Seaborn)と組み合わせて、実データ分析に適用してみてください。