RMdifGamma: Partial Gamma DIF Analysis

View source: R/dif_partgam.R

RMdifGammaR Documentation

Partial Gamma DIF Analysis

Description

Computes partial gamma coefficients for Differential Item Functioning (DIF) using iarm::partgam_DIF(). Each item is tested for association with a single categorical DIF variable, controlling for the total score.

Usage

RMdifGamma(
  data,
  dif_var,
  cutoff = NULL,
  p_value = NULL,
  correction = c("fwer", "fdr_bh", "fdr_by", "none"),
  alpha = 0.05,
  output = "kable"
)

Arguments

data

A data.frame or matrix of item responses. Items must be scored starting at 0 (non-negative integers). Missing values (NA) are allowed, but at least one complete case must exist after combining data and dif_var.

dif_var

A vector (factor or character) of the same length as nrow(data), representing the grouping variable for DIF analysis.

cutoff

Optional. Default NULL (no cutoff applied). Can be:

  • The return value of RMdifGammaCutoff (a list with ⁠$item_cutoffs⁠): the data.frame is extracted automatically and simulation metadata is included in the kable caption.

  • The ⁠$item_cutoffs⁠ data.frame from RMdifGammaCutoff directly: must have columns Item, gamma_low, gamma_high. When provided, adds columns Gamma_low, Gamma_high, and Flagged (logical; TRUE when the observed partial gamma falls outside the credible range) to the result.

p_value

Logical or NULL. When TRUE, adds two-sided bootstrap p-values (p_gamma, padj_gamma) comparing each item's observed partial gamma against its simulated null distribution, and flagged reflects padj_gamma < alpha instead of the credible range. The asymptotic BH-adjusted p-value and star columns from iarm::partgam_DIF() are dropped in this mode (two p-value families in one table would invite double-reading); the simulated gamma_low / gamma_high band is kept as the effect-size reference. TRUE requires the full RMdifGammaCutoff object as cutoff (it carries the simulated distributions in ⁠$results⁠). NULL, the default, means TRUE when cutoff is that full object and FALSE otherwise, so calling with no cutoff keeps the asymptotic table unchanged. Pass FALSE to flag against the interval instead. The interval makes a poor decision rule, since its width sets a family-wise error rate of 1 - width^k over all k items at once (Johansson, 2026).

correction

Character. Multiplicity correction for the bootstrap p-values: "fwer" (default; Westfall-Young studentised-max step-down), "fdr_bh", "fdr_by", or "none". Ignored when p_value = FALSE.

alpha

Numeric in (0, 1). Significance level used to flag items on the corrected p-value. Default 0.05. Ignored when p_value = FALSE.

output

Character string controlling the return value. Either "kable" (default) for a formatted knitr::kable() table, or "dataframe" for the underlying data.frame.

Details

Partial gamma (Bjorner et al., 1998) measures the association between item response and an exogenous grouping variable, controlling for the total score. Values near 0 indicate no DIF. Recommended interpretive thresholds (Bjorner et al., 1998):

  • No or negligible DIF: gamma within [-0.21, 0.21], or gamma not significantly different from 0.

  • Slight to moderate DIF: gamma within [-0.31, 0.31] (and outside [-0.21, 0.21]), or not significantly outside [-0.21, 0.21].

  • Moderate to large DIF: gamma outside [-0.31, 0.31], and significantly outside [-0.21, 0.21].

The iarm package must be installed (it is in Suggests, not Imports). The asymptotic adjusted p-value is a Benjamini-Hochberg adjustment over the items of the p-values from iarm::partgam_DIF(), applied by easyRasch2. iarm's own adjusted column is a Bonferroni correction whatever method is named, because iarm adjusts one p-value at a time, so it is not used.

Bootstrap p-values. When p_value = TRUE, each item's observed partial gamma is compared against its simulated null distribution (from cutoff$results, simulated with no DIF by construction). The per-item statistic is the residual studentised by the bootstrap mean and SD; the marginal p-value is the two-sided Monte-Carlo p-value ⁠(1 + #\{|t*| >= |t|\}) / (B + 1)⁠, so it can be no smaller than 1 / (B + 1). The test is two-sided because DIF has no privileged direction: an item can favour either group. This differs from RMlocdepGamma(), which tests one-sided for excess positive local dependence. correction = "fwer" uses the Westfall-Young studentised-max step-down, which exploits the bootstrap dependence among items (Ferreira, 2024); it is mildly liberal below 400 iterations in RMdifGammaCutoff() (a message is issued below that). Unlike the asymptotic p-values from iarm::partgam_DIF(), these are calibrated against the simulated Rasch null rather than the asymptotic SE; they are model-conditional and sample-size-sensitive, and are reported alongside the simulated effect-size band, not in place of it. The observed partial gamma they test is computed by the same internal routine as the simulated values, which reproduces iarm::partgam_DIF() exactly.

Value

  • If output = "kable": a knitr_kable object with columns "Item", "Partial gamma", "SE", "Lower CI", "Upper CI", "Adj. p-value (BH)", and "p-value sign." (stars for the BH-adjusted p-value, as iarm shows them). When cutoff is provided, additional columns "Gamma low", "Gamma high", and "Flagged" are included. With p_value = TRUE, the asymptotic p-value columns are replaced by bootstrap "p" and "p (adj)".

  • If output = "dataframe": a data.frame with columns Item, gamma, se, lower, upper, padj_bh, Significance. When cutoff is provided, columns gamma_low, gamma_high, and flagged are also included. With p_value = TRUE, padj_bh and Significance are replaced by p_gamma and padj_gamma.

Multiple comparisons

The marginal p-value controls the error rate of a single comparison: for one item (or item pair) decided on in advance it is the relevant value. But scanning all k comparisons and flagging whichever fall below alpha tests k hypotheses at once, so the chance of at least one false flag inflates to roughly 1 - (1 - \alpha)^k (e.g. about 34% for k = 8 at alpha = 0.05) – even when every marginal p-value is correctly calibrated. The corrected (adjusted) p-value controls this: correction = "fwer" bounds the probability of any false flag (strict, lower power), while "fdr_bh" / "fdr_by" bound the expected proportion of false flags among those raised (a more lenient middle ground). Rule of thumb: use the marginal p-value for a single pre-specified comparison, and a corrected p-value when screening the whole table – the usual workflow.

References

Bjorner, J. B., Kreiner, S., Ware, J. E., Damsgaard, M. T., & Bech, P. (1998). Differential item functioning in the Danish translation of the SF-36. Journal of Clinical Epidemiology, 51(11), 1189–1202. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1016/S0895-4356(98)00111-5")}

Ferreira, J. A. (2024). Methods of testing a 'small' or 'moderate' number of hypotheses simultaneously. Journal of Statistical Theory and Practice, 19(6). \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1007/s42519-024-00412-4")}

Westfall, P. H., & Young, S. S. (1993). Resampling-Based Multiple Testing. Wiley.

Johansson, M. (2026). Simulation-based cutoffs for conditional item fit in Rasch models: Iterations, multiplicity correction, and decision stability. PsyArXiv. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.31234/osf.io/7pqz4_v2")}

See Also

RMdifGammaCutoff

Examples


if (requireNamespace("iarm", quietly = TRUE)) {
  set.seed(42)
  sim_data <- as.data.frame(
    matrix(sample(0:1, 200 * 10, replace = TRUE), nrow = 200, ncol = 10)
  )
  colnames(sim_data) <- paste0("Item", 1:10)
  dif_group <- factor(sample(c("A", "B"), 200, replace = TRUE))

  # Default kable output
  RMdifGamma(sim_data, dif_group)

  # Return as data.frame
  RMdifGamma(sim_data, dif_group, output = "dataframe")

  # Simulation-based cutoffs (100 Monte-Carlo iterations)
  if (requireNamespace("ggdist", quietly = TRUE)) {
    cutoff_res <- RMdifGammaCutoff(sim_data, dif_var = dif_group,
                                   iterations = 100, parallel = FALSE,
                                   seed = 42)
    RMdifGamma(sim_data, dif_group, cutoff = cutoff_res)

    # The full cutoff object switches on bootstrap p-values with the
    # family-wise (Westfall-Young) correction; 100 iterations keeps the
    # example quick, use the default 400 or more in real analyses
    RMdifGamma(sim_data, dif_group, cutoff = cutoff_res,
               output = "dataframe")
  }
}


easyRasch2 documentation built on Oct. 6, 2026, 5:06 p.m.