Pandas Melt: 와이드 데이터를 롱 포맷으로 리쉐이프하기 (완전 가이드)
Updated on
각 월이 각각의 컬럼으로 들어있는 스프레드시트를 가지고 있다고 해봅시다. January, February, March… December까지. 수익 숫자가 12개 컬럼에 흩어져 있고, 이를 시계열로 플로팅하거나, groupby를 하거나, 머신러닝 모델에 넣어야 합니다. 그런데 이런 작업들은 wide-format 데이터에서는 잘 동작하지 않습니다. 월 이름을 담는 컬럼 하나와 수익 값을 담는 컬럼 하나가 필요합니다. 이 변환—즉 컬럼을 행으로 바꾸는 작업—을 정확히 해주는 것이 pandas melt입니다.
문제는 금방 더 복잡해집니다. 설문 데이터, 센서 측정값, 재무 보고서, Excel이나 SQL에서 피벗된 형태로 내보낸 데이터처럼 수십~수백 개 컬럼을 가진 wide 데이터셋은 흔합니다. 이를 수동으로 재구성하는 건 번거롭고 실수도 나기 쉽습니다. unpivot해야 할 컬럼이 하나 늘어날 때마다, 제대로 된 도구 없이 처리하려면 보일러플레이트 코드가 계속 늘어납니다.
pd.melt() 함수는 이를 한 번의 호출로 해결합니다. wide DataFrame을 받아 선택한 컬럼들을 행으로 “녹여(melt)” long 포맷으로 바꾸면서, 식별자(identifier) 컬럼은 그대로 보존합니다. 이 가이드에서는 전체 문법, 모든 파라미터, 실전 시나리오 기반 예제, 그리고 pivot, stack, wide_to_long 같은 관련 리쉐이핑 함수들과의 비교까지 다룹니다.
pd.melt()가 하는 일
pd.melt()는 DataFrame을 wide format에서 long format으로 unpivot합니다. wide format에서는 각 변수가 자기 컬럼을 하나씩 가집니다. long format(“tidy” 포맷이라고도 함)에서는 변수 이름을 담는 컬럼 하나와 값(value)을 담는 컬럼 하나가 있습니다.
개념적으로는 다음과 같은 변환입니다:
Wide format (melt 전):
| student | math | science | english |
|---|---|---|---|
| Alice | 90 | 85 | 88 |
| Bob | 78 | 92 | 80 |
Long format (melt 후):
| student | subject | score |
|---|---|---|
| Alice | math | 90 |
| Alice | science | 85 |
| Alice | english | 88 |
| Bob | math | 78 |
| Bob | science | 92 |
| Bob | english | 80 |
여기서 student 컬럼은 식별자로 유지되고, 3개의 과목 컬럼은 두 개 컬럼으로 melt됩니다: 하나는 과목명(subject), 다른 하나는 점수(score)를 담습니다.
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)파라미터 레퍼런스
| Parameter | Description | Default |
|---|---|---|
frame | melt할 DataFrame (df.melt()를 쓰면 필요 없음) | Required |
id_vars | 식별자 변수로 유지할 컬럼(들) (melt되지 않음) | None |
value_vars | 행으로 unpivot할 컬럼(들). 생략하면 id_vars에 없는 모든 컬럼이 사용됨 | None |
var_name | 기존 컬럼명(변수명)을 담는 새 컬럼의 이름 | 'variable' |
value_name | 값(value)을 담는 새 컬럼의 이름 | 'value' |
col_level | 컬럼이 MultiIndex일 때, melt할 레벨 | None |
ignore_index | True면 결과가 새 정수 인덱스를 사용. False면 기존 인덱스를 유지 | 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)Output:
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)Output:
month temp_f
0 Jan 30
1 Jan 28
2 Feb 32
3 Feb 31
4 Mar 45
5 Mar 42모든 컬럼이 측정값이고 보존할 식별자가 없는 경우에 유용합니다.
식별자 컬럼이 여러 개인 경우
실제 데이터셋에는 식별자가 하나 이상인 경우가 많습니다. 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)Output:
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 1200region과 product가 모든 행에 대해 보존되고, 4개의 분기 매출 컬럼이 두 컬럼으로 접힙니다.
melt 이후 데이터 정리하기
melt 이후 variable 컬럼에는 종종 정리하고 싶은 문자열이 들어있습니다. 위 예제에서 quarter 값은 Q1이 아니라 q1_revenue 같은 형태입니다. 문자열 연산으로 정리할 수 있습니다:
long_sales['quarter'] = long_sales['quarter'].str.replace('_revenue', '').str.upper()
print(long_sales.head())Output:
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)Output:
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 4income은 melt 대상 컬럼이 아니지만 식별자 컬럼으로 유지됩니다.
실전 예제: 시계열 데이터
재무/거시경제 데이터는 날짜가 컬럼 헤더로 오는 wide 포맷인 경우가 많습니다. 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)Output:
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의 컬럼 헤더가 multi-level인 경우 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)복잡한 multi-level 시나리오에서는 melt 전에 droplevel()로 레벨을 내리거나, 언더스코어로 레벨들을 join해서 컬럼을 평탄화(flatten)한 뒤 melt해야 할 때도 있습니다.
Melt vs Pivot: 서로 역연산
melt()와 pivot()은 서로 역연산입니다. melt는 wide → long, pivot은 long → wide로 변환합니다.
import pandas as pd
# Start wide
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 (round-trip)
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)Output:
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-column 조합이 유일해야 합니다. long 데이터에 중복이 있다면 집계 함수를 사용하는 pivot_table()을 대신 사용해야 합니다.
Melt vs Stack vs wide_to_long
pandas에는 여러 리쉐이핑 함수가 있습니다. 각각의 사용 시점은 다음과 같습니다:
| Function | Direction | Best For | Key Difference |
|---|---|---|---|
melt() | wide to long | 특정 컬럼들을 행으로 unpivot | 컬럼 기반; 초보자에게 가장 직관적 |
stack() | wide to long | 컬럼 레벨을 인덱스 레벨로 내려서 접기 | 인덱스 기반; MultiIndex에 강함 |
wide_to_long() | wide to long | 공통 prefix + 숫자 suffix 컬럼 (예: score1, score2) | stub name을 자동 파싱 |
pivot() | long to wide | 값을 컬럼으로 펼치기(유일 키) | melt()의 역연산 |
unstack() | long to wide | 인덱스 레벨을 컬럼으로 확장 | stack()의 역연산 |
stack()을 대신 써야 하는 경우
stack()은 컬럼 인덱스를 행 인덱스로 밀어 넣습니다. 이미 MultiIndex를 쓰고 있고 인덱스 레벨에서 리쉐이프하고 싶을 때 유용합니다:
wide = pd.DataFrame({
'math': [90, 78],
'science': [85, 92]
}, index=['Alice', 'Bob'])
stacked = wide.stack()
print(stacked)Output:
Alice math 90
science 85
Bob math 78
science 92
dtype: int64결과는 DataFrame이 아니라 MultiIndex를 가진 Series입니다(melt()처럼 이름이 있는 컬럼을 가진 깔끔한 DataFrame이 아님). 명시적 컬럼명을 가진 평평한(flat) 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())Output:
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컬럼을 melt하는 벤치마크 예시는 다음과 같습니다:
| Operation | Approximate Time |
|---|---|
melt() with 50 value columns | ~15 ms |
stack() equivalent | ~10 ms |
Manual loop with concat() | ~500 ms |
성능을 더 끌어올리는 팁:
value_vars를 명시적으로 지정 — 필요한 컬럼만 melt하는 것이 전체를 melt하는 것보다 빠릅니다.ignore_index=True사용(기본값) — 원래 인덱스를 보존하면 오버헤드가 생깁니다.- melt 후 즉시 다시 pivot하는 왕복을 피하기 — 다른 wide 포맷이 필요하면 round-trip 대신
pivot_table()이나rename()등을 직접 고려하세요. - 아주 큰 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: Column Not Found
id_vars 또는 value_vars에 지정한 컬럼명이 DataFrame에 없을 때 발생합니다:
# Wrong: 컬럼 이름에 오타가 있음
long = df.melt(id_vars=['stduent']) # KeyError
# Fix: 먼저 컬럼명을 확인
print(df.columns.tolist())2. 기대하지 않은 중복 행이 생김
melt 자체가 중복 행을 “만드는” 것은 아닙니다. id 변수 조합마다 한 행씩 생성할 뿐입니다. 중복이 보인다면 원본 데이터에 식별자 행이 중복되어 있었을 가능성이 큽니다:
# id 컬럼 기준 중복 확인
print(df.duplicated(subset=['student']).sum())3. value 컬럼의 dtype이 섞이는 문제
서로 다른 dtype 컬럼들을 함께 melt하면(예: int64와 float64 혼합) 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 결과 시각화하기
wide 포맷에서 long 포맷으로 데이터를 리쉐이프한 다음 자연스러운 다음 단계는 결과를 시각적으로 탐색하는 것입니다. 분포를 확인하고, 그룹을 비교하고, 이상치를 찾는 작업이죠. PyGWalker (opens in a new tab)는 어떤 pandas DataFrame이든 Jupyter Notebook 안에서 Tableau처럼 인터랙티브하게 탐색할 수 있게 해주는 오픈소스 Python 라이브러리입니다.
import pandas as pd
import pygwalker as pyg
# wide 데이터를 long 포맷으로 melt
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로 색상을 나누면 과목별 성과를 즉시 비교할 수 있습니다—차트 코드를 한 줄도 쓰지 않고도 가능합니다. bar chart, scatter plot, box plot 등도 드래그앤드롭으로 지원합니다.
Google Colab (opens in a new tab), Kaggle (opens in a new tab)에서 PyGWalker를 사용해보거나,
pip install pygwalker로 설치할 수 있습니다.
FAQ
pandas melt는 무엇을 하나요?
pandas melt()는 DataFrame을 wide 포맷에서 long 포맷으로 리쉐이프합니다. 컬럼들을 행으로 바꾸면서 두 개의 새 컬럼을 만듭니다: 하나는 원래 컬럼명(변수명, variable), 다른 하나는 값(value)입니다. 이는 “unpivoting”이라고도 합니다.
pandas에서 melt와 pivot의 차이는 무엇인가요?
melt()는 wide 포맷을 long 포맷으로 변환합니다(컬럼이 행이 됨). pivot()은 그 반대로 long 포맷을 wide 포맷으로 변환합니다(행이 컬럼이 됨). 둘은 역연산 관계입니다. 같은 파라미터로 DataFrame을 melt한 뒤 결과를 pivot하면 원래 DataFrame으로 되돌릴 수 있습니다.
pandas에서 melt와 stack 중 언제 무엇을 써야 하나요?
이름이 있는 컬럼을 갖는 깔끔한 DataFrame을 원하고, 어떤 컬럼을 unpivot할지 명시적으로 제어하고 싶다면 melt()를 사용하세요. MultiIndex 컬럼을 다루고 있고, 컬럼 레벨을 행 인덱스 레벨로 내리고 싶다면 stack()을 사용하세요. melt()는 초보자에게 더 직관적이고, stack()은 계층형 리쉐이핑에서 더 강력합니다.
pandas에서 여러 컬럼을 melt하려면 어떻게 하나요?
value_vars 파라미터에 컬럼명 리스트를 전달하세요: df.melt(id_vars=['id'], value_vars=['col_a', 'col_b', 'col_c']). 지정한 컬럼들이 모두 행으로 unpivot됩니다. value_vars를 생략하면 pandas는 id_vars에 없는 모든 컬럼을 melt합니다.
컬럼명이 중복인 DataFrame도 melt할 수 있나요?
pandas는 melt할 수 있지만, 결과에서 variable 컬럼에 중복 값이 생겨 혼란스러울 수 있습니다. 모호함을 피하려면 df.columns = [...] 또는 df.rename()으로 중복 컬럼을 먼저 이름 변경하는 것이 좋습니다.
pandas에서 melt를 되돌리려면 어떻게 하나요?
pivot() 또는 pivot_table()을 사용해 melt된(long) 데이터를 다시 wide 포맷으로 바꿀 수 있습니다: long.pivot(index='id', columns='variable', values='value'). index-column 조합이 중복되어 집계가 필요하면 pivot_table()을 사용하세요.
결론
pandas melt() 함수는 Python에서 wide-format DataFrame을 long(tidy) 포맷으로 바꾸는 표준적인 방법입니다. 핵심 요약은 다음과 같습니다:
- 식별자로 유지할 컬럼은 **
id_vars**로 지정합니다. - melt할 컬럼은 **
value_vars**로 제어합니다. 이를 생략하면 식별자 외의 모든 컬럼이 melt됩니다. - 결과 컬럼 이름은 **
var_name,value_name**으로 의미 있게 지정하세요. - melt와 pivot은 역연산입니다 — wide→long은
melt(), long→wide는pivot()입니다. - MultiIndex Series가 아니라 명시적인 컬럼을 가진 평평한 DataFrame이 필요하면
stack()보다melt()가 적합합니다. - melt 후에는 정리(clean-up)가 자주 필요합니다 — variable 컬럼에 대해 string 메서드로 prefix/suffix 제거, 포맷 정규화를 하세요.
데이터를 리쉐이프한 뒤에는 PyGWalker (opens in a new tab) 같은 도구로 차트 코드를 쓰지 않고도 결과를 시각적으로 탐색할 수 있어 분석 워크플로우를 더 빠르고 직관적으로 만들 수 있습니다.