| Type: | Package |
| Title: | Specification Search for Partial Factorial Invariance |
| Version: | 0.1.6 |
| Description: | Automate specification search for identifying noninvariant items in factorial invariance analyses, as described in Yoon & Millsap (2007) <doi:10.1080/10705510701301677>. Models are specified in 'lavaan' syntax. Supports continuous indicators with loadings, intercepts, residuals (uniqueness), and residual covariances invariance, and ordered categorical indicators with loadings, thresholds, and residual covariances invariance. |
| License: | GPL-3 |
| Encoding: | UTF-8 |
| Imports: | lavaan, stats |
| Suggests: | boot, difR, knitr, MASS, rmarkdown, testthat (≥ 3.0.0), withr |
| URL: | https://github.com/marklhc/pinsearch, https://marklhc.github.io/pinsearch/ |
| BugReports: | https://github.com/marklhc/pinsearch/issues |
| Config/testthat/edition: | 3 |
| VignetteBuilder: | knitr |
| Depends: | R (≥ 3.5.0) |
| LazyData: | true |
| Config/roxygen2/version: | 8.1.0 |
| NeedsCompilation: | no |
| Packaged: | 2026-08-26 13:56:43 UTC; marklai |
| Author: | Mark Lai |
| Maintainer: | Mark Lai <marklhc@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-09-08 13:40:07 UTC |
Compute dMACS effect size described in Nye & Drasgow (2011) for two groups.
Description
dmacs returns the dMACS effect size statistics given a set of loadings
and intercepts.
Usage
dmacs(
intercepts,
loadings = NULL,
pooled_item_sd = NULL,
latent_mean = 0,
latent_sd = 1,
uniqueness = NULL,
ns = NULL,
item_weights = NULL
)
dmacs_ordered(
thresholds,
loadings,
thetas = 1,
link = c("probit", "logit"),
pooled_item_sd = NULL,
latent_mean = 0,
latent_sd = 1,
item_weights = NULL
)
Arguments
intercepts |
A |
loadings |
A |
pooled_item_sd |
A numeric vector of length p of the pooled standard deviation (SD) of the items across groups. |
latent_mean |
latent factor mean for the reference group. Default to 0. |
latent_sd |
latent factor SD for the reference group. Default to 1. |
uniqueness |
A vector of length |
ns |
A vector of length |
item_weights |
Default is |
thresholds |
A matrix with two rows for measurement thresholds. The matrix must have column names indicating to which item index each column corresponds. |
thetas |
Not currently used. |
link |
Link function for the model (probit or logit). |
Details
The d_\text{MACS} effect size is defined as
(Nye & Drasgow, 2011, p. 968)
d_{\text{MACS}, i} = \frac{1}{\mathit{SD}_{iP}}
\sqrt{\int [(\nu_{iR} - \nu{iF}) +
(\lambda_{iR} - \lambda_{iF}) \eta]^2 f(\eta) d \eta}
where \lambda is the loading and \nu is the intercept, F and R
denote the focal and the reference group. The effect size reflects the
standardized mean difference on an item due to measurement noninvariance,
and is analogous to the Cohen's d effect size.
Value
A 1 x p matrix of dMACS effect size. If item_weights is not
NULL, p = 1.
References
Nye, C. & Drasgow, F. (2011). Effect size indices for analyses of measurement equivalence: Understanding the practical importance of differences between groups. Journal of Applied Psychology, 96(5), 966-980.
Examples
lambdaf <- c(.8, .5, .7, .5)
lambdar <- c(.8, .5, .4, .6)
nuf <- c(0.1, 0, 0.2, 0)
nur <- c(0.2, 0, 0, 0)
dmacs(rbind(nuf, nur),
loadings = rbind(lambdaf, lambdar),
pooled_item_sd = c(1, 1, 1, 1),
latent_mean = 0,
latent_sd = 1)
dmacs(rbind(nuf, nur),
loadings = rbind(lambdaf, lambdar),
pooled_item_sd = c(1, 1, 1, 1),
latent_mean = 0,
latent_sd = 1,
item_weights = c(1, 1, 1, 1))
# Thresholds
lambda <- rbind(c(.8, .5, .7, .5),
c(.8, .5, .4, .6))
tau <- rbind(c(-0.5, 0, 1, -0.3, 0.1, 0.5, -0.5, 1.5),
c(-0.5, 0, 1, -0.5, 0.3, 0.5, -1, 1.5))
# three thresholds for items 1 and 2; one threshold for items 3 and 4
colnames(tau) <- c(1, 1, 1, 2, 2, 2, 3, 4)
dmacs_ordered(tau,
loadings = lambda,
pooled_item_sd = c(1, 1, 1, 1),
latent_mean = 0,
latent_sd = 1)
Item-level effect size for non-invariance
Description
For two groups, the function uses dmacs() to compute
d_\text{MACS}. For more than two groups, the function
uses fmacs() to compute f_\text{MACS}, a
generalisation of d_\text{MACS} similar to the
Cohen's f effect size.
Usage
es_lavaan(object, ...)
pin_effsize(object, ...)
Arguments
object |
A CFA model of class |
... |
Value
A matrix of 1 row showing the effect size values for each non-invariant item on each latent variable.
Adjusted alpha level for multiple testing.
Description
Computes the adjusted significance level at step i of m
tests in the sequential selection procedure of Benjamini &
Gavrilov (2009), as used by pinSearch() when control_fdr = TRUE:
Usage
fdr_alpha(i, m, q = 0.05)
Arguments
i |
Step index (rank) of the current test in the sequential search. |
m |
Total number of tests (maximum number of constraints that can be freed). |
q |
Target false discovery rate. Default is .05. |
Details
\frac{iq}{m + 1 - i(1 - q)}
where i is the step index in the search, m is the
maximum number of constraints that can be freed, and q is the
target significance level.
Note that i is defined for 1 <= i <= m, with
m >= 1 and 0 < q < 1. These bounds are maintained by
the internal logic of pinSearch(), but external callers must
ensure them on their own.
Value
A numeric scalar, the adjusted alpha level at step i.
References
Benjamini, Y. & Gavrilov, N. M. (2009). Sequential selection procedures for testing dependent hypotheses.
Examples
fdr_alpha(1, 10)
Compute fMACS effect size for two or more groups.
Description
fmacs returns the fMACS effect size statistics given a set of loadings
and intercepts.
Usage
fmacs(
intercepts,
loadings = NULL,
pooled_item_sd,
num_obs = NULL,
weights = 0 * intercepts + 1,
group_factor = NULL,
contrast = contr.sum(nrow(intercepts)),
latent_mean = 0,
latent_sd = 1,
item_weights = NULL
)
fmacs_ordered(
thresholds,
loadings,
thetas = 1,
num_obs = NULL,
weights = 0 * loadings + 1,
group_factor = NULL,
contrast = contr.sum(nrow(thresholds)),
link = c("probit", "logit"),
pooled_item_sd = NULL,
latent_mean = 0,
latent_sd = 1,
item_weights = NULL
)
Arguments
intercepts |
A |
loadings |
A |
pooled_item_sd |
A numeric vector of length p of the pooled standard deviation (SD) of the items across groups. |
num_obs |
A vector of length |
weights |
A |
group_factor |
A vector of length |
contrast |
A |
latent_mean |
latent factor mean for the reference group. Default to 0. |
latent_sd |
latent factor SD for the reference group. Default to 1. |
item_weights |
Default is |
thresholds |
A matrix with G rows for measurement thresholds. The matrix must have column names indicating to which item index each column corresponds. |
thetas |
Not currently used. |
link |
Link function for the model (probit or logit). |
Details
The f_\text{MACS} effect size is defined as
f_{\text{MACS}, i} = \frac{1}{\mathit{SD}_{iP}}
\sqrt{\int [(\nu_{ij} - \bar{\nu}_j) +
(\lambda_{ij} - \bar{\lambda}_j) \eta]^2 f(\eta) d \eta}
where \lambda is the loading and \nu is the intercept,
and j indexes group. The effect size reflects the square root of
the ratio between the variance in observed item score due to
measurement noninvariance and the variance of the observed item scores.
f_\text{MACS} is analogous to the Cohen's f effect size. When there
are two groups with equal sample sizes, f_\text{MACS} =
f_\text{MACS} / 2
Value
A 1 x p matrix of fMACS effect size.
Examples
lambda <- rbind(c(.7, .8, .7, .9),
c(.7, .8, .7, .8),
c(.8, .7, .7, .5))
nu <- rbind(c(0, .5, 0, 1),
c(0, .2, 0, 1.1),
c(0, .3, 0, 1.2))
fmacs(nu,
loadings = lambda,
pooled_item_sd = c(1, 1, 1, 1),
latent_mean = 0,
latent_sd = 1)
# With contrast (Group 1 & 2 vs. Group 3)
fmacs(nu,
loadings = lambda,
pooled_item_sd = c(1, 1, 1, 1),
group_factor = c(1, 1, 2),
latent_mean = 0,
latent_sd = 1)
# Thresholds
lambda <- rbind(c(.8, .5, .7, .5),
c(.8, .5, .4, .6),
c(.8, .7, .7, .5))
tau <- rbind(c(-0.5, 0, 1, -0.3, 0.1, 0.5, -0.5, 1.5),
c(-0.5, 0, 1, -0.5, 0.3, 0.5, -1, 1.5),
c(-0.5, 0, 1, -0.5, 0.3, 0.5, -1, 0.5))
# three thresholds for items 1 and 2; one threshold for items 3 and 4
colnames(tau) <- c(1, 1, 1, 2, 2, 2, 3, 4)
fmacs_ordered(tau,
loadings = lambda,
pooled_item_sd = c(1, 1, 1, 1),
latent_mean = 0,
latent_sd = 1)
# With contrast (Group 1 & 2 vs. Group 3)
fmacs_ordered(tau,
loadings = lambda,
pooled_item_sd = c(1, 1, 1, 1),
group_factor = c(1, 2, 1),
latent_mean = 0,
latent_sd = 1)
Simulated data with 15 items of an Alcohol Belief Scale
Description
Simulation based on fitting a configural invariance model to data made public by Lui (2019, https://psycnet.apa.org/record/2019-08221-001). The original data can be found at https://osf.io/wxjsg.
Usage
lui_sim
Format
lui_sim
A data frame with 910 rows and 16 columns:
- class1 to class15
Binary items
- group
six groups: 1 = White Male, 2 = Asian Male, 3 = Hispanic Male, 4 = White Female, 5 = Asian Female, 6 = Hispanic Female
Source
Search for noninvariant parameters across groups.
Description
The function implements the sequential selection method similar to that discussed in Yoon and Millsap (2007) doi:10.1080/10705510701301677. The function proceeds in the order of metric, scalar (threshold), and strict invariance. In each stage, invariance constraints in all items, and the constraint associated with the biggest test statistic above a predefined threshold is freed, before recomputing the test statistic for the next constraint to free.
Usage
pinSearch(
config_mod,
...,
type = c("loadings", "intercepts", "thresholds", "residuals", "residual.covariances"),
inv_test = c("mod", "score", "lrt"),
sig_level = 0.05,
control_fdr = FALSE,
min2 = FALSE,
effect_size = FALSE,
progress = FALSE
)
Arguments
config_mod |
Syntax of a configural invariance model to be passed to
|
... |
Additonal arguments passed to |
type |
Character variable indicating the stage of invariance to be searched. Currently supported options are (a) for continuous indicators, "loadings", "intercepts", "residuals", and "residual.covariances", and (b) "loadings", "thresholds", "residual.covariances", in an increasingly strict order. A stricter model (e.g., "residual.covariances") will have constraints of all previous stages. |
inv_test |
Character variable indicating the statistical test to be
used for specification search. Currently supported options are |
sig_level |
Significance level used to determine whether the parameter associated with the highest modification index should be removed. Default is .05. |
control_fdr |
Logical; whether to use adjust for false discovery rate
for multiple testing. If |
min2 |
Logical; whether to require at least two invariant items when searching for noninvariance. |
effect_size |
Logical; whether to compute dmacs (two groups) or fmacs (> two groups) effect size or not (default). This is an experimental feature. |
progress |
Logical; an experimental feature of showing a progress bar
if |
Details
Note that when an item has a non-invariant loading, the corresponding intercept constraint will automatically be freed, as intercept difference across groups is sensitive to the location of the zero point for the latent variable and the item.
For a particular stage of invariance constraints, the Benjamini &
Gavrilov method uses an adjusted alpha level of
iq / [m + 1 - i(1 - q)]
where i is the step index in the search, m is the
maximum number of constraints that can be freed, and q is
the desirable significance level.
Under lavaan >= 0.7, ordinal (categorical) models in which
thresholds are tied across groups can yield a degenerate stage
test (a likelihood ratio test with Df diff = 0). pinSearch()
identifies the threshold stage (and any later stage, but not the
configural or loadings stages) with the item intercepts fixed at
0 and the latent mean fixed in the first group only (free in the
remaining groups), so threshold invariance remains testable,
including for 2-group binary data. Any other degenerate stage
test falls back to the per-parameter test, so results for
ordinary multi-category and 3-group ordinal data are unchanged;
continuous models are not affected.
Value
A list of three elements:
-
Partial Invariance Fit: Alavaan::lavaanobject containing the final partial invariance model. -
Non-Invariant Items: A data frame of non-invariant parameters. -
effect_size: Effect size statistics obtained frompin_effsize().
References
Yoon, M., & Millsap, R. E. (2007). Detecting violations of factorial invariance using data-based specification searches: A Monte Carlo study. Structural Equation Modeling: A Multidisciplinary Journal, 14(3), 435-463.
Examples
library(lavaan)
# Simulate random data
set.seed(14) # for reproducible results
mod_ninv <- "f =~ c(1, 0.8, .6)*y1 + c(0.8, 1.2, 1.0)*y2"
mod_inv <- paste0("1*y", 3:6, collapse = " + ")
sim_mod <- paste(
paste(mod_ninv, "+", mod_inv),
"f ~~ c(1, 1.3, 1.5)*f
f ~ c(0, 0.5, 1.0)*1
y1 ~ c(0, 0.3, 0)*1
y3 ~ c(0.3, 0, -0.3)*1
y1 ~~ c(1, .5, 1)*y1",
sep = "\n"
)
# The uniqueness of each item is assumed to be 1.0
dat_sim <- simulateData(sim_mod, sample.nobs = c(100, 100, 100))
# Fit configural model:
sam_config <- paste(
paste0("f =~ ", paste0("y", 1:6, collapse = " + "))
)
pinSearch(sam_config,
data = dat_sim, group = "group",
type = "intercepts"
)
Map invariance types to lavaan parTable operators.
Description
Maps the invariance type strings used by pinSearch() to the
corresponding values of the op column of a lavaan parameter
table (lavaan::parTable()): "loadings" maps to "=~",
"intercepts" to "~1", "thresholds" to "|", and both
"residuals" and "residual.covariances" to "~~".
Usage
type2op(type)
Arguments
type |
A character string indicating the invariance type. Must
be one of |
Value
A character string, the corresponding lavaan operator.
Examples
type2op("loadings")