Yuta NakataのBlog

Python / AWS / ITについて役立つ情報を発信します

【numpyのバグ?】numpy.ma.core.MaskedArrayで意図せずMaskがはずされる場合があるって話

はじめに・前提

numpyにかかるバグ?っぽい挙動を発見したので、その共有記事です。

本内容については、Github issuesとして共有済みです(2024.11.9現在返信待ち)。

github.com

前提となる実行環境は以下のとおりです。

結論

下記のようなプログラムを書くと、numpyでマスクした値が外れるようです。

import numpy as np


a3 = np.array([[-9999, 8, -9999], [1, 100, 2], [-9999, -9999, 2]]) # a3.shapeは、3.3
a3 = np.ma.masked_where(a3 == -9999, a3) # -9999をマスクする
b3 = np.array([[-9999, 8, -9999], [1, 100, 2], [-9999, -9999, 2]]) # b3.shapeは、3.3
b3 = np.ma.masked_where(b3 == -9999, b3) # -9999をマスクする

print(a3) # [[-- 8 --] [1 100 2] [-- -- 2]]
print(b3) # [[-- 8 --] [1 100 2] [-- -- 2]]

x3 = np.zeros(((2, 3, 3))) # a3やb3を含むためのnumpy.ndarrayを定義
x3[0, :, :] = a3
x3[1, :, :] = b3
print(x3)
"""
マスクしたはずの値に対して、マスクが外れる!
[[[-9.999e+03  8.000e+00 -9.999e+03]
  [ 1.000e+00  1.000e+02  2.000e+00]
  [-9.999e+03 -9.999e+03  2.000e+00]]

 [[-9.999e+03  8.000e+00 -9.999e+03]
  [ 1.000e+00  1.000e+02  2.000e+00]
  [-9.999e+03 -9.999e+03  2.000e+00]]]
"""

低次元配列を複数定義し、その低次元配列を高次元配列で覆うように書き換えると、マスクが外れるようです。

ただし、

a = np.array([3, 4, -9999, 8, -9999])
a = np.ma.masked_where(a == -9999, a)

c = np.zeros(len(a))
c = a # [3 4 -- 8 --] マスクが維持される!
print(c)

のように同次元系の配列の場合、マスクは外れないようです。。。

マスクされていることが前提の統計処理等はよく気をつけたほうがいいですね。

numpy.ma.core.MaskedArrayとは

値をマスクするときに使われるnumpyのマスクメソッドです。

欠損・異常値処理において役に立つモジュールです。

a = np.array([3, 4, np.nan, 8, np.nan])
print(a.mean()) # nan
a = np.ma.masked_where(np.isnan(a), a)
print(a) # [3.0 4.0 -- 8.0 --]
print(a.mean()) # 5.0

qiita.com

numpy.ndarrayとは

numpyで多次元配列を定義するために、使われます。

numpyユーザーの中でも最もよく使われるもののひとつではないかと思います。

a3 = np.array([[-9999, 8, -9999], [1, 100, 2], [-9999, -9999, 2]])

高次元の配列も簡単に定義することが可能です。

追記 2024.11.23

上記バグについて、numpy側からフォローがあったので共有します。

結論、

x3 = np.zeros(((2, 3, 3)))

ではなく、

x3 = np.zeros(((2, 3, 3)))
x3 = np.ma.asarray(x3)

とするとよいとのことでした。

私の環境下でも改善が確認できたので、共有します。