| Title: | Marginal Maximum Likelihood Estimation for Item Response Models |
|---|---|
| Description: | Self-contained marginal maximum likelihood (MML) estimation for unidimensional and multidimensional item response models, including the Rasch / one-parameter logistic, partial credit, rating scale, two-parameter logistic and generalised partial credit models, with latent regression, multiple groups and case weights. A parallelised, dimension-factorised streaming estimation engine supports large between-item (simple-structure) multidimensional models with bounded memory and an opt-in controlled-accuracy quadrature mode that reports a measured approximation error. A usability layer serves non-specialists and automated pipelines: one-stop estimation from common file formats ('Excel', delimited text, 'SPSS', 'Stata', 'SAS') with automatic cleaning and answer-key scoring, pre-estimation data checks, classical item statistics and item fit, plain-language quality ratings, bilingual (English/Chinese) output, spreadsheet exports for item banking and cross-year linking, audience-specific 'Word'/'HTML' reports, and machine-readable results with structured error conditions. Methods follow Adams, Wilson and Wang (1997) <doi:10.1177/0146621697211001>. |
| Authors: | Kunxiang Ma [aut, cre], WEIAN DATA TECH (Beijing) Co., Ltd. [cph, fnd] |
| Maintainer: | Kunxiang Ma <[email protected]> |
| License: | GPL (>= 2) |
| Version: | 1.1.2 |
| Built: | 2026-09-26 07:58:09 UTC |
| Source: | https://github.com/weiandata/irtc |
A self-contained marginal maximum likelihood estimation package for unidimensional and multidimensional item response models. It supports Rasch / 1PL, partial credit, rating scale, 2PL and generalized partial credit models, latent regression, multiple groups, case weights, and a bounded-memory streaming engine for large simple-structure models.
Two entry points are provided.
For statisticians, the expert functions irtc.mml and
irtc.mml.2pl return an object of class irtc with
summary, print, logLik, anova and
plot methods.
For non-specialists and automated pipelines, the one-stop function
irtc reads data from common file formats
(irtc_read), scores raw responses
(irtc_score), checks the data
(irtc_check_data) and enriches the fitted model with
classical statistics (irtc_ctt), item fit
(irtc_itemfit) and plain-language quality ratings
(irtc_quality). Results can be exported as three Excel
workbooks (irtc_excel), audience-specific Word/HTML reports
(irtc_report), a layered text summary
(plain_summary) or machine-readable structures
(irtc_results, irtc_json; schema reference in
inst/llms.txt). User-facing output is bilingual; see
irtc_lang.
Kunxiang Ma and WEIAN DATA TECH (Beijing) Co., Ltd.
A simulated dataset of polytomous item responses suitable for fitting partial credit and generalised partial credit models, provided as an example dataset for IRTC.
data(data.gpcm)data(data.gpcm)
A data frame with 392 persons (rows) and 3 polytomously scored items
(columns Comfort, Work, Benefit), each scored in four
ordered categories 0, 1, 2, 3.
The data were simulated from a partial credit model with three step parameters per item. The generating script is ‘scripts/gen_data.R’ in the package sources.
Simulated by IRTC. See ‘scripts/gen_data.R’ in the package sources for the reproducible generating code.
A simulated dataset of multiple-choice item responses provided in both raw and scored form, provided as an example dataset for IRTC.
data(data.mc)data(data.mc)
A list with two components:
A character matrix (143 persons x 30 items) of raw multiple-choice
responses coded "A"–"D", with "9" denoting an omitted or
not-reached response.
A data frame of the corresponding dichotomously scored (0/1) responses, where 1 marks the keyed (correct) option.
Correctness was simulated under a Rasch model; raw responses were then built from a randomly assigned answer key with distractors drawn at random. The generating script is ‘scripts/gen_data.R’ in the package sources.
Simulated by IRTC. See ‘scripts/gen_data.R’ in the package sources for the reproducible generating code.
A simulated dataset of dichotomous (0/1) item responses generated under the Rasch model, provided as an example dataset for IRTC.
data(data.sim.rasch)data(data.sim.rasch)
A matrix with 2000 persons (rows) and 40 dichotomous items (columns), with entries coded 0 or 1.
The data were simulated with person abilities drawn from a standard normal
distribution and item difficulties spread evenly over .
The generating script is ‘scripts/gen_data.R’ in the package sources.
Simulated by IRTC. See ‘scripts/gen_data.R’ in the package sources for the reproducible generating code.
Reads data from a file or R object, cleans it, optionally scores raw multiple-choice responses against an answer key, checks it, estimates the requested item response model and enriches the result with classical item statistics, item fit and plain-language quality ratings.
Designed for non-specialists and automated callers; professional users keep
full control because all additional arguments are passed unchanged to
irtc.mml (for "1PL", "PCM", "PCM2",
"RSM") or irtc.mml.2pl (for "2PL",
"GPCM").
irtc(data, model, key = NULL, rules = NULL, q = NULL, on_mismatch = c("warn", "error"), rare_categories = c("collapse", "prior"), id = NULL, weights = NULL, sheet = 1, missing_codes = c(-9, -99, 99, 999), check = TRUE, quality = TRUE, verbose = TRUE, ...)irtc(data, model, key = NULL, rules = NULL, q = NULL, on_mismatch = c("warn", "error"), rare_categories = c("collapse", "prior"), id = NULL, weights = NULL, sheet = 1, missing_codes = c(-9, -99, 99, 999), check = TRUE, quality = TRUE, verbose = TRUE, ...)
data |
A file path ( |
model |
Required. One of |
key |
An optional answer key for raw responses: a named vector or
a key-file path (optionally with a partial-answer column); see
|
rules |
An optional partial-credit scoring table or a path to one;
see |
q |
An optional Q (item-by-dimension) matrix: a file path, data
frame, matrix, or an object from |
on_mismatch |
How to handle items that differ between the Q matrix
and the data; see |
rare_categories |
How to handle score categories nobody reached
(nobody fully correct / partially correct / wrong).
|
id |
An optional person identifier column (name or index); passed to
|
weights |
An optional sampling weight column (name or index); passed
to |
sheet |
Excel sheet to read (number or name). |
missing_codes |
Numeric values recoded to |
check |
Logical: stop with a structured error if
|
quality |
Logical: attach CTT statistics, item fit and quality ratings to the result? |
verbose |
Logical: print estimation progress? |
... |
Further arguments passed to |
Items without any observed responses or without variance are removed
before estimation (with a warning of class irtc_warning_estimation).
All errors raised by the usability layer are structured conditions with
fields code, reason and fix; see the package file
inst/llms.txt for the full list of codes.
The language of user-facing output is controlled by
options(irtc.lang = "zh") (default) or "en".
An object of class irtc (see irtc.mml) with an
additional list component usability containing model,
data_log (cleaning log), check (the
irtc_check_data result), removed_items,
ctt, itemfit and quality.
irtc_read, irtc_check_data,
plain_summary, irtc_excel,
irtc_report, irtc_results
set.seed(1) theta <- rnorm(150) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 5), function(b) { as.numeric(runif(150) < plogis(theta - b)) })) colnames(resp) <- paste0("I", 1:5) mod <- irtc(resp, model = "1PL", verbose = FALSE) plain_summary(mod, lang = "en") mod$usability$qualityset.seed(1) theta <- rnorm(150) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 5), function(b) { as.numeric(runif(150) < plogis(theta - b)) })) colnames(resp) <- paste0("I", 1:5) mod <- irtc(resp, model = "1PL", verbose = FALSE) plain_summary(mod, lang = "en") mod$usability$quality
Checks whether a saved analysis was hit by the answer-key defect fixed in IRTC 1.1.2, without re-running it.
Up to and including 1.1.1, irtc_read renumbered an item's
categories to consecutive scores starting at 0 (a file coded 1..5 became
0..4) and irtc_score then matched the answer key against the
renumbered values. A key written in the coding of the data file therefore
selected the wrong option. Nothing warned, and the resulting 0/1 matrix
looks entirely plausible, so an affected analysis cannot be spotted by
inspecting its results.
The risk condition is narrow and mechanically checkable: an item was scored against a key or a rules table and its categories were renumbered. Both facts are recorded in the cleaning log that every fit carries, so an archived analysis can be triaged from the saved object alone.
irtc_audit_scoring(x) ## S3 method for class 'irtc_audit' print(x, lang = irtc_lang(), ...)irtc_audit_scoring(x) ## S3 method for class 'irtc_audit' print(x, lang = irtc_lang(), ...)
x |
A fitted |
lang |
Output language, |
... |
Ignored. |
An object of class irtc_audit: a list with status,
affected_items, an items data frame (item,
categories_renumbered, original_categories),
package_version, n_scored and n_recoded.
status is one of:
"not_scored"No key or rules were used: the responses were already scored, so the defect cannot apply.
"not_affected"A key was used, but no scored item had its categories renumbered, so the key applied to the intended options.
"ok_fixed"Produced by IRTC 1.1.2 or later, where the key is applied in the coding of the data file itself.
"affected"Scored against a key after renumbering, by a version known to predate the fix. Re-run the analysis.
"affected_if_before_1.1.2"The same risk condition, but the recording version is not stamped on the object (fits made before 1.1.2 carry no stamp). Establish which version produced it.
set.seed(1) n <- 120 raw <- data.frame(pid = seq_len(n), Q1 = sample(1:4, n, TRUE), Q2 = sample(1:4, n, TRUE), Q3 = sample(1:3, n, TRUE), Q4 = sample(1:3, n, TRUE)) f <- tempfile(fileext = ".csv") utils::write.csv(raw, f, row.names = FALSE) mod <- irtc(f, model = "2PL", key = c(Q1 = 2, Q2 = 3, Q3 = 1, Q4 = 2), id = "pid", verbose = FALSE) irtc_audit_scoring(mod) ## an archived fit from an older version reports the items to re-run archived <- mod archived$usability$package_version <- "1.1.1" irtc_audit_scoring(archived)$affected_items unlink(f)set.seed(1) n <- 120 raw <- data.frame(pid = seq_len(n), Q1 = sample(1:4, n, TRUE), Q2 = sample(1:4, n, TRUE), Q3 = sample(1:3, n, TRUE), Q4 = sample(1:3, n, TRUE)) f <- tempfile(fileext = ".csv") utils::write.csv(raw, f, row.names = FALSE) mod <- irtc(f, model = "2PL", key = c(Q1 = 2, Q2 = 3, Q3 = 1, Q4 = 2), id = "pid", verbose = FALSE) irtc_audit_scoring(mod) ## an archived fit from an older version reports the items to re-run archived <- mod archived$usability$package_version <- "1.1.1" irtc_audit_scoring(archived)$affected_items unlink(f)
Checks a response data set before model estimation and returns both a human-readable report and a machine-readable issue table, so that survey staff and automated callers (e.g. AI agents) can verify data quality and act on concrete fixes.
Checked, among others: number of items and persons, non-numeric (raw) response columns, negative or non-integer values, items without responses or without variance, extreme missingness, sparse categories, persons without any responses and duplicated person IDs.
irtc_check_data(x, key = NULL, verbose = TRUE) ## S3 method for class 'irtc_check' print(x, lang = irtc_lang(), ...)irtc_check_data(x, key = NULL, verbose = TRUE) ## S3 method for class 'irtc_check' print(x, lang = irtc_lang(), ...)
x |
An |
key |
If an answer key will be used for scoring, pass it here so that raw text columns are reported as scorable instead of as errors. |
verbose |
Logical: print the result? |
lang |
Output language, |
... |
Ignored. |
An object of class irtc_check: a list with ok (logical:
no error-level issues), n_persons, n_items,
n_errors, n_warnings and issues, a data frame with
columns code, severity ("error", "warning",
"info"), scope, where, message_en,
message_zh, fix_en, fix_zh.
d <- data.frame(i1 = c(0, 1, 1, 0), i2 = c(1, 1, 1, 1), i3 = c(0, NA, 1, -9)) chk <- irtc_check_data(d, verbose = FALSE) chk$ok chk$issues[, c("code", "severity", "where")] print(chk, lang = "en")d <- data.frame(i1 = c(0, 1, 1, 0), i2 = c(1, 1, 1, 1), i3 = c(0, NA, 1, -9)) chk <- irtc_check_data(d, verbose = FALSE) chk$ok chk$issues[, c("code", "severity", "where")] print(chk, lang = "en")
Computes classical item statistics: item means and difficulty (share of the maximum score), corrected item-total (item-rest) correlations, Cronbach's alpha and alpha-if-item-deleted.
irtc_ctt(x, key = NULL, weights = NULL) ## S3 method for class 'irtc_ctt' print(x, lang = irtc_lang(), ...)irtc_ctt(x, key = NULL, weights = NULL) ## S3 method for class 'irtc_ctt' print(x, lang = irtc_lang(), ...)
x |
An |
key |
An optional answer key; raw responses are scored first via
|
weights |
Optional case (sampling) weights, one per person. All statistics – item means, difficulties, item-rest correlations and Cronbach's alpha – are then computed on the weighted sample, which is what a complex-sample survey needs so that the classical statistics describe the same population as the weighted IRT estimates. Constant weights reproduce the unweighted results exactly. |
lang |
Output language, |
... |
Ignored. |
An object of class irtc_ctt: a list with items (data frame
with columns item, N, miss_rate, max_score,
N_weighted, M, pvalue, discr,
alpha_if_deleted), alpha, n_persons,
n_items and weighted (whether case weights were applied).
set.seed(1) theta <- rnorm(200) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 6), function(b) { as.numeric(runif(200) < plogis(theta - b)) })) ctt <- irtc_ctt(resp) ctt$alpha ctt$items ## with sampling weights w <- runif(200, 0.5, 3) irtc_ctt(resp, weights = w)$items$pvalueset.seed(1) theta <- rnorm(200) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 6), function(b) { as.numeric(runif(200) < plogis(theta - b)) })) ctt <- irtc_ctt(resp) ctt$alpha ctt$items ## with sampling weights w <- runif(200, 0.5, 3) irtc_ctt(resp, weights = w)$items$pvalue
One call writes three separate .xlsx files (requires the optional
openxlsx package):
<prefix>_item_quality.xlsx - a plain-language item quality
table (score rate, difficulty label, discrimination, fit, colour-coded
overall rating, reasons and advice) plus a notes sheet explaining every
column, for judging item quality without statistical training.
<prefix>_item_parameters.xlsx - the IRT difficulty /
discrimination table with a frozen column schema
(schema_version, analysis_id, model,
item_id, n_obs, p_value, slope_a,
difficulty_b, se_b, tau_k), designed for
cross-year anchor-item linking: files from different administrations
can be merged directly on item_id.
<prefix>_person_ability.xlsx - a flat person ability table
(ID, number answered, raw score, EAP, standard error, percentile,
T score) whose rows stay in input order so it can be pasted next to a
master sample sheet.
irtc_excel(mod, dir = ".", prefix = "IRTC", lang = irtc_lang(), resp = NULL, overwrite = FALSE, verbose = TRUE) irtc_param_table(mod, resp = NULL, weights = NULL) irtc_person_table(mod, lang = irtc_lang(), weights = NULL)irtc_excel(mod, dir = ".", prefix = "IRTC", lang = irtc_lang(), resp = NULL, overwrite = FALSE, verbose = TRUE) irtc_param_table(mod, resp = NULL, weights = NULL) irtc_person_table(mod, lang = irtc_lang(), weights = NULL)
mod |
A fitted |
dir |
Output directory (created if necessary). |
prefix |
File name prefix. |
lang |
Language of headers and notes, |
resp |
The response data; only needed when the model object does not store it (streaming engine). |
weights |
Optional case (sampling) weights, one per person. For
|
overwrite |
Logical: overwrite existing files? Otherwise an error of
class |
verbose |
Logical: print a completion message? |
irtc_excel invisibly returns a named character vector with the
three file paths (quality, parameters, ability).
irtc_param_table and irtc_person_table return the underlying
data frames and work without openxlsx.
For a multidimensional model irtc_param_table adds a
dimension column naming the latent dimension each item loads on
(from the Q-matrix column headers when available), and n_loadings,
the number of dimensions the item loads on. slope_a and
difficulty_b are read from that dimension. An item loading on
several dimensions at once (within-item structure) has no single slope:
slope_a is then the sum of its loadings and n_loadings is
above 1, flagging the value as a composite.
The person-level percentile and T_score columns are
norm-referenced: with case weights they are computed on the weighted
sample, so a percentile is the share of the represented population below
that person. The ability estimates and their standard errors are
person-level quantities and are never weighted.
irtc, irtc_report,
irtc_results
set.seed(1) theta <- rnorm(150) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 5), function(b) { as.numeric(runif(150) < plogis(theta - b)) })) mod <- irtc(resp, model = "1PL", verbose = FALSE) ## data frames behind the workbooks (no extra packages needed) irtc_param_table(mod) head(irtc_person_table(mod, lang = "en")) if (requireNamespace("openxlsx", quietly = TRUE)) { out <- irtc_excel(mod, dir = tempdir(), overwrite = TRUE, verbose = FALSE) basename(out) }set.seed(1) theta <- rnorm(150) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 5), function(b) { as.numeric(runif(150) < plogis(theta - b)) })) mod <- irtc(resp, model = "1PL", verbose = FALSE) ## data frames behind the workbooks (no extra packages needed) irtc_param_table(mod) head(irtc_person_table(mod, lang = "en")) if (requireNamespace("openxlsx", quietly = TRUE)) { out <- irtc_excel(mod, dir = tempdir(), overwrite = TRUE, verbose = FALSE) basename(out) }
Residual-based item fit for a fitted model: infit and outfit mean squares with Wilson-Hilferty t statistics. Values near 1 indicate good fit; the conventional normal range is 0.7 to 1.3.
The residuals are evaluated at the EAP (expected a posteriori) person estimates – worth knowing when comparing the numbers against software that uses WLE estimates instead.
irtc_itemfit(mod, resp = NULL, weights = NULL) ## S3 method for class 'irtc_itemfit' print(x, lang = irtc_lang(), ...)irtc_itemfit(mod, resp = NULL, weights = NULL) ## S3 method for class 'irtc_itemfit' print(x, lang = irtc_lang(), ...)
mod |
A fitted |
resp |
The response data; only needed when the model object does not store it (streaming engine). |
weights |
Optional case (sampling) weights, one per person: each
person then contributes |
x |
An |
lang |
Output language, |
... |
Ignored. |
A data frame of class irtc_itemfit with columns item,
N, outfit, outfit_t, infit, infit_t.
set.seed(1) theta <- rnorm(200) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 5), function(b) { as.numeric(runif(200) < plogis(theta - b)) })) mod <- irtc.mml(resp = resp, verbose = FALSE) irtc_itemfit(mod)set.seed(1) theta <- rnorm(200) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 5), function(b) { as.numeric(runif(200) < plogis(theta - b)) })) mod <- irtc.mml(resp = resp, verbose = FALSE) irtc_itemfit(mod)
Returns the current output language, "zh" (Chinese, the default) or
"en" (English). Set it globally with
options(irtc.lang = "en"). The language affects user-facing text
(messages, printed summaries, Excel headers, reports); machine-readable
schemas (irtc_results, the linking table of
irtc_excel) never depend on it.
irtc_lang()irtc_lang()
"zh" or "en".
old <- options(irtc.lang = "en") irtc_lang() options(old)old <- options(irtc.lang = "en") irtc_lang() options(old)
Combines classical difficulty and discrimination
(irtc_ctt) with IRT item fit (irtc_itemfit)
into a four-level quality rating per item - "good",
"acceptable", "review", "revise" - with bilingual
reasons and advice that non-specialists can act on. Negative
discrimination (usually a wrong answer key) always leads to
"revise".
irtc_quality(mod, resp = NULL, thresholds = NULL, weights = NULL) irtc_quality_thresholds() ## S3 method for class 'irtc_quality' print(x, lang = irtc_lang(), ...)irtc_quality(mod, resp = NULL, thresholds = NULL, weights = NULL) irtc_quality_thresholds() ## S3 method for class 'irtc_quality' print(x, lang = irtc_lang(), ...)
mod |
A fitted |
resp |
The response data; only needed when the model object does not store it (streaming engine). |
weights |
Optional case (sampling) weights, one per person, passed on
to |
thresholds |
An optional named list overriding entries of
|
x |
An |
lang |
Output language, |
... |
Ignored. |
A data frame of class irtc_quality with columns item,
N, pvalue, discr, outfit, infit,
rating, reasons_en, reasons_zh, advice_en,
advice_zh, and attributes alpha (Cronbach's alpha) and
thresholds. irtc_quality_thresholds() returns the default
threshold list.
set.seed(1) theta <- rnorm(300) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 6), function(b) { as.numeric(runif(300) < plogis(theta - b)) })) resp$noise <- rbinom(300, 1, 0.5) # a broken item mod <- irtc.mml(resp = resp, verbose = FALSE) qual <- irtc_quality(mod) qual[, c("item", "pvalue", "discr", "rating")] print(qual, lang = "en")set.seed(1) theta <- rnorm(300) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 6), function(b) { as.numeric(runif(300) < plogis(theta - b)) })) resp$noise <- rbinom(300, 1, 0.5) # a broken item mod <- irtc.mml(resp = resp, verbose = FALSE) qual <- irtc_quality(mod) qual[, c("item", "pvalue", "discr", "rating")] print(qual, lang = "en")
Unified data import with automatic cleaning. Supports Excel
(.xlsx, .xls; requires readxl), delimited text
(.csv, .tsv, .txt, .dat; delimiter and
UTF-8/GBK encoding are detected automatically), SPSS/Stata/SAS files
(.sav, .por, .dta, .sas7bdat, .xpt;
requires haven) and in-memory data frames, matrices and tibbles.
Every cleaning action is recorded in a bilingual log attached to the returned object, so results remain traceable.
irtc_read(x, sheet = 1, id = NULL, weights = NULL, missing_codes = c(-9, -99, 99, 999), na_strings = c("", "NA", "N/A", "n/a", ".", "*", "\u7f3a\u5931", "\u65e0", "\u7a7a"), guess_id = TRUE, guess_weights = TRUE, clean = TRUE, recode = TRUE, verbose = TRUE) ## S3 method for class 'irtc_data' print(x, lang = irtc_lang(), ...)irtc_read(x, sheet = 1, id = NULL, weights = NULL, missing_codes = c(-9, -99, 99, 999), na_strings = c("", "NA", "N/A", "n/a", ".", "*", "\u7f3a\u5931", "\u65e0", "\u7a7a"), guess_id = TRUE, guess_weights = TRUE, clean = TRUE, recode = TRUE, verbose = TRUE) ## S3 method for class 'irtc_data' print(x, lang = irtc_lang(), ...)
x |
A file path or a data frame / matrix. For the print method: an
|
sheet |
Excel sheet to read (number or name). |
id |
Person identifier column(s), by name or index. The first is kept
as the person ID; the rest are set aside. If |
missing_codes |
Numeric codes recoded to |
na_strings |
Strings treated as missing in text columns. |
weights |
Sampling weight column, by name or index. If |
guess_id |
Logical: detect the person ID column automatically? |
guess_weights |
Logical: detect the weight column automatically? |
clean |
Logical: apply the cleaning steps (drop empty rows/columns, convert numeric-like text, recode missing codes)? |
recode |
Logical: recode integer response categories to consecutive scores starting at 0 (e.g. a 1-5 Likert item becomes 0-4)? |
verbose |
Logical: print the object (including the cleaning log)? |
lang |
Output language, |
... |
Ignored. |
An object of class irtc_data: a list with components resp
(cleaned response data frame), pid (person IDs or NULL),
weights (sampling weights or NULL), dropped
(set-aside non-response columns), log (cleaning log
data frame with columns step, code, message_en,
message_zh) and source.
With recode = TRUE (the default) integer response columns are
renumbered to consecutive scores starting at 0, which is what the estimation
needs. The original categories are kept in the recode_map component so
that an answer key written in the file's own coding still applies; see
irtc_score.
irtc, irtc_score,
irtc_check_data
path <- tempfile(fileext = ".csv") writeLines(c("id,i1,i2,i3", "s1,0,1,99", "s2,1,-9,1", "s3,1,0,0"), path) d <- irtc_read(path, verbose = FALSE) d$resp d$pid print(d, lang = "en") unlink(path)path <- tempfile(fileext = ".csv") writeLines(c("id,i1,i2,i3", "s1,0,1,99", "s2,1,-9,1", "s3,1,0,0"), path) d <- irtc_read(path, verbose = FALSE) d$resp d$pid print(d, lang = "en") unlink(path)
irtc_read_q reads a Q (item-by-dimension) matrix from any file
format supported by irtc_read (Excel, delimited text,
SPSS/Stata/SAS) or from a data frame / matrix. The dimension column
headers become the dimension names used in all downstream person-level
output (ability and standard-error headers). An optional partial-credit
column declares which items are scored in multiple levels, and
optionally their maximum scores.
irtc_align_q compares the Q-matrix items with the response-data
items. By default mismatches raise warnings (W420, W421)
and the analysis continues with the shared items;
on_mismatch = "error" turns any mismatch into an error
(E422).
irtc_read_q(x, sheet = 1) irtc_align_q(data, q, on_mismatch = c("warn", "error")) ## S3 method for class 'irtc_qmatrix' print(x, lang = irtc_lang(), ...)irtc_read_q(x, sheet = 1) irtc_align_q(data, q, on_mismatch = c("warn", "error")) ## S3 method for class 'irtc_qmatrix' print(x, lang = irtc_lang(), ...)
x |
A file path, data frame, or numeric matrix with item rownames.
For the print method: an |
sheet |
Excel sheet to read (number or name). |
data |
An |
q |
An |
on_mismatch |
|
lang |
Output language, |
... |
Ignored. |
Expected table layout for irtc_read_q:
An item-ID column named item, item_id,
"题目", "题号"
(etc.); if no such name is found, the first non-numeric column is
used.
One or more numeric dimension columns; each header is a dimension
name (e.g. "代数", algebra). Values are
usually 0/1 loadings. Empty cells count as 0. Every item must load
on at least one dimension.
Optionally one partial-credit column named partial,
max_score, "分部计分",
"满分" (etc.). Values may be 0/1,
TRUE/FALSE, yes/no, "是"/"否",
or integer maximum scores
(a value >= 2 declares a partial-credit item and records its
maximum score).
irtc_read_q returns an object of class irtc_qmatrix: a
list with components Q (numeric item-by-dimension matrix with
dimnames), partial (named logical vector), max_score
(named integer vector, NA when not declared), log and
source.
irtc_align_q returns a list with components data (the
input data restricted to the shared items), q (the aligned
irtc_qmatrix, rows in data column order), common,
q_only and data_only (character vectors of item names).
qdf <- data.frame(item = c("I1", "I2", "I3"), algebra = c(1, 1, 0), geometry = c(0, 0, 1), partial = c(0, 1, 0)) qm <- irtc_read_q(qdf) print(qm, lang = "en") resp <- data.frame(I1 = c(0, 1), I2 = c(2, 0), I4 = c(1, 1)) al <- suppressWarnings(irtc_align_q(resp, qm)) al$q_only al$data_onlyqdf <- data.frame(item = c("I1", "I2", "I3"), algebra = c(1, 1, 0), geometry = c(0, 0, 1), partial = c(0, 1, 0)) qm <- irtc_read_q(qdf) print(qm, lang = "en") resp <- data.frame(I1 = c(0, 1), I2 = c(2, 0), I4 = c(1, 1)) al <- suppressWarnings(irtc_align_q(resp, qm)) al$q_only al$data_only
Writes an analysis report with figures (Wright map, ability distribution,
item quality summary, item characteristic curves) and tables. Three
audience layouts are available: "decision" (a short executive
summary with recommendations), "survey" (a plain-language full
report) and "stat" (a complete technical report including item
parameters, item fit and model information). Every layout includes a
Model diagnostics section (convergence, information criteria with
interpretation, EAP reliability band, item-fit reading) and a Data
processing transparency section (weights summary, Q-matrix item
alignment, unobserved-category collapses, dropped items, scoring
summary, and - for "stat"/"survey" - the full cleaning
log), detailed for statisticians and condensed for other audiences.
HTML reports are fully self-contained single files (images embedded); Word reports require the optional officer package.
irtc_report(mod, file, format = NULL, audience = c("survey", "decision", "stat"), lang = irtc_lang(), resp = NULL, title = NULL, overwrite = FALSE, verbose = TRUE)irtc_report(mod, file, format = NULL, audience = c("survey", "decision", "stat"), lang = irtc_lang(), resp = NULL, title = NULL, overwrite = FALSE, verbose = TRUE)
mod |
A fitted |
file |
Output file path ending in |
format |
|
audience |
Report layout; see Description. |
lang |
Output language, |
resp |
The response data; only needed when the model object does not store it (streaming engine). |
title |
Report title; a bilingual default is used when |
overwrite |
Logical: overwrite an existing file? |
verbose |
Logical: print a completion message? |
Invisibly, the output file path.
irtc, plain_summary,
irtc_excel, plot.irtc
set.seed(1) theta <- rnorm(150) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 5), function(b) { as.numeric(runif(150) < plogis(theta - b)) })) mod <- irtc(resp, model = "1PL", verbose = FALSE) file <- tempfile(fileext = ".html") irtc_report(mod, file, audience = "decision", lang = "en", verbose = FALSE) file.exists(file) unlink(file)set.seed(1) theta <- rnorm(150) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 5), function(b) { as.numeric(runif(150) < plogis(theta - b)) })) mod <- irtc(resp, model = "1PL", verbose = FALSE) file <- tempfile(fileext = ".html") irtc_report(mod, file, audience = "decision", lang = "en", verbose = FALSE) file.exists(file) unlink(file)
Extracts fitted-model results as tidy data frames with a stable,
documented schema aimed at automated callers (pipelines, AI agents).
Column names are fixed English snake_case identifiers and never depend on
the language option. irtc_json exports the same content as JSON
(requires the optional jsonlite package).
Schema v1.2 components: model_info (one row: schema and package
version, model, sizes, deviance, AIC/BIC, EAP reliability, iterations,
and dimension_names when a Q matrix was supplied),
items (per item: parameters, classical statistics, fit, quality
rating and bilingual reasons/advice, plus a status column, for
polytomous items semantic step difficulties and rare-category
annotations, and for multidimensional models dimension and
n_loadings), persons (per person: ID, number answered, raw
score, EAP, SE, percentile, T score; ability/SE headers use the Q
dimension names when available), cleaning_log and
check_issues (present when the model was fitted via
irtc). Schema 1.2 is additive over 1.1: existing column
names and meanings are unchanged.
Sampling weights given to irtc are honoured throughout: the
item statistics share one population with the IRT parameters, and the
norm-referenced person columns percentile and t_score are
computed on the weighted sample, so a percentile is the share of the
represented population below that person. Pass weights to override
the weights stored on the model. The ability estimates and their standard
errors are person-level quantities and are never weighted. Items declared in the Q matrix but not
estimated keep a row with status "dropped_no_response" or
"declared_not_estimated". See the package file
inst/llms.txt for the full schema reference.
irtc_results(mod, resp = NULL, weights = NULL) irtc_json(mod, file = NULL, resp = NULL, pretty = TRUE) ## S3 method for class 'irtc_results' print(x, lang = irtc_lang(), ...)irtc_results(mod, resp = NULL, weights = NULL) irtc_json(mod, file = NULL, resp = NULL, pretty = TRUE) ## S3 method for class 'irtc_results' print(x, lang = irtc_lang(), ...)
mod |
A fitted |
resp |
The response data; only needed when the model object does not store it (streaming engine). |
weights |
Optional case (sampling) weights, one per person, used for
the norm-referenced |
file |
Optional path; when given, the JSON is written there (UTF-8) and the path is returned invisibly. |
pretty |
Logical: pretty-print the JSON? |
x |
An |
lang |
Output language, |
... |
Ignored. |
irtc_results returns an object of class irtc_results, a list
of data frames as described above. irtc_json returns a JSON string,
or invisibly the file path when file is given.
set.seed(1) theta <- rnorm(150) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 5), function(b) { as.numeric(runif(150) < plogis(theta - b)) })) mod <- irtc(resp, model = "1PL", verbose = FALSE) res <- irtc_results(mod) res$model_info head(res$items) if (requireNamespace("jsonlite", quietly = TRUE)) { json <- irtc_json(mod) substr(json, 1, 80) }set.seed(1) theta <- rnorm(150) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 5), function(b) { as.numeric(runif(150) < plogis(theta - b)) })) mod <- irtc(resp, model = "1PL", verbose = FALSE) res <- irtc_results(mod) res$model_info head(res$items) if (requireNamespace("jsonlite", quietly = TRUE)) { json <- irtc_json(mod) substr(json, 1, 80) }
Converts raw multiple-choice responses (e.g. "A", "B",
"C", "D") to 0/1 scores using an answer key, or maps
responses to partial-credit scores through a rules table. Responses are
normalised before matching: whitespace is trimmed, case is ignored and
full-width characters are converted to half-width.
Both key and rules may also be file paths in any format
supported by irtc_read. A key file needs an item column
(item, "题目", ...) and an answer column
(answer, "答案", ...); an optional
partial-answer column (partial_answer,
"部分正确答案", ...)
lists answers worth partial credit, several separated by |,
;, , or "、". Items with partial
answers are scored full = 2, partial = 1, other = 0; items without stay
0/1. For more than three score levels supply an explicit rules
table instead.
irtc_score(resp, key = NULL, rules = NULL, na_as_wrong = FALSE, sheet = 1)irtc_score(resp, key = NULL, rules = NULL, na_as_wrong = FALSE, sheet = 1)
resp |
An |
key |
A named vector mapping item names to correct answers, e.g.
|
rules |
A data frame with columns |
na_as_wrong |
Logical: score missing responses as 0 instead of
|
sheet |
Excel sheet of the key / rules file (number or name). |
The same type as the input: an irtc_data object with scored
resp and an extended log, or a data frame with a
"scoring_log" attribute.
Write the key in the coding of your own data file. irtc_read
may renumber an item's categories to consecutive scores starting at 0 (a file
coded 1..5 becomes 0..4, logged as I124); it records the original
categories, and irtc_score translates the key, the partial answers and
a rules table through that record. An answer that never occurs among
an item's responses is reported with a warning (code W205) rather than
scoring every respondent wrong – which catches a mistyped key as well as an
option nobody chose.
resp <- data.frame(Q1 = c("A", "b", "C"), Q2 = c("c", "C", "D"), stringsAsFactors = FALSE) irtc_score(resp, key = c(Q1 = "A", Q2 = "C")) rules <- data.frame(item = "Q1", response = c("A", "B", "C"), score = c(2, 1, 0)) irtc_score(resp["Q1"], rules = rules)resp <- data.frame(Q1 = c("A", "b", "C"), Q2 = c("c", "C", "D"), stringsAsFactors = FALSE) irtc_score(resp, key = c(Q1 = "A", Q2 = "C")) rules <- data.frame(item = "Q1", response = c("A", "B", "C"), score = c(2, 1, 0)) irtc_score(resp["Q1"], rules = rules)
Estimates unidimensional and multidimensional fixed-slope item response
models by marginal maximum likelihood (MML). Supported models include the
Rasch / 1PL model, the partial credit model ("PCM", "PCM2")
and the rating scale model ("RSM"). Latent regression on covariates,
multiple groups, case weights and user-specified design matrices are
supported.
irtc.mml(resp, Y = NULL, group = NULL, irtmodel = "1PL", formulaY = NULL, dataY = NULL, ndim = 1, pid = NULL, xsi.fixed = NULL, xsi.inits = NULL, beta.fixed = NULL, beta.inits = NULL, variance.fixed = NULL, variance.inits = NULL, est.variance = TRUE, constraint = "cases", A = NULL, B = NULL, B.fixed = NULL, Q = NULL, est.slopegroups = NULL, E = NULL, pweights = NULL, userfct.variance = NULL, variance.Npars = NULL, item.elim = TRUE, verbose = TRUE, control = list()) ## S3 method for class 'irtc' summary(object, file = NULL, ...) ## S3 method for class 'irtc' print(x, ...) ## S3 method for class 'irtc' logLik(object, ...) ## S3 method for class 'irtc' anova(object, ...)irtc.mml(resp, Y = NULL, group = NULL, irtmodel = "1PL", formulaY = NULL, dataY = NULL, ndim = 1, pid = NULL, xsi.fixed = NULL, xsi.inits = NULL, beta.fixed = NULL, beta.inits = NULL, variance.fixed = NULL, variance.inits = NULL, est.variance = TRUE, constraint = "cases", A = NULL, B = NULL, B.fixed = NULL, Q = NULL, est.slopegroups = NULL, E = NULL, pweights = NULL, userfct.variance = NULL, variance.Npars = NULL, item.elim = TRUE, verbose = TRUE, control = list()) ## S3 method for class 'irtc' summary(object, file = NULL, ...) ## S3 method for class 'irtc' print(x, ...) ## S3 method for class 'irtc' logLik(object, ...) ## S3 method for class 'irtc' anova(object, ...)
resp |
A data frame or matrix of polytomous or dichotomous item responses. Missing responses are coded |
Y |
An optional matrix of covariates for the latent regression. |
group |
An optional vector of integer group identifiers for multiple-group estimation. |
irtmodel |
The estimated item response model. One of |
formulaY |
An optional formula for the latent regression in conjunction with |
dataY |
An optional data frame of covariates used together with |
ndim |
The number of latent dimensions (used when no |
pid |
An optional vector of person identifiers. |
xsi.fixed |
An optional matrix with two columns for fixing item parameters: parameter index and fixed value. |
xsi.inits |
An optional matrix of initial item parameter values. |
beta.fixed |
An optional matrix for fixing regression coefficients, or |
beta.inits |
An optional matrix of initial regression coefficients. |
variance.fixed |
An optional matrix for fixing entries of the covariance matrix. |
variance.inits |
An optional initial covariance matrix. |
est.variance |
Logical: should the covariance matrix be estimated? |
constraint |
Identification constraint, either |
A |
An optional design array for the item intercepts. |
B |
An optional design array for the (fixed) item slopes / scoring. |
B.fixed |
An optional matrix for fixing entries of the loading array |
Q |
An optional loading (Q-) matrix mapping items to dimensions. |
est.slopegroups |
An optional vector grouping items with common slope design (advanced use). |
E |
An optional design array used together with |
pweights |
An optional vector of person (case) weights. |
userfct.variance |
An optional user function imposing constraints on the covariance matrix. |
variance.Npars |
The number of estimated parameters in a user-constrained covariance matrix. |
item.elim |
Logical: should items with no observed responses in a category be eliminated? |
verbose |
Logical: print progress during estimation? |
control |
A list of control arguments for the estimation (e.g. |
object, x
|
An object of class |
file |
An optional file name; if supplied, summary output is written there. |
... |
Further arguments passed to methods. |
An object of class irtc, a list with (among others) the components:
xsi |
A data frame of estimated item parameters and standard errors. |
beta |
The estimated latent regression coefficients. |
variance |
The estimated covariance matrix of the latent dimensions. |
person |
A data frame of person parameter estimates (EAP) and related statistics. |
EAP.rel |
The EAP reliability per dimension. |
deviance |
The deviance ( |
ic |
A list of information criteria (AIC, BIC, ...). |
CALL |
The matched call. |
Adams, R. J., Wilson, M., & Wang, W. C. (1997). The multidimensional random coefficients multinomial logit model. Applied Psychological Measurement, 21(1), 1–23. doi:10.1177/0146621697211001
irtc.mml.2pl for models with estimated item slopes.
############################################################################# # EXAMPLE 1: Unidimensional Rasch model ############################################################################# data(data.sim.rasch) mod1 <- irtc.mml(resp = data.sim.rasch) summary(mod1) ############################################################################# # EXAMPLE 2: Partial credit model ############################################################################# data(data.gpcm) mod2 <- irtc.mml(resp = data.gpcm, irtmodel = "PCM") summary(mod2)############################################################################# # EXAMPLE 1: Unidimensional Rasch model ############################################################################# data(data.sim.rasch) mod1 <- irtc.mml(resp = data.sim.rasch) summary(mod1) ############################################################################# # EXAMPLE 2: Partial credit model ############################################################################# data(data.gpcm) mod2 <- irtc.mml(resp = data.gpcm, irtmodel = "PCM") summary(mod2)
Estimates unidimensional and multidimensional item response models with
estimated item slopes (discriminations) by marginal maximum likelihood
(MML). Supported models are the two-parameter logistic model ("2PL"),
the generalized partial credit model ("GPCM"), their design-matrix
and slope-group variants ("GPCM.design", "2PL.groups",
"GPCM.groups").
irtc.mml.2pl(resp, Y = NULL, group = NULL, irtmodel = "2PL", formulaY = NULL, dataY = NULL, ndim = 1, pid = NULL, xsi.fixed = NULL, xsi.inits = NULL, beta.fixed = NULL, beta.inits = NULL, variance.fixed = NULL, variance.inits = NULL, est.variance = FALSE, A = NULL, B = NULL, B.fixed = NULL, Q = NULL, est.slopegroups = NULL, E = NULL, gamma.init = NULL, pweights = NULL, userfct.variance = NULL, variance.Npars = NULL, item.elim = TRUE, verbose = TRUE, method = c("auto", "grid", "streaming"), control = list())irtc.mml.2pl(resp, Y = NULL, group = NULL, irtmodel = "2PL", formulaY = NULL, dataY = NULL, ndim = 1, pid = NULL, xsi.fixed = NULL, xsi.inits = NULL, beta.fixed = NULL, beta.inits = NULL, variance.fixed = NULL, variance.inits = NULL, est.variance = FALSE, A = NULL, B = NULL, B.fixed = NULL, Q = NULL, est.slopegroups = NULL, E = NULL, gamma.init = NULL, pweights = NULL, userfct.variance = NULL, variance.Npars = NULL, item.elim = TRUE, verbose = TRUE, method = c("auto", "grid", "streaming"), control = list())
resp |
A data frame or matrix of item responses ( |
Y |
Optional covariate matrix for the latent regression. |
group |
Optional integer vector of group identifiers for multiple groups. |
irtmodel |
The estimated model: one of |
formulaY |
Optional formula for the latent regression (with |
dataY |
Optional covariate data frame used with |
ndim |
Number of latent dimensions (when no |
pid |
Optional vector of person identifiers. |
xsi.fixed |
Optional matrix (index, value) fixing item intercept parameters. |
xsi.inits |
Optional matrix of initial item parameters. |
beta.fixed |
Optional matrix fixing regression coefficients, or |
beta.inits |
Optional matrix of initial regression coefficients. |
variance.fixed |
Optional matrix fixing covariance entries. |
variance.inits |
Optional initial covariance matrix. |
est.variance |
Logical: estimate the covariance matrix? (default |
A |
Optional design array for item intercepts. |
B |
Optional design array for item slopes. |
B.fixed |
Optional matrix fixing entries of the slope array |
Q |
Optional loading (Q-) matrix mapping items to dimensions. |
est.slopegroups |
Optional vector grouping items that share a common estimated slope. |
E |
Optional design array used with |
gamma.init |
Optional initial values for the estimated slope parameters. |
pweights |
Optional vector of person (case) weights. |
userfct.variance |
Optional user function constraining the covariance matrix. |
variance.Npars |
Number of estimated parameters in a user-constrained covariance. |
item.elim |
Logical: eliminate items with empty response categories? |
verbose |
Logical: print estimation progress? |
method |
Estimation engine. |
control |
List of control arguments (e.g. When |
An object of class irtc (see irtc.mml) with, in addition,
the estimated slope array B and the slope parameter vector. ic is a
one-row data frame from either engine. The person table carries the
caller's pid, the case weights normalised to mean 1, the EAP estimates and
their posterior standard errors (SD.EAP.Dim1, ...). Streaming-engine
fits also carry a routing component (chosen engine and reasoning) and, when
fast=TRUE with verification on, an accuracy_report: a measured,
stratified approximation check (per-metric error quantiles plus a met
verdict), not a strict mathematical error bound.
Both engines treat NA as missing at random: the item contributes a
likelihood factor of 1 for that person and no expected counts, so no case has
to be dropped. Persons with no responses at all are kept and sit at the prior
mean.
The reported deviance, loglike and the information criteria in
ic are marginal log-probabilities of the discretised model, on the same
scale for both engines. They may therefore be compared across the "grid"
and "streaming" paths, across different ndim, and across
different control$nodes settings, up to the quadrature accuracy itself.
Use logLik, AIC, BIC or anova on the
fitted objects.
Two cautions. First, quadrature accuracy is finite: comparisons are only as
good as the node grid, so keep the nodes fixed when comparing models. Second,
when an estimated latent correlation reaches the boundary the model has
degenerated to a lower-dimensional one; a warning with code W427 is
raised, and the criteria and EAP reliabilities of such a fit are computed on a
grid that can no longer resolve the collapsed covariance.
For strictly dichotomous (0/1) data irtmodel = "GPCM" is mathematically
equivalent to irtmodel = "2PL": with two categories the generalised
partial credit model reduces to the two-parameter logistic. Switching to
"GPCM" therefore does not by itself change the model; only
ndim / Q do.
The grid engine materialises an N by nodes^ndim posterior
matrix. When the prediction exceeds the memory available to the session,
estimation stops with an error of class irtc_error_estimation (code
E409) naming the predicted size and pointing at
method = "streaming", rather than failing later inside an allocation.
method = "auto" already routes such models to the streaming engine.
irtc.mml for fixed-slope (Rasch / PCM / RSM) models.
data(data.sim.rasch) mod1 <- irtc.mml.2pl(resp = data.sim.rasch, irtmodel = "2PL") summary(mod1) data(data.gpcm) mod2 <- irtc.mml.2pl(resp = data.gpcm, irtmodel = "GPCM") summary(mod2)data(data.sim.rasch) mod1 <- irtc.mml.2pl(resp = data.sim.rasch, irtmodel = "2PL") summary(mod1) data(data.gpcm) mod2 <- irtc.mml.2pl(resp = data.gpcm, irtmodel = "GPCM") summary(mod2)
Prints a layered, plain-language summary of a fitted item response model:
conclusion first, then analysis overview, item quality, ability
distribution and suggested next steps. Intended for survey staff and
decision makers; statisticians can use summary(mod) for the full
technical output.
plain_summary(mod, lang = irtc_lang())plain_summary(mod, lang = irtc_lang())
mod |
A fitted |
lang |
Output language, |
Invisibly, the list of summary sections (each with title and
body); the same content that irtc_report uses.
set.seed(1) theta <- rnorm(150) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 5), function(b) { as.numeric(runif(150) < plogis(theta - b)) })) mod <- irtc(resp, model = "1PL", verbose = FALSE) plain_summary(mod, lang = "en")set.seed(1) theta <- rnorm(150) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 5), function(b) { as.numeric(runif(150) < plogis(theta - b)) })) mod <- irtc(resp, model = "1PL", verbose = FALSE) plain_summary(mod, lang = "en")
Base-graphics plots for a fitted model: "wright" (item difficulty
vs person ability map), "ability" (person ability histogram, one
panel per dimension), "quality" (item quality rating summary) and
"icc" (item characteristic curves; unidimensional models only).
The same figures are embedded in irtc_report reports.
## S3 method for class 'irtc' plot(x, type = c("wright", "ability", "quality", "icc"), lang = irtc_lang(), items = NULL, resp = NULL, ...)## S3 method for class 'irtc' plot(x, type = c("wright", "ability", "quality", "icc"), lang = irtc_lang(), items = NULL, resp = NULL, ...)
x |
A fitted |
type |
Plot type; see Description. |
lang |
Label language, |
items |
For |
resp |
The response data; only needed when the model object does not store it (streaming engine). |
... |
Ignored. |
Invisibly, x.
set.seed(1) theta <- rnorm(150) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 5), function(b) { as.numeric(runif(150) < plogis(theta - b)) })) mod <- irtc(resp, model = "1PL", verbose = FALSE) plot(mod, type = "wright", lang = "en") plot(mod, type = "icc", lang = "en", items = 1:2)set.seed(1) theta <- rnorm(150) resp <- as.data.frame(sapply(seq(-1, 1, length.out = 5), function(b) { as.numeric(runif(150) < plogis(theta - b)) })) mod <- irtc(resp, model = "1PL", verbose = FALSE) plot(mod, type = "wright", lang = "en") plot(mod, type = "icc", lang = "en", items = 1:2)