pandasにおける欠損値(nan, None, pd.NA)

Modified: | Tags: Python, pandas

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, Seriesprint()出力では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()といったメソッドで判定・削除・置換などができる。

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

データ型dtypeobjectの列の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.nannanを生成できる。pandasにおいて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の組み込み定数。

print(None)
# None

print(type(None))
# <class 'NoneType'>

数値列では、Noneを含むpandas.DataFrameSeriesを生成またはNoneを要素に代入した時点で、Nonenanに変換される。

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であっても、Nonenanに変換されるとその列のデータ型dtypefloatになる。

s_none_int = pd.Series([None, 1, 2])
print(s_none_int)
# 0    NaN
# 1    1.0
# 2    2.0
# dtype: float64

object列のNoneNoneのままだが、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_naTrueにすると、pandas.DataFrame, Seriesの中のinfnanに変換されて欠損値として扱われる。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のオプション設定については以下の記事を参照。

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

関連カテゴリー

関連記事