pandasにおける欠損値(nan, None, pd.NA)
pandasにおいて欠損値(Missing value, NA: not available)は主にnan(not a number、非数)を用いて表される。そのほか、Noneも欠損値として扱われる。
本記事のサンプルコードのpandasのバージョンは以下の通り。バージョンによって仕様が異なる可能性があるので注意。説明のため、mathおよびNumPyも使う。
import math
import numpy as np
import pandas as pd
print(pd.__version__)
# 2.0.3
ファイルの読み込みなどで生じる欠損値
値が欠損したCSVファイルなどを読み込むとnanが生じる。pandas.DataFrame, Seriesのprint()出力ではNaNと表記される。
df = pd.read_csv('data/src/sample_pandas_normal_nan.csv')[:3]
print(df)
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 1 NaN NaN NaN NaN NaN
# 2 Charlie NaN CA NaN NaN
isnull(), dropna(), fillna()といったメソッドで判定・削除・置換などができる。
- 関連記事: pandasで欠損値NaNが含まれているか判定、個数をカウント
- 関連記事: pandasで欠損値NaNを削除(除外)するdropna
- 関連記事: pandasで欠損値NaNを置換(穴埋め)するfillna
print(df.isnull())
# name age state point other
# 0 False False False True True
# 1 True True True True True
# 2 False True False True True
print(df.dropna(how='all'))
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 2 Charlie NaN CA NaN NaN
print(df.fillna(0))
# name age state point other
# 0 Alice 24.0 NY 0.0 0.0
# 1 0 0.0 0 0.0 0.0
# 2 Charlie 0.0 CA 0.0 0.0
データ型dtypeがobjectの列のnanはPython組み込みのfloat型で、floatXXの列のnanはNumPyのnumpy.floatXX型。どちらも同じように欠損値として処理されるので特に気にする必要はない。
print(df.dtypes)
# name object
# age float64
# state object
# point float64
# other float64
# dtype: object
print(df.at[1, 'name'])
# nan
print(type(df.at[1, 'name']))
# <class 'float'>
print(df.at[1, 'age'])
# nan
print(type(df.at[1, 'age']))
# <class 'numpy.float64'>
ファイル読み込みのほか、reindex()やmerge()などで値が存在しない場合も欠損値としてnanが用いられる。
nan(not a number)は欠損値
Pythonではfloat('nan')やmath.nan, np.nanでnanを生成できる。pandasにおいてnanは欠損値として扱われる。
- 関連記事: Pythonにおけるnanの判定
s_nan = pd.Series([float('nan'), math.nan, np.nan])
print(s_nan)
# 0 NaN
# 1 NaN
# 2 NaN
# dtype: float64
print(s_nan.isnull())
# 0 True
# 1 True
# 2 True
# dtype: bool
Noneも欠損値
pandasではNoneも欠損値として扱われる。NoneはPythonの組み込み定数。
- 関連記事: PythonにおけるNoneの判定
print(None)
# None
print(type(None))
# <class 'NoneType'>
数値列では、Noneを含むpandas.DataFrameやSeriesを生成またはNoneを要素に代入した時点で、Noneがnanに変換される。
s_none_float = pd.Series([None, 0.1, 0.2])
s_none_float[2] = None
print(s_none_float)
# 0 NaN
# 1 0.1
# 2 NaN
# dtype: float64
print(s_none_float.isnull())
# 0 True
# 1 False
# 2 True
# dtype: bool
nanは浮動小数点数floatなので、他の値が整数intであっても、Noneがnanに変換されるとその列のデータ型dtypeはfloatになる。
s_none_int = pd.Series([None, 1, 2])
print(s_none_int)
# 0 NaN
# 1 1.0
# 2 2.0
# dtype: float64
object列のNoneはNoneのままだが、isnull()などでは欠損値として判定される。当然、dropna(), fillna()などでも処理の対象になる。
s_none_object = pd.Series([None, 'abc', 'xyz'])
print(s_none_object)
# 0 None
# 1 abc
# 2 xyz
# dtype: object
print(s_none_object.isnull())
# 0 True
# 1 False
# 2 False
# dtype: bool
print(s_none_object.fillna(0))
# 0 0
# 1 abc
# 2 xyz
# dtype: object
文字列'NaN'や'None'などは欠損値ではない
文字列'NaN'や'None'などは表示上は見分けがつかないが、あくまでも文字列なので欠損値とはみなされない。空文字列''も欠損値とはみなされない。
s_str = pd.Series(['NaN', 'None', ''])
print(s_str)
# 0 NaN
# 1 None
# 2
# dtype: object
print(s_str.isnull())
# 0 False
# 1 False
# 2 False
# dtype: bool
欠損値として扱いたい値はreplace()メソッドでfloat('nan')などに置き換えればよい。
s_replace = s_str.replace(['NaN', 'None', ''], float('nan'))
print(s_replace)
# 0 NaN
# 1 NaN
# 2 NaN
# dtype: float64
print(s_replace.isnull())
# 0 True
# 1 True
# 2 True
# dtype: bool
なお、CSVファイルの空白の値がnanとして読み込まれるように、read_csv()などのファイルを読み込む関数では空文字列''や文字列'NaN', 'null'などがデフォルトで欠損値とみなされnanに置き換えられる。詳細は以下の記事を参照。
無限大infはデフォルトでは欠損値ではない(設定で変更可能)
Pythonの浮動小数点数floatには無限大を表すinfがある。
無限大infはデフォルトでは欠損値とみなされない。
s_inf = pd.Series([float('inf'), -float('inf')])
print(s_inf)
# 0 inf
# 1 -inf
# dtype: float64
print(s_inf.isnull())
# 0 False
# 1 False
# dtype: bool
pandasのオプション設定でpd.options.mode.use_inf_as_naをTrueにすると、pandas.DataFrame, Seriesの中のinfはnanに変換されて欠損値として扱われる。Noneとは異なりobject列でもnanに変換される。
pd.options.mode.use_inf_as_na = True
print(s_inf)
# 0 NaN
# 1 NaN
# dtype: float64
print(s_inf.isnull())
# 0 True
# 1 True
# dtype: bool
s_inf_object = pd.Series([float('inf'), -float('inf'), 'abc'])
print(s_inf_object)
# 0 NaN
# 1 NaN
# 2 abc
# dtype: object
print(s_inf_object.isnull())
# 0 True
# 1 True
# 2 False
# dtype: bool
pandasのオプション設定については以下の記事を参照。
- 関連記事: pandasのオプション設定を確認・変更する方法
pd.NAは実験的な欠損値(2.0.3時点)
pandas1.0.0で実験的(Experimental)な欠損値としてpd.NAが導入された。
print(pd.NA)
# <NA>
print(type(pd.NA))
# <class 'pandas._libs.missing.NAType'>
nan同士を==で比較するとFalseとなるが、pd.NA同士を==で比較するとpd.NAとなる(R言語と同じ仕様)。
print(float('nan') == float('nan'))
# False
print(pd.NA == pd.NA)
# <NA>
もちろんisnull()やfillna()などの対象となる。
s_na = pd.Series([None, 1, 2], dtype='Int64')
print(s_na)
# 0 <NA>
# 1 1
# 2 2
# dtype: Int64
print(s_na.isnull())
# 0 True
# 1 False
# 2 False
# dtype: bool
print(s_na.fillna(0))
# 0 0
# 1 1
# 2 2
# dtype: Int64
上記サンプルコードに登場するInt64については以下を参照。欠損値を含んでいても他の整数の値が浮動小数点数に変換されることなく処理できる。
なお、2.0.3(2023年6月時点)でも「Experimental」。仕様が変わる可能性もあるので本格的に使うのは難しそうだが、とりあえず存在は知っておくといいかもしれない。
Warning
Experimental: the behaviour of pd.NA can still change without warning. Working with missing data - Experimental NA scalar to denote missing values — pandas 2.0.3 documentation