コンテンツにスキップ

単元 7:欠損値とデータ型

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

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

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

現実のデータには 「値が存在しない」「記録が取れなかった」「未回答」 といった状態が必ず出てきます。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()) # 中央値

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

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

Section titled “前後の値で埋める(時系列向け)”
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 型(大文字)を試して、欠損を許容する整数列を作る