条件に合う「行」を残したいときは、行数と同じ長さの1次元マスクを作る。2次元配列と同じshapeのマスクをそのまま使うと、条件に合う要素が集まり、結果は1次元になる。表の行を選ぶ処理と、表の各セルを選ぶ処理を区別すると直しやすい。
最小例で確かめる
以下は説明用に作った小さなデータである。実測データや実行速度の測定結果ではない。コード全体をexample.pyとして保存すれば、入力ファイルを別途用意せずに実行できる。assertは、この例で成り立つべき形や値を確認するために入れてある。
import numpy as np
x = np.array([[1., 10.], [2., np.nan], [3., 30.]])
element_mask = x > 2
flat = x[element_mask]
row_mask = np.isfinite(x).all(axis=1) & (x[:, 0] >= 2)
rows = x[row_mask, :]
masked = np.where(element_mask, x, np.nan)
print("element selection:", flat.tolist(), flat.shape)
print("row mask:", row_mask.tolist())
print("row selection:", rows.tolist(), rows.shape)
print("same shape:", masked.tolist(), masked.shape)
empty = x[x[:, 0] > 100, :]
print("no matches:", empty.shape)
assert flat.shape == (3,)
np.testing.assert_array_equal(flat, [10, 3, 30])
np.testing.assert_array_equal(rows, [[3, 30]])
assert rows.shape == (1, 2) and empty.shape == (0, 2)
assert masked.shape == x.shape
assert np.isnan(masked[0, 0]) and np.isnan(masked[1]).all()
実行結果
element selection: [10.0, 3.0, 30.0] (3,)
row mask: [False, False, True]
row selection: [[3.0, 30.0]] (1, 2)
same shape: [[nan, 10.0], [nan, nan], [3.0, 30.0]] (3, 2)
no matches: (0, 2)
マスクのshapeを見る
element_maskはxと同じ(3, 2)で、各セルについてTrueまたはFalseを持つ。x[element_mask]はTrueの位置の値を順に取り出すため、10、3、30という1本の配列になる。選ばれたセル数は行ごとに違うので、そのまま長方形の2次元表にはできない。これは情報を並べ替える仕様であり、不具合ではない。
row_maskは(3,)で、各測定行を残すかどうかだけを表す。x[row_mask, :]なら行を条件抽出し、すべての列を保持する。たとえ一致した行が1行でも結果は(1, 2)、0行でも(0, 2)になる。後段が2列の表を受け取ることを期待している場合、このshapeの安定性が役に立つ。
複数列の条件を行へまとめる
np.isfinite(x)は有限値のセルをTrueにする。all(axis=1)で「その行の全列が有限」を一つの真偽値へまとめ、さらに第1列が2以上という条件と組み合わせている。例の2行目は第1列が2でも、もう一方の列がNaNなので採用されない。欠測行を除くか、一部の列だけを必須にするかは先に決めておこう。
どれか一つの列が条件を満たせばよいならany(axis=1)を使う。allとanyを取り違えると、コードは動いても採用する行が大きく変わる。条件式を組み合わせるときはandやorではなく、配列用の&や|を使い、比較式を括弧で囲む。演算子の優先順位にも注意したい。
元の格子を保つなら値を置き換える
行も列も落とさず、条件に合わないセルを欠測として表示したいならnp.whereが使える。この例ではTrueのセルに元の値、FalseのセルにNaNを置くので、shapeは(3, 2)のままである。画像や時刻×センサーの格子を維持して後で描画するときに都合がよい。
NaNを入れる場合は浮動小数点型など対応するdtypeが必要になる。整数の入力へその場でNaNを代入する方法とは異なり、np.whereは結果の型も決め直す。セルを除外したのか、行を除外したのか、場所を残して欠測にしたのかを、出力を受け取る処理にも伝える必要がある。
抽出前の対応を失わないために
boolean indexingで読み出した配列は通常copyなので、rowsを書き換えても元のxが補正されるとは限らない。元配列を更新したいならx[row_mask, :]を代入の左辺に使い、原本を残したいなら別の結果として扱う。抽出と更新は目的を分けて書くと安全である。
条件抽出後には行番号が詰め直されるため、元の試料IDや時刻との対応も同じマスクで選ぶ。測定値だけを抽出してIDを元のまま使うと、形は合っても別の試料に値を割り当ててしまう。小さな既知のデータで、採用行、欠測の扱い、0件時のshapeまで確かめてから本番の配列へ適用しよう。
動作確認環境と参考資料
Linux・CPython 3.12.14、NumPy 2.3.5、pandas 2.2.3、SciPy 1.17.0、Matplotlib 3.10.8の環境で掲載コードを実行した。使用するライブラリはコード冒頭のimportを参照してほしい。公式資料の最新版と、この実行確認版は区別している。数値の末尾や表の表示幅は環境によって変わることがある。
