[Python / Pandas] DataFrameに対して`replace`で`None`に置き換えようとするとバグが発生する


何が起きたか

pandasDataFrameにあるreplaceメソッドを使い、np.nanNoneに置換しようとしたらバグが発生した(ように見えた)

Environment

Google Colaboratory で実施

ソースコード

1. 置換前のDataFrame作成

動作確認用のDataFrameがこちら

import pandas as pd
import numpy as np

indexes = [
    datetime.datetime(2020, 1, 1, 11, 50),
    datetime.datetime(2020, 1, 1, 12, 50),
    datetime.datetime(2020, 1, 1, 12, 52),
    datetime.datetime(2020, 1, 1, 18, 50),
    datetime.datetime(2020, 1, 1, 19, 50),
    datetime.datetime(2020, 1, 1, 21, 50),
]
df = pd.DataFrame({
    'high': [1, np.nan, 3, np.nan, np.nan, 11],
    'close': [4, 5, 6, 7, np.nan, 2],
    'memo': ['sign', '', np.nan, 'sign2', np.nan, 'sign3'],
    'bool': [True, None, True, False, None, False],
    'stoploss': [True, None, True, False, None, False]
}, index=indexes)

df
->                    high   close   memo   bool    stoploss
2020-01-01 11:50:00   1.0    4.0     sign   True    True
2020-01-01 12:50:00   NaN    5.0            None    None
2020-01-01 12:52:00   3.0    6.0     NaN    True    True
2020-01-01 18:50:00   NaN    7.0     sign2  False   False
2020-01-01 19:50:00   NaN    NaN     NaN    None    None
2020-01-01 21:50:00   11.0   2.0     sign3  False   False

2. replace方法その1

バグが起こる方

df.replace(np.nan, None)
->                   high   close   memo    bool    stoploss
2020-01-01 11:50:00  1.0    4.0     sign    True    True
2020-01-01 12:50:00  1.0    5.0             True    True
2020-01-01 12:52:00  3.0    6.0             True    True
2020-01-01 18:50:00  3.0    7.0     sign2   False   False
2020-01-01 19:50:00  3.0    7.0     sign2   False   False
2020-01-01 21:50:00  11.0   2.0     sign3   False   False

...なんじゃこりゃ!!ヾノ。ÒдÓ)ノシ バンバン!!
np.nanだったところがNoneじゃなくて、直前の値で埋められてます
fillnaされたみたいになってます)

3. replace方法その2

大丈夫?な方

df.replace({np.nan: None})
->                    high   close   memo   bool    stoploss
2020-01-01 11:50:00   1      4       sign   True    True
2020-01-01 12:50:00   None   5              None    None
2020-01-01 12:52:00   3      6       None   True    True
2020-01-01 18:50:00   None   7       sign2  False   False
2020-01-01 19:50:00   None   None    None   None    None
2020-01-01 21:50:00   11     2       sign3  False   False

期待通りではある(?
いや、気づいたけど、なんか、floatが、全部整数にされてる....
だいじょばないです(助けて)

...なんて一瞬(30分以上)焦りましたが、よく見てみたら中身はfloatのままでした

tmp_df = df.replace({np.nan: None})

tmp_df.values
-> array([[1.0, 4.0, 'sign', True, True],
       [None, 5.0, '', None, None],
       [3.0, 6.0, None, True, True],
       [None, 7.0, 'sign2', False, False],
       [None, None, None, None, None],
       [11.0, 2.0, 'sign3', False, False]], dtype=object)

ε-(´∀`*)ホッ

この書き方覚えとかないとね...( ..)φdf.replace({np.nan: None})

参考資料

一応、pandasの公式ドキュメントでもこの件は言及されています。
ただ、見つけるのにかなり時間がかかったので、今回記録しておくことにしました。

When value=None and to_replace is a scalar, list or tuple, replace uses the method parameter (default ‘pad’) to do the replacement. So this is why the ‘a’ values are being replaced by 10 in rows 1 and 2 and ‘b’ in row 4 in this case. The command s.replace('a', None) is actually equivalent to s.replace(to_replace='a', value=None, method='pad'):

日本語で書かれてたらもう少し早く気づけたかも...

その他関連資料

  • nan_to_num
    最新のnumpyにはnan_to_numというメソッドがあり、こちらでnp.nanを他の値に置き換えられるらしい
    もしかしたら、これなら今回のようなバグは起きないかも(試せていない) https://numpy.org/doc/stable/reference/generated/numpy.nan_to_num.html
  • 逆の操作をしようとした場合 ちょっと関係あるのかわからないけれど、Nonenp.nanで埋めようとする場合も、別の問題が発生する模様

StackOverflow : Replace None with NaN in pandas dataframe