grmforest: Fit a Forest of Graded Response Model Trees for...

View source: R/grmforest.R

grmforestR Documentation

Fit a Forest of Graded Response Model Trees for Ensemble-Based DIF Detection

Description

This function implements a forest of graded response model trees (GRM Forest) using bootstrap aggregation (bagging) or random subsampling to enhance the detection and analysis of differential item functioning (DIF) in polytomous items. The GRM Forest approach combines the strengths of multiple GRMTrees to provide more robust and stable DIF detection, particularly for complex datasets with high-dimensional covariates or subtle DIF patterns.

Usage

grmforest(
  formula,
  data,
  control = grmforest.control(),
  tree_fun = grmtree,
  tree_args = list(),
  ...
)

Arguments

formula

A formula specifying the model structure with the response matrix on the left and partitioning variables on the right (e.g., response_matrix ~ age + gender + education + clinical_variables). The response may be a matrix column of data or an inline cbind(item1, item2,...) construction.

data

A data frame containing the response matrix and partitioning variables. The response matrix should contain polytomous items coded as ordered factors.

control

A control object created by grmforest.control().

tree_fun

The tree-fitting function grown at each resample. Defaults to grmtree() for a cross-sectional GRM forest. Supply longitudinal_grmtree() to grow a longitudinal GRM forest for response-shift screening; the forest machinery (resampling, out-of-bag scoring, variable importance, parallelism) is identical for both.

tree_args

A named list of extra arguments passed on to tree_fun. For a longitudinal forest this is where the items-per-occasion count is supplied, e.g. tree_args = list(n_items = 8).

...

Additional arguments passed to the tree-fitting function (tree_fun).

Details

Each tree is grown by grmtree() on a resample of the rows of data. Rows not selected form that tree's out-of-bag (OOB) sample, which is used by varimp() and never enters that tree's fitting.

Setting mtry in grmforest.control() offers a fresh random subset of partitioning variables at every node, decorrelating the trees. Leaving mtry = NULL grows a bagged ensemble in which every tree sees every variable at every node; such trees are highly correlated, which limits the variance reduction the ensemble can deliver and can make importance scores harder to interpret when partitioning variables are themselves correlated.

Out-of-bag membership is stored as row indices rather than as copies of the data, which keeps the fitted object small and lets forests grown separately be combined with c.grmforest().

Key advantages of the GRM Forest approach include:

  • Enhanced stability in DIF detection across different sampling variations

  • Robust variable importance measures that quantify the relative contribution of each covariate to DIF patterns

  • Reduced false positive rates through consensus-based detection

  • Ability to handle high-dimensional covariate spaces effectively

  • Internal validation through out-of-bag error estimation

The forest implementation supports both bootstrap aggregation (where samples are drawn with replacement) and subsampling (without replacement), allowing flexibility for different data characteristics and research objectives.

Value

An object of class grmforest, a list with components:

trees

List of fitted grmtree objects.

oob_indices

List of integer vectors giving, for each tree, the row positions of data held out of that tree's resample.

in_indices

List of integer vectors giving the rows used to fit each tree.

formula

The model formula.

data

The original data frame.

control

The control object used.

call

The matched call.

Computational cost

Model-based recursive partitioning selects each split point by refitting the node model at every admissible cut point on the selected variable. For continuous covariates with many distinct values this dominates run time, and cost grows with the number of items and response categories. Practical levers, roughly in order of effect: bin continuous partitioning variables to a manageable number of candidate cut points before fitting; raise minbucket; set mtry; and increase n_cores.

See Also

grmtree fits a Graded Response Model Tree, grmtree.control creates a control object for grmtree, grmforest.control creates a control object for grmforest, c.grmforest combines grmforest objects, varimp calculates the variable importance for GRM Forest, plot.varimp creates a bar plot of variable importance scores

Examples


library(grmtree)
library(hlt)
  data("asti", package = "hlt")
  asti$resp <- data.matrix(asti[, 1:4])

  # Fit forest with default parameters
  forest <- grmforest(resp ~ gender + group, data = asti)

  # Fit with custom control
  ctrl <- grmforest.control(n_tree = 20, sampling = "subsample")
  forest <- grmforest(resp ~ gender + group, data = asti, control = ctrl)

#' ## Longitudinal GRM forest: same engine, longitudinal tree
  data("grmtree_long_data", package = "grmtree")
  items_t1 <- c("MOS_Listen", "MOS_Info", "MOS_Advice_Crisis", "MOS_Confide",
                "MOS_Advice_Want", "MOS_Fears", "MOS_Personal", "MOS_Understand")
  ld <- prepare_longitudinal_data(
    data = grmtree_long_data, items_t1 = items_t1,
    items_t2 = paste0(items_t1, "_year1"),
    covariates = c("sex", "age", "residency", "job",
                   "education", "comorbidity_count", "ever_smoker"))

  lforest <- grmforest(
    resp_wide ~ sex + age + residency + job +
      education + comorbidity_count + ever_smoker,
    data = ld,
    control = grmforest.control(n_tree = 20, seed = 123,
                control = grmtree.control(minbucket = 200)),
    tree_fun = longitudinal_grmtree, tree_args = list(n_items = 8))
  print(lforest)


grmtree documentation built on Sept. 2, 2026, 1:07 a.m.