① 依序閱讀:每節先讀說明;遇到互動元件時,先預測結果,再操作驗證。 ② 對照程式範例:程式碼卡下方的「來源」標了課程練習筆記本(lab);需要實作時,再打開原始筆記本對照。 ③ 檢查理解:答錯時先看回饋,再回到相關說明;標為延伸的內容可留待第二輪。 ④ 最後翻關鍵詞彙卡自測術語,並用 重點速查與來源查閱。
教科書:An Introduction to Statistical Learning with Applications in Python(ISLP)。章節旁的來源標記可點到本頁書目。
統計程式經常需要對一整排數字做同一個運算。
算殘差是每一筆都減掉擬合值,算標準化是每一筆都減平均再除標準差。
Python 內建的串列(list)做不到這件事。它的 + 是「接起來」,不是「加起來」。
+ 是串接,陣列的 + 是逐元素相加。NumPy 讓這類整排數值運算更容易表達。
x + y 把 y 接在 x 後面,長度變成 6。x + y 把對齊的元素相加,長度還是 3。
[3, 4, 5, 4, 9, 7]
注意輸出是 [3, 4, 5, 4, 9, 7],六個元素,不是三個。
Ch02-statlearn-lab-zh.ipynb
array([ 7, 13, 12])
同一組數字,換成 np.array 之後 + 的意思就變了。
Ch02-statlearn-lab-zh.ipynb
DataFrame 的每一欄底下就是一個 NumPy 陣列,df['mpg'].mean() 走的是 NumPy 的路。pandas 管理欄名與索引,數值運算則會用到 NumPy。x**2 不會回到 Python 逐個平方。讀取陣列時,先看三個屬性:ndim(幾維)、dtype(裝什麼型別)、
shape(每一維多長)。看不懂錯誤訊息的時候,先印出這三個屬性,確認維度、型別與形狀。
shape 是一個 tuple。二維陣列的 shape 是 (列數, 欄數),列在前、欄在後。
np.array([[1, 2], [3.0, 4]]) 裡只要有一個浮點數,整塊就變成 float64——陣列不允許同一塊裡混型別。想指定就寫 np.array([[1, 2], [3, 4]], float)。
X.shape 是 (10, 3),這個陣列是什麼?
np.linspace(start, stop, num) 指定點數,預設包含兩端;
np.arange(start, stop, step) 指定間距,採含頭不含尾的範圍。
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.]) array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Ch02-statlearn-lab-zh.ipynb
例如前者在 0 到 10 放 11 個點,所以包含 10;後者使用預設間距 1,最後是 9。
linspace(..., endpoint=False) 可排除右端。
浮點間距累加會受捨入影響;畫固定數量的網格時,通常直接指定 linspace 的點數較清楚。
本例的 reshape 可以用同一塊記憶體的另一種讀法完成:
六個數字排成一排,或排成兩列三欄,底下是同一塊。一般情況則是能共用就回傳檢視,否則可能複製。若沒注意這個差別,
之後修改一個變數時,可能漏看另一個變數也受到影響。
reshape((2, 3)) 得到的第一列是前三個數字,不是每隔一個取一次。
beginning x: [1 2 3 4 5 6] reshaped x: [[1 2 3] [4 5 6]]
Ch02-statlearn-lab-zh.ipynb
x before we modify x_reshape: [1 2 3 4 5 6] x_reshape before we modify x_reshape: [[1 2 3] [4 5 6]] x_reshape after we modify its top left element: [[5 2 3] [4 5 6]] x after we modify top left element of x_reshape: [5 2 3 4 5 6]
這就是「同一塊記憶體」的證據:改 x_reshape[0, 0],x 的第一個元素跟著變成 5。
Ch02-statlearn-lab-zh.ipynb
((2, 3),
2,
array([[5, 4],
[2, 5],
[3, 6]])).T 同樣不搬資料,它只是把「先走列還是先走欄」對調。
Ch02-statlearn-lab-zh.ipynb
A[1:3])給的是檢視(view);reshape 則在可行時共用資料,否則可能複製。花式索引(用整數串列或布林陣列去挑,例如 A[[1,3]])與明確呼叫 .copy() 才會複製。需要獨立資料時,明確使用 .copy()。x.reshape((2, 3)) 與 x.reshape(2, 3) 都可以,前者跟 shape 屬性的寫法一致,讀起來比較不會誤會。x = np.array([1,2,3,4,5,6])、y = x.reshape((2,3)),接著執行 y[0,0] = 99。x[0] 現在是多少?
reshape(-1, 1) 保留二維形狀;第 12 章做主成分之前要先把資料中心化,靠的就是這裡的 shape 對齊。→ P6 · reshape 的操作範例 → 線性迴歸 · 多元迴歸
記憶體也有差別:基本切片可共享原陣列,整數陣列的進階索引會建立複本。對本頁連續儲存的 A,reshape 可以共享資料;需要獨立修改時應明確複製。
同時指定列與欄時,要看清楚索引如何配對。lab 裡有一格叫
「糟糕——發生了什麼?」,因為 A[[1,3],[0,2]] 拿到的
不是 第 1、3 列與第 0、2 欄交叉的那個 2×2 子矩陣。
A[[1,3],[0,2]] 亮的只有兩格——它把兩個索引串列配對成 (1,0) 與 (3,2)。
np.ix_([1,3],[0,2]) 明講「這兩組是要交叉的」,或者分兩步 A[[1,3]][:,[0,2]]。兩種都對,np.ix_ 少複製一次。
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])Ch02-statlearn-lab-zh.ipynb
array([[ 4, 5, 6, 7],
[12, 13, 14, 15]])
array([[ 0, 2],
[ 4, 6],
[ 8, 10],
[12, 14]])單獨挑列或單獨挑欄都很直覺,問題出在兩個一起寫。
來源:Ch02-statlearn-lab-zh.ipynb
array([ 4, 14])
只拿到兩個數字:A[1,0] 與 A[3,2]。兩個索引串列被逐位配對,不是交叉。
Ch02-statlearn-lab-zh.ipynb
IndexError: shape mismatch: indexing arrays could not be broadcast together with shapes (2,) (3,)
錯誤訊息裡的 could not be broadcast together 已經在暗示:花式索引走的是廣播規則,不是交叉。
Ch02-statlearn-lab-zh.ipynb
array([[ 4, 6],
[12, 14]])
array([[ 4, 6, 7],
[12, 14, 15]])A[[1,3]][:,[0,2]] 會產生一個中間陣列;np.ix_ 可一次指定列與欄的索引。
Ch02-statlearn-lab-zh.ipynb
array([[ 4, 6],
[12, 14]])切片是 起:迄:步長,迄不包含。1:4:2 取到第 1、3 列,0:3:2 取到第 0、2 欄。
Ch02-statlearn-lab-zh.ipynb
想從 A 取出「第 0、2 列」與「第 1、3 欄」交叉的 2×2 子矩陣,下列哪一個不會給你想要的東西?
統計程式常依條件選取資料: 「馬力大於 150 的那些車」「訓練集的那些列」。做法是先算出一排 True/False, 再拿它去索引。這排布林值的長度必須跟那一維一樣長。
A[np.array([0,1,0,1])] 是整數索引,意思是「第 0、1、0、1 列」,會拿到四列而且有重複;A[np.array([False,True,False,True])] 才是布林索引,拿到兩列。長得很像,結果差很多。
值 > 門檻 為 True 的列。
array([False, False, False, False]) array([False, True, False, True])
np.zeros(n, bool) 開一排 False,再把要的位置設成 True。
Ch02-statlearn-lab-zh.ipynb
array([[0, 1, 2, 3],
[4, 5, 6, 7],
[0, 1, 2, 3],
[4, 5, 6, 7]])
array([[ 4, 5, 6, 7],
[12, 13, 14, 15]])同樣是 [0,1,0,1],型別不同結果就不同:整數版拿到四列(還重複了),布林版拿到兩列。
Ch02-statlearn-lab-zh.ipynb
array([[ 4, 6, 7],
[12, 14, 15]])Ch02-statlearn-lab-zh.ipynb
Auto 有 392 列。mask = Auto['mpg'] > 30 之後,mask 的長度是多少?
比較陣列會得到一排布林值;np.all 問「是否全部成立」,np.any 問「是否至少一個成立」。
它們把多個真假值歸約成單一判斷,也能用 axis 分別判斷每列或每欄。
上例的 True 只代表逐元素值相等;整數陣列與布林陣列仍有不同的索引語意。
對布林值 [True, False],all 為 False,any 為 True。
不要直接把含多個元素的陣列放入 if;先說清楚你要「全部」還是「至少一個」。
形狀不一樣的兩個陣列也能相加,NumPy 會照一套固定規則把它們對齊,這叫廣播
(broadcasting)。標準化資料 (X - X.mean(0)) / X.std(0) 就是靠它——
左邊是 (10, 3),右邊是 (3,),形狀不同卻能直接相減。
X.mean(0) 的 shape 是 (3,),右對齊補成 (1, 3),再把第一維拉伸成 10——等於每一列都減同一組欄平均,正是我們要的。
array([2.23606798, 1.41421356, 1.73205081, 2. , 2.23606798,
2.44948974])
array([25, 4, 9, 16, 25, 36])x**2 的 2 是純量,shape 是 (),被拉伸成跟 x 一樣長。這也是廣播的用法。
Ch02-statlearn-lab-zh.ipynb
| A 的 shape | B 的 shape | 結果 | 為什麼 |
|---|---|---|---|
| (10, 3) | (3,) | (10, 3) | 右對齊補成 (1,3),第一維 1→10 |
| (10, 3) | (10, 1) | (10, 3) | 第二維 1→3 |
| (10, 3) | (10,) | ✗ 報錯 | 右對齊後是 (1,10),第二維 3 vs 10 對不上 |
| (10, 3) | (10, 3) | (10, 3) | 完全一樣,不用拉伸 |
X - X.mean(0) 對,因為 (3,) 會對到欄。但 X - X.mean(1) 會報錯——(10,) 右對齊之後跑去對欄了。要減列平均得寫 X - X.mean(1, keepdims=True),或 X - X.mean(1)[:, None]。
理解 axis 時,可以先看彙總沿著哪一維進行:
axis 指的是「被摺掉」的那一維。
X.mean(0) 摺掉第 0 維(列),剩下每一欄一個平均。
keepdims=True,被摺掉的那一維會留下長度 1,(10,3) 摺 axis 0 得到 (1,3) 而不是 (3,)。這樣廣播回去就不會對錯邊。
(np.float64(-0.1126795190952861), np.float64(-0.1126795190952861)) (np.float64(2.7243406406465125), np.float64(2.7243406406465125), np.float64(2.7243406406465125))
np.var 預設除以 n(不是 n−1),所以它跟np.mean((y - y.mean())**2) 完全相同。要不偏估計得寫 np.var(y, ddof=1)。第 2 章會再遇到這件事。
Ch02-statlearn-lab-zh.ipynb
(np.float64(1.6505576756498128), np.float64(1.6505576756498128))
Ch02-statlearn-lab-zh.ipynb
array([ 0.15030588, 0.14030961, -0.34238602])
X 是 (10, 3),摺掉列之後剩三個數字,每一欄一個平均。
來源:Ch02-statlearn-lab-zh.ipynb
X.mean(0) 與 X.mean(axis=0) 有差嗎?axis=0 比較好讀,尤其後面還要接 keepdims 的時候。.var() 預設是 ddof=1,兩邊的預設值不同,同一份資料可能得到不同數字;比較前先核對設定。X.shape 是 (10, 3),X.sum(axis=1).shape 是什麼?
((y - yhat)**2).mean();偏差—變異分解的每一項都是沿某個 axis 的平均。→ 統計學習 · 評估擬合品質
這門課後面每一章都要模擬:自助法重抽、交叉驗證切分、隨機森林抽變數。 只要用到亂數,就一定要固定種子,否則你今天的圖跟明天的圖不一樣, 跟同學的也不一樣,討論時就要先分辨差異是否來自抽樣。
np.random.normal(...) 用的是全域狀態,誰在哪裡呼叫都會動到它。新版建議自己開一個產生器:rng = np.random.default_rng(1303),之後都用 rng. 開頭。
[9.78166034 7.77407713] [-1.41427653 -4.97598078]
同一行跑兩次會得到不同數字,報告中要交代抽樣設定。
來源:Ch02-statlearn-lab-zh.ipynb
[ 4.09482632 -1.07485605] [ 4.09482632 -1.07485605]
default_rng(1303) 開兩次,抽出來逐位相同。本站每一張自己算的圖都是這樣產生的。
Ch02-statlearn-lab-zh.ipynb
array([[1. , 0.78689588],
[0.78689588, 1. ]])對角線一定是 1,非對角線是兩個變數的相關係數。第 3 章看共線性時會一直用到這個矩陣。
來源:Ch02-statlearn-lab-zh.ipynb
回顧本頁觀念與原 Lab 的程式,確認各個操作的意義與使用條件。
A 是 (4, 4)。A[1:4:2, 0:3:2].shape 是什麼?
A[[0,1,2],[0,1,2]] 拿到的是什麼?
沿用本頁連續儲存的 A 矩陣,下列哪一個操作會複製一份資料?
需要核對完整公式、比較表或實作時,再展開下方速查。
下面三張表整理索引、彙總方向與容易混淆的寫法,後續處理資料時可以回查。
| 你想做的事 | 寫法 | 回傳的是檢視還是複本 |
|---|---|---|
| 取連續的列 | A[1:3] | 檢視 |
| 取指定的幾列 | A[[1, 3]] | 複本 |
| 取指定的幾欄 | A[:, [0, 2]] | 複本 |
| 取子矩陣(交叉) | A[np.ix_([1,3], [0,2])] | 複本 |
| 用條件挑列 | A[mask](mask 是布林陣列) | 複本 |
| 換形狀 | A.reshape((2, 3)) | 可行時共用資料,否則複製 |
| 轉置 | A.T | 檢視 |
| 確定要獨立一份 | A.copy() | 複本 |
| 彙總 | axis=0 | axis=1 | 不給 axis |
|---|---|---|---|
| (10, 3) 的輸入 | 摺掉列 → (3,) | 摺掉欄 → (10,) | 全摺 → 純量 |
| 直覺說法 | 每一欄一個數字 | 每一列一個數字 | 整塊一個數字 |
| 常見用途 | 欄平均、欄標準差(標準化) | 每筆觀測的總和 | 整體 MSE |
| 容易搞混的一對 | 差在哪 |
|---|---|
np.var(y) vs y.var() | 沒差,兩個都是除以 n |
np.var(y) vs pandas 的 s.var() | 有差:NumPy 預設 ddof=0,pandas 預設 ddof=1 |
A[[0,1]] vs A[np.array([False,True])] | 整數索引挑「第 0、1 列」;布林索引挑「True 的那些列」 |
X.mean(1) vs X.mean(1, keepdims=True) | 前者 (10,) 廣播會對錯邊;後者 (10,1) 才對得回列 |
np.random.normal vs rng.normal | 前者動到全域狀態;後者是你自己的產生器,可重現 |
.copy()。詞彙卡整理本頁引用的課程 lab中常用的統計或機器學習專有名詞,正面附英文名稱。 先看正面的術語,試著說出定義,再翻面核對。可以洗牌複習,也可以搭配本章例子練習解釋。