The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Intersectional Differential Item Functioning Analysis
iDIFr is an R package for detecting Differential Item
Functioning (DIF) using Logistic Regression, IRT Likelihood Ratio Tests,
and model-based recursive partitioning (MOB) — with first-class support
for intersectional group designs and built-in
Intersectional Contrast Analysis (ICA).
Most DIF packages focus on two-group comparisons along a single
demographic dimension. iDIFr is built around the idea that
test-takers belong to multiple groups simultaneously, and that DIF
sometimes only appears at the intersection of those
identities.
Key features:
~ gender * nationality * age_bandica = TRUE classifies
each item as amplified, pure intersection,
obscured, or none by comparing single-variable and
intersectional analysescheck_groups() and merge_groups() help you
manage sparse intersectional cellstidy() returns a flat
data frame for use with dplyr and ggplot2# From CRAN
install.packages("iDIFr")
# Development version from GitHub
# install.packages("remotes")
remotes::install_github("thmsrgrs/iDIFr")library(iDIFr)
# 1. Check your group structure first
check_groups(my_data, group = ~ gender * nationality * age_band)
# 2. Run DIF analysis — method selection is required
result <- idifr(
data = my_data,
items = 1:20,
group = ~ gender * nationality * age_band,
method = c("LR", "LRT")
)
# 3. Explore results
print(result) # Flagged items with effect sizes
summary(result) # Full breakdown by method + concordance
plot(result) # Effect size heatmap
plot(result, type = "concordance") # Method agreement
tidy(result) # Flat data frame
tidy(result, table = "direction") # Group-level direction table| Argument | Method | Effect size | Best for |
|---|---|---|---|
"LR" |
Logistic Regression | Nagelkerke ΔR² | General use, no IRT assumptions |
"LRT" |
IRT Likelihood Ratio Test | Standardised chi (df-scaled) | IRT-based programmes |
"MOB" |
Model-based recursive partitioning | Standardised score difference | Intersectional designs, exploratory |
Pass ica = TRUE to idifr() to run ICA
automatically. After the main analysis, iDIFr runs one
additional idifr() per demographic variable and classifies
each item by comparing where it was flagged:
| Classification | Meaning |
|---|---|
amplified |
Flagged in single-variable and intersectional runs |
pure_intersection |
Flagged only in the intersectional run |
obscured |
Flagged in a single-variable run but not intersectionally |
none |
Not flagged anywhere |
result <- idifr(
data = my_data,
items = 1:20,
group = ~ gender * nationality * age_band,
method = "LR",
ica = TRUE
)
print(result) # ICA section printed automatically
tidy(result, table = "ica") # Flat ICA classification tableNote: ICA runs N + 1 analyses without cross-analysis p-value correction. Interpret
pure_intersectionandobscuredfindings with caution in small samples.
iDIFr requires both statistical significance
(after p-value adjustment) and a meaningful effect size before
flagging an item. This reduces false positives in large samples.
| Method | Metric | Negligible | Moderate | Large |
|---|---|---|---|---|
| LR (uniform) | Nagelkerke ΔR² | < .035 | .035–.070 | ≥ .070 |
| LR (non-uniform) | MAPPD | < .05 | .05–.10 | ≥ .10 |
| LRT (uniform) | Std. chi (df-scaled) | < 0.10×√(df/2) | 0.10–0.20×√(df/2) | ≥ 0.20×√(df/2) |
| LRT (non-uniform) | MAPPD | < .05 | .05–.10 | ≥ .10 |
| MOB | Std. score difference | < .35 | .35–.70 | ≥ .70 |
LRT thresholds are df-adjusted following Oshima et al. (1997) to maintain equivalent sensitivity across designs with different numbers of groups. The MOB threshold of 0.35 is intentionally conservative to avoid over-detection in multigroup designs.
# Inspect cell sizes before analysis
check_groups(my_data, group = ~ gender * nationality * age_band)
# Merge sparse cells
grp <- check_groups(my_data, group = ~ gender * nationality * age_band)
merged_data <- merge_groups(
grp,
nationality = list("Other" = c("DE", "FR", "ES"))
)
# Merge multiple variables in one call
merged_data <- merge_groups(
grp,
nationality = list("Other" = c("DE", "FR")),
age_band = list("18-30" = c("18-24", "25-30"))
)
# Exclude groups below a minimum size at run time
result <- idifr(
my_data, 1:20,
group = ~ gender * nationality * age_band,
method = "LR",
exclude_below_min = TRUE,
min_cell_size = 50
)simulate_dif() generates synthetic dichotomous item
response data with known DIF structure, including intersection-only DIF
for validating iDIFr on controlled data:
# Standard DIF
dat <- simulate_dif(n_persons = 1000, n_items = 20, dif_items = c(3, 7))
# DIF confined to a single intersectional cell
dat_ix <- simulate_dif(
n_persons = 2000,
n_items = 20,
dif_items = c(5, 12),
dif_effect = 1.5,
dif_structure = "intersection",
dif_group = list(group = "G1", nationality = "UK", age_band = "Young"),
demo_vars = list(nationality = c("UK", "DE", "FR"),
age_band = c("Young", "Old")),
seed = 42
)
# Mixed DIF — some items standard, some intersectional
dat_mixed <- simulate_dif(
n_persons = 2000,
n_items = 20,
dif_items = list(standard = c(3, 7), intersection = c(12, 15)),
dif_effect = 1.0,
dif_structure = "mixed",
dif_group = list(group = "G1", nationality = "UK", age_band = "Young"),
demo_vars = list(nationality = c("UK", "DE", "FR"),
age_band = c("Young", "Old")),
seed = 42
)If you use iDIFr in published work, please cite:
Rogers, T. (2026). iDIFr: Intersectional Differential Item Functioning Analysis. R package version 1.0.1.
https://CRAN.R-project.org/package=iDIFr
Bug reports and feature requests are welcome via GitHub Issues.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.