kits 入門:從作答到常模

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:左邊是孩子能力的分佈,右邊是每題每個步驟的門檻。 兩邊用同一個 θ 軸。

plot(k_mix)

後面的連結、計分、常模、檢查,二點計分和混合計分都一樣做。

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() 是四步,每一步也是一個函數,可以一步一步跑,看每一步的結果:

  1. kits_link_points():每個共同步驟一個點(M36 的門檻、M03 的門檻);
  2. kits_link_screen():用所有的點畫一條線,殘差超過 2 個 SD 的點標記、丟掉;
  3. kits_link_line():用留下的點畫最後的線(mean/sigma);
  4. 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)、最後的線。 滑鼠停在點上可以看題號和殘差。

plot(co)

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%。

plot(screen)

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" 改成每個孩子一個點。

plot(norm, scores = use)

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。

plot(fit03)

全部一起檢查: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)。