# Getting Started with the grmtree Package

# Introduction

The `grmtree` package implements recursive partitioning for Graded Response Models (GRM), allowing researchers to test for differential item functioning (DIF) and identify heterogeneous subgroups in their data based on item response patterns and covariates. This vignette describes the implementation of the tree-based graded response model (GRMTree) to test for DIF on the sample Medical Outcomes Study Social Support Survey (MOS-SS). This vignette demonstrates:

- Data preparation and exploration

- Unidimensionality checking

- GRM fitting

- GRMTree construction and interpretation

# Install and Load required packages

To implement the tree-based GRM (GRMTree), you will install the following packages if not previously installed.

    ## Install packages from CRAN repository
    install.packages(c("dplyr", "grmtree", "mirt", "psych"))

Once installed, load the packages as follows:

    library(dplyr)        # For data manipulation
    library(grmtree)      # For tree-based GRM DIF Test
    library(mirt)         # For traditional GRM
    library(psych)        # For psychometrics

# Import and explore the data

The data set used in this demonstration is a test/sample data for the package.

Now, let’s load this tidy data set into R.

    ## Load the data
    data("grmtree_data", package = "grmtree")

    ## Take a glimpse at the data
    glimpse(grmtree_data)
    #> Rows: 3,500
    #> Columns: 17
    #> $ MOS_Listen        <chr> "5", "4", "5", "3", "5", "2", "5", "5", "2", "3", "3…
    #> $ MOS_Info          <chr> "5", "3", "5", "3", "5", "3", "5", "5", "2", "4", "3…
    #> $ MOS_Advice_Crisis <chr> "5", "3", "5", "3", "5", "5", "5", "5", "4", "4", "3…
    #> $ MOS_Confide       <chr> "5", "4", "4", "2", "4", "4", "5", "5", "2", "3", "3…
    #> $ MOS_Advice_Want   <chr> "5", "2", "4", "3", "4", "3", "5", "5", "4", "3", "3…
    #> $ MOS_Fears         <chr> "5", "2", "5", "4", "5", "4", "5", "5", "1", "2", "1…
    #> $ MOS_Personal      <chr> "5", "2", "5", "1", "5", "2", "5", "5", "4", "2", "3…
    #> $ MOS_Understand    <chr> "5", "2", "5", "1", "4", "3", "5", "5", "4", "2", "3…
    #> $ sex               <chr> "Male", "Male", "Male", "Male", "Male", "Female", "M…
    #> $ age               <dbl> 69, 66, 72, 52, 61, 57, 61, 71, 77, 65, 55, 64, 45, …
    #> $ residency         <chr> "urban", "urban", "urban", "urban", "urban", "rural"…
    #> $ depressed         <chr> "No", "No", "No", "Yes", "No", "No", "Yes", "No", "N…
    #> $ bmi               <dbl> 22.85714, 33.59375, 24.38272, 31.14187, 25.88057, 24…
    #> $ Education         <chr> "Primary/High school", "College/University", "Colleg…
    #> $ job               <chr> "Unemployed", "Unemployed", "Unemployed", "Unemploye…
    #> $ smoker            <chr> "No", "No", "Yes", "No", "No", "No", "Yes", "Yes", "…
    #> $ multimorbidity    <chr> "2+", "2+", "1", "1", "2+", "2+", "2+", "1", "2+", "…

# IRT Assumptions: Check for unidimensionality assumption

There are various ways to check for the unidimensionality assumption. Some including using exploratory factor analysis (through parallel analysis), scree plot of the eigen values, and contrasts from principal component analysis.

Here, we will use the scree plot of eigen values. Reeve, et al. suggested that a ratio of first-to-second eigenvalues greater than four is evidence of unidimensionality.

    ## Create the response data (the 8 MOS-SS items)
    response_data <- grmtree_data %>%
      dplyr::select(MOS_Listen:MOS_Understand) %>%
      mutate_at(vars(starts_with("MOS")), as.ordered)

    ## Create response as outcomes
    response_data$resp <- data.matrix(response_data[, 1:8])

    ## Calculate the polychoric correlation of items
    polycorr_mos <- polychoric(response_data$resp, global=FALSE)$rho

    ## Return the eigen values
    polycorr_eigen_mos <- eigen(polycorr_mos)$values
    round(polycorr_eigen_mos, 3)

    ## Ratio of first and second eigen value
    mos_ratio <- round(round(polycorr_eigen_mos, 3)[1]/round(polycorr_eigen_mos, 3)[2],3)

    ## Print the result
    mos_ratio

    cat("The ratio of the first-to-second eigen value is", mos_ratio,
        "which is >4 suggesting that the unidimensionality assumption is satisfied", "\n")

Now, let’s create the screen plot.

    ## Scree plot of eigen values
    plot(1:length(polycorr_eigen_mos), polycorr_eigen_mos,
         type = "b", pch = 20, xlab = "", ylab = "Eigen values")

Note: The `type = "b"` argument means we desire that the plot creates both points and lines,

Based on the scree plot, we assume that the MOS-SS emotional domain is unidimensional. This implies that one dominant latent trait is being measured and that this trait is the driving force for the responses observed for each item in the measure.

Other ways to check for unidimensionality of the MOS-SS emotional domain are shown below

    ## Perform EFA with 1 factors
    efa_mos <- fa(response_data$resp, nfactors = 1, rotate = "varimax", fm = "mle")

    ## Print the results
    summary(efa_mos)
    print(efa_mos, sort=TRUE)
    efa_mos$loadings

    ## Visualizing factor structure using fa.diagram
    fa.diagram(efa_mos, main = "EFA Factor Structure")

    ## Scree plot to visualize the number of factors
    fa.parallel(response_data$resp, fa = "fa")

# Fit the graded response model (GRM)

Having established unidimensionality, let’s create the GRM.

    ## Create GRM
    mos_grm <- mirt(data = response_data$resp,
                    model = 1,
                    itemtype = "graded", SE = TRUE, method = "EM",
                    verbose = FALSE)

Note: The argument `model = 1` means that default is 1, indicating that a unidimensional model will be fitted

## Fit Indices for the GRM

    ## Get the coefficients
    mos_coef <- coef(mos_grm, IRTpars = T, simplify = TRUE)
    mos_coef
    #> $items
    #>                       a     b1     b2     b3    b4
    #> MOS_Listen        3.815 -2.012 -1.400 -0.805 0.168
    #> MOS_Info          3.140 -2.057 -1.333 -0.535 0.658
    #> MOS_Advice_Crisis 3.647 -1.875 -1.186 -0.474 0.639
    #> MOS_Confide       5.352 -1.697 -1.137 -0.562 0.281
    #> MOS_Advice_Want   4.000 -1.660 -1.079 -0.386 0.559
    #> MOS_Fears         4.508 -1.431 -0.961 -0.447 0.340
    #> MOS_Personal      5.690 -1.519 -1.000 -0.401 0.461
    #> MOS_Understand    4.915 -1.644 -1.085 -0.457 0.458
    #>
    #> $means
    #> F1
    #>  0
    #>
    #> $cov
    #>    F1
    #> F1  1

    ## Get the residuals
    residuals(mos_grm, type = "Q3")
    #> Q3 summary statistics:
    #>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
    #>  -0.334  -0.235  -0.152  -0.122  -0.064   0.358
    #>
    #>                   MOS_Listen MOS_Info MOS_Advice_Crisis MOS_Confide
    #> MOS_Listen             1.000    0.049            -0.071       0.163
    #> MOS_Info               0.049    1.000             0.358      -0.129
    #> MOS_Advice_Crisis     -0.071    0.358             1.000      -0.186
    #> MOS_Confide            0.163   -0.129            -0.186       1.000
    #> MOS_Advice_Want       -0.186   -0.052             0.049      -0.190
    #> MOS_Fears             -0.129   -0.298            -0.310      -0.068
    #> MOS_Personal          -0.301   -0.241            -0.250      -0.334
    #> MOS_Understand        -0.233   -0.163            -0.193      -0.273
    #>                   MOS_Advice_Want MOS_Fears MOS_Personal MOS_Understand
    #> MOS_Listen                 -0.186    -0.129       -0.301         -0.233
    #> MOS_Info                   -0.052    -0.298       -0.241         -0.163
    #> MOS_Advice_Crisis           0.049    -0.310       -0.250         -0.193
    #> MOS_Confide                -0.190    -0.068       -0.334         -0.273
    #> MOS_Advice_Want             1.000    -0.116       -0.161         -0.089
    #> MOS_Fears                  -0.116     1.000        0.062         -0.144
    #> MOS_Personal               -0.161     0.062        1.000          0.015
    #> MOS_Understand             -0.089    -0.144        0.015          1.000

    ## Compute the M2 model fit statistic
    M2(mos_grm, type = "C2")
    #>             M2 df p RMSEA RMSEA_5 RMSEA_95 SRMSR   TLI   CFI
    #> stats 1568.994 20 0 0.149   0.143    0.155 0.039 0.962 0.973

    ## Infit and outfit statistics
    mirt::itemfit(mos_grm, c('S_X2', 'infit'), method = 'EAP')
    #>                item outfit z.outfit infit z.infit    S_X2 df.S_X2 RMSEA.S_X2
    #> 1        MOS_Listen  0.835   -1.612 0.950  -1.526 248.617      59      0.030
    #> 2          MOS_Info  0.891   -2.890 0.996  -0.108 162.253      62      0.021
    #> 3 MOS_Advice_Crisis  0.851   -3.504 0.976  -0.770 171.474      59      0.023
    #> 4       MOS_Confide  0.692   -3.506 0.871  -4.157 133.765      48      0.023
    #> 5   MOS_Advice_Want  0.886   -2.036 0.984  -0.527 238.147      58      0.030
    #> 6         MOS_Fears  0.917   -0.679 0.942  -1.794 214.704      57      0.028
    #> 7      MOS_Personal  0.680   -2.855 0.891  -3.538 176.772      48      0.028
    #> 8    MOS_Understand  0.747   -2.740 0.914  -2.782 177.138      51      0.027
    #>   p.S_X2
    #> 1      0
    #> 2      0
    #> 3      0
    #> 4      0
    #> 5      0
    #> 6      0
    #> 7      0
    #> 8      0

    ## Could also use method = 'ML'

## Visualization: Visualize the results for all the items

    ## Plot the category response curves
    plot(mos_grm, type = 'trace')

    ## Plot the test information
    plot(mos_grm, type = 'info')

# Fit the tree-based graded response model (GRMTree)

Based on multiple fit indices including the Tucker–Lewis index (TLI) and Comparative fit index (CFI), we conclude that GRM has a good fit to the data.

Now, let’s create the GRNTree using covariates to test for DIF. To implement the GRMTree, it follows this four-step process.

GRMTree implementation process showing the four-step recursive partitioning approach for detecting differential item functioning

Before we proceed, let’s put the variables in their proper formats:

- All variables to be included in the model except `age` and `BMI` will be converted to factor since they are categorical variables and the MOS-SS emotional domain items will be converted to ordinal variables as required for the GRMTree.

    ## Prepare the data
    resp.data <- grmtree_data %>%
      mutate_at(vars(starts_with("MOS")), as.ordered) %>%
      mutate_at(vars(c(sex, residency, depressed,
                       Education, job, smoker,
                       multimorbidity)), as.factor)

    ## Explore the data
    head(resp.data)
    #> # A tibble: 6 × 17
    #>   MOS_Listen MOS_Info MOS_Advice_Crisis MOS_Confide MOS_Advice_Want MOS_Fears
    #>   <ord>      <ord>    <ord>             <ord>       <ord>           <ord>
    #> 1 5          5        5                 5           5               5
    #> 2 4          3        3                 4           2               2
    #> 3 5          5        5                 4           4               5
    #> 4 3          3        3                 2           3               4
    #> 5 5          5        5                 4           4               5
    #> 6 2          3        5                 4           3               4
    #> # ℹ 11 more variables: MOS_Personal <ord>, MOS_Understand <ord>, sex <fct>,
    #> #   age <dbl>, residency <fct>, depressed <fct>, bmi <dbl>, Education <fct>,
    #> #   job <fct>, smoker <fct>, multimorbidity <fct>

    ## Check the structure of the data
    glimpse(resp.data)
    #> Rows: 3,500
    #> Columns: 17
    #> $ MOS_Listen        <ord> 5, 4, 5, 3, 5, 2, 5, 5, 2, 3, 3, 4, 5, 2, 5, 3, 5, 4…
    #> $ MOS_Info          <ord> 5, 3, 5, 3, 5, 3, 5, 5, 2, 4, 3, 4, 5, 2, 4, 4, 4, 3…
    #> $ MOS_Advice_Crisis <ord> 5, 3, 5, 3, 5, 5, 5, 5, 4, 4, 3, 4, 2, 2, 4, 4, 4, 3…
    #> $ MOS_Confide       <ord> 5, 4, 4, 2, 4, 4, 5, 5, 2, 3, 3, 4, 5, 2, 5, 4, 5, 3…
    #> $ MOS_Advice_Want   <ord> 5, 2, 4, 3, 4, 3, 5, 5, 4, 3, 3, 4, 2, 2, 5, 4, 4, 2…
    #> $ MOS_Fears         <ord> 5, 2, 5, 4, 5, 4, 5, 5, 1, 2, 1, 4, 5, 2, 5, 4, 4, 4…
    #> $ MOS_Personal      <ord> 5, 2, 5, 1, 5, 2, 5, 5, 4, 2, 3, 4, 5, 2, 5, 4, 4, 3…
    #> $ MOS_Understand    <ord> 5, 2, 5, 1, 4, 3, 5, 5, 4, 2, 3, 4, 5, 2, 5, 4, 4, 3…
    #> $ sex               <fct> Male, Male, Male, Male, Male, Female, Male, Female, …
    #> $ age               <dbl> 69, 66, 72, 52, 61, 57, 61, 71, 77, 65, 55, 64, 45, …
    #> $ residency         <fct> urban, urban, urban, urban, urban, rural, urban, urb…
    #> $ depressed         <fct> No, No, No, Yes, No, No, Yes, No, No, No, Yes, No, N…
    #> $ bmi               <dbl> 22.85714, 33.59375, 24.38272, 31.14187, 25.88057, 24…
    #> $ Education         <fct> Primary/High school, College/University, College/Uni…
    #> $ job               <fct> Unemployed, Unemployed, Unemployed, Unemployed, Unem…
    #> $ smoker            <fct> No, No, Yes, No, No, No, Yes, Yes, No, No, No, Yes, …
    #> $ multimorbidity    <fct> 2+, 2+, 1, 1, 2+, 2+, 2+, 1, 2+, 2+, 2+, 2+, 1, 2+, …

    ## Create response as outcomes
    resp.data$resp <- data.matrix(resp.data[, 1:8])

    ## GRMTree control parameters with Benjamini-Hochberg
    grm_control <- grmtree.control(
      minbucket = 350,
      p_adjust = "BH", alpha = 0.05)

    ## Fit the GRMTree model
    mos_grmtree <- grmtree(resp ~ sex + age + bmi + Education + depressed +
                           residency + job + multimorbidity + smoker,
                           data = resp.data,
                           control = grm_control)

Note: Unlike previous tree-based item response theory models, currently, the `grmtree` package implemented other post-hoc multiple adjustments methods and also the Bonferroni correction. The implemented post-hoc multiple adjustments methods include, “bonferroni” (Bonferroni), “holm” (Holm-Bonferroni), “BH” (Benjamini-Hochberg), “BY” (Benjamini-Yekutieli), “hochberg” (Hochberg), and “hommel” (Hommel).

See `?grmtree` and `?grmtree.control` for more information on other control arguments. Also see below an example of using other multiple comparison methods with the control parameters in the function.

    ## Bonferroni correction
    tree_bonf <- grmtree(response ~ covariate1 + covariate2, data = df,
                        control = grmtree.control(p_adjust = "bonferroni"))

    ## Hommel
    tree_bh <- grmtree(response ~ covariate1 + covariate2, data = df,
                      control = grmtree.control(p_adjust = "hommel"))

    ## Holm-Bonferroni
    tree_holm <- grmtree(response ~ covariate1 + covariate2, data = df,
                        control = grmtree.control(p_adjust = "holm"))

## Print the tree

We can print the tree using the `print()` function. Note that using `print(mos_grmtree)` will print the tree with the item parameters. The code below also includes a way to print a simplified version of the tree showing only the nodes.

    ## Print the tree model
    print(mos_grmtree)
    #> Graded Response Model Tree
    #>
    #> Model formula:
    #> resp ~ sex + age + bmi + Education + depressed + residency +
    #>     job + multimorbidity + smoker
    #>
    #> Fitted party:
    #> [1] root
    #> |   [2] age <= 69
    #> |   |   [3] sex in Female: n = 371
    #> |   |       $items
    #> |   |                                 a     b1     b2     b3    b4
    #> |   |       respMOS_Listen        3.327 -2.309 -1.466 -0.792 0.389
    #> |   |       respMOS_Info          3.172 -1.920 -1.146 -0.446 0.891
    #> |   |       respMOS_Advice_Crisis 3.714 -1.701 -1.029 -0.444 0.865
    #> |   |       respMOS_Confide       4.595 -1.775 -1.259 -0.651 0.458
    #> |   |       respMOS_Advice_Want   4.130 -1.566 -1.057 -0.409 0.606
    #> |   |       respMOS_Fears         4.601 -1.378 -0.868 -0.450 0.531
    #> |   |       respMOS_Personal      5.513 -1.539 -0.972 -0.374 0.666
    #> |   |       respMOS_Understand    4.084 -1.551 -1.007 -0.337 0.662
    #> |   |
    #> |   |       $means
    #> |   |       F1
    #> |   |        0
    #> |   |
    #> |   |       $cov
    #> |   |          F1
    #> |   |       F1  1
    #> |   |
    #> |   |   [4] sex in Male: n = 1919
    #> |   |       $items
    #> |   |                                 a     b1     b2     b3    b4
    #> |   |       respMOS_Listen        3.663 -2.040 -1.389 -0.854 0.106
    #> |   |       respMOS_Info          2.868 -2.183 -1.386 -0.553 0.697
    #> |   |       respMOS_Advice_Crisis 3.443 -1.889 -1.215 -0.459 0.675
    #> |   |       respMOS_Confide       5.416 -1.716 -1.118 -0.559 0.260
    #> |   |       respMOS_Advice_Want   4.024 -1.654 -1.070 -0.391 0.580
    #> |   |       respMOS_Fears         4.480 -1.422 -0.953 -0.448 0.327
    #> |   |       respMOS_Personal      5.805 -1.507 -0.969 -0.398 0.468
    #> |   |       respMOS_Understand    5.096 -1.625 -1.036 -0.434 0.474
    #> |   |
    #> |   |       $means
    #> |   |       F1
    #> |   |        0
    #> |   |
    #> |   |       $cov
    #> |   |          F1
    #> |   |       F1  1
    #> |   |
    #> |   [5] age > 69: n = 1210
    #> |       $items
    #> |                                 a     b1     b2     b3    b4
    #> |       respMOS_Listen        4.361 -1.896 -1.397 -0.740 0.205
    #> |       respMOS_Info          3.657 -1.923 -1.322 -0.540 0.540
    #> |       respMOS_Advice_Crisis 3.999 -1.923 -1.197 -0.507 0.525
    #> |       respMOS_Confide       5.674 -1.645 -1.129 -0.539 0.266
    #> |       respMOS_Advice_Want   3.947 -1.704 -1.099 -0.371 0.511
    #> |       respMOS_Fears         4.514 -1.466 -1.005 -0.443 0.307
    #> |       respMOS_Personal      5.604 -1.534 -1.056 -0.412 0.389
    #> |       respMOS_Understand    5.054 -1.712 -1.183 -0.526 0.374
    #> |
    #> |       $means
    #> |       F1
    #> |        0
    #> |
    #> |       $cov
    #> |          F1
    #> |       F1  1
    #> |
    #>
    #> Number of inner nodes:    2
    #> Number of terminal nodes: 3
    #> Number of parameters per node: 3
    #> Objective function (negative log-likelihood): 25186.57
    print(mos_grmtree, FUN = function(x) " *")
    #> Graded Response Model Tree
    #>
    #> Model formula:
    #> resp ~ sex + age + bmi + Education + depressed + residency +
    #>     job + multimorbidity + smoker
    #>
    #> Fitted party:
    #> [1] root
    #> |   [2] age <= 69
    #> |   |   [3] sex in Female *
    #> |   |   [4] sex in Male *
    #> |   [5] age > 69 *
    #>
    #> Number of inner nodes:    2
    #> Number of terminal nodes: 3
    #> Number of parameters per node: 3
    #> Objective function (negative log-likelihood): 25186.57

## Plot the GRMTree

Now, we can plot the tree using the `plot()` function. We have implemented several plotting options including plot the distribution of the factor scores in each node overlayed by a normal curve. See `?plot` for options of plots available.

    ## Create the regions plot (by default)
    plot(mos_grmtree)

    ## This also creates a regions plot
    plot(mos_grmtree, type = "regions",  tnex = 2L)

    ## Create the histogram plot of the factor scores
    plot(mos_grmtree, type = "histogram",  tnex = 2L)
    #> Processing node: 3
    #> Processing node: 4
    #> Processing node: 5

    ## Create the profile plot with different options
    plot(mos_grmtree, type = "profile", tnex = 2L, what = "threshold")

    plot(mos_grmtree, type = "profile", tnex = 2L, what = "discrimination")

    plot(mos_grmtree, type = "profile", tnex = 2L, what = "item")

Note: The `tnex = 2L` argument is a numeric value giving the terminal node extension in relation to the inner nodes.

You may also rename a covariate in the plot. Let’s assume we want to rename the variable that splits at the root node from `age` to `Age`.

    ## Return the names of the covariates to know the position of age
    names(mos_grmtree$data)
    #>  [1] "resp"           "sex"            "age"            "bmi"
    #>  [5] "Education"      "depressed"      "residency"      "job"
    #>  [9] "multimorbidity" "smoker"

    ## Rename the age to Age (Uncomment the code below and change to the correct name)
    #names(mos_grmtree$data)[3] <- "Age"

    ## Create the regions plot (by default)
    plot(mos_grmtree)

More than one subgroup (terminal node) was identified, suggesting the presence of sample heterogeneity with respect to DIF on the MOS-SS emotional domain items. Specifically, the GRMTree identified three distinct subgroups defined by interactions among age, sex and smoking status as splitting variables. Subgroup 1 consists of female patients who are ≤69 years old, subgroup 2 consists of males who are ≤69 years, and subgroup 3 consists of patients who are > 69 years. In the figure above, the larger the sections of the bars for each response category, the more likely patients are to endorse that category. For a more detailed example on the interpretation of these types of plot, see the article below.

- Arimoro, O.I., Josephson, C.B., James, M.T. et al. Screening for depression in patients with epilepsy: same questions but different meaning to different patients. Qual Life Res 33, 3409–3419 (2024). https://doi.org/10.1007/s11136-024-03782-1.

## Extract item parameters for terminal nodes

    ## Extract and print the threshold parameters
    thresholds <- threshpar_grmtree(mos_grmtree)
    print(thresholds)
    #>    Node                  Item        b1         b2         b3        b4
    #> 1     3        respMOS_Listen -2.308581 -1.4656879 -0.7924741 0.3891970
    #> 2     3          respMOS_Info -1.920238 -1.1458453 -0.4455881 0.8912573
    #> 3     3 respMOS_Advice_Crisis -1.700618 -1.0292038 -0.4444648 0.8650516
    #> 4     3       respMOS_Confide -1.774537 -1.2588305 -0.6510803 0.4584325
    #> 5     3   respMOS_Advice_Want -1.566310 -1.0568062 -0.4091240 0.6060837
    #> 6     3         respMOS_Fears -1.377683 -0.8681593 -0.4503415 0.5313636
    #> 7     3      respMOS_Personal -1.538954 -0.9723585 -0.3744289 0.6658615
    #> 8     3    respMOS_Understand -1.551128 -1.0068864 -0.3367904 0.6619284
    #> 9     4        respMOS_Listen -2.040280 -1.3890196 -0.8544978 0.1057268
    #> 10    4          respMOS_Info -2.183446 -1.3856741 -0.5527815 0.6969514
    #> 11    4 respMOS_Advice_Crisis -1.889219 -1.2148736 -0.4590243 0.6753132
    #> 12    4       respMOS_Confide -1.715802 -1.1176107 -0.5585143 0.2596022
    #> 13    4   respMOS_Advice_Want -1.654020 -1.0700153 -0.3908090 0.5801000
    #> 14    4         respMOS_Fears -1.421533 -0.9528592 -0.4478088 0.3268731
    #> 15    4      respMOS_Personal -1.507029 -0.9686785 -0.3980860 0.4683781
    #> 16    4    respMOS_Understand -1.625419 -1.0364005 -0.4343498 0.4736214
    #> 17    5        respMOS_Listen -1.896189 -1.3971894 -0.7398447 0.2051288
    #> 18    5          respMOS_Info -1.923081 -1.3221471 -0.5396625 0.5395678
    #> 19    5 respMOS_Advice_Crisis -1.922918 -1.1967558 -0.5068723 0.5249993
    #> 20    5       respMOS_Confide -1.645498 -1.1292589 -0.5388327 0.2656753
    #> 21    5   respMOS_Advice_Want -1.703551 -1.0992689 -0.3705977 0.5110057
    #> 22    5         respMOS_Fears -1.466102 -1.0048158 -0.4430565 0.3071656
    #> 23    5      respMOS_Personal -1.533905 -1.0556259 -0.4122411 0.3888965
    #> 24    5    respMOS_Understand -1.711838 -1.1829442 -0.5260505 0.3743661

    ## Extract and print the discrimination parameters
    discriminations <- discrpar_grmtree(mos_grmtree)
    print(discriminations)
    #>    Node                  Item Discrimination
    #> 1     3        respMOS_Listen       3.327051
    #> 2     3          respMOS_Info       3.171943
    #> 3     3 respMOS_Advice_Crisis       3.714370
    #> 4     3       respMOS_Confide       4.595353
    #> 5     3   respMOS_Advice_Want       4.129881
    #> 6     3         respMOS_Fears       4.600672
    #> 7     3      respMOS_Personal       5.513398
    #> 8     3    respMOS_Understand       4.084293
    #> 9     4        respMOS_Listen       3.663307
    #> 10    4          respMOS_Info       2.868277
    #> 11    4 respMOS_Advice_Crisis       3.443248
    #> 12    4       respMOS_Confide       5.415794
    #> 13    4   respMOS_Advice_Want       4.024122
    #> 14    4         respMOS_Fears       4.479991
    #> 15    4      respMOS_Personal       5.804789
    #> 16    4    respMOS_Understand       5.096428
    #> 17    5        respMOS_Listen       4.361493
    #> 18    5          respMOS_Info       3.656771
    #> 19    5 respMOS_Advice_Crisis       3.998893
    #> 20    5       respMOS_Confide       5.673946
    #> 21    5   respMOS_Advice_Want       3.946687
    #> 22    5         respMOS_Fears       4.514427
    #> 23    5      respMOS_Personal       5.603528
    #> 24    5    respMOS_Understand       5.053900

    ## Extract and print the item parameters
    itemspars <- itempar_grmtree(mos_grmtree)
    print(itemspars)
    #>    Node                  Item Discrimination AvgThreshold
    #> 1     3        respMOS_Listen       3.327051   -1.0443865
    #> 2     3          respMOS_Info       3.171943   -0.6551036
    #> 3     3 respMOS_Advice_Crisis       3.714370   -0.5773087
    #> 4     3       respMOS_Confide       4.595353   -0.8065038
    #> 5     3   respMOS_Advice_Want       4.129881   -0.6065391
    #> 6     3         respMOS_Fears       4.600672   -0.5412052
    #> 7     3      respMOS_Personal       5.513398   -0.5549699
    #> 8     3    respMOS_Understand       4.084293   -0.5582190
    #> 9     4        respMOS_Listen       3.663307   -1.0445177
    #> 10    4          respMOS_Info       2.868277   -0.8562376
    #> 11    4 respMOS_Advice_Crisis       3.443248   -0.7219508
    #> 12    4       respMOS_Confide       5.415794   -0.7830813
    #> 13    4   respMOS_Advice_Want       4.024122   -0.6336862
    #> 14    4         respMOS_Fears       4.479991   -0.6238320
    #> 15    4      respMOS_Personal       5.804789   -0.6013538
    #> 16    4    respMOS_Understand       5.096428   -0.6556369
    #> 17    5        respMOS_Listen       4.361493   -0.9570236
    #> 18    5          respMOS_Info       3.656771   -0.8113308
    #> 19    5 respMOS_Advice_Crisis       3.998893   -0.7753867
    #> 20    5       respMOS_Confide       5.673946   -0.7619786
    #> 21    5   respMOS_Advice_Want       3.946687   -0.6656029
    #> 22    5         respMOS_Fears       4.514427   -0.6517023
    #> 23    5      respMOS_Personal       5.603528   -0.6532189
    #> 24    5    respMOS_Understand       5.053900   -0.7616168
    #>                                       Thresholds
    #> 1  -2.3085809, -1.4656879, -0.7924741, 0.3891970
    #> 2  -1.9202380, -1.1458453, -0.4455881, 0.8912573
    #> 3  -1.7006177, -1.0292038, -0.4444648, 0.8650516
    #> 4  -1.7745371, -1.2588305, -0.6510803, 0.4584325
    #> 5  -1.5663100, -1.0568062, -0.4091240, 0.6060837
    #> 6  -1.3776834, -0.8681593, -0.4503415, 0.5313636
    #> 7  -1.5389536, -0.9723585, -0.3744289, 0.6658615
    #> 8  -1.5511276, -1.0068864, -0.3367904, 0.6619284
    #> 9  -2.0402800, -1.3890196, -0.8544978, 0.1057268
    #> 10 -2.1834461, -1.3856741, -0.5527815, 0.6969514
    #> 11 -1.8892186, -1.2148736, -0.4590243, 0.6753132
    #> 12 -1.7158023, -1.1176107, -0.5585143, 0.2596022
    #> 13     -1.654020, -1.070015, -0.390809, 0.580100
    #> 14 -1.4215330, -0.9528592, -0.4478088, 0.3268731
    #> 15 -1.5070286, -0.9686785, -0.3980860, 0.4683781
    #> 16 -1.6254187, -1.0364005, -0.4343498, 0.4736214
    #> 17 -1.8961890, -1.3971894, -0.7398447, 0.2051288
    #> 18 -1.9230815, -1.3221471, -0.5396625, 0.5395678
    #> 19 -1.9229181, -1.1967558, -0.5068723, 0.5249993
    #> 20 -1.6454980, -1.1292589, -0.5388327, 0.2656753
    #> 21 -1.7035508, -1.0992689, -0.3705977, 0.5110057
    #> 22 -1.4661024, -1.0048158, -0.4430565, 0.3071656
    #> 23 -1.5339051, -1.0556259, -0.4122411, 0.3888965
    #> 24 -1.7118384, -1.1829442, -0.5260505, 0.3743661

Note: The `itempar_grmtree` extracts both discrimination parameters and average threshold parameters for each item from all terminal nodes of a graded response model tree. See `?itempar_grmtree` for more information.

# Conclusion

This vignette demonstrated basic usage of `grmtree` for identifying DIF in polytomous items. Key steps included checking dimensionality, fitting the GRM, and interpreting the resulting tree.