Skip to content

Pandas Melt:ワイドデータをロング形式に整形する(完全ガイド)

Updated on

各月が別々の列になっているスプレッドシートを想像してください。1月、2月、3月……12月まで。売上の数値が12列に並んでいて、これを時系列としてプロットしたり、groupby をかけたり、機械学習モデルに渡したりしたい。しかし、こうした処理はワイド形式のデータとは相性がよくありません。必要なのは「月名の列が1つ」と「売上値の列が1つ」です。この変換——列を行に変える——を行うのが、まさに pandas melt です。

問題はすぐに深刻化します。列が数十〜数百に及ぶワイドなデータセットは、アンケートデータ、センサー読み取り、財務レポート、Excel や SQL からピボットされたエクスポートなどでよく見られます。手作業で再構成するのは面倒でミスも起きやすい。unpivot したい列が増えるたびに、適切なツールを使わない限りボイラープレートコードが増えていきます。

pd.melt() はこれを1回の呼び出しで解決します。ワイドな DataFrame を受け取り、選択した列を行に変換してロング形式へ「melt」しつつ、識別子となる列はそのまま保持します。このガイドでは、完全な構文、すべてのパラメータ、実務シナリオに基づく例、そして pivotstackwide_to_long のような関連する整形関数との比較まで解説します。

📚

pd.melt() が行うこと

pd.melt() は DataFrame をワイド形式からロング形式へ unpivot(縦持ち化)します。ワイド形式では変数ごとに列が分かれています。ロング形式(「tidy」形式とも呼ばれる)では、「変数名」の列が1つと「値」の列が1つになります。

概念的な変換は次のとおりです。

ワイド形式(melt 前):

studentmathscienceenglish
Alice908588
Bob789280

ロング形式(melt 後):

studentsubjectscore
Alicemath90
Alicescience85
Aliceenglish88
Bobmath78
Bobscience92
Bobenglish80

student 列は識別子として保持され、3つの科目列は2列(科目名を入れる列とスコアを入れる列)に melt されます。

pd.melt() の構文とパラメータ

pd.melt(frame, id_vars=None, value_vars=None, var_name=None,
        value_name='value', col_level=None, ignore_index=True)

DataFrame のメソッドとして呼ぶこともできます:

df.melt(id_vars=None, value_vars=None, var_name=None,
        value_name='value', col_level=None, ignore_index=True)

パラメータ一覧

ParameterDescriptionDefault
framemelt する DataFrame(df.melt() を使う場合は不要)Required
id_vars識別子変数として保持する列(melt しない)None
value_vars行に展開(unpivot)する列。省略すると id_vars 以外のすべての列が対象None
var_name元の列名を入れる新しい列の名前'variable'
value_name値を入れる新しい列の名前'value'
col_level列が MultiIndex の場合、melt するレベルNone
ignore_indexTrue なら新しい整数 index を採用。False なら元の index を保持True

基本例:学生の成績

先ほどの成績例から始めます。

import pandas as pd
 
grades = pd.DataFrame({
    'student': ['Alice', 'Bob', 'Charlie'],
    'math': [90, 78, 85],
    'science': [85, 92, 88],
    'english': [88, 80, 91]
})
 
long = pd.melt(grades, id_vars=['student'], value_vars=['math', 'science', 'english'],
               var_name='subject', value_name='score')
print(long)

出力:

   student  subject  score
0    Alice     math     90
1      Bob     math     78
2  Charlie     math     85
3    Alice  science     85
4      Bob  science     92
5  Charlie  science     88
6    Alice  english     88
7      Bob  english     80
8  Charlie  english     91

これで各行が「学生×科目」の組み合わせを表すようになります。元の 3x4 DataFrame(3行、4列)が、9x3 DataFrame(9行、3列)になります。

value_vars を省略する

value_vars を省略すると、pandas は id_vars に含まれない列をすべて melt します。

long = grades.melt(id_vars=['student'], var_name='subject', value_name='score')
print(long)

これは先ほどの例と同じ結果になります。value_vars の省略は、識別子以外をすべて melt したいときに便利です。

id_vars なしで melt する

識別子列を指定せずに melt することもできます。この場合、すべての列が variable/value のペアになります。

temperatures = pd.DataFrame({
    'Jan': [30, 28],
    'Feb': [32, 31],
    'Mar': [45, 42]
})
 
long = temperatures.melt(var_name='month', value_name='temp_f')
print(long)

出力:

  month  temp_f
0   Jan      30
1   Jan      28
2   Feb      32
3   Feb      31
4   Mar      45
5   Mar      42

すべての列が測定値で、保持すべき識別子がない場合に有用です。

複数の識別子列

実データでは識別子が1つとは限りません。id_vars に列名のリストを渡せます。

sales = pd.DataFrame({
    'region': ['North', 'South', 'North', 'South'],
    'product': ['Widget', 'Widget', 'Gadget', 'Gadget'],
    'q1_revenue': [1200, 1500, 800, 950],
    'q2_revenue': [1400, 1600, 900, 1100],
    'q3_revenue': [1100, 1450, 850, 1000],
    'q4_revenue': [1500, 1700, 1000, 1200]
})
 
long_sales = sales.melt(
    id_vars=['region', 'product'],
    var_name='quarter',
    value_name='revenue'
)
print(long_sales)

出力:

    region product      quarter  revenue
0    North  Widget   q1_revenue     1200
1    South  Widget   q1_revenue     1500
2    North  Gadget   q1_revenue      800
3    South  Gadget   q1_revenue      950
4    North  Widget   q2_revenue     1400
5    South  Widget   q2_revenue     1600
6    North  Gadget   q2_revenue      900
7    South  Gadget   q2_revenue     1100
8    North  Widget   q3_revenue     1100
9    South  Widget   q3_revenue     1450
10   North  Gadget   q3_revenue      850
11   South  Gadget   q3_revenue     1000
12   North  Widget   q4_revenue     1500
13   South  Widget   q4_revenue     1700
14   North  Gadget   q4_revenue     1000
15   South  Gadget   q4_revenue     1200

regionproduct の両方が各行で保持され、四半期の4列が2列へ圧縮されます。

melt 後の後処理(クリーンアップ)

melt の後、variable 列には整形したい文字列が入っていることがよくあります。上の例では q1_revenue のような値が入っていますが、Q1 だけにしたいかもしれません。文字列操作で整形できます。

long_sales['quarter'] = long_sales['quarter'].str.replace('_revenue', '').str.upper()
print(long_sales.head())

出力:

  region product quarter  revenue
0  North  Widget      Q1     1200
1  South  Widget      Q1     1500
2  North  Gadget      Q1      800
3  South  Gadget      Q1      950
4  North  Widget      Q2     1400

特定の列だけ melt する

列の一部だけ melt したいこともあります。その場合は value_vars で明示します。

survey = pd.DataFrame({
    'respondent': ['R1', 'R2', 'R3'],
    'age': [25, 34, 42],
    'q1_satisfaction': [4, 5, 3],
    'q2_satisfaction': [3, 4, 5],
    'q3_satisfaction': [5, 3, 4],
    'income': [50000, 75000, 60000]
})
 
# 満足度の列だけ melt し、age と income は識別子として保持
long_survey = survey.melt(
    id_vars=['respondent', 'age', 'income'],
    value_vars=['q1_satisfaction', 'q2_satisfaction', 'q3_satisfaction'],
    var_name='question',
    value_name='rating'
)
print(long_survey)

出力:

  respondent  age  income         question  rating
0         R1   25   50000  q1_satisfaction       4
1         R2   34   75000  q1_satisfaction       5
2         R3   42   60000  q1_satisfaction       3
3         R1   25   50000  q2_satisfaction       3
4         R2   34   75000  q2_satisfaction       4
5         R3   42   60000  q2_satisfaction       5
6         R1   25   50000  q3_satisfaction       5
7         R2   34   75000  q3_satisfaction       3
8         R3   42   60000  q3_satisfaction       4

income 列は melt 対象ではありませんが、識別子として保持されます。

実例:時系列データ

金融・経済データは、日付(年など)が列ヘッダになったワイド形式で届くことがよくあります。melt でプロットしやすい時系列へ変換できます。

import pandas as pd
 
gdp = pd.DataFrame({
    'country': ['USA', 'UK', 'Germany'],
    '2020': [20.94, 2.71, 3.89],
    '2021': [23.00, 3.12, 4.26],
    '2022': [25.46, 3.07, 4.07],
    '2023': [27.36, 3.33, 4.46]
})
 
gdp_long = gdp.melt(id_vars=['country'], var_name='year', value_name='gdp_trillion_usd')
gdp_long['year'] = gdp_long['year'].astype(int)
gdp_long = gdp_long.sort_values(['country', 'year']).reset_index(drop=True)
print(gdp_long)

出力:

   country  year  gdp_trillion_usd
0  Germany  2020              3.89
1  Germany  2021              4.26
2  Germany  2022              4.07
3  Germany  2023              4.46
4       UK  2020              2.71
5       UK  2021              3.12
6       UK  2022              3.07
7       UK  2023              3.33
8      USA  2020             20.94
9      USA  2021             23.00
10     USA  2022             25.46
11     USA  2023             27.36

これで GDP の推移を簡単にプロットしたり、国別に groupby したり、前年比成長率を計算したりできます。

MultiIndex 列で melt する

DataFrame の列ヘッダが複数レベル(MultiIndex)の場合、col_level でどのレベルを melt するか指定します。

arrays = [['score', 'score', 'attendance', 'attendance'],
          ['midterm', 'final', 'midterm', 'final']]
columns = pd.MultiIndex.from_arrays(arrays, names=['metric', 'exam'])
 
data = pd.DataFrame([[85, 90, 95, 100], [78, 82, 90, 88]],
                     index=['Alice', 'Bob'], columns=columns)
 
# 上位レベルを melt
melted = data.melt(col_level=0, var_name='metric', value_name='value', ignore_index=False)
print(melted)

より複雑な多段階のケースでは、melt の前に droplevel() で列レベルを落としたり、レベル同士をアンダースコアで結合して列をフラット化してから melt したりする必要がある場合があります。

Melt vs Pivot:逆操作

melt()pivot() は逆操作です。melt は wide → long、pivot は long → wide に変換します。

import pandas as pd
 
# ワイドから開始
wide = pd.DataFrame({
    'name': ['Alice', 'Bob'],
    'math': [90, 78],
    'science': [85, 92]
})
 
# Melt: wide -> long
long = wide.melt(id_vars='name', var_name='subject', value_name='score')
print("Long format:")
print(long)
 
# Pivot: long -> wide(往復)
back_to_wide = long.pivot(index='name', columns='subject', values='score').reset_index()
back_to_wide.columns.name = None
print("\nBack to wide format:")
print(back_to_wide)

出力:

Long format:
    name  subject  score
0  Alice     math     90
1    Bob     math     78
2  Alice  science     85
3    Bob  science     92

Back to wide format:
    name  math  science
0  Alice    90       85
1    Bob    78       92

重要な違い: pivot() は index と columns の組み合わせが一意であることを要求します。ロング形式データに重複がある場合は、代わりに pivot_table() と集計関数を使います。

Melt vs Stack vs wide_to_long

pandas には整形(reshape)のための関数がいくつかあります。使い分けの目安は次のとおりです。

FunctionDirectionBest ForKey Difference
melt()wide to long特定の列を行に unpivot する列ベースで直感的。初心者に分かりやすい
stack()wide to long列レベルを index レベルへ畳み込むindex ベース。MultiIndex と相性が良い
wide_to_long()wide to long共通の prefix と数値 suffix を持つ列(例: score1, score2stub 名を自動解析する
pivot()long to wide値を列へ展開(キーが一意)melt() の逆
unstack()long to wideindex レベルを列へ展開stack() の逆

stack() を使うべき場合

stack() は列 index を操作し、それを行 index 側へ押し込みます。MultiIndex をすでに扱っていて、index レベルで整形したいときに有用です。

wide = pd.DataFrame({
    'math': [90, 78],
    'science': [85, 92]
}, index=['Alice', 'Bob'])
 
stacked = wide.stack()
print(stacked)

出力:

Alice  math       90
       science    85
Bob    math       78
       science    92
dtype: int64

結果は Series(MultiIndex 付き)になり、melt() のように列名付きのきれいな DataFrame にはなりません。明示的な列名を持つフラットな DataFrame が欲しい場合は melt() を使います。

wide_to_long() を使うべき場合

wide_to_long()score1, score2, score3 のような命名パターンに沿った列向けです。

df = pd.DataFrame({
    'student': ['Alice', 'Bob'],
    'score1': [90, 78],
    'score2': [85, 92],
    'score3': [88, 80]
})
 
long = pd.wide_to_long(df, stubnames='score', i='student', j='exam_num')
print(long.reset_index())

出力:

  student  exam_num  score
0   Alice         1     90
1   Alice         2     85
2   Alice         3     88
3     Bob         1     78
4     Bob         2     92
5     Bob         3     80

列名が一貫した prefix-suffix パターンに従っているなら wide_to_long() が便利です。そうでなければ melt() の方が柔軟です。

パフォーマンス上の考慮点

多くのデータセット(数百万行未満)では、melt() は十分高速です。100,000 行・50 列の DataFrame を melt するベンチマーク例は次のとおりです。

OperationApproximate Time
melt() with 50 value columns~15 ms
stack() equivalent~10 ms
Manual loop with concat()~500 ms

パフォーマンス改善のヒント:

  1. value_vars を明示する — 必要な列だけ melt する方が、全列を melt するより速いです。
  2. ignore_index=True を使う(デフォルト)— 元の index を保持するとオーバーヘッドが増えます。
  3. melt してすぐに再 pivot するのを避ける — 別のワイド形式が必要なら、往復するより pivot_table()rename() を直接検討してください。
  4. 非常に大規模な DataFrame(melt 後に 1億行+ など)では、lazy evaluation と並列処理を提供する polars や Dask の利用も検討してください。
# 実際に必要な列だけ melt する
long = df.melt(
    id_vars=['id'],
    value_vars=['col_a', 'col_b', 'col_c'],  # 50列すべてではない
    var_name='metric',
    value_name='reading'
)

よくあるエラーと対処法

1. KeyError: 列が見つからない

id_varsvalue_vars に指定した列名が DataFrame に存在しないと発生します。

# 誤り: 列名にタイプミス
long = df.melt(id_vars=['stduent'])  # KeyError
 
# 対処: 先に列名を確認する
print(df.columns.tolist())

2. 想定外の重複行が出る

melt 自体は重複を「作る」のではなく、id 変数の組み合わせごとに行を生成します。重複に見える場合、元データ側に識別子の重複行があることが原因です。

# id 列に重複があるか確認
print(df.duplicated(subset=['student']).sum())

3. value 列の dtype が混在する

異なる dtype の列を melt(例: int64float64)すると、pandas は value 列をより一般的な型へ昇格(upcast)します。数値列と文字列列を混ぜて melt すると value 列は object dtype になります。

df = pd.DataFrame({
    'id': [1, 2],
    'score': [90, 85],
    'grade': ['A', 'B']
})
 
long = df.melt(id_vars='id')
print(long.dtypes)
# variable    object
# value       object  <-- score と grade が両方 object になる

これを避けるには、数値列と文字列列を分けて melt してください。

PyGWalker で melt 後のデータを可視化する

データをワイド形式からロング形式へ整形したら、次は視覚的に探索するのが自然な流れです。分布の確認、グループ比較、外れ値の発見などに役立ちます。PyGWalker (opens in a new tab) は、任意の pandas DataFrame を Jupyter Notebook 内で Tableau のように操作できる、オープンソースの Python ライブラリです。

import pandas as pd
import pygwalker as pyg
 
# ワイドなデータをロング形式に変換
grades = pd.DataFrame({
    'student': ['Alice', 'Bob', 'Charlie', 'Diana'],
    'math': [90, 78, 85, 92],
    'science': [85, 92, 88, 79],
    'english': [88, 80, 91, 84]
})
 
long = grades.melt(id_vars='student', var_name='subject', value_name='score')
 
# インタラクティブ可視化を起動
walker = pyg.walk(long)

PyGWalker では、subject を x-axis に、score を y-axis にドラッグし、student で色分けすれば、科目ごとのパフォーマンス比較を即座に行えます。チャートコードは不要です。棒グラフ、散布図、箱ひげ図などをドラッグ&ドロップで作れます。

Google Colab (opens in a new tab)Kaggle (opens in a new tab) で PyGWalker を試すか、pip install pygwalker でインストールできます。

FAQ

pandas melt は何をしますか?

pandas の melt() は DataFrame をワイド形式からロング形式へ整形します。列を行に変換し、2つの新しい列(元の列名を入れる列=variable、値を入れる列=value)を作ります。これは「unpivot」とも呼ばれます。

pandas の melt と pivot の違いは何ですか?

melt() はワイド形式をロング形式に変換します(列が行になる)。pivot() はその逆で、ロング形式をワイド形式に変換します(行が列になる)。両者は逆操作です。同じパラメータで DataFrame を melt してから pivot すると、元の DataFrame に戻せます。

pandas では melt と stack をいつ使い分けるべきですか?

明示的な列名を持つきれいな DataFrame を得たい、かつ unpivot する列を制御したい場合は melt() を使います。MultiIndex 列を扱っていて、列レベルを行 index に押し込みたい場合は stack() を使います。melt() は初心者に直感的で、stack() は階層構造の整形により強力です。

pandas で複数列を melt するには?

value_vars に列名のリストを渡します: df.melt(id_vars=['id'], value_vars=['col_a', 'col_b', 'col_c'])。リストに含めた列がすべて行に展開されます。value_vars を省略すると、id_vars に含まれないすべての列が melt されます。

重複した列名を持つ DataFrame を melt できますか?

pandas は melt できますが、結果の variable 列に重複値が入るため分かりにくくなる可能性があります。曖昧さを避けるため、df.columns = [...]df.rename() で先に列名を変更してください。

pandas で melt を元に戻すには?

pivot() または pivot_table() を使って、melt 済み(ロング形式)のデータをワイド形式へ戻せます: long.pivot(index='id', columns='variable', values='value')。index と columns の組み合わせが重複して集約が必要なら pivot_table() を使います。

まとめ

pandas の melt() は、Python でワイド形式の DataFrame をロング(tidy)形式へ変換する標準的な方法です。要点は次のとおりです。

  • id_vars を使う — 識別子として保持する列を指定します。
  • value_vars を使う — melt する列を制御します。省略すると識別子以外がすべて対象になります。
  • var_namevalue_name を使う — 出力列に意味のある名前を付けます。
  • melt と pivot は逆操作 — wide-to-long は melt()、long-to-wide は pivot()
  • stack() より melt() を選ぶ — MultiIndex Series ではなく、フラットな DataFrame と明示的な列名が欲しい場合。
  • melt 後の後処理を行う — variable 列に対して文字列メソッドで prefix/suffix の除去や値の整形をします。

整形後のデータは、PyGWalker (opens in a new tab) のようなツールでチャートコードなしに視覚探索でき、分析ワークフローをより速く直感的にできます。

📚