National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 80 24°C PM2.5 21
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 11

長表與寬表

pivot_table、melt、crosstab
Interaction實作 30 minute(s)

Learning Objectives

  • 分辨長表與寬表,說出整齊資料(tidy data)的三條規則
  • 用 pivot_table 把長表轉成寬表,說明 aggfunc 在什麼時候會用到、寬表的 NaN 代表什麼
  • 用 melt 把寬表轉回長表,再接 groupby 或 pivot_table
  • 用 crosstab 做兩個類別欄位的交叉表,並用 normalize、margins 算比例與總和

同一份資料,兩種形狀

同樣是三位學生、兩門課的期末成績,可以寫成兩種形狀:

長表(一列一筆修課):

學號 課程 期末
S1 微積分 62
S1 英文 80
S2 微積分 71
S2 英文 75
S3 英文 90

寬表(一列一位學生、一欄一門課):

學號 微積分 英文
S1 62 80
S2 71 75
S3 NaN 90

寬表適合給人看、做報表:一眼就能比較同一個人的各科成績。長表適合給程式算: groupby("課程")、畫圖時依課程分色、篩選某一門課,都只要指定「課程」這一欄。 這一課學兩個方向的轉換:長轉寬用 pivot_table,寬轉長用 melt。

課堂資料 grades.csv、students.csv 是程式產生的模擬資料,不是任何學校的真實資料。

逐步看:長表轉寬表,再轉回來

先用 5 列的小長表。注意右邊的寬表:列是學號、欄是課程,S3 沒修微積分的那一格會是什麼?

整齊資料:一列一個觀察、一欄一個變數

Hadley Wickham 在 2014 年的論文〈Tidy Data〉(Journal of Statistical Software, 59(10))把「方便分析的形狀」整理成三條規則:

  1. 每一個變數是一欄。
  2. 每一個觀察是一列。
  3. 每一種觀察單位是一張表。

用這三條來看成績:「期中」和「期末」是同一個變數(分數)在兩次考試的值,還是兩個不同的變數? 要比較兩次考試時,把它們攤成「考試」與「分數」兩欄(長表)比較方便;要算每位學生的進步分數(期末 − 期中)時,留在同一列(寬表)比較方便。 所以沒有永遠正確的形狀,依接下來要做的計算選形狀,需要時再轉。第 3 條規則也解釋了上一課為什麼資料會分成學生表、成績表、社團表: 它們的觀察單位不同(一位學生、一筆修課、一筆社團紀錄)。

互動練習:每一格從哪裡來

上面是長表,轉成寬表後,點寬表的任何一格,就會標出它是從長表的哪幾列算出來的。注意 S2 的英文有兩筆(重修)。

逐步看:600 筆成績轉成 200 × 6 的寬表

換成真正的課堂資料。每位學生只修其中幾門課,所以寬表會有很多 NaN。後半段先合併系所(上一課),再做「系所 × 課程」的平均表。

逐步看:把期中、期末攤成長表

成績表裡「期中」「期末」是兩欄。想用一次 groupby 比較兩次考試,就先 melt。

交叉表:兩個類別欄位各有幾筆

兩個欄位都是類別(例如系所與是否住宿)時,最常問的是「每一種組合各有幾個人」。pd.crosstab(列, 欄) 直接算出這張次數表, 等於 pivot_table(..., aggfunc="count") 的簡寫:

參數 作用
margins=True 加上每列、每欄的總和(名稱是 All)
normalize="index" 每一列加起來是 1(各系內部的比例)
normalize="columns" 每一欄加起來是 1
normalize="all" 全部格子加起來是 1

比較人數不同的組時要看比例:企管系 52 人、物理系 15 人,直接比「住宿人數」沒有意義,比「住宿比例」才有。 第 21 課會用卡方檢定問:這些比例的差異,大到不太像是抽樣的偶然嗎?

逐步看:各系住宿的人數與比例

同一張交叉表先看人數,再看每一列的比例,最後畫成堆疊長條圖。

實作:各課程的期中與期末平均

讀入 grades.csv,先用 melt 把 midterm、final 攤成 exam 與 score 兩欄(保留 student_id、course), 再用 pivot_table 做出「列是課程、欄是考試」的平均表,四捨五入到小數 1 位後印出。

你的輸出
預期輸出

小測驗:轉換形狀

1.

長表有欄位 student_id、course、final,共有 200 位學生、6 門課。pivot_table(index="student_id", columns="course", values="final") 的形狀是?

2.

下面程式會印出什麼?

import pandas as pd
df = pd.DataFrame({"id": ["a", "a", "b"], "k": ["x", "x", "y"], "v": [1, 3, 5]})
print(df.pivot_table(index="id", columns="k", values="v").loc["a", "x"])
3.

下面程式會印出什麼?

import pandas as pd
w = pd.DataFrame({"id": [1, 2, 3], "q1": [5, 4, 3], "q2": [2, 2, 1], "q3": [4, 5, 5]})
print(w.melt(id_vars="id").shape)
第 1 次作答

小測驗:pivot 與交叉表

1.

pd.crosstab(students["dept"], students["dorm"], normalize="index") 的結果有什麼特性?

2.

pivot(不是 pivot_table)遇到同一個 id、同一個 k 有兩列時會怎樣?

import pandas as pd
df = pd.DataFrame({"id": ["a", "a"], "k": ["x", "x"], "v": [1, 3]})
print(df.pivot(index="id", columns="k", values="v"))
第 1 次作答

重點整理

  • 長表一列一筆觀察,適合 groupby、篩選與畫圖;寬表一列一個對象,適合給人比較與做報表。
  • 整齊資料(Wickham, 2014):每個變數一欄、每個觀察一列、每種觀察單位一張表。依接下來的計算選形狀。
  • pivot_table(index=, columns=, values=, aggfunc=) 長轉寬;同一格有好幾筆時用 aggfunc 彙總(預設 mean)。寬表的 NaN 代表「沒有這個組合」。
  • melt(id_vars=, value_vars=, var_name=, value_name=) 寬轉長;寬表的每一格變成一列,缺值也算,需要時再 dropna()。
  • pd.crosstab 算兩個類別欄位的次數表;margins=True 加總和,normalize="index" 看每一列的比例。
  • 下一課處理時間:把文字轉成日期、依週或月彙總、算移動平均,以及「一天」該從幾點算起。
這一課完成了!
Next lesson:時間資料
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。