選擇統計方法

這次要回答兩個問題:台灣學生的社經地位(ESCS)和數學成績(PV1MATH)有沒有關係?學生年齡(AGE)和社經地位(ESCS)有沒有關係?兩組都是「兩個連續變項,看它們一起變動的方向和強度」,所以我用 Pearson 積差相關係數 r,並且畫散佈圖。

r 的範圍是 −1 到 +1。正的代表一個變項高的時候另一個也傾向高,負的代表一高一低,接近 0 就是幾乎沒有直線關係。r 只描述直線關係,也不能直接說誰造成誰,所以一定要搭配散佈圖一起看。我另外用 cor.test() 做顯著性檢定,並報 95% 信賴區間。

資料型態判斷

ESCS 是經濟、社會與文化地位的綜合指數,PISA 把它做成連續分數,OECD 平均約 0、標準差約 1,負數代表比國際平均低,是正常分數。PV1MATH 是數學成就的第一個可能值,也是連續分數,不是 0 到 100 的百分制,所以分數可以超過 500。

AGE 是學生的年齡,單位是「年」,帶小數(例如 15.83 歲)。PISA 只抽 15 歲 3 個月到 16 歲 2 個月之間的學生,所以 AGE 的全距很窄(大約 15.33 到 16.33),這會讓它和其他變項的相關很容易很小。

三個變項都是連續的量,適合 Pearson 相關。檔案標籤裡 95、97、98、99(或 9995、9997、9998、9999)是跳答、不適用、無效、沒作答,留在資料裡會讓 r 完全失真,所以分析前先改成遺漏,計算時用成對刪除(use = "complete.obs")。

載入套件

haven 讀 SPSS 的 .sav;dplyr 整理資料;ggplot2 畫散佈圖;knitr 的 kable() 排版表格。Windows 要先用 windowsFonts() 登記「微軟正黑體」,圖上的中文才不會變成方框,theme_set() 一次設好後面所有圖的外觀。

library(haven)
library(dplyr)
library(ggplot2)
library(knitr)

if (.Platform$OS.type == "windows") {
  windowsFonts(JhengHei = windowsFont("Microsoft JhengHei"))
  base_family <- "JhengHei"
} else {
  base_family <- ""
}
theme_set(
  theme_minimal(base_size = 12, base_family = base_family) +
    theme(
      plot.title = element_text(face = "bold"),
      panel.grid.minor = element_blank()
    )
)

讀檔,並把特殊代碼改成遺漏

read_sav() 讀入資料,transmute() 只留 AGE、ESCS、PV1MATH 三欄。as.numeric() 去掉 SPSS 標籤。if_else() 把特殊代碼改成 NA。這份檔其實沒有這些代碼,還是先處理,以免以後被當成真的分數。fmt2() 把數字固定印成小數兩位(四捨五入),並避免印出 -0.00。

fmt2 <- function(x) {
  out <- sprintf("%.2f", round(as.numeric(x), 2))
  out[out == "-0.00"] <- "0.00"
  out
}

raw <- read_sav("PISA_tawian2022_trimmed_lab.sav")

dat <- raw %>%
  transmute(
    age   = if_else(AGE %in% c(9995, 9997, 9998, 9999), NA_real_, as.numeric(AGE)),
    escs  = if_else(ESCS %in% c(95, 97, 98, 99), NA_real_, as.numeric(ESCS)),
    pv1math = if_else(PV1MATH %in% c(9995, 9997, 9998, 9999), NA_real_, as.numeric(PV1MATH))
  )

cat("總人數:", nrow(dat), "\n")
## 總人數: 5599
cat("遺漏值(age / escs / pv1math):",
    sum(is.na(dat$age)), "/", sum(is.na(dat$escs)), "/", sum(is.na(dat$pv1math)), "\n")
## 遺漏值(age / escs / pv1math): 0 / 0 / 0

描述統計

先用 summarise() 看每個變項的人數、平均數、標準差、最小值和最大值,確認資料範圍合理,也確認 AGE 的全距確實很窄。

desc <- bind_rows(
  dat %>% summarise(變項 = "ESCS", 人數 = sum(!is.na(escs)), 平均數 = mean(escs, na.rm = TRUE),
                    標準差 = sd(escs, na.rm = TRUE), 最小值 = min(escs, na.rm = TRUE), 最大值 = max(escs, na.rm = TRUE)),
  dat %>% summarise(變項 = "PV1MATH", 人數 = sum(!is.na(pv1math)), 平均數 = mean(pv1math, na.rm = TRUE),
                    標準差 = sd(pv1math, na.rm = TRUE), 最小值 = min(pv1math, na.rm = TRUE), 最大值 = max(pv1math, na.rm = TRUE)),
  dat %>% summarise(變項 = "AGE", 人數 = sum(!is.na(age)), 平均數 = mean(age, na.rm = TRUE),
                    標準差 = sd(age, na.rm = TRUE), 最小值 = min(age, na.rm = TRUE), 最大值 = max(age, na.rm = TRUE))
)

kable(desc %>% mutate(across(-c(變項, 人數), fmt2)), caption = "三個變項的描述統計(小數兩位)")
三個變項的描述統計(小數兩位)
變項 人數 平均數 標準差 最小值 最大值
ESCS 5599 -0.27 0.91 -3.84 4.21
PV1MATH 5599 537.82 109.73 219.06 917.38
AGE 5599 15.81 0.29 15.33 16.33

計算相關係數

cor.test() 一次給出 r、t 檢定、自由度、p 值和 95% 信賴區間。method = "pearson" 指定 Pearson 相關。公式左右各放一個變項,R 會自動排除任何一邊遺漏的人。下面把兩組結果整理成一張表;r^2 是決定係數,代表其中一個變項的變異有多少比例可以被另一個用直線解釋。p 值太小時用 < .001 表示。

fmt_p <- function(p) ifelse(p < .001, "< .001", paste0("= ", sub("^0", "", sprintf("%.3f", p))))

ct1 <- cor.test(~ escs + pv1math, data = dat, method = "pearson")
ct2 <- cor.test(~ age + escs,     data = dat, method = "pearson")

cor_tab <- tibble(
  組別 = c("第一組:ESCS 與 PV1MATH", "第二組:AGE 與 ESCS"),
  n = c(ct1$parameter + 2, ct2$parameter + 2),
  r = c(ct1$estimate, ct2$estimate),
  `r²` = c(ct1$estimate^2, ct2$estimate^2),
  `95% CI 下界` = c(ct1$conf.int[1], ct2$conf.int[1]),
  `95% CI 上界` = c(ct1$conf.int[2], ct2$conf.int[2]),
  t = c(ct1$statistic, ct2$statistic),
  p = c(ct1$p.value, ct2$p.value)
)

kable(
  cor_tab %>% mutate(across(c(r, `r²`, starts_with("95"), t), fmt2), p = fmt_p(p)),
  caption = "Pearson 相關係數摘要(小數兩位)"
)
Pearson 相關係數摘要(小數兩位)
組別 n r r² 95% CI 下界 95% CI 上界 t p
第一組:ESCS 與 PV1MATH 5599 0.39 0.15 0.37 0.41 31.95 < .001
第二組:AGE 與 ESCS 5599 0.00 0.00 -0.03 0.02 -0.33 = .740
r1 <- ct1$estimate; r2 <- ct2$estimate

圖表繪製說明

兩個變項都是連續的,所以用散佈圖:一個點代表一位學生,橫軸放解釋用的變項,縱軸放被解釋的變項。第一組 x 是 ESCS、y 是 PV1MATH;第二組 x 是 AGE、y 是 ESCS。

人數有 5 千多人,點會疊在一起,所以 alpha = 0.25 讓點半透明,越深的地方人越多。geom_smooth(method = "lm") 加一條最小平方法的迴歸直線,用來看趨勢是向上還是向下;再用 labs() 把 r 寫在副標題。

AGE 因為只記到月(約 0.08 歲),點會排成一直條一直條,所以第二組用 geom_jitter() 在水平方向加一點點隨機位移,只是讓點散開,不改變計算結果(相關係數仍用原始資料)。

ggplot(dat, aes(x = escs, y = pv1math)) +
  geom_point(alpha = 0.25, color = "#4C78A8", size = 1.2) +
  geom_smooth(method = "lm", se = TRUE, color = "#E45756", fill = "#E45756", alpha = 0.2) +
  labs(
    title = "ESCS 與 PISA 數學成績(PV1MATH)的散佈圖",
    subtitle = paste0("Pearson r = ", fmt2(r1), ";紅線 = 線性迴歸線"),
    x = "社經地位 ESCS",
    y = "數學成績 PV1MATH"
  )

ggplot(dat, aes(x = age, y = escs)) +
  geom_jitter(width = 0.02, height = 0, alpha = 0.25, color = "#4C78A8", size = 1.2) +
  geom_smooth(method = "lm", se = TRUE, color = "#E45756", fill = "#E45756", alpha = 0.2) +
  labs(
    title = "年齡 AGE 與社經地位 ESCS 的散佈圖",
    subtitle = paste0("Pearson r = ", fmt2(r2), ";紅線 = 線性迴歸線(點在水平方向略微抖動)"),
    x = "年齡 AGE(歲)",
    y = "社經地位 ESCS"
  )

資料分析摘要

第一組:ESCS 與 PV1MATH 的 Pearson 相關係數 r = 0.39(n = 5599,95% CI [0.37, 0.41],p < .001),是中等的正相關;r² = 0.15,也就是數學成績約有 15.43% 的變異可以由 ESCS 的直線關係解釋。

第二組:AGE 與 ESCS 的 Pearson 相關係數 r = 0.00(n = 5599,95% CI [-0.03, 0.02],p = .740),是幾乎沒有的線性相關,未達統計顯著;r² = 0.00,也就是年齡幾乎解釋不了 ESCS 的變異。

完整最終回答

第一組(社經地位與數學成績):兩個變項都是連續分數,所以用 Pearson 相關。結果 r = 0.39,方向為正,強度屬於中等。散佈圖也看得出來,點雲整體由左下往右上,迴歸線向上:社經地位越高的台灣學生,PISA 數學成績傾向越高。不過點雲很寬,同樣 ESCS 的學生成績落差很大,社經地位只解釋了約 15.43% 的變異,所以它是一個有關但不是唯一的因素。另外這是相關,不代表社經地位「造成」成績高低。

第二組(年齡與社經地位):結果 r = 0.00,強度幾乎沒有直線關係。散佈圖中不同年齡的點雲高度幾乎一樣,迴歸線接近水平。這個結果合理,因為 PISA 抽的學生都在約 15.3 到 16.3 歲之間,年齡只差不到一歲,而且年齡不是由家庭背景決定的,所以沒有理由會有明顯的直線關係。檢定結果也未達統計顯著,95% 信賴區間包含 0,沒有證據顯示兩者有直線關係。

整體結論:台灣學生的社經地位與數學成績有中等的正相關(r = 0.39),而年齡與社經地位幾乎沒有直線關係(r = 0.00)。