選擇統計方法

這題只看 ESCS 一個連續變項。要做的事就是先把每個人的分數換成 z 分數,再算這組 z 分數的平均數和標準差,最後畫直方圖,看它長什麼樣子。這是在做標準化,看看每個人離這批人的平均有幾個標準差。

算法是:一個人的 ESCS,減掉這份樣本的平均數,再除以標準差。標準差用 R 的 sd(),分母是 n − 1。算完以後,整組 z 分數的平均數會落到 0,標準差會落到 1。題目要報到小數兩位,其實就是在檢查有沒有算對。0 就是這批學生的平均位置,1 就是比平均高一個標準差。

直方圖用來看高峰在哪、左右平不平、兩端有沒有拖很長。z 分數只是把單位換掉,形狀還是原來的 ESCS,所以圖不會因此變成常態分布。

資料型態判斷

ESCS(Index of economic, social and cultural status)是經濟、社會與文化地位的綜合指數。PISA 把它做成連續分數,OECD 平均大概在 0、標準差大概是 1,所以比平均低的人會出現負數。這裡的負數是正常分數,代表比國際平均低。

因為它是可以有小數的連續分數,我用平均數、標準差和直方圖來看。z 分數就是拿這個分數去減平均、再除以標準差,算出來還是連續的。除的時候標準差要大於 0;這份資料裡大家的 ESCS 有高有低,所以除得下去。

檔案標籤裡的 95、97、98、99 是跳答、不適用、無效、沒作答。這些如果留在資料裡,平均數和標準差會被拉得很誇張,z 分數也會跟著錯,所以分析前先改成遺漏。

載入套件

haven 用來讀 SPSS 的 .sav。dplyr 用來挑欄位、改代碼、算 z 分數和做摘要。ggplot2 畫直方圖。knitr 的 kable() 把表排整齊。moments 算偏態,看圖是往左歪還是往右歪。

Windows 畫圖時不認得「微軟正黑體」這個名字,所以先用 windowsFonts() 登記成 JhengHei,圖上的中文才不會變成方框。theme_set() 是一次把後面所有圖的底色和字級設好。

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

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() 把 PISA_tawian2022_trimmed_lab.sav 讀進來。%>% 就是把左邊算出來的東西交給右邊。transmute() 只留 ESCS,其他欄位先拿掉。as.numeric() 把 SPSS 的標籤去掉,後面才能直接算平均數和標準差。if_else() 看到 95、97、98、99 就改成 NA。這份檔其實沒有這些代碼,還是先處理,免得以後被當成真的分數。

raw <- read_sav("PISA_tawian2022_trimmed_lab.sav")

dat <- raw %>%
  transmute(
    escs = if_else(ESCS %in% c(95, 97, 98, 99), NA_real_, as.numeric(ESCS))
  )

cat("有效樣本:", sum(!is.na(dat$escs)),
    ";遺漏:", sum(is.na(dat$escs)), "\n")
## 有效樣本: 5599 ;遺漏: 0

計算 z 分數

先用 mean() 和 sd() 算出有效樣本的 ESCS 平均數和標準差,再代進公式。na.rm = TRUE 的意思是算的時候跳過遺漏值。公式裡用的是完整數字,畫面上的小數兩位只是給人看的,不能先四捨五入再去除。

mutate() 幫每個人加一欄 z_escs。z 是正的,代表這個人比這批學生的平均高;是負的,代表比平均低;數字離 0 越遠,就離平均越遠。下面先印出拿來計算的平均數和標準差,再列前 6 筆對一下公式。真正的摘要還是用全部人,這 6 筆只是範例。

小數兩位交給 fmt2()。z 分數的平均數在電腦裡會是一個非常接近 0 的小負數,直接四捨五入會印成 -0.00,看起來像算錯。所以這個函式如果看到 -0.00,就改印成 0.00。代進公式的還是沒有四捨五入的數。

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

escs_mean <- mean(dat$escs, na.rm = TRUE)
escs_sd <- sd(dat$escs, na.rm = TRUE)

dat <- dat %>%
  mutate(z_escs = (escs - escs_mean) / escs_sd)

cat("計算 z 用的 ESCS 平均數:", fmt2(escs_mean),
    ";標準差:", fmt2(escs_sd), "\n")
## 計算 z 用的 ESCS 平均數: -0.27 ;標準差: 0.91
example_z <- dat %>%
  filter(!is.na(z_escs)) %>%
  slice_head(n = 6) %>%
  transmute(
    ESCS = fmt2(escs),
    z分數 = fmt2(z_escs)
  )

kable(example_z, caption = "前 6 筆有效資料的 ESCS 與 z 分數(小數兩位,只用來對照公式)")
前 6 筆有效資料的 ESCS 與 z 分數(小數兩位,只用來對照公式)
ESCS z分數
-0.32 -0.06
0.16 0.47
-0.57 -0.34
-0.82 -0.60
0.51 0.86
0.29 0.62

z 分數的平均數與標準差

summarise() 把全部有效的 z 分數再算一次平均數和標準差。照公式,這兩個數應該是 0 和 1。最小值和最大值是看圖的兩端有多遠;中位數和偏態是看左右對不對稱。abs(z_escs) <= 1 是在數離平均不超過一個標準差的人,再換成百分比。moments::skewness() 接近 0 就是大致左右對稱;負的是左偏,正的是右偏。

z_stats <- dat %>%
  summarise(
    人數 = sum(!is.na(z_escs)),
    平均數 = mean(z_escs, na.rm = TRUE),
    標準差 = sd(z_escs, na.rm = TRUE),
    最小值 = min(z_escs, na.rm = TRUE),
    最大值 = max(z_escs, na.rm = TRUE),
    中位數 = median(z_escs, na.rm = TRUE),
    偏態 = moments::skewness(z_escs, na.rm = TRUE),
    正負1以內 = mean(abs(z_escs) <= 1, na.rm = TRUE) * 100,
    正負2以內 = mean(abs(z_escs) <= 2, na.rm = TRUE) * 100
  )

kable(
  z_stats %>%
    select(人數, 平均數, 標準差, 最小值, 最大值, 中位數, 偏態) %>%
    mutate(across(-人數, \(x) fmt2(x))),
  caption = "ESCS 的 z 分數摘要(小數兩位)"
)
ESCS 的 z 分數摘要(小數兩位)
人數 平均數 標準差 最小值 最大值 中位數 偏態
5599 0.00 1.00 -3.94 4.94 0.04 -0.18

圖表繪製說明

z 分數是連續的,所以用直方圖看分布,縱軸用次數,直接看每一段有幾個人。binwidth = 0.5 是每一柱寬 0.5 個標準差。這批分數大概從 −4 到 5,這個寬度看得到中間的山峰,兩端也不會被切得太碎。center = 0 是讓其中一柱以 0 當中心,平均數才會落在中間那一柱裡面。

geom_vline() 加兩條線幫忙看。紅虛線是平均數,也就是 0。青點線是中位數。兩條線幾乎疊在一起,就表示平均數和中位數很接近。labs() 是在寫圖的標題和軸的名字。

ggplot(dat, aes(x = z_escs)) +
  geom_histogram(
    binwidth = 0.5,
    center = 0,
    fill = "#4C78A8",
    color = "white"
  ) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "#E45756") +
  geom_vline(xintercept = z_stats$中位數, linetype = "dotted", color = "#72B7B2") +
  labs(
    title = "ESCS 的 z 分數直方圖",
    subtitle = "每一柱寬 0.5;紅虛線 = 平均數 0;青點線 = 中位數",
    x = "z 分數",
    y = "次數"
  )

資料分析摘要

有效樣本有 5599 人,沒有遺漏。z 分數的平均數是 0.00,標準差是 1.00,跟公式預期的一樣。拿來算 z 的 ESCS 平均數是 -0.27,標準差是 0.91。z 分數最小是 -3.94,最大是 4.94,中位數是 0.04,偏態是 -0.18。有 64.60% 的人落在 −1 到 1,97.04% 的人落在 −2 到 2。

直方圖是單峰,中間凸起來,0 附近最高,而且最高的那一柱在 0 的右邊一點,往兩邊就降下來。平均數比中位數低一點點,偏態是負的,所以整體大致對稱,只是略為左偏。右邊最遠有極少數人接近 5,左邊 −3 到 −1 的人比較多,把平均數輕輕往左拉。大部分人還是集中在 −2 到 2。z 分數只是把原來的 ESCS 改成「平均是 0、標準差是 1」這種單位,形狀還是原來那樣。

完整最終回答

這份資料是 PISA 2022 的臺灣樣本,看的是 ESCS,也就是一個連續的社經地位指數。我先把 95、97、98、99 當成遺漏,實際上沒有人是這些代碼,有效人數還是 5599。z 分數用這份樣本的平均數 -0.27 和標準差 0.91 來算,公式是 (ESCS − 平均數) / 標準差。算出來整組的平均數是 0.00、標準差是 1.00,跟標準化該有的結果一樣:0 是這批學生的平均,1 是比平均高一個標準差。

直方圖長得像中間凸起來的一座山,高峰在 0 附近,還稍微偏到右邊,往兩邊下降。中位數是 0.04,偏態是 -0.18,所以大致對稱、略為左偏。大概 64.60% 的人離平均不到一個標準差,大概 97.04% 的人在兩個標準差以內。最小值是 -3.94,最大值是 4.94,兩端還是有離平均比較遠的人。

所以某位學生的 z 分數,就是在說他的經濟、社會與文化地位比這批臺灣學生的平均高或低幾個標準差。圖的形狀跟原來的 ESCS 一樣,標準化只是換了單位。