第2回 データの前処理
はじめに
本シリーズ「Pythonで学ぶデータ分析実践」の第2回では、データの前処理について解説します。
データ分析において、前処理は全工程の60〜80%を占めるとも言われる重要な作業です。どれだけ優れた分析手法を使っても、入力データの品質が低ければ正確な結果は得られません(Garbage In, Garbage Out)。本記事では、Pythonを使った具体的な前処理手法を、実践的なコード例とともに解説します。
シリーズ全体の構成(全12回)
| 回 | テーマ |
|---|---|
| 第1回 | データ分析とは |
| 第2回 | データの前処理(本記事) |
| 第3回 | データの把握 |
| 第4回 | 相関分析 |
| 第5回 | 統計的推定 |
| 第6回 | 統計的検定 |
| 第7回 | 分散分析 |
| 第8回 | 回帰分析 |
| 第9回 | 時系列データ分析 |
| 第10回 | クラス分類 |
| 第11回 | クラスタリング |
| 第12回 | 次元削減 |
2-1. 前処理とは
データの前処理とは、データに含まれる欠損値や外れ値を処理する作業のことで、一般的にはデータクレンジングとも呼ばれます。収集したデータをそのまま分析に使用すると、計算エラーが発生したり、分析結果の精度が低下したりする可能性があるため、事前にデータを整形する必要があります。
前処理で実施する主な作業
| 作業 | 目的 |
|---|---|
| 欠損値の対処 | 値が存在しないデータを補完または除去する |
| 外れ値の対処 | 極端に大きい/小さい値を検出し対処する |
| 型変換 | 計算可能なデータ型に統一する |
| ダミー変数化 | カテゴリカルデータを数値に変換する |
| データスケーリング | 特徴量のスケールを統一する |
| 表記ゆれの修正 | 同一の意味を持つ異なる表記を統一する |
| 新しい列の作成 | 分析に有用な特徴量を生成する |
| 不要な行・列の削除 | 分析に不要なデータを除去する |
前処理の全体フロー
データ読み込み → データ確認 → 不要列削除 → 欠損値処理 → 外れ値処理
→ 型変換 → 表記ゆれ修正 → ダミー変数化 → スケーリング → 分析・学習
前処理前の確認作業
前処理に取りかかる前に、まずデータの全体像を把握することが重要です。
import pandas as pd
import numpy as np
# データの読み込み
df = pd.read_csv("sample_data.csv")
# 基本情報の確認
print(df.shape) # 行数・列数
print(df.dtypes) # 各列のデータ型
print(df.info()) # 欠損値の状況を含む情報
print(df.describe()) # 基本統計量
print(df.head(10)) # 先頭10行
2-2. CSVの読み込み
データ分析では、CSVファイルからデータを読み込むケースが最も一般的です。PandasのDataFrameとしてデータを扱うことで、効率的にデータ操作を行うことができます。
基本的な読み込み
import pandas as pd
# 基本的なCSV読み込み
df = pd.read_csv("data.csv")
# エンコーディングを指定(日本語を含む場合)
df = pd.read_csv("data.csv", encoding="utf-8")
df = pd.read_csv("data.csv", encoding="shift_jis")
# ヘッダーがない場合
df = pd.read_csv("data.csv", header=None, names=["col1", "col2", "col3"])
# 特定の列だけ読み込む
df = pd.read_csv("data.csv", usecols=["name", "age", "salary"])
# インデックス列を指定
df = pd.read_csv("data.csv", index_col="id")
さまざまなデータソースからの読み込み
# Excelファイル
df = pd.read_excel("data.xlsx", sheet_name="Sheet1")
# TSV(タブ区切り)
df = pd.read_csv("data.tsv", sep="\t")
# JSON
df = pd.read_json("data.json")
# クリップボード(Excelからコピーしたデータ)
df = pd.read_clipboard()
大きなファイルの読み込み
# チャンク(分割)で読み込み
for chunk in pd.read_csv("large_data.csv", chunksize=10000):
# チャンクごとに処理
process(chunk)
# データ型を指定してメモリ削減
df = pd.read_csv("data.csv", dtype={
"id": "int32",
"category": "category",
"value": "float32"
})
2-3. 欠損値
「欠損値」とは、データのうち一部に値が存在しないことを指します。Pandasでは欠損値をNaN(Not a Number)として表現し、Pythonの組み込みではNoneとして扱います。
欠損値が含まれると、機械学習の計算にエラーが発生する場合があるため、事前にデータを整形して欠損値を処理する必要があります。
欠損値の確認
import pandas as pd
import numpy as np
# サンプルデータの作成
df = pd.DataFrame({
"name": ["田中", "鈴木", None, "佐藤", "山田"],
"age": [25, np.nan, 30, 28, np.nan],
"salary": [300000, 450000, np.nan, 350000, 280000],
"department": ["営業", "開発", "営業", None, "開発"]
})
# 欠損値の確認
print(df.isnull()) # 各セルがNaNかどうか(True/False)
print(df.isnull().sum()) # 列ごとの欠損値の数
print(df.isnull().sum().sum()) # 全体の欠損値の合計
# 欠損値の割合を確認
print(df.isnull().mean() * 100) # 列ごとの欠損割合(%)
欠損値の対処方法
欠損値の対処方法は大きく削除と補完の2つがあります。
方法1:削除
# 欠損値を含む行を削除
df_dropped = df.dropna()
# 特定の列に欠損がある行のみ削除
df_dropped = df.dropna(subset=["age", "salary"])
# 全ての値が欠損の行を削除
df_dropped = df.dropna(how="all")
# 欠損値が一定割合以上の列を削除(70%以上欠損している列を削除)
threshold = len(df) * 0.3
df_dropped = df.dropna(axis=1, thresh=threshold)
方法2:補完(穴埋め)
# 固定値で補完
df["age"] = df["age"].fillna(0)
# 平均値で補完
df["age"] = df["age"].fillna(df["age"].mean())
# 中央値で補完
df["salary"] = df["salary"].fillna(df["salary"].median())
# 最頻値で補完
df["department"] = df["department"].fillna(df["department"].mode()[0])
# 前の値で補完(前方補完)
df["age"] = df["age"].fillna(method="ffill")
# 後ろの値で補完(後方補完)
df["age"] = df["age"].fillna(method="bfill")
# 線形補間
df["age"] = df["age"].interpolate(method="linear")
欠損値の対処方法の選択基準
| 状況 | 推奨する対処法 |
|---|---|
| 欠損が少ない(5%未満) | 行の削除 |
| 欠損が中程度(5〜30%) | 平均値・中央値・最頻値で補完 |
| 欠損が多い(30%以上) | 列の削除を検討、または高度な補完手法 |
| 時系列データ | 前方補完・後方補完・線形補間 |
| カテゴリカルデータ | 最頻値で補完、または「不明」カテゴリを作成 |
注意点
- 欠損値を削除するとデータ量が減少し、分析の精度に影響する可能性がある
- 平均値で補完するとデータの分散が小さくなる傾向がある
- 欠損の発生パターン(ランダムか、特定条件で発生するか)を確認することが重要
2-4. 重複データ
重複データとは、同一の内容が複数行にわたって記録されているデータです。重複が含まれると集計結果に偏りが生じるため、前処理で適切に対応する必要があります。
重複データの確認
import pandas as pd
df = pd.DataFrame({
"name": ["田中", "鈴木", "田中", "佐藤", "鈴木"],
"age": [25, 30, 25, 28, 30],
"city": ["東京", "大阪", "東京", "名古屋", "大阪"]
})
# 重複行の確認
print(df.duplicated()) # 重複している行をTrue/Falseで表示
print(df.duplicated().sum()) # 重複行の数
# 特定の列で重複を確認
print(df.duplicated(subset=["name"]))
重複データの削除
# 完全一致する重複行を削除(最初の行を残す)
df_unique = df.drop_duplicates()
# 最後の行を残す
df_unique = df.drop_duplicates(keep="last")
# 特定の列を基準に重複削除
df_unique = df.drop_duplicates(subset=["name"])
# 全ての重複行を削除(最初の行も含めて)
df_unique = df.drop_duplicates(keep=False)
重複データが発生する原因
- データ入力時のミス(二重登録)
- 複数のデータソースを統合した際の重複
- システムのバグによる二重書き込み
- スクレイピング時の重複取得
2-5. 外れ値
「外れ値」とは、データの範囲から外れた極端に大きな値または小さな値を指します。外れ値には以下のような特徴があります。
- データの大多数から著しく離れているが、必ずしも誤りとは限らない
- 自然な変動や特殊な条件下で発生する可能性がある
- 統計的手法(四分位範囲など)を用いて検出することが多い
- 標本数が少ない場合、分析結果に大きな影響を与える
外れ値と異常値の違い
| 項目 | 外れ値 | 異常値 |
|---|---|---|
| 定義 | 他のデータから著しく離れた値 | 明らかな誤りを含む値 |
| 原因 | 自然な変動、特殊条件 | 測定機器の故障、人為的ミス、記録エラー |
| 例 | 年収2000万円(高所得者) | 年齢が-5歳、室温200℃ |
| 対処 | 除外するか慎重に判断 | 発見次第、修正または除外 |
外れ値の検出方法
方法1:四分位範囲(IQR)法
最も一般的な外れ値検出方法です。第1四分位数(Q1)と第3四分位数(Q3)の差(IQR)を使い、Q1 - 1.5×IQR 未満または Q3 + 1.5×IQR を超える値を外れ値とみなします。
import pandas as pd
import numpy as np
df = pd.DataFrame({
"value": [10, 12, 14, 15, 13, 11, 100, 14, 12, 13, -20]
})
# IQR法による外れ値検出
Q1 = df["value"].quantile(0.25)
Q3 = df["value"].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
print(f"下限: {lower_bound}, 上限: {upper_bound}")
# 外れ値の検出
outliers = df[(df["value"] < lower_bound) | (df["value"] > upper_bound)]
print(f"外れ値:\n{outliers}")
# 外れ値を除外
df_clean = df[(df["value"] >= lower_bound) & (df["value"] <= upper_bound)]
方法2:Zスコア法
データの平均と標準偏差を用いて、平均から標準偏差の何倍離れているかを計算します。一般的に|Z| > 3 の値を外れ値とします。
from scipy import stats
z_scores = stats.zscore(df["value"])
outliers = df[np.abs(z_scores) > 3]
print(f"外れ値:\n{outliers}")
外れ値の可視化(箱ひげ図)
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 4))
plt.boxplot(df["value"], vert=False)
plt.xlabel("値")
plt.title("箱ひげ図による外れ値の可視化")
plt.show()
外れ値の対処方法
# 方法1:外れ値を含む行を削除
df_clean = df[(df["value"] >= lower_bound) & (df["value"] <= upper_bound)]
# 方法2:外れ値を上限・下限値で置き換え(クリッピング)
df["value"] = df["value"].clip(lower=lower_bound, upper=upper_bound)
# 方法3:外れ値を中央値で置き換え
median_val = df["value"].median()
df.loc[(df["value"] < lower_bound) | (df["value"] > upper_bound), "value"] = median_val
# 方法4:対数変換で外れ値の影響を軽減
df["value_log"] = np.log1p(df["value"]) # log(1+x)変換
外れ値対処の判断基準
- 除外する場合 — 明らかな異常値(入力ミス、機器故障など)
- 残す場合 — ビジネス上意味のある値(高額購入者、特殊イベントなど)
- 変換する場合 — データの分布を正規化したい時
外れ値は重要な情報を含んでいる可能性があります。安易に除外せず、なぜその値が発生したかを考察することが重要です。
2-6. 型変換
Pythonは動的型付け言語であるため、型はオブジェクトが定義された時点で決まります。NumPyでは全てのデータ型が統一されている必要がありますが、PandasのDataFrameでは列ごとに異なるデータ型を持つことができます。
ただし、データ型が適切でない場合、計算が除外されたりエラーが発生したりするため、一般的には列ごとにデータ型を揃えるようにします。
Pythonの主要データ型
| Pythonの型 | 説明 | Pandasのdtype |
|---|---|---|
| int | 符号あり整数型 | int64 |
| float | 倍精度浮動小数点型 | float64 |
| str | 文字列型 | object |
| bool | 真偽値型 | bool |
| datetime | 日付時刻型 | datetime64 |
データ型の確認と変換
import pandas as pd
df = pd.DataFrame({
"id": ["1", "2", "3", "4", "5"],
"price": ["1000", "2000", "3000", "N/A", "5000"],
"date": ["2024-01-01", "2024-01-02", "2024-01-03", "2024-01-04", "2024-01-05"],
"is_active": [1, 0, 1, 1, 0]
})
# データ型の確認
print(df.dtypes)
# 文字列 → 整数型
df["id"] = df["id"].astype(int)
# 文字列 → 浮動小数点型(エラー値を含む場合)
df["price"] = pd.to_numeric(df["price"], errors="coerce") # 変換できない値はNaNになる
# 文字列 → 日付型
df["date"] = pd.to_datetime(df["date"])
# 整数 → 真偽値型
df["is_active"] = df["is_active"].astype(bool)
print(df.dtypes)
よくある型変換パターン
# カテゴリ型に変換(メモリ効率向上)
df["category"] = df["category"].astype("category")
# 日付から年・月・日を抽出
df["year"] = df["date"].dt.year
df["month"] = df["date"].dt.month
df["day_of_week"] = df["date"].dt.dayofweek # 0=月曜, 6=日曜
# 数値を文字列に変換
df["id_str"] = df["id"].astype(str)
型変換時の注意点
-
astype()は変換できない値があるとエラーになる →pd.to_numeric(errors="coerce")を使う - NaN(欠損値)が含まれる列はint型に変換できない →
Int64(nullable integer)を使う - 日付形式が統一されていない場合は
formatを指定する
# NaN含みの整数列
df["age"] = df["age"].astype("Int64") # Nullable Integer型
# 日付フォーマット指定
df["date"] = pd.to_datetime(df["date"], format="%Y/%m/%d")
2-7. ダミー変数
項目やラベルを区別するために与えられる文字列や数値の集合をカテゴリカルデータ(質的データ)と呼びます。カテゴリカルデータが文字列の場合、多くの機械学習アルゴリズムはそのまま扱えないため、数値に変換する必要があります。この変換をダミー変数化(データエンコーディング)と呼びます。
エンコード手法の比較
| 項目 | One-Hotエンコード | 順序ラベルエンコード |
|---|---|---|
| 適用条件 | カテゴリ間に順序がない場合 | カテゴリ間に明確な順序がある場合 |
| 方法 | 各カテゴリに0または1を割り当て | 順序に応じた数値ラベルを割り当て |
| メリット | カテゴリ間の誤った相関を防ぐ | データの次元数が増えず計算コストが低い |
| デメリット | カテゴリ数が多いと次元が増大 | 順序のないデータに適用すると誤った相関を導入 |
| 例 | 血液型(A, B, O, AB) | 学歴(中学 < 高校 < 大学 < 大学院) |
One-Hotエンコード
import pandas as pd
df = pd.DataFrame({
"name": ["田中", "鈴木", "佐藤", "山田"],
"blood_type": ["A", "B", "O", "AB"],
"city": ["東京", "大阪", "東京", "名古屋"]
})
# pd.get_dummies() によるOne-Hotエンコード
df_encoded = pd.get_dummies(df, columns=["blood_type", "city"])
print(df_encoded)
実行結果のイメージ:
| name | blood_type_A | blood_type_AB | blood_type_B | blood_type_O | city_名古屋 | city_大阪 | city_東京 |
|---|---|---|---|---|---|---|---|
| 田中 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
| 鈴木 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
| 佐藤 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |
| 山田 | 0 | 1 | 0 | 0 | 1 | 0 | 0 |
ダミー変数トラップの回避
One-Hotエンコードでは、1つのカテゴリを基準(ベースライン)として除外することで、多重共線性(ダミー変数トラップ)を回避できます。
# drop_first=True で1列を削除(ダミー変数トラップ回避)
df_encoded = pd.get_dummies(df, columns=["blood_type"], drop_first=True)
順序ラベルエンコード
from sklearn.preprocessing import OrdinalEncoder
df = pd.DataFrame({
"education": ["高校", "大学", "大学院", "中学", "大学"]
})
# 順序を定義
order = [["中学", "高校", "大学", "大学院"]]
encoder = OrdinalEncoder(categories=order)
df["education_encoded"] = encoder.fit_transform(df[["education"]])
print(df)
# 中学=0, 高校=1, 大学=2, 大学院=3
LabelEncoderとの違い
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df["city_encoded"] = le.fit_transform(df["city"])
# アルファベット順に数値が割り当てられる(順序に意味はない)
LabelEncoderは順序のないカテゴリに数値を割り当てるため、回帰分析など「数値の大小に意味がある」と解釈される手法では注意が必要です。基本的にはOne-Hotエンコードを使う方が安全です。
2-8. データスケーリング
複数の特徴量があり、それぞれの尺度が異なる場合は、単位を揃えることで分析精度を上げることができます。この尺度を揃える処理をスケーリングと呼びます。
なぜスケーリングが必要か
例えば、「年齢(0〜100)」と「年収(200万〜2000万)」を同時に使って分析する場合、年収の方がスケールが大きいため、分析結果に対して年収の影響が過大に評価されてしまいます。スケーリングにより、すべての特徴量が平等に評価されるようになります。
スケーリングが特に重要なアルゴリズム:
- k-means(距離ベース)
- SVM(サポートベクターマシン)
- k-NN(k近傍法)
- 主成分分析(PCA)
- ニューラルネットワーク
スケーリングが不要なアルゴリズム:
- 決定木
- ランダムフォレスト
- 勾配ブースティング
正規化(Min-Max法)
特定の特徴量を0〜1の範囲内に収めるスケーリング手法です。
$$x_{normalized} = \frac{x - x_{min}}{x_{max} - x_{min}}$$
from sklearn.preprocessing import MinMaxScaler
import pandas as pd
import numpy as np
df = pd.DataFrame({
"age": [25, 30, 35, 40, 45],
"salary": [3000000, 4500000, 5000000, 7000000, 8000000]
})
# MinMaxScalerによる正規化
scaler = MinMaxScaler()
df_normalized = pd.DataFrame(
scaler.fit_transform(df),
columns=df.columns
)
print(df_normalized)
正規化が適している場合:
- データが一様分布に近い場合
- 特定の範囲内にデータを収める必要がある場合
- 画像データのピクセル値(0〜255 → 0〜1)
標準化(Zスコア標準化)
特徴量を平均0、標準偏差1になるようスケーリングする手法です。
$$x_{standardized} = \frac{x - \mu}{\sigma}$$
from sklearn.preprocessing import StandardScaler
# StandardScalerによる標準化
scaler = StandardScaler()
df_standardized = pd.DataFrame(
scaler.fit_transform(df),
columns=df.columns
)
print(df_standardized)
標準化が適している場合:
- データが正規分布に近い場合
- 外れ値が存在する場合(Min-Maxより影響を受けにくい)
- 異なるスケールの特徴量を統一的に扱いたい場合
正規化と標準化の比較
| 項目 | 正規化(Min-Max) | 標準化(Zスコア) |
|---|---|---|
| 変換後の範囲 | 0〜1 | 平均0、標準偏差1 |
| 外れ値の影響 | 受けやすい | 受けにくい |
| 適した分布 | 一様分布 | 正規分布 |
| 使用クラス | MinMaxScaler | StandardScaler |
RobustScaler(外れ値に強いスケーリング)
外れ値の影響を受けにくいスケーリング手法です。中央値と四分位範囲(IQR)を使用します。
from sklearn.preprocessing import RobustScaler
scaler = RobustScaler()
df_robust = pd.DataFrame(
scaler.fit_transform(df),
columns=df.columns
)
2-9. 学習データとテストデータ
データ分析や機械学習では、データを学習データ(訓練データ)とテストデータに分割することが重要です。これにより、構築したモデルが未知のデータに対してどれだけ正確に予測できるかを評価できます。
機械学習の目的は未知のデータに対する予測精度を高めることです。学習データに過剰に適合したモデルは未知データに対して精度が低くなる可能性があるため、データセットを訓練用とテスト用に分割することでモデルの汎化性能を正しく評価します。
なぜデータを分割するのか
全データを使ってモデルを構築すると、そのデータに対しては高い精度を示しますが、新しいデータに対しては精度が低下する可能性があります。この現象を**過学習(オーバーフィッティング)**と呼びます。
データを分割して、テストデータで評価することで、モデルの汎化性能(未知のデータへの対応力)を確認できます。
ホールドアウト法
ホールドアウト法は、データセットをランダムに学習用と評価用の2つに分割し、一方で学習・もう一方で評価を行う最も基本的な手法です。
学習用データ(X_train, y_train)→ 予測モデルを作成
↓
評価用データ(X_test)→ モデルに適用して予測値を計算
↓
予測値と正解データ(y_test)を照合 → 精度を評価
train_test_splitのパラメータ
scikit-learnのsklearn.model_selection.train_test_splitメソッドでデータ分割を行います。
| パラメータ | デフォルト値 | 意味 |
|---|---|---|
| arrays | — | 分割対象のデータ(リスト、NumPy配列、DataFrame) |
| test_size | 0.25 | テストデータの割合(0.0〜1.0)または件数(整数) |
| train_size | None | 学習データの割合または件数 |
| random_state | None | 乱数シード(再現性のため固定推奨) |
| shuffle | True | 分割前にデータをランダムに並び替えるか |
| stratify | None | 層化サンプリングを行う場合にクラスラベルを指定 |
基本的なデータ分割
from sklearn.model_selection import train_test_split
import pandas as pd
df = pd.read_csv("data.csv")
# 特徴量(X)と目的変数(y)の分離
X = df.drop("target", axis=1)
y = df["target"]
# データの分割(学習:テスト = 8:2)
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2, # テストデータの割合
random_state=42 # 再現性のための乱数シード
)
print(f"学習データ: {X_train.shape[0]}件")
print(f"テストデータ: {X_test.shape[0]}件")
分割比率の目安
| データ量 | 学習:テスト | 備考 |
|---|---|---|
| 少ない(〜1000件) | 7:3 | テストデータを十分確保 |
| 中程度(1000〜10000件) | 8:2 | 一般的な分割比率 |
| 多い(10000件〜) | 9:1 | 学習データを十分確保 |
学習データ・検証データ・テストデータの3分割
より厳密な評価を行う場合は、データを3つに分割します。
# まず学習データとテストデータに分割
X_train_full, X_test, y_train_full, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 学習データをさらに学習データと検証データに分割
X_train, X_val, y_train, y_val = train_test_split(
X_train_full, y_train_full, test_size=0.25, random_state=42
)
# 結果: 学習60%、検証20%、テスト20%
print(f"学習データ: {X_train.shape[0]}件")
print(f"検証データ: {X_val.shape[0]}件")
print(f"テストデータ: {X_test.shape[0]}件")
| データ | 用途 |
|---|---|
| 学習データ(Train) | モデルの構築に使用 |
| 検証データ(Validation) | ハイパーパラメータの調整に使用 |
| テストデータ(Test) | 最終的なモデルの評価に使用 |
層化抽出(Stratified Split)
分類問題で目的変数のクラス比率に偏りがある場合、stratifyパラメータを指定することで、学習データとテストデータのクラス比率を元データと同じに保つことができます。
# 層化抽出を使った分割
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
random_state=42,
stratify=y # 目的変数の比率を維持
)
交差検証(Cross Validation)
データ量が少ない場合、1回の分割だけでは評価が不安定になります。k分割交差検証を使うことで、より信頼性の高い評価が可能です。
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LinearRegression
model = LinearRegression()
# 5分割交差検証
scores = cross_val_score(model, X, y, cv=5, scoring="r2")
print(f"各分割のスコア: {scores}")
print(f"平均スコア: {scores.mean():.4f} ± {scores.std():.4f}")
補足:その他の前処理テクニック
表記ゆれの修正
表記ゆれの修正は実務で非常に重要なポイントです。同じ意味を持つデータが異なる表記で記録されていると、正しい集計や分析ができません。
よくある表記ゆれパターン
| パターン | 例 |
|---|---|
| 全角・半角 | 「A」と「A」、「1」と「1」 |
| 大文字・小文字 | 「Tokyo」と「tokyo」と「TOKYO」 |
| 価格表示 | 「1万円」「10000円」「10,000」「¥10,000」 |
| 空値の表現 | 「なし」「-」「0」「ブランク」「N/A」 |
| スペース | 「田中 太郎」「田中 太郎」「田中太郎」 |
import pandas as pd
df = pd.DataFrame({
"city": ["東京", "とうきょう", "Tokyo", "TOKYO", "東京都"]
})
# 文字列の統一
df["city"] = df["city"].str.strip() # 前後の空白除去
df["city"] = df["city"].str.lower() # 小文字に統一
df["city"] = df["city"].str.replace(" ", "") # 全角スペース除去
# 置換マッピングによる統一
mapping = {
"とうきょう": "東京",
"tokyo": "東京",
"東京都": "東京"
}
df["city"] = df["city"].replace(mapping)
# 全角→半角変換
import unicodedata
df["value"] = df["value"].apply(lambda x: unicodedata.normalize("NFKC", str(x)))
新しい列の作成(特徴量エンジニアリング)
収集したデータのままでは分析しにくいケースがあります。その場合、適切な切り口でデータを加工し、新たな列を作成します。
import pandas as pd
df = pd.DataFrame({
"year": [2024, 2024, 2024],
"month": [1, 2, 3],
"day": [15, 20, 10],
"birth_year": [1990, 1985, 1995],
"text": ["購入しました", "検討中です", "返品希望"]
})
# 年・月・日を合成して日付列を作成
df["date"] = pd.to_datetime(df[["year", "month", "day"]])
# 日付から曜日を作成
df["day_of_week"] = df["date"].dt.day_name()
# 年齢を計算
df["age"] = 2024 - df["birth_year"]
# テキストデータを数値フラグに変換
df["is_purchase"] = df["text"].str.contains("購入").astype(int)
df["is_return"] = df["text"].str.contains("返品").astype(int)
不要な行や列の削除
分析に不要な列や行は削除します。不必要なデータがあると、データ取り込みや計算処理に余計な負荷がかかります。
# 不要な列の削除
df = df.drop(columns=["id", "created_at", "updated_at"])
# 条件に基づく行の削除
df = df[df["status"] != "deleted"] # 削除済みデータを除外
# 特定の値の行を削除
df = df[df["age"] > 0] # 年齢が0以下の行を除外
まとめ
本記事では、データの前処理について以下の内容を解説しました。
| 項目 | ポイント |
|---|---|
| 前処理とは | データクレンジングとも呼ばれ、分析前のデータ整形作業 |
| CSV読み込み | Pandasで多様なデータソースから効率的に読み込み |
| 欠損値 | 削除または補完(平均値・中央値・前方補完など) |
| 重複データ | duplicated()で検出、drop_duplicates()で削除 |
| 外れ値 | IQR法やZスコア法で検出し、除外・置換・変換で対処 |
| 型変換 | astype()やpd.to_numeric()で適切な型に変換 |
| ダミー変数 | One-Hotエンコードと順序ラベルエンコードを使い分け |
| スケーリング | 正規化(MinMaxScaler)と標準化(StandardScaler) |
| 学習/テスト分割 | train_test_splitで分割し、過学習を防止 |
前処理のチェックリスト
実務でデータの前処理を行う際は、以下のチェックリストを参考にしてください。
- データの行数・列数を確認したか
- 各列のデータ型を確認したか
- 欠損値の数と割合を確認したか
- 重複データがないか確認したか
- 外れ値・異常値を確認したか
- 不要な列を削除したか
- 表記ゆれを修正したか
- カテゴリカルデータを数値に変換したか
- 必要に応じてスケーリングを適用したか
- 学習データとテストデータに分割したか
シリーズ記事一覧
- 【第1回】Pythonで学ぶデータ分析実践 〜データ分析とは〜
- 【第2回】データの前処理(欠損値・外れ値・ダミー変数・データスケーリングなど)
- 【第3回】データの把握(基本統計量・ヒストグラム・箱ひげ図・散布図など)
- 【第4回】相関分析(共分散・相関係数・相関比・連関係数など)
- 【第5回】統計的推定(母集団と標本・点推定・区間推定・信頼区間)
- 【第6回】統計的検定(仮説検定・p値・t検定・カイ二乗検定など)
- 【第7回】分散分析(一元分散分析・二元分散分析・多重比較)
- 【第8回】回帰分析(単回帰・重回帰・一般化線形モデル・モデル評価)
- 【第9回】時系列データ分析(トレンド・季節性・ARIMA・SARIMAなど)
- 【第10回】クラス分類(決定木・ランダムフォレスト・ロジスティック回帰など)
- 【第11回】クラスタリング(k-means・階層クラスタリング・エルボー法など)
- 【第12回】次元削減(主成分分析・寄与率・可視化)
- 【発展編】分析精度の向上(特徴量エンジニアリング・ハイパーパラメータ調整・アンサンブル学習など)
- 【発展編】ニューラルネットワーク入門(パーセプトロン・誤差逆伝播法・TensorFlow・Kerasなど)
次回の第3回では、前処理を行ったデータに対して基本統計量を算出し、ヒストグラムや箱ひげ図、散布図を使ってデータの全体像を把握する方法を解説します。