--- title: "Comparison with other packages" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Comparison with other packages} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") ``` ```{r setup} library(intraclass) ``` If you already use another R package for intraclass correlations, two questions matter before switching: **does `intraclass` agree with the tool I trust on the problems that tool handles?** and **what does it do that my current tool cannot?** This article answers both, on the package's own shipped datasets, with every number computed live as the page builds. Any unfamiliar term is defined in the [*Glossary*](glossary.html). The comparison packages are `psych` (Revelle's `psych::ICC`, the most widely used ANOVA ICC in R), `irr` (`irr::icc`, a classical inter-rater-reliability toolkit), and `irrICC` (Gwet's model-based ICCs). All three are optional: the code chunks below only run when the package is installed. ```{r have, include = FALSE} have_psych <- requireNamespace("psych", quietly = TRUE) have_irr <- requireNamespace("irr", quietly = TRUE) have_irr_icc <- requireNamespace("irrICC", quietly = TRUE) # psych and irr consume a wide subjects-by-raters matrix; the shipped `ratings` # data are long, so reshape once here. to_wide <- function(d) { w <- reshape(d, idvar = "subject", timevar = "rater", direction = "wide") w <- w[order(as.integer(as.character(w$subject))), ] as.matrix(w[, -1]) } ``` ## Does it agree? (validation) On a **balanced** design, one where every subject is rated by every rater, the whole ICC family is defined for all of these tools. So we can line them up coefficient by coefficient. The `ratings` dataset is six subjects each scored by the same four raters. `intraclass` estimates the coefficients from [variance components](glossary.html#variance-component) fitted by [REML](glossary.html#reml) (a mixed model), whereas `psych` and `irr` derive them from classical **ANOVA mean squares**. Those are different computational routes to the same population quantity, and they are known to converge to each other. The table shows how close they land here: ```{r validation, eval = have_psych && have_irr} wm <- to_wide(ratings) # Scalar `type` and `unit` make this a one-row fit, so `[1]` is that row -- # the coefficient asked for. Elsewhere, select by `term`. ic <- function(model, type, unit) { tidy(icc(ratings, subject = subject, rater = rater, score = score, model = model, type = type, unit = unit))$estimate[1] } ps <- psych::ICC(wm)$results psv <- stats::setNames(ps$ICC, ps$type) rows <- list( c("ICC(1)", "oneway", "agreement", "single", "ICC1"), c("ICC(1,k)", "oneway", "agreement", "average", "ICC1k"), c("ICC(A,1)", "twoway", "agreement", "single", "ICC2"), c("ICC(A,k)", "twoway", "agreement", "average", "ICC2k"), c("ICC(C,1)", "twoway", "consistency", "single", "ICC3"), c("ICC(C,k)", "twoway", "consistency", "average", "ICC3k") ) comparison <- do.call(rbind, lapply(rows, function(r) { data.frame( coefficient = r[1], intraclass = ic(r[2], r[3], r[4]), psych = unname(psv[r[5]]), irr = irr::icc(wm, model = r[2], type = r[3], unit = r[4])$value ) })) knitr::kable(comparison, digits = 5, row.names = FALSE) ``` ```{r validation-gap, eval = have_psych && have_irr} max_gap <- max(abs(comparison$intraclass - comparison$psych), abs(comparison$intraclass - comparison$irr)) ``` Every coefficient matches to five decimal places. The largest disagreement anywhere in the table is `r if (exists("max_gap")) format(max_gap, digits = 2) else "<1e-5"`. That residual is not error in either tool. It is the small-sample gap between a REML fit and ANOVA mean squares, which vanishes as the sample grows. **On the designs classical tools handle, you lose nothing by using `intraclass`**, and the `psych` agreement is in fact checked on every test run of this package. A model-based tool from a different lineage agrees too. `irrICC` implements Gwet's ICCs, estimated by a moment method rather than either REML or ANOVA. Its two-way random agreement coefficient (`icc2r`) reproduces `intraclass`'s `ICC(A,1)`: ```{r irricc, eval = have_irr_icc} w <- reshape(ratings, idvar = "subject", timevar = "rater", direction = "wide") w <- w[order(as.integer(as.character(w$subject))), ] gwet_frame <- data.frame( Target = as.integer(as.character(w$subject)), J1 = w$score.1, J2 = w$score.2, J3 = w$score.3, J4 = w$score.4 ) gwet_agree <- irrICC::icc2.inter.fn(gwet_frame)$icc2r intraclass_a1 <- with(tidy(icc(ratings, subject = subject, rater = rater, score = score, model = "twoway", type = "agreement", unit = "single")), estimate[term == "ICC(A,1)"]) data.frame( source = c("intraclass ICC(A,1)", "irrICC icc2r (Gwet)"), estimate = c(intraclass_a1, gwet_agree) ) ``` ## What does it add? (differentiation) The classical tools were built for the balanced, complete case. Real rating data are rarely so tidy, and that is where the packages diverge. ### Incomplete and unbalanced data The `ratings_incomplete` dataset is the same study with four ratings missing. In particular, the second rater scored only two of the six subjects: ```{r incomplete-show} wide_incomplete <- reshape(ratings_incomplete, idvar = "subject", timevar = "rater", direction = "wide") wide_incomplete <- wide_incomplete[order(as.integer(as.character(wide_incomplete$subject))), ] colnames(wide_incomplete) <- c("subject", paste0("rater", 1:4)) knitr::kable(wide_incomplete, row.names = FALSE) ``` A classical ANOVA ICC needs a complete rectangle, so `psych` and `irr` **listwise-delete** any subject with a missing cell. Here that discards the four subjects rater 2 skipped, leaving only two: ```{r incomplete-classical} wm_inc <- to_wide(ratings_incomplete) surviving <- sum(stats::complete.cases(wm_inc)) c(observed_cells = nrow(ratings_incomplete), possible_cells = nrow(ratings), subjects_after_listwise_deletion = surviving) ``` An ICC computed from two subjects is not usable, whatever its value. `intraclass` instead fits the mixed model to **every observed rating** and reports an [effective number of ratings](glossary.html#effective-number-of-ratings-k_eff) (`k_eff`) that accounts for the imbalance: ```{r incomplete-intraclass} fit_inc <- icc(ratings_incomplete, subject = subject, rater = rater, score = score, model = "twoway", type = "agreement", unit = "average") gl_inc <- glance(fit_inc) c(estimate = with(tidy(fit_inc), estimate[term == "ICC(A,k)"]), subjects_used = gl_inc$n_subjects, ratings_used = gl_inc$n_obs, k_eff = gl_inc$k_eff) ``` All six subjects and all twenty observed ratings contribute, and nothing is thrown away. `irrICC` can also fit incomplete data with its own model, as the capability matrix below shows, but the mean-squares tools cannot. ### The bigger picture Agreement on balanced data and graceful handling of missing data are two entries in a wider gap. The table below summarizes what each package computes. It is a map of intent, not a scorecard: each tool is excellent at what it was designed for. | Capability | `psych` | `irr` | `irrICC` | `intraclass` | |---|:--:|:--:|:--:|:--:| | Balanced ANOVA ICC family | ✅ | ✅ | ✅ | ✅ | | Incomplete / unbalanced data | no | no | ✅ | ✅ | | Multilevel (subject **and** cluster) IRR | no | no | no | ✅ | | Boundary-aware interval | no | no | partial | ✅ | | [Fixed vs. random](glossary.html#fixed-vs--random-raters) rater framing | partial | partial | no | ✅ | | Guidance on *which* ICC to report | no | no | no | ✅ | Two rows deserve a word. Model-based extractors such as `performance::icc` return **variance components** or a variance-partition coefficient. That is the raw material of an ICC, but not the inter-rater-reliability coefficient family itself, nor the error-variance framing that distinguishes agreement from consistency. `intraclass`'s own generalizability coefficients were validated against `gtheory`, agreeing to within 0.001. `gtheory` is a generalizability-theory package archived from CRAN in March 2025, and is not a dependency here. Those committed reference values live in the package's reference notes. And an **interval** that is [boundary-aware](glossary.html#monte-carlo-interval) is something none of the classical tools provide. Such an interval behaves correctly when a variance component is estimated at its [zero boundary](glossary.html#zero-variance-boundary), where a normal-approximation interval silently misbehaves. `intraclass` earns its extra machinery on exactly these cases. For the details of each, see the companion articles: - [*Choosing an ICC*](choosing-an-icc.html): the selection framework the last matrix row points to. - [*Multilevel designs*](multilevel-designs.html): subject- and cluster-level reliability when raters are nested. - [*Interval methods*](interval-methods.html): the boundary-aware Monte-Carlo and bootstrap intervals. - [*Estimation engines*](engines.html): the mixed-model, SEM, and Bayesian backends behind these numbers. ## When to use which If your design is **balanced and complete** and you only need the classic McGraw–Wong coefficients, `psych` and `irr` are mature, familiar, and, as the table above shows, numerically identical to `intraclass`. Reach for `intraclass` when your data are **incomplete or unbalanced**, or when raters are **nested in clusters**. Reach for it too when you need an **interval you can trust near the boundary**, or when you want the package to help you **choose and justify** the coefficient in the first place.