Fits a Rasch model by CML via psychotools (a dichotomous item is a
2-category PCM), extracts the standardized residuals \((x - E)/\sqrt{Var}\)
at WLE person locations, and runs an unrotated principal-component
analysis on those residuals via stats::prcomp(). The function reports
the top n_components eigenvalues and their proportions of unexplained
variance, and optionally compares the first-contrast eigenvalue against
a simulation-based bound from RMdimResidualPCACutoff.
Arguments
- data
A data.frame or matrix of item responses. Items must be scored starting at 0 (non-negative integers). Rows with any
NAare dropped before PCA, sinceprcomp()does not accept missing values.- cutoff
Optional. The list returned by
RMdimResidualPCACutoff(itssuggested_cutoffis used), or a single numeric value to use as the cutoff directly. When provided, the result includes aFlaggedcolumn (logical: is the eigenvalue above the simulated bound?) and the kable caption notes the cutoff.- p_value
Logical. When
TRUE, adds a one-sided bootstrap p-value for the first-contrast eigenvalue: the proportion of simulated first-contrast eigenvalues at least as large as the observed one,(1 + #\{lambda* >= lambda\}) / (B + 1). Requires the fullRMdimResidualPCACutoffobject ascutoff(it carries the simulated eigenvalues in$results); a bare numeric cutoff is not sufficient. The simulated null is the distribution of the largest eigenvalue, so the p-value applies to PC1 only (NAfor the other components). This is a single test — no multiplicity correction is involved. DefaultFALSE.- n_components
Integer. Number of eigenvalues to report. Capped at the number of items. Default
5.- output
Character.
"kable"(default) for a formattedknitr::kable()table,"dataframe"for the underlying data.frame, or"ggplot"for a ggplot of PC1 loadings against item locations."loadings"is accepted as a backward-compatible alias for"ggplot".
Value
If
output = "kable": aknitr_kableobject with columns Component, Eigenvalue, Proportion of variance (andFlaggedwhencutoffis provided;pwhenp_value = TRUE). The caption gives the variance partition (% of total observed variance explained by measures vs. unexplained), the model fitted, sample size, and cutoff/p-value metadata if applicable.If
output = "dataframe": a data.frame with columnsComponent,Eigenvalue,Proportion_of_variance(andFlaggedwhencutoffis provided;pwhenp_value = TRUE, non-NAfor PC1 only). The variance partition is attached as the"variance_partition"attribute — a list with elementstotal,explained,unexplained,pct_explained,pct_unexplained,n_persons. Access viaattr(result, "variance_partition").If
output = "ggplot": a ggplot showing each item's PC1 loading on the x-axis and Rasch item location on the y-axis, with dashed reference lines at zero, and the variance partition in the figure caption. Item names are labelled viaggrepel::geom_text_repel()whenggrepelis installed; otherwise plaingeom_text().
Details
Rule-of-thumb thresholds for the first-contrast eigenvalue (e.g., the
"> 2" heuristic occasionally cited from Winsteps documentation) are not
reliable indicators of multidimensionality; the first-contrast eigenvalue
under a correctly fitting unidimensional model varies systematically with
sample size, test length, and item-parameter spread. Empirical (simulated)
bounds tailored to the data structure should be used instead — see
RMdimResidualPCACutoff, and Chou & Wang (2010) for the underlying
simulation argument.
The PCA is performed on the standardized residuals
\((x - E)/\sqrt{Var}\) from the shared CML/WLE engine (CML item
parameters via psychotools, WLE person locations). The reported
eigenvalues are unrotated; rotation is appropriate for interpreting a
multidimensional solution but obscures the dominant first contrast that
dimensionality assessment is concerned with.
Item locations on the loadings plot are the per-item mean of the CML Andrich thresholds.
The variance partition follows Linacre's convention: per-item observed variance is compared to per-item expected variance under the fitted model, summed across items. Expected scores are computed from the CML item parameters and WLE person locations. WLE is finite at extreme scores, so all persons are retained (the previous MLE partition dropped extreme-score cases).
Bootstrap p-value. When p_value = TRUE, the observed
first-contrast eigenvalue is compared against the simulated null
distribution of largest eigenvalues (from cutoff$results), giving the
one-sided Monte-Carlo p-value (1 + #\{lambda* >= lambda\}) / (B + 1).
Because the maximum eigenvalue is a single family-wise statistic, no
multiplicity correction applies. The p-value is model-conditional and
sample-size-sensitive; it is reported alongside the simulated cutoff, not
in place of it, and can be no smaller than 1 / (B + 1).
References
Chou, Y.-T., & Wang, W.-C. (2010). Checking dimensionality in item response models with principal component analysis on standardized residuals. Educational and Psychological Measurement, 70(5), 717-731. doi:10.1177/0013164410379322
Examples
# \donttest{
set.seed(1)
dat <- as.data.frame(
matrix(sample(0:1, 200 * 12, replace = TRUE), nrow = 200, ncol = 12)
)
colnames(dat) <- paste0("I", 1:12)
# Default kable output
RMdimResidualPCA(dat)
#>
#>
#> Table: Rasch model (12 items), n = 200 respondents. Total observed variance: 8.3% explained by measures, 91.7% unexplained.
#>
#> |Component | Eigenvalue| Proportion of variance|
#> |:---------|----------:|----------------------:|
#> |PC1 | 1.431| 0.121|
#> |PC2 | 1.407| 0.119|
#> |PC3 | 1.254| 0.106|
#> |PC4 | 1.132| 0.096|
#> |PC5 | 1.105| 0.093|
# PC1 loadings vs item location plot
if (requireNamespace("ggplot2", quietly = TRUE) &&
requireNamespace("ggrepel", quietly = TRUE)) {
RMdimResidualPCA(dat, output = "ggplot")
}
# Simulation-based cutoff (use 250+ iterations in real analyses)
bound <- RMdimResidualPCACutoff(dat, iterations = 50, parallel = FALSE, seed = 1)
RMdimResidualPCA(dat, cutoff = bound)
#>
#>
#> Table: Rasch model (12 items), n = 200 respondents. Total observed variance: 8.3% explained by measures, 91.7% unexplained. First-contrast cutoff = 1.623 based on 50 simulation iterations (99th percentile).
#>
#> |Component | Eigenvalue| Proportion of variance|Flagged |
#> |:---------|----------:|----------------------:|:-------|
#> |PC1 | 1.431| 0.121|FALSE |
#> |PC2 | 1.407| 0.119|FALSE |
#> |PC3 | 1.254| 0.106|FALSE |
#> |PC4 | 1.132| 0.096|FALSE |
#> |PC5 | 1.105| 0.093|FALSE |
# With the one-sided bootstrap p-value for the first contrast
RMdimResidualPCA(dat, cutoff = bound, p_value = TRUE)
#>
#>
#> Table: Rasch model (12 items), n = 200 respondents. Total observed variance: 8.3% explained by measures, 91.7% unexplained. First-contrast cutoff = 1.623 based on 50 simulation iterations (99th percentile). One-sided bootstrap p-value for the first contrast (single test, no multiplicity correction); it cannot be smaller than 1/(50+1) = 0.0196.
#>
#> |Component | Eigenvalue| Proportion of variance|Flagged | p|
#> |:---------|----------:|----------------------:|:-------|------:|
#> |PC1 | 1.431| 0.121|FALSE | 0.7451|
#> |PC2 | 1.407| 0.119|FALSE | NA|
#> |PC3 | 1.254| 0.106|FALSE | NA|
#> |PC4 | 1.132| 0.096|FALSE | NA|
#> |PC5 | 1.105| 0.093|FALSE | NA|
# }