ゼロ除算する要素だけを避けるなら、np.divideにwhereと初期化済みのoutを渡す。計算しない位置に何を残すかも同時に指定できる。np.whereで分母ゼロの結果を置き換えるだけでは、選別前に除算そのものが評価されるため、警告や不正な中間値を防げない。
最小例で確かめる
以下は説明用に作った小さなデータである。実測データや実行速度の測定結果ではない。コード全体をexample.pyとして保存すれば、入力ファイルを別途用意せずに実行できる。assertは、この例で成り立つべき形や値を確認するために入れてある。
import numpy as np
numerator = np.array([10., 20., np.nan, 5., 8.])
denominator = np.array([2., 0., 4., np.inf, -2.])
valid = (np.isfinite(numerator) & np.isfinite(denominator)
& (denominator != 0))
ratio = np.full(numerator.shape, np.nan, dtype=np.float64)
with np.errstate(divide="raise", invalid="raise"):
np.divide(numerator, denominator, out=ratio, where=valid)
print("valid:", valid.tolist())
print("ratio:", ratio.tolist())
try:
with np.errstate(divide="raise", invalid="raise"):
np.where(denominator != 0, numerator / denominator, np.nan)
except FloatingPointError:
print("expected: FloatingPointError in eager expression")
else:
raise AssertionError("division was unexpectedly skipped")
np.testing.assert_allclose(ratio, [5., np.nan, np.nan, np.nan, -4.],
equal_nan=True)
assert np.isnan(ratio[~valid]).all()
assert ratio.shape == numerator.shape and ratio.dtype == np.float64
実行結果
valid: [True, False, False, False, True]
ratio: [5.0, nan, nan, nan, -4.0]
expected: FloatingPointError in eager expression
whereは計算する場所を指定する
np.divideのwhereには、除算を実行する場所をTrueにしたマスクを渡す。この例では分子と分母が有限で、分母が0でない場所だけを選んでいる。Trueの場所には除算結果が書き込まれ、Falseの場所はoutの元の値が残る。したがって計算前にoutをどう初期化するかが重要になる。
np.fullでNaNを入れておけば、無効な比率を明示的に欠測として残せる。outを省略しながらwhereだけを指定すると、計算されなかった場所には未初期化の値が残り得る。小さな数や0が偶然表示されても、意味のある結果ではない。必ず初期値を持つ出力配列を用意しよう。
np.whereでは除算を止められない
np.where(condition, a / b, np.nan)は、関数へ渡す引数を作る段階でa / bを評価する。conditionがFalseの場所も先に除算されるので、ゼロ除算の警告が発生する。この例ではnp.errstateで該当する浮動小数点エラーを例外へ変え、意図的な失敗として確かめた。
一方、np.divideのwhereはufuncの計算位置を制御するため、同じ例外設定の下でも必要な場所だけ計算できる。警告を無視して最後にinfを消す方法と比べ、入力条件と出力の扱いがコードに直接現れる。なおerrstateはこのwithの範囲だけに適用され、全体の警告設定を変えない。
ゼロ以外にも無効条件を決める
例では無限大の分母も無効としている。数学的に有限値をinfで割れば0になる場合があるが、測定失敗の印としてinfが混ざったなら0という有効値を作るべきではない。ここでは有限な入力だけを採用する契約にした。NaNを欠測として残す理由と、どの入力を除外したかを別々に記録すると解釈しやすい。
非常に小さな分母を許すかどうかは別の問題である。厳密なゼロだけを避けても比率が極端に大きくなり、場合によってはオーバーフローする。測定限界などから最小許容値を決める場合は、単位に沿った閾値を使う。都合よく分母に小さな定数を足すと、比率の定義そのものが変わってしまう。
dtypeとshapeを明示しておく
整数型の出力配列は小数やNaNを保存できないので、ここではfloat64を指定した。zeros_likeを入力の整数配列へそのまま使うと、出力まで整数型になることがある。配列の形だけを借りたい場合も、計算結果に必要なdtypeを合わせて指定する。
複数のshapeを組み合わせる除算ではbroadcastingも関係する。outは最終的な結果shapeに合うものを用意し、分子と分母の対応する軸を確認する。例では同じshapeに限定し、計算した場所の値と計算しなかった場所のNaNを両方検査している。このように有効件数も数えておけば、比率が減った理由を後から追いやすい。
動作確認環境と参考資料
Linux・CPython 3.12.14、NumPy 2.3.5、pandas 2.2.3、SciPy 1.17.0、Matplotlib 3.10.8の環境で掲載コードを実行した。使用するライブラリはコード冒頭のimportを参照してほしい。公式資料の最新版と、この実行確認版は区別している。数値の末尾や表の表示幅は環境によって変わることがある。
