
for文を書き連ねて処理が遅い…配列の形が合わなくてエラー…
axisが分からない…そんな悩み、丸ごと解消します。
本記事では、NumPyの3本柱「ベクトル化」「ブロードキャスト」「軸(axis)」を核に、現場でそのまま使える最小セットを配布します。
見通しのよいコードに置き換え、速度・保守性・再現性を一気に底上げしましょう。
この記事で身に付く力
- ndarrayの最小作法(生成・dtype・形変換)
- ベクトル化(算術・比較・where・ufunc)でforを減らす
- ブロードキャスト(5パターン)を安全に使い分ける
- インデクシング(スライス/ブール/ファンシー)とコピーの罠回避
- 軸(axis)思考で集計と接続(pandas/Sklearn)を一気通貫に
NumPyが「速く・短く・間違いにくい」理由
配列思考のコアは3つ。ベクトル化で式にまとめ、ブロードキャストで形を合わせ、axisは「どちらに潰すか」で考える。筆者(ふみと)は現場でこの3原則に徹底的に寄せることで、10〜100倍の速度改善とバグ削減を繰り返してきました。以下、“コピペで確認できる”最短ルートで進めます。
1) ndarrayの最小作法(生成・shape・dtype)
まずは配列の「型」と「形」を揃える作法から。ここを丁寧にすると後工程が一気に安定します。
import numpy as np
# 生成(Pythonリスト→ndarray)
a = np.array(\[1, 2, 3], dtype=np.int32)
# 等差/ゼロ/同形ones
r = np.arange(0, 10, 2) # 0,2,4,6,8
z = np.zeros((2, 3), dtype=float)
ones = np.ones\_like(z) # 形とdtypeを継承
# 形と次元
b = np.array(\[\[1,2,3],\[4,5,6]])
print(b.shape) # (2, 3)
print(b.ndim) # 2
# 形変換
b\_T = b.T # 転置(ビュー)
b2 = b.reshape(3, 2) # 要素数は不変
b3 = b.reshape(-1) # 1次元へ(長さは自動)
# dtypeの変換(不要なコピーを避ける)
x = np.array(\[1, 2, 3], dtype=np.float32)
x64 = x.astype(np.float64, copy=False)
要点
reshape
は要素数不変が大原則。astype(copy=False)
で不要なコピー回避。
2) ベクトル化:forを書かず“式にする”
配列演算は基本「要素ごと」。np.where
や np.clip
は、高速で読みやすい条件分岐に最適です。
x = np.array([1,2,3,4])
# 1) 算術
x2 = x \* 2 + 1
# 2) 比較とマスク
mask = (x % 2 == 0) # 偶数だけTrue
# 3) where(ifの代替)
y = np.where(mask, x\*10, -x)
# 4) ufunc(普遍関数)
z = np.sqrt(x)
w = np.clip(x, 2, 3)
3) ブロードキャスト:形が違っても足せる理由
右から次元を突き合わせ、長さが同じ or どちらか1なら整合します。合わなければエラー。使い分けの5パターンを覚えれば怖くありません。
# (3,) と スカラー → (3,)
np.array([1,2,3]) + 10 # [11,12,13]
# (3, 1) と (1, 4) → (3, 4)
A = np.arange(3).reshape(3,1)
B = np.arange(4).reshape(1,4)
C = A + B # 3×4 の足し合わせ表
# 画像(H×W×3)と (1×1×3)
img = np.random.rand(480, 640, 3)
shift = np.array(\[0.1, -0.05, 0.0]).reshape(1,1,3)
adj = np.clip(img + shift, 0, 1)
ブロードキャストの5パターン
- スカラー × 配列
- (n, 1) × (1, m)
- (n, m) × (1, m)(行方向)
- (n, m) × (n, 1)(列方向)
- (b, h, w, c) × (1, 1, 1, c)(画像/チャネル)
4) インデクシング:スライス/ブール/ファンシー
「どれがビュー(参照共有)で、どれがコピーか」を理解しておくと、思わぬ破壊を防げます。
A = np.arange(12).reshape(3,4)
# スライス:ビュー(元を共有)
A\[:2, 1:3] = 0
# ブール
mask = A % 2 == 0
A\[mask] = -A\[mask]
# ファンシー(整数配列で抽出)
rows = np.array(\[0,2])
cols = np.array(\[1,3])
sub = A\[rows\[:,None], cols] # 交差(2×2)
注意:コピーとビュー
- スライスはビュー、ファンシーはコピーが返りやすい。
- 代入の可否・速度・メモリに影響します。
5) 軸(axis)と集計:「どちらに潰すか」
axis=0
は縦に潰す(列ごと)、axis=1
は横に潰す(行ごと)。言い換えると「残したい方向の逆がaxis」です。
X = np.array([[1,2,3],[4,5,6]]) # (2,3)
print(X.sum()) # 21(全体)
print(X.sum(axis=0)) # \[5,7,9] 列和(2行を潰す)
print(X.mean(axis=1)) # \[2.,5.] 行平均(3列を潰す)
print(np.nanmean(X)) # NaN無視の平均
6) 乱数と線形代数(最小セット)
rng = np.random.default_rng(42)
# サンプル
u = rng.uniform(0, 1, size=5)
n = rng.normal(loc=0, scale=1, size=(2,3))
idx = rng.choice(10, size=5, replace=False)
# 線形代数
A = rng.normal(size=(3,3))
b = rng.normal(size=3)
sol = np.linalg.solve(A, b)
vals, vecs = np.linalg.eig(A)
C = A @ A.T
7) メモリ/コピーの罠(ravel/flatten/copy)
X = np.arange(6).reshape(2,3)
Y = X[:, :2] # ビュー(共有)
Y[0,0] = 999
print(X[0,0]) # 999(元も変わる)
# 1次元化
v1 = X.ravel() # 可能ならビュー
v2 = X.flatten() # 常にコピー
Y2 = Y.copy()
8) スタッキング/結合/反復の基本
A = np.array([[1,2],[3,4]])
B = np.array([[5,6],[7,8]])
print(np.concatenate(\[A,B], axis=0)) # 上下(4×2)
print(np.concatenate(\[A,B], axis=1)) # 左右(2×4])
print(np.stack(\[A,B], axis=0).shape) # (2,2,2) 新しい軸
print(np.tile(\[1,2,3], reps=2)) # \[1,2,3,1,2,3]
print(np.repeat(\[1,2,3], repeats=\[1,2,3])) # \[1,2,2,3,3,3]
9) NumPy×pandas×Scikit-learnの接続
pandas→NumPyは df.to_numpy()
を推奨。MLでは X.shape=(n_samples, n_features)
、y.shape=(n_samples,)
が基本形です。
→ [内部リンク:pandas基礎]/[内部リンク:scikit-learn基礎]
10) ベンチ“型”:forをやめる(timeit)
import numpy as np, timeit
x = np.arange(10_000, dtype=np.float64)
# ループ
loop = timeit.timeit('s=0\nfor v in x: s+=v\*v+1', number=100, globals=globals())
# ベクトル化
vec = timeit.timeit('y = x\*x + 1', number=100, globals=globals())
print(loop/vec) # 通常 10x〜100x 以上速い
業務テンプレ3本(コピペ)
A) 標準化(平均0・分散1)
X = np.array([[1,2,3],[4,5,6]], dtype=float)
mu = X.mean(axis=0, keepdims=True)
sd = X.std(axis=0, keepdims=True)
Z = (X - mu) / sd
B) 距離行列(各行のユークリッド距離)
P = np.array([[0,0],[1,1],[2,0]], dtype=float)
# 3×2 と 1×2 のブロードキャストで差分→2乗→和→平方根
D = np.sqrt(((P[:,None,:] - P[None,:,:])**2).sum(axis=2)) # 3×3
C) one-hot(カテゴリ→ベクトル)
labels = np.array([0,2,1])
K = labels.max()+1
onehot = (np.arange(K) == labels[:,None]).astype(np.int8) # 3×K
用途別の最初の一手(現場チートシート)
- 可視化前処理:
clip/where
で外れ値を抑えてから → [内部リンク:可視化入門] - 学習データ作成:
stack/concatenate
で特徴を結合 → [内部リンク:scikit-leーン基礎] - 大量CSV整形:ブロードキャストで条件置換 → [内部リンク:pandas実践]
今日やること(45分)
- 既存の
for
を配列式(ベクトル化)1箇所に置換。 - 自分のデータでブロードキャスト5パターンのいずれかを再現。
axis
を変えた集計(sum/mean
)をkeepdims=True
あり/なしで比較。ravel/flatten/copy
の挙動の違いを確認。
よくある罠と対処(早見表)
症状 | 原因 | 対処 |
---|---|---|
結果が変わる | ビューを意図せず書き換え | copy() で境界/ arr.base で共有確認 |
ブロードキャストエラー | 形の不一致 | .shape をprint、None/np.newaxis で次元追加 |
NaNが伝播 | 演算にNaN混入 | np.nan* 系(nanmean など)を使う |
遅い | Pythonループ/ファンシー乱用 | ベクトル化/スライス(ビュー)を優先 |
dtypeが勝手に変わる | 混合型/整数÷整数 | 初期からdtype=float 、// と/ の違いに注意 |
まとめ:NumPyは“式で考える”
forが遅い、axisが曖昧、ビューで壊す——この3つの壁はベクトル化・ブロードキャスト・axis思考で一気に越えられます。テンプレと罠回避表を手元に、今日から配列思考へ置き換えていきましょう。
この記事から次に読むべきもの(内部リンク)
最近のコメント