MyNixOS website logo
Description

Intersectional Differential Item Functioning Analysis.

A toolkit for detecting Differential Item Functioning (DIF) using Logistic Regression (LR) as described in Swaminathan and Rogers (1990) <doi:10.1111/j.1745-3984.1990.tb00754.x>, the IRT Likelihood Ratio Test (LRT) following Thissen, Steinberg & Wainer (1993, ISBN:0-8058-0972-4), and model-based recursive partitioning (MOB) as implemented in 'strucchange' following Strobl, Kopf and Zeileis (2015) <doi:10.1007/s11336-013-9388-3>. Designed for both standard two-group and intersectional multi-group designs, 'iDIFr' prioritises effect size reporting alongside statistical significance, clear guidance on group construction, and interpretable output suitable for applied testing contexts. Built-in Intersectional Contrast Analysis (ICA) classifies items as amplified, pure-intersection, obscured, or none by comparing single-variable and intersectional analyses.

iDIFr

Intersectional Differential Item Functioning Analysis

R-CMD-check CRAN status

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).

Why iDIFr?

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:

  • Intersectional group support — define groups using ~ gender * nationality * age_band
  • Effect sizes as first-class outputs — results lead with Nagelkerke ΔR² and standardised chi, not just p-values
  • Three methods in one interface — LR, LRT, and MOB with consistent output
  • Built-in ICAica = TRUE classifies each item as amplified, pure intersection, obscured, or none by comparing single-variable and intersectional analyses
  • Transparent cell-size guidancecheck_groups() and merge_groups() help you manage sparse intersectional cells
  • Tidy outputtidy() returns a flat data frame for use with dplyr and ggplot2

Installation

# From CRAN
install.packages("iDIFr")

# Development version from GitHub
# install.packages("remotes")
remotes::install_github("thmsrgrs/iDIFr")

Quick start

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

Methods

ArgumentMethodEffect sizeBest for
"LR"Logistic RegressionNagelkerke ΔR²General use, no IRT assumptions
"LRT"IRT Likelihood Ratio TestStandardised chi (df-scaled)IRT-based programmes
"MOB"Model-based recursive partitioningStandardised score differenceIntersectional designs, exploratory

Intersectional Contrast Analysis (ICA)

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:

ClassificationMeaning
amplifiedFlagged in single-variable and intersectional runs
pure_intersectionFlagged only in the intersectional run
obscuredFlagged in a single-variable run but not intersectionally
noneNot 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 table

Note: ICA runs N + 1 analyses without cross-analysis p-value correction. Interpret pure_intersection and obscured findings with caution in small samples.

Effect size thresholds

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.

MethodMetricNegligibleModerateLarge
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
MOBStd. 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.

Group management

# 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
)

Simulating DIF data

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
)

Citation

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

Contributing

Bug reports and feature requests are welcome via GitHub Issues.

Metadata

Version

1.0.1

License

Unknown

Platforms (79)

    Darwin
    FreeBSD
    Genode
    GHCJS
    Linux
    MMIXware
    NetBSD
    none
    OpenBSD
    Redox
    Solaris
    uefi
    wasip1
    Windows
Show all
  • aarch64-darwin
  • aarch64-freebsd
  • aarch64-genode
  • aarch64-linux
  • aarch64-netbsd
  • aarch64-none
  • aarch64-uefi
  • aarch64-windows
  • aarch64_be-none
  • arc-linux
  • arm-none
  • armv5tel-linux
  • armv6l-linux
  • armv6l-netbsd
  • armv6l-none
  • armv7a-linux
  • armv7a-netbsd
  • armv7l-linux
  • armv7l-netbsd
  • avr-none
  • i686-cygwin
  • i686-freebsd
  • i686-genode
  • i686-linux
  • i686-netbsd
  • i686-none
  • i686-openbsd
  • i686-windows
  • javascript-ghcjs
  • loongarch64-linux
  • m68k-linux
  • m68k-netbsd
  • m68k-none
  • microblaze-linux
  • microblaze-none
  • microblazeel-linux
  • microblazeel-none
  • mips-linux
  • mips-none
  • mips64-linux
  • mips64-none
  • mips64el-linux
  • mipsel-linux
  • mipsel-netbsd
  • mmix-mmixware
  • msp430-none
  • or1k-none
  • powerpc-linux
  • powerpc-netbsd
  • powerpc-none
  • powerpc64-linux
  • powerpc64le-linux
  • powerpcle-none
  • riscv32-linux
  • riscv32-netbsd
  • riscv32-none
  • riscv64-linux
  • riscv64-netbsd
  • riscv64-none
  • rx-none
  • s390-linux
  • s390-none
  • s390x-linux
  • s390x-none
  • sh4-linux
  • vc4-none
  • wasm32-wasip1
  • wasm64-wasip1
  • x86_64-cygwin
  • x86_64-freebsd
  • x86_64-genode
  • x86_64-linux
  • x86_64-netbsd
  • x86_64-none
  • x86_64-openbsd
  • x86_64-redox
  • x86_64-solaris
  • x86_64-uefi
  • x86_64-windows