install.packages("measr")We are ecstatic to announce the release of measr 2.0.0. measr is an R package for estimating and evaluating diagnostic classification models (DCMs). You can specify a variety of DCMs, choose a Stan backend and estimation method, and then evaluate the model using a wide range of model fit analyses.
You can install measr from CRAN with:
This blog post highlights the most important changes in this release, including a decoupling of measr’s functionality, a new interface for specifying and estimating models, and many new functions for model evaluation. You can see a full list of changes in the release notes.
Introducing r-dcm
A major focus of development work since the previous release has been to modularize the functionality of measr, creating smaller and more focused packages that are easier to maintain and quickly update with new features. Functionality for model estimation and evaluation remains the purview of measr. However, previously, DCMs were specified within the same function call as model estimation using measr_dcm(). This created confusion and blurred the lines between how the model was defined and how it was estimated.
Therefore, the specification of DCMs, including the creation of Stan scripts, is now handled by the new dcmstan package. This decoupling allows for more flexible model specifications and will also allow us to more quickly add new model types without disrupting model estimation functionality in measr.
Additionally, the example data sets have been split out into their own package, dcmdata, allowing the data sets to be easily used across both measr and dcmstan (and future packages) without creating troublesome recursive dependencies between packages.
In most cases, you will only need to load measr as relevant functions (e.g., dcm_specify() from dcmstan) are reexported by measr. So while there are some significant changes on the backend, the transition should be (hopefully) seamless for users.
Specification and estimation
As part of the decoupling of functionality, we have also decoupled model specification and estimation. Conceptually, the model specification is separate from model estimation. That is, the choice of model (e.g., DINA vs. LCDM) should not impact model estimation choices (e.g., MCMC vs. optimization, estimation engine). Therefore, decoupling also allows for a more intuitive user interface. Using previous versions of measr, you would both specify and estimate a DCM with measr_dcm(). In the following code, we estimate an LCDM with an unconstrained structural model, but we limit the LCDM to only contain intercepts, main effects, and two-way interactions (max_interaction = 2).
However, this code is ambiguous. Does the max_interaction apply to the type, the attribute_structure, or some other element of the model estimation process? This information is clarified in the function documentation, but the code itself is not intuitive. Under the new specification interface, additional arguments to measurement or structural models are now defined explicitly in the model specification. We specify a model with dcm_specify(), and arguments that apply to a measurement or structural model are defined within that model’s constructor. For a list of all supported measurement and structural models and their associated arguments, see ?dcm_specify.
lcdm_spec <- dcm_specify(
qmatrix = ecpe_qmatrix,
identifier = "item_id",
measurement_model = lcdm(max_interaction = 2),
structural_model = unconstrained()
)We can then use our model specification and dcm_estimate() to estimate the model.
lcdm <- dcm_estimate(
dcm_spec = lcdm_spec,
data = ecpe_data,
identifier = "resp_id",
method = "variational",
backend = "rstan",
file = "fits/ecpe-lcdm"
)measr_dcm() has been deprecated, but will continue to work with limited functionality, as new development work will go toward dcm_specify() and dcm_estimate(). Using measr_dcm() will result in a warning that you should update your workflow.
lcdm <- measr_dcm(
data = ecpe_data,
qmatrix = ecpe_qmatrix,
resp_id = "resp_id",
item_id = "item_id",
type = "lcdm",
attribute_structure = "unconstrained",
method = "variational",
backend = "cmdstanr",
file = "fits/ecpe-lcdm"
)
#> Warning: `measr_dcm()` was deprecated in measr 2.0.0.
#> ℹ This is a limited version of dcm_estimate(); use it instead.New model specifications
Speaking of new development work, we have added support for several new measurement and structural models. On the measurement model side, we now support the noisy-input, deterministic “and” gate (nida(); Junker & Sijtsma, 2001); the noisy-input, deterministic “or” gate (nido(); Templin, 2006); and the noncompensatory reparameterized unified model (ncrum(); DiBello et al., 1995). This means that we now support the specification and estimation of all 6 core DCMs identified by Rupp et al. (2010) in addition to the general LCDM.
We also added support for the loglinear structural model (loglinear(); Xu & von Davier, 2008), which allows the user to limit the interactions included between the attributes. We also added support for attribute hierarchies. For example, we might theorize that some attributes are dependent on others that represent prerequisite skills, as in the ECPE data set we used earlier. This data set measures 3 attributes: morphosyntactic, cohesive, and lexical rules. These attributes represent rules of the English language with increasing levels of complexity from lexical, to cohesive, and finally morphosyntactic. measr now supports two different ways to specify attribute relationships in a DCM. The first is the hierarchical DCM (hdcm(); Templin & Bradshaw, 2014), which enforces a strict hierarchy.
hdcm_spec <- dcm_specify(
qmatrix = ecpe_qmatrix,
identifier = "item_id",
measurement_model = lcdm(),
structural_model = hdcm(hierarchy = "lexical -> cohesive -> morphosyntactic")
)We specify the hierarchy in our structural model using arrows to indicate the dependencies (see vignette("attribute-hierarchies", package = "dcmstan") for more examples). As we can see, the full model with an unconstrained structural model includes 8 possible attribute patterns. However, when we use the HDCM, only patterns that are consistent with the specified hierarchy are included in the model.
create_profiles(lcdm_spec)
#> # A tibble: 8 × 3
#> morphosyntactic cohesive lexical
#> <int> <int> <int>
#> 1 0 0 0
#> 2 1 0 0
#> 3 0 1 0
#> 4 0 0 1
#> 5 1 1 0
#> 6 1 0 1
#> 7 0 1 1
#> 8 1 1 1
create_profiles(hdcm_spec)
#> # A tibble: 4 × 3
#> morphosyntactic cohesive lexical
#> <int> <int> <int>
#> 1 0 0 0
#> 2 0 0 1
#> 3 0 1 1
#> 4 1 1 1The second structural model that can support a user-defined hierarchy is a Bayesian network (bayesnet(); Hu & Templin, 2020). When using a BayesNet, the hierarchical relationship is specified in the same way using -> or <- to indicate dependencies. However, the BayesNet specification does not completely eliminate patterns that are inconsistent with the specified hierarchy. Rather, inconsistent patterns are downweighted using conditional probabilities.
bn_spec <- dcm_specify(
qmatrix = ecpe_qmatrix,
identifier = "item_id",
measurement_model = lcdm(),
structural_model = bayesnet(
hierarchy = "lexical -> cohesive -> morphosyntactic"
)
)All measurement and structural models can be mixed and matched in dcm_specify() to create a model that meets your needs and matches your conceptual understanding of the items and attributes.
Estimation improvements
We’ve also made some improvements to how models are estimated in the form of two new estimation methods for dcm_estimate(). First, we have added support for Stan’s variational algorithm for approximate posterior sampling (method = "variational"). This method will wrap rstan::vb() when using the rstan backend and cmdstanr::variational() when using cmdstanr.
We’ve also added support for Stan’s pathfinder variational inference algorithm (method = "pathfinder"). This method is similar to the variational method; however, the pathfinder algorithm is generally faster and more stable than the automatic differentiation algorithm that is used by rstan::vb() and cmdstanr::variational(). Notably, the pathfinder algorithm is not available in rstan, and therefore this method can only be used when using cmdstanr (wrapping cmdstanr::pathfinder()).
hdcm <- dcm_estimate(
dcm_spec = hdcm_spec,
data = ecpe_data,
identifier = "resp_id",
method = "pathfinder",
backend = "cmdstanr",
file = "fits/ecpe-hdcm"
)Although we still recommend using full posterior sampling when possible (method = "mcmc"), we recognize that this can be very slow for these models, and the variational algorithms should be much quicker.
Model evaluation updates
We added several new tools for evaluating a model once it is estimated. These include updates to how to extract parameter estimates and other information from a model, new functions for evaluating model assumptions, and additional methods for conducting model comparisons.
Extract information
We’ve added a few new elements that can be extracted from a model with measr_extract(). We can extract the base rate of proficiency or presence on each of the attributes with what = "attribute_base_rate". We can also now extract the π matrix with what = "pi_matrix", which is the estimated probability that a respondent in each class will provide a correct response to each item.
measr_extract(lcdm, what = "attribute_base_rate")
#> # A tibble: 1 × 3
#> morphosyntactic cohesive lexical
#> <rvar[1d]> <rvar[1d]> <rvar[1d]>
#> 1 0.38 ± 0.016 0.55 ± 0.02 0.67 ± 0.019
measr_extract(lcdm, what = "pi_matrix")
#> # A tibble: 28 × 9
#> item_id `[0,0,0]` `[1,0,0]` `[0,1,0]` `[0,0,1]`
#> <chr> <rvar[1d]> <rvar[1d]> <rvar[1d]> <rvar[1d]>
#> 1 E1 0.69 ± 0.014 0.76 ± 0.030 0.81 ± 0.0227 0.69 ± 0.0138
#> 2 E2 0.75 ± 0.011 0.75 ± 0.011 0.92 ± 0.0104 0.75 ± 0.0113
#> 3 E3 0.42 ± 0.010 0.54 ± 0.023 0.42 ± 0.0104 0.52 ± 0.0300
#> 4 E4 0.47 ± 0.013 0.47 ± 0.013 0.47 ± 0.0129 0.82 ± 0.0125
#> 5 E5 0.76 ± 0.012 0.76 ± 0.012 0.76 ± 0.0123 0.96 ± 0.0065
#> 6 E6 0.72 ± 0.014 0.72 ± 0.014 0.72 ± 0.0143 0.94 ± 0.0099
#> 7 E7 0.47 ± 0.015 0.72 ± 0.059 0.47 ± 0.0150 0.68 ± 0.0206
#> 8 E8 0.82 ± 0.011 0.82 ± 0.011 0.97 ± 0.0068 0.82 ± 0.0107
#> 9 E9 0.54 ± 0.010 0.54 ± 0.010 0.54 ± 0.0105 0.80 ± 0.0121
#> 10 E10 0.50 ± 0.012 0.88 ± 0.014 0.50 ± 0.0117 0.50 ± 0.0117
#> # ℹ 18 more rows
#> # ℹ 4 more variables: `[1,1,0]` <rvar[1d]>, `[1,0,1]` <rvar[1d]>,
#> # `[0,1,1]` <rvar[1d]>, `[1,1,1]` <rvar[1d]>We’ve also simplified the extract process. Previously, many what options required that an analysis be added to a model before it could be extracted. For example, if we tried to extract model fit information from a model, we would get an error telling us that model fit information need to first be added to the model.
# old code
measr_extract(lcdm, what = "m2")
#> Error: Model fit information must be added to a model object before the M2
#> can be extracted. See add_fit()."Now, adding elements is no longer required. If you request to extract something that has not been added, it will now automatically be calculated for the extract.
measr_extract(lcdm, what = "m2")
#> # A tibble: 1 × 3
#> m2 df pval
#> <dbl> <int> <dbl>
#> 1 531. 325 3.90e-12Note that you can, and in many cases should, still add elements to a model with add_fit(), add_criterion(), add_reliability(), and add_respondent_estimates(). Many of these calculations are time consuming. If you plan to extract a particular what many times, adding an element first means that the calculation will only happen once, whereas the calculation will need to be conducted every time measr_extract() is called if the element has not been added.
lcdm <- add_fit(lcdm, method = "m2")
measr_extract(lcdm, "m2")
#> # A tibble: 1 × 3
#> m2 df pval
#> <dbl> <int> <dbl>
#> 1 531. 325 3.90e-12Check assumptions
As with any psychometric model, DCMs make assumptions that we should check. This release comes with checks for two key assumptions: local item dependence and the accuracy of the Q-matrix.
Local item dependence refers to the assumption that item responses are independent of each other, conditional on the respondents’ attribute patterns. That is, after we account for the respondents attribute pattern, we should not see any additional or residual relationship among the items. This is often checked using the Q3 statistic (Yen, 1984), which can now be calculated with yens_q3(). yens_q3() will calculate the residual correlations between items and optionally flag any item pairs above a critical value. A significant number of residual correlations, particularly if the same group of items are involved, may indicate additional dimensions (i.e., attributes) that should be included.
We can also evaluate the alignment of items to the attributes identified in the Q-matrix. qmatrix_validation() implements the method described by de la Torre & Chiu (2016) to determine if there are other Q-matrix specifications that would better explain the response data. For example, in our ECPE model, item E9 measures only the third attribute (lexical rules) in the Q-matrix used to estimate the model. However, the data suggests that this item may also be measuring the first attribute (morphosyntactic rules). Note that this method will not evaluate whether the number of attributes is correct, only the item to attribute alignment.
qmatrix_validation(lcdm)
#> # A tibble: 28 × 5
#> item_id original_specification original_pvaf empirical_specification
#> <chr> <chr> <dbl> <chr>
#> 1 E1 [1, 1, 0] 0.994 <NA>
#> 2 E2 [0, 1, 0] 0.993 <NA>
#> 3 E3 [1, 0, 1] 0.996 <NA>
#> 4 E4 [0, 0, 1] 0.982 <NA>
#> 5 E5 [0, 0, 1] 0.993 <NA>
#> 6 E6 [0, 0, 1] 0.983 <NA>
#> 7 E7 [1, 0, 1] 0.999 <NA>
#> 8 E8 [0, 1, 0] 0.994 <NA>
#> 9 E9 [0, 0, 1] 0.909 [1, 0, 1]
#> 10 E10 [1, 0, 0] 0.978 <NA>
#> # ℹ 18 more rows
#> # ℹ 1 more variable: empirical_pvaf <chr>Make comparisons
Finally, we’ve also added support for Bayes factors for model comparisons. bayes_factor() will calculate the posterior probability that a model is correct, given our prior probability (specified through the prior_prob argument) and the calculated Bayes factor. Note the Bayes factors currently depend on the bridgesampling package, which does not support cmdstanr. Therefore, model comparisons with Bayes factors are only available when the models are estimated with full MCMC (method = "mcmc") using rstan (backend = "rstan"). Support for additional estimation methods and backends will be added as they are made available in bridgesampling.
Other new features
Users can now specify a probability classification threshold when calculating reliability metrics with
reliability().Discrimination methods are now available through
cdi()to evaluate how well an item or attribute can discern different classes.predict()has been replaced byscore()to be more intuitive. The model is estimated by predicting item responses; however,predict()returned respondent-level class and attribute probabilities. Thus, this function was renamed to better reflect its intended use.
Acknowledgments
A big thank you to all the folks who helped make this release happen: @auburnjimenez34, @dgkf, @JeffreyCHoover, @ralmond, and @wjakethompson.
The research reported here was supported by the Institute of Education Sciences, U.S. Department of Education, through Grants R305D210045 and R305D240032 to the University of Kansas Center for Research, Inc., ATLAS. The opinions expressed are those of the authors and do not represent the views of the Institute or the U.S. Department of Education.
Featured photo by William Warby on Unsplash.
References
Citation
@online{thompson2026,
author = {Thompson, W. Jake},
title = {Measr 2.0.0},
date = {2026-01-13},
url = {https://r-dcm.org/blog/2026-01-measr-2.0.0/},
langid = {en}
}