17
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【numpy】0除算のwarningを回避する

Last updated at Posted at 2020-02-03

自分でプログラムを書いているときに迷った部分なので、メモとして書き残しておきます。
もっと良い対処法をご存知でしたら、ご指摘をお願いします。

※環境

  • Python 3.7.5
  • numpy 1.18.1

##numpyの0除算

Pythonで通常の0除算を行うとZeroDivisionErrorが発生します。

try:
    divided = 1 / 0
except ZeroDivisionError as e:
    print(f'ZeroDivisionError: {e}')

# -> ZeroDivisionError: division by zero

一方、numpyで0除算を行うとRuntimeWarningが発生しますが、処理が止まることなく結果が出力されます。

import numpy as np

divided = 1 / np.array(0)
print(devided)
# -> inf
# -> RuntimeWarning: divide by zero encountered in true_divide

1 / np.array(0)の計算結果は、numpyで無限大を表すオブジェクトnumpy.infになります。
警告は出してくれるけど処理が止まるわけではないので、中途半端な感じです。おそらく、「一応計算できるけど、警告を出しておくよ。どうするかは任せるよ」と使用者に委ねられているのではないでしょうか。
そこで、処理を止めたい場合と、処理を許容して警告を無視する場合にどうするか考えてみます。

##warnings.simplefilterを使う

warnings.simplefilterを使えば、RuntimeWarningを例外扱いにしたり、無視扱いにすることができます。

  • 例外扱いにして処理を止めたいとき
import warnings
import numpy as np
warnings.simplefilter('error', category=RuntimeWarning)  # RuntimeWarningを例外扱いに設定

a = np.array(0)  # 0 が入ることを想定していない変数
divided = 1 / a
print(divided)

# -> RuntimeWarning: divide by zero encountered in true_divide
# divided = 1 / aで処理が止まり、print(divided)が実行されない
  • 計算を許容して警告を無視したいとき
import warnings
import numpy as np
warnings.simplefilter('ignore', category=RuntimeWarning)  # RuntimeWarningを無視扱いに設定

a = np.array(0)  # 0 が入ってもよい変数
divided = 1 / a
print(divided)

# -> inf
# 警告が出ず、処理が続行される

しかし、このとき0除算以外のRuntimeWarningにも制御が適用されるため、特に無視する場合にはバグを補足できなくなる危険性があります。そこで、別の方法を考えます

##numpy.seteerを使う
(コメントによるご指摘を受け、numpy.seteerを追記しました。)
numpy.seteerを使えば、より細かいWarningの制御を設定できます。

※以下、例外扱いにするときも無視扱いにするときもほぼ同様のため、無視扱いするときのソースコードのみを記載します。

import numpy as np
np.seterr(divide='ignore')  # 0除算のRuntimeWarningのみを無視扱いとする

a = np.array(0)
divided = 1 / a
print(divided)

# -> inf

これで、0除算のみに制御を加えることができました。warningsimportする煩雑さも解消されています。
しかし、この場合ソースコード全体にこの制御が適用されます。できれば、制御のスコープを最小限にしたいです。
除算の前後でnumpy.seterrを書いて制御を切り替えてもいいのですが、もっとスマートな記述を考えます。

##numpy.errstateを使う
(こちらも、コメントによるご指摘から追記した部分です)

numpy.errstateを使うことで、コンテキストマネージャーによる制御が可能になります。

import numpy as np

a = np.array(0) 
with np.errstate(divide='ignore'):  # withスコープ内のみ制御を適用する
    divided = 1 / a
print(divided)
# -> inf

divided = 1 / a  # 無視扱いのスコープ外なので、RuntimeWarningが発生する
# -> RuntimeWarning: divide by zero encountered in true_divide

こうすることで制御変更のスコープを小さくし、他の箇所の(許可していない)0除算によるバグを補足できます。

例では分かりやすさのため数値の除算を考えましたが、配列の除算も同様に制御できます。

import numpy as np

a = np.array([1, 2, 0]) 
with np.errstate(divide='ignore'):  # withスコープ内のみ制御を適用する
    divided = 1 / a
print(divided)
# -> [1.  0.5 inf]

1つの数値を除算する場合単純にif文を書けばいいのですが、配列の場合それが難しいので、この制御方法が活躍すると思います。
※配列の要素一つ一つをif文で判定するのはナンセンスだと思っています。可能な限り避けたいです。

##おまけ
当初、numpy.errstateを知らず、次のような書き方を考えました。

import numpy as np

a = np.array([1, 2, 0])
divided = np.full(len(a), np.inf)  # 全ての要素がnp.infの配列を作っておく
nonzero_indices = np.where(a != 0)[0]  # aの0以外の要素を持つindexの配列
divided[nonzero_indices] = 1 / a[nonzero_indices]  # nonzero_arrayの要素だけ除算に書き換える

print(divided)
# -> [1.  0.5 inf]

0除算のみの警告を回避したかったのですが、冗長感ありますね。
numpy.errstateのような便利メソッドを覚えて使いこなせるようになりたいです。

17
15
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?