Computes, for each item, the percentage of examinees choosing each
response option (or, for already-scored polytomous data, each score
category), together with each option's/category's point-biserial
correlation with the total score. When an answer key is supplied, the
function operates in "selected-response" mode - treating data as raw,
unscored option responses (as consumed by score_resp()) - and
additionally reports omission/double-marking rates and flags distractors
(incorrect options) whose correlation with the total score is positive, a
common indicator of a miskeyed or poorly written item. When no key is
supplied, the function operates in "scored-category" mode - treating
data as an already-scored response matrix (0, 1, 2, ..., cats - 1) - and
simply reports each score category's response proportion and
category-total correlation, with no notion of a "correct" option to flag
distractors against.
Usage
ctt_distr(
data,
item.id = NULL,
key = NULL,
opt = NULL,
cats = NULL,
total = NULL,
correct = FALSE,
missing = NA,
crit.distractor = 0
)Arguments
- data
In selected-response mode (
keysupplied), a data frame or matrix of raw item responses in the same format as thedataargument ofscore_resp()(a selected option number, a missing-response indicator, or a comma-separated string such as"1,5"for a double-marked response). In scored-category mode (key = NULL), a data frame or matrix of already-scored item responses with scores 0 tocats[j] - 1for each item j. Either way, examinees are rows and items are columns.- item.id
A character vector of item identifiers, in the same order as the columns of
data. IfNULL(default), item IDs are generated automatically aspaste0("V", 1:ncol(data)), following the convention used elsewhere in irtQ (e.g.,shape_df(),est_irt()). Note that this generated ID does not fall back tocolnames(data); passitem.idexplicitly to label items using the column names ofdataor any other identifier scheme.- key
NULL(default) for scored-category mode, or - to use selected-response mode - either a vector of correct options in item order, or a data frame with columnsitemandkey, exactly as accepted byscore_resp(). As inscore_resp(), each item's option-coding scheme (numeric, Latin-letter, or general label such as a Korean syllable label) is inferred independently from its own key value; see Details.- opt
A vector giving the full set of possible response option values in selected-response mode (e.g.,
1:5for a five-option item, orc("A", "B", "C", "D", "E")for a letter-coded item), applied to every item. Only used whenkeyis supplied; it is silently ignored in scored-category mode (key = NULL), which instead derives its category set fromcats. IfNULL(default), the option set is inferred as the sorted set of all single (non-blank, non-double-marked) response values observed anywhere indata, using each item's own option-coding scheme to decide what counts as a single response (see Details). Becauseoptis shared across every item,ctt_distr()assumes a test form uses one consistent option-coding scheme throughout; see Details for what happens if items actually differ.- cats
A numeric vector giving the number of score categories per item, following the same convention as
ctt(). Only used in scored-category mode (key = NULL); silently ignored whenkeyis supplied. IfNULL(default), inferred per item as the observed maximum score plus one.- total
An optional numeric vector of total scores, one per examinee, aligned with the rows of
data. IfNULL(default), the total score is computed internally: viascore_resp()in selected-response mode, or asrowSums(data)in scored-category mode (see Details for howtotalinteracts withmissing/listwise deletion in scored-category mode).- correct
Logical. Both the raw (uncorrected) item-total correlation - where an item (or, in
ctt_distr(), a response option/category) is correlated with the total score that includes its own contribution - and the corrected item-total correlation - excluding its own contribution - are always computed and reported as separate columns (see Details/Value). This argument only controls which of the two is used for flagging: ifTRUE, flagging (inctt_item()'sflagcolumn,ctt_alpha()'s summary discrimination figure, and, inctt_distr(), thecrit.distractorcheck) is based on the corrected value; ifFALSE(default), on the raw value.- missing
A value indicating missing responses, analogous to the
missingargument inest_irt()andscore_resp(). Its effect differs by mode (see Details): in selected-response mode, a cell equal tomissingis recoded toNAand then treated as a blank (omitted) response, contributing to that item's omission rate, without removing the examinee from the analysis. In scored-category mode, a cell equal tomissingis recoded toNAand the examinee's row is then excluded listwise, as inctt(). Default isNA.- crit.distractor
A single numeric value; in selected-response mode, a distractor (non-key option) is flagged when its point-biserial correlation with the total score exceeds this value. Default is
0.
Value
A list with these elements:
- distr
A data frame in long format with one row per item x option (selected-response mode) or item x category (scored-category mode), containing
item,option(the option number, option label, or score category; numeric for a numeric-coded or scored-category test form, character for a letter- or general-label-coded test form),is_key(logical; whether this option is the item's correct answer, orNAin scored-category mode),freq(the raw number of examinees choosing this option/category),pct(the same count expressed as a percentage of examinees),pb_raw(raw, uncorrected point-biserial correlation with the total score),pb_corrected(corrected, item-excluded point-biserial correlation with the total score), andflag(a description ofcrit.distractorviolations, based on whichever ofpb_raw/pb_correctedis selected bycorrect, in selected-response mode, or""otherwise).- omit
In selected-response mode, a data frame with one row per item giving the blank/omission and double-marked response percentages (
pct_blank,pct_double).NULLin scored-category mode.
Details
In selected-response mode, each item's option-coding scheme is inferred
from its own key value, using exactly the same three-way rule as
score_resp(): a key value that parses as a number is compared
numerically; a key value consisting of one or more Latin letters is
compared as letters, case-insensitively, and a response must itself
consist only of Latin letters to count as a single valid option; any
other key value (e.g., a Korean syllable label, a Roman numeral written
in a non-Latin numeral script, or a circled-number symbol) is treated as
a general option label, under which any non-blank, non-double-marked
response is accepted as a single valid option and compared to the key
(and to opt) with a case-insensitive exact string match. This ensures
ctt_distr() interprets a given data/key pair identically to
score_resp(). Because opt (the option universe used for every
item's frequency table) is a single vector shared across all items,
ctt_distr() is intended for a test form that uses one consistent
option-coding scheme throughout (e.g., every item numbered 1-5, or every
item lettered A-E); mixing schemes across items within a single call is
not well-supported, since the shared opt cannot represent two different
option universes at once.
The two modes handle missing responses differently because they represent
different kinds of data. In selected-response mode, an omitted response is
itself a meaningful, common outcome for a selected-response item (the
examinee saw the item and chose not to answer it), so omissions are
tabulated as their own category rather than causing the examinee to be
dropped, exactly as in score_resp(). In scored-category mode,
data is assumed to already be a scored response matrix (as used by
ctt()), where a missing cell instead means the
item was not administered to, or not observed for, that examinee, so the
examinee's row is excluded listwise for that analysis. When total is
supplied together with scored-category mode's listwise deletion, total
is assumed to align with the original rows of data; it is subset using
the same completeness mask before use, so its length need only match the
original nrow(data).
Point-biserial correlations are computed as the Pearson correlation
between a 0/1 indicator (whether the examinee chose that particular
option, or scored in that particular category) and the total score. As
with the item-level discrimination statistics computed by ctt(), both
the raw (uncorrected, item/option-included) and
corrected (item-excluded) versions are always computed and returned as
separate columns (pb_raw/pb_corrected); correct only selects which
of the two feeds the crit.distractor flagging criterion in
selected-response mode. A point-biserial correlation is undefined, and
reported as NA, whenever an option/category was chosen by zero or all
examinees (a constant indicator).
Note
Selected-response mode (key supplied) is restricted to
dichotomous, single-key items only, exactly like
score_resp(), which it calls internally to obtain the 0/1 scored
responses and total score: a selected-response item has exactly one
correct option and therefore cannot yield a partial-credit score by
construction, so a mixed dichotomous/polytomous test form cannot be
analyzed in this mode. Scored-category mode (key = NULL) has no such
restriction: because cats is always a per-item vector, a single call
fully supports a genuinely mixed dichotomous/polytomous test form (see
Examples). If raw selected-response data and already-scored
polytomous data need to be analyzed together, score the
selected-response items first (via score_resp() or this
function's own selected-response mode), then combine the result with
the polytomous scores and call ctt_distr() again in scored-category
mode on the combined matrix.
Author
Hwanggyu Lim hglim83@gmail.com
Examples
# Selected-response mode: raw option responses + an answer key
raw <- data.frame(
V1 = c("1", "2", "1", "1", "3", "2", "1", "4"),
V2 = c("4", "4", "2", "2,4", "4", "1", "4", "4")
)
key <- c(1, 4)
out <- ctt_distr(data = raw, key = key)
out$distr
#> item option is_key freq pct pb_raw pb_corrected flag
#> 1 V1 1 TRUE 4 50.0 0.626 -0.258
#> 2 V1 2 FALSE 2 25.0 -0.602 -0.149
#> 3 V1 3 FALSE 1 12.5 -0.079 0.293
#> 4 V1 4 FALSE 1 12.5 -0.079 0.293
#> 5 V2 1 FALSE 1 12.5 -0.709 -0.378
#> 6 V2 2 FALSE 1 12.5 -0.079 0.378
#> 7 V2 3 FALSE 0 0.0 NA NA
#> 8 V2 4 TRUE 5 62.5 0.592 -0.258
out$omit
#> item pct_blank pct_double
#> 1 V1 0 0.0
#> 2 V2 0 12.5
# Scored-category mode: an already-scored polytomous matrix, no key
set.seed(1)
scored <- data.frame(matrix(sample(0:2, 8 * 3, replace = TRUE), nrow = 8))
out2 <- ctt_distr(data = scored)
out2$distr
#> item option is_key freq pct pb_raw pb_corrected flag
#> 1 V1 0 NA 3 37.5 0.026 0.613
#> 2 V1 1 NA 2 25.0 -0.533 -0.493
#> 3 V1 2 NA 3 37.5 0.450 -0.172
#> 4 V2 0 NA 3 37.5 -0.609 -0.098
#> 5 V2 1 NA 3 37.5 -0.185 -0.488
#> 6 V2 2 NA 2 25.0 0.889 0.655
#> 7 V3 0 NA 4 50.0 -0.513 0.107
#> 8 V3 1 NA 2 25.0 0.415 0.309
#> 9 V3 2 NA 2 25.0 0.178 -0.433
# Scored-category mode also handles a genuinely mixed dichotomous/
# polytomous test form in a single call, since `cats` is always a
# per-item vector (see Note): item 3 below contributes 3 category rows
# while items 1-2 contribute 2 each
mixed <- data.frame(
I1 = c(1, 0, 1, 1, 0, 1, 0, 1), # dichotomous item (cats = 2)
I2 = c(0, 1, 0, 1, 1, 0, 0, 1), # dichotomous item (cats = 2)
I3 = c(2, 0, 1, 2, 1, 0, 2, 1) # polytomous item (cats = 3)
)
out2b <- ctt_distr(data = mixed, cats = c(2, 2, 3))
out2b$distr
#> item option is_key freq pct pb_raw pb_corrected flag
#> 1 V1 0 NA 3 37.5 -0.467 0.038
#> 2 V1 1 NA 5 62.5 0.467 -0.038
#> 3 V2 0 NA 4 50.0 -0.258 0.258
#> 4 V2 1 NA 4 50.0 0.258 -0.258
#> 5 V3 0 NA 2 25.0 -0.745 -0.120
#> 6 V3 1 NA 3 37.5 0.067 0.269
#> 7 V3 2 NA 3 37.5 0.600 -0.162
# Selected-response mode with letter-coded (A-E) options: each item's
# coding scheme is inferred from its own key value, exactly as in
# irtQ::score_resp()
raw3 <- data.frame(
V1 = c("A", "B", "A", "A", "C", "B", "A", "D"),
V2 = c("D", "D", "B", "B,D", "D", "A", "D", "D")
)
key3 <- c("A", "D")
out3 <- ctt_distr(data = raw3, key = key3)
out3$distr
#> item option is_key freq pct pb_raw pb_corrected flag
#> 1 V1 A TRUE 4 50.0 0.626 -0.258
#> 2 V1 B FALSE 2 25.0 -0.602 -0.149
#> 3 V1 C FALSE 1 12.5 -0.079 0.293
#> 4 V1 D FALSE 1 12.5 -0.079 0.293
#> 5 V2 A FALSE 1 12.5 -0.709 -0.378
#> 6 V2 B FALSE 1 12.5 -0.079 0.378
#> 7 V2 C FALSE 0 0.0 NA NA
#> 8 V2 D TRUE 5 62.5 0.592 -0.258
