pandasで欠損値NaNを置換(穴埋め)するfillna
pandasでDataFrameやSeriesの欠損値NaNを任意の値に置換(穴埋め、代入)するにはfillna()メソッドを使う。
- pandas.DataFrame.fillna — pandas 2.1.4 documentation
- pandas.Series.fillna — pandas 2.1.4 documentation
単純な置換ではなく前後の値から補間するにはinterpolate()を使う。
欠損値NaNの抽出・削除・カウントについては以下の記事を参照。
- 関連記事: pandasで欠損値NaNを含む行・列を抽出
- 関連記事: pandasで欠損値NaNを削除(除外)するdropna
- 関連記事: pandasで欠損値NaNが含まれているか判定、個数をカウント
なお、pandasではNaN(Not a Number: 非数)のほか、Noneも欠損値として扱われる。
本記事のサンプルコードのpandasのバージョンは以下の通り。バージョンによって仕様が異なる可能性があるので注意。例として、空白を含むCSVファイルを読み込んで使用する。
import pandas as pd
print(pd.__version__)
# 2.1.4
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
欠損値NaNを共通の値で一律に置換
fillna()の第一引数valueに置き換えたい値を指定すると、すべての欠損値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
NaNを含む数値の列はデータ型が浮動小数点数floatとなるため、NaNを整数intの値に置換してもデータ型はfloatのまま。intに変換したい場合はastype()を使う。
欠損値NaNを列ごとに異なる値で置換
fillna()の第一引数valueに辞書dictを指定すると、列ごとに異なる値を代入できる。
{key: value}を{列名: 置き換えたい値}とする。指定されていない列は欠損値NaNのまま。列名と一致しないkeyは無視される。
print(df.fillna({'name': 'XXX', 'age': 20, 'ZZZ': 100}))
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 1 XXX 20.0 NaN NaN NaN
# 2 Charlie 20.0 CA NaN NaN
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen 20.0 CA 88.0 NaN
# 5 Frank 30.0 NaN NaN NaN
Seriesも指定可能。
Seriesのラベルと一致する列名の列の欠損値がSeriesの値で置換される。Seriesのラベルと対応しない列は欠損値のまま。列名と一致しないSeriesのラベルは無視される。
s_for_fill = pd.Series(['XXX', 20, 100], index=['name', 'age', 'ZZZ'])
print(s_for_fill)
# name XXX
# age 20
# ZZZ 100
# dtype: object
print(df.fillna(s_for_fill))
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 1 XXX 20.0 NaN NaN NaN
# 2 Charlie 20.0 CA NaN NaN
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen 20.0 CA 88.0 NaN
# 5 Frank 30.0 NaN NaN NaN
欠損値NaNを列ごとに平均値・中央値・最頻値などで置換
列ごとの平均値はmean()メソッドで算出できる。欠損値NaNは除外して算出されるが、すべての要素がNaNの列はNaN。引数numeric_onlyをTrueとすると対象を数値列に限定できる。返り値はSeries。
print(df.mean(numeric_only=True))
# age 40.666667
# point 79.000000
# other NaN
# dtype: float64
このSeriesをfillna()の第一引数valueに指定すると、上述のように、対応する列の欠損値が平均値で置換される。
print(df.fillna(df.mean(numeric_only=True)))
# 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()メソッドを使う。偶数個の場合は中央二つの値の平均値が中央値となる。
- 関連記事: pandasで中央値を取得するmedian
print(df.fillna(df.median(numeric_only=True)))
# 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()はDataFrameを返すのでiloc[0]で先頭行を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
欠損値NaNを前後の値で置換: ffill(), bfill()
指定した値ではなく欠損値NaNの前後(上下)の要素の値で置換するにはffill()およびbfill()メソッドを使う。
- pandas.DataFrame.ffill — pandas 2.1.4 documentation
- pandas.DataFrame.bfill — pandas 2.1.4 documentation
ffill()は欠損値を前(上)の値で置き換え、bfill()は後ろ(下)の値で置き換える。
print(df.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.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.ffill(limit=1))
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 1 Alice 24.0 NY NaN NaN
# 2 Charlie NaN 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.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
引数axisを1または'columns'とすると左右の値で置換される。ffill()は左の値、bfill()は右の値で置き換える。
print(df.ffill(axis=1))
# name age state point other
# 0 Alice 24.0 NY NY NY
# 1 NaN NaN NaN NaN NaN
# 2 Charlie Charlie CA CA CA
# 3 Dave 68.0 TX 70.0 70.0
# 4 Ellen Ellen CA 88.0 88.0
# 5 Frank 30.0 30.0 30.0 30.0
print(df.bfill(axis=1))
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 1 NaN NaN NaN NaN NaN
# 2 Charlie CA CA NaN NaN
# 3 Dave 68.0 TX 70.0 NaN
# 4 Ellen CA CA 88.0 NaN
# 5 Frank 30.0 NaN NaN NaN
なお、ffill(), bfill()と同じ処理を行うpad(), backfill()は、バージョン2.0.0からDeprecated(非推奨)になっている。
- pandas.DataFrame.pad — pandas 2.1.4 documentation
- pandas.DataFrame.backfill — pandas 2.1.4 documentation
fillna()の引数methodはバージョン2.1.0でDeprecated(非推奨)
バージョン2.1.0でDeprecated(非推奨)になったが、fillna()の引数method, limitを指定することでffill(), bfill()と同じ処理が可能。
- What’s new in 2.1.0 (Aug 30, 2023) — pandas 2.1.4 documentation
- DEPR: fillna 'method' · Issue #53394 · pandas-dev/pandas
引数methodを'ffill'または'pad'とするとffill()と同じ処理、'bfill'または'backfill'とするとbfill()と同じ処理になる。
バージョン2.1.4ではまだ使用可能だが、FutureWarningが出る。
print(df.fillna(method='ffill', limit=1))
# name age state point other
# 0 Alice 24.0 NY NaN NaN
# 1 Alice 24.0 NY NaN NaN
# 2 Charlie NaN 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
#
# /var/folders/rf/b7l8_vgj5mdgvghn_326rn_c0000gn/T/ipykernel_50534/2498159999.py:1: FutureWarning: DataFrame.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead.
元のオブジェクトを変更: 引数inplace
fillna()やfill(), bfill()はデフォルトでは新しいオブジェクトを返し、元のオブジェクトは変更されない。引数inplaceをTrueとすると元のオブジェクト自体が変更される。
例はfillna()だが、fill()とbfill()でも同様。
df.fillna(0, inplace=True)
print(df)
# 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
pandas.Seriesのfillna(), ffill(), bfill()
Seriesの場合もこれまでのDataFrameの例と同様にfillna()が使える。
s = pd.read_csv('data/src/sample_pandas_normal_nan.csv')['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: -100}))
# 0 24.0
# 1 100.0
# 2 NaN
# 3 68.0
# 4 -100.0
# 5 30.0
# Name: age, dtype: float64
ffill()とbfill()も提供されている。
print(s.ffill(limit=1))
# 0 24.0
# 1 24.0
# 2 NaN
# 3 68.0
# 4 68.0
# 5 30.0
# Name: age, dtype: float64
print(s.bfill(limit=1))
# 0 24.0
# 1 NaN
# 2 68.0
# 3 68.0
# 4 30.0
# 5 30.0
# Name: age, dtype: float64
pad(), backfill()もあるが、バージョン2.0.0からDeprecated(非推奨)になっている。
fillna()の引数methodはバージョン2.1.0でDeprecated(非推奨)になった。バージョン2.1.4ではまだ使用可能だが、FutureWarningが出る。
print(s.fillna(method='ffill', limit=1))
# 0 24.0
# 1 24.0
# 2 NaN
# 3 68.0
# 4 68.0
# 5 30.0
# Name: age, dtype: float64
#
# /var/folders/rf/b7l8_vgj5mdgvghn_326rn_c0000gn/T/ipykernel_50534/2241812369.py:1: FutureWarning: Series.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead.