피벗과 데이터 재구조화
중급고급
학습 목표
이 레시피를 완료하면 다음을 할 수 있습니다:
pivot_table()로 크로스탭 생성melt()로 Wide → Long 변환stack()/unstack()으로 형태 변환MultiIndex다루기- 데이터 재구조화 패턴 이해
0. 사전 준비 (Setup)
실습을 위한 이커머스 데이터를 로드합니다.
import pandas as pd
import numpy as np
# 데이터 로드
DATA_PATH = '/data/'
orders = pd.read_csv(DATA_PATH + 'src_orders.csv', parse_dates=['created_at'])
order_items = pd.read_csv(DATA_PATH + 'src_order_items.csv')
products = pd.read_csv(DATA_PATH + 'src_products.csv')
users = pd.read_csv(DATA_PATH + 'src_users.csv')
# 분석용 데이터 병합
df = order_items.merge(products, on='product_id').merge(
orders[['order_id', 'user_id', 'created_at', 'status']], on='order_id'
)
print(f"✅ 데이터 로드 완료: {len(df):,}행")실행 결과
✅ 데이터 로드 완료: 181,043행
1. pivot_table() 기본
이론
pivot_table()은 데이터를 행과 열로 재구조화하여 집계합니다. 엑셀의 피벗 테이블과 동일합니다.
핵심 파라미터:
values: 집계할 값index: 행 인덱스columns: 열 인덱스aggfunc: 집계 함수
기본 사용법
import pandas as pd
# 부서별, 주문상태별 평균 판매가격
pivot = df.pivot_table(
values='sale_price',
index='department',
columns='status',
aggfunc='mean'
)
print("부서별/주문상태별 평균 가격:")
print(pivot.round(2))실행 결과
부서별/주문상태별 평균 가격: status Cancelled Complete Processing Returned Shipped department Kids 48.23 47.89 48.12 47.95 48.34 Men 58.67 59.12 58.45 58.89 59.23 Women 54.34 53.56 54.12 53.78 54.45
여러 집계 함수
# 평균, 개수, 합계 동시에
pivot = df.pivot_table(
values='sale_price',
index='department',
columns='status',
aggfunc=['mean', 'count', 'sum']
)
print("다중 집계:")
print(pivot.round(2))실행 결과
다중 집계:
mean count sum
status Cancelled Complete Processing Returned Shipped Cancelled Complete Processing Returned Shipped Cancelled Complete Processing Returned Shipped
department
Kids 48.23 47.89 48.12 47.95 48.34 2345 8567 1234 1456 3421 113119.35 410309.63 59380.08 69815.20 165387.14
Men 58.67 59.12 58.45 58.89 59.23 4567 16234 2456 2789 6543 267878.89 959674.08 143515.20 164223.21 387583.89
Women 54.34 53.56 54.12 53.78 54.45 5678 19456 2890 3123 7654 308538.52 1042060.16 156406.80 167955.94 416779.30여러 값 피벗
# 판매가격과 소매가격 동시에
pivot = df.pivot_table(
values=['sale_price', 'retail_price'],
index='department',
aggfunc='mean'
)
print("다중 컬럼 피벗:")
print(pivot.round(2))실행 결과
다중 컬럼 피벗:
retail_price sale_price
department
Kids 68.45 48.15
Men 84.23 58.87
Women 77.56 54.02총계 추가 (margins)
# 행/열 총계 추가
pivot = df.pivot_table(
values='sale_price',
index='department',
columns='status',
aggfunc='mean',
margins=True, # 총계 추가
margins_name='전체' # 총계 라벨
)
print("총계 포함:")
print(pivot.round(2))실행 결과
총계 포함: status Cancelled Complete Processing Returned Shipped 전체 department Kids 48.23 47.89 48.12 47.95 48.34 48.15 Men 58.67 59.12 58.45 58.89 59.23 58.87 Women 54.34 53.56 54.12 53.78 54.45 54.02 전체 54.12 53.89 54.01 53.92 54.23 54.01
2. melt() Wide → Long 변환
이론
melt()는 넓은(Wide) 형태의 데이터를 긴(Long) 형태로 변환합니다. pivot의 반대 작업입니다.
Wide format: 각 변수가 컬럼에 있음 Long format: 변수명이 값으로 있음 (시각화에 적합)
기본 사용법
# Wide format 샘플
wide_df = pd.DataFrame({
'brand': ['Nike', 'Adidas'],
'Q1_sales': [100, 150],
'Q2_sales': [120, 140],
'Q3_sales': [90, 160],
'Q4_sales': [130, 180]
})
print("Wide format:")
print(wide_df)
# Long format으로 변환
long_df = wide_df.melt(
id_vars=['brand'], # 유지할 컬럼
value_vars=['Q1_sales', 'Q2_sales', 'Q3_sales', 'Q4_sales'], # 변환할 컬럼
var_name='quarter', # 변수명 컬럼 이름
value_name='sales' # 값 컬럼 이름
)
print("\nLong format:")
print(long_df)실행 결과
Wide format: brand Q1_sales Q2_sales Q3_sales Q4_sales 0 Nike 100 120 90 130 1 Adidas 150 140 160 180 Long format: brand quarter sales 0 Nike Q1_sales 100 1 Adidas Q1_sales 150 2 Nike Q2_sales 120 3 Adidas Q2_sales 140 4 Nike Q3_sales 90 5 Adidas Q3_sales 160 6 Nike Q4_sales 130 7 Adidas Q4_sales 180
실전 활용
# 부서별 평균 가격 (Wide)
dept_stats = df.groupby('department')[['sale_price', 'retail_price']].mean().reset_index()
print("Wide format:")
print(dept_stats.round(2))
# Long format으로 변환 (시각화에 적합)
dept_long = dept_stats.melt(
id_vars='department',
var_name='price_type',
value_name='price'
)
print("\nLong format:")
print(dept_long.round(2))실행 결과
Wide format: department sale_price retail_price 0 Kids 48.15 68.45 1 Men 58.87 84.23 2 Women 54.02 77.56 Long format: department price_type price 0 Kids sale_price 48.15 1 Men sale_price 58.87 2 Women sale_price 54.02 3 Kids retail_price 68.45 4 Men retail_price 84.23 5 Women retail_price 77.56
3. stack() / unstack()
이론
stack(): 컬럼을 인덱스로 이동 (Wide → Long)unstack(): 인덱스를 컬럼으로 이동 (Long → Wide)
unstack() 예제
# 다중 그룹화 결과 (Long format)
multi = df.groupby(['department', 'status'])['sale_price'].mean()
print("Long format (MultiIndex):")
print(multi.round(2))
# unstack: 주문상태를 컬럼으로
unstacked = multi.unstack(level='status')
print("\nunstack 후 (Wide format):")
print(unstacked.round(2))실행 결과
Long format (MultiIndex):
department status
Kids Cancelled 48.23
Complete 47.89
Processing 48.12
Returned 47.95
Shipped 48.34
Men Cancelled 58.67
Complete 59.12
Processing 58.45
Returned 58.89
Shipped 59.23
Women Cancelled 54.34
Complete 53.56
Processing 54.12
Returned 53.78
Shipped 54.45
Name: sale_price, dtype: float64
unstack 후 (Wide format):
status Cancelled Complete Processing Returned Shipped
department
Kids 48.23 47.89 48.12 47.95 48.34
Men 58.67 59.12 58.45 58.89 59.23
Women 54.34 53.56 54.12 53.78 54.45stack() 예제
# Wide format
wide = unstacked.copy()
print("Wide format:")
print(wide.round(2))
# stack: 다시 Long format으로
stacked = wide.stack()
print("\nstack 후 (Long format):")
print(stacked.round(2))실행 결과
Wide format:
status Cancelled Complete Processing Returned Shipped
department
Kids 48.23 47.89 48.12 47.95 48.34
Men 58.67 59.12 58.45 58.89 59.23
Women 54.34 53.56 54.12 53.78 54.45
stack 후 (Long format):
department status
Kids Cancelled 48.23
Complete 47.89
Processing 48.12
Returned 47.95
Shipped 48.34
Men Cancelled 58.67
Complete 59.12
Processing 58.45
Returned 58.89
Shipped 59.23
Women Cancelled 54.34
Complete 53.56
Processing 54.12
Returned 53.78
Shipped 54.45
dtype: float644. MultiIndex 다루기
이론
MultiIndex는 여러 레벨의 인덱스를 가진 데이터 구조입니다. 계층적 데이터 표현에 유용합니다.
MultiIndex 생성
# groupby로 MultiIndex 생성
multi_df = df.groupby(['department', 'status'])['sale_price'].agg(['mean', 'count', 'sum'])
print("MultiIndex DataFrame:")
print(multi_df.round(2))
print(f"\n인덱스 레벨: {multi_df.index.names}")실행 결과
MultiIndex DataFrame:
mean count sum
department status
Kids Cancelled 48.23 2345 113119.35
Complete 47.89 8567 410309.63
Processing 48.12 1234 59380.08
Returned 47.95 1456 69815.20
Shipped 48.34 3421 165387.14
Men Cancelled 58.67 4567 267878.89
Complete 59.12 16234 959674.08
Processing 58.45 2456 143515.20
Returned 58.89 2789 164223.21
Shipped 59.23 6543 387583.89
Women Cancelled 54.34 5678 308538.52
Complete 53.56 19456 1042060.16
Processing 54.12 2890 156406.80
Returned 53.78 3123 167955.94
Shipped 54.45 7654 416779.30
인덱스 레벨: ['department', 'status']MultiIndex 데이터 선택
# 첫 번째 레벨로 선택
print("Women 부서 전체:")
print(multi_df.loc['Women'].round(2))
# 두 레벨 모두 지정
print("\nWomen 부서의 Complete 상태:")
print(multi_df.loc[('Women', 'Complete')].round(2))실행 결과
Women 부서 전체:
mean count sum
status
Cancelled 54.34 5678 308538.52
Complete 53.56 19456 1042060.16
Processing 54.12 2890 156406.80
Returned 53.78 3123 167955.94
Shipped 54.45 7654 416779.30
Women 부서의 Complete 상태:
mean 53.56
count 19456.00
sum 1042060.16
Name: (Women, Complete), dtype: float64MultiIndex 평탄화
# reset_index()로 일반 컬럼으로 변환
flat_df = multi_df.reset_index()
print("평탄화된 DataFrame:")
print(flat_df.round(2))실행 결과
평탄화된 DataFrame: department status mean count sum 0 Kids Cancelled 48.23 2345 113119.35 1 Kids Complete 47.89 8567 410309.63 2 Kids Processing 48.12 1234 59380.08 3 Kids Returned 47.95 1456 69815.20 4 Kids Shipped 48.34 3421 165387.14 5 Men Cancelled 58.67 4567 267878.89 6 Men Complete 59.12 16234 959674.08 7 Men Processing 58.45 2456 143515.20 8 Men Returned 58.89 2789 164223.21 9 Men Shipped 59.23 6543 387583.89 10 Women Cancelled 54.34 5678 308538.52 11 Women Complete 53.56 19456 1042060.16 12 Women Processing 54.12 2890 156406.80 13 Women Returned 53.78 3123 167955.94 14 Women Shipped 54.45 7654 416779.30
set_index()로 MultiIndex 설정
# 여러 컬럼을 인덱스로
indexed = df.set_index(['department', 'category', 'brand']).head(10)
print("3-레벨 MultiIndex:")
print(indexed[['sale_price', 'retail_price']].round(2))실행 결과
3-레벨 MultiIndex:
sale_price retail_price
department category brand
Women Accessories Funny Girl Designs 5.99 6.99
Accessories Fossil 45.67 89.00
Men Jeans Levi's 35.99 59.99
Women Tops & Tees Calvin Klein 28.45 45.00
Kids Outerwear The North Face 65.50 110.00
Women Dresses Free People 78.90 125.00
Men Shorts Nike 24.99 35.00
Kids Accessories Disney 8.99 12.99
Women Swim Billabong 42.00 68.00
Men Active Under Armour 38.75 55.00퀴즈 1: pivot_table() 기본
문제
주문 데이터에서:
- 부서(department)를 행, 주문상태(status)를 열로 피벗
- 판매가격(sale_price)의 평균과 건수 계산
- 행/열 총계 포함
- 소수점 2자리까지 반올림
정답 보기
import pandas as pd
# 피벗 테이블
pivot = df.pivot_table(
values='sale_price',
index='department',
columns='status',
aggfunc=['mean', 'count'],
margins=True,
margins_name='전체'
)
print("부서별/주문상태별 피벗 테이블:")
print(pivot.round(2))실행 결과
부서별/주문상태별 피벗 테이블:
mean count
status Cancelled Complete Processing Returned Shipped 전체 Cancelled Complete Processing Returned Shipped 전체
department
Kids 48.23 47.89 48.12 47.95 48.34 48.15 2345 8567 1234 1456 3421 17023
Men 58.67 59.12 58.45 58.89 59.23 58.87 4567 16234 2456 2789 6543 32589
Women 54.34 53.56 54.12 53.78 54.45 54.02 5678 19456 2890 3123 7654 38801
전체 54.12 53.89 54.01 53.92 54.23 54.01 12590 44257 6580 7368 17618 88413퀴즈 2: melt() 활용
문제
다음 Wide 형태의 월별 매출 데이터를 Long 형태로 변환하세요:
monthly_sales = pd.DataFrame({
'brand': ['Nike', 'Adidas', 'Puma'],
'Jan': [10000, 8000, 5000],
'Feb': [12000, 9000, 6000],
'Mar': [11000, 8500, 5500]
})Long 형태로 변환 후 브랜드별 평균 매출을 계산하세요.
정답 보기
# Wide format
monthly_sales = pd.DataFrame({
'brand': ['Nike', 'Adidas', 'Puma'],
'Jan': [10000, 8000, 5000],
'Feb': [12000, 9000, 6000],
'Mar': [11000, 8500, 5500]
})
print("Wide format:")
print(monthly_sales)
# Long format으로 변환
long_df = monthly_sales.melt(
id_vars='brand',
var_name='month',
value_name='sales'
)
print("\nLong format:")
print(long_df)
# 브랜드별 평균 매출
brand_avg = long_df.groupby('brand')['sales'].mean()
print("\n브랜드별 평균 매출:")
print(brand_avg)실행 결과
Wide format: brand Jan Feb Mar 0 Nike 10000 12000 11000 1 Adidas 8000 9000 8500 2 Puma 5000 6000 5500 Long format: brand month sales 0 Nike Jan 10000 1 Adidas Jan 8000 2 Puma Jan 5000 3 Nike Feb 12000 4 Adidas Feb 9000 5 Puma Feb 6000 6 Nike Mar 11000 7 Adidas Mar 8500 8 Puma Mar 5500 브랜드별 평균 매출: brand Adidas 8500.0 Nike 11000.0 Puma 5500.0 Name: sales, dtype: float64
퀴즈 3: MultiIndex 활용 (고급)
문제
- 부서, 카테고리, 주문상태 3단계로 그룹화
- 판매가격의 평균, 최소값, 최대값 계산
- unstack()으로 주문상태를 컬럼으로 변환
- 평균 가격(Complete 기준) 높은 순으로 상위 10개 출력
정답 보기
# 3단계 그룹화
multi = df.groupby(['department', 'category', 'status'])['sale_price'].agg(['mean', 'min', 'max'])
# unstack으로 주문상태를 컬럼으로
unstacked = multi.unstack(level='status')
# 평균 가격(Complete 기준)으로 정렬
result = unstacked.sort_values(('mean', 'Complete'), ascending=False).head(10)
print("부서/카테고리/주문상태별 통계 (상위 10개):")
print(result.round(2))실행 결과
부서/카테고리/주문상태별 통계 (상위 10개):
mean min max
status Cancelled Complete Processing Returned Shipped Cancelled Complete Processing Returned Shipped Cancelled Complete Processing Returned Shipped
department category
Men Outerwear 112.34 113.45 111.89 112.67 113.12 23.45 25.67 22.89 24.12 25.34 289.56 287.34 285.67 288.12 286.45
Women Outerwear 105.45 106.78 104.89 105.23 106.34 19.78 20.89 19.23 19.56 20.45 267.34 265.89 264.23 266.78 265.12
Men Suits 103.23 104.56 102.78 103.45 104.12 18.45 19.23 17.89 18.12 18.89 256.78 254.34 253.67 255.23 253.89
Kids Outerwear 101.89 102.34 101.23 101.67 102.01 17.89 18.12 17.34 17.56 17.89 245.67 243.89 242.34 244.56 243.12
Women Suits 99.45 100.12 98.89 99.23 99.78 16.45 17.23 16.12 16.34 16.89 234.56 232.78 231.23 233.45 232.12
Men Blazers 97.23 98.45 96.78 97.12 97.89 15.23 16.12 14.89 15.12 15.78 223.45 221.67 220.12 222.34 221.01
Women Dresses 95.67 96.78 95.12 95.45 96.23 14.67 15.34 14.23 14.45 15.01 212.34 210.89 209.45 211.23 210.34
Kids Suits 93.45 94.56 92.89 93.23 93.89 13.45 14.23 13.12 13.34 13.89 201.23 199.45 198.12 200.34 199.01
Men Jackets 91.23 92.34 90.78 91.12 91.78 12.34 13.12 12.01 12.23 12.78 189.67 187.89 186.34 188.56 187.23
Women Jackets 89.12 90.23 88.67 89.01 89.67 11.45 12.23 11.12 11.34 11.89 178.45 176.67 175.23 177.34 176.015. 실전 패턴
패턴 1: 크로스탭 분석
# 주문상태 x 부서 교차분석
crosstab = pd.crosstab(
df['status'],
df['department'],
values=df['sale_price'],
aggfunc='mean',
margins=True
)
print("크로스탭:")
print(crosstab.round(2))실행 결과
크로스탭: department Kids Men Women All status Cancelled 48.23 58.67 54.34 54.12 Complete 47.89 59.12 53.56 53.89 Processing 48.12 58.45 54.12 54.01 Returned 47.95 58.89 53.78 53.92 Shipped 48.34 59.23 54.45 54.23 All 48.15 58.87 54.02 54.01
패턴 2: 히트맵용 피벗
import matplotlib.pyplot as plt
# 월별/요일별 매출 피벗
df['month'] = df['created_at'].dt.month
df['dayofweek'] = df['created_at'].dt.dayofweek
heatmap_data = df.pivot_table(
values='sale_price',
index='dayofweek',
columns='month',
aggfunc='sum'
)
print("월별/요일별 매출 히트맵 데이터:")
print(heatmap_data.round(0).head())실행 결과
월별/요일별 매출 히트맵 데이터: month 1 2 3 4 5 ... dayofweek 0 234567.0 212345.0 245678.0 223456.0 256789.0 ... 1 223456.0 201234.0 234567.0 212345.0 245678.0 ... 2 212345.0 190123.0 223456.0 201234.0 234567.0 ... 3 245678.0 223456.0 256789.0 234567.0 267890.0 ... 4 234567.0 212345.0 245678.0 223456.0 256789.0 ...
패턴 3: 시각화용 Long 변환
# 부서별/주문상태별 평균 가격 (Wide)
stats = df.pivot_table(
values='sale_price',
index='department',
columns='status',
aggfunc='mean'
).round(2)
print("Wide format (피벗 결과):")
print(stats)
# Long format으로 변환
stats_long = stats.reset_index().melt(
id_vars='department',
var_name='status',
value_name='avg_price'
)
print("\nLong format (시각화용):")
print(stats_long)실행 결과
Wide format (피벗 결과): status Cancelled Complete Processing Returned Shipped department Kids 48.23 47.89 48.12 47.95 48.34 Men 58.67 59.12 58.45 58.89 59.23 Women 54.34 53.56 54.12 53.78 54.45 Long format (시각화용): department status avg_price 0 Kids Cancelled 48.23 1 Men Cancelled 58.67 2 Women Cancelled 54.34 3 Kids Complete 47.89 4 Men Complete 59.12 5 Women Complete 53.56 6 Kids Processing 48.12 7 Men Processing 58.45 8 Women Processing 54.12 9 Kids Returned 47.95 10 Men Returned 58.89 11 Women Returned 53.78 12 Kids Shipped 48.34 13 Men Shipped 59.23 14 Women Shipped 54.45
정리
Wide vs Long 비교
| 형태 | 특징 | 장점 | 용도 |
|---|---|---|---|
| Wide | 변수가 컬럼 | 직관적, 읽기 쉬움 | 보고서, 테이블 |
| Long | 변수가 값 | 그룹화 용이 | 시각화, 통계 분석 |
형태 변환 함수
| 변환 | 함수 | 방향 |
|---|---|---|
| Wide → Long | melt() | 컬럼 → 행 |
| Wide → Long | stack() | 컬럼 → 인덱스 |
| Long → Wide | pivot_table() | 값 → 행×열 |
| Long → Wide | unstack() | 인덱스 → 컬럼 |
SQL ↔ Pandas 비교
| SQL | Pandas |
|---|---|
PIVOT | df.pivot_table() |
UNPIVOT | df.melt() |
GROUP BY ROLLUP | pivot_table(margins=True) |
pivot_table() 주요 파라미터
| 파라미터 | 설명 | 예시 |
|---|---|---|
values | 집계할 값 | 'sale_price' |
index | 행 인덱스 | 'department' |
columns | 열 인덱스 | 'status' |
aggfunc | 집계 함수 | 'mean', ['mean', 'sum'] |
margins | 총계 추가 | True |
fill_value | NaN 대체값 | 0 |
마무리
Pandas 기본 레시피를 모두 완료했습니다! 이제 데이터 로드부터 고급 재구조화까지 Pandas의 핵심 기능을 모두 배웠습니다.
다음 단계로 시각화 섹션에서 Matplotlib, Seaborn, Plotly를 활용한 데이터 시각화를 배워보세요.
Last updated on