Learning Objectives
- 用 pd.crosstab 做交叉表,用列百分比比較各組的分布
- 說出期望次數怎麼算,用 chi2_contingency 做卡方獨立性檢定,並正確解讀 p 值與 Cramér's V
- 用最小平方法與 scipy.stats.linregress 算出迴歸直線,解讀斜率、截距與 R²
- 畫殘差圖檢查直線模型合不合適,並說出高槓桿點與外推的風險
兩種「有沒有關係」的問題
前幾課比較的是「各組的平均」。這一課處理另外兩種常見問題:
| 問題 | 兩個變數的類型 | 工具 |
|---|---|---|
| 各系同學使用 AI 的頻率一樣嗎? | 類別 × 類別(系所、常用/偶爾/不用) | 交叉表+卡方獨立性檢定 |
| 每週讀書時數多的人,期末成績也比較高嗎?高多少? | 數值 × 數值 | 散布圖+簡單線性迴歸 |
兩者都在問「兩個變數有沒有關聯」,但卡方檢定只回答「有沒有」,迴歸還會給出「x 多 1,y 平均多多少」的斜率,可以拿來預測。
這一課用到三份資料:teaching_trial.csv(一個教學試驗的 180 位同學,含系所與 AI 使用頻率)、students.csv(200 位學生的讀書時數)、
grades.csv(600 筆修課成績)。三份都是程式產生的模擬資料,不是任何學校的真實資料,所以這一課算出來的關聯不能拿來推論真實世界。
逐步看:交叉表與列百分比
交叉表(contingency table)的每一格是「某一列的類別 × 某一欄的類別」各有幾個人。注意右邊記憶體區的 table 與 share。
卡方檢定在比什麼
卡方獨立性檢定的虛無假設是「兩個變數獨立」:系所和 AI 使用頻率沒有關係。 如果真的獨立,每一格「應該」有幾個人?這叫期望次數:
期望次數 = 該列合計 × 該欄合計 ÷ 總人數
例如資工系 35 人、全體「常用」77 人、總共 180 人,資工 × 常用的期望次數=35 × 77 ÷ 180 ≈ 15.0。實際是 21 人,比期望多 6 人。
每一格算 (觀察 − 期望)² ÷ 期望,全部加起來就是卡方值:觀察和期望差得越多,卡方值越大。 在虛無假設為真時,卡方值大約服從自由度 (列數 − 1) × (欄數 − 1) 的卡方分布,由此算出 p 值。
用 scipy.stats.chi2_contingency(table) 時要知道幾件事:
- 回傳卡方值
statistic、pvalue、自由度dof、期望次數expected_freq。 - 2 × 2 的表(自由度 1)預設做 Yates 連續性校正(
correction=True),卡方值會比沒校正的小一點;其他大小的表沒有影響。 - 期望次數太小時近似不準。常見的經驗法則是:期望次數小於 5 的格子不要超過兩成。太多時可以合併類別,2 × 2 的表可以改用
scipy.stats.fisher_exact。 - 卡方值會隨人數變大:比例完全不變、人數加倍,卡方值也加倍,p 值變小。所以要另外報效果量,常用的是 Cramér's V(0 到 1,越大關聯越強):
V = √( 卡方值 ÷ (總人數 × (列數與欄數中較小者 − 1)) )
逐步看:系所與 AI 使用頻率的卡方檢定
這張表是 6 × 3,自由度=(6 − 1) × (3 − 1)=10,不是 2 × 2,所以 Yates 校正不會用到。
簡單線性迴歸:找一條最貼近的直線
兩個數值變數的關係,先畫散布圖。如果點大致沿著一條直線分布,可以用一條直線 ŷ = 截距 + 斜率 × x 描述它。 點的實際值 y 和直線的預測值 ŷ 的差,叫做殘差(residual)。最小平方法找的是讓「所有殘差的平方加總」最小的那條直線,公式是:
斜率 = Σ (x − x 的平均)(y − y 的平均) ÷ Σ (x − x 的平均)²
截距 = y 的平均 − 斜率 × x 的平均
解讀時要注意:
- 斜率:x 多 1 個單位,y 的平均預測值多多少。它描述的是資料裡的關聯,觀察資料不能說「多讀 1 小時會讓成績提高多少分」;那是因果的說法,需要實驗設計。
- 截距:x=0 時的預測值。x=0 常常落在資料範圍外,這時截距只是數學上的位置,不必硬要解讀。
- R²:y 的變異中,能用這條直線說明的比例(簡單線性迴歸時 R² 就是相關係數 r 的平方)。R² 小不代表斜率不存在,只代表個別的人散得很開。
scipy.stats.linregress(x, y)回傳slope、intercept、rvalue、pvalue、stderr。pvalue檢定的是「斜率=0」(雙尾)。- 不要拿直線去預測資料範圍以外的 x(外推):資料裡沒有每週讀 60 小時的人,直線在那裡的樣子沒有根據。
觀察單位也要想清楚:grades.csv 每位學生有 3 筆成績,而讀書時數是每個人一個值。如果直接用 600 列做迴歸,等於把同一個人算成 3 個獨立的觀察,
p 值會被低估、看起來更「顯著」。下面的範例先把每位學生的期末成績平均,一人一列再做迴歸。
逐步看:用公式算出最小平方法的直線
只有 5 個點,用純 Python 照公式算一次。看右邊 sxy、sxx 怎麼一圈一圈累加。
逐步看:讀書時數與期末平均的迴歸、殘差圖
這次用 200 位學生。最後一步畫出兩張圖:左邊是散布圖加上迴歸直線,右邊是殘差圖。
殘差圖怎麼看
殘差圖把「直線沒說明到的部分」單獨畫出來,比原本的散布圖更容易看出問題:
| 殘差圖的樣子 | 代表什麼 | 可以怎麼做 |
|---|---|---|
| 在 0 上下均勻散開 | 直線模型合適 | 照常解讀 |
| 呈彎曲(例如 U 形) | 關係不是直線 | 轉換變數、加入 x²,或改用其他模型 |
| 越往右散得越開(喇叭形) | 誤差大小隨 x 改變 | 斜率的標準誤與 p 值可能不準 |
| 有一兩個點離得特別遠 | 可能是極端值或輸入錯誤 | 回頭檢查原始資料,報告時說明怎麼處理 |
還有一種點要特別小心:高槓桿點,也就是 x 遠離其他點的觀察值。最小平方法把殘差平方加總,離群的 x 對斜率的影響特別大, 一個點就可能把直線拉歪。下面的小工具可以親手試。
實作:年級與打工的卡方檢定
讀入 students.csv,做「年級(year)× 是否打工(part_time_job)」的交叉表並印出,
再用 chi2_contingency 做卡方檢定,依序印出卡方值(四捨五入到小數 2 位)、自由度、p 值(四捨五入到小數 4 位)。
輸出格式照下面:
(交叉表)
卡方值: …
自由度: …
p 值: …
小測驗:交叉表與卡方檢定
一張 6 列 × 3 欄的交叉表做卡方獨立性檢定,自由度是多少?
某一格所在的列合計 40 人、欄合計 60 人,全表 200 人。這一格的期望次數是多少?
系所 × AI 使用頻率的卡方檢定得到 p 值約 0.002。下列哪一個解讀正確?
小測驗:迴歸與殘差
下面程式會印出什麼?
from scipy import stats
fit = stats.linregress([1, 2, 3, 4], [3, 5, 4, 8])
print(round(fit.slope, 2), round(fit.intercept, 2))
讀書時數與期末平均的迴歸,R² 約 0.134。下列哪一個解讀最恰當?
某個迴歸的殘差圖呈明顯的 U 形:兩端的殘差多為正、中間多為負。最合理的判斷是什麼?
重點整理
- 兩個類別變數:
pd.crosstab做交叉表,normalize="index"看列百分比;chi2_contingency做卡方獨立性檢定。 - 期望次數=列合計 × 欄合計 ÷ 總數;卡方值是每格 (觀察 − 期望)² ÷ 期望的總和;自由度=(列 − 1)(欄 − 1)。
chi2_contingency在 2 × 2 的表預設做 Yates 校正;期望次數小於 5 的格子太多時近似不準。- 卡方值與 p 值會隨人數改變,要同時報 Cramér's V 這類效果量與列百分比。
- 兩個數值變數:最小平方法讓殘差平方和最小;
linregress給出斜率、截距、r(R²=r²)與斜率的 p 值。 - 斜率描述關聯,不是因果;不要外推到資料範圍以外;先確定觀察單位(一人一列)。
- 一定要看殘差圖;x 很極端的高槓桿點可以把直線拉歪。
- 下一課整理資料分析常見的陷阱:抽樣偏誤、倖存者偏誤、p-hacking,以及怎麼讓分析可以重現。