Machine Learning: Pandasによるデータ処理 — 読み込み・クリーニング・変換・集約の完全ガイド
最終更新:2026-08-26
Pandasはデータサイエンスのスイスアーミーナイフです。データの読み込み、クリーニング、変換、集約を、これひとつでこなせます。
1. この章で学ぶこと
- DataFrameとSeriesの基本操作:作成、インデックス指定、選択、フィルタリング
- データクリーニングの実践:欠損値の処理、重複の削除、外れ値の検出
- データの変換:apply/map/transform、groupbyによる集約、pivot_table
- 複数データソースの結合:マージ/結合/concat — Aliceの米国注文データとBobの中国注文データを組み合わせて分析する
- 時系列処理:日付のパース、リサンプリング、ローリングウィンドウ
2. データアナリストの実例
(1) 課題:散らかったCSVデータ、クリーニングに全時間の80%を費やす
BobはSalesPredictの生の注文データを入手しました。20万件のレコードのうち、5%の金額がnullで、3%が重複注文、ユーザーIDの形式もバラバラでした。Aliceの米国データはBobのデータとフィールド名が異なり、CharlieのEUR建て金額は通貨変換が必要です。データクリーニングが機械学習プロジェクト最大の時間的ボトルネックになっていました。
(2) Pandasによる解決策
Pandasは完全なデータクリーニングツールチェーンを提供します。欠損値の処理、重複の検出、型変換、複数ソースの結合まで、データクリーニングを手作業から再現可能なコードへと変えてくれます。
PYTHON
import pandas as pd
# Load and clean in a pipeline
df = pd.read_csv("orders.csv")
df_clean = (df
.drop_duplicates()
.fillna({"amount": df["amount"].median()})
.assign(amount_usd=lambda x: x["amount"] * x["exchange_rate"])
)
print(f"Clean rows: {len(df_clean)}, Original: {len(df)}")
(3) 効果:クリーニング時間が3日から30分に短縮
BobがPandasでデータクリーニングのワークフローをコード化したところ、以前は3日かかっていた手作業のクリーニングが、30分の自動実行に短縮されました。しかも同じパイプラインは、新しいデータが入ってくるたびに再利用できます。
3. DataFrameとSeriesの基本操作
(1) DataFrameの作成
PYTHON
import pandas as pd
import numpy as np
# From dictionary
sales_data = pd.DataFrame({
"date": pd.date_range("2024-01-01", periods=5, freq="D"),
"category": ["Electronics", "Clothing", "Food", "Books", "Home"],
"revenue_k_usd": [250, 145, 90, 70, 400],
"orders": [1200, 800, 3000, 500, 600],
})
# From NumPy array
arr = np.random.rand(3, 4)
df_from_arr = pd.DataFrame(arr, columns=["A", "B", "C", "D"])
print(sales_data)
print(f"\nShape: {sales_data.shape}")
print(f"Dtypes:\n{sales_data.dtypes}")
▶ サンプル:SalesPredictの実データを読み込む
PYTHON
import pandas as pd
# Load CSV with type hints
df = pd.read_csv("sales_data.csv", parse_dates=["order_date"])
# Quick overview
print(f"Shape: {df.shape}")
print(f"\nFirst 5 rows:\n{df.head()}")
print(f"\nData types:\n{df.dtypes}")
print(f"\nMemory usage:\n{df.memory_usage(deep=True)}")
出力:
TEXT
📖 参照専用
# Executed successfully
(2) 選択とフィルタリング
▶ サンプル:データを選択するさまざまな方法
PYTHON
import pandas as pd
df = pd.DataFrame({
"product": ["Laptop", "Phone", "Tablet", "Monitor", "Keyboard"],
"category": ["Electronics", "Electronics", "Electronics", "Electronics", "Accessories"],
"price": [999, 699, 399, 299, 49],
"stock": [50, 200, 100, 80, 500],
})
# Column selection
prices = df["price"] # Series
subset = df[["product", "price"]] # DataFrame
# Row selection with loc (label) and iloc (position)
row = df.loc[0] # First row by label
rows = df.iloc[1:3] # Rows 1-2 by position
# Conditional filtering
expensive = df[df["price"] > 400]
elec_cheap = df[(df["category"] == "Electronics") & (df["price"] < 500)]
print(f"Expensive items:\n{expensive}")
出力:
TEXT
📖 参照専用
# Executed successfully
| 選択方法 | 構文 | ユースケース |
|---|---|---|
| 列の選択 | df["col"] / df[["c1","c2"]] |
1つまたは複数の列を取得する |
| loc | df.loc[row, col] |
ラベルでインデックス指定する |
| iloc | df.iloc[r, c] |
位置でインデックス指定する |
| 条件フィルタリング | df[df["col"] > val] |
条件で絞り込む |
| クエリ | df.query("price > 400") |
SQLスタイルのフィルタリング |
4. データクリーニングの実践
Pandasにおけるデータクリーニングはパイプライン処理です。各ステップが1種類の問題に対処し、汚いデータを段階的にきれいなデータへと変えていきます:
graph LR
RAW[Raw Data<br/>5% Missing, 3% Dupes] --> DEDUP[Deduplicate<br/>drop_duplicates]
DEDUP --> FILL[Fill Missing<br/>fillna median/mode]
FILL --> OUTLIER[Remove Outliers<br/>IQR clipping]
OUTLIER --> CONVERT[Type Convert<br/>astype / to_datetime]
CONVERT --> CLEAN[Clean Data<br/>Ready for ML]
(1) 欠損値の処理
▶ サンプル:SalesPredictの欠損値を診断・処理する
PYTHON
import pandas as pd
import numpy as np
# Simulate data with missing values
df = pd.DataFrame({
"order_id": [1001, 1002, 1003, 1004, 1005, 1006],
"amount_usd": [150, np.nan, 280, np.nan, 95, 320],
"category": ["Electronics", "Clothing", np.nan, "Food", "Books", "Electronics"],
"user_rating": [4.5, 3.8, np.nan, 4.2, np.nan, 5.0],
})
# Diagnose missing values
print(f"Missing count:\n{df.isnull().sum()}")
print(f"\nMissing ratio:\n{df.isnull().mean().round(3)}")
# Strategy 1: Drop rows with any missing
df_drop = df.dropna()
# Strategy 2: Fill with median/mode
df_fill = df.fillna({
"amount_usd": df["amount_usd"].median(),
"category": df["category"].mode()[0],
"user_rating": df["user_rating"].mean(),
})
print(f"\nFilled data:\n{df_fill}")
出力:
TEXT
📖 参照専用
# Executed successfully
| 戦略 | メソッド | ユースケース | リスク |
|---|---|---|---|
| 削除 | dropna() |
欠損割合が5%未満の場合 | 情報の損失 |
| 平均値補完 | fillna(df["col"].mean()) |
数値型でほぼ正規分布の場合 | 分散が小さくなる |
| 中央値補完 | fillna(df["col"].median()) |
数値型で外れ値がある場合 | 保守的な推定値になる |
| 最頻値補完 | fillna(df["col"].mode()[0]) |
カテゴリ型の場合 | 多数派が過大表現される可能性 |
| 前方/後方補完 | fillna(method="ffill") |
時系列データの場合 | バイアスが伝播する |
(2) 重複と外れ値
▶ サンプル:重複と外れ値の検出・処理
PYTHON
import pandas as pd
import numpy as np
df = pd.DataFrame({
"order_id": [1001, 1002, 1002, 1003, 1004],
"amount": [150, 280, 280, 95000, 95],
})
# Duplicate detection
dupes = df.duplicated(subset=["order_id", "amount"])
print(f"Duplicated rows:\n{df[dupes]}")
# Remove duplicates
df_clean = df.drop_duplicates(subset=["order_id"], keep="first")
# Outlier detection with IQR method
def detect_outliers_iqr(series, factor=1.5):
q1, q3 = series.quantile([0.25, 0.75])
iqr = q3 - q1
lower, upper = q1 - factor * iqr, q3 + factor * iqr
return (series < lower) | (series > upper)
outlier_mask = detect_outliers_iqr(df_clean["amount"])
print(f"\nOutliers:\n{df_clean[outlier_mask]}")
# Cap outliers at 99th percentile
cap = df_clean["amount"].quantile(0.99)
df_capped = df_clean.assign(amount=df_clean["amount"].clip(upper=cap))
出力:
TEXT
📖 参照専用
# Functions defined successfully
5. データの変換と集約
(1) apply/map/transform
▶ サンプル:特徴量の変換
PYTHON
import pandas as pd
df = pd.DataFrame({
"product": ["Laptop", "Phone", "Tablet"],
"price_usd": [999, 699, 399],
"cost_usd": [600, 350, 180],
})
# map: element-wise transform on Series
df["price_level"] = df["price_usd"].map(
lambda x: "High" if x > 700 else ("Mid" if x > 400 else "Low")
)
# apply: row/column-wise transform
df["margin_pct"] = df.apply(
lambda row: (row["price_usd"] - row["cost_usd"]) / row["price_usd"] * 100,
axis=1
)
# transform: same-shape output
df["price_zscore"] = df["price_usd"].transform(
lambda x: (x - x.mean()) / x.std()
)
print(df)
出力:
TEXT
📖 参照専用
# Executed successfully
(2) groupbyによるグループ集約
▶ サンプル:カテゴリ別の売上指標を集約する
PYTHON
import pandas as pd
df = pd.DataFrame({
"category": ["Elec", "Elec", "Cloth", "Cloth", "Food", "Food"],
"month": ["Jan", "Feb", "Jan", "Feb", "Jan", "Feb"],
"revenue_k": [250, 260, 145, 150, 90, 95],
"orders": [1200, 1250, 800, 820, 3000, 3100],
})
# Single aggregation
cat_revenue = df.groupby("category")["revenue_k"].sum()
print(f"Revenue by category:\n{cat_revenue}")
# Multiple aggregations
cat_stats = df.groupby("category").agg({
"revenue_k": ["sum", "mean", "std"],
"orders": ["sum", "mean"],
})
print(f"\nCategory stats:\n{cat_stats}")
# Named aggregations
cat_named = df.groupby("category").agg(
total_revenue=("revenue_k", "sum"),
avg_revenue=("revenue_k", "mean"),
total_orders=("orders", "sum"),
avg_order_value=("revenue_k", lambda x: x.sum() / df.loc[x.index, "orders"].sum() * 1000),
)
出力:
TEXT
📖 参照専用
# Executed successfully
(3) pivot_tableによるピボットテーブル
▶ サンプル:カテゴリ別の月次売上ピボット
PYTHON
import pandas as pd
df = pd.DataFrame({
"category": ["Elec"]*3 + ["Cloth"]*3 + ["Food"]*3,
"month": ["Jan", "Feb", "Mar"]*3,
"revenue_k": [250, 260, 270, 145, 150, 155, 90, 95, 100],
})
# Pivot: categories as rows, months as columns
pivot = df.pivot_table(
values="revenue_k",
index="category",
columns="month",
aggfunc="sum",
margins=True, # Add row/column totals
)
print(pivot)
出力:
TEXT
📖 参照専用
# Executed successfully
| 観点 | groupby | pivot_table |
|---|---|---|
| 出力の形状 | 縦長 | 横長 |
| 複数指標 | ✅ 複数列にaggを適用 | ✅ 複数列にvaluesを適用 |
| 小計 | ❌ 手作業が必要 | ✅ margins=True |
| 柔軟性 | 高い(任意の集約関数) | 中程度(固定のaggfunc) |
6. 複数データソースの結合と時系列処理
(1) マージ操作
▶ サンプル:Aliceの米国データとBobの中国データを結合する
PYTHON
import pandas as pd
# Alice's US orders
us_orders = pd.DataFrame({
"order_id": ["US001", "US002", "US003"],
"amount_usd": [150, 280, 95],
"product": ["Laptop", "Phone", "Book"],
})
# Bob's China orders (need currency conversion)
cn_orders = pd.DataFrame({
"order_id": ["CN001", "CN002"],
"amount_cny": [1200, 3500],
"product": ["Phone", "Laptop"],
})
# Concat vertically (append rows)
cn_orders_usd = cn_orders.assign(
amount_usd=cn_orders["amount_cny"] * 0.14 # CNY to USD
).drop(columns=["amount_cny"])
all_orders = pd.concat([us_orders, cn_orders_usd], ignore_index=True)
print(f"Combined orders:\n{all_orders}")
# Merge with product catalog
catalog = pd.DataFrame({
"product": ["Laptop", "Phone", "Book", "Tablet"],
"category": ["Electronics", "Electronics", "Books", "Electronics"],
"margin_pct": [35, 45, 20, 30],
})
enriched = all_orders.merge(catalog, on="product", how="left")
print(f"\nEnriched orders:\n{enriched}")
出力:
TEXT
📖 参照専用
# Executed successfully
| 結合タイプ | 構文 | SQLとの対応 | 説明 |
|---|---|---|---|
| Inner | merge(how="inner") |
INNER JOIN | 共通部分のみ取得する |
| Left | merge(how="left") |
LEFT JOIN | 左テーブルの全行を保持する |
| Outer | merge(how="outer") |
FULL JOIN | 両テーブルの和集合を取得する |
| Concat | concat(axis=0) |
UNION | 行を縦に積み重ねる |
| Concat | concat(axis=1) |
— | 列を横に並べる |
(2) 時系列処理
▶ サンプル:SalesPredictの日次データでリサンプリングとローリング統計
PYTHON
import pandas as pd
import numpy as np
# Daily sales data
rng = pd.date_range("2024-01-01", periods=90, freq="D")
daily = pd.DataFrame({
"date": rng,
"revenue": np.random.normal(5000, 1000, 90).cumsum(),
}, index=rng)
# Resample to monthly
monthly = daily["revenue"].resample("M").agg(["first", "last", "mean", "sum"])
print(f"Monthly stats:\n{monthly}")
# Rolling window: 7-day moving average
daily["ma_7d"] = daily["revenue"].rolling(window=7).mean()
daily["ma_30d"] = daily["revenue"].rolling(window=30).mean()
# Percentage change
daily["pct_change"] = daily["revenue"].pct_change()
print(f"\nLast 5 rows with rolling stats:\n{daily.tail()}")
出力:
TEXT
📖 参照専用
# Executed successfully
❓ よくある質問
Q locとilocの違いは何ですか?
A locはラベルでインデックス指定し、終端を含みます。ilocは位置でインデックス指定し、終端を含みません。例えば、df.loc[0:3]は4行返しますが、df.iloc[0:3]は3行返します。
Q SettingWithCopyWarningはどうすれば解消できますか?
A これはチェーン代入の警告です。
.copy()で明示的にコピーを作成するか、チェーン操作を単一の.loc[]代入に置き換えてください。例えば、df[mask]["col"] = valueの代わりにdf.loc[mask, "col"] = valueを使用します。Q groupbyの後、グループ化に使用した列を通常の列として残すにはどうすればよいですか?
A
as_index=Falseパラメータを使用します:df.groupby("category", as_index=False).agg(...)。または、groupbyの後に.reset_index()を呼び出してください。Q mergeで予期しない余分な行が生成される場合はどうすればよいですか?
A マージキーに重複がないか確認してください。
df.duplicated(subset=key).sum()でチェックできます。重複がある場合は、マージ前に対応方針(重複削除または集約)を先に決めてください。Q 大規模データセットでメモリ不足になる場合はどうすればよいですか?
A 3つの戦略があります。1)dtypeを指定してメモリを削減する(例:int64 → int32)。2)
chunksizeパラメータで分割読み込みする。3)超大規模データにはDaskライブラリを使用する。Q 時系列でfreqパラメータがエラーになる場合はどうすればよいですか?
A
df.asfreq("D")で規則的な頻度を保証するか、df.resample("D").asfreq()を使用してください。不規則な時系列データは、分析前にリサンプリングが必要です。📖 まとめ
- DataFrameは2次元の表形式データ構造、Seriesは1次元の配列です。この2つがPandasの中核を成しています
- 欠損値への主な戦略は3つ:削除(dropna)、補完(fillna)、補間(interpolate)— 状況に応じて使い分けます
- groupby + aggでグループ集約を行い、pivot_tableで横長形式のピボットを作成します。それぞれに適した用途があります
- mergeはSQLスタイルのテーブル結合、concatは行や列の積み重ねを行います。howパラメータの選択に注意してください
- 時系列処理:parse_datesで日付をパースし、resampleでリサンプリング、rollingでローリング統計、pct_changeで変化率を計算します
📝 練習問題
- 基礎(難易度 ⭐):CSVファイルを読み込み、
info()とdescribe()で概要を把握し、欠損値の数を数えてください。ヒント:df.isnull().sum()。 - 応用(難易度 ⭐⭐):2つのDataFrame(Aliceの米国売上とCharlieの欧州売上)を作成し、mergeで商品名をキーに結合して、各商品の全世界合計売上を計算してください。ヒント:先に金額の単位をUSDに統一してからマージします。
- 発展(難易度 ⭐⭐⭐):日次売上データに対して、1)週次にリサンプリングする、2)4週間のローリング平均を計算する、3)外れ値(ローリング平均から標準偏差の2倍を超えるポイント)を検出する、の3つを行ってください。ヒント:
resample("W")+rolling(4)+ ブールインデックス。