要素削除の問題点
NumPyで欠損値や外れ値を除く時どうしてますか?多くの人が単純にその要素を配列から削除していると思います。例えばnan
を除くなら以下のような感じ。
a = np.array([3, 4, np.nan, 8, np.nan])
a = a[~np.isnan(a)]
print(a) # [3. 4. 8.]
print(a.mean()) # 5.0
この方法でもいいのですが、よくある問題として要素を削除するとそれが元のデータの何番目だったのか分からなくなるということがあります。例えば値を除いて分析した後にこの最大値を取るものを個別に見てみたいとか思っても、消している要素があるのでそれが元のデータ上では何番目になるのかわかりません。
Masked Array
そんな時に便利なのがmasked arrayです。masked arrayはNumPyに元から入っている機能です。masked arrayは普通のndarrayとマスクを一つにした型で、個々の要素についてマスクするかしないかを決められます。そしてマスクした要素はndarrayに存在はしますが、あらゆる計算から除外されます。
例えば上の例をmasked arrayを使って書き直したのが以下です。
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
np.ma.masked_where
関数でmasked arrayを作っています。第一引数がマスクのbool型ndarray、第二引数がマスクされる元のndarrayです。上記のようにmasked arrayではマスクされた要素は--
で表されており、a.mean()
では計算から外されています.
Masked arrayでは要素を消している訳ではないので加工前と後の対応関係がわからなくなるということがありません。
もっと例を見る
行列で-1
を含む行全体をマスクしたい時。
a = np.array([
[0, 1, -1],
[3, 3, 9],
[1, 4, 6],
[-1, 5, -1],
])
a = np.ma.mask_rows(np.ma.masked_where(a == -1, a))
print(a)
# [[-- -- --]
# [3 3 9]
# [1 4 6]
# [-- -- --]]
print(a.sum(axis=1)) # [-- 15 11 --]
Masked arrayのスカラー倍は同じ場所がマスクされたmasked arrayを返す。行列積はエラーになる模様(正確なマスク伝搬の仕組みは理解してない...)。
a = np.ma.masked_where([False, True, False, True], [0, 1, 2, 3])
print(2 * a) # [0 -- 4 --]
print(a @ np.array([[1], [1], [1], [1]])) # ValueError: could not broadcast where mask from shape (4,4) into shape (1,)
nan
とinf
を両方マスクしたいとき、np.ma.masked_invalid
関数がショートカットとして使える。
a = np.array([ 0, 1, np.nan, np.inf, -np.inf])
print(np.ma.masked_invalid(a)) # [0.0 1.0 -- -- --]
あとがき
masked arrayはNumPy標準の機能なのに知名度が低いと思います。まあ私も何番目か分からなくなるのを防ぐ以外のメリットが思いつかないので、利用価値が低いのでしょう。でもこの機能があること知っていればそのような特定の場面でとても便利に使えると思います。