① 依序閱讀:每節先讀說明;遇到互動元件時,先預測結果,再操作驗證。 ② 對照程式範例:程式碼卡下方的「來源」標了課程練習筆記本(lab);需要實作時,再打開原始筆記本對照。 ③ 檢查理解:答錯時先看回饋,再回到相關說明;標為延伸的內容可留待第二輪。 ④ 最後用重點速查與來源複習。
教科書:An Introduction to Statistical Learning with Applications in Python(ISLP)。章節旁的來源標記可點到本頁書目。
先用三個小動作認識流程:依條件選一條路、對每個數字重做一次、把步驟包成可重用的函式。這一頁先練 Python,再連回課程的模型程式。把重複步驟寫成一個函式,修改時較不容易漏改其中一份。
同一段分析程式碼在 notebook 裡複製了三次。最大的風險是什麼?
& 與 and 的運算對象 課程 Lab Ch2 · lab 範例if 需要一個真假值;and/or 可以合併純量條件。下面則是一整欄同時判斷的寫法。
and 一次只能判斷一個真假值;& 是逐元素做「且」,一整排一起算。選資料的時候永遠是後者,而且每個條件都要用括號包起來,因為 & 的優先順序比 > 高。
& 把它們逐列做「且」。按按鈕切換運算子。
df['year'] > 80 & df['mpg'] > 30 會被讀成 df['year'] > (80 & df['mpg']) > 30,然後拋一個看起來莫名其妙的錯。每個條件都加上括號,這條沒有例外。
weight origin name plymouth reliant 2490.0 1 buick skylark 2635.0 1 dodge aries wagon (sw) 2620.0 1 chevrolet citation 2725.0 1 plymouth reliant 2385.0 1 toyota starlet 1755.0 3 plymouth champ 1875.0 1 honda civic 1300 1760.0 3 subaru 2065.0 3 datsun 210 mpg 1975.0 3 toyota tercel 2050.0 3 mazda glc 4 1985.0 3 plymouth horizon 4 2215.0 1 ford escort 4w 2045.0 1 ford escort 2h 2380.0 1 volkswagen jetta 2190.0 2 honda prelude 2210.0 3 toyota corolla 2350.0 3 datsun 200sx 2615.0 3 mazda 626 2635.0 3 peugeot 505s turbo diesel 3230.0 2 volvo diesel 3160.0 2 toyota cressida 2900.0 3 datsun 810 maxima 2930.0 3 buick century 3415.0 1 oldsmobile cutlass ls 3725.0 1 ford granada gl 3060.0 1 chrysler lebaron salon 3465.0 1 chevrolet cavalier 2605.0 1 chevrolet cavalier wagon 2640.0 1 chevrolet cavalier 2-door 2395.0 1 pontiac j2000 se hatchback 2575.0 1 dodge aries se 2525.0 1 pontiac phoenix 2735.0 1 ford fairmont futura 2865.0 1 volkswagen rabbit l 1980.0 2 mazda glc custom l 2025.0 3 mazda glc custom 1970.0 3 plymouth horizon miser 2125.0 1 mercury lynx l 2125.0 1 nissan stanza xe 2160.0 3 honda accord 2205.0 3 toyota corolla 2245.0 3 honda civic 1965.0 3 honda civic (auto) 1965.0 3 datsun 310 gx 1995.0 3 buick century limited 2945.0 1 oldsmobile cutlass ciera (diesel) 3015.0 1 chrysler lebaron medallion 2585.0 1 ford granada l 2835.0 1 toyota celica gt 2665.0 3 dodge charger 2.2 2370.0 1 chevrolet camaro 2950.0 1 ford mustang gl 2790.0 1 vw pickup 2130.0 2 dodge rampage 2295.0 1 ford ranger 2625.0 1 chevy s-10 2720.0 1
先算出一整排 True/False,再拿它去 loc 選列。
Ch02-statlearn-lab-zh.ipynb
weight origin name toyota starlet 1755.0 3 plymouth champ 1875.0 1 honda civic 1300 1760.0 3 subaru 2065.0 3 datsun 210 mpg 1975.0 3 toyota tercel 2050.0 3 mazda glc 4 1985.0 3 plymouth horizon 4 2215.0 1 ford escort 4w 2045.0 1 volkswagen jetta 2190.0 2 honda prelude 2210.0 3 toyota corolla 2350.0 3 datsun 200sx 2615.0 3 mazda 626 2635.0 3 volvo diesel 3160.0 2 chevrolet cavalier 2-door 2395.0 1 pontiac j2000 se hatchback 2575.0 1 volkswagen rabbit l 1980.0 2 mazda glc custom l 2025.0 3 mazda glc custom 1970.0 3 plymouth horizon miser 2125.0 1 mercury lynx l 2125.0 1 nissan stanza xe 2160.0 3 honda accord 2205.0 3 toyota corolla 2245.0 3 honda civic 1965.0 3 honda civic (auto) 1965.0 3 datsun 310 gx 1995.0 3 oldsmobile cutlass ciera (diesel) 3015.0 1 toyota celica gt 2665.0 3 dodge charger 2.2 2370.0 1 vw pickup 2130.0 2 dodge rampage 2295.0 1 chevy s-10 2720.0 1
注意兩個條件各自都有括號。另外這裡用了 lambda df:。它讓你在鏈式操作中間也能引用「當下這張表」。
Ch02-statlearn-lab-zh.ipynb
weight origin name ford maverick 2587.0 1 datsun pl510 2130.0 3 datsun pl510 2130.0 3 ford torino 500 3302.0 1 ford mustang 3139.0 1 datsun 1200 1613.0 3 ford pinto runabout 2226.0 1 ford pinto (sw) 2395.0 1 datsun 510 (sw) 2288.0 3 ford maverick 3021.0 1 datsun 610 2379.0 3 ford pinto 2310.0 1 datsun b210 1950.0 3 ford pinto 2451.0 1 datsun 710 2003.0 3 ford maverick 3158.0 1 ford pinto 2639.0 1 datsun 710 2545.0 3 ford pinto 2984.0 1 ford maverick 3012.0 1 ford granada ghia 3574.0 1 datsun b-210 1990.0 3 ford pinto 2565.0 1 datsun f-10 hatchback 1945.0 3 ford granada 3525.0 1 ford mustang ii 2+2 2755.0 1 datsun 810 2815.0 3 ford fiesta 1800.0 1 datsun b210 gx 2070.0 3 ford fairmont (auto) 2965.0 1 ford fairmont (man) 2720.0 1 datsun 510 2300.0 3 datsun 200-sx 2405.0 3 ford fairmont 4 2890.0 1 datsun 210 2020.0 3 datsun 310 2019.0 3 ford fairmont 2870.0 1 datsun 510 hatchback 2434.0 3 datsun 210 2110.0 3 datsun 280-zx 2910.0 3 datsun 210 mpg 1975.0 3 ford escort 4w 2045.0 1 ford escort 2h 2380.0 1 datsun 200sx 2615.0 3 datsun 810 maxima 2930.0 3 ford granada gl 3060.0 1 ford fairmont futura 2865.0 1 datsun 310 gx 1995.0 3 ford granada l 2835.0 1 ford mustang gl 2790.0 1 ford ranger 2625.0 1
排氣量小於 300,而且(是 ford 或 datsun)。括號決定了誰先算,跟數學一樣。
來源:Ch02-statlearn-lab-zh.ipynb
| 情境 | 用什麼 | 為什麼 |
|---|---|---|
| 一個 if 判斷 | and / or / not | 只有一個真假值 |
| 選 DataFrame 的列 | & / | / ~ | 一整排真假值要逐元素算 |
| NumPy 陣列的條件 | & / | / ~ | 同上 |
| 檢查「有沒有任何一個」 | .any() / .all() | 把一整排收成一個真假值 |
Auto[(Auto['year'] > 80) and (Auto['mpg'] > 30)] 會發生什麼?
迴圈就是「把同一件事對每一個東西各做一次」。統計程式裡它出現在三個地方: 試不同的參數(多項式次數、λ)、重抽樣(自助法的 B 次)、 逐欄處理(每一欄算遺漏比例)。
total += value 這個迴圈,右邊的變數表會同步更新。這正是 lab 範例 的那一段。
boot_SE 裡面是 for _ in range(B):,B 通常是 1000。看懂這個三圈的迴圈,那個一千圈的也就看懂了。
Total is: 24
total += value 是 total = total + value 的簡寫。縮排決定了哪些行屬於迴圈裡面——Python 用縮排代替大括號。
Ch02-statlearn-lab-zh.ipynb
Total is: 144
裡面那圈跑完,外面才前進一格,所以總共跑 3×3 = 9 次。巢狀兩層以上通常代表該換個寫法了。
來源:Ch02-statlearn-lab-zh.ipynb
Weighted average is: 10.8
zip 把兩個串列逐位配對,所以這一段算的是加權平均。長度不一樣時,它會在短的那個結束時停下來。
Ch02-statlearn-lab-zh.ipynb
| 你想走過什麼 | 寫法 |
|---|---|
| 一個串列的每個元素 | for x in a: |
| 連同位置一起 | for i, x in enumerate(a): |
| 兩個串列配對 | for x, w in zip(a, ws): |
| 跑固定次數 | for _ in range(B): |
| 字典的鍵與值 | for k, v in d.items(): |
| DataFrame 的每一欄 | for col in df.columns: |
for value, weight in zip([2,3,19], [0.2,0.3,0.5]): 會跑幾圈?
函式用 return 交回結果;沒有寫 return 時,回傳 None,不會自動取最後一行。
函式有三個部分:名字、參數(要給它什麼)、 回傳值(它給你什麼)。寫得好的函式就像一個黑盒子—— 用的人只要知道這三件事,不必知道裡面怎麼做。
假設你要比較三種多項式次數的驗證誤差。不寫函式的話,你會把同一段 「切分 → 擬合 → 預測 → 算 MSE」複製三次,只改中間一個數字。 三份幾乎一樣的程式碼,就是三個各自會出錯、而且改了一份忘了改另外兩份的地方。
evalMSE(terms, response, train, test),然後用一個迴圈跑三次。程式碼從多份重複變成一份共用邏輯,修改時較容易保持一致。
以下先讀懂函式如何被重用。要實跑,請開啟 重抽樣方法的課程筆記本,
先執行套件安裝與匯入、Auto 資料載入與切分,再執行下面的函式與呼叫。
這會準備好 MS、sm、poly、Auto_train 與 Auto_valid。
這個函式取自課程 lab 的模型比較流程。四個參數:要放哪些項、反應變數是誰、訓練集、測試集。
來源:Ch05-resample-lab-zh.ipynb
array([25.57387819, 22.21802005, 22.66767544])
三個次數的驗證 MSE 一次算完。要改成試五個次數,把 range(1, 4) 改成 range(1, 6),並把存放結果的 np.zeros(3) 改成 np.zeros(5),避免陣列越界。
Ch05-resample-lab-zh.ipynb
evalMSE:四個參數進去、裡面做四件事、一個數字出來。
return,函式立刻結束並把值交出去,後面的行不會執行。沒有寫 return 的函式會回傳 None——忘記寫是很常見的 bug。
這裡的函式參數是呼叫時接收輸入的名稱,例如 X_train。
統計的母體參數則描述母體或機率模型,例如平均數 μ;要由語境分辨。
這個函式由參數接收資料,較容易換資料重用。是否有副作用,還要看它會不會修改傳入物件、全域狀態或檔案。
def evalMSE(terms, response, ...) 裡的 terms);呼叫時實際傳進去的值叫引數。日常對話裡兩個字常常混用,看得懂就好。lambda df: df['year'] > 80 建立一個接受 df 的函式,並把冒號後的運算式結果回傳。
它相當於定義一個函式,內容只有 return df['year'] > 80。
loc 接到這個函式時,會把當下的資料框傳給它,再使用回傳的布林條件選列。
weight origin name plymouth reliant 2490.0 1 buick skylark 2635.0 1 dodge aries wagon (sw) 2620.0 1 chevrolet citation 2725.0 1 plymouth reliant 2385.0 1 toyota starlet 1755.0 3 plymouth champ 1875.0 1 honda civic 1300 1760.0 3 subaru 2065.0 3 datsun 210 mpg 1975.0 3 toyota tercel 2050.0 3 mazda glc 4 1985.0 3 plymouth horizon 4 2215.0 1 ford escort 4w 2045.0 1 ford escort 2h 2380.0 1 volkswagen jetta 2190.0 2 honda prelude 2210.0 3 toyota corolla 2350.0 3 datsun 200sx 2615.0 3 mazda 626 2635.0 3 peugeot 505s turbo diesel 3230.0 2 volvo diesel 3160.0 2 toyota cressida 2900.0 3 datsun 810 maxima 2930.0 3 buick century 3415.0 1 oldsmobile cutlass ls 3725.0 1 ford granada gl 3060.0 1 chrysler lebaron salon 3465.0 1 chevrolet cavalier 2605.0 1 chevrolet cavalier wagon 2640.0 1 chevrolet cavalier 2-door 2395.0 1 pontiac j2000 se hatchback 2575.0 1 dodge aries se 2525.0 1 pontiac phoenix 2735.0 1 ford fairmont futura 2865.0 1 volkswagen rabbit l 1980.0 2 mazda glc custom l 2025.0 3 mazda glc custom 1970.0 3 plymouth horizon miser 2125.0 1 mercury lynx l 2125.0 1 nissan stanza xe 2160.0 3 honda accord 2205.0 3 toyota corolla 2245.0 3 honda civic 1965.0 3 honda civic (auto) 1965.0 3 datsun 310 gx 1995.0 3 buick century limited 2945.0 1 oldsmobile cutlass ciera (diesel) 3015.0 1 chrysler lebaron medallion 2585.0 1 ford granada l 2835.0 1 toyota celica gt 2665.0 3 dodge charger 2.2 2370.0 1 chevrolet camaro 2950.0 1 ford mustang gl 2790.0 1 vw pickup 2130.0 2 dodge rampage 2295.0 1 ford ranger 2625.0 1 chevy s-10 2720.0 1
沿用 Ch02 已建立的 Auto_re;列條件由 lambda 計算,欄條件仍是欄名串列。
來源:Ch02-statlearn-lab-zh.ipynb
需要多個步驟、註解或重複呼叫時,具名的 def 通常更方便。
lambda 只容許單一運算式,不用寫 return,也不是提早執行一次的篩選結果。
一個函式忘了寫 return,呼叫它會拿到什麼?
兩件事:預設引數讓常用的設定不必每次都寫,
作用域決定一個名字在哪裡看得到。
課程 lab 的 boot_SE 兩者都用上了。
這個進階應用同樣在重抽樣方法的課程筆記本中執行:先跑lab 範例 的安裝與匯入,
再跑lab 範例 建立 Portfolio 與 alpha_func,最後跑下面的lab 範例。
第一輪先看引數如何傳入即可,bootstrap 的用途留待正課。
n=None、B=1000、seed=0 都有預設值,所以呼叫時可以只給前兩個。有預設值的參數一定要放在沒有預設值的後面。
Ch05-resample-lab-zh.ipynb
用關鍵字指定 B=1000、seed=0,順序就不重要了,而且讀的人一眼看得出每個數字是什麼意思。
Ch05-resample-lab-zh.ipynb
def f(acc=[]) 這種寫法,那個空串列只會建立一次,後續每次呼叫都共用同一個,於是它會越積越多。要用可變的預設值,寫 def f(acc=None) 再在函式裡面 if acc is None: acc = []。
boot_SE(..., seed=0) 把種子做成可設定的參數,這樣你既能重現結果(給同一個 seed),也能檢查結果穩不穩定(換幾個 seed 跑跑看)。把這項設定留在呼叫處,之後較容易重現與比較結果。
程式報錯時,可以從錯誤訊息找出原因。程式沒有報錯時,也要核對結果是否符合預期。 Python 的錯誤訊息(traceback)要從最後一行開始讀: 最後一行是錯誤的種類與說明,往上是它發生在哪一行。
IndexError: shape mismatch: indexing arrays could not be broadcast together with shapes (2,) (3,)
最後一行說得很清楚:shape mismatch,兩個索引陣列的形狀 (2,) 與 (3,) 沒辦法一起廣播。看到 shape 就知道要去印 shape。這是 P3 的第一課。
Ch02-statlearn-lab-zh.ipynb
NameError 名字沒定義(多半是打錯字或儲存格沒跑)、KeyError 欄名不存在、IndexError 索引超出範圍、ValueError 型別對但值不合理(形狀對不上多半是這個)。
try: ... except: pass 會把錯誤吞掉,讓程式帶著錯誤的狀態繼續跑——後續結果就可能出錯,卻沒有訊息提醒你。只有在你知道會發生什麼錯、而且知道該怎麼處理時才用它,例如「這個套件在 Colab 上才有,本機沒有就跳過」。
| 錯誤 | 多半的原因 | 先去做什麼 |
|---|---|---|
NameError | 打錯字,或那個儲存格還沒跑 | 從上到下重跑一次 |
KeyError | 欄名不存在或打錯 | 印 df.columns |
IndexError | 索引超出範圍 | 印 len() 或 shape |
ValueError: shape mismatch | 兩邊形狀對不上 | 印兩邊的 shape |
TypeError | 型別不對(例如字串當數字加) | 印 type() 或 dtypes |
NotFittedError | 還沒 fit 就 predict | 檢查有沒有漏跑 fit |
traceback 很長,你應該先看哪一行?
回顧本頁觀念與原 Lab 的程式,確認各個操作的意義與使用條件。
你要選出「1980 年後、而且 mpg 大於 30」的車。哪一個寫法對?
想對 degrees = [1,2,3] 各算一次 MSE 並收集結果。哪一個寫法最好?
def boot_SE(func, D, n=None, B=1000, seed=0)。呼叫時想只改 B,其他都用預設,怎麼寫?
你看到 KeyError: 'horsepower'。第一件該做的事是?
需要核對完整公式、比較表或實作時,再展開下方速查。
三張速查表。
| 情境 | 用 and / or | 用 & / | |
|---|---|---|
| 一個 if 判斷 | ✓ | ✗ |
| 選 DataFrame 的列 | ✗(會報錯) | ✓ |
| NumPy 陣列的條件 | ✗ | ✓ |
| 每個條件要不要括號 | 不用 | 一定要 |
| 「非」怎麼寫 | not x | ~mask |
| 迴圈寫法 | 什麼時候用 |
|---|---|
for x in a: | 只要值 |
for i, x in enumerate(a): | 同時要位置 |
for x, w in zip(a, ws): | 兩排東西並排走 |
for _ in range(B): | 只是要跑 B 次(自助法) |
[f(x) for x in a] | 把結果收成一個串列 |
| 向量化(不寫迴圈) | 對整排數字做同一件事時的第一選擇 |
| 函式的部件 | 怎麼寫 | 注意 |
|---|---|---|
| 定義 | def name(a, b=預設): | 有預設值的參數放後面 |
| 回傳 | return 值 | 沒寫就回傳 None |
| 呼叫(位置) | name(x, y) | 順序不能錯 |
| 呼叫(關鍵字) | name(x, b=y) | 參數多時一律用這個 |
| 可變預設值 | def f(acc=None) | 不要寫 acc=[] |
&/|,而且每個條件都加上括號。and 一次只判斷一個真假值。