note.nkmk.me

pandasで欠損値NaNを除外(削除)・置換(穴埋め)する

Date: 2017-11-16 / Modified: 2018-05-20 / tags: Python, pandas
このエントリーをはてなブックマークに追加

例えばcsvファイルをpandasで読み込んだとき、要素が空白だったりすると欠損値NaN(Not a Number)だと見なされる。

欠損値を除外(削除)するにはdropna()メソッド、欠損値を他の値に置換(穴埋め)するにはfillna()メソッドを使う。

例として空白を含む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.Seriesの場合
  • 欠損値を置換(穴埋め)する
    • 共通の値で一律に置換する
    • 列ごとに異なる値で置換する
    • 列ごとに平均値、中央値、最頻値などで置換する
    • 前後の値で置換する
    • 最大連続置換回数を指定
    • pandas.Seriesの場合

削除や置換ではなく、行・列に欠損値が含まれているかを判定したい場合は以下の記事を参照。

pandasにおける欠損値NaNの型

pandasにおいて、列に一つでも欠損値NaNが含まれていると、ほかの値がすべて整数intでもその列のdtypeは浮動小数点として処理される。文字列などPythonの組み込み型を格納するobject型の列はそのまま。

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.nanmath.nan==で比較してもFalseを返すので注意。

print(pd.isnull(df.at[0, 'point']) == np.nan)
# False

要素ごとの判定ではなく、行・列に欠損値が含まれているかを判定したい場合は以下の記事を参照。

欠損値を除外(削除)する

欠損値を除外(削除)するにはメソッドdropna()を使う。

デフォルトでは新しいオブジェクトを返して元のオブジェクトは変更されないが、引数inplace=Trueを指定すると元のオブジェクト自体が変更される。

すべての値が欠損値である行・列を削除する

引数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]とすると、すべての値が欠損値の行と列が削除される。

print(df.dropna(how='all', axis=[0, 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', axis=[0, 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

欠損値の数に応じて行・列を削除する

引数threshに個数を指定すると含まれる欠損値の数に応じて行・列を削除できる。

例えばthresh=3とすると欠損値が3個以上含まれている行が削除される。

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を指定すると元のオブジェクト自体が変更される。

共通の値で一律に置換する

引数に置き換えたい値を指定するとすべての欠損値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.Seriesfillna()の引数に指定すると、上述のように、対応する列の欠損値が平均値で置換される。

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
スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事