Social Data Science Courseware

7. 欠損値とデータ型

学習目標

  • isna() / notna() で欠損値の有無を確認できる
  • fillna() で欠損値を補完できる(固定値・平均・前方埋め)
  • dropna() で欠損値を含む行・列を除去できる
  • astype() で列のデータ型を変換できる

本文

欠損値は「データの正体不明」を表す

現実のデータには 「値が存在しない」「記録が取れなかった」「未回答」 といった状態が必ず出てきます。Excel ならセルが空欄、CSV なら値がない、アンケートなら「未回答」と書かれている。

pandas はこれを NaN(Not a Number) という特別な値で表します。NaN は単なる「空」ではなく、「あるべき値が無い」状態のマーカー です。集計関数(mean, sum など)はデフォルトで NaN を無視して計算してくれますが、そのままで意味のある結果になるとは限りません。欠損が多すぎる列をそのまま平均すると、サンプル数が見かけより少ない平均になり、ノイズが乗ります。

欠損とどう向き合うか(無視する/補完する/落とす)は、分析の質を左右する大事な判断です。

欠損値を検出する

まず欠損がどこに、どれくらいあるかを把握します:

df.isna()           # 全セルに True/False を返す DataFrame
df.isna().sum()     # 列ごとの欠損数
df.isna().any()     # 列ごとに「1 つでも欠損があるか」
df.notna()          # isna の逆

実務では df.isna().sum() を読み込み直後に必ず一回呼ぶのが定石です。「どの列がどれだけ欠けているか」が分かれば、次の対処法が決まります。

欠損値の補完戦略

欠損を 埋める落とす かは状況次第。代表的な補完戦略:

固定値で埋める

df["備考"] = df["備考"].fillna("なし")
df["年齢"] = df["年齢"].fillna(0)

文字列列なら「なし」「未回答」、数値列なら 0 や中央値で埋めるのが一般的。

統計量で埋める

df["年齢"] = df["年齢"].fillna(df["年齢"].mean())     # 平均
df["年齢"] = df["年齢"].fillna(df["年齢"].median())   # 中央値

平均は外れ値の影響を受けるので、分布が歪んでいるときは 中央値の方が安全 です。

前後の値で埋める(時系列向け)

df.fillna(method="ffill")   # forward fill:前の有効値で埋める
df.fillna(method="bfill")   # backward fill:後ろの有効値で埋める

時系列データで「観測が一時的に欠けた」場合、直前の値を引き継ぐ前方埋めが自然なことが多いです。

欠損値の行・列を落とす

「欠損があったら捨てる」も立派な戦略です:

df.dropna()                       # 1 つでも欠損がある行を全部落とす
df.dropna(subset=["年齢", "収入"])  # 指定列のどれかが欠損なら落とす
df.dropna(axis=1)                 # 列を落とす(普通は使わない)
df.dropna(thresh=3)               # 有効値が 3 個未満の行を落とす

ただし、dropna() を雑に使うと データの大半が消えてしまう ことがあります(特に多列で欠損がバラけている場合)。先に isna().sum() で全体像を見てから判断するのが安全です。

データ型を変換する

数値であるべき列が文字列として読み込まれることがあります(例:「1,000」のカンマ付き数字、ゼロ埋め郵便番号)。こうした列に集計を当てる前に、型を直す 必要があります。

df["年齢"] = df["年齢"].astype(int)        # 整数に
df["価格"] = df["価格"].astype(float)      # 浮動小数点に
df["郵便番号"] = df["郵便番号"].astype(str) # 文字列に(ゼロ埋め保持)

文字列から数値への変換でエラーが出る場合(カンマや単位記号が混じっている)、str.replace で先に整形してから astype する流れになります:

df["価格"] = df["価格"].str.replace(",", "").astype(int)

より柔軟な変換には pd.to_numeric が便利。エラー時の挙動を指定できます:

df["価格"] = pd.to_numeric(df["価格"], errors="coerce")
# errors="coerce" は変換できない値を NaN にする

欠損値と型のセットで考える

欠損値と型は 密接に関係 しています。

  • NaN は浮動小数点(float)の特殊値なので、NaN を含む整数列は 暗黙のうちに float になりますint64float64
  • 欠損を補完してから astype(int) で整数に戻す、というセットで進めることが多い
  • pandas 1.x 以降は Int64(大文字)型で「欠損を許容する整数」も使えるが、慣れるまでは float 経由が簡単

よく出る躓きどころ

  • 欠損を確認せずに mean() を呼ぶ — 結果が「サンプル数の少ない平均」になるが見た目には分からない
  • fillna(0) を機械的に使う — 0 が「存在する値」と混同される(年齢 0 は 0 歳と区別がつかない)。0 が意味を持つ列では避ける
  • dropna() で大量の行が消える — 必ず isna().sum() で確認してから
  • astype(int)NaN を含む列が変換できないintNaN を表せないため、先に補完するか Int64 を使う

サンプルコード

演習

users.csv を題材に、次を順に行いなさい。

  1. df.isna().sum() で各列の欠損数を表示する
  2. 「備考」列の欠損値を「なし」で埋める(fillna
  3. 「年齢」列を int から float に変換し、その後また int に戻して挙動を確認する
  4. 欠損のある行を dropna() で落とし、行数の変化を確認する

最後の dropna() を呼ぶ前と後で df.shape を比較すると、「欠損で行が大量に消える」場面のイメージがつかめます。

発展課題(オプション)

  • 数値列に対して、欠損を中央値で補完するコードを書く
  • pd.to_numeric(..., errors="coerce") を使って、文字列が混入した「数値であるはずの列」を整える
  • Int64 型(大文字)を試して、欠損を許容する整数列を作る