概要(NumPyの条件抽出は「ブール配列で一発フィルタ」する最短ルート)
NumPyの条件抽出(Boolean indexing)は、配列に対する比較結果(True/Falseの配列)をそのままインデックスとして使い、条件に合う要素だけを高速に取り出す仕組みです。重要なのは「条件式が配列全体に同時適用される」「複数条件は&(かつ)・|(または)・~(否定)で組み合わせる」「括弧が必須」「np.where・nonzeroでインデックスを取得できる」「NaNは特別扱い(np.isnan)」の5点です。まずは一次元で感覚を掴み、二次元・三次元で形(shape)を意識しながら使うと、ループ無しで自然に書けるようになります。
基本の使い方(ここが重要)
単一条件で抽出(ブール配列をそのままインデックスに使う)
import numpy as np
a = np.array([10, 20, 15, 8, 30])
mask = a > 15
print(mask) # [False True False False True]
print(a[mask]) # [20 30]
Python条件式は配列の各要素に一度に評価され、同じ長さのTrue/False配列が返ります。これをインデックスに入れると、Trueの要素だけが取り出されます。
二次元配列の抽出(同形状マスクで値を取り出す)
M = np.array([[1, 2, 3],
[4, 5, 6]])
mask = M % 2 == 0 # 偶数をTrueに
print(M[mask]) # [2 4 6]
Python同形状のマスクを使うと、Trueの場所の値が一次元に平坦化されて返ります。形を保ったまま残したい場合は、後述の手法を使います。
複数条件の組み合わせ(& | ~ と括弧が最重要)
かつ・または・否定を組み合わせて柔軟に絞る
import numpy as np
a = np.array([10, 20, 15, 8, 30])
# 15より大きく、かつ偶数
mask = (a > 15) & (a % 2 == 0)
print(a[mask]) # [20]
# 10未満、または30以上
mask = (a < 10) | (a >= 30)
print(a[mask]) # [8 30]
# 否定(15超ではない)
mask = ~(a > 15)
print(a[mask]) # [10 15 8]
Python&や|を使うときは、各条件を必ず括弧で囲みます。括弧なしだと演算子の優先順位でエラーや意図しない結果になります。
インデックスの取得と置換(np.where・nonzero・argwhere)
Trueの位置(インデックス)を取りたいとき
import numpy as np
a = np.array([10, 20, 15, 8, 30])
idx = np.where(a > 15) # (行)インデックスのタプル
print(idx) # (array([1, 4]),)
print(a[idx]) # [20 30]
Python一次元ではnp.whereは「該当位置の配列」を返します。二次元以上では軸ごとのインデックスをタプルで返すため、そのまま使えば位置抽出も可能です。
二次元で座標の一覧が欲しいとき(行・列をペアで)
import numpy as np
M = np.array([[1, 2, 3],
[4, 5, 6]])
rows, cols = np.where(M > 3) # 各Trueの行・列
coords = list(zip(rows, cols))
print(coords) # [(1, 0), (1, 1), (1, 2)]
Python行配列・列配列が得られるので、座標ペアにして回す処理にも使えます。全座標を一つの配列で欲しいならnp.argwhereが便利です。
条件に応じた値の置換(if-elseのベクトル化)
import numpy as np
a = np.array([10, 20, 15, 8, 30])
b = np.where(a >= 20, a, -1) # 20以上なら元値、それ以外は-1
print(b) # [10 -1 15 -1 30] ← 10は20未満では?
Python上の例は「20以上を残し、それ以外を-1」とする想定なら、実装は次のようにします。
b = np.where(a >= 20, a, -1) # [ -1 20 -1 -1 30 ]
Python条件・真値・偽値の対応関係を明確に書くと、配列全体に一度で適用されます。
NaN・特別値の扱い(比較では拾えないものを正しく処理)
NaN検出と除外・置換
import numpy as np
x = np.array([1.0, np.nan, 3.0, np.nan])
mask = ~np.isnan(x) # NaN以外
print(x[mask]) # [1. 3.]
filled = np.where(np.isnan(x), 0.0, x) # NaNを0に置換
print(filled) # [1. 0. 3. 0.]
PythonNaNは通常の比較では拾えないため、np.isnanを使うのが安全です。
値の集合で抽出(isinで「含まれるか」を判定)
import numpy as np
names = np.array(["a", "b", "c", "d"])
mask = np.isin(names, ["b", "d"])
print(names[mask]) # ['b' 'd']
Python複数候補との一致はnp.isinが最短です。
二次元・多次元の条件抽出(軸と形を意識して使う)
列条件・行条件での抽出
import numpy as np
M = np.array([[10, 20, 30],
[ 5, 25, 35],
[15, 8, 40]])
# 1列目が10以上の行を丸ごと抽出
row_mask = M[:, 0] >= 10
print(M[row_mask]) # [[10 20 30] [15 8 40]]
# 2列目が20以上の列を丸ごと抽出
col_mask = M[:, 1] >= 20
print(M[:, col_mask]) # [[20 30] [25 35] [ 8 40]]
Python行と列でマスクの掛け方が違うため、M[行マスク]またはM[:, 列マスク]の形を意識します。
形を保ったまま「該当だけ残し、他は埋める」
import numpy as np
M = np.array([[1, 2, 3],
[4, 5, 6]])
mask = M % 2 == 0
out = np.where(mask, M, 0) # 偶数は値、奇数は0
print(out) # [[0 2 0] [4 0 6]]
Python同形状で戻したい場合はnp.whereで置換するのが簡単です。
安全な代入と更新(マスクで一括更新、ループ不要)
条件を満たす要素へまとめて代入
import numpy as np
a = np.array([10, 20, 15, 8, 30])
a[a < 15] = 0
print(a) # [10 20 15 0 30]
Python条件式を左辺のインデックスに使えば、対象要素を一括更新できます。二次元でも同様に機能します。
二次元で部分的な更新
import numpy as np
M = np.array([[1, 2, 3],
[4, 5, 6]])
M[M % 2 == 1] = -M[M % 2 == 1] # 奇数だけ符号反転
print(M) # [[-1 2 -3] [ 4 -5 6]]
Python右辺にも同じマスクを使うと、対象の値を取り出して加工し、戻す流れが自然に書けます。
よくある落とし穴(括弧忘れ・形不一致・比較の勘違い)
括弧を忘れるとエラー・意図せぬ結果
import numpy as np
a = np.array([10, 20, 30])
# 悪い例:a > 10 & a < 30 ← 演算子優先順位のせいでエラー・誤動作
mask = (a > 10) & (a < 30) # 良い例
print(a[mask]) # [20]
Python& と | は「ビット演算子」で、Pythonのand/orとは仕様が違います。必ず各条件を括弧で囲みます。
形が合わないマスクは使えない
import numpy as np
M = np.arange(6).reshape(2, 3) # (2,3)
mask = np.array([True, False]) # (2,)
# M[mask] は行選択なのでOK
print(M[mask])
col_mask = np.array([True, False]) # (2,) ← 列数と不一致
# M[:, col_mask] はエラーになる(列は3本なのに2長のマスク)
Python行を選ぶマスクは行数と、列を選ぶマスクは列数と一致している必要があります。
NaNは比較では拾えない
import numpy as np
x = np.array([np.nan, 1.0])
print(x == np.nan) # [False False]
print(np.isnan(x)) # [ True False]
PythonNaNの検出はnp.isnanを使います。比較では見つかりません。
例題で身につける(定番から一歩先まで)
例題1:閾値と奇数・偶数で抽出
import numpy as np
a = np.array([3, 10, 21, 14, 7, 30])
print(a[(a >= 10) & (a % 2 == 0)]) # 10以上かつ偶数 → [10 14 30]
Python例題2:二次元で行・列条件を組み合わせる
import numpy as np
M = np.array([[10, 20, 30],
[ 5, 25, 35],
[15, 8, 40]])
row_mask = M[:, 0] >= 10 # 1列目が10以上の行
col_mask = M[0, :] >= 25 # 1行目が25以上の列
print(M[row_mask][:, col_mask]) # 行→列の順で絞る → [[30] [40]]
Python例題3:np.whereで条件置換とインデックス取得
import numpy as np
a = np.array([10, 20, 15, 8, 30])
print(np.where(a >= 20, a, -1)) # 条件置換 → [-1 20 -1 -1 30]
print(np.where(a < 10)) # インデックス取得 → (array([3]),)
Python例題4:NaN除外して統計、欠損は0で補完
import numpy as np
x = np.array([1.2, np.nan, 0.8, np.nan, 1.5])
clean = x[~np.isnan(x)]
print(clean.mean()) # 欠損除外の平均
filled = np.where(np.isnan(x), 0.0, x) # 欠損を0に
print(filled)
Pythonまとめ
NumPyの条件抽出は「比較で作るブール配列をインデックスに使う」だけで、配列全体を一発でフィルタできます。複数条件は&・|・~で組み合わせ、必ず括弧を付ける。インデックスが必要ならnp.where・nonzero・argwhere、値の置換はnp.whereが最短。NaN検出はnp.isnan、集合一致はnp.isin。二次元・多次元では「どの軸に対するマスクか」をshapeで確認し、行・列の取り扱いを意識する。更新はマスク代入で安全に一括。これらを型にすれば、初心者でもループなしで短く、速く、壊れない抽出・加工が自然に書けるようになります。
