pandasでNaN(欠損値)を正しく扱う方法|初心者向け完全ガイド

python

Pythonでデータ分析をしていると、必ずといっていいほど出会うのが「NaN(ナン)」という値です。「計算がうまくいかない」「グラフが変になった」という経験はありませんか?

この記事では、pandasでのNaN処理を基本から応用まで、実際のコード例とともにわかりやすく解説します。

スポンサーリンク

NaNって何?データ分析での欠損値の意味

NaNの正体を理解しよう

「NaN」とは「Not a Number」の略で、「数値ではない」という意味です。簡

単にいうと、「データがない」「不明」「空っぽ」を表す特別な値なのです。

pandasでは、このような場面でNaNが登場します:

import pandas as pd
import numpy as np

# サンプルデータを作ってみよう
df = pd.DataFrame({
    '名前': ['田中', '鈴木', None],  # Noneは自動的にNaNになる
    '年齢': [25, np.nan, 30],        # np.nanも明示的にNaN
    '身長': [170, 165, np.nan]
})

print(df)

このコードを実行すると、Nonenp.nanが入力された部分はNaNとして表示されます。

なぜNaNが発生するの?

実際のデータ分析では、次のような理由でNaNが発生します:

よくある原因

  • CSVファイルに空白セルがある
  • アンケートで「未回答」や「無効回答」がある
  • センサーの故障でデータが取得できない
  • 計算で無効な結果が出る(例:0で割る計算)

具体例で見てみよう

# 実際のデータっぽい例
df_real = pd.DataFrame({
    'ユーザーID': [1, 2, 3, 4],
    '年収': [400, np.nan, 600, 350],  # 年収未回答
    '年齢': [25, 30, np.nan, 28],     # 年齢未回答
    '地域': ['東京', '大阪', '福岡', np.nan]  # 地域未記入
})

このように、実際のデータには必ずといっていいほどNaNが含まれています。

NaNを見つける方法|欠損値の検出テクニック

データにNaNがあるかどうかを確認する方法を覚えましょう。

基本的な検出方法

全体の欠損状況を確認

# NaNがある場所をTrue/Falseで表示
print(df.isna())

# 各列の欠損数を数える
print(df.isna().sum())

より詳しい情報を取得

# データの基本情報を表示(欠損数も含む)
print(df.info())

# 欠損率を計算
missing_rate = (df.isna().sum() / len(df)) * 100
print(f"欠損率: {missing_rate}%")

欠損がある行を抽出する

# 年齢が欠損している行だけを表示
age_missing = df[df['年齢'].isna()]
print(age_missing)

# どこかの列に欠損がある行をすべて表示
any_missing = df[df.isna().any(axis=1)]
print(any_missing)

# すべての列に欠損がない行だけを表示
no_missing = df[df.notna().all(axis=1)]
print(no_missing)

これらの方法を使えば、データのどこに問題があるかを素早く把握できます。

NaNを処理する3つの方法

NaNが見つかったら、次は処理方法を選択します。主に3つのアプローチがあります。

方法1:削除する

メリット: シンプルで確実 デメリット: データが減ってしまう

# 欠損がある行をすべて削除
df_dropped = df.dropna()

# 特定の列だけを対象にして削除
df_age_dropped = df.dropna(subset=['年齢'])

# 欠損がある列を削除
df_col_dropped = df.dropna(axis=1)

方法2:補完する(埋める)

メリット: データ量を維持できる デメリット: 推測に基づくため、精度に影響する可能性

# 平均値で補完
df['年齢'].fillna(df['年齢'].mean(), inplace=True)

# 中央値で補完
df['年齢'].fillna(df['年齢'].median(), inplace=True)

# 固定値で補完
df['年齢'].fillna(0, inplace=True)

# 前の値で補完
df.fillna(method='ffill', inplace=True)

# 後の値で補完
df.fillna(method='bfill', inplace=True)

方法3:特別な値として扱う

メリット: 「データがない」という情報も活用できる デメリット: 後の処理で特別な配慮が必要

# NaNを特別な値(-1など)に変換
df['年齢'].fillna(-1, inplace=True)

# カテゴリ変数の場合は「不明」などに変換
df['地域'].fillna('不明', inplace=True)

NaNがあるときの計算の注意点

NaNがあると、計算結果が予想と違うことがあります。

基本的な計算の挙動

# サンプルデータ
ages = pd.Series([25, np.nan, 30, 35])

# 多くの関数はNaNを自動的に無視します
print(f"合計: {ages.sum()}")      # 90 (NaNは無視)
print(f"平均: {ages.mean()}")     # 30.0 (NaNは無視)
print(f"最大: {ages.max()}")      # 35 (NaNは無視)

# ただし、データ数にはNaNも含まれます
print(f"データ数: {len(ages)}")   # 4 (NaNも数える)
print(f"有効数: {ages.count()}")  # 3 (NaNは数えない)

NaNとの比較は特殊

# NaN同士は「等しくない」と判定される
print(np.nan == np.nan)  # False

# NaNかどうかを調べるときは isna() を使う
print(pd.isna(np.nan))   # True

# 間違った書き方
wrong_filter = df[df['年齢'] == np.nan]  # 何も取得できない

# 正しい書き方
correct_filter = df[df['年齢'].isna()]   # NaNの行を取得

よくあるミスと解決方法

ミス1:空文字とNaNを混同する

# 問題のあるデータ
df_messy = pd.DataFrame({
    '名前': ['田中', '', '鈴木', np.nan]
})

# 空文字('')とNaNは別物
print(df_messy.isna().sum())  # 1つだけ(np.nanのみ)

# 空文字をNaNに統一
df_messy['名前'].replace('', np.nan, inplace=True)
print(df_messy.isna().sum())  # 2つ(空文字もNaNになった)

ミス2:fillnaが効かない

# 間違った書き方(元のDataFrameは変更されない)
df['年齢'].fillna(0)
print(df)  # まだNaNが残っている

# 正しい書き方1: inplace=True を使う
df['年齢'].fillna(0, inplace=True)

# 正しい書き方2: 結果を代入する
df['年齢'] = df['年齢'].fillna(0)

ミス3:型の不整合

# 数値列に文字列で補完すると型が変わる
df['年齢'].fillna('不明', inplace=True)
print(df.dtypes)  # 年齢がobject型(文字列型)になってしまう

# 解決策:事前に型を統一するか、適切な値で補完
df['年齢'].fillna(-1, inplace=True)  # 数値で補完

まとめ

pandasでのNaN処理は、データ分析の基本中の基本です。

覚えておきたいポイント

  1. NaNの検出: isna()sum()info()を活用
  2. 処理方法の選択: 削除・補完・特別値のどれが適切か考える
  3. 計算時の注意: NaNは自動的に無視されることが多い
  4. 比較の注意: == np.nanではなくisna()を使う

コメント

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