ctt() returned a standardized alpha (alpha_std) that was too
large, and could exceed 1, when a test included a constant item. The
constant item is now kept in the item count as in the raw alpha, and
the value is 0 when only one item varies, as is the raw alpha. An
undefined standardized alpha or mean discrimination is NA instead of
NaN.
ctt(), ctt_distr(), and freq_score() silently left out of their
counts a score within rounding error of a whole number, such as
(0.1 + 0.2) * 10. Such a score is now counted as that whole number.
freq_score() tabulated a factor by its level codes (1, 2, 3, ...)
instead of its labels. A factor is now converted through its labels.
ctt_distr() listed an option twice in selected-response mode when
the responses wrote it in different ways, such as "1" and "01",
which inflated the frequencies and percentages. The option is now
listed once.
score_resp() and ctt_distr() did not treat a response with
surrounding spaces, such as " 9", as missing when missing = "9".
Responses are now compared with missing after trimming spaces.
score_resp() and ctt_distr() silently scored every response to an
item as wrong when its key value contained a comma, such as "1,5".
They now stop with an error, because every item must have exactly one
correct option.
ctt() did not flag an item whose discrimination is undefined, such
as an item with a constant score, so the item could pass the
crit.dis check. Such an item is now flagged with
"discrimination undefined".
shape_df_fipc() misplaced the parameter columns of the new items
when the fixed items included a polytomous item and every new item was
dichotomous, which changed a threshold of a fixed polytomous item. The
new items now follow the columns of the fixed items. FIPC results
built from such metadata should be rerun.
The GPCM probabilities of prm(), traceline(), and info() were
distorted when an exponent exceeded 700, and the values at the other
thetas and items of the same call changed with them. Each row is now
rescaled by its own maximum. Results in the usual range change by less
than 1e-12, and prm() no longer returns a GPCM probability of
exactly 0.
info() returned NaN instead of 0 for a dichotomous item at a theta
far from the item location.
traceline() returned a vector instead of a one-row matrix in
prob.cats for a polytomous item when a single theta was given, which
made plot() fail.
drm() checked each probability against the guessing parameter of
another item, and the expected item score of traceline() included
categories that the item does not have. Probabilities and expected
scores could differ by about 1e-10, and item parameter estimates by
about 1e-9.
traceline() and info() stopped with an error for a two-category
GRM or GPCM item, such as one made by bring.mirt(), and for a 3PLM
or DRM item with an NA guessing parameter, for which drm()
returned NA. An NA guessing parameter is now treated as 0, so a
two-category GRM or GPCM item is computed as a 2PLM item, also when
the test has polytomous items of the same model.
shape_df() used a single g value for the first dichotomous item
only and set the guessing parameters of the other items to 0. A single
g value is now used for every dichotomous item, as in simdat(),
and a g vector of any other wrong length stops with an error.
est_score() with ncore > 1 failed when a chunk of examinees had a
single row, for example with 3 examinees and ncore = 2, and it did
not close the cluster when a worker failed. Each chunk is now kept as
a matrix, also when it has one row, and the cluster is always closed.
est_score() with method = "ML", "WL", "MLF", or "MAP" could
stop at a point that was not a solution when the iterations alternated
between two values, which happened mostly for low-scoring examinees on
tests with 3PLM items. The estimate then depended on max.iter. When
the iterations do not converge, the root of the score function is now
found within the range of the visited values.
est_score() with method = "INV.TCC" returned wrong standard errors
when intpol = FALSE or when the interpolation was skipped with a
warning (a range.tcc that is too narrow), because the estimates were
paired with the wrong sum scores of the score distribution. The
standard deviation is now taken over the sum scores that have an
estimate. Results with intpol = TRUE and no warning are unchanged.
est_score() with method = "MLF" gave an examinee with all
responses missing an estimate near 0 and a standard error of 316.2
without a warning. The examinee now gets NA and the warning, as with
the other methods.
est_score() with method = "EAP" returned NaN, with a warning
about missing responses, for tests of about 1,000 items or more,
because the product of the item probabilities underflowed. The
posterior is now computed from log-likelihoods, and results for usual
tests change by less than 1e-14.
llike_score() with method = "MLF" stopped with an error under the
default fence.b = NULL. The fences are now placed at -5 and 5, the
default range of est_score(). The function also merged examinees
whose rows had the same row name, and an examinee without any
response, who stopped the function ("ML", "MAP") or got values
from the fence items alone ("MLF"), now gets NA.
lwrc() stopped with an error for a single theta when the test had
polytomous items.
sx2_fit() counted some summed score groups twice in the tables of
polytomous items when the test was so short that the groups merged at
the two ends overlapped (for example, two items with five categories).
The observed and expected frequencies and the degrees of freedom were
too large. Each summed score group is now counted once.
sx2_fit() and irtfit() reported a p-value of 0 (or NaN with a
warning) for an item without degrees of freedom left after collapsing,
which read as a strong misfit. The critical value and p-value of such
an item are now NA, with a warning that names the item.
sx2_fit() named the columns of the observed frequency and proportion
tables (obs_freq and obs_prop) with an exp_freq. prefix
(exp_freq.score.0, ...) when the test had a single polytomous item.
The columns are now named score.0, score.1, ... as for tests with
several polytomous items. The values are unchanged.
plot.irtfit() ignored xlab.text when type = "both" and always
used theta as the x-axis title. The given title is now used, as for
the other types.
plot.irtfit() drew the standardized residuals as blue crosses,
instead of red circles, when every plotted residual exceeded overSR.
Each kind of point now keeps its color and shape.
irtfit() stopped with an error for a logical response matrix, and
irtfit() and sx2_fit() stopped with an error when the groups of an
item were merged into a single row (for example, with a large
min.collapse). These cases now work; an item left without degrees of
freedom gets NA for the critical value and p-value of the chi-square
statistic, with a warning.
sx2_fit() for an est_irt object computed the expected frequencies
with a standard normal distribution even when a different latent
distribution was used or estimated in the calibration (for example,
with EmpHist = TRUE or FIPC). When weights, norm.prior, and
nquad are all omitted, the latent distribution stored in the object
is now used. The statistics change slightly for a calibration with a
standard normal distribution and can change enough to alter the
decision at alpha when the distribution was estimated. Set
norm.prior = c(0, 1) to reproduce the earlier results.
run_mst() stopped with an obscure error for a panel whose routing
module is not module 1 when ini_mod was NULL, because the starting
module was drawn with sample() from a single module number. The
examinees now start in the routing module.
reval_mst() took the score distribution of the first stage from
module 1 even when the routing module had another number, which gave
wrong results without an error. The routing module is now taken from
route_map, and a first stage with more than one module stops with an
error. run_mst() still runs such panels.
cac_lee() and cac_rud() used weights that did not sum to 1 as
given, so the marginal indices could exceed 1. Such weights are now
rescaled to sum to 1, with a warning. Weights that are missing,
infinite, or negative, or that have a non-positive sum, now stop with
an error.
plot.find_cut() with theta_range still stretched the x-axis to the
crossing lines outside the range. The x-axis now covers the requested
range.
ripd() stopped with an obscure error when an examinee had no ability
estimate: a missing value in score, an examinee without any
response, a focal group examinee left out by min.resp (so min.resp
could not be used), or an examinee whose responses were all removed
during purification. Such examinees are now excluded, and the function
stops when no examinee has an ability estimate. A warning reports the
examinees excluded because of score or min.resp, and one warning
at the end of the purification reports the examinees excluded during
it. min.resp now also applies to the first analysis when score is
given, and an examinee with a missing value in score stays out of
the purification. The purification stops with a warning and
complete = FALSE when min.resp, which counts the items that
remain, leaves a group without any ability estimate; score and the
number of examinees reported as excluded then belong to the last
iteration whose statistics are reported.
ripd() and pcd2() stopped with an obscure error when purification
left a single item. They now work. When every item is flagged, the
purification stops with a warning and returns all items as flagged
with complete = FALSE.
ripd() and pcd2() silently accepted responses outside the score
categories (for example, an unrecorded missing code such as -9, which
gave chi-square values in the hundreds of thousands), a group of the
wrong length (ripd()), and an item.skip that was not a set of item
positions (an ID given to pcd2() added a spurious row and skipped
nothing). They now stop with a clear message, a logical item.skip is
read as item positions, also during purification, and character
responses are read as numbers.
pcd2() warned about excluded items even when min.resp excluded
none, and worded the warning poorly. It now warns only when items are
excluded.
ripd() compared the p-values rounded to four decimals with alpha,
so an item with a p-value just above alpha (for example, 0.05004 for
alpha = 0.05) was flagged. The decision now uses the unrounded
p-values, and the tables still show four decimals.
ripd() computed RIPD_RS from arbitrary values when the covariance
matrix of RIPD_R and RIPD_S was singular, which happens when all
examinees who responded to an item have the same probability of a
correct response (a tiny constant was added to every entry of the
matrix). The statistic is now computed with a generalized inverse and
the rank of the matrix as degrees of freedom, and a warning names the
items. Because the degrees of freedom can then differ between items,
the purification removes the flagged item with the smallest p-value of
the purify.by statistic, compared on the log scale, instead of the
item with the largest statistic. Results for regular data are
unchanged.
rdif() and crdif() stopped with an obscure error when an examinee
had no ability estimate: a missing value in score, an examinee
without any response, an examinee left out by min.resp (so
min.resp could not exclude any examinee), or an examinee whose
responses were all removed during purification. Such examinees are now
excluded, and the functions stop when no examinee has an ability
estimate. A warning reports the examinees excluded because of score
or min.resp, and one warning at the end of the purification reports
the examinees excluded during the purification.
rdif() and crdif() stopped with an obscure error when purification
left a single item, and rdif() also stopped with "non-conformable
arguments" when purification removed the item with the most score
categories from a test with polytomous items. They now work. When
every item is flagged, the purification stops with a warning and
returns all items as flagged with complete = FALSE. The purification
also stops with a warning and complete = FALSE when min.resp,
which counts the items that remain, leaves a group without any ability
estimate; score and the number of examinees reported as excluded
then belong to the last iteration whose statistics are reported.
rdif() and crdif() silently accepted responses outside the score
categories (for example, an unrecorded missing code such as -9, which
flagged every item), a group of the wrong length, a focal.name
that is not in group, an item.skip that was not a set of item
positions (a logical item.skip skipped only the first item during
purification), an alpha outside (0, 1), a max.iter that was not a
positive whole number when purify = TRUE, and a group with more
than two values (the values other than focal.name were pooled into
the reference group). They now stop with a clear message; use
grdif() to compare more than two groups. A logical item.skip is
read as item positions, also during purification, and character
responses are read as numbers.
rdif() compared the p-values rounded to four decimals with alpha,
so an item with a p-value just above alpha (for example, 0.05004 for
alpha = 0.05) was flagged, and an item with a p-value below 0.00005
was flagged even for an alpha of 1e-30. The decision now uses the
unrounded p-values, and the tables still show four decimals.
rdif() computed RDIF_RS from arbitrary values, including negative
chi-square values, when the covariance matrix of RDIF_R and RDIF_S
was singular, which happens when all examinees who responded to an
item have the same probability of a correct response (a tiny constant
was added to every entry of the matrix). The statistic is now computed
with a generalized inverse and the rank of the matrix as degrees of
freedom, and a warning names the items. Results for regular data are
unchanged.
crdif() compared RDIF_R-CR and RDIF_RS-CR of polytomous items
with chi-square distributions with K and 2K degrees of freedom,
although the category residuals of each examinee sum to zero, so the
covariance matrices have ranks K - 1 and 2K - 1. The tests were
therefore conservative (about 2% to 3% of the DIF-free items were
flagged by RDIF_R-CR and RDIF_RS-CR at alpha = 0.05). The
degrees of freedom are now K - 1, K, and 2K - 1, and the statistics
are computed without adding a small constant to a singular covariance
matrix, which changed them in the fourth decimal. Results for
dichotomous items are unchanged.
The purification of crdif() removed the item with the smallest
p-value, which was the item listed first when several p-values were
too small to be represented (they were 0). The purification of
rdif() and crdif() now removes the flagged item with the smallest
p-value of the purify.by statistic, compared on the log scale, so
the choice also holds for an item of rdif() whose RDIF_RS has
fewer degrees of freedom because of a singular covariance matrix.
Results for regular data are unchanged.
rdif() and crdif() ignored min.resp in the initial analysis when
score was supplied. min.resp is now applied from the initial
analysis, with or without score: the scores of the examinees with
fewer than min.resp responses are set to missing and these examinees
are excluded.
grdif() stopped with the error "object 'chisq' not found" when a
group had no response to an item. The three GRDIF statistics and their
p-values of such an item are now NaN, and the item is not flagged.
grdif() stopped with an obscure error when an examinee had a missing
ability estimate, and catsib() left such an examinee out without a
message. Both functions now exclude the examinee with a warning, and
stop with a message when no examinee has an ability estimate.
grdif() put the examinees whose group is missing into the reference
group, while its post-hoc analyses left them out. They are now left
out of every analysis, as in the other DIF functions.
The purification of grdif() and catsib() stopped with an obscure
error when one item was left, so it could not end when every item was
flagged. It now stops with a warning and complete = FALSE when every
item is flagged or when a group has no ability estimate left
(catsib(): fewer than two within range, which happens when a
single item is left and method = "ML"), and the remaining items keep
the results of the last counted iteration, as in rdif() and
crdif(). The flagged item with the smallest p-value is removed at
each iteration, as in rdif() and crdif(); for regular data this is
the item removed before. One warning reports the examinees who lost
their ability estimates during the purification, the warning for an
incomplete purification reads as that of rdif(), and n.iter of the
with_purify results is the integer 0 when no item is flagged in the
initial analysis.
grdif() and catsib() silently computed wrong statistics for some
invalid inputs, such as character responses, responses outside 0 and
1, or a group, score, or se whose length does not match data,
and stopped with an obscure message for others, such as a focal.name
that is not found in group or method = "EAP.SUM" or "INV.TCC".
They now stop with a clear message, also for an alpha outside (0,
1), a max.iter that is not a positive whole number when
purify = TRUE, or an invalid min.resp. The est_item method of
grdif() stops when score is passed, which it ignored before, and
the est_irt and est_item methods stop when data or D is
passed. catsib() also stops when min.binsize is not an integer of
2 or more (with 1, the p-values of all items were missing), when
n.bin is not the maximum and the minimum number of bins in this
order, and when max.del is not from 0 to less than 1.
A logical item.skip of catsib() skipped the wrong items during the
purification, so a skipped item could be tested, flagged, and removed.
It is now read as item positions, also during the purification.
grdif() and catsib() compared the p-values rounded to four
decimals with alpha, so an item whose p-value was within rounding of
alpha could be flagged wrongly, and the purification could remove
it. The unrounded p-values are now compared; the tables still show
four decimals.
grdif() computed GRDIF_R, GRDIF_S, and GRDIF_RS after adding
small constants to the covariance matrix of an item when the matrix
was singular or nearly singular, for example when all examinees of a
group had the same ability estimate, and could stop with an error. The
statistics now use a generalized inverse, the degrees of freedom are
the rank of the matrix instead of G - 1, G - 1, and 2(G - 1), and one
warning names the items. Results for regular data are unchanged.
catsib() returned a statistic of 0, a standard error of 0, and a
p-value of NaN for an item when the corrected ability estimates of
the two groups did not overlap, and ended the purification as
complete. The statistics of such an item are now NA, one warning
names the items, and the item is not flagged. The purification stops
with a warning and complete = FALSE when this happens to every
remaining item.
The estimation of GPCM items with large slopes in est_irt(),
est_mg(), and est_item() rescaled all cumulative logits by a
common factor once one exceeded 700 in absolute value, which distorted
the objective function, and the gradient and the Hessian could become
NaN even for logits well below 700. Each row is now shifted by its
own maximum when a logit exceeds 100 in absolute value; results below
that range are unchanged.
est_irt() and est_mg() computed the standard errors of GRM and
GPCM items with D = 1 whatever D was. With D other than 1, such
as 1.702, the standard errors of polytomous items were about 1.4 to 2
times too large, those of the dichotomous items of the same test were
also affected, and so were covariance and test.2. The estimates
were not affected.
With fix.g = TRUE and a g.val other than 0.2, est_irt() and
est_mg() computed the standard errors of 3PLM items as if the
guessing parameter were 0.2. The error grows with the distance between
g.val and 0.2, for example about 9 percent with g.val = 0.25 and
about 25 percent with 0.05 or 0.35 for the LSAT6 data. The estimates
were not affected.
When D was not 1, the Hessian of 1PLM items with a common estimated
slope (fix.a.1pl = FALSE) had a wrong cross term between the slope
and the difficulties. In est_item() the standard error of the slope
came out too small and the optimization could stop short of the
maximum; in est_irt() and est_mg() the M-step followed a different
path, which changed the number of EM cycles and the estimates within
the convergence tolerance. The Hessian is now correct, and results for
D = 1 are unchanged.
covirt() underestimated the standard errors of polytomous items by
about 10 to 25 percent, because it computed their information with
Equation A8 of Li and Lissitz (2004), which treats the category
indicators of a response as independent Bernoulli variables. It now
uses the information of the multinomial response, whose standard
errors agreed with the empirical standard deviations of the estimates
in a simulation; see Details of covirt(). The results for
dichotomous items do not change.
covirt() stopped with an error for a GRM or GPCM item with two score
categories. Such an item is now treated as a 2PLM item, and a GPCM
item listed in pcm.loc as a 1PLM item with a fixed slope.
covirt() printed a message instead of issuing a warning when the
information matrix of an item was singular, and the matrix and the
vector of standard errors filled with NA had no names. It now issues
a warning, and the NA values carry the parameter names.
In a test with many items, such as several hundred, the likelihood of
an examinee could underflow to 0 at every quadrature point.
est_irt() and est_mg() then stopped with "NA/NaN gradient
evaluation", and pcd2() returned NaN for every item. The
likelihood of such an examinee is now rescaled by its largest
log-likelihood, and the marginal log-likelihood adds the scale back.
Results for tests where no likelihood underflows are unchanged.
pcd2() also no longer returns NaN when a quadrature point has no
posterior mass.
est_irt(), est_mg(), and est_item() could return the thresholds
of a GRM item in a decreasing order, which is not a valid solution
because the category probabilities then sum to more than 1 and the
reported log-likelihood is inflated. It happened mostly in small
samples or when a score category has few responses (for example, in 13
of 40 simulated data sets with five categories and 300 examinees).
When the unconstrained estimates are not in increasing order, the item
is now estimated again with its thresholds kept in increasing order.
Results of items whose thresholds were already ordered are unchanged.
Fits with a GRM item whose thresholds were reversed should be rerun.
When the second column of weights did not sum to 1, est_irt() and
est_mg() reported a log-likelihood, AIC, and BIC that were off by
N times the log of the sum, and a variance in group.par that was
multiplied by the sum. With EmpHist = TRUE the empirical histogram
was rescaled to that wrong variance, which changed the scale of the
item parameters (by up to 1.8 in an example with a sum of 4). The
weights are now rescaled to sum to 1 with the warning "The weights do
not sum to 1 and were rescaled to sum to 1.", as in cac_lee() and
cac_rud(); weights that are negative, not finite, or sum to zero
stop with an error. Weights that sum to 1 give the same results as
before.
With EmpHist = TRUE and user weights whose number of quadrature
points is not 49 (the default of Quadrature[1]), est_irt() and
est_mg() stopped with an error such as "non-conformable arguments"
when there were fewer points, and extrapolated the upper tail of the
empirical histogram from the wrong point when there were more. The
number of points is now taken from weights. The results for 49
points are unchanged.
est_irt() with fipc = TRUE counted a fixed item twice in the first
E-step without notice when fix.loc listed it more than once, which
changed the estimates of the items to calibrate (by up to 0.45 in an
example with fipc.method = "OEM"). It now stops with an error.
With fipc = TRUE, all items fixed, and user weights, the change in
the first EM cycle was measured from the mean and variance of the
default distribution instead of those of weights, which could shift
the cycle at which the iteration stopped. It is now measured from the
moments of weights.
npar.est, aic, and bic of est_irt() and est_mg() did not
count the weights of the empirical histogram (EmpHist = TRUE) as
free parameters, and est_irt() with FIPC counted only the mean and
variance of the latent distribution (2 values) even with
EmpHist = TRUE. With 49 quadrature points, the AIC of a model with
an empirical histogram was 92 too small (2 times the 46 weights),
which made it look better than a model with a normal distribution. The
count now includes the free weights: Q - 3 for a histogram rescaled to
a fixed mean and variance (est_irt() without FIPC, and the reference
groups of est_mg()) and Q - 1 for a histogram that is not rescaled
(est_irt() with FIPC, and the free groups of est_mg()), where Q is
the number of quadrature points. The values for a normal distribution
do not change.
est_mg() with x = NULL stopped with an error when cats = NULL,
although the help page allows it when all models are dichotomous, and
when model included "DRM". Both now work, as in est_irt().
est_mg() counted a group that was listed twice in free.group twice
in npar.est, which made aic and bic too large. It silently
treated a group name in free.group that is not in group.name as a
reference group, and it silently dropped a position in fix.loc that
is outside the rows of x, so that a different set of items was
fixed. A repeated group is now counted once, and the other two cases
stop with an error.
est_mg() ran with a response such as 0.5, which the likelihood and
the frequency tables treated differently. Responses must now be whole
numbers from 0 to the number of score categories minus 1, as in
est_irt().
With missing supplied, the group element of data returned by
est_mg() still held the missing-value code, while overall had
NA. Both now have NA.
est_item() stopped with an unclear error when score was a data
frame, contained a missing value, or had a length different from the
number of rows of data, and when only one item had responses. A data
frame score now works, and the other cases stop with a clear message
or work. The message about items that did not converge numbered the
items after the items without responses had been removed; it now uses
the positions in the supplied data.
est_mg() with fipc = TRUE always stopped with an error when
item.id was supplied: with "do not share common items" when the IDs
were those of x, and with "At least one model name is mis-specified"
when they were different (present in 1.3.1). A supplied item.id now
replaces the IDs in x, as the help page says, and fix.id refers to
the new IDs.
FIPC with fixed items whose responses are all the same (for example,
all correct) produced an error ("NA/NaN gradient evaluation") with
fipc.method = "MEM", degenerate estimates (a slope of 0.01 and a
difficulty above 10) with "OEM", and NaN group parameters after
500 cycles when all items were fixed. Such fixed items carry no
information to place the ability scale. est_irt() and est_mg() now
stop with a message when the responses to all fixed items show no
variation (est_mg() uses the responses pooled over the groups). A
failed short search for starting values in the first two EM cycles is
now skipped, and when the last search of an item in an M-step fails,
the item keeps its previous values and is reported as not converged;
both cases stopped the run before.
For est_mg() with FIPC and all items fixed, test.2 read "Solution
is a possible local maximum." although no information matrix is
computed. It now reads as in est_irt(): "No item parameters are
estimated, so the information matrix is not computed."
est_item() returned the log-likelihood plus the log prior densities
as loglikelihood when prior distributions were used, for example for
3PLM items with the default use.gprior = TRUE, although the help
page and the printed "-2loglikelihood" call it the log-likelihood. It
now returns the log-likelihood at the final estimates without the log
prior densities, as est_irt() and est_mg() do. The estimates,
standard errors, and covariances are unchanged, and so is
loglikelihood when no prior is used.
bring.mirt() returned NA parameters, or silently wrong ones, for
some mixed-format fits (present in 1.3.1). The columns of the
coefficient matrix of mirt follow the model of the first item, so a
fit that began with a polytomous item gave NA for its dichotomous
items, and a fit that used the graded or gpcm model for every item,
including two-category items, gave wrong values (the category
probabilities differed by up to 0.5). The parameters of each item are
now read by the column names. The intercept c of the gpcmIRT model,
which was dropped, is moved into the thresholds (b - c / a); it
changes the thresholds only when c is not 0. A dichotomous item of
an unsupported model in a mixed-format fit, such as a 4PL item, now
stops with an error instead of losing its upper asymptote. A model
fitted to multiple groups, which is not supported, now stops with a
message that asks for one extracted group (mirt::extract.group()).
write.flexmirt() wrapped the lines of the file at the width of the
console (80 characters in Rscript), which broke the file:
bring.flexmirt() could not read it, and flexMIRT would not either.
This happened for the lines of items with several categories (GRM and
GPCM), so the files written by the examples of the help page were
affected. Each item and each group is now written as one line, and the
file does not depend on the console width.
write.flexmirt() wrote a wrong first step parameter for GPCM items
with a number of categories other than five (the difference was up to
0.69 for the four-category items of simCAT_MX), because the mean of
the step parameters was computed with a constant 4 instead of the
number of steps. With rePar = TRUE, it also left out the last
scoring function value of GPCM items. Files written for GPCM items
with five categories are unchanged. A GPCM item with two categories
and rePar = FALSE stopped with a subscript error; it is now written
as a two-parameter item. The file is also closed when the function
stops with an error.
est_irt() stopped with an out-of-range error for response columns
stored as character, such as "0" and "1", and est_mg() stopped
with an error when only some of its groups had character columns
(present in 1.3.1). Character and factor columns are now read by their
values in est_irt() and est_mg(), as in est_item(). A
non-numeric missing code, such as missing = ".", is recoded before
the columns are read in est_irt(), as in est_mg().
catsib() has a new argument rho.screen, TRUE by default. When
the ability estimates of a few examinees have very large standard
errors, the estimated reliability (rho^2) of the regression correction
can be far below the reliability of the other estimates, or negative,
which shrinks the corrected abilities too much and inflates the Type I
error rate. With rho.screen = TRUE, the examinees whose squared
standard error is equal to or greater than the variance of the ability
estimates of their group are left out of the computation of the group
mean, the variance, and the error variance used in the regression
correction; they are still matched and tested. This changes the
default results of catsib(). The warning about rho^2 falling below
0.05 is given less often, and once per call instead of at every
purification iteration. rho.screen = FALSE computes rho^2 as earlier
versions did.
The post-hoc pairwise analyses of grdif() take the reference group
as the reference group of every pair that includes it, and, for a pair
of focal groups, the group listed earlier in focal.name. Before, the
group that came later in the sorted order of the group values was the
focal group of a pair. When the two orders differ, group.pair is
labeled in the new order, the signs of rdifr, z.rdifr, rdifs,
and z.rdifs are reversed, and n.ref and n.foc are swapped.
rdifrs, the p-values, and the flags do not change.
The default of range in grdif() is c(-5, 5), as in rdif(),
crdif(), ripd(), catsib(), and est_score(), instead of
c(-4, 4). The ability estimates of the examinees whose ML estimates
reach a limit of the range (for example, all responses correct or all
wrong) change, and so do the results that depend on them.
grdif() supports dichotomous items only and stops with an error when
x includes a polytomous item or any item of the "GRM" or "GPCM"
model, even one with two score categories, because the GRDIF framework
has been evaluated for dichotomous items (Lim et al., 2024). Before,
polytomous items were analyzed as net DIF, and the default post-hoc
analyses of mixed-format data could stop with an error.
grdif() stops with an error when the examinees whose group is not in
focal.name belong to more than one group, because they form the
reference group. Before, the GRDIF statistics combined them into one
reference group while the post-hoc analyses compared them separately.
grdif() and catsib() apply min.resp from the initial analysis
also when score is supplied: the scores of the examinees with fewer
than min.resp responses are set to missing and these examinees are
excluded, and a warning reports their number.
est_irt() stops with a clear message that names the items when a
response is not a whole number from 0 to the number of score
categories minus 1. Before, it stopped with "subscript out of bounds".
With fipc = TRUE it also stops when x and data have different
numbers of items, when fix.loc is empty or points outside the rows
of x, and when an ID in fix.id is not found in the item IDs.
Before, an unmatched ID in fix.id was dropped without notice.
A supplied item.id replaces the IDs in x in est_irt() also when
fipc = FALSE, as it already did with fipc = TRUE. Before, it was
ignored when x was supplied. It must have one value per item. The
same holds for est_mg() (one vector of IDs per group, also without
FIPC) and est_item().
For est_irt() with FIPC and all items fixed, test.2 reads "No item
parameters are estimated, so the information matrix is not computed.",
instead of the message about a possible local maximum.
est_irt(), est_mg(), and est_item() issue one warning, with the
item IDs, when an item to be estimated has a score category without
responses or all its responses are the same. The maximum likelihood
estimate does not exist for such an item, and the functions returned
extreme or starting values with standard errors of 99999 without
notice. The estimates are not changed. est_mg() judges an item on
its responses pooled over the groups, and est_item() judges the
items that have responses.
bring.mirt() labels a two-category item fitted with the graded,
gpcm, or gpcmIRT model as "DRM", with a guessing parameter of 0,
because a two-category item is a dichotomous item in irtQ. Such an
item was labeled "GRM" or "GPCM", with wrong or missing parameters
(see Bug Fixes).
The type argument of bring.flexmirt(), bring.bilog(), and
bring.parscale() is "par" when omitted, as the usage line
suggests, and its case is still ignored. Omitting it stopped with "the
condition has length > 1" before. An unknown type stops with the
available choices.
bring.parscale() stops with an error for the model codes 2, 4, 6,
and 8 of PARSCALE when the file has polytomous items. The thresholds
of such items were all NA.
est_mg() checks its inputs and stops with a clear message when
group.name does not have one unique name per group, when x,
model, cats, or item.id does not match the groups or the columns
of data, when an item ID is repeated within a group, when items with
the same ID have different models or numbers of score categories in
different groups, when free.group names or numbers a group that does
not exist or leaves no reference group without FIPC, when fix.loc is
not a list with one element per group or has repeated positions, when
an ID in fix.id is not found, and when fipc.method is not "MEM"
or "OEM" (also in est_irt()).
est_mg() with FIPC warns when a fixed item has different parameter
values in different groups. The values of the first group that
includes the item are used, as before. Differences of at most 1e-10
are ignored.
run_flexmirt() stops with a clear message on systems other than
Windows, where flexMIRT does not run, and when "C:/Program Files"
holds more than one flexMIRT folder (give dir.flex then).
write.flexmirt() stops with a clear message when file is NULL.
getirt() can extract the matched call of est_irt(), est_mg(),
and est_item() objects with what = "call".
ctt() records the correct setting in its crit element, and the
summary() report names the item-total correlation used for flagging.
ctt() and ctt_distr() stop with a clear message when crit.p,
crit.dis, crit.distractor, or total is not valid, when fewer
than two examinees remain, or when ctt_distr() receives numeric and
non-numeric keys together.
Inputs that were silently misread now stop with a clear message.
Functions that take item metadata reject parameter columns stored as
text, a missing or too small cats, a dichotomous model with cats
other than 2, and a polytomous item with more thresholds than
cats - 1, including any value in par.3 of a two-category GRM or
GPCM item. shape_df() and simdat() reject parameter vectors that
do not match the items, shape_df_fipc() checks fix.loc,
gen.weight() checks dist and theta, and prm() rejects an
unknown pr.model.
prm() uses the GRM when pr.model is omitted, simdat() repeats a
single pr.model for all polytomous items, shape_df() accepts
par.drm without g, and item metadata without a par.3 column is
accepted when every item is 1PLM or 2PLM.
plot.info() and plot.traceline() show item IDs as given
("item-2" was shown as item.2), and the panel titles of
plot.traceline() read Score: 0 instead of Score: resp.0.
The warning for "DRM" items reads "All 'DRM' items are treated as
'3PLM' items." and no longer mentions item parameter estimation,
because it also appears in functions such as info() and simdat().
est_mg() gives it once, not once for each group.
est_score() stops with a clear message when data has a number of
columns other than the number of items (it scored the wrong items
before), when a response is not an integer within the categories of
its item, and when method, stval.opt, tol, or max.iter is not
valid (a max.iter below 1 or not a whole number removed the
iteration limit). For an est_irt object, supplying data or D is
now an error instead of being ignored.
est_score() now has a method for est_item objects, as its
documentation already stated.
irtfit() and sx2_fit() stop with a clear message when data has a
number of columns other than the number of items or a response is not
an integer within the categories of its item (such a response was
counted in the wrong cells before), and they read character and factor
responses as numbers instead of level codes. irtfit() also stops for
a score of the wrong length, scores without variation, an invalid
range.score, and a loc.theta other than "average" or "middle"
(ignoring case; any other value was treated as "average"), leaves
out examinees with a missing ability estimate instead of failing, and
works when only one item remains after items with fewer than two
responses are excluded. plot.irtfit() stops for a type other than
"both", "icc", or "sr" and for an item.loc that is not an
evaluated item. sx2_fit() stops for a test with a single item, which
has no S-X2 statistic. The warnings for excluded items and for missing
responses in sx2_fit() name the items, and the error for a response
outside the categories names the item and its column.
bisection() warns when the function values at lb and ub have the
same sign, instead of returning a bound without notice, and it stops
after exactly max.it iterations instead of max.it + 1. Its help
page names the last returned element delta, as in the code.
est_score() with method = "INV.TCC" stops with a clear message
when the test characteristic curve does not reach a sum score within
theta from -20 to 20, for example for items with very small slopes or
guessing parameters that sum to nearly the maximum score.
ctt() computes alpha with each item removed faster for large tests.
cac_lee() and cac_rud() leave out examinees with a missing ability
estimate (or standard error) with a warning. Before, cac_lee() and
cac_rud() stopped with an obscure error for a missing ability
estimate, and cac_rud() returned wrong indices for a missing
standard error. Both stop for cut scores that are not in strictly
ascending order, and cac_rud() stops for a standard error that is
not positive. cac_lee() no longer stops when a performance level
contains no integer summed score, and it is much faster with ability
estimates.
panel_info() stops with a clear message for a route map that does
not define a panel (a module without transitions, a cycle, or a module
outside the stages) instead of running forever, and it finds every
stage when the modules are not numbered in the order of the stages.
panel_info(), run_mst(), reval_mst(), and find_cut() read a
logical route map as a 0/1 matrix.
reval_mst() accepts item metadata with a missing guessing parameter,
such as 2PLM items from shape_df(). It checks and completes the item
metadata as run_mst() does, so item.by.mod and item.by.path hold
plain data frames with upper-case model names and without parameter
columns that are all NA. reval_mst() and run_mst() stop with a
clear message when cut_score does not match the panel or module
has a number of columns other than the number of modules, they read a
NULL element of cut_score for a stage with a single module as an
empty vector, and they assign the cut scores to the modules of a stage
in the order of the module indices. reval_mst() also stops with a
clear message when a sum score has no inverse TCC estimate (with
intpol = FALSE or a range.tcc that does not cover the estimates);
before, it stopped with an obscure error or returned NA for every
row of eval.tb.
run_mst() stops with a clear message when tol or max.iter in
route_score or final_score is not valid (a max.iter of 0 or 2.5
never ended), and it warns once about examinees without any observed
response to the administered items.
find_cut() no longer warns about two proper crossings when a
crossing lies exactly on a grid point.
ripd() results have a print() method that prints the flagged items
and the RIPD statistics of the analyses without and with purification;
what selects "no_purify" or "with_purify". Before, print()
listed the whole object, including the ability estimates of every
examinee.
The est_irt and est_item methods of rdif() and crdif() stop
with a clear message when data or D is passed, because both are
taken from the object, and the est_item methods do so for score as
well. Before, they were silently ignored or caused an obscure error.
The purification of rdif(), crdif(), ripd(), grdif(), and
catsib() is faster: after the first iteration, only the examinees
who responded to the removed item are rescored when ncore = 1 and
method is a pattern scoring method. Results are unchanged.
The purification of rdif(), crdif(), ripd(), grdif(), and
catsib() gives the warning of est_score() about ncore > 1 once
instead of at every iteration.
rdif(), crdif(), and ripd() stop with a clear message for
method = "EAP.SUM" or "INV.TCC" when they estimate the abilities
(score = NULL or purify = TRUE), because these methods give no
ability estimate for each response pattern. Before, rdif() and
crdif() stopped with an obscure message. ripd() and pcd2() also
stop with a clear message when min.resp is not a single nonnegative
number, and ripd() stops when score is not numeric, when alpha
is not a single number between 0 and 1 (an alpha of 1 or more, or a
text value, flagged every item), or when max.iter is not a positive
whole number and purify = TRUE. The est_irt and est_item methods
of ripd() stop with a clear message when data or D is passed,
and the est_item method when score is passed, as those of rdif()
and crdif() do.
pcd2() stops with a clear message when max.iter is not a positive
whole number and purify = TRUE, and when crit.val is not NULL or
a single nonnegative number. Before, it accepted max.iter = 2.5, and
a text crit.val flagged no item without a message.
The print() methods of grdif() and catsib() leave the
significance symbol of an item with a missing p-value blank, as those
of rdif() and crdif() do for an item skipped with item.skip.
Before, it showed ?, which the legend does not explain.
The print() methods of rdif() and crdif() leave the significance
symbol of an item skipped with item.skip blank. Before, it showed
?, which the legend does not explain.
The dif_item elements rdifr and rdifs of the no_purify results
of rdif() are integer vectors like rdifrs, and n.iter of the
with_purify results of rdif() and crdif() is the integer 0 when
no item is flagged in the initial analysis. npar.est of est_mg()
and n.iter of the with_purify results of catsib() are integers
as well. The values are unchanged.
When the Hessian of an item is singular during the estimation of
est_irt(), est_mg(), and est_item(), it is now repaired by
adding a ridge to its diagonal instead of random numbers. The results
are deterministic, and the random number stream of the user is no
longer used by the estimation.
The help pages of est_irt(), est_mg(), est_item(), and
getirt() now describe how the standard errors are computed: the
information of the item parameter priors is added to the cross-product
information matrix of est_irt() and est_mg(), and est_item()
uses the observed information with the abilities treated as known. The
help pages of est_irt(), est_mg(), and getirt() also state that
test.2 checks whether the information matrix is singular, and those
of est_irt(), est_mg(), and est_item() state that the slope
prior aprior is placed on D times the slope.
est_irt() and est_mg() return a new last element, npar.detail,
an integer vector with the numbers of free item parameters (item),
of means and variances of the latent distributions (dist), and of
weights of the empirical histograms (weight); their sum is
npar.est. The summary() output shows these three numbers under
"Number of free parameters", and getirt() extracts it with
what = "npar.detail". Objects created by earlier versions have no
npar.detail, and their summary shows the total only.
Corrected and extended the help page of est_irt(). The returned
loglikelihood is the sum over examinees of the marginal
log-likelihood of the response pattern, without the prior densities
(it is not a sum over items); ncase and posterior.dist refer to
examinees, not response patterns; data is a matrix; and the returned
call is listed. The help page also describes the response coding (0
to the number of categories minus 1), the role of x, item.id,
weights, group.mean, and group.var with and without FIPC, the EM
algorithm, the empirical histogram, the two FIPC methods (including
that the returned distribution of fipc.method = "OEM" is updated
once afterward and that the standard errors in group.par for FIPC
are approximate), the counting of the free parameters, the bounds of
the slopes, and the items without a maximum likelihood estimate.
Etol and MaxE are described as the criterion and the maximum
number of EM cycles, and the argument tables, the lists in the
argument descriptions, and the examples were corrected. The same
corrections apply to getirt() and to the arguments shared with other
help pages.
Corrected and extended the help pages of est_mg(), est_item(), and
getirt() in the same way. The help page of est_mg() now explains
free.group, the roles of weights, group.mean, group.var, and
EmpHist for groups in and not in free.group, the MG-FIPC algorithm
(Kim & Kolen, 2016), and the structure of the returned
loglikelihood, group.par, posterior.dist, data, ncase, and
nitem. The help page of est_item() states that the estimates are
Bayes modal when priors are used, that the items are estimated
separately, and what group.par, data, deleted.item, and
n.response contain. The README and the reference of Kim and
Kolen (2016) were corrected.
The help pages of est_irt() and est_mg() state that the EM cycles
stop on the largest absolute change in the estimated item parameters
(with all items fixed in FIPC, in the means and variances of the
latent distributions). The estimated means and variances of the latent
distributions and the weights of empirical histograms are not part of
this criterion, so they can be less converged than the item
parameters, and a smaller Etol gives more precise values; the
default Etol of est_mg() (1e-3) is larger than that of
est_irt() (1e-4). The help pages also state that, with
EmpHist = TRUE, group.par reports the fixed mean and variance of a
rescaled histogram, which can differ slightly from the mean and
variance of the returned weights, and that the standard errors of
the item parameters treat the latent distributions as known.
Corrected the help pages of bring.flexmirt() and the other import
functions (the returned elements, the arguments type, rePar, and
rePar.gpc, the model labels, and the item types of mirt that are
supported), write.flexmirt() (norm.pop holds the mean and the
variance, metadata from irtQ needs rePar = FALSE, and the scaling
constant), run_flexmirt() (Windows only), summary() (the returned
object), sx2_fit() (missing responses are replaced with 0), and the
data sets LSAT6, simCAT_DC, simCAT_MX, simIPD, simMG, and
simMST. The package overview gives D = 1 as the default and lists
the functions for item parameter drift, MST panels, and the import and
export of files. The help pages of rdif(), crdif(), grdif(),
catsib(), est_irt(), est_mg(), and est_item() state that the
digits argument of their print methods is not used.
The print methods are now documented on the help pages of the
functions that create the objects (rdif(), crdif(), grdif(),
catsib(), irtfit(), run_mst(), and find_cut()), and those of
the est_irt(), est_mg(), and est_item() objects and their
summaries on the help page of summary(), including the use of their
digits argument.
Fixed the routing in run_mst(). The ability estimate used to choose
the next module was computed from the responses to the current module
only. It is now computed from the responses to all modules
administered so far, as intended. This applies to every routing method
("bmat", "mfi", and cut scores) and every route_score method, so
simulated paths and results can differ from earlier versions, in
particular from stage 3 onward.
Fixed cut-score routing in run_mst() for route maps in which a
module reaches only some modules of the next stage. The examinee could
be sent to a module that did not match the cut scores. Only the cut
scores that separate the reachable modules are now used, which is the
rule applied by reval_mst(). Results are unchanged when every module
of a stage can be reached.
Fixed the final estimate of run_mst() for responses with missing
values (response argument). The ML, WL, MLF, MAP, and EAP estimates
used the parameters of other items instead of those of the observed
items.
score_resp() and ctt_distr() now match a data frame key to the
item columns by item number when its item column is character or
factor. Before, keys for tests with 10 or more items could be assigned
to the wrong items.
shape_df() with default.par = TRUE now repeats a single value of
cats or model for all items. Before, a single cats value gave
item IDs of "V1" for every item and a guessing parameter of 0 instead
of 0.2 for 3PLM items.
irtfit() now computes the observed category proportions directly
from the frequencies. Before, they came from
janitor::adorn_percentages(), which can include the total column in
the denominator in some environments and halve the proportions, the
residuals, and overSR.prop. Results are unchanged when the
proportions were computed correctly.
catsib() no longer overwrites score with se when se is given
as a matrix or data frame.
cac_rud() no longer stops with a dimnames error when a performance
level has no examinees, and the label of the total row of marginal
is now "marginal", as in cac_lee().
simdat() treats NA values in g.drm as zeros, as the item metadata
input does. Before, the responses to those items were all NA.
The Wald confidence intervals in plot.irtfit() now use the two-sided
critical value, qnorm(1 - alpha / 2). Before, they used
qnorm(1 - alpha) and were 90% intervals at the default
alpha = 0.05, while the Wilson intervals were 95% intervals.
reval_mst() now stops with an informative message when the modules
in a stage differ in maximum sum score.
In run_mst() and reval_mst(), an ability estimate equal to a cut
score is now routed to the higher module, as in the classification
rule of cac_lee() and cac_rud(). Results change only when an
estimate equals a cut score exactly.
ctt(), ctt_distr() (scored-category mode), and the CTT helper
functions now stop with an informative error when an item score is not
a whole number between 0 and cats - 1. When cats is inferred,
every item has at least two categories, so an item that every examinee
scores 0 on gets a difficulty of 0 and is flagged.
ctt() reports the listwise deletion of incomplete rows once, and the
item table has default row names.
plot.find_cut() no longer passes an unused inherit.aes argument to
geom_vline(), which caused warnings with some ggplot2 versions.
catsib() now stops with an informative message when score is
supplied without se. Before, it failed with a "missing value where
TRUE/FALSE needed" error.
Stated in ?reval_mst that the recursion is based on inverse TCC
estimates, that only inverse TCC scoring with cut-score routing is
supported, and that modules in a stage must have the same maximum sum
score; corrected the documented default of theta and the description
of the returned list.
In ?run_mst, described the cumulative routing estimate and the
relation to reval_mst(), and stated that "EAP.SUM" and "INV.TCC"
count a missing response as 0 in the final sum score.
Corrected the interpretation of alpha with the item removed in ?ctt.
The ctt() examples and the CTT article now use simulated IRT data.
Corrected the description of the expected frequencies in pcd2() and
stated that crit.val = NULL flags no items.
Reworded the motivation of ripd().
In ?simMST, noted that the item parameters are on the D = 1.702
scale. In ?find_cut, clarified that the cut scores in simMST were
obtained with find_cut() and softened the statement about path
reversals.
Corrected the probability matrix of the first example in ?lwrc, the
GPCM formula note in ?irtQ, and the class of prob.cats in
?traceline. Corrected statements in the README, the vignette
overview, and the articles (shrinkage of MAP and EAP, range.score in
irtfit(), fix.id in est_mg(), the effect of EmpHist in FIPC,
the fixed-slope 1PLM, fixed guessing, the CATSIB regression
correction, the purification procedure, and the usage notes of the
utility functions). The DIF article now simulates item difficulties in
a narrower range so that the pooled calibrations converge.
New classical test theory (CTT) functions: ctt() for item- and
test-level statistics, with print() and summary() methods;
freq_score() for a total-score frequency table; ctt_distr() for
option or score-category distributions with point-biserial
correlations; and score_resp() for scoring raw selected-response
data with an answer key. Item-total and option-total correlations are
reported both with and without the item.
Added a vignette, "Introduction to irtQ" (vignette("irtQ")), that
walks through calibration, scoring, and model-fit evaluation.
Fixed the EM convergence check in est_irt() and est_mg(). It used
the largest signed change instead of the largest absolute change, so
the EM could stop before the Etol criterion was met, most often when
some parameters were held constant (for example, the guessing
parameter of 1PLM and 2PLM items, or slopes fixed by fix.a.1pl or
fix.a.gpcm). Refitting a model can change the estimates and take
more EM cycles. A fit from an earlier version that reported
maxpar.diff equal to 0 most likely stopped early.
est_irt() and est_mg() no longer warn that the convergence
criteria are not satisfied when fipc.method = "OEM" or when the EM
converges at the MaxE-th cycle. In these cases, test.2 can now
report a possible local maximum.
est_item() now uses Beta(5, 16) as the default prior for the
guessing parameter, as in est_irt() and est_mg().
getirt() now returns posterior.dist and scale.D for objects from
est_irt() and est_mg(), as documented.
find_cut() now uses D = 1 by default, like the other functions in
the package (previously 1.702). Supply D = 1.702 to reproduce
earlier results.
shape_df_fipc() now accepts a single value of cats or model for
all new items.
The mirt package moved from Imports to Suggests. Only bring.mirt()
uses it, and it now stops with an informative message when mirt is not
installed.
Added examples to print.ctt(), summary.ctt(),
print.summary.ctt(), and the data sets.
Corrected the descriptions of Etol, test.1, test.2, and
maxpar.diff, the return class of est_mg(), dif_item in the
purified results of rdif(), crdif(), and grdif(), and the
component names of simCAT_DC and simCAT_MX.
Corrected bibliographic details, added DOIs, cited the polytomous RDIF
extensions in rdif() and crdif(), and listed every reference cited
in the help pages on the package help page (?irtQ).
Added a new function, find_cut(), which identifies TIF-crossing
routing cut scores for MST panels. For each adjacent module pair
within a stage, the function locates the theta where the two modules'
test information functions (TIFs) intersect. A warning is issued when
the mean difficulty order of modules within a stage differs from their
input index order.
Added a new S3 method, plot.find_cut(), which visualizes TIF curves
and routing cut scores stage by stage using ggplot2 facets.
Proper, anomalous, and unselected cut scores are distinguished by line
type. A layout argument ("vertical" / "horizontal") controls the
facet orientation.
Added a new function, run_mst(), which simulates MST administrations
for a given panel structure and returns response data along with
ability and routing information for each simulated examinee.
Added a new exported dataset, simIPD, which contains simulated CAT
response data for illustrating IPD detection with ripd() and
pcd2(). The dataset represents one replication of a CAT simulation
(N = 3,000; test length = 30; 360-item 3PLM pool) in which 5% of items
(18 items) had both $a$ and $b$ parameters decreased by 0.5.
est_irt(), est_item(), and est_mg() now accept a partial
control list. Users can specify only the arguments they wish to
override (e.g., control = list(iter.max = 500)); unspecified
arguments fall back to their defaults via modifyList().
Reorganized the pkgdown reference page: ripd() and pcd2() are
now grouped under a new Item Parameter Drift (IPD) section, and
reval_mst(), panel_info(), find_cut(), plot.find_cut(), and
run_mst() are grouped under a new Multistage-Adaptive Test (MST)
section.
Expanded the documentation for ripd() by adding a @details section
that covers the theoretical background of the RIPD framework,
asymptotic distributions of the three RIPD statistics, a drift-type
diagnostic guide, the CAT-specific three-step workflow, and the
purification procedure. Also added a \donttest{} example
demonstrating a complete CAT-based IPD detection workflow using the
simIPD dataset. Updated @references to include Lim & Choe (2023)
and replaced the previous conference paper citation with the journal
reference (Lim & Han, 2026).
Added a \donttest{} example to pcd2() demonstrating CAT-based IPD
detection using the simIPD dataset, including the bootstrap critical
value procedure described in Lim & Han (2026).
Updated the MST Panel Evaluation and Simulation article
(vignettes/articles/mst-panel-evaluation.Rmd) to introduce
run_mst() and extend the existing reval_mst() content with routing
and scoring examples, a find_cut()-based principled cut score
derivation, a side-by-side routing method comparison, and a Monte
Carlo-vs-analytical validation example (Example 6).
simMST dataset: the previous version had 9 items
duplicated across non-adjacent modules because the original assembly
only enforced no-overlap within a single routing pathway. The new
version enforces a global no-overlap constraint across all 7 modules
(56 unique items total) and adds a mean(b) per-module band
constraint; cut scores were regenerated via find_cut() on the
rebuilt modules.est_irt(),
est_item(), and est_mg().est_score() by up to 52% for
dichotomous items (N = 10,000) and up to 27% for mixed-format tests,
through a series of optimizations.sx2_fit() substantially by
replacing the O(J^2) Lord-Wingersky recursion with a forward-backward
pass (up to 11x faster for mixed-format tests with J = 55 items) and
vectorizing internal helper functions expFreq(), obsFreq(), and
the PRM category-collapsing routine.testthat >= 3.0.0). Tests cover core functions including drm(),
prm(), est_irt(), est_score(), est_mg(), rdif(), crdif(),
and catsib(), with the relevant tests across dichotomous,
polytomous, and mixed-format item scenarios (355 tests total).ripd(), which implements the Residual-based
Item Parameter Drift (RIPD) detection framework. The function computes
three RIPD statistics: $RIPD_R$, $RIPD_S$, and $RIPD_{RS}$ (one for
each item). $RIPD_R$ captures uniform item parameter drift (IPD) via
differences in mean raw residuals between groups, $RIPD_S$ captures
nonuniform IPD via differences in mean squared residuals, and
$RIPD_{RS}$ is a combined chi-square-based statistic sensitive to both
types of drift. An optional purification procedure is also supported.est_irt(),
est_item(), and est_mg().est_score().irtfit() and sx2_fit() to
assess model fit.rdif(), grdif(), and catsib() to
detect item bias.cac_lee() and cac_rud().info(), traceline(), lwrc(),
simdat(), and related helpers.reval_mst(): measurement precision
and bias evaluation for multistage adaptive tests.sx2_fit() that caused incorrect cell collapsing
between two adjacent score categories for polytomous items when
computing the S-$X^2$ item fit statistic.est_score() and info().catsib() where the function failed when all
responses were missing (NA) in either the reference or focal group.cac_rud() to include the x argument, allowing users to
pass item metadata data frames directly.control parameters in est_irt(), est_item(), and
est_mg(), and updated the documentation accordingly.est_score() function in terms of Newton-Raphson
method.catsib():
catsib_item() used a hardcoded
threshold of 3 instead of the user-supplied min.binsize argument,
causing inconsistent bin filtering behavior.rho2 in catsib_one() was not clamped to
$[0, 1]$, so when errvar > sigma2 (e.g., very few items or
purification cascade), a negative rho2 reversed the regression
correction direction, inflating the Type I error rate.errvar >= sigma2 during purification, rho2 collapsed to 0,
causing all corrected scores to converge to the group mean. With
group mean differences (impact), this produced empty bin data frames
and an invalid purification result. A minimum floor of 0.05 is now
enforced for rho2 to preserve score spread.covirt() where the guessing parameter
(par[,3]) was incorrectly passed as the difficulty parameter
(par[,2]) to the integrand() function for DRM items. This caused
the gradient computation to receive c = b, which zeroed out the
$\partial P/\partial a$ and $\partial P/\partial b$ gradient
components and produced a singular Fisher information matrix.
Additionally, NA values in par.3 for 1PLM and 2PLM items are now
substituted with 0 prior to gradient evaluation to prevent NA
propagation.The documentation for the irtQ package has been revised to reflect
updates to function behavior, fix typos, and provide more relevant and
detailed information for existing functions.
A new function, crdif(), has been added. This function computes
three statistics from the residual-based DIF detection framework using
categorical residuals (RDIF-CR). It allows for the detection of global
DIF, particularly in polytomously scored items.
A new function, shape_df_fipc(), has been introduced. This function
merges fixed-item metadata with automatically generated metadata for
new items and produces a single data frame ordered by test position.
It is designed to support fixed item parameter calibration (FIPC) via
the est_irt() function.
The plot() method has been enhanced to support the display of all
item characteristic curves for a given item in a single panel.
The pcd2() function has been updated to include a purification
procedure.
The rdif() and catsib() functions now include an item.skip
argument. This allows users to specify a numeric vector of item
indices to exclude from the DIF analysis.
Enhanced functionality of the bind.fill() function by adding a new
argument fill. The value in the argument is used to fill in missing
data when aligning datasets.
Fixed a bug within the est_irt() function that was previously unable
to implement the fixed item parameter calibration (FIPC) when only
freely estimating a single item given that all other items are fixed.
Added a new function, reval_mst(), which evaluates the measurement
precision and bias in Multistage-Adaptive Test (MST) panels using a
recursion-based evaluation method introduced by Lim et al. (2020).
Added a new function, pcd2(), which computes the Pseudo-count
$D^{2}$ statistics (Cappaert et al., 2018; Stone, 2000) to detect item
parameter drift.
Introduced Warm's (1989) Weighted Likelihood (WL) estimation method to
the est_score() function. This WL scoring method can now be utilized
by setting method = "WL".
Enhanced the speed of ability parameter estimation in the
est_score() function when using the ML, MLF, or MAP methods for the
method argument. The updated version performs approximately three
times faster than its predecessor.
Addressed a bug within the est_score() function that was previously
unable to accurately compute scores when only a single item data was
provided. This issue was occurring with the EAP.SUM and INV.TCC
estimation methods.
Added two new functions for computing classification accuracy and
consistency: cac_rud() and cac_lee().
cac_rud: This function implements Rudner's (2001, 2005) method for
computing classification accuracy and consistency. It takes cut
scores, ability estimates, standard errors, and optional weights as
inputs and returns a list containing a confusion matrix, marginal
and conditional classification accuracy and consistency indices, the
probability of being assigned to each level category, and the cut
scores used in the analysis.cac_lee: This function implements Lee's (2010) method for
computing classification accuracy and consistency. It takes a data
frame containing item metadata, cut scores, optional ability
estimates, optional weights, a scaling factor, and a logical value
indicating the cut score metric as inputs. It returns a list similar
to cac_rud.Added a new function, llike_score(), which computes the
loglikelihood of ability parameters given the item parameters and
response data.
Enhanced functionality of the rdif() and grdif() functions: Both
now support the graded response model (GRM) and generalized partial
credit model (GPCM).
Fixed an issue in the grdif() function that inaccurately calculated
the GRDIF statistics when group membership was specified in a
non-standard way. Specifically, the problem arose when 0 wasn't used
as the reference group and consecutive numbers (e.g., 1, 2, 3) weren't
used to represent focal groups in the group argument.
Resolved the misalignment issue of standard errors in the output of
the est_irt() function when fix.a.1pl = TRUE is specified and the
items are calibrated using the 1PLM.
Added a new function, grdif(), to perform differential item
functioning (DIF) analysis across multiple groups. This function
calculates three generalized IRT residual DIF (GRDIF) statistics. For
more information about the function and its usage, please refer to the
accompanying documentation.
Fixed several typos in the manual documentation
Initial release on CRAN
The irtQ package is a successor of the irtplay package which was
retracted from R CRAN due to the intellectual property (IP) violation.
All issues of the IP violation have been clearly resolved in the
irtQ package.
Most of the functions the irtQ package are identical in appearance
and functionality to those of irtplay package except a few functions
(e.g., shape_df(), est_score()). However, the computing speed of
several functions (e.g., est_irt(), est_score(), lwrc()) in the
irtQ package are faster than the previous ones in the irtplay
package. Read the documentation carefully prior to using the
functions.