Learning Objectives
- 用 sort_values 依一欄或多欄排序,設定由大到小,並說出排序後的索引怎麼變
- 用整欄運算新增計算欄位(例如加權總分、是否及格)
- 分辨會改到原表的 df["新欄"] = … 與回傳新表的 assign、sort_values
- 用 apply 對每個值套用自己寫的函數,並說出何時改用 np.where、pd.cut 這類向量化寫法
排名與換算
選出需要的資料之後,常見的下一步是排序(誰的期末最高、哪門課缺席最多)和新增計算欄位 (期中占 40%、期末占 60% 的總分,是否及格,等第)。這一課學三件事:
| 想做的事 | 寫法 |
|---|---|
| 依某欄排序 | df.sort_values("final", ascending=False) |
| 新增一欄,用其他欄算出來 | df["total"] = df["midterm"] * 0.4 + df["final"] * 0.6 |
| 對每個值套用自己寫的規則 | df["final"].apply(letter),或向量化的 np.where、pd.cut |
資料一樣是 grades.csv(600 筆修課成績),這是程式產生的模擬資料,不是任何學校的真實資料。
逐步看:依期末排序
注意每次印出的最左欄(索引):排序時整列一起移動,索引跟著走。
新增欄位:整欄一起算
df["total"] = df["midterm"] * 0.4 + df["final"] * 0.6 不需要迴圈:右邊的運算對每一列各自做一次,
得到一整欄的結果,再放進新欄。這種「整欄一起算」的寫法稱為向量化(vectorization)。
- 右邊可以是數字運算、比較(得到 True/False 的欄),也可以是一個值(每一列都放同一個值)。
- 欄名已經存在時,會覆蓋那一欄。
df["新欄"] = …會改到df本身;df.assign(新欄=…)和sort_values一樣,回傳一個新的資料表,原本的表不變。 想保留結果,要指定回去:df = df.sort_values("total")。
逐步看:總分、進步分數與是否及格
看記憶體區的 df:每執行一行 df["…"] = …,表格右邊就多一欄。
apply 與向量化
有些規則不是一條算式,例如等第:80 分以上 A、70 分以上 B、60 分以上 C、其他 F。最直覺的寫法是寫一個函數,
再用 apply 對每個值呼叫一次:df["final"].apply(letter)。
apply 很有彈性,但它其實是在幕後跑一個 Python 迴圈,資料有幾列就呼叫幾次函數。資料多的時候,改用向量化的寫法通常快很多,
也比較短:
| 規則 | 向量化寫法 |
|---|---|
| 兩種結果(及格/不及格) | np.where(df["final"] >= 60, "及格", "不及格") |
| 依區間分組(等第、年齡層) | pd.cut(df["final"], bins=[0, 60, 70, 80, 101], right=False, labels=["F", "C", "B", "A"]) |
| 一般的算式 | 直接用運算子:df["a"] * 0.4 + df["b"] * 0.6 |
pd.cut 的 bins 是區間的邊界;right=False 表示每個區間包含左端、不含右端,例如 [60, 70) 是 60~69 分。
最後一個邊界寫 101,100 分才會落在 A。
逐步看:apply 逐一呼叫,np.where 與 pd.cut 一次算完
只對前 4 列用 apply,好看清楚 letter 被呼叫了 4 次;整份 600 列改用向量化的寫法。
實作:總分前五名
讀入 grades.csv,新增 total 欄:期中(midterm)× 0.4 + 期末(final)× 0.6,四捨五入到小數 1 位。
再依 total 由高到低排序存進 top,最後一行會印出前 5 名的學號、課程與總分。
小測驗:排序與新增欄位
下面程式會印出什麼?
import pandas as pd
s = pd.Series([30, 10, 20], index=["a", "b", "c"])
print(s.sort_values(ascending=False).index.tolist())
下面程式會印出什麼?
import pandas as pd
df = pd.DataFrame({"x": [3, 1, 2]})
df.sort_values("x")
print(df["x"].tolist())
下面程式會印出什麼?
import pandas as pd
df = pd.DataFrame({"a": [1, 2, 3], "b": [10, 20, 30]})
df["c"] = df["b"] - df["a"] * 2
print(df["c"].tolist())
要把 60 萬筆期末成績換成「及格/不及格」,關於 apply 與 np.where,哪一個說法正確?
下面程式會印出什麼?
import numpy as np
import pandas as pd
s = pd.Series([55, 60, 72])
print(np.where(s >= 60, "P", "F").tolist())
重點整理
sort_values("欄", ascending=False)由大到小;多欄排序用串列,ascending也用串列一欄對應一個。- 排序時整列一起移動,索引跟著走;
reset_index(drop=True)重新編號。同分的順序不保證,需要固定時加第二個排序鍵。 df["新欄"] = 整欄運算會改到原表;assign、sort_values回傳新表,要保留就指定回去。apply對每個值呼叫一次函數,彈性大但慢;兩種結果用np.where,依區間分組用pd.cut,一般算式直接用運算子。- 下一課處理缺值:期中缺考的 13 筆在算總分時變成 NaN,該刪掉、補值,還是保留?