【Python】NaNを除いた平均に有効データ数を添える

PythonのTopに戻る

欠測を除いた平均だけを出すと、有効値が1件の列と多数ある列が同列に扱われてしまう。平均と有効件数を一緒に計算し、全欠測列にはNaNを残すのが分かりやすい。また、nanmeanが除くのはNaNであり、無限大まで自動的に無視するわけではない。

最小例で確かめる

以下は説明用に作った小さなデータである。実測データや実行速度の測定結果ではない。コード全体をexample.pyとして保存すれば、入力ファイルを別途用意せずに実行できる。assertは、この例で成り立つべき形や値を確認するために入れてある。

import warnings
import numpy as np
x = np.array([[1., np.nan, np.inf],
              [3., np.nan, 4.],
              [np.nan, np.nan, 6.]])
with warnings.catch_warnings(record=True) as captured:
    warnings.simplefilter("always", RuntimeWarning)
    raw_mean = np.nanmean(x, axis=0)
assert any(issubclass(w.category, RuntimeWarning) for w in captured)
valid = np.isfinite(x)
count = valid.sum(axis=0)
total = np.where(valid, x, 0.).sum(axis=0)
mean = np.full(x.shape[1], np.nan, dtype=np.float64)
np.divide(total, count, out=mean, where=count > 0)
print("nanmean:", raw_mean.tolist())
print("finite count:", count.tolist())
print("finite mean:", mean.tolist())
print("all missing:", (count == 0).tolist())
assert np.isinf(raw_mean[2]) and np.isnan(raw_mean[1])
np.testing.assert_array_equal(count, [2, 0, 2])
np.testing.assert_allclose(mean, [2., np.nan, 5.], equal_nan=True)

実行結果

nanmean: [2.0, nan, inf]
finite count: [2, 0, 2]
finite mean: [2.0, nan, 5.0]
all missing: [False, True, False]

何を有効値とするか先に決める

この例の3列目にはinfが入っている。nanmeanではNaNだけを除くので、3列目の結果はinfになる。もし測定値として無限大を受け入れないなら、np.isfiniteで有限値だけを有効とする方針を明示する必要がある。infが飽和や計算失敗を表すなら、その発生件数を別途残して原因を確認することも大切である。

一方、無限大に意味がある数理データを扱う場合、有限値だけの平均へ置き換えると問題の定義を変えてしまう。欠測、無限大、異常値は同じものではない。ここでは「有限値だけを平均し、除外した場所は欠測扱いにする」という例としている。利用分野に合わせ、採用条件を文章や設定に残しておこう。

全欠測列の分母を確認する

2列目には有効値が一つもないので、countは0になる。平均0と報告すると、ゼロという観測値があったように読めてしまう。このため出力配列をNaNで初期化し、countが正の列だけ除算する。結果を見れば、平均が計算できなかった列と、本当に平均がゼロの列を区別できる。

nanmeanは全NaNのスライスに対してNaNを返し、RuntimeWarningを出す。例ではこの挙動を確認するために警告を局所的に捕捉している。プログラム全体の警告を無効化しているわけではない。実務では警告が多いから消すより、どの列が全欠測なのかを件数として確認する方が次の対応につながる。

合計と件数から平均を組み立てる

valid.sum(axis=0)は列ごとのTrueの数である。np.where(valid, x, 0.)で無効値を合計に寄与しない0へ置き換え、同じ軸で和を取る。その後に合計を件数で割るので、有効値の選別と分母の定義が一致する。NaNを含む配列へ単純にマスクを掛け算するとNaN×0が残るため、この形にはならない。

1列目の有効値は1と3なので平均2、3列目は4と6なので平均5になる。件数はどちらも2である。コードでは平均値だけでなくcount、全欠測列、nanmeanで残るinfを検査した。結果表には平均と件数を隣接させると、データ量の違いを読み落としにくい。

平均が出た後にも必要な点検

有限値なら何でも妥当とは限らない。単位の混在やセンサーの測定範囲外の値は別の条件で検査する。有効件数が少ない列を採用するか、最低件数を設けるかも解析の方針である。平均と件数だけで不確かさを完全に表せるわけではないので、必要に応じてばらつきや欠測の偏りも調べたい。

大きな値の合計は浮動小数点でも丸めやオーバーフローの影響を受ける。float32入力なら集約時にfloat64を指定するなど、値の尺度を考えた計算型を選ぶ。さらにチャンクへ分けて読む場合には、部分平均の単純平均ではなく、ここで作った合計と件数を足し合わせれば同じ定義の全体平均につながる。

動作確認環境と参考資料

Linux・CPython 3.12.14、NumPy 2.3.5、pandas 2.2.3、SciPy 1.17.0、Matplotlib 3.10.8の環境で掲載コードを実行した。使用するライブラリはコード冒頭のimportを参照してほしい。公式資料の最新版と、この実行確認版は区別している。数値の末尾や表の表示幅は環境によって変わることがある。

関連するTips

PythonのTopに戻る