[pandas]DataFrameのNaNをサクッとNoneに置き換える方法2つ

結論

df.where(df.notnull(), None)

もしくは

df.replace([np.nan], [None])

で、NaNをNoneに置換できます。

はじめに

pandasのDataFrameは、カラムの型がobjectでない場合、欠損値にはNaNが入ります。
このNaNは、Noneと似てますがNoneではないので、そのことを考慮せずに扱うとハマります。

なので、サクッとNaNをNoneに変換する方法を調べました。

NoneがNaNに変換される時

まずはNoneで値を入れてもNaNになるケースを確認しておきます。

df = pd.DataFrame({
    'A': ['a', 1, 'B', None],
    'B': [0, 1, None, 4]
    })
df

      A    B
0     a  0.0
1     1  1.0
2     B  NaN
3  None  4.0

df.dtypes
A     object
B    float64
dtype: object

Aカラムは、文字列、数字、Noneが入り型が混在しているので、カラムの定義はobject型になります。
Bカラムは、None以外の値が数字なので、float64型になります。この時、NoneはNaNに変換されています。

このBカラムのNaNをNoneにしたい、というのが今回やりたいことです。

Seriesでの処理も確認するために切り出しておきます

sr = df.B
sr
0    0.0
1    1.0
2    NaN
3    4.0
Name: B, dtype: float64

fillna()ではNoneを入れることはできない

欠損値の置換では、df.fillna()をよく使いますが、Noneに置き換えることはできません。

df.fillna(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/anaconda3/lib/python3.7/site-packages/pandas/core/series.py", line 3425, in fillna
    **kwargs)
  File "/anaconda3/lib/python3.7/site-packages/pandas/core/generic.py", line 5348, in fillna
    value, method = validate_fillna_kwargs(value, method)
  File "/anaconda3/lib/python3.7/site-packages/pandas/util/_validators.py", line 346, in validate_fillna_kwargs
    raise ValueError("Must specify a fill 'value' or 'method'.")
ValueError: Must specify a fill 'value' or 'method'.

sr.fillna(None)
# 同じ結果

理由はおまけの項にて後述。

where()ならNoneを入れることができる

# DataFrame
df.where(df.notna(), None)
      A     B
0     a     0
1     1     1
2     B  None
3  None     4


# Series
sr.where(sr.notna(), None)
0       0
1       1
2    None
3       4
Name: B, dtype: object

whereは、第一引数の条件に当てはまっているなら元の値を、そうでないなら第二引数の値を入れます。

これでNoneをサクッと入れることができます。
ただし、型はfloat64からobjectに変更されるので、その点ご留意ください。

df.where(df.notna(), None).dtypes
A    object
B    object
dtype: object

replace()を使うことでNoneを入れられる

別の方法もあります。

# DataFrame
df.replace(dict(B={np.nan: None}))
      A     B
0     a     0
1     1     1
2     B  None
3  None     4


# Series
sr.replace(dict({np.nan: None}))
0       0
1       1
2    None
3       4
Name: B, dtype: object

この場合も、列の型はobjectになります。

より確実な方法として、一旦fillnaで欠損値をすべてNaNに変換してから、replaceでNoneに置換することもできます。
(今回のサンプルだと結果は同じです)

# DataFrame
df.fillna(np.nan).replace([np.nan],[None])
      A     B
0     a     0
1     1     1
2     B  None
3  None     4

# Series
sr.fillna(np.nan).replace([np.nan],[None])
0       0
1       1
2    None
3       4
Name: B, dtype: object

おまけ: object型のカラムに変更したらできる?→できない

型がobjectになるなら、列の型をobjectに変更したらfillna(None)で行ける?と思いきや、そうではないようです。

df
0    0.0
1    1.0
2    NaN
3    4.0
Name: B, dtype: float64

obj_df = df.astype(object)
obj_df

0      0
1      1
2    NaN
3      4
Name: B, dtype: object

obj_df.fillna(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/anaconda3/lib/python3.7/site-packages/pandas/core/series.py", line 3425, in fillna
    **kwargs)
  File "/anaconda3/lib/python3.7/site-packages/pandas/core/generic.py", line 5348, in fillna
    value, method = validate_fillna_kwargs(value, method)
  File "/anaconda3/lib/python3.7/site-packages/pandas/util/_validators.py", line 346, in validate_fillna_kwargs
    raise ValueError("Must specify a fill 'value' or 'method'.")
ValueError: Must specify a fill 'value' or 'method'.

理由ですが、列の型が問題なのではなく、fillna(None)と書くとfillnaという関数に引数を入れずに渡した時と同じことになるからと思われます。

df.fillna()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/anaconda3/lib/python3.7/site-packages/pandas/core/frame.py", line 3790, in fillna
    downcast=downcast, **kwargs)
  File "/anaconda3/lib/python3.7/site-packages/pandas/core/generic.py", line 5348, in fillna
    value, method = validate_fillna_kwargs(value, method)
  File "/anaconda3/lib/python3.7/site-packages/pandas/util/_validators.py", line 346, in validate_fillna_kwargs
    raise ValueError("Must specify a fill 'value' or 'method'.")
ValueError: Must specify a fill 'value' or 'method'.

参考