Pandas 資料重塑

資料重塑(Reshape)是將資料在「長格式」和「寬格式」之間轉換的操作。這在資料分析和視覺化中經常需要用到。

長格式 vs 寬格式

寬格式(Wide Format):每個變數佔一欄

     name  math  english
0   Alice    90       85
1     Bob    80       90

長格式(Long Format):一欄是變數名稱,一欄是值

     name  subject  score
0   Alice     math     90
1   Alice  english     85
2     Bob     math     80
3     Bob  english     90

melt():寬轉長

import pandas as pd

# 寬格式資料
df_wide = pd.DataFrame({
    'name': ['Alice', 'Bob'],
    'math': [90, 80],
    'english': [85, 90]
})

# 轉成長格式
df_long = df_wide.melt(id_vars='name', var_name='subject', value_name='score')
print(df_long)
    name  subject  score
0  Alice     math     90
1    Bob     math     80
2  Alice  english     85
3    Bob  english     90

melt 參數說明

df.melt(
    id_vars='name',      # 保持不變的欄位(識別欄位)
    value_vars=['math', 'english'],  # 要轉換的欄位(預設全部)
    var_name='subject',   # 新的變數名稱欄位
    value_name='score'    # 新的值欄位
)

多個識別欄位

df = pd.DataFrame({
    'name': ['Alice', 'Bob'],
    'class': ['A', 'B'],
    'math': [90, 80],
    'english': [85, 90]
})

df_long = df.melt(id_vars=['name', 'class'], var_name='subject', value_name='score')

pivot():長轉寬

df_long = pd.DataFrame({
    'name': ['Alice', 'Alice', 'Bob', 'Bob'],
    'subject': ['math', 'english', 'math', 'english'],
    'score': [90, 85, 80, 90]
})

# 轉成寬格式
df_wide = df_long.pivot(index='name', columns='subject', values='score')
print(df_wide)
subject  english  math
name                  
Alice         85    90
Bob           90    80

如果有重複的組合,pivot() 會報錯,需要使用 pivot_table()

stack() 和 unstack()

stack():將欄位層級轉成索引

df = pd.DataFrame({
    'math': [90, 80],
    'english': [85, 90]
}, index=['Alice', 'Bob'])

stacked = df.stack()
print(stacked)
Alice  math       90
       english    85
Bob    math       80
       english    90
dtype: int64

unstack():將索引層級轉成欄位

unstacked = stacked.unstack()
print(unstacked)
       english  math
Alice       85    90
Bob         90    80

指定層級

對於多層索引,可以指定要操作的層級:

df = pd.DataFrame({
    'year': [2023, 2023, 2024, 2024],
    'city': ['Taipei', 'Tokyo', 'Taipei', 'Tokyo'],
    'sales': [100, 200, 150, 250]
}).set_index(['year', 'city'])

# unstack 第二層(city)
print(df.unstack(level='city'))

# unstack 第一層(year)
print(df.unstack(level='year'))

wide_to_long()

處理有規則命名的欄位:

df = pd.DataFrame({
    'name': ['Alice', 'Bob'],
    'score_2023': [90, 80],
    'score_2024': [92, 85]
})

# 轉成長格式
df_long = pd.wide_to_long(
    df,
    stubnames='score',  # 欄位前綴
    i='name',           # 識別欄位
    j='year',           # 新的變數欄位名稱
    sep='_'             # 分隔符號
)
print(df_long.reset_index())
    name  year  score
0  Alice  2023     90
1  Alice  2024     92
2    Bob  2023     80
3    Bob  2024     85

transpose():轉置

df = pd.DataFrame({
    'A': [1, 2, 3],
    'B': [4, 5, 6]
})

# 轉置(列欄互換)
df_t = df.T
# 或
df_t = df.transpose()
print(df_t)
   0  1  2
A  1  2  3
B  4  5  6

explode():展開列表

將包含列表的儲存格展開成多列:

df = pd.DataFrame({
    'name': ['Alice', 'Bob'],
    'hobbies': [['reading', 'music'], ['sports']]
})

df_exploded = df.explode('hobbies')
print(df_exploded)
    name  hobbies
0  Alice  reading
0  Alice    music
1    Bob   sports

實際應用

資料視覺化前處理

很多視覺化工具需要長格式資料:

# 原始寬格式
df = pd.DataFrame({
    'month': ['Jan', 'Feb', 'Mar'],
    'product_A': [100, 120, 150],
    'product_B': [80, 90, 110]
})

# 轉成長格式以便繪圖
df_long = df.melt(id_vars='month', var_name='product', value_name='sales')

多時間點資料

df = pd.DataFrame({
    'id': [1, 2],
    'pre_test': [60, 70],
    'post_test': [75, 85]
})

# 轉換
df_long = df.melt(id_vars='id', var_name='test_time', value_name='score')
df_long['test_time'] = df_long['test_time'].str.replace('_test', '')

建立交叉表

df = pd.DataFrame({
    'gender': ['M', 'F', 'M', 'F', 'M'],
    'city': ['Taipei', 'Tokyo', 'Taipei', 'Taipei', 'Tokyo'],
    'count': [1, 1, 1, 1, 1]
})

# 使用 pivot_table 建立交叉表
cross_tab = df.pivot_table(index='gender', columns='city', values='count', aggfunc='sum', fill_value=0)