New RMreliabilityCurve() plots conditional SEM, test information or
conditional reliability across the latent scale, with the respondent
distribution behind it. benchmark shades the region reaching a given
reliability and reports the percentage of respondents inside it.
New RMpersonChange() tests, per respondent, whether a person location moved
between two occasions by more than measurement error allows. null selects
the estimand, "measurement" (default) or "retest". Critical values come
from the exact null by default, needing no simulation, and are well below 1.96
on short scales. retest_sd_tip reports how much occasion noise each flagged
result tolerates. Single-respondent use needs item_params from an external
calibration.
New RMretestSD() estimates the per-occasion SD of occasion-to-occasion
fluctuation from a test-retest study, for use as RMpersonChange(retest_sd =).
Negative variance estimates are reported, not floored.
RMtargeting() gains category_labels, row_gap, viridis_option,
viridis_begin and viridis_end.
RMtargeting() now draws response-category bands in its bottom panel,
with threshold estimates and confidence intervals as a coloured error-bar row
beneath. Categories that are never most likely, and threshold reversals, are
marked in red. Pass panel = "thresholds" for the previous dot-and-whisker
panel. Estimates are unchanged.
RMreliability()'s marginal reliability changes formula, and the row is
renamed "Marginal (curve mean)". It is now the latent-density-weighted mean
of sigma^2 / (sigma^2 + SEM(theta)^2) rather than Green's subtractive
coefficient, which could fall below zero and was floored there. Results move
upward, more so on short scales (phq9 .862 to .886, eRm::raschdat1[, 1:20]
.695 to .769). The superseded value is the marginal_green attribute of
RMreliabilityCurve(output = "dataframe").
RMdimCFACutoff() and RMdimCFA() failed when an item name was not a
syntactic R name, such as Item 1. The CFA is now fitted under placeholder
names and the loadings mapped back. Results for syntactic names are unchanged.RMitemInfit(), RMlocdepQ3() and RMlocdepGamma() now flag on the
multiplicity-corrected p-value rather than on the simulated interval.
p_value defaults to NULL, meaning TRUE when cutoff is the full
cutoff object and FALSE otherwise, so calls that pass no cutoff or a bare
cutoff table are unchanged. Flagging against an interval tests every item or
pair at once and sets a family-wise error rate of 1 - width^m, which the
corrected p-value targets directly instead. Tables gain the p_ and padj_
columns. Flagged items and pairs change. Pass p_value = FALSE for the
old behaviour. Follows Johansson (2026),
https://doi.org/10.31234/osf.io/7pqz4_v2.
All three cutoff functions default to hdci_width = 0.95, was 0.999
in RMitemInfitCutoff() and 0.99 in RMlocdepQ3Cutoff() and
RMlocdepGammaCutoff(), and the two local dependence cutoff functions
default to iterations = 400, was 500 and 250. The interval is now a
description of where a fitting statistic is expected to fall rather than a
decision rule, and 400 is the count that width needs. Cutoff values
change. RMitemInfitCutoffMI() keeps hdci_width = 0.999, since
RMitemInfitMI() has no corrected-p-value path and the interval is still
its decision rule there.
RMlocdepGamma() now tests each item pair once, on the larger of its two
conditioning directions. The p-value and the flag previously came from the
canonical direction alone and were repeated in both tables, so the
direction-2 table showed a coefficient beside a p-value computed from a
different one, sometimes of the opposite sign. A new gamma_pair column
carries the tested statistic and gamma still carries each direction's own
coefficient. RMlocdepGammaCutoff() and RMlocdepGammaPlot() describe that
maximum. Cutoff values, p-values and flags all change.
RMdimMartinLof() sampled dichotomous null patterns from the wrong
distribution (since 1.0.0), which inflated the null distribution, gave a
Type-I error near zero against a nominal 0.05 and badly reduced power.
Polytomous data was unaffected. Dichotomous Martin-Löf p-values from
1.0.0 and 1.1.x should be recomputed.
parallel = TRUE and parallel = FALSE now give identical results for the
same seed, the per-iteration runners now pinning the generator that
mirai workers otherwise change. Parallel results change, to agree with the
sequential ones. Affects RMdifGammaCutoff(), RMdimCFACutoff(),
RMdimMartinLof(), RMdimResidualPCACutoff(), RMitemInfitCutoff(),
RMitemInfitCutoffMI(), RMitemRestscoreBoot(), RMlocdepGammaCutoff(),
RMlocdepQ3Cutoff() and RMreliability().
RMpersonFit(parallel = TRUE) was not reproducible from seed at all, its
workers having had no per-task seed. Respondents are now seeded
individually. Results change in both paths.
RMreliability() with the default seed = NULL now reproduces from a
session-level set.seed(), as the other simulation functions already did.
The RMU row and the bootstrap intervals previously differed between two
otherwise identical calls. Results for an explicit seed are unchanged.
RMdimMartinLof() and RMdimMartinLofResiduals() no longer drop
respondents for missingness on items outside partition. The 30-case
minimum is likewise counted over the partitioned items only.
The reminder about iteration counts is now two-tier, reworded from calibration to reproducibility, and applies to item fit and both local dependence functions. Below 400 iterations a once-per-session message reports that the Westfall-Young correction is mildly liberal, and between 400 and 1000 the table caption notes that error rates are calibrated but decisions remain somewhat seed-dependent. A separate once-per-session message reports the family-wise error rate implied by the interval whenever flagging is interval-based.
RMlocdepQ3() and RMlocdepGamma() warn when correction is "fdr_bh"
or "fdr_by" and the bootstrap is too small for the procedure to reject
anything. Over 36 item pairs Benjamini-Hochberg needs 720 iterations
against 19 for the default Westfall-Young.
RMlocdepGammaCutoff() is roughly 14 times faster, partial gamma now being
computed by a vectorised internal rather than by iarm::partgam_LD(), which
also derives an asymptotic standard error and confidence interval that a
simulated null does not use.
The two conditional samplers are now one recursion over the nested gamma
functions, computed once per call rather than per simulated person, and
about four times faster on a six-item scale. A given seed no longer
reproduces the p-values of earlier versions.
Captions now report simulated datasets that had to be discarded, so the iteration count a result rests on can be read against the count requested.
The three cutoff objects gain requested_iterations, and RMdimMartinLof()
gains sample_n_total and sample_has_na.
RMitemICCPlot()'s caption now names the class-interval grouping that was
actually used: the method, the number of groups formed, how many of those
contain respondents when that is fewer, and whether the requested grouping
had to fall back to score level.
RMitemInfitPlot() draws its outer interval at simfit$hdci_width instead
of a fixed percentile range, so the plot and the table describe the same
interval.
RMpersonFit(parallel = TRUE, n_cores = NULL) now reads
getOption("mc.cores") before falling back to two workers, matching the
cutoff functions. Results are unaffected by the worker count.
New help topic ?easyRasch2-reproducibility covers what seed guarantees,
what the default seed = NULL inherits from a session-level set.seed(),
and the pinned generator's side effects.
Both Martin-Löf functions have been validated against the pml SAS macro
(Christensen, 2004), kindly shared by Karl Bang Christensen. The statistic,
the conditional log-likelihoods, the expected counts and the residuals
reproduce the macro to numerical precision.
RMtargeting() no longer fails when the maximum possible raw score is
odd. The default bins was the maximum observed raw score divided by
two, which is fractional for an odd maximum (e.g. 13.5 for a nine-item
0-3 instrument); ggplot2 4.0 rejects a non-whole bins in
geom_histogram(), which produced an Error in seq.default(): 'to'
must be a finite number when the empty panel was scaled. The default
is now rounded up.RMitemInfit() gains a statistic argument ("infit", the default, or
"outfit") that selects which conditional fit statistic the table
reports. RMitemInfitCutoff() already simulated both, but only infit
could be displayed and tested. With statistic = "outfit" the cutoff
bounds come from outfit_low/outfit_high, the bootstrap p-values from
the simulated outfit distribution, and the columns are named
Outfit_MSQ, Outfit_low, Outfit_high, p_outfit, padj_outfit.
The multiplicity correction runs across items within the selected
statistic, so testing both and reporting whichever flags is a larger
family than either call corrects for. The default is unchanged.RMitemInfitCutoff() changes its default iterations setting from 250 to 400. This is based on a simulation study, available at https://github.com/pgmj/rasch_fwer, and is intended for use with the FWER corrected p-values in RMitemInfit(), which will be the new default method in a future release. While 400 iterations is the new recommended lowest level for all sample sizes, a final analysis should use 1000-2000 iterations.NA rows) no longer crash
the functions that fit a CML model on data retaining missing values.
psychotools::pcmodel() errors on all-NA rows with an opaque
"invalid argument type" (and psychotools::raschmodel() segfaults), so
such rows are now dropped up front — with the standard
"N respondent(s) with no responses dropped." message — in
RMlocdepQ3Cutoff(), RMitemInfit(), RMitemRestscore(),
RMitemRestscoreBoot(), RMreliability(), and the observed-data
overlays of RMlocdepQ3Plot() and RMitemInfitPlot(). RMdifLR()
now drops such rows too (jointly with dif_var) instead of failing
with eRm::LRtest()'s error. Functions that already used complete
cases or called .drop_empty_respondents() are unaffected.RMlocdepQ3Cutoff()'s sample_n now counts the respondents actually
used (all-NA rows excluded, incomplete responses still retained) and
sample_n_total the raw input rows, matching RMlocdepQ3() and the
other *Cutoff() objects; previously the two were documented as always
equal. Captions in the affected functions keep reporting the raw total
in the n = X of Y respondents form.
RMdimMartinLof() / RMdimMartinLofResiduals(): corrected the
category weights in the Monte Carlo null sampler and the
expected-count computation (present in 1.0.0). The item parameters
were passed to psychotools::elementary_symmetric_functions() with
the wrong sign convention (it weights categories by exp(-par)), so
the gamma functions used sign-inverted weights while the sampler's
numerator used the correct ones. Consequences of the bug: simulated
null datasets did not follow the fitted model, the Monte Carlo null
distribution (and hence the p-value) depended systematically on the
column order of the data, and the residual table's expected counts
were wrong. The fixed sampler reproduces the exact conditional
pattern distribution (verified against brute-force enumeration), and
expected counts match enumeration exactly. Martin-Löf p-values and
residuals change relative to 1.0.0 — typically the corrected null
distribution sits lower, so rejections become somewhat clearer.
In addition, items are now processed in a fixed (alphabetical)
internal order, so the same seed reproduces the same p-value
regardless of how the data's columns are arranged.
RMreliability() is now fully reproducible with the same seed: the
RMU estimate previously varied slightly between identical calls because
mirt's Metropolis-Hastings plausible-value sampler leaves the R
random-number stream in a nondeterministic state (the draws themselves
are reproducible), which perturbed the RMU split-half column
assignments downstream. The function now re-seeds before the RMU
iterations.
RMdimMartinLof() now returns p_value_floor
(1 / (actual_iterations + 1)), the smallest attainable Monte Carlo
p-value; the documentation explains that a p-value equal to the floor
means no simulated statistic reached the observed one and should be
read as "p < floor" (increase iterations for finer resolution). The
vignette's Martin-Löf example now sets a seed and discusses this.RMdifTree() gains output = "list", returning both the tidy
effect-size table ($table) and the augmented tree object ($tree)
from a single fit (used by the jamovi module).RMpersonFit() gains output = "list", returning both the per-person
data.frame ($fit) and the named list of person-fit maps ($plots)
from a single computation — avoiding a second resampling run when both
views are needed (used by the jamovi module).
output = "dataframe" now returns unrounded values across the
package; rounding is a presentation concern and now happens only when
rendering the kable (whose displayed precision is unchanged). Affected
functions: RMitemInfit(), RMitemInfitMI(), RMitemRestscore(),
RMitemRestscoreBoot(), RMlocdepQ3() (the $pairs table),
RMdifGamma(), RMlocdepGamma(), RMdimResidualPCA(), RMdimCFA()
(both $fit and $loadings), RMreliability(), RMdifLR(),
RMdifTree() (including the stability summary attribute),
RMdimMartinLof() (the wle_correlation element),
RMdimMartinLofResiduals(), RMpersonParameters() (dataframe and
file/CSV output), RMpersonFit(), and RMitemParameters() (dataframe
and file/CSV output; previously rounded to 4 decimals, which also made
the wide format's mean location differ from the mean of its rounded
threshold columns in the 5th decimal). RMscoreSE() already followed
this convention.
Flagged labels and sorting are now always computed on
the exact (unrounded) values. Items or pairs sitting exactly on a
rounded boundary could in principle change flag relative to earlier
releases; displayed tables are otherwise identical.RMtargeting() regarding number of bins if not set
by user. Earlier it was sum score + 1, now it is sum score divided by 2.
RMitemICCPlot() gets slightly larger points and thicker error bars.RMitemICCPlot() gains two grouping methods for the observed means,
alongside the existing quantile grouping (method = "cut" is now a
legacy alias for method = "quantile"): method = "width" for
equal-width intervals on the total-score scale, and method = "manual"
with the new score_breaks argument -- the total scores at which a new
group starts (the concept of RASCHplot's lower.groups, without the
leading zero). The documentation of the grouping rules and of the
error_band (the model-implied interval for the observed mean per
total score) was substantially expanded.RMplotTile() gains a text_size argument for the cell labels
(default 4, matching RMitemCatProb()), and cells with zero
responses are now visually distinct: they are taken off the colour
scale and shown unfilled with a light grey outline (zero_fill =
"white", set to NULL for the previous behaviour), so the absence of
data reads as absence rather than as the darkest end of the fill
scale, where n = 0 was nearly indistinguishable from n = 1. The
caption notes the convention when empty cells are present.RMlocdepGamma()'s output = "dataframe" tables now include the
se, lower, and upper (95% Wald CI) columns for each pair,
matching RMdifGamma(); the formatted kable output is unchanged.A large consolidation release. The
headline change is that the whole package now runs on a single estimation
engine — conditional maximum likelihood (CML) item parameters via
psychotools and Warm's weighted-likelihood (WLE) person locations — replacing
the previous mix of eRm (MLE) and mirt (MML/EAP). On top of that: new CFA
loading diagnostics, three new functions for person+item parameters and person
fit, bootstrap p-values with multiplicity correction, and a round of
naming/output consistency fixes. After this release eRm is used only by
RMdifLR() (Andersen's LR test) and mirt only for the RMU plausible values
(RMitemInfitPlot()'s observed-fit overlay moved from an eRm fit to
psychotools::pcmodel(), matching RMitemInfitCutoff(); values agree to
~1e-6). Accordingly, eRm has moved from Imports to Suggests: RMdifLR()
errors with an install hint when it is absent.
Many items below shift reported numbers slightly: the underlying statistics are unchanged, only the estimation engine (WLE locations are finite at extreme scores, so extreme-score cases are now retained rather than dropped).
RMitemRestscore(), RMitemInfit(),
RMitemCatProb(), RMitemHierarchy(), RMtargeting() — moved to CML
(psychotools) + WLE. The conditional iarm statistics are engine-invariant;
only minor things move on the common grand-mean-zero scale (Relative_location
via the WLE person mean; threshold SEs ~few %; polytomous ICC curve position;
the targeting person distribution). RMdifLR() deliberately stays on
eRm::LRtest()..rasch_fit_cml() behind
RMitemParameters(), RMpersonParameters(), RMscoreSE() now fits by CML via
psychotools. Locations, person estimates and score SEs are unchanged (match
eRm to ~5e-5); only RMitemParameters()'s threshold SEs shift slightly
(psychotools vcov vs the eRm delta method, ~few %).RMscoreSE() — the WLE method now reports the information-based SE
1/sqrt(I(theta)) (matching RMpersonParameters(), catR, TAM); point
estimates unchanged, SEs differ (larger) at the score extremes, and the WLE
solver/centring is now shared with RMpersonParameters().RMreliability() — empirical reliability is replaced by a native
marginal reliability (Green 1984; the "Empirical" row is now "Marginal"),
While mirt::marginal_rxx() uses N(0,1), this integrates over the estimated
latent variance, so it is correct on the Rasch logit scale; and
the PSI is now the WLE-based separation reliability
(1 - mean(SEM^2)/Var(theta), excluding extreme scorers), replacing
eRm::SepRel() + mirt. The bootstrap is now fully native (no mirt per
resample). PSI can differ noticeably for scales with many extreme scorers;
alpha and RMU are unchanged.RMlocdepQ3()/RMlocdepQ3Cutoff(), RMitemInfitCutoff(),
RMitemInfitMI()/RMitemInfitCutoffMI(), RMitemRestscoreBoot(),
RMdimResidualPCA()/RMdimResidualPCACutoff(), RMdimCFACutoff(),
RMdimMartinLof(), RMdifGammaCutoff(), RMlocdepGammaCutoff(). The
eRm-based internal extract_item_thresholds() helper is removed.
Conditional / scale-invariant statistics (infit/outfit, item-restscore
classification, the Martin-Löf Monte-Carlo p-value, pooled MI infit MSQ/SE)
are unchanged; residual-based and simulated quantities shift slightly — Q3
values/cut-offs/flags (the statistic is unchanged; observed-vs-old-MML
correlation is typically >0.95), PCA eigenvalues / variance partition /
cut-off, and the gamma cut-offs.RMlocdepQ3()/RMlocdepQ3Cutoff() keep the previous engine available via
estimator = "MML" (default "CML"); the estimator is stored in the cutoff
object and reused by RMlocdepQ3()/RMlocdepQ3Plot() (a mismatch is
overridden with a warning). RMitemInfitMI()'s pooled-SE caveat (Müller 2020)
is now documented.RMpersonFit() — per-respondent conditional infit/outfit MSQ and the
standardized log-likelihood lz, computed per response pattern (handles
partial missingness, no biased person estimate in the residual). Significance
is by Monte-Carlo resampling under the fitted model, not the unreliable
asymptotic null (Sinharay 2016; Müller 2020). Output as table, data.frame, or
a named list of person-fit maps; zstd = TRUE adds the Wilson-Hilferty
transform (comparability only, not for inference); flag = "underfit"
restricts flagging to the validity-relevant direction.RMitemParameters() — item difficulty (dichotomous) or Andrich-threshold
(polytomous) parameters in long/wide format, with optional SEs and Wald CIs.RMpersonParameters() — per-respondent theta and SEM computed on each
response pattern (handles partial missingness), via Warm's WLE (default) or
EAP under a normal prior (SD estimated by marginal ML unless fixed).RMitemParameters() and RMpersonParameters() support
output = "file" to write the result table to a CSV at filename (the
data.frame is also returned invisibly).CFA now also reports per-item standardized factor loadings (observed vs a simulated expected range), restructured to match the other simulation tools:
RMdimCFACutoff() is now simulate-only — returns the simulation object
(null distributions + cutoffs for fit indices and loadings); it no longer
computes the observed fit, and output = "kable" errors with a pointer to
RMdimCFA(). (Breaking.)RMdimCFA() (new) takes data + a required cutoff object and returns a list
of two tables, $fit (CFI/RMSEA/SRMR) and $loadings (observed loading vs the
two-sided expected range), as kables (default) or data.frames.RMdimCFAPlot(cutoff_res, data) now returns a list of two ggplots
($loadings, in the RMitemInfitCutoffPlot() style; and $fit, the faceted
fit-index distributions) and requires data. (Breaking: previously a single ggplot.)Six simulation-based diagnostics gain optional bootstrap p-values computed
against their simulated null distributions (p_value = TRUE; the default
FALSE leaves existing output unchanged). Shared mechanics: each statistic is
studentised by its bootstrap mean/SD; marginal Monte-Carlo p-values have floor
1/(B+1); correction offers Westfall-Young studentised-max step-down FWER
(default), Benjamini-Hochberg or Benjamini-Yekutieli FDR, or "none";
Flagged then reflects padj < alpha while the simulated effect-size bands
stay in the tables. The full *Cutoff() object is required, and >= 1000
cutoff iterations are recommended (warning below that). (Ferreira 2024;
Westfall & Young 1993.) Per function:
RMdifGamma(): two-sided per item. The asymptotic BH-adjusted p-value and
star columns from iarm are dropped in this mode (one p-value family per
table); p_gamma / padj_gamma replace them.RMdimCFA(): one-sided in the unfavourable direction for CFI / RMSEA /
SRMR, two-sided for the per-item loadings, corrected as two separate
families.RMdimResidualPCA(): a single one-sided test of the first-contrast
eigenvalue, so no multiplicity correction is involved.RMitemInfit(): two-sided per item.RMlocdepGamma(): one-sided per pair for excess positive LD (matching
RMlocdepQ3()), computed once per pair in the canonical direction (rest
score = total − Item2, the simulated direction) and repeated in the
direction-2 table; correction runs over the full pair family before any
n_pairs display filter; the iarm BH columns are dropped as in
RMdifGamma().RMlocdepQ3(): one-sided per pair, folded into the new per-pair $pairs
table returned alongside $matrix (observed Q3 vs simulated band,
directional flag, sorted by departure from the per-pair median, optional
n_pairs cap).RMitemInfit(), RMlocdepQ3(), and the newly extended functions share a
"Multiple comparisons" help section.
n = X of Y respondents (<policy>), where of Y appears only
when respondents were excluded and the policy note (complete cases /
incomplete responses retained / missing values imputed) only when the
input contained missing values — complete data reads simply
n = X respondents. Terminology is "respondents" throughout (previously a
mix of "persons" / "complete cases"). Sample size is newly reported by
RMitemParameters(), RMpersonParameters(), RMitemHierarchy(),
RMitemCatProb(), RMscoreSE(), RMlocdepQ3(), RMdimResidualPCA(),
RMpersonFit()'s kable, and the descriptive plots (RMplotBar(),
RMplotStackedbar(), RMplotTile()); all other captions are reworded to
the common form. The bootstrap-null plots (RMitemInfitPlot(),
RMdifGammaPlot(), RMlocdepGammaPlot(), RMlocdepQ3Plot(),
RMdimCFAPlot()) report the simulation sample the same way, suffixed
per dataset; their *Cutoff() objects store sample_n_total /
sample_has_na, with a graceful fallback for cutoff objects from older
versions. Respondents with no responses (all-NA rows) are dropped with a
one-time message rather than triggering a CML fitting error, and
RMplotTile() likewise messages when rows with NA in group are dropped
(previously silent; item-level NAs are retained in the descriptive plots).RMdifGammaCutoff() no longer prints an iarm::partgam_DIF() result table
on every iteration in sequential (parallel = FALSE) runs; the silencing
sink is now also restored safely on iteration failure (hardened in
RMlocdepGammaCutoff() too).RMdifLR() captions now report the Andersen LR p-value as a plain number
(exact to three decimals, p < 0.001 below that) instead of the
format.pval() scientific notation (<1e-04), and the test statistic is
shown as χ² instead of chi^2.RMdimResidualPCA()
plot output is now output = "ggplot" ("loadings" kept as a backward-compatible
alias); RMtargeting() output = "figure" → "patchwork" (matching
RMitemICCPlot()); RMitemRestscore() p.adj → p_adj;
RMitemInfitPlot() output → statistic.RMitemInfitCutoffPlot() renamed to RMitemInfitPlot(), matching the
<base>Plot form of the other bootstrap-cutoff plot functions
(RMdifGammaPlot(), RMlocdepGammaPlot(), RMlocdepQ3Plot(),
RMdimCFAPlot()). Because the old name shipped in the 0.8.0 CRAN release, it
is kept as a deprecated alias that warns and forwards (unlike the 0.8.0
renames, which dropped old names outright). Separately, RMdimCFAPlot()'s
first argument cutoff_res was renamed to simfit to match the other plot
functions (no alias).RMdifLR() now defaults to output = "kable" (was "ggplot"), matching every
other function that offers both a kable and a ggplot output (RMscoreSE(),
RMdimMartinLof(), RMpersonFit(), RMpersonParameters(),
RMdimResidualPCA()). Pass output = "ggplot" for the previous default.Flagged column now labels misfit direction ("overfit" /
"underfit" / "") instead of logical TRUE/FALSE in RMitemInfit() /
RMitemInfitMI() (column type changes from logical to character).
RMitemRestscore() gains a Flagged column and drops the redundant
significance-star and Location columns (trimmed headers + explanatory
caption). Note the value direction differs: a high infit is underfit, a
high restscore correlation is overfit.RMitemInfitMI() and RMitemInfitCutoffMI() now accept mids objects whose
items were imputed as ordered factors (as required by mice's polr method):
the completed data are coerced back to numeric responses internally, rather
than erroring on the factor columns.RMitemInfitCutoff() and RMlocdepQ3Cutoff() gain an experimental dgp
argument — "resample" (default; resample WLE locations and simulate, a
marginal null) vs "conditional" (simulate each pattern from the exact Rasch
conditional given the observed total score, a matched conditional null) —
stored in the result. See dev/q3_dgp_comparison.R /
dev/infit_dgp_comparison.R.$matrix / $pairs structure.
RMlocdepQ3Plot() returns a list of two plots: $pairs (the per-pair
simulated-vs-observed dot-interval, previously the single return value) and
$matrix (a lower-triangle diverging-RdYlBu Q3 tile heatmap with above-cutoff
pairs outlined, adapted from RASCHplot::ggQ3star(); needs data, else
NULL).RMitemICCPlot() is reimplemented on the CML engine, replacing the
iarm::ICCplot() wrapper. The conditional item curve and its variance now
come from psychotools CML thresholds + the exact conditional distribution
given the total score. It draws confidence intervals on the observed
class-interval means (ci, default on) and an optional model band
(error_band); in DIF mode it shows per-group CIs and annotates each panel
with the partial-gamma DIF magnitude (iarm::partgam_DIF, as in
RMdifGamma()), with the full table attached as attr(., "dif_gamma"). New
arguments ci, error_band, conf_level, min_n (per-cell floor, 8),
items; method / class_intervals / dif_var / output are unchanged.
The conditional-ICC approach follows Buchardt, Christensen & Jensen (2023)
and their RASCHplot package.RMlocdepGamma(): corrected the $direction2 caption / @return wording to
"total - Item2" (with a note that direction 2 lists pairs in reverse order);
computations unchanged (labelling fix).RMdifTree(): fixed a "variable lengths differ" error when a single covariate
was passed by index or expression (e.g. covariates = phq9[, 10]). The
derived non-syntactic column name is now matched as a literal column rather
than re-evaluated as an R expression against the original (pre-NA-drop) data.RMdifTree(stability = TRUE) no longer silences the console in front-ends
(e.g. RStudio) that redirect the message stream. The internal stderr capture
used to muffle resample-fit noise reset the message sink to the default,
clobbering the front-end's sink; it now only redirects when no foreign message
sink is active.RMitemRestscore(p_adj = "none") now returns the unadjusted p-values instead
of NA with a "NAs introduced by coercion" warning. With no adjustment
iarm::item_restscore() omits the adjusted-p column, so the p-value is now
selected by name rather than by a fixed column position (which had picked up
the significance-stars column). The table header reads "p-value" in this case.RM* function that returns a ggplot now applies three
shared internal theme helpers:er2_axis_margins() --- extra breathing room around the x and y
axis titles.er2_plot_caption() --- left-aligned 9 pt plot caption. When the
optional ggtext package is installed (new in Suggests), the
caption renders via ggtext::element_markdown() so the
APA-conventional italic "Note." prefix can sit alongside
roman body text. Without ggtext, it falls back to a plain
element_text(face = "italic") and a plain "Note. " prefix
--- no markdown asterisks ever leak through to the rendered text.er2_caption() --- builds the caption string with the "Note."
prefix and wraps long captions at 90 characters via strwrap()
so they no longer run off the right edge of the plot. The body
text is wrapped first then prefixed, so the prefix is never
broken by a line break.@noRd) and not exported.RMitemCatProb() plots model-implied category-response
probability curves per item, similar to eRm::plotICC() or
mirt trace plots but with a ggplot2 / viridis output. Each
item gets its own facet panel; one curve per response category
is coloured from low to high using a continuous viridis palette.
Polytomous items are fit with eRm::PCM(); dichotomous items
fall back to eRm::RM() (recovering the standard two-category
logistic ICC). Optional descriptive item_labels and
category_labels make the output report-ready.label_curves = "path" mode (requires the
optional geomtextpath package, in Suggests) writes each
category's label along its own curve in the classic IRT
trace-plot style. Single-item only --- pass the full
multi-item dataset and use the item argument to choose
which item's curves to plot. The model is still fit on all
items (CML threshold estimation needs the full dataset);
only the rendering is filtered. Each category's label is
positioned at that curve's modal theta (the peak of its
bell for middle categories; the relevant edge for the
monotone extreme categories), clamped a small margin in
from the plot edges to prevent clipping.This release renames 22 exported functions under a consistent
domain-prefix → method → variant-suffix scheme so that autocompletion
on a prefix (RMdif, RMlocdep, RMitem, RMdim, RMplot) surfaces
every related function. No semantic changes; only names.
A complete mapping is on the new ?easyRasch2-renaming help page. The
short version:
| Domain | Prefix | Examples |
|-----------------|--------------|----------|
| DIF | RMdif | RMdifGamma(), RMdifGammaCutoff(), RMdifGammaPlot(), RMdifLR(), RMdifTree() |
| Local dependence| RMlocdep | RMlocdepQ3(), RMlocdepQ3Cutoff(), RMlocdepQ3Plot(), RMlocdepGamma(), RMlocdepGammaCutoff(), RMlocdepGammaPlot() |
| Item statistics | RMitem | RMitemInfit(), RMitemInfitCutoff(), RMitemInfitCutoffPlot(), RMitemInfitMI(), RMitemInfitCutoffMI(), RMitemRestscore(), RMitemRestscoreBoot(), RMitemICCPlot(), RMitemHierarchy() |
| Dimensionality | RMdim | RMdimResidualPCA(), RMdimResidualPCACutoff(), RMdimCFACutoff(), RMdimCFAPlot(), RMdimMartinLof(), RMdimMartinLofResiduals() |
| Descriptive plot| RMplot | RMplotTile(), RMplotBar(), RMplotStackedbar() |
Unchanged: RMreliability(), RMUreliability(), RMtargeting(),
RMscoreSE().
Breaking change. No deprecation aliases are shipped — existing
scripts will need a search-and-replace. See ?easyRasch2-renaming for
the full table.
RMlocdepQ3plot() for visualising per-pair simulated Q3
distributions against observed Q3 values, mirroring the design of
RMpgLDplot() and RMpgDIFplot(). Supports items and n_pairs
filters; with data supplied, ranks n_pairs by
|observed Q3 - median(simulated Q3 per pair)|.RMlocdepQ3cutoff() now retains per-pair iteration-level data:
the returned list gains pair_results, pair_cutoffs, item_names,
cutoff_method, and hdci_width. Existing scalar outputs
(suggested_cutoff etc.) are unchanged. New arguments cutoff_method
("hdci" / "quantile") and hdci_width (default 0.99) control
how per-pair credible intervals are computed.RMlocdepQ3() can now be called with the full list returned by
RMlocdepQ3cutoff() (it auto-extracts $suggested_cutoff), matching
the partial-gamma family convention. Numeric scalar still works.RMpartgamLD() and RMpgLDplot() gain an n_pairs argument
to keep only the top-N pairs by |gamma| (LD) or by
|observed - median(sim)| deviation (plot).RMpartgamLD(), RMpartgamDIF(), and RMitemrestscore()
now use consistent column headers: Adj. p-value (BH) and a new
p-value sign. star-string column. RMitemrestscore()'s former
Absolute_difference column is now a signed Difference
(observed − expected), so over- and underfit are visually
distinguishable at a glance.RMbarplot() and RMstackedbarplot() join RMtileplot() for
easy visualization of item response data distributions.RMciccPlot() for conditional item characteristic curves plot, also
includes DIF analysis for categorical DIF variables.RMitemHierarchy(), which outputs a plot illustrating the item hierarchy
with item thresholds and confidence intervals.RMcfaCutoff() to only use .scaled metrics for RMSEA and CFI, for stability.
See documentation for more details.RMmartinLof() with dichotomous items.RMitemHierarchy() making option item_labels work as intended. RMcfaCutoff() for testing unidimensionality against simulation-based
cutoff values for model fit metrics.RMcfaPlot() shows figure with distribution of simulation results and
observed model fit values.RMdifTree() for testing DIF with continuous variables, such as age
in years, as well as combinations of DIF variables (DIF interactions).stablelearner, as recommended by Henninger et al.
(2025) to assess stability of tree-based results.?RMdifTree) for more details:RMdifLR() for testing DIF of categorical variables using Andersens's
Likelihood Ratio test as implemented in package eRmRMtileplot().group faceting, which is useful for DIF-analyses.RMmartinLof() based on Christensen & Kreiner
(2007, doi: 10.1177/0146621605286204), for both dichotomous and polytomous (PCM) items.RMmartinLofresiduals().RMresidualPCA() for evaluating patterns in the standardized residuals
from the RM/PCM. Outputs either a table with eigenvalues and explained variance
or a figure with standardized loadings on the first residual contrast and item
locations.RMpcaCutoff() to determine a simulation-based critical value for the
largest eigenvalue.RMbootRestscore() for use with large sample sizes. See https://pgmj.github.io/rasch_itemfit/ for more details.RMscoreSE() that produces a transformation table (or figure) from ordinal sum
scores to WLE (Weighted Likelihood Estimation) interval scores.RMlocdepQ3cutoff() to improve speed.RMtargeting() for Wright map style plots. mice.RMinfitcutoff_mi() uses the mids object containing multiple imputated datasets (output by mice::mice()) to run simulations on each datasets and combines the results. Splits iterations across imputations (e.g. 250 total / 5 imputations = 50 each).RMiteminfit_mi() calculates and pools conditional infit from the imputated datasets and optionally uses the RMinfitcutoff_mi() output for cutoff values.RMinfitcutoffPlot()RMpartgamLD() and RMpgLDcutoff() for evaluating local dependency of item pairs in both directions.RMpgLDplot(), similar to RMpgDIFplot()RMpartgamDIF() and RMpgDIFcutoff() for evaluating DIF of categorical external variables.RMpgDIFplot(), similar to RMinfitcutoffPlot()RMinfitcutoffPlot() to illustrate distribution of simulated
conditional item infit MSQ values together with the observed value.RMinfitcutoff() gains cutoff_method and hdci_width parameters:
By default (cutoff_method = "hdci", hdci_width = 0.999), per-item cutoff
intervals are now computed using the Highest Density Interval via
ggdist::hdci() (99.9% HDCI). Set cutoff_method = "quantile" to restore the
previous behaviour (2.5th/97.5th percentiles). The ggdist package is only
required when cutoff_method = "hdci" (added to Suggests). The returned list
now also includes cutoff_method and hdci_width fields.
RMiteminfit() caption updated: When cutoff is the return value of
RMinfitcutoff(), the kable caption now states the cutoff method, e.g.
"Cutoff values based on 250 simulation iterations (99.9% HDCI)." or
"Cutoff values based on 250 simulation iterations (2.5th/97.5th percentile).".
RMiteminfit() gains optional cutoff parameter: Accepts the return
value of RMinfitcutoff() (or its $item_cutoffs data.frame directly).
When provided, per-item cutoff boundaries (Infit_low, Infit_high) and a
logical Flagged column are added to both "dataframe" and "kable"
output. The kable caption includes the number of simulation iterations when
available.
New RMinfitcutoff(): Simulation-based (parametric bootstrap) cutoff
determination for [RMiteminfit()]. Supports both dichotomous and polytomous
data. Optional parallel processing via mirai (falls back to sequential if
not installed). Returns per-item 2.5th and 97.5th percentiles of simulated
infit and outfit MSQ distributions ($item_cutoffs), together with the full
iteration-level results ($results). Requires the iarm package (Suggests).
New RMiteminfit(): Computes conditional infit MSQ statistics for each
item via iarm::out_infit(), enriched with item locations relative to the
sample mean person location. Supports both dichotomous (Rasch model via
eRm::RM()) and polytomous (Partial Credit Model via eRm::PCM()) data.
Requires the iarm package (Suggests). Output options: "kable" (default,
plain-text knitr::kable()) or "dataframe". Optional sort = "infit"
sorts by infit MSQ descending. Only complete cases are used for conditional
fit calculation.
New RMitemrestscore(): Computes observed and model-expected
item-restscore correlations via iarm::item_restscore(), enriched with
absolute differences between observed and expected values, item average
locations, and item locations relative to the sample mean person location.
Supports both dichotomous (Rasch model via eRm::RM()) and polytomous
(Partial Credit Model via eRm::PCM()) data. Requires the iarm package
(Suggests). Output options: "kable" (default, plain-text knitr::kable())
or "dataframe". Optional sort = "diff" sorts by absolute difference
descending.
RMlocdepQ3(): cutoff is now optional (default NULL). When omitted,
the raw Q3 residual correlation matrix is returned without any dynamic
cut-off applied. When provided, the dynamic cut-off (mean Q3 + cutoff) is
shown in the kable caption as before. Supersedes the requirement to always
supply a cutoff value (PRs #2 and #3).RMlocdepQ3cutoff(): Simulation-based (parametric bootstrap) cutoff
determination for RMlocdepQ3(). Supports both dichotomous and polytomous
data. Optional parallel processing via mirai (falls back to sequential if
not installed). The $suggested_cutoff is the 99th percentile of the
simulated Q3 max–mean distribution.R/utils-simulation.R: sim_poly_item(),
sim_partial_score(), and extract_item_thresholds().eRm, psychotools (>= 0.7-3), parallel, and utils
moved/added to Imports. mirai added to Suggests.RMlocdepQ3() for Yen's Q3 residual correlation analysis of local dependence.Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.