概要(pandas の欠損値処理は「検出→方針決定→削除/補完→型整備」の順で進める)
欠損値は NaN(Not a Number)や None で表現されます。集計・可視化・機械学習で誤差やエラーの原因になるため、「どこに、どれくらいあるか」を最初に把握し、列ごとの方針(削除か補完か)を決めてから処理します。重要なのは「isna/notna で検出」「dropna と fillna(ffill/bfill も)で対処」「to_numeric/to_datetime(errors=’coerce’) で安全に型整備」「空文字や番兵値(-1, 999 など)を NaN に正規化する」ことです。
欠損の基礎(検出と数え上げ)
欠損の検出と件数の集計
import pandas as pd
import numpy as np
df = pd.DataFrame({
"name": ["Taro", "Hanako", None],
"age": [25, np.nan, 30],
"height": [170, 165, np.nan]
})
# 欠損の位置(True が欠損)
mask = df.isna()
# 列ごとの欠損件数
print(df.isna().sum())
# 行ごとの欠損件数
print(df.isna().sum(axis=1))
Python欠損の「位置」と「量」をまず確認します。isna/notna は最初に覚えるべき基本ツールです。
欠損行の抽出とレポート用の表示
# いずれかの列が欠損の行
bad = df[df.isna().any(axis=1)]
print(bad)
# 特定列だけに注目
missing_age = df[df["age"].isna()]
Python全体の傾向を掴み、影響が大きい列(必須列)を特定します。
削除と補完(方針を列ごとに決める)
行を削除する(dropna)
# どれか欠損があれば行ごと削除
clean = df.dropna()
# 指定列に欠損がある行だけ削除
clean_subset = df.dropna(subset=["age", "height"])
# 欠損でないセルの最小数(閾値)を満たさない行を削除
clean_thresh = df.dropna(thresh=2) # 欠損でない値が2つ未満の行は削除
Python「重要列が欠損なら削除」「副次列なら残す」など、列ごとに基準を決めるのが安全です。
値で補完する(fillna)
import numpy as np
# 一律のデフォルト値
df["age"] = df["age"].fillna(0)
df["name"] = df["name"].fillna("Unknown")
# 列ごとに辞書で指定
df = df.fillna({"age": 0, "height": df["height"].median()})
Python数値列は平均/中央値/最頻値、カテゴリは「Unknown」などをよく使います。統計値で埋めると分布が崩れにくいです。
直前・直後の値で埋める(ffill/bfill)
# 時系列や連続観測で有効
df = df.sort_values("name") # 実務では日時でソート
df["height"] = df["height"].ffill() # 直前値で補完
df["height"] = df["height"].bfill() # 直後値で補完
Python連続性がある列は ffill/bfill が自然です。ソート順を必ず確認しましょう。
グループ単位で統計値補完(transform)
# 例:都市ごとに age の平均で補完
g = pd.DataFrame({"city": ["Tokyo","Tokyo","Osaka","Osaka"], "age":[25, np.nan, 30, np.nan]})
g["age"] = g["age"].fillna(g.groupby("city")["age"].transform("mean"))
Pythonカテゴリ内の代表値で埋めると、全体を平均で埋めるより歪みが減ります。
正規化と型整備(空文字・番兵値・安全な変換)
空文字や記号を NaN に正規化
import numpy as np
raw = pd.DataFrame({"score": ["10", "", "NA", "—", "30"]})
raw["score"] = raw["score"].replace(["", "NA", "—"], np.nan)
Pythonデータ源によっては空文字や特殊記号が欠損の意味を持ちます。最初に NaN 化しておくと後工程が楽になります。
番兵値(-1, 9999 など)を NaN 化
s = pd.Series([10, -1, 30, 9999])
s = s.replace({-1: np.nan, 9999: np.nan})
Python「存在しない」を数値で表した列は、解析前に NaN へ正規化しておくのが定石です。
数値・日付の安全変換(coerce)
# 壊れた値を NaN に落として型統一
df["age"] = pd.to_numeric(df["age"], errors="coerce")
df["date"] = pd.to_datetime(df["date"], errors="coerce", format="%Y-%m-%d")
Pythonerrors=”coerce” は「不正値を NaN にして進める」安全策。型が揃うと計算・抽出が安定します。
欠損を許容する整数型やカテゴリ型
df["age"] = df["age"].astype("Int64") # 欠損対応の整数型(Pandas拡張)
df["city"] = df["city"].astype("category")
PythonInt64 は NaN を許容する整数。category は軽量で、groupby/pivot との相性が良いです。
実務の設計ポイント(方針の明文化と品質担保)
欠損ポリシーを列ごとに決める
- 必須列: 欠損ならその行を削除(dropna(subset=…))。
- 重要数値列: 統計値(中央値/最頻値)で補完、またはグループ平均で補完。
- 説明用列(メモなど): 空文字に置換・そのままでも可。
処理前後の差分を記録する
- 件数: 処理前後で行数と欠損件数を比較。
- 影響範囲: どの列に何件補完したかをログに出す。
- 再現性: 補完値の根拠(中央値、グループ平均など)をコードで明示。
解析・可視化時の注意
- 集計関数: mean/sum は NaN を無視(結果は期待通りが多い)。count は NaN を除いた件数、size は総行数。
- グラフ: 欠損は線が途切れることがある。補完か除外を先に決める。
例題で身につける(定番から一歩先まで)
例題1:欠損の検出→削除→補完の基本線
import pandas as pd
import numpy as np
df = pd.DataFrame({
"name": ["A","B","C","D"],
"age": [25, np.nan, 30, np.nan],
"score": ["10", "", "x", "20"]
})
# 1) 検出
print("missing per col:\n", df.isna().sum())
# 2) 正規化(空文字・不正値)
df["score"] = df["score"].replace(["", "x"], np.nan)
df["score"] = pd.to_numeric(df["score"], errors="coerce")
# 3) 方針:age は中央値、score は0で補完
df["age"] = df["age"].fillna(df["age"].median())
df["score"] = df["score"].fillna(0)
print(df)
Python例題2:時系列データを ffill/bfill で滑らかに
import pandas as pd
import numpy as np
ts = pd.DataFrame({
"date": pd.date_range("2025-01-01", periods=6, freq="D"),
"value": [1.2, np.nan, 1.3, np.nan, np.nan, 1.5]
}).set_index("date").sort_index()
ts["value_ffill"] = ts["value"].ffill()
ts["value_bfill"] = ts["value"].bfill()
print(ts)
Python例題3:グループ平均による補完(カテゴリ別)
import pandas as pd
import numpy as np
g = pd.DataFrame({
"city": ["Tokyo","Tokyo","Osaka","Osaka","Nagoya"],
"amount": [120, np.nan, 150, np.nan, 90]
})
g["amount"] = g["amount"].fillna(g.groupby("city")["amount"].transform("mean"))
print(g)
Python例題4:削除基準を厳格に(必須列が欠損なら落とす)
import pandas as pd
import numpy as np
df = pd.DataFrame({
"id": [1,2,3,4],
"email": ["a@example.com", None, "c@example.com", ""],
"amount": [10.0, 20.0, np.nan, 5.0]
})
# 空文字も欠損扱いに正規化
df["email"] = df["email"].replace("", np.nan)
# 必須列: email が欠損なら削除
df = df.dropna(subset=["email"])
# amount は欠損なら0で補完
df["amount"] = df["amount"].fillna(0.0)
print(df)
Pythonまとめ
欠損値処理は「検出→方針決定→削除/補完→型整備」の順で進めるのが最短で安全です。isna/notna と sum/any で状況把握、dropna と fillna(ffill/bfill)で対処、to_numeric/to_datetime(errors=’coerce’) と Int64/category で型を揃える。空文字や番兵値は最初に NaN に正規化し、重要列は削除か統計値補完、時系列は ffill/bfill、カテゴリはグループ統計での補完が有効。処理前後の件数や補完根拠を記録して再現性を保てば、初心者でも実務品質の欠損対応が書けます。
