Python | ファイル・OS 操作:pandas の欠損値処理

Python Python
スポンサーリンク

概要(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")
Python

errors=”coerce” は「不正値を NaN にして進める」安全策。型が揃うと計算・抽出が安定します。

欠損を許容する整数型やカテゴリ型

df["age"] = df["age"].astype("Int64")   # 欠損対応の整数型(Pandas拡張)
df["city"] = df["city"].astype("category")
Python

Int64 は 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、カテゴリはグループ統計での補完が有効。処理前後の件数や補完根拠を記録して再現性を保てば、初心者でも実務品質の欠損対応が書けます。

タイトルとURLをコピーしました