返回文章列表

Pandas 資料表格寬長格式轉換技巧

本文探討 Pandas 中資料表格寬格式與長格式之間的轉換技巧,涵蓋 `stack`、`unstack`、`melt` 和 `wide_to_long` 等方法,並搭配實際案例說明如何根據不同情境選擇合適的轉換策略,提升資料處理效率。

資料科學 Python

在資料分析過程中,經常需要在寬格式和長格式資料表格之間切換,以利於不同分析工具和模型的使用。Pandas 提供了多種方法來實作這些轉換,例如 stackunstackmeltwide_to_long 等。選擇哪種方法取決於資料的結構和分析目標。對於簡單的轉換,stackmelt 通常足以應付;而當需要從欄位名稱中提取資訊時,wide_to_long 則更為適用。理解這些方法的差異和應用場景,能有效提升資料處理效率,讓資料分析工作更加順暢。

資料表格轉換的技巧

從廣式轉換為長式資料表格

在資料建模中,我們常會遇到「廣式」資料表格,即每一行代表一個特定的狀態或實體,而不同的資料列則分別表示該實體的不同屬性。以水果生產資料為例,我們可以將各州生產的不同水果數量放在不同的列中:

import pandas as pd

data = {
    "state": ["Texas", "Arizona", "Florida"],
    "Apple": [12, 9, 0],
    "Orange": [10, 7, 14],
    "Banana": [40, 12, 190]
}

df = pd.DataFrame(data).set_index("state")
df = df.convert_dtypes(dtype_backend="numpy_nullable")
df

此圖示

假設我們想將這個廣式表格轉換為「長式」資料表格,即每一行代表某個州和某種水果的組合。這時我們可以使用 pd.DataFrame.stack 方法來實作。這個方法會將水果的列標籤移到行索引中,形成一個多層索引(MultiIndex):

stacked_df = df.stack()
stacked_df

內容解密:

此段程式碼首先建立了一個包含美國各州及其生產的各種水果數量的 DataFrame。接著使用 convert_dtypes 方法將 DataFrame 的型態轉換為支援可空型別(nullable types)。最後使用 stack 方法將原本的列標籤移到行索引中,形成一個多層索引(MultiIndex),使得每一行都代表一個州與某種水果的組合。

此圖示

還原為 DataFrame

通常,在呼叫 pd.DataFrame.stack 後,會繼續使用 pd.Series.reset_index 方法將多層索引轉換回 DataFrame,並且可以指定新的列名稱:

long_df = stacked_df.reset_index(name="number_grown")
long_df

內容解密:

在這段程式碼中,我們使用 reset_index 方法將之前轉換後的多層索引還原為一般 DataFrame。引數 name 用於指定新增加的一列名稱為 “number_grown”。

此圖示

長式轉換回廣式

有時候我們需要將長式資料表格轉換回廣式。這樣做可以讓資料更加緊湊、便於顯示和分析。例如,當我們需要總結資料時,使用兩維度來顯示通常比讓觀眾滾動觀看大量資料更有效。

讓我們從前面的長式資料開始:

stacked_series = long_df.set_index(["state", "fruit"])["number_grown"]
stacked_series.unstack()

內容解密:

在這段程式碼中,我們首先使用 set_index 機制將原本的 DataFrame 的列名設定為 MultiIndex(多重索引),然後使用 unstack() 函式將內層索引(fruits)展開成欄位。

測試 unstack 的 level


# 預設狀況下,unstack() 傾向將最內層的行索引移到欄位上。
# level=0 則是指將最外層的行索引移到欄位上。
# level="state" 則是根據行索引名稱來決定要移動哪一層。
wide_df_unstack_level_innermost = stacked_series.unstack()
wide_df_unstack_level_outmost = stacked_series.unstack(level=0)
wide_df_unstack_level_name = stacked_series.unstack(level="state")

print("Default unstack:")
print(wide_df_unstack_level_innermost)
print("\nUnstack by outermost level:")
print(wide_df_unstack_level_outmost)
print("\nUnstack by level name 'state':")
print(wide_df_unstack_level_name)

說明 reshape 的 unstack 的三種情境

假如你能正確理解 unstack 的三種情境,當然你就能正確運用:

  • 預設狀況下 unstack() 是依照最內層的 MultiIndex 層級來操作。
  • 指定 level=0 是依照最外層的 MultiIndex 層級來操作。
  • 指定 level=name 是依照指定名稱來操作。

內容解密:

這段程式碼演示了 unstack() 函式的三種不同操作方式:預設情況下會展開最內層索引、指定展開最外層索引以及根據索引名稱展開。透過這些操作,你可以根據需求選擇適合自己的資料展開方式。

此圖示 default unstack:

特殊狀況

Unstack by outermost level:
Unstack by name state:
@startuml
note
  無法自動轉換的 Plantuml 圖表
  請手動檢查和調整
@enduml

Reshaping with pd.DataFrame.melt

在上述範例中,玄貓針對資料表格進行轉換時,是先把 state 放到 row index 中再進行 stack 操作。但在某些情況下,可能並不希望進行這樣的預處理步驟。這時可以使用 pd.DataFrame.melt 函式來直接轉換資料表格。


df = pd.DataFrame([["Texas",      "apple",         "orange",        "banana"],["Arizona",      "orange"      "apple"      "banana"],["Florida",       "orange"      "apple"        "banana"]],columns=["state","fruit","value"])

df.convert_dtypes(dtype_backend="numpy_nullable")
df.melt(id_vars=["state"],var_name="fruit",value_name="number_grown")

內容解密:

在這段程式碼中,我們建立了一個包含各州和其對應水果數量的 DataFrame。接著利用 melt 函式直接將其從廣式轉換為長式格式。引數 id_vars=["state"] 指定不參與融化操作的欄位;var_name="fruit"value_name="number_grown" 分別指定融化後新增欄位名稱。

特殊狀況:

此處很值得注意:

  • 預設狀況下 column name 需要特別指定。
  • 若是想只挑選特定 column 或排除特定 column 則可以使用 value_vars=[] 或 exclude_cols=[]。

拆解實戰案例:只挑選特定欄位融化:


df.melt(id_vars=["state"],var_name="fruit",value_name="number_grown",value_vars=["apple","orange"])

如果不希望包含 banana 的值則可以透過 value_vars=[“apple”,“orange”] 指定僅融化 apple 和 orange 的值。

內容解密:

這段程式碼演示瞭如何利用 pd.DataFrame.melt 函式來進行更精細控制的廣至長轉換。透過 value_vars 指定僅融化特定欄位(如 apple 和 orange),可以有效篩選出所需之資料。

擴充套件與轉換 DataFrame 格式

在資料科學的實務應用中,經常需要將資料在寬格式(wide format)和長格式(long format)之間進行轉換。這不僅有助於資料的視覺化和分析,還能夠滿足不同分析工具和模型的需求。本章將介紹如何使用 pandas 函式庫中的多種方法來進行這些轉換,並以具體案例說明其應用。

使用 pd.wide_to_long 轉換寬格式為長格式

在前文中,我們已經介紹了使用 pd.DataFrame.stackpd.DataFrame.melt 將寬格式資料轉換為長格式。這些方法雖然功能強大,但當資料結構較為複雜時,可能會顯得繁瑣。這時,pd.wide_to_long 函式就派上了用場。

情境介紹

假設我們有一個 DataFrame,其中包含一個 widget 變數和四個代表業務季度銷售資料的列。每個銷售列都以 "quarter_" 作為字首:

import pandas as pd

df = pd.DataFrame([
    ["Widget 1", 1, 2, 4, 8],
    ["Widget 2", 16, 32, 64, 128],
], columns=["widget", "quarter_1", "quarter_2", "quarter_3", "quarter_4"])
df = df.convert_dtypes(dtype_backend="numpy_nullable")
資料展示
    widget  quarter_1  quarter_2  quarter_3  quarter_4
0   Widget 1          1          2          4          8
1   Widget 2         16         32         64        128

轉換方法

如果我們回到之前使用 pd.DataFrame.stack 的範例,可以透過以下方法將資料從寬格式轉換為長格式:

df.set_index("widget").stack().reset_index().rename(columns={
    "level_1": "quarter",
    0: "quantity",
})
結果展示
    widget     quarter  quantity
0   Widget 1    quarter_1         1
1   Widget 1    quarter_2         2
2   Widget 1    quarter_3         4
3   Widget 1    quarter_4         8
4   Widget 2    quarter_1        16
5   Widget 2    quarter_2        32
6   Widget 2    quarter_3        64
7   Widget 2    quarter_4       128

使用 pd.DataFrame.melt 則可以更加簡潔地完成這個轉換:

df.melt(
    id_vars=["widget"],
    var_name="quarter",
    value_name="quantity",
)
結果展示
    widget     quarter quantity
0   Widget 1     quarter_1        1
1   Widget 2     quarter_1       16
2   Widget 1     quarter_2        2
3   Widget 2     quarter_2       32
4   Widget 1     quarter_3        4
5   Widget 2     quarter_3       64
6   Widget 1     quarter_4        8
7   Widget     quarter_4       (此處應該是Widget)      .      (此處應該是2)       (此處應該是)

然而,這些方法並不能直接從列標籤中提取出新的變數。pd.wide_to_long 則能夠解決這個問題,將季度字首 "quarter_" 提取出來,留下數字部分:

pd.wide_to_long(
    df,
    i=["widget"],
    stubnames="quarter_",
    j="quarter"
).reset_index().rename(columns={"quarter_": "quantity"})
結果展示
widget quarter quantity
0 Widget      (此處應該是1)      .      (此處應該是)       (此處應該是)

內容解密:

  • i=["widget"]: 指定唯一識別變數。
  • stubnames="quarter_": 指定要提取的列字首。
  • j="quarter": 指定新的變數名稱。
  • reset_index(): 還原索引。
  • rename(columns={"quarter_": "quantity"}): 還原原來的列名。

在不同 DataFrame 轉換方法間的選擇

根據實際情況,可以選擇不同的轉換方法:

  • 簡單且直接pd.DataFrame.stackpd.DataFrame.melt 是最常見且簡單的選擇。
  • 結構複雜:當需要從列標籤中提取新變數時,pd.wide_to_long 是更好的選擇。

自動化與高效性

在實務中,我們經常需要將大量資料從寬格式轉換為長格式,或反之亦然。這些方法不僅能夠提高資料處理的效率,還能夠確保資料的完整性和一致性。

其他轉換方法

除了上述方法外,pandas 中還提供了其他轉換工具,如 pd.pivotpd.pivot_table。這些工具不僅能夠進行格式轉換,還能夠進行資料聚合和分析。

情境介紹

假設我們有一個長格式的 DataFrame,包含州份、水果種類別、生產量和食用量等資訊:

df = pd.DataFrame([
    ["Texas", "apple", "養殖車間", "養殖車間", ],
    ["Arizona", "apple", "養殖車間", "養殖車間"],
],
columns=["state", "fruit", "number_grown"]
df = df.convert_dtypes(dtype_backend="numpy_nullable")
資料展示
		state		fruit		number_grown		number_eaten		(此處應該沒有出現)
0	Texas			apple				(此處應該沒有出現)						(此處應該沒有出現)
...
8	Florida			banana			(此處應該沒有出現)						(此處應該沒有出現)

轉換方法

使用 pd.pivot可以直接將長格式資料轉換為寬格式:

df.pivot(index=["state"], columns=["fruit"])
結果展示
Florida0			(此處應該是)			0			(此處應該是)
Texas			(此處應該沒有出現)					(此處應該沒有出現)

內容解密:

  • index=["state"]: 指定行索引。
  • columns=["fruit"]: 指定列索引。

自動化與高效性

在實務中,我們經常需要將大量資料從長格式轉換為寬格式。這些方法不僅能夠提高資料處理的效率,還能夠確保資料的完整性和一致性。