pandasで欠損値NaNを除外(削除)・置換(穴埋め)・抽出
例えばcsvファイルをpandasで読み込んだとき、要素が空白だったりすると欠損値NaN
(Not a Number)だと見なされる。
欠損値を除外(削除)するにはdropna()
メソッド、欠損値を他の値に置換(穴埋め)するにはfillna()
メソッドを使う。
- pandas.DataFrame.dropna — pandas 0.23.0 documentation
- pandas.DataFrame.fillna — pandas 0.23.0 documentation
また、欠損値を含む行や列を抽出したい場合は、要素が欠損値かどうかを判定するisnull()
メソッドを使う。
例として空白を含むcsvファイルをread_csv
で読み込んで使用する。NumPyとmathは説明のためにインポートしているだけなので、処理自体には必要ない。
import pandas as pd
import numpy as np
import math
df = pd.read_csv('data/src/sample_pandas_normal_nan.csv')
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
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen NaN CA 88.0 NaN
# 5 Frank 30.0 NaN NaN NaN
サンプルのcsvファイルはコチラ。
ここでは以下の内容について説明する。
- pandasにおける欠損値
NaN
の型 - pandasにおいて欠損値
NaN
として扱われる値 - 欠損値を除外(削除)する
- すべての値が欠損値である行・列を削除する
- 欠損値が一つでも含まれる行・列を削除する
- 欠損値ではない要素の数に応じて行・列を削除する
- 特定の行・列に欠損値がある列・行を削除する
pandas.Series
の場合
- 欠損値を置換(穴埋め)する
- 共通の値で一律に置換する
- 列ごとに異なる値で置換する
- 列ごとに平均値、中央値、最頻値などで置換する
- 前後の値で置換する
- 最大連続置換回数を指定
pandas.Series
の場合
- 欠損値を抽出する
- 特定の行・列に欠損値がある列・行を抽出する
- 欠損値が一つでも含まれる行・列を抽出する
削除や置換、抽出ではなく、行・列に欠損値が含まれているかを判定したり欠損値の個数をカウントしたい場合は以下の記事を参照。
また、単純な置換ではなく前後の値から補間する場合は以下の記事を参照。
pandasにおける欠損値NaNの型
pandasにおいて、列に一つでも欠損値NaN
が含まれていると、ほかの値がすべて整数int
でもその列のdtype
は浮動小数点として処理される。文字列などPythonの組み込み型を格納するobject
型の列はそのまま。
df = pd.read_csv('data/src/sample_pandas_normal_nan.csv')
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
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen NaN CA 88.0 NaN
# 5 Frank 30.0 NaN NaN NaN
print(df.dtypes)
# name object
# age float64
# state object
# point float64
# other float64
# dtype: object
コードを書く上では特に気にする必要はないが、object
型の列の欠損値は組み込み型float
で、浮動小数点型の列の欠損値はNumPyのnumpy.float64
となる(ビット数を表す末尾の数字は環境によって異なる場合がある)。
print(df.at[1, 'name'])
print(type(df.at[1, 'name']))
# nan
# <class 'float'>
print(df.at[0, 'point'])
print(type(df.at[0, 'point']))
# nan
# <class 'numpy.float64'>
欠損値であるかどうかの判定にはpandas.isnull()
を使う。numpy.isnan()
, math.isnan()
でもいい(それぞれNumPy, mathのインポートが必要)。
print(pd.isnull(df.at[0, 'point']))
print(np.isnan(df.at[0, 'point']))
print(math.isnan(df.at[0, 'point']))
# True
# True
# True
np.nan
やmath.nan
と==
で比較してもFalse
を返すので注意。
print(df.at[0, 'point'] == np.nan)
# False
要素ごとの判定ではなく、行・列に欠損値が含まれているかを判定したい場合は以下の記事を参照。
pandasにおいて欠損値NaNとして扱われる値
pandasにおいては、None
, np.nan
, math.nan
およびpd.np.nan
が欠損値NaN
として扱われ、以降で説明するdropna()
やfillna()
の対象となる。なお、pd.np.nan
はpandasをインポートしていればNumPyをインポートしていなくても使用可能。
None
もnumpy.float64
のnan
に変換される。
s_nan = pd.Series([None, np.nan, math.nan, pd.np.nan])
print(s_nan)
# 0 NaN
# 1 NaN
# 2 NaN
# 3 NaN
# dtype: float64
print(s_nan[0])
print(type(s_nan[0]))
# nan
# <class 'numpy.float64'>
print(s_nan.isnull())
# 0 True
# 1 True
# 2 True
# 3 True
# dtype: bool
上述のように、欠損値が含まれていると整数int
型の値が浮動小数点float
型にキャストされる。
s_nan_int = pd.Series([None, pd.np.nan, 0, 1])
print(s_nan_int)
# 0 NaN
# 1 NaN
# 2 0.0
# 3 1.0
# dtype: float64
print(s_nan_int.isnull())
# 0 True
# 1 True
# 2 False
# 3 False
# dtype: bool
文字列str
型の値が含まれていると、そのpandas.Series
(およびpandas.DataFrame
の列)データ型はobject
となる。None
はnumpy.float64
のnan
に変換されずNone
のままとなるが、dropna()
やfillna()
の対象となるので実用上は特に気にする必要はない。
s_nan_str = pd.Series([None, pd.np.nan, 'NaN', 'nan'])
print(s_nan_str)
# 0 None
# 1 NaN
# 2 NaN
# 3 nan
# dtype: object
print(s_nan_str[0])
print(type(s_nan_str[0]))
# None
# <class 'NoneType'>
print(s_nan_str.isnull())
# 0 True
# 1 True
# 2 False
# 3 False
# dtype: bool
文字列'NaN'
などのように欠損値として扱いたい値がある場合はreplace()
メソッドで欠損値に置き換えればよい。
s_nan_str_replace = s_nan_str.replace({'NaN': pd.np.nan, 'nan': pd.np.nan})
print(s_nan_str_replace)
# 0 NaN
# 1 NaN
# 2 NaN
# 3 NaN
# dtype: float64
print(s_nan_str_replace.isnull())
# 0 True
# 1 True
# 2 True
# 3 True
# dtype: bool
なお、上の例でcsvファイルの空白の値が欠損値として読み込まれたように、read_csv()
などのファイルを読み込む関数では、空文字列(空白)や文字列'NaN'
, 'null'
などがデフォルトで欠損値として扱われる。詳細は以下の記事を参照。
欠損値を除外(削除)する
欠損値を除外(削除)するにはメソッドdropna()
を使う。
デフォルトでは新しいオブジェクトを返して元のオブジェクトは変更されないが、引数inplace=True
を指定すると元のオブジェクト自体が変更される。
先に読み込んだpandas.DataFrame
を例とする。
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
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen NaN CA 88.0 NaN
# 5 Frank 30.0 NaN NaN NaN
すべての値が欠損値である行・列を削除する
引数how='all'
を指定するとすべての値が欠損値の行が削除される。
print(df.dropna(how='all'))
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 2 Charlie NaN CA NaN NaN
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen NaN CA 88.0 NaN
# 5 Frank 30.0 NaN NaN NaN
さらにaxis=1
とするとすべての値が欠損値の列が削除される。
print(df.dropna(how='all', axis=1))
# name age state point
# 0 Alice 24.0 NY NaN
# 1 NaN NaN NaN NaN
# 2 Charlie NaN CA NaN
# 3 Dave 68.0 TX 70.0
# 4 Ellen NaN CA 88.0
# 5 Frank 30.0 NaN NaN
axis=[0, 1]
とするとすべての値が欠損値の行と列が削除されるが、バージョン0.23.0
から引数axis
のリストやタプルでの指定はDeprecated(非推奨)となった。
行・列両方に適用したい場合はdropna()
を繰り返し適用すればOK。
print(df.dropna(how='all').dropna(how='all', axis=1))
# name age state point
# 0 Alice 24.0 NY NaN
# 2 Charlie NaN CA NaN
# 3 Dave 68.0 TX 70.0
# 4 Ellen NaN CA 88.0
# 5 Frank 30.0 NaN NaN
欠損値が一つでも含まれる行・列を削除する
すべての値が欠損値である行と列を削除したデータを例とする。
df2 = df.dropna(how='all').dropna(how='all', axis=1)
print(df2)
# name age state point
# 0 Alice 24.0 NY NaN
# 2 Charlie NaN CA NaN
# 3 Dave 68.0 TX 70.0
# 4 Ellen NaN CA 88.0
# 5 Frank 30.0 NaN NaN
引数how='any'
を指定すると、欠損値が一つでも含まれる行が削除される。デフォルトがhow='any'
なので、何も指定しないとこの動作になる。
print(df2.dropna(how='any'))
# name age state point
# 3 Dave 68.0 TX 70.0
print(df2.dropna())
# name age state point
# 3 Dave 68.0 TX 70.0
axis=1
とすると列に適用。欠損値が一つでも含まれる列が削除される。
print(df2.dropna(how='any', axis=1))
# name
# 0 Alice
# 2 Charlie
# 3 Dave
# 4 Ellen
# 5 Frank
欠損値ではない要素の数に応じて行・列を削除する
(2019年2月5日誤記修正)
引数thresh
に個数を指定すると欠損値ではない要素の数に応じて行・列を削除できる。
例えばthresh=3
とすると欠損値ではない要素の数が3個以上含まれている行が残り、それ以外の行(欠損値ではない要素の数が2個以下の行)が削除される。
print(df.dropna(thresh=3))
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen NaN CA 88.0 NaN
axis=1
とすると列に適用。
print(df.dropna(thresh=3, axis=1))
# name age state
# 0 Alice 24.0 NY
# 1 NaN NaN NaN
# 2 Charlie NaN CA
# 3 Dave 68.0 TX
# 4 Ellen NaN CA
# 5 Frank 30.0 NaN
特定の行・列に欠損値がある列・行を削除する
特定の行・列を基準に削除したい場合は、引数subset
に対象としたい行ラベル・列ラベルをリストで指定する。リストである必要があるので、対象が一つでもsubset=['name']
のように指定する。
デフォルトではsubset
で指定した列に欠損値がある行を削除する。
print(df.dropna(subset=['age']))
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 3 Dave 68.0 TX 70.0 NaN
# 5 Frank 30.0 NaN NaN NaN
複数列を指定した場合、デフォルトでは指定した列のいずれかが欠損値である行をすべて削除する。
print(df.dropna(subset=['age', 'state']))
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 3 Dave 68.0 TX 70.0 NaN
引数how='all'
とすると、指定した列すべてが欠損値である行のみを削除する。
print(df.dropna(subset=['age', 'state'], how='all'))
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 2 Charlie NaN CA NaN NaN
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen NaN CA 88.0 NaN
# 5 Frank 30.0 NaN NaN NaN
axis=1
とすると、subset
で指定した行に欠損値がある列を削除する。引数how
も使える。
print(df.dropna(subset=[0, 4], axis=1))
# name state
# 0 Alice NY
# 1 NaN NaN
# 2 Charlie CA
# 3 Dave TX
# 4 Ellen CA
# 5 Frank NaN
print(df.dropna(subset=[0, 4], axis=1, how='all'))
# name age state point
# 0 Alice 24.0 NY NaN
# 1 NaN NaN NaN NaN
# 2 Charlie NaN CA NaN
# 3 Dave 68.0 TX 70.0
# 4 Ellen NaN CA 88.0
# 5 Frank 30.0 NaN NaN
pandas.Seriesの場合
データが一次元のpandas.Series
の場合、シンプルにdropna()
を呼ぶだけでいい。欠損値が削除される。
s = df['age']
print(s)
# 0 24.0
# 1 NaN
# 2 NaN
# 3 68.0
# 4 NaN
# 5 30.0
# Name: age, dtype: float64
print(s.dropna())
# 0 24.0
# 3 68.0
# 5 30.0
# Name: age, dtype: float64
欠損値を置換(穴埋め)する
メソッドfillna()
を使うと欠損値を任意の値で置き換えられる。
デフォルトでは新しいオブジェクトを返して元のオブジェクトは変更されないが、引数inplace=True
を指定すると元のオブジェクト自体が変更される。
先に読み込んだpandas.DataFrame
を例とする。
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
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen NaN CA 88.0 NaN
# 5 Frank 30.0 NaN NaN NaN
なお、単純な置換ではなく前後の値から補間する場合はinterpolate()
を使う。以下の記事を参照。
共通の値で一律に置換する
引数に置き換えたい値を指定するとすべての欠損値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
# 3 Dave 68.0 TX 70.0 0.0
# 4 Ellen 0.0 CA 88.0 0.0
# 5 Frank 30.0 0 0.0 0.0
列ごとに異なる値で置換する
引数に辞書を指定すると、列ごとに異なる値が代入される。辞書のkey
を列ラベル(列名)、value
を置き換えたい値とする。指定されていない列は欠損値NaN
のまま。
print(df.fillna({'name': 'XXX', 'age': 20, 'point': 0}))
# name age state point other
# 0 Alice 24.0 NY 0.0 NaN
# 1 XXX 20.0 NaN 0.0 NaN
# 2 Charlie 20.0 CA 0.0 NaN
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen 20.0 CA 88.0 NaN
# 5 Frank 30.0 NaN 0.0 NaN
辞書だけでなくpandas.Series
も指定可能。pandas.Series
のラベルと一致する列ラベル(列名)の列の欠損値がpandas.Series
の値で置換される。pandas.Series
のラベルと対応しない列は欠損値のまま。
s_for_fill = pd.Series(['ZZZ', 100], index=['name', 'age'])
print(s_for_fill)
# name ZZZ
# age 100
# dtype: object
print(df.fillna(s_for_fill))
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 1 ZZZ 100.0 NaN NaN NaN
# 2 Charlie 100.0 CA NaN NaN
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen 100.0 CA 88.0 NaN
# 5 Frank 30.0 NaN NaN NaN
列ごとに平均値、中央値、最頻値などで置換する
列ごとの平均値はmean()
メソッドで算出できる。結果はpandas.Series
。欠損値は除外して算出される。
print(df.mean())
# age 40.666667
# point 79.000000
# other NaN
# dtype: float64
このpandas.Series
をfillna()
の引数に指定すると、上述のように、対応する列の欠損値が平均値で置換される。
print(df.fillna(df.mean()))
# name age state point other
# 0 Alice 24.000000 NY 79.0 NaN
# 1 NaN 40.666667 NaN 79.0 NaN
# 2 Charlie 40.666667 CA 79.0 NaN
# 3 Dave 68.000000 TX 70.0 NaN
# 4 Ellen 40.666667 CA 88.0 NaN
# 5 Frank 30.000000 NaN 79.0 NaN
同様に、中央値で置き換えたい場合はmedian()
メソッドを使う。偶数個の場合は中央二つの値の平均値が中央値となる。
print(df.fillna(df.median()))
# name age state point other
# 0 Alice 24.0 NY 79.0 NaN
# 1 NaN 30.0 NaN 79.0 NaN
# 2 Charlie 30.0 CA 79.0 NaN
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen 30.0 CA 88.0 NaN
# 5 Frank 30.0 NaN 79.0 NaN
最頻値はmode()
メソッド。mode()
はpandas.DataFrame
を返すのでiloc[0]
で先頭行をpandas.Series
として取得している。
print(df.fillna(df.mode().iloc[0]))
# name age state point other
# 0 Alice 24.0 NY 70.0 NaN
# 1 Alice 24.0 CA 70.0 NaN
# 2 Charlie 24.0 CA 70.0 NaN
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen 24.0 CA 88.0 NaN
# 5 Frank 30.0 CA 70.0 NaN
この例では特に問題ないが、mean()
などのメソッドはデフォルトでは数値列だけでなくほかの型の列に対しても処理を試みるので思いもよらない値を返す場合がある。
引数numeric_only=True
とすると対象が数値列に限定される。なお、その場合もbool
型の列はTrue=1
, False=0
として処理対象となる。
前後の値で置換する
引数method
を使うと指定した値ではなく前後の値で置換できる。
method='ffill'
とすると前の値で置き換えられ、method='bfill'
とすると後ろの値で置き換えられる。時系列データのときに便利。
print(df.fillna(method='ffill'))
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 1 Alice 24.0 NY NaN NaN
# 2 Charlie 24.0 CA NaN NaN
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen 68.0 CA 88.0 NaN
# 5 Frank 30.0 CA 88.0 NaN
print(df.fillna(method='bfill'))
# name age state point other
# 0 Alice 24.0 NY 70.0 NaN
# 1 Charlie 68.0 CA 70.0 NaN
# 2 Charlie 68.0 CA 70.0 NaN
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen 30.0 CA 88.0 NaN
# 5 Frank 30.0 NaN NaN NaN
最大連続置換回数を指定
引数limit
で、最大何回まで連続して置換するかを指定できる。
print(df.fillna(method='bfill', limit=1))
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 1 Charlie NaN CA NaN NaN
# 2 Charlie 68.0 CA 70.0 NaN
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen 30.0 CA 88.0 NaN
# 5 Frank 30.0 NaN NaN NaN
pandas.Seriesの場合
pandas.Series
の場合もこれまでの例と同様に処理が可能。
s = df['age']
print(s)
# 0 24.0
# 1 NaN
# 2 NaN
# 3 68.0
# 4 NaN
# 5 30.0
# Name: age, dtype: float64
print(s.fillna(100))
# 0 24.0
# 1 100.0
# 2 100.0
# 3 68.0
# 4 100.0
# 5 30.0
# Name: age, dtype: float64
print(s.fillna({1: 100, 4: 0}))
# 0 24.0
# 1 100.0
# 2 NaN
# 3 68.0
# 4 0.0
# 5 30.0
# Name: age, dtype: float64
print(s.fillna(method='bfill', limit=1))
# 0 24.0
# 1 NaN
# 2 68.0
# 3 68.0
# 4 30.0
# 5 30.0
# Name: age, dtype: float64
欠損値を抽出する
特定の行・列に欠損値がある列・行を抽出する
特定の列に欠損値が含まれている行を選択して確認したい場合、列のisnull()
メソッドで欠損値がTrue
、それ以外がFalse
となるpandas.Series
を取得し、ブールインデックス参照で抽出する。
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
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen NaN CA 88.0 NaN
# 5 Frank 30.0 NaN NaN NaN
print(df['point'].isnull())
# 0 True
# 1 True
# 2 True
# 3 False
# 4 False
# 5 True
# Name: point, dtype: bool
print(df[df['point'].isnull()])
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 1 NaN NaN NaN NaN NaN
# 2 Charlie NaN CA NaN NaN
# 5 Frank 30.0 NaN NaN NaN
特定の行に欠損値が含まれている列を選択する場合も考え方は同じ。行名(行ラベル)で選択する場合はloc[]
、位置で選択する場合はiloc[]
を使う。
print(df.iloc[2].isnull())
# name False
# age True
# state False
# point True
# other True
# Name: 2, dtype: bool
print(df.loc[:, df.iloc[2].isnull()])
# age point other
# 0 24.0 NaN NaN
# 1 NaN NaN NaN
# 2 NaN NaN NaN
# 3 68.0 70.0 NaN
# 4 NaN 88.0 NaN
# 5 30.0 NaN NaN
欠損値が一つでも含まれる行・列を抽出する
特定の行・列に対する判定ではなく、欠損値が一つでも含まれる行・列を抽出する場合。
すべての値が欠損値である行と列を削除したデータを例とする。
df2 = df.dropna(how='all').dropna(how='all', axis=1)
print(df2)
# name age state point
# 0 Alice 24.0 NY NaN
# 2 Charlie NaN CA NaN
# 3 Dave 68.0 TX 70.0
# 4 Ellen NaN CA 88.0
# 5 Frank 30.0 NaN NaN
pandas.DataFrame
のisnull()
メソッドは各要素が欠損値かどうかを判定しTrue
, False
とするpandas.DataFrame
を返す。
print(df2.isnull())
# name age state point
# 0 False False False True
# 2 False True False True
# 3 False False False False
# 4 False True False False
# 5 False False True True
これに対してany()
メソッドを適用しブールインデックス参照で抽出する。
any
メソッドは行または列に一つでもTrue
が含まれているとTrue
を返す。引数axis=1
とすると行に対する処理となる。
print(df2.isnull().any(axis=1))
# 0 True
# 2 True
# 3 False
# 4 True
# 5 True
# dtype: bool
print(df2[df2.isnull().any(axis=1)])
# name age state point
# 0 Alice 24.0 NY NaN
# 2 Charlie NaN CA NaN
# 4 Ellen NaN CA 88.0
# 5 Frank 30.0 NaN NaN
列を抽出する場合も同様。any()
の引数axis=0
とすると列に対する処理となる。デフォルトがaxis=0
なので省略可能。
print(df2.isnull().any())
# name False
# age True
# state True
# point True
# dtype: bool
print(df2.loc[:, df2.isnull().any()])
# age state point
# 0 24.0 NY NaN
# 2 NaN CA NaN
# 3 68.0 TX 70.0
# 4 NaN CA 88.0
# 5 30.0 NaN NaN