【ピボット・melt】データの変形とピボット操作
同じデータでも、人間が読みやすい「ワイドテーブル」として保存する場合と、プログラムによる分析に適した「ロングテーブル」として保存する場合があります。Pandasは、これらの形式を相互に変換するための4つの必須ツール、pivot / melt / stack / unstack を提供しています。本レッスンでは、Mermaid図を使って各変形操作の方向性を視覚的に示し、「melt(溶かす)」や「pivot(軸で回転させる)」が実際に何を意味するのかを直感的に理解できるようにします。これは、競合する多くのチュートリアルにはない可視化です。
⚠️ 注意: 以下のコードを実行するには、ローカルのPython環境が必要です。
1. 学べること
- ❶ pivot — 基本的なピボット
- ❷ pivot_table — 集計を伴うピボット
- ❸ melt — ワイドからロングへ
- ❹ stack / unstack
- ❺ ワイドテーブルとロングテーブル、そしてTidy Data
2. アリスの売上レポート変形
(1) 困りどころ:レポート形式 ≠ 分析形式
アリスの月次売上レポートはワイド形式(月が列方向に並ぶ)ですが、分析にはロング形式(月を1つの列として持つ)が必要です:
PYTHON
import pandas as pd
# ワイド形式 — 読みやすいが、分析しにくい
wide = pd.DataFrame({
'product': ['Latte', 'Mocha', 'Americano'],
'Jan': [320, 280, 200],
'Feb': [350, 300, 220],
'Mar': [380, 310, 240]
})
print(wide)
# product Jan Feb Mar
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
(2) 解決策:meltでロング形式へ
▶ サンプル
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
:meltでワイドからロングへ(難易度 ⭐)
PYTHON
import pandas as pd
wide = pd.DataFrame({
'product': ['Latte', 'Mocha', 'Americano'],
'Jan': [320, 280, 200],
'Feb': [350, 300, 220],
'Mar': [380, 310, 240]
})
# Melt:月の列が行になる
long = wide.melt(id_vars='product', var_name='month', value_name='sales')
print(long)
# product month sales
# 0 Latte Jan 320
# 1 Mocha Jan 280
# 2 Americano Jan 200
# 3 Latte Feb 350
# ...
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
3. ワイドテーブルとロングテーブル
(1) Mermaid図:ワイド ⇄ ロング
graph LR
subgraph Wide["ワイドテーブル — 人間向き"]
W["product | Jan | Feb | Mar<br>Latte | 320 | 350 | 380<br>Mocha | 280 | 300 | 310"]
end
subgraph Long["ロングテーブル — 機械分析向き"]
L["product | month | sales<br>Latte | Jan | 320<br>Latte | Feb | 350<br>Latte | Mar | 380<br>Mocha | Jan | 280<br>..."]
end
Wide -->|"melt()"| Long
Long -->|"pivot()"| Wide
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
(2) ワイドとロングの比較
| 特徴 | ワイドテーブル | ロングテーブル |
|---|---|---|
| 値の格納場所 | 列名の中 | 行の中 |
| 向いている用途 | 人間が読む/レポート | 機械による分析/ggplot |
| 新しい月の追加 | 列を追加(構造が変わる) | 行を追加(構造は不変) |
| groupby | 不便 | 自然にサポート |
| 典型的な用途 | Excelレポート | データベース/Tidy Data |
4. pivot — 基本的なピボット
(1) 基本的なピボット
▶ サンプル
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
:pivotでロングからワイドへ(難易度 ⭐)
PYTHON
import pandas as pd
long = pd.DataFrame({
'product': ['Latte', 'Latte', 'Latte', 'Mocha', 'Mocha', 'Mocha'],
'month': ['Jan', 'Feb', 'Mar', 'Jan', 'Feb', 'Mar'],
'sales': [320, 350, 380, 280, 300, 310]
})
# ピボット:一意な行=product、一意な列=month、値=sales
wide = long.pivot(index='product', columns='month', values='sales')
print(wide)
# month Feb Jan Mar
# product
# Americano 220 200 240
# Latte 350 320 380
# Mocha 300 280 310
# インデックスをリセットしてproductを通常の列にする
wide = wide.reset_index()
print(wide.columns) # MultiIndex → フラット化
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
⚠️ 注意: pivotは、indexとcolumnsの組み合わせが一意であることを要求します。重複がある場合(例:同じ月の同じ商品に複数のレコードがある場合)、エラーが発生します。その場合は、pivot_table(自動的に集計してくれる)を使用してください。
5. pivot_table — 集計を伴うピボット
(1) 集計ピボット
▶ サンプル
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
:pivot_tableによる集計(難易度 ⭐⭐)
PYTHON
import pandas as pd
# product+monthごとに複数のエントリ(集計が必要)
df = pd.DataFrame({
'product': ['Latte', 'Latte', 'Latte', 'Latte', 'Mocha', 'Mocha'],
'month': ['Jan', 'Jan', 'Feb', 'Feb', 'Jan', 'Feb'],
'sales': [150, 170, 180, 170, 140, 160],
'quantity': [30, 34, 36, 34, 28, 32]
})
# 集計関数を指定したpivot_table
result = pd.pivot_table(
df,
values='sales',
index='product',
columns='month',
aggfunc='sum' # デフォルトは'mean'
)
print(result)
# month Feb Jan
# product
# Latte 350 320
# Mocha 160 140
# 複数の集計関数
result2 = pd.pivot_table(
df,
values=['sales', 'quantity'],
index='product',
columns='month',
aggfunc={'sales': 'sum', 'quantity': 'mean'}
)
# Margins — 行/列の合計を追加
result3 = pd.pivot_table(
df, values='sales', index='product', columns='month',
aggfunc='sum', margins=True, margins_name='Total'
)
print(result3)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
(2) pivotとpivot_tableの比較
| 特徴 | pivot | pivot_table |
|---|---|---|
| 重複する値 | エラー発生 | 集計する |
| 集計関数 | なし | sum/mean/countなど |
| margins | 非対応 | ✅ 行/列の合計 |
| 複数の値列 | 単一列 | 複数列 |
| 向いている用途 | 重複のないクリーンなデータ | 重複があり集計が必要なデータ |
6. melt — 詳細解説
(1) meltのパラメータ
▶ サンプル
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
:meltのパラメータ解説(難易度 ⭐⭐)
PYTHON
import pandas as pd
df = pd.DataFrame({
'product': ['Latte', 'Mocha'],
'region': ['North', 'South'],
'Jan_sales': [320, 280],
'Feb_sales': [350, 300],
'Jan_quantity': [64, 56],
'Feb_quantity': [70, 60]
})
# 基本的なmelt — id以外のすべての列が行になる
result1 = df.melt(id_vars=['product', 'region'])
print(result1)
# value_varsの指定 — 特定の列だけをmeltする
result2 = df.melt(
id_vars=['product', 'region'],
value_vars=['Jan_sales', 'Feb_sales'],
var_name='month',
value_name='sales'
)
print(result2)
# 複数のid_vars
result3 = df.melt(
id_vars=['product'],
value_vars=['Jan_sales', 'Feb_sales'],
var_name='month_sales',
value_name='amount'
)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
7. stack / unstack
(1) stack — 列を行へ
▶ サンプル
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
:MultiIndexでのstack/unstack(難易度 ⭐⭐)
PYTHON
import pandas as pd
df = pd.DataFrame({
'Jan': [320, 280],
'Feb': [350, 300],
'Mar': [380, 310]
}, index=['Latte', 'Mocha'])
df.index.name = 'product'
# stack:列 → 行レベル(ワイド → ロング)
stacked = df.stack()
print(stacked)
# product month
# Latte Jan 320
# Feb 350
# Mar 380
# Mocha Jan 280
# ...
# unstack:行レベル → 列(ロング → ワイド)
unstacked = stacked.unstack()
print(unstacked)
# Jan Feb Mar
# product
# Latte 320 350 380
# Mocha 280 300 310
# MultiIndexの場合:どのレベルをunstackするかを選択
multi = pd.DataFrame({
'sales': [320, 350, 280, 300],
'region': ['North', 'North', 'South', 'South'],
'month': ['Jan', 'Feb', 'Jan', 'Feb']
})
pivot = multi.set_index(['region', 'month'])['sales']
print(pivot.unstack(level='month'))
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
(2) 4つの変形メソッドの比較
| メソッド | 方向 | 入力 → 出力 | 向いている用途 |
|---|---|---|---|
| pivot | ロング → ワイド | index/columns/valuesを指定 | 重複のないロングテーブル |
| pivot_table | ロング → ワイド | + aggfuncで集計 | 重複のあるロングテーブル |
| melt | ワイド → ロング | id_varsを指定 | 列名を行の値にする |
| stack | 列 → 行 | columns → インデックスレベル | MultiIndex操作 |
| unstack | 行 → 列 | インデックスレベル → columns | stackの逆操作 |
8. 総合例:ワイド⇄ロング変換の完全パイプライン
▶ サンプル
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
:ワイド⇄ロング変換の完全パイプライン(難易度 ⭐⭐⭐)
PYTHON
import pandas as pd
import numpy as np
# ============================================
# 総合例:ワイド ⇄ ロング
# 売上データの変形パイプライン
# ============================================
# 1. ワイド形式から始める(レポート形式)
np.random.seed(42)
wide = pd.DataFrame({
'product': ['Latte', 'Mocha', 'Americano', 'Espresso', 'Cappuccino'],
'region': ['North', 'South', 'East', 'West', 'North'],
'Jan': np.random.randint(200, 500, 5),
'Feb': np.random.randint(220, 520, 5),
'Mar': np.random.randint(240, 540, 5),
'Apr': np.random.randint(250, 550, 5)
})
print("=== ワイド形式(5商品 × 4か月) ===")
print(wide)
# 2. meltでロング形式へ
long = wide.melt(
id_vars=['product', 'region'],
var_name='month',
value_name='sales'
)
print(f"\n=== ロング形式:{len(long)}行 ===")
print(long.head(8))
# 3. ロング形式で分析(groupbyが自然)
monthly_total = long.groupby('month')['sales'].sum()
print(f"\n=== 月別合計 ===\n{monthly_total}")
region_avg = long.groupby('region')['sales'].mean().round(0)
print(f"\n=== 地域別平均 ===\n{region_avg}")
# 4. ピボットでワイドに戻す(product × monthのサマリ)
summary_wide = pd.pivot_table(
long, values='sales', index='product', columns='month',
aggfunc='sum', margins=True, margins_name='Total'
)
print(f"\n=== ピボットテーブルのサマリ ===")
print(summary_wide)
# 5. MultiIndexでのstack/unstack
multi = long.set_index(['region', 'product', 'month'])['sales']
by_region = multi.unstack(level='month')
print(f"\n=== 月別unstack(地域×商品) ===")
print(by_region.head(8))
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して、一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
❓ よくある質問
Q pivotとpivot_tableの違いは何ですか?
A pivotはindexとcolumnsの組み合わせが一意であることを要求し、重複があるとエラーになります。pivot_tableは重複を集計して処理し(デフォルトはmeanですが、sum/countなども指定可能)、margins(行/列の合計)もサポートします。重複のないシンプルなデータにはpivotを、重複がある場合や集計が必要な場合はpivot_tableを使用してください。実務では、日常のシナリオの80%においてpivot_tableの方が安全な選択肢です。
Q meltのid_varsとは何ですか?
A id_varsはmeltされない列、つまり行の識別子として残る列です。それ以外のすべての列は、その列名が変数列(var_name)に、その値が値列(value_name)に集められます。例えば、月の列Jan/Feb/Marをmeltすると、文字列'Jan'/'Feb'/'Mar'はmonth列の値になり、対応する数値はsales列の値になります。
Q stackとunstackは互いに逆操作ですか?
A はい — stackは列を行に押し込み(ワイド → ロング)、unstackは行を列に展開します(ロング → ワイド)。
df.stack().unstack()は元のテーブルを復元します。これらはMultiIndexのレベルに対して動作し、stackは行レベルを追加し、unstackは列レベルを追加します。levelパラメータで、どのレベルを対象にするかを制御します。Q ワイドテーブルとロングテーブルはいつ使い分けるべきですか?
A ワイドテーブルは人間が読むのに最適です(レポート、プレゼン、Excel)。ロングテーブルはプログラムによる分析に最適です(groupby、merge、可視化)。Tidy Dataの原則はロング形式を推奨しています:1列に1変数、1行に1観測。データはロング形式(安定した構造)で保存し、表示するときにワイド形式(pivotの出力)にしましょう。
Q pivotで重複値のエラーが出たらどうすればいいですか?
A これは、indexとcolumnsの組み合わせに重複する行があることを意味します(例:同じ月の同じ商品に複数のレコードがある場合)。選択肢は2つあります:① aggfunc='sum'/'mean'を指定したpivot_tableで自動的に集計する;② 先にgroupbyで集計してからpivotする。pivot_tableの方が簡潔で、一般的に好まれます。
Q melt操作を元に戻すにはどうすればいいですか?
A pivotまたはpivot_tableで元の形を復元します:
long.pivot(index='product', columns='month', values='sales')。meltとpivotは逆操作です。なお、melt後にデータ型が変わる場合がある(数値列がobjectになる場合がある)ため、復元前にastypeでの変換が必要になることがあります。Q Tidy Dataとは何ですか?
A Tidy Dataは、Hadley Wickhamが提唱したデータ整理の原則です:① 各変数は1つの列を占める;② 各観測は1つの行を占める;③ 各観測単位のタイプは1つのテーブルを占める。ロングテーブルは自然にTidy Dataを満たしますが、ワイドテーブルは満たしません(month変数が列名に隠れているため)。Pandasのmeltは、まさにワイドテーブルをTidy Data形式に「整える(tidy)」ために存在しています。
📖 まとめ
- pivotはロングからワイドへ変換する(重複がない場合);pivot_tableは集計を伴いながらロングからワイドへ変換する(重複がある場合)
- meltはワイドからロングへ変換する:id_varsは保持される列で、残りの列名は変数列に、その値は値列になる
- stackは列を行に押し込み(ワイド → ロング)、unstackは行を列に展開する(ロング → ワイド)— 互いに逆操作
- ワイドテーブルは人間向き、ロングテーブルは機械分析向き;Tidy Dataはロング形式を推奨
- 典型的なワークフロー:生データ → meltで整える → groupbyで分析 → pivot_tableでレポート
- pivotで重複エラーが出たら → pivot_table + aggfuncに切り替える
📝 練習問題
- 基礎(難易度 ⭐):ワイドテーブル(3商品 × 3か月)を作成し、meltでロング形式に変換してから、pivotでワイド形式に戻し、結果が一致することを確認してください。
- 中級(難易度 ⭐⭐):重複する値を含む売上データ(同じ月の同じ商品に複数のレコード)を作成し、pivot_table(aggfunc='sum') + margins=Trueを使って、行と列の合計を持つピボットテーブルを生成してください。
- チャレンジ(難易度 ⭐⭐⭐):3地域 × 4商品 × 3か月のロング形式データをシミュレートし、次を完成させてください:set_indexでMultiIndexを作成 → unstackで月別に展開 → stackで元に戻す → meltで別の変形 → pivot_tableでクロス集計。



