kits 把不同年齡、答不同題目的孩子放到同一把尺上,並做出同齡常模。 KIT 是 Kids in Taiwan(台灣幼兒發展調查)。 這份教學用模擬資料,把流程的第 0 到 6 步從頭走一次。 資料是假的,但結構和 KIT 一樣。 資料很小,整份文件幾秒鐘就跑完。
第 7 到 10 步(題目分析、新的波次、演算法和程式、量尺的性質)在進階篇:vignette("kits-advanced", package = "kits")。 看不懂的詞,查套件 README 的「名詞表」,或 ?"kits-package" 的 Glossary(英文)。
流程
| 0 資料 |
kits_sim()、kits_resp() |
作答矩陣和孩子的資料(persons);或一張作答表 |
入門篇 |
| 1 校準 |
kits_item() |
每個世代各估一次 PCM 的步驟參數和門檻(thr) |
入門篇 |
| 2 世代連結 |
kits_linking(item36, item03) |
把 M36 的量尺接到 M03 |
入門篇 |
| 3 接到 D |
kits_linking(item03, anchors) |
用錨題把 M03 的量尺接到 GSED 的 D |
入門篇 |
| 4 計分與換算 |
kits_subj()、kits_convert()、kits_subj_table()、kits_expected_score() |
每個人次的 WLE;換到 D;原始分換算表和對照 |
入門篇 |
| 5 常模 |
kits_norm_screen()、kits_norm_ages()、kits_norms()、kits_norm_z() |
篩波次、定月齡範圍、配常模、算同齡 z |
入門篇 |
| 6 檢查 |
summary()、kits_itemfit()、kits_check()、kits_info() |
配得好不好,結果從哪裡來 |
入門篇 |
| 7 題目分析與報告的圖 |
kits_dif()、kits_item_table() |
性別 DIF 和 LR 檢定、通過月齡、題目的位置 |
進階篇 |
| 8 新的波次 |
kits_drift()、kits_item(fixed_params = ) |
偏移檢查、固定參數校準 |
進階篇 |
| 9 演算法和程式 |
kits_algorithm()、algo_ 函數 |
每一步的公式、函數和文獻;拆開的演算法;教學腳本 |
進階篇 |
| 10 量尺的性質 |
kits_convert() 的 se_link、kits_vertical()、kits_precision()、kits_link_check() |
連結誤差、垂直量尺、精準度、連結的檢查(Kolen & Brennan, 2014) |
進階篇 |
第 0 到 6 步的資料怎麼流:
0 資料 1 校準 2 世代連結、3 接到 D
X03 ──kits_item()──▶ item03 ──kits_linking(item03, anchors)──▶ d_link:lan M03 → D
X36 ──kits_item()──▶ item36 ──kits_linking(item36, item03)───▶ co: lan M36 → lan M03
4 計分與換算
kits_subj(X03, item03, persons) ──kits_convert(d_link)──────────────────────────▶ s03(D)
kits_subj(X36, item36, persons) ──kits_convert(co)──▶ ──kits_convert(d_link)──▶ s36(D)
rbind(s03, s36) ──▶ lan
5 常模
lan ──▶ kits_norm_screen() ──▶ kits_norm_ages() ──▶ kits_norms() ──▶ kits_norm_z()
6 檢查
kits_check(item03, item36, co, d_link, s03, s36, norm, ...)
M36 的分數要經過兩條連結(co 和 d_link),因為只有 M03 的量尺直接接到 D。
kits 的估計用 RTMB 和 base R:
- 校準:PCM 的條件最大概似(CML),RTMB。
- 計分:WLE,RTMB 的牛頓法。 PCM 的 WLE 等於 Jeffreys 眾數估計,kits 算的是後者(Warm, 1989; Firth, 1993; Magis, 2015)。
- 常模:sinh-arcsinh(shash)分佈的最大概似,RTMB。
- 連結、篩選、月齡範圍、適配:base R 的封閉解。
整理資料(長格式、彙總、接表)用 tidyverse 的 dplyr、tidyr、purrr,這份教學也是。 kits 的表都是 data.frame,dplyr 的動詞直接可以用,量尺的標籤會留著。 參數的名字照 tidyverse 的習慣:收作答的函數,第一個參數叫 data;常模的函數收分數的參數叫 d。 編號的欄:visit_id 是人次(一個孩子的一次施測),subj_id 是孩子(kits 0.23.0 起;以前叫 person_id、pid, 資料裡的舊欄名這一版還認得,並說一聲)。 連結是一條直線:新的值 = intercept + slope × 舊的值(kits 0.23.0 起;以前叫 a、b,a 是截距、b 是斜率)。
library(kits)
library(dplyr)
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
library(tidyr)
library(purrr)
0 資料
一個領域(語言),14 題,每題 3 個分數(0、1、2),由易到難。 M03 答第 1–8 題,M36 答第 5–12 題。 所以第 5–8 題是兩個世代的共同題。 第 13、14 題留給進階篇第 8 節的新波次。
set.seed(11)
betas <- lapply(seq(-3, 5, length.out = 14), function(b) b + c(-0.6, 0.6))
names(betas) <- sprintf("lan%02d", 1:14)
M03 和 M36 是 KIT 的兩個世代:從 3 個月開始追蹤的孩子,和從 36 個月開始追蹤的孩子。 每個波次 300 個孩子。 M03 有 6、12、24 個月三波,M36 有 36、48 個月兩波。 wave_m 是波次(預定的月齡),age_m 是施測時實際的月齡;_m 是月。 孩子的資料每個人次一列:一個孩子在一個波次是一個人次。
persons <- tibble(cohort = rep(c("M03", "M36"), c(900, 600)),
wave_m = rep(c(6, 12, 24, 36, 48), each = 300)) |>
mutate(visit_id = sprintf("%s_%04d", cohort, row_number()), .by = cohort) |>
mutate(age_m = wave_m + runif(n(), -1, 1),
sex = sample(c("boy", "girl"), n(), replace = TRUE))
head(persons)
#> # A tibble: 6 × 5
#> cohort wave_m visit_id age_m sex
#> <chr> <dbl> <chr> <dbl> <chr>
#> 1 M03 6 M03_0001 5.55 boy
#> 2 M03 6 M03_0002 5.00 girl
#> 3 M03 6 M03_0003 6.02 girl
#> 4 M03 6 M03_0004 5.03 boy
#> 5 M03 6 M03_0005 5.13 boy
#> 6 M03 6 M03_0006 6.91 boy
能力隨月齡成長。 kits_sim() 依 PCM 模擬作答。 作答是矩陣:一列一個人次,一欄一題,列名是 visit_id,NA 是沒施測。 prefix 是 visit_id 的開頭;這裡給世代的名字,所以 id 和 persons 的 visit_id 對得上。
theta <- 2 * log(persons$age_m / 12) + rnorm(nrow(persons), 0, 0.9)
m03 <- persons$cohort == "M03"
X03 <- kits_sim(theta[m03], betas[1:8], prefix = "M03_")
X36 <- kits_sim(theta[!m03], betas[5:12], prefix = "M36_")
X03[1:3, ]
#> lan01 lan02 lan03 lan04 lan05 lan06 lan07 lan08
#> M03_0001 1 2 2 2 1 0 1 0
#> M03_0002 0 2 1 1 0 0 0 0
#> M03_0003 2 2 2 2 1 2 0 0
M03 有 12 個人次少答了 lan08(第 4 節會用到)。
X03[sample(nrow(X03), 12), "lan08"] <- NA
接到 D 時要用幾個領域一起接(第 3 節)。 所以再模擬一個動作領域的 M03,6 題,二點計分。 是同一群孩子,所以用同一個 prefix。
motor_betas <- as.list(seq(-2.5, 2.5, length.out = 6))
names(motor_betas) <- sprintf("mot%02d", 1:6)
Xm03 <- kits_sim(theta[m03] + rnorm(sum(m03), 0, 0.5), motor_betas, prefix = "M03_")
資料直接交給函數,有三種形式:
- 矩陣(上面這種)。
- 寬的 data.frame:一樣的形狀;visit_id 是一個叫
visit_id 的欄(沒有時用列名),其他欄都是題。
- 長的 data.frame:
visit_id、item_id、item_score,一列一個作答。
每個函數用同一個方式檢查:
- 人次和題號不重複。
- 分數是 0 以上的整數,中間沒有缺(例如只有 0 和 2);有缺時先重新編碼。
- 分數從 1 開始時(例如 1、2、3),先減 1。
- 沒有作答的題和人次拿掉,並說一聲。
孩子的資料(波次、月齡、性別、世代)在需要的地方用參數給,例如 kits_subj(X03, item03, persons = persons)。 persons 的欄名要是 visit_id、wave_m、age_m、sex、cohort。 沒有 visit_id(矩陣沒有列名、data.frame 沒有 visit_id 欄)時,kits 照列的順序編號 1、2、3……,並說一聲。
作答表:kits_resp()
另一個做法是先把作答和孩子的資料放進同一張表,後面就不用自己對齊。 kits_resp() 收長格式(一列一個作答,加上孩子的欄),變成一列一個人次的表;kits 記得哪些欄是題。 一張表只放一個領域。 套件附的模擬資料 kitm03 是長格式:
resp <- kitm03$responses |>
filter(domain == "lan", wave_m <= 12) |>
kits_resp()
resp
#> <kits_resp> 1322 person-waves, 15 items, 8 item pattern(s); domain lan
#> Person-waves per wave: 3: 434, 6: 441, 12: 447
#> Columns of the child: visit_id, subj_id, cohort, sex, wave_m, age_m
#> Next: kits_item(x, scale = ), kits_subj(x, item), kits_item_table(thr, norm, data = x), kits_dif(x, by = "sex"), summary(x), plot(x).
#> Checks: OK
有了 resp,校準和計分都直接給它:
item_resp <- kits_item(resp)
#> No `scale` given: the scale is labelled "lan M03" (the domain and cohort of the responses).
s_resp <- kits_subj(resp, item_resp)
沒給 scale 時,量尺叫「領域 世代」(這裡是 lan M03),並說一聲。 kits_dif(resp, "sex")、kits_itemfit(resp, item)、kits_item_table(thr, norm, data = resp) 也一樣。 summary(resp) 列出每一波的題本,plot(resp) 畫每一波答了哪些題。 下面的流程用矩陣加上 persons,兩種寫法的數字相同。
1 校準
每個世代各估一次。 scale 是這個校準的量尺標籤,後面每個結果都會帶著它。 一定要給 scale:沒給時用資料物件的名字(例如 "X03"),看起來像錯誤。
item03 <- kits_item(X03, scale = "lan M03")
item36 <- kits_item(X36, scale = "lan M36")
itemm03 <- kits_item(Xm03, scale = "motor M03")
item03
#> <kits_item> PCM (kits, CML): 900 person-waves, 8 items, 16 steps, 2 item pattern(s)
#> Scale: lan M03
#> item_id item_score beta beta_se thr thr_se
#> lan01 1 -2.72 0.24 -2.90 0.20
#> lan01 2 -1.30 0.10 -1.11 0.08
#> lan02 1 -2.07 0.19 -2.32 0.15
#> lan02 2 -1.09 0.10 -0.83 0.08
#> lan03 1 -1.68 0.14 -1.84 0.12
#> lan03 2 -0.10 0.09 0.06 0.07
#> ... 10 more steps (more: print(x, n = 16); all steps: coef(x))
#> Item fit (conditional): infit and outfit within 0.6-1.4 for all 8 items.
#> Next: kits_subj(data, x), kits_linking(x, ...), plot(x, type = "fit"), plot(x), confint(x).
#> Checks: OK
怎麼讀:
Scale: lan M03 是這個校準定義的量尺。
item pattern(s) 是作答組合的數目:一個孩子實際答的那組題是一個作答組合(pattern_id)。 這裡有 12 個人次少答 lan08,所以有 2 個作答組合。
- 一列是一題的一個步驟;
item_score 是第幾步。
beta 是步驟參數。
thr 是門檻:能力在這裡時,得到 k 分以上的機率剛好是 .5。
- 同一題的門檻一定由小到大;
beta 不一定。
- 第 1 步叫 milestone step,就是「通過」(分數至少 1)。
- 全部的步驟:
coef(item03),或 print(item03, n = 20)。
- 最後一行
Checks: OK 表示自動檢查都過了。
summary() 列出檢查的內容:
summary(item03)
#> <kits_item> PCM (kits, CML): 900 person-waves, 8 items, 16 steps, 2 item pattern(s)
#> Scale: lan M03
#> item_id item_score beta beta_se thr thr_se
#> lan01 1 -2.72 0.24 -2.90 0.20
#> lan01 2 -1.30 0.10 -1.11 0.08
#> lan02 1 -2.07 0.19 -2.32 0.15
#> lan02 2 -1.09 0.10 -0.83 0.08
#> lan03 1 -1.68 0.14 -1.84 0.12
#> lan03 2 -0.10 0.09 0.06 0.07
#> ... 10 more steps (more: print(x, n = 16); all steps: coef(x))
#> Item fit (conditional): infit and outfit within 0.6-1.4 for all 8 items.
#> Next: kits_subj(data, x), kits_linking(x, ...), plot(x, type = "fit"), plot(x), confint(x).
#> Checks: OK
#> Diagnostics:
#> status check value message
#> ok optimizer convergence 0 converged (nlminb: relative convergence (4))
#> ok max |gradient| 2.42e-13 below 1e-4
#> ok Hessian positive definite 1 yes
#> ok extreme estimates 0.237 largest SE 0.237, largest |beta| 2.84
#> ok smallest category count 22 every score of every item has at least 5 persons
#> ok fixed items left out 0 none
#> ok persons 900 900 person-waves in the calibration
#> ok item patterns 2 2 item pattern(s) (the combinations of items answered)
#> ok smallest main item pattern 888 1 main item pattern(s) (5% or more of the person-waves, or the largest): 888 person-waves; 1 other item pattern(s) hold 1%
#> ok item fit 0 conditional infit and outfit within 0.6-1.4 for all 8 items
第一列的 nlminb code 1 是正常的:它的停止標準很嚴,梯度已經夠小。
plot() 畫每題每個分數的機率曲線,一題一格。 items = 選要畫的題。
plot(item03, items = c("lan01", "lan04", "lan08"))
每次校準的原點都不一樣。 所以 M03 和 M36 的量尺還不能直接比,要先連結(第 2 節)。
拆開來看。 kits_item() 背後的演算法也是一個個 algo_ 函數,可以自己接起來。 充分統計量、CML、門檻三步,門檻和 kits_item() 的相同:
thr_algo <- kits_stats(X03) |>
algo_cml() |>
algo_thresholds()
all.equal(thr_algo$thr, coef(item03)$thr)
#> [1] TRUE
algo_ 函數不記量尺、不檢查輸入,給想看演算法的人;平常用 kits_item()(進階篇第 9 節)。
1.1 印出來的數字
表格印小數點下 2 位。 物件裡的數字沒有被捨入。 要看更多位數,用 digits:
th <- coef(item03)
print(th, digits = 3)
#> item_id item_score beta beta_se thr thr_se
#> 1 lan01 1 -2.716 0.237 -2.900 0.199
#> 2 lan01 2 -1.298 0.103 -1.114 0.083
#> 3 lan02 1 -2.070 0.186 -2.324 0.146
#> 4 lan02 2 -1.086 0.101 -0.831 0.077
#> 5 lan03 1 -1.681 0.140 -1.843 0.119
#> 6 lan03 2 -0.101 0.089 0.060 0.074
#> 7 lan04 1 -1.027 0.118 -1.236 0.096
#> 8 lan04 2 0.222 0.090 0.431 0.072
#> 9 lan05 1 -0.343 0.101 -0.544 0.082
#> 10 lan05 2 0.961 0.093 1.161 0.075
#> 11 lan06 1 0.306 0.094 0.085 0.075
#> 12 lan06 2 1.482 0.099 1.703 0.080
#> 13 lan07 1 0.750 0.089 0.578 0.075
#> 14 lan07 2 2.251 0.111 2.423 0.094
#> 15 lan08 1 1.509 0.091 1.313 0.075
#> 16 lan08 2 2.842 0.135 3.038 0.112
th$thr[1]
#> [1] -2.899633
超過 30 列的表只印前 10 列。 print(x, n = 50) 印更多列,as.data.frame(x) 拿到全部的列。
1.2 二點計分和混合計分
上面的題目都是 0、1、2 分。 只有 0、1 分的題(二點計分)也用同樣的函數,不用另外設定。 只有一個世代、不需要連結時,kits() 一步做完校準和計分。
二點計分:6 題,每題一個難度。
betas_01 <- list(b1 = -2, b2 = -1, b3 = 0, b4 = 0.5, b5 = 1, b6 = 2)
X_01 <- kits_sim(rnorm(500), betas_01, prefix = "d", seed = 1)
k_01 <- kits(X_01, scale = "two-point")
coef(k_01$item)
#> item_id item_score beta beta_se thr thr_se
#> 1 b1 1 -2.10 0.13 -2.10 0.13
#> 2 b2 1 -0.98 0.10 -0.98 0.10
#> 3 b3 1 -0.20 0.09 -0.20 0.09
#> 4 b4 1 0.52 0.09 0.52 0.09
#> 5 b5 1 0.82 0.10 0.82 0.10
#> 6 b6 1 1.94 0.13 1.94 0.13
怎麼讀:
- 每題只有一個步驟(
item_score 都是 1)。
- 這時 PCM 就是 Rasch 模型。
- 門檻
thr 等於 beta。
max(abs(coef(k_01$item)$thr - coef(k_01$item)$beta))
#> [1] 2.315925e-13
混合計分:3 題二點計分,2 題 0–2 分,1 題 0–3 分。
betas_mix <- list(m1 = -1.5, m2 = 0, m3 = 1.5,
m4 = c(-1, 0), m5 = c(0, 1),
m6 = c(-1, 0, 1))
X_mix <- kits_sim(rnorm(500), betas_mix, prefix = "m", seed = 2)
k_mix <- kits(X_mix, scale = "mixed")
coef(k_mix$item)
#> item_id item_score beta beta_se thr thr_se
#> 1 m1 1 -1.50 0.11 -1.50 0.11
#> 2 m2 1 -0.05 0.09 -0.05 0.09
#> 3 m3 1 1.30 0.11 1.30 0.11
#> 4 m4 1 -0.75 0.13 -1.08 0.09
#> 5 m4 2 -0.14 0.11 0.19 0.08
#> 6 m5 1 -0.03 0.11 -0.27 0.08
#> 7 m5 2 1.03 0.13 1.27 0.10
#> 8 m6 1 -0.83 0.14 -1.16 0.10
#> 9 m6 2 -0.10 0.13 -0.02 0.07
#> 10 m6 3 1.08 0.14 1.31 0.11
- 一題有幾個步驟,就有幾列(m6 有 3 列)。
- 二點計分的題,門檻還是等於
beta。
- 多點計分的題,門檻和
beta 不同。
- 滿分
raw_max 是每題最高分加起來:3 × 1 + 2 × 2 + 3 = 10。
head(k_mix$subj, 3)
#> <kits_subj> 3 person-waves; scale: mixed
#> visit_id theta se flag
#> 1 m0001 -0.39 0.65 ok
#> 2 m0002 1.27 0.78 ok
#> 3 m0003 1.27 0.78 ok
#> Other columns: pattern_id, raw, raw_max, infit, outfit
#> Also kept: the raw score -> theta of each item pattern (kits_subj_table(x) gives the table of each wave).
#> Next: kits_convert(x, link), kits_norms(x), kits_norm_z(x, norm), kits_subj_table(x), kits_vertical(x).
plot() 是 Wright map:左邊是孩子能力的分佈,右邊是每題每個步驟的門檻。 兩邊用同一個 θ 軸。
後面的連結、計分、常模、檢查,二點計分和混合計分都一樣做。
2 世代連結(M36 → M03)
kits_linking(from, to) 的 to 是一個校準時,是兩個校準之間的連結。 from 是要被換算的校準(這裡是 M36),to 是目標量尺(M03)。 預設用共同題的所有步驟。 每個步驟的門檻是一個點:M36 的值對 M03 的值。 世代連結(cohort link)的變數習慣叫 co。
co <- kits_linking(item36, item03)
co
#> <kits_link> new value = intercept + slope x old value
#> scales: lan M36 -> lan M03
#> intercept = 2.50
#> slope = 1.05
#> points from: every common item step
#> points: 8 (items: 4); dropped: 0
#> before screening: intercept 2.50, slope 1.05; screening: |e| > 2 SD(e)
#> Next: kits_convert(scores, x), plot(x), summary(x, theta = ), confint(x).
#> Checks: OK
怎麼讀:
- 這是一條直線:M03 量尺上的 θ = intercept + slope × M36 量尺上的 θ。 intercept 是截距,slope 是斜率(Kolen & Brennan 的 B、A)。 兩個都用共同題門檻的平均和 SD 求(mean/sigma)。
scales: lan M36 -> lan M03:連結記得從哪條量尺換到哪條量尺。 兩個方向都合法,所以給反了不會停;要看這一行。
points 是點數(4 題共同題 × 2 步 = 8 個點)。
dropped 是篩掉的點:殘差超過 2 個 SD。
- 結果還是一個 list:
co$intercept、co$slope、co$points 都可以直接取;coef(co) 給截距和斜率。
連結誤差預設不算(n_boot = 0)。 要看的時候給 n_boot:共同題整題重抽 n_boot 次,每次重做連結(含篩點)。 intercept_error、slope_error 是截距和斜率在這些重抽之間的 SD,也就是「換一組共同題,線會差多少」。 200 次就看得出大小;報告用時多抽一些(例如 2000 次)。 重抽的亂數種子預設是 seed = 1,所以每次跑結果都一樣。
co_boot <- kits_linking(item36, item03, n_boot = 200)
c(intercept = co_boot$intercept, intercept_error = co_boot$intercept_error,
slope = co_boot$slope, slope_error = co_boot$slope_error)
#> intercept intercept_error slope slope_error
#> 2.50335282 0.07240139 1.05338685 0.05498134
step = 1 只用每題的 milestone step。 這裡只剩 4 個點,所以多一個警告。
kits_linking(item36, item03, step = 1)
#> <kits_link> new value = intercept + slope x old value
#> scales: lan M36 -> lan M03
#> intercept = 2.43
#> slope = 1.01
#> points from: step 1 of each common item
#> points: 4 (items: 4); dropped: 0
#> before screening: intercept 2.43, slope 1.01; screening: |e| > 2 SD(e)
#> Next: kits_convert(scores, x), plot(x), summary(x, theta = ), confint(x).
#> Checks: 1 warning (see summary() or kits_check())
一步一步做。 kits_linking() 是四步,每一步也是一個函數,可以一步一步跑,看每一步的結果:
kits_link_points():每個共同步驟一個點(M36 的門檻、M03 的門檻);
kits_link_screen():用所有的點畫一條線,殘差超過 2 個 SD 的點標記、丟掉;
kits_link_line():用留下的點畫最後的線(mean/sigma);
kits_link_error():整題重抽,算連結誤差。
points <- kits_link_points(item36, item03)
screened <- kits_link_screen(points, cut = 2)
co_steps <- kits_link_line(screened) |>
kits_link_error(n_boot = 200, seed = 1)
c(co_steps$intercept, co_steps$intercept_error) == c(co_boot$intercept, co_boot$intercept_error)
#> [1] TRUE TRUE
四步的結果和 kits_linking() 除了 info(呼叫紀錄)以外完全相同。 kits_algorithm(co_boot) 列出這四步,和每一步的公式與文獻(進階篇第 9 節)。
plot() 畫每個點、篩選前的線和它的篩選帶(± 2 SD)、最後的線。 滑鼠停在點上可以看題號和殘差。
3 接到 D
D 分數是 GSED(Global Scales for Early Development,WHO 的嬰幼兒發展量表)的發展分數。 接到 D,KIT 的分數就可以和其他國家比。 錨題是和 GSED 相同的題目。 每題有 GSED 的難度,欄名是 tau,單位是 D。 tau 不是 KIT 的門檻(thr):它是 GSED 那邊的數字。 這裡假設語言有 5 題、動作有 3 題錨題。 模擬時,tau 是門檻的一條直線加上誤差。
pass <- function(item) {
coef(item) |>
filter(item_score == 1)
}
milestone <- bind_rows(as.data.frame(pass(item03)), as.data.frame(pass(itemm03)))
anchors <- tibble(item_id = c("lan01", "lan02", "lan03", "lan06", "lan07", "mot01", "mot03", "mot05")) |>
left_join(select(milestone, item_id, thr), by = "item_id") |>
mutate(tau = 60 + 7 * thr + rnorm(n(), 0, 1.5)) |>
select(item_id, tau)
anchors
#> # A tibble: 8 × 2
#> item_id tau
#> <chr> <dbl>
#> 1 lan01 43.2
#> 2 lan02 42.5
#> 3 lan03 47.0
#> 4 lan06 60.0
#> 5 lan07 64.9
#> 6 mot01 43.2
#> 7 mot03 55.7
#> 8 mot05 69.5
連結和第 2 節是同一個函數。 to 是錨題表(item_id、tau)時,是接到外部量尺的連結:
- 每題錨題用通過的那一步(
step = 1:分數至少 1,就是 milestone step)。
- 終點量尺預設叫
D。
- 至少要 3 題錨題。
KIT 的語言、動作、認知共用一條線。 這時把幾個領域放進一個有名字的 list。
d_link <- kits_linking(list(lan = item03, motor = itemm03), anchors)
d_link
#> <kits_link> new value = intercept + slope x old value
#> scales: lan M03, motor M03 -> D
#> intercept = 59.83
#> slope = 6.70
#> points from: the pass step of each anchor item (score >= 1)
#> points: 8 (items: 8); dropped: 0
#> before screening: intercept 59.83, slope 6.70; screening: |e| > 2 SD(e)
#> kept points by domain: lan 5 (62%), motor 3 (38%)
#> domain check: one slope shared by the domains, fitted with domain intercepts (kept points);
#> it is not the slope of the line above:
#> domain n intercept slope
#> lan 5 60.18 6.76
#> motor 3 59.41 6.76
#> Next: kits_convert(scores, x), plot(x), summary(x, theta = ), confint(x).
#> Checks: OK
怎麼讀:
- D = intercept + slope × θ,θ 在各領域 M03 自己的量尺上。
domains 是檢查用:同樣留下的錨題,各領域有自己的截距,再配一個各領域共用的斜率。 這個斜率和上面那條線的 slope 不是同一個數(上面那條線不分領域),所以會差一點。
- 各領域的截距和共同的 intercept 很接近,就支持共用一條線。
只有一個領域時,直接給一個校準:kits_linking(item03, anchors)。 沒有錨題時,跳過這一步:後面的計分和常模都可以留在 logit 上做。
真的 KIT 資料的錨題表在 kits_anchors():每題 KIT 的題目對到一題 GSED 的題目,tau 來自 dscore 的 key gsed2406。 這裡的模擬題號不是 KIT 的題號,所以上面自己做了一張表。
head(kits_anchors())
#> item_id gsed_item key tau note domain
#> 1 lana06 gs1lgc087 gsed2406 56.10 original match kept on 2026-10-10 (A) lan
#> 2 lanc04 gs1lgc099 gsed2406 60.55 original 30-item anchor set lan
#> 3 bodyag04 ddigmd055 gsed2406 18.52 original 30-item anchor set motor
#> 4 bodyag02 ddigmd057 gsed2406 20.00 original 30-item anchor set motor
#> 5 bodyaf03 ddigmd006 gsed2406 22.42 original 30-item anchor set motor
#> 6 bodyaf08 by3fmd017 gsed2406 30.33 original 30-item anchor set motor
一條連結只用一個 key;kits_anchors("gsed2510") 的錨題少、D 的尺度也不同(見 ?kits_anchors)。
4 計分與換算
kits_subj(X, item) 算每個人次的 θ(WLE)。 給 persons 時,孩子的欄(波次、月齡、性別、世代)照 visit_id 接上;persons 多的列沒關係。 kits_convert() 換量尺,用管線一行一條連結,照套用的順序寫:
- M03:lan M03 → D。
- M36:lan M36 → lan M03 → D。
s03 <- kits_subj(X03, item03, persons = persons) |>
kits_convert(d_link) # lan M03 -> D
s36 <- kits_subj(X36, item36, persons = persons) |>
kits_convert(co) |> # lan M36 -> lan M03
kits_convert(d_link) # lan M03 -> D
s36
#> <kits_subj> 600 person-waves; scale: D
#> visit_id wave_m age_m theta se flag
#> 1 M36_0001 36 35.17 79.00 4.26 ok
#> 2 M36_0002 36 35.68 74.05 4.23 ok
#> 3 M36_0003 36 36.92 76.52 4.22 ok
#> 4 M36_0004 36 35.03 76.52 4.22 ok
#> 5 M36_0005 36 35.26 84.19 4.44 ok
#> 6 M36_0006 36 35.72 74.05 4.23 ok
#> 7 M36_0007 36 36.92 90.01 4.92 ok
#> 8 M36_0008 36 35.41 81.55 4.32 ok
#> 9 M36_0009 36 36.82 86.98 4.62 ok
#> 10 M36_0010 36 36.61 86.98 4.62 ok
#> ... 590 more rows (more: print(x, n = 50); all rows: as.data.frame(x))
#> Other columns: pattern_id, raw, raw_max, infit, outfit, sex, cohort
#> Also kept: the raw score -> theta of each item pattern (kits_subj_table(x) gives the table of each wave).
#> Next: kits_convert(x, link), kits_norms(x), kits_norm_z(x, norm), kits_subj_table(x), kits_vertical(x).
#> Checks: 1 note (see summary() or kits_check())
kits_convert(x, co, d_link) 是同一件事寫成一次呼叫,結果相同。
換算後,theta 和 se 這兩欄已經是 D。 欄名不變;量尺記在 attr(, "scale")。 se 是 WLE 的標準誤(Warm 的公式)。 拆開的函數是 algo_jme()(Jeffreys 眾數估計,對 PCM 就是 WLE;進階篇第 9 節)。 同一個作答組合、同一個原始分的孩子,θ 和 SE 都一樣。
print(x, n = 50) 印更多列。 物件裡的數字沒有被捨入:
s36 |>
select(visit_id, raw, theta, se) |>
print(n = 3, digits = 3)
#> <kits_subj> 600 person-waves; scale: D
#> visit_id raw theta se
#> 1 M36_0001 9 79.004 4.256
#> 2 M36_0002 7 74.055 4.228
#> 3 M36_0003 8 76.517 4.224
#> ... 597 more rows (more: print(x, n = 50); all rows: as.data.frame(x))
s36$theta[1]
#> [1] 79.00437
連結的順序給錯,會停下來,並說明正確的順序:
kits_subj(X36, item36) |>
kits_convert(d_link)
#> Error:
#> ! `x` is on scale lan M36, but link 1 converts lan M03, motor M03 -> D. The links given: lan M03, motor M03 -> D. Give the links in order, starting with a link from lan M36.
疊表
疊兩個 kits 的表用 rbind() 或 purrr::list_rbind()。 兩個都會檢查量尺,也會把兩張表的紀錄(換算、原始分對照)合在一起。 量尺不同時會停下來,所以先換算,再疊起來:
rbind(s03, kits_subj(X36, item36))
#> Error:
#> ! The tables are on different scales: D and lan M36. Convert them to one scale with kits_convert() first.
lan <- rbind(s03, s36)
attr(lan, "scale")
#> [1] "D"
疊好的表,summary() 和 kits_check() 照樣能用:kits_subj 的診斷會重算。 dplyr::bind_rows() 只留第一張表的紀錄,所以疊 kits 的表時會警告;改用 rbind() 或 list_rbind()。
summary() 有 flag 的比例,以及每個波次的信度(1 − 平均 SE² / θ 的變異數):
summary(s03)
#> <kits_subj> 900 person-waves; scale: D
#> theta mean 65.48, SD 10.38, range 31.17 to 89.00; flags: ok 811, misfit 55, zero 1, perfect 33
#> Conversion lan M03 -> D (kits_convert(), the links in the order applied):
#> step from to intercept slope
#> 1 lan M03 D 59.83 6.70
#> composed: new value = 59.83 + 6.70 x old value
#> Chained links compose as lines: slope = slope_2 x slope_1,
#> intercept = intercept_2 + slope_2 x intercept_1.
#> linking error: not available (a link has no bootstrap draws, n_boot = 0)
#> Diagnostics:
#> status check value message
#> ok share of zero scores 0.111 0.1% of person-waves
#> ok share of perfect scores 3.67 3.7% of person-waves
#> note share of misfit 6.11 6.1% of person-waves (above 5%)
#> ok reliability, wave 6 0.646 1 - mean(se^2) / var(theta)
#> ok reliability, wave 12 0.64 1 - mean(se^2) / var(theta)
#> note reliability, wave 24 0.373 1 - mean(se^2) / var(theta): below 0.5; check the shares of perfect and zero scores (flag)
flag 是判斷:zero(0 分)、perfect(滿分)、misfit(infit 或 outfit 大於 2)、ok。 24 個月的信度低:8 題對這一波太簡單,很多孩子接近滿分。 信度低或是負的,通常是題本對這一波太容易或太難;看 perfect 和 zero 的比例。
原始分換算表
每個波次的題本:原始分 → θ,加上人數。 題本(standard form)是這一波大多數孩子答的那組題。 分數表直接給 kits_subj_table(): 計分時算過的「原始分 → θ、SE」對照存在分數表裡(彙總的數字,沒有孩子的資料)。 s03 已經換到 D,所以換算表也是 D。 人數從分數表現在的列算;先 filter() 也對。
tab <- s03 |>
kits_subj_table()
filter(tab, wave_m == 12)
#> <kits_subj_table> the standard form of each wave of the data, with counts; scale: D
#> wave_m n_items raw theta se n pct cum_pct wave_n form_pct
#> 1 12 8 0 31.17 10.35 1 0.34 0.34 300 98.67
#> 2 12 8 1 39.44 6.31 1 0.34 0.68 300 98.67
#> 3 12 8 2 43.83 5.17 0 0.00 0.68 300 98.67
#> 4 12 8 3 47.12 4.64 0 0.00 0.68 300 98.67
#> 5 12 8 4 49.94 4.35 4 1.35 2.03 300 98.67
#> 6 12 8 5 52.53 4.18 7 2.36 4.39 300 98.67
#> 7 12 8 6 54.99 4.08 19 6.42 10.81 300 98.67
#> 8 12 8 7 57.37 4.03 21 7.09 17.91 300 98.67
#> 9 12 8 8 59.72 4.01 25 8.45 26.35 300 98.67
#> 10 12 8 9 62.08 4.04 38 12.84 39.19 300 98.67
#> 11 12 8 10 64.47 4.10 38 12.84 52.03 300 98.67
#> 12 12 8 11 66.95 4.22 35 11.82 63.85 300 98.67
#> 13 12 8 12 69.61 4.41 34 11.49 75.34 300 98.67
#> 14 12 8 13 72.55 4.73 41 13.85 89.19 300 98.67
#> 15 12 8 14 76.02 5.28 25 8.45 97.64 300 98.67
#> 16 12 8 15 80.59 6.42 5 1.69 99.32 300 98.67
#> 17 12 8 16 89.00 10.47 2 0.68 100.00 300 98.67
#> Note: each table converts complete responses to the standard form only.
#> Wave 6: 4 of 300 person-waves with missing responses are not in the table; score them with kits_subj().
#> Wave 12: 4 of 300 person-waves with missing responses are not in the table; score them with kits_subj().
#> Wave 24: 4 of 300 person-waves with missing responses are not in the table; score them with kits_subj().
這張表只換算答了完整題本的分數。 少答(有 NA)的人次不在表裡,要用 kits_subj() 計分。 印出來的最後幾行(Note:)列出每個波次少答的人次。 每一波的作答組合在 attr(tab, "forms"),第 1 個(type 是 standard)就是題本:
attr(tab, "forms") |>
select(wave_m, type, n)
#> wave_m type n
#> 1 6 standard 296
#> 2 6 missing responses 4
#> 3 12 standard 296
#> 4 12 missing responses 4
#> 5 24 standard 296
#> 6 24 missing responses 4
form_pct 是答題本的孩子佔這一波的百分比。
只給校準(kits_subj_table(item03))時,列出每個作答組合的每個原始分,不用資料(施測現場查表用)。
兩組題的原始分對照
同一個校準裡的兩組題,一組的原始分在另一組大約是幾分? 例如少答 lan08 的孩子(7 題),在完整的 8 題上的期望原始分。 把 kits_subj_table(item03) 裡這個作答組合的 θ,代進完整題本的期望分數:
tab7 <- kits_subj_table(item03) |>
filter(pattern_id == "bk2") # 少答 lan08 的作答組合(7 題)
mutate(tab7, full = kits_expected_score(item03)(theta)) # 完整 8 題的期望原始分
#> <kits_subj_table> every raw score of every item pattern, from the item parameters; scale: lan M03
#> pattern_id raw theta se full
#> 1 bk2 0 -4.27 1.55 0.45
#> 2 bk2 1 -3.04 0.94 1.35
#> 3 bk2 2 -2.38 0.78 2.27
#> 4 bk2 3 -1.89 0.70 3.21
#> 5 bk2 4 -1.46 0.65 4.17
#> 6 bk2 5 -1.07 0.63 5.14
#> 7 bk2 6 -0.69 0.62 6.14
#> 8 bk2 7 -0.32 0.62 7.15
#> 9 bk2 8 0.05 0.62 8.17
#> 10 bk2 9 0.42 0.63 9.22
#> 11 bk2 10 0.82 0.66 10.28
#> 12 bk2 11 1.26 0.70 11.38
#> 13 bk2 12 1.77 0.79 12.55
#> 14 bk2 13 2.45 0.96 13.81
#> 15 bk2 14 3.72 1.57 15.22
PCM 的原始分是充分統計量,所以原始分 → θ 的表就是計分;兩組題不需要另外做原始分對原始分的等化(Kolen & Brennan, 2014, §6.4)。 固定的一組題 S 的分量表分數是 kits_expected_score(item, items = S)(theta),SE 是 IS(θ) × SE(θ),IS 來自 kits_information(item, items = S)。
5 常模
KIT 用 sinh-arcsinh(shash)模型。 它是一種可以偏態、厚尾的分佈;四個參數各是月齡的多項式。 分數可以是 0 或負數,不用加常數。 常模的函數直接用換算好的 lan(D、波次、月齡、世代都在裡面)。
5.1 篩波次。 一個波次有 20% 以上的孩子拿到這一波的最低分,就不放進常模。 這種波次看不出誰落後,分佈也不連續。 兩個世代都有的波次分開算,lan 的 cohort 欄會自動用上。
screen <- kits_norm_screen(lan)
screen
#> <kits_norm_screen> waves with 20% or more at the lowest score are left out
#> cohort wave_m n floor_pct normed
#> M03 6 300 1.70 TRUE
#> M03 12 300 0.30 TRUE
#> M03 24 300 0.70 TRUE
#> M36 36 300 0.70 TRUE
#> M36 48 300 1.00 TRUE
#> Person-waves in the norms: 1500 of 1500 (use $normed)
#> Checks: OK
use <- filter(lan, screen$normed)
這份模擬資料每一波都放進常模。 plot(screen) 畫每一波最低分的比例;拿掉的波次是橘色,虛線是 20%。
5.2 月齡範圍。 從第一個波次的第 1 百分位,到最後一個波次的第 99 百分位。 kits_norm_ages() 直接用 use 的 age_m 和 wave_m 欄。
ages <- kits_norm_ages(use)
range(ages)
#> [1] 5 49
5.3 配常模。 給分數表、不給 ages 時,kits_norms() 自己用 kits_norm_ages() 的規則,只用範圍內的資料;印出來時寫明範圍。 要別的範圍時給 ages =(要寫名字),pad 是範圍外再多用幾個月的資料。 degrees 是四個參數的多項式次數。 預設的 c(5, 4, 3, 2) 是給 KIT 的 3 到 72 個月、十幾個波次。 這裡只有 5 個波次,次數太多時曲線會亂跳,所以用 c(3, 2, 1, 1)。
norm <- use |>
kits_norms(degrees = c(3, 2, 1, 1))
norm
#> <kits_norms> scale: D
#> norm fitted on 5 to 49 months (set by kits_norm_ages())
#> age_m mu sigma epsilon delta n neg_loglik P3 P5 P10 P15 P25 P50 P75 P85 P90
#> 1 5.00 54.45 6.64 -0.03 0.95 1500 5128.86 41.35 43.05 45.66 47.40 49.95 54.66 59.46 62.11 63.94
#> 2 5.50 55.47 6.65 -0.03 0.95 1500 5128.86 42.37 44.08 46.68 48.43 50.97 55.68 60.49 63.15 64.98
#> 3 6.00 56.42 6.65 -0.03 0.95 1500 5128.86 43.32 45.03 47.64 49.38 51.93 56.64 61.45 64.12 65.95
#> 4 6.50 57.32 6.66 -0.03 0.95 1500 5128.86 44.22 45.93 48.54 50.28 52.83 57.54 62.36 65.03 66.87
#> 5 7.00 58.16 6.66 -0.03 0.95 1500 5128.86 45.07 46.78 49.39 51.13 53.68 58.40 63.22 65.89 67.73
#> 6 7.50 58.97 6.67 -0.03 0.95 1500 5128.86 45.88 47.59 50.20 51.94 54.49 59.21 64.03 66.71 68.56
#> 7 8.00 59.73 6.67 -0.04 0.95 1500 5128.86 46.65 48.35 50.96 52.71 55.26 59.98 64.81 67.49 69.34
#> 8 8.50 60.46 6.67 -0.04 0.95 1500 5128.86 47.38 49.08 51.69 53.44 55.99 60.71 65.55 68.23 70.08
#> 9 9.00 61.15 6.68 -0.04 0.95 1500 5128.86 48.07 49.78 52.39 54.13 56.69 61.41 66.26 68.94 70.80
#> 10 9.50 61.82 6.68 -0.04 0.95 1500 5128.86 48.74 50.45 53.06 54.80 57.36 62.08 66.93 69.63 71.48
#> P95 P97
#> 1 66.68 68.49
#> 2 67.73 69.53
#> 3 68.71 70.52
#> 4 69.63 71.44
#> 5 70.50 72.32
#> 6 71.33 73.15
#> 7 72.11 73.94
#> 8 72.87 74.69
#> 9 73.58 75.41
#> 10 74.27 76.11
#> ... 79 more rows (more: print(x, n = 50); all rows: as.data.frame(x))
#> Next: kits_norm_z(scores, x), plot(x), plot(x, scores = scores).
#> Checks: OK
norm |>
filter(age_m %in% c(6, 12, 24, 36, 48)) |>
select(age_m, mu, sigma, P10, P50, P90)
#> <kits_norms> scale: D
#> norm fitted on 5 to 49 months (set by kits_norm_ages()); showing 5 rows
#> age_m mu sigma P10 P50 P90
#> 1 6 56.42 6.65 47.64 56.64 65.95
#> 2 12 64.78 6.70 56.03 65.07 74.53
#> 3 24 74.24 6.80 65.52 74.65 84.35
#> 4 36 80.12 6.90 71.39 80.61 90.54
#> 5 48 84.46 6.99 75.72 85.04 95.18
#> Next: kits_norm_z(scores, x), plot(x), plot(x, scores = scores).
怎麼讀:
- 一列一個月齡(每半個月一列)。
mu、sigma、epsilon、delta 是這個月齡的分佈:位置、尺度、偏態、尾巴。
P3 到 P97 是百分位。
summary(norm) 檢查常模本身:收斂、最大的梯度、係數有沒有碰到邊界、同齡 z 的平均和 SD。 收斂的標準和 kits_item() 相同:不在界上的係數,最大的梯度在 10−4 以下,而且 Hessian 正定。 不收斂時,警告會建議降低 degrees。
summary(norm)
#> <kits_norms> ages 5 to 49 months (89 rows); scale: D
#> Diagnostics:
#> status check value message
#> ok optimizer convergence 0 converged (nlminb code 0: relative convergence (4); max |gradient| < 1e-4, Hessian of the free coefficients positive definite)
#> ok max |gradient| 5.89e-07 largest |d(-log L) / d coefficient| over the free coefficients (tolerance 1e-4)
#> ok coefficients at their limits 0 none
#> ok max delta 0.95 largest tail parameter over the ages
#> ok same-age z: max |mean| by age decile 0.0913 largest at ages (19,24.1]
#> ok same-age z: SD furthest from 1 by age decile 1.06 at ages (10.2,12]
plot(norm) 畫百分位曲線。 給 scores =(分數表)時,也畫出孩子:每一格畫人數,少於 5 個孩子的格子不畫。 persons = "points" 改成每個孩子一個點。
kits_norms() 也是三步,每一步一個函數: kits_norm_data()(範圍內的資料)、kits_norm_fit()(shash 的最大概似)、kits_norm_table()(每個月齡的參數和百分位)。 三步給的常模數字和 kits_norms() 相同,但三步的結果沒有量尺標籤,所以 kits_norm_z() 無法檢查分數的量尺。 kits_algorithm(norm) 也列出這三步,最後再加 kits_norm_z()(同齡 z)。
norm_data <- kits_norm_data(use$theta, use$age_m, ages = ages, pad = 0)
norm_fit <- kits_norm_fit(norm_data, degrees = c(3, 2, 1, 1))
norm_steps <- kits_norm_table(norm_fit)
all.equal(norm_steps$P50, norm$P50)
#> [1] TRUE
5.4 同齡 z。 kits_norm_z(lan, norm) 用 lan 的 D 和月齡,回傳分數表,多 z(同齡 z)和 pct(百分等級)兩欄。 各波次的平均應該接近 0、SD 接近 1。
lan <- lan |>
kits_norm_z(norm)
lan |>
filter(!is.na(z)) |>
summarise(mean = mean(z), sd = sd(z), .by = c(cohort, wave_m))
#> cohort wave_m mean sd
#> 1 M03 6 0.001031379 0.9833353
#> 2 M03 12 0.019518836 1.0301579
#> 3 M03 24 -0.045863521 1.0037756
#> 4 M36 36 0.043292893 0.9664685
#> 5 M36 48 -0.015085188 1.0197346
月齡在常模範圍外時,z 是 NA。 pct 是 100 * pnorm(z)。 給一串數字(kits_norm_z(d, age_m, norm))時,回傳一串 z。
6 檢查
題目:kits_itemfit(),每題一列。
fit03 <- kits_itemfit(X03, item03)
fit03
#> Item fit (conditional): infit and outfit given the raw score within each item pattern
#> item_id n pvalue rir infit outfit flag
#> 1 lan01 866 0.90 0.38 1.07 1.36 ok
#> 2 lan02 866 0.87 0.48 0.97 0.99 ok
#> 3 lan03 866 0.78 0.53 1.00 1.02 ok
#> 4 lan04 866 0.71 0.57 1.01 0.96 ok
#> 5 lan05 866 0.60 0.59 1.02 1.01 ok
#> 6 lan06 866 0.49 0.62 0.98 0.96 ok
#> 7 lan07 866 0.39 0.58 0.95 1.05 ok
#> 8 lan08 854 0.28 0.52 0.95 0.98 ok
#> Checks: OK
怎麼讀:
rir:這題和其他題總分的相關(item-rest)。
infit、outfit:殘差的均方,期望值是 1。
- 大於 1 表示作答比模型預期的亂;小於 1 表示比預期的整齊。
- 預設(
type = "conditional")的期望值和變異數,是給定這個人次在他的題本裡的原始分, 這題分數的條件分布(只用題目參數,不用估 θ)。所以題數少時也不會偏低。
type = "wle" 把每個孩子的 WLE 代進模型(Winsteps、TAM、eRm 的做法)。 題數少時,這個版本的均方會低於 1:每個孩子的 θ 是用同樣的作答估的。
flag:infit 或 outfit 高於 1.4 是 underfit(比模型預期的亂); 不然低於 0.6 是 overfit(太好預測);其他是 ok。 flag 是提醒去看這一題,不是刪題的規則。
plot() 畫每題的 infit 和 outfit;灰色的帶是常用的範圍 0.6 到 1.4,點的顏色是 flag。
全部一起檢查:kits_check() 把每個結果的警告和註記放在同一張表。 預設只列警告和註記;all = TRUE 列出每一項。 疊過的表(例如 lan)也可以給。
kits_check(item03, item36, itemm03, co, d_link, s03, s36, lan, tab, fit03, screen, norm)
#> object status check value message
#> item36 note smallest category count 4 fewer than 5 persons in: lan05:0 (4)
#> s03 note share of misfit 6.11 6.1% of person-waves (above 5%)
#> s03 note reliability, wave 24 0.373 1 - mean(se^2) / var(theta): below 0.5; check the shares of perfect and zero scores (flag)
#> s36 note share of misfit 6.83 6.8% of person-waves (above 5%)
#> lan note share of misfit 6.4 6.4% of person-waves (above 5%)
#> lan note reliability, wave 24 0.373 1 - mean(se^2) / var(theta): below 0.5; check the shares of perfect and zero scores (flag)
讀法:
warn:要處理或要在報告中說明。
note:知道就好。
來源:kits_info() 記下呼叫、kits 版本、設定、資料的指紋(md5)。 md5 是資料的指紋:資料改了,md5 就不同。 沒有時間戳記,所以同樣的輸入給完全相同的物件。
kits_info(s36)
#> <kits_info>
#> Call: kits_subj(X36, item36, persons = persons)
#> kits version: 0.25.2
#> Settings: scale = lan M36
#> Data: 600 person-waves, 8 items (lan05, lan06, lan07, lan08, lan09, lan10, ...)
#> person ids md5 150c84263d0e63bb15a1983009fd9a9a, responses md5 ebf9f08830873be6b202b4d40fa23ac6
#> Persons table: 600 person-waves, columns wave_m, age_m, sex, cohort; md5 8a12c728442b7256f520f9e2de58837d
#> Item parameters from data: 600 person-waves, 8 items (lan05, lan06, lan07, lan08, lan09, lan10, ...)
#> person ids md5 150c84263d0e63bb15a1983009fd9a9a, responses md5 ebf9f08830873be6b202b4d40fa23ac6
#> Converted: lan M36 -> lan M03; lan M03 -> D
訊息和錯誤只給人次的數目,不列出 person id。 要看 id 時,設 options(kits.show_ids = TRUE)。
一列的摘要:as.data.frame() 把一個結果變成一列(或每個波次一列)的 data.frame, 幾個結果用 bind_rows() 疊起來比較,例如兩條連結和兩個校準:
bind_rows(as.data.frame(co_boot), as.data.frame(d_link)) |>
select(from, to, kind, intercept, slope, intercept_error, n_points, n_dropped, checks)
#> from to kind intercept slope intercept_error n_points n_dropped checks
#> 1 lan M36 lan M03 calibration 2.503353 1.053387 0.07240139 8 0 ok
#> 2 lan M03, motor M03 D anchors 59.833373 6.695894 NA 8 0 ok
bind_rows(as.data.frame(item03), as.data.frame(item36)) |>
select(scale, n_persons, n_items, n_patterns, checks)
#> scale n_persons n_items n_patterns checks
#> 1 lan M03 900 8 2 ok
#> 2 lan M36 600 8 1 note
下一步
- 題目分析(通過月齡、性別 DIF)和報告的圖:進階篇第 7 節。
- 新的波次進來:進階篇第 8 節。
- 每一步的公式、函數和文獻(寫方法段用):
kits_algorithm(co),進階篇第 9 節。
- 連結誤差、垂直量尺、精準度(Kolen & Brennan, 2014):進階篇第 10 節。
vignette("kits-advanced", package = "kits")
引用
citation("kits") 是 kits 本身。 用到的方法,原始文獻列在 ?"kits-package" 的 References,例如 PCM(Masters, 1982)、WLE(Warm, 1989)、 mean/sigma(Marco, 1977)、D score(Weber et al., 2019)、shash(Jones & Pewsey, 2009)、 cNORM(Lenhard & Lenhard, 2021)。