Model or dataset
easystats/performance avatar
easystats/performance

performance: One R Interface for R2, ICC and Model Diagnostics Across Model Classes

:muscle: Models' quality and performance metrics (R2, ICC, LOO, AIC, BF, ...)

1,154 stars109 forksRGPL-3.0

At a glance

What is it?
The easystats performance package collects fit indices and diagnostic checks for lm, glm, polr, lme4, rstanarm and brms models behind a single function surface. It is convenient for R users who fit several model families, and it hides which formula each index actually uses.
Who is it for?
Adopt performance if you fit more than one model family in R and want r2(), icc() and check_overdispersion() to behave consistently across lm, glm, polr, lme4, rstanarm and brms objects, and if you are willing to read the reference index to learn which specific index each call returns.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly R, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The gap performance fills: fit indices scattered across model-specific packages

The README states the problem plainly: functions to create diagnostic plots or compute fit measures exist, but they are mostly spread over different packages, and there is no single consistent approach to assessing model quality across different kinds of models. That is the gap the package claims to fill. In practice this matters when a single analysis contains an lm, a binomial glm, an ordinal polr and a mixed model, and each one would otherwise need its own R2 function with its own argument names and its own return shape. performance answers with generic entry points: r2(), icc(), and checks such as check_overdispersion(). The intended audience is applied researchers and analysts working in R who report fit statistics rather than machine learning engineers optimizing a loss function. The package's own framing is model assessment, comparison and testing, and its citation points to the Journal of Open Source Software paper by Lüdecke, Ben-Shachar, Patil, Waggoner and Makowski.

How r2() picks an index: dispatch by model class, not by a single formula

The mechanism is dispatch on the fitted model object. The README says r2() returns a list containing values related to the most appropriate r-squared for the given model, and its examples make the consequences visible. On lm(mpg ~ wt + cyl, data = mtcars) the output is labelled R2 for Linear Regression and reports R2: 0.830 alongside adj. R2: 0.819. On glm(am ~ wt + cyl, data = mtcars, family = binomial) the same call is labelled R2 for Logistic Regression and reports Tjur's R2: 0.705. On a polr fit from MASS with weights = Freq it reports Nagelkerke's R2: 0.108. Those three numbers answer different questions, and the generic name does not flatten them into one scale. The README also notes that the individual measures can be called directly through functions such as r2_bayes(), r2_coxsnell() or r2_nagelkerke(), with a full list in the reference index. That escape hatch is the honest part of the design: the generic is a convenience layer, and the specific function is where the definition lives.

Marginal versus conditional R2 for mixed models, and what icc() reports

For mixed models r2() returns two values, and the README defines the split. The marginal R-squared considers only the variance of the fixed effects and indicates how much of the model's variance is explained by the fixed effects part alone. The conditional R-squared takes both fixed and random effects into account and describes the complete model. The README's lme4 example on lmer(Reaction ~ Days + (1 + Days | Subject), data = sleepstudy) gives Conditional R2: 0.799 and Marginal R2: 0.279, a gap that reflects how much of the variance sits in the subject-level random effects. For the Bayesian stan_glmer fit on the iris data, the same two quantities appear with compatibility intervals: Conditional R2: 0.954 (95% CI [0.951, 0.957]) and Marginal R2: 0.414 (95% CI [0.204, 0.644]). The README adds that for frequentist mixed models r2() (or r2_nakagawa()) computes mean random effect variances, which it says makes r2() appropriate for more complex random effects structures such as random slopes or nested random effects, citing Johnson 2014 and Nakagawa, Johnson and Schielzeth 2017. icc() is the companion measure, described as the proportion of the variance explained by the grouping structure in the population, citing Hox 2010. On the same sleepstudy model it reports Adjusted ICC: 0.722 and Unadjusted ICC: 0.521, and on a brms fit it reports Adjusted ICC: 0.930 and Unadjusted ICC: 0.771.

Diagnostics beyond fit: overdispersion, zero-inflation, convergence and singularity

The package is not only a fit-index calculator. The README lists functions that check mixed models for overdispersion, zero-inflation, convergence or singularity, and it documents check_overdispersion() as a test of whether the observed variance in count data exceeds the variance the model assumes, noting that for Poisson models variance roughly equals the mean. That check belongs to a different stage of the workflow than r2(). A high R2 on a count model does not tell you the variance assumption held, and an overdispersion test does not tell you how much variance the predictors explain. Treating the package as one checklist risks skipping the diagnostic that actually matters for the model family in front of you. The README presents a workflow figure (man/figures/figure_workflow.png) that sequences these steps, but the figure itself is not described in the text, so the intended ordering has to be read off the documentation site rather than the README.

Installing performance from CRAN or R-universe

The README gives two sources. The release version comes from CRAN with install.packages("performance"). The development version comes from R-universe, which the README attributes to rOpenSci, with install.packages("performance", repos = "https://easystats.r-universe.dev"). After installation the package is loaded with library("performance"). The README then suggests an alternative: instead of library(performance), use library(easystats), which makes all features of the easystats ecosystem available, and it points to easystats::install_latest() for staying current. That last tip is a real dependency decision rather than a stylistic one. Loading the meta-package pulls in the wider ecosystem, while loading performance alone keeps the namespace narrower. The README does not state which other packages performance depends on, so the size of that difference cannot be judged from this material. Citation is handled through citation("performance"), which returns the JOSS reference, volume 6, issue 60, article 3139, DOI 10.21105/joss.03139.

Where the generic interface can mislead you

The main limitation is the one the examples themselves expose. Because r2() chooses an index per model class, a table of r2() outputs across models mixes Tjur's R2, Nagelkerke's R2 and ordinary R2 in one column. Tjur's R2 and Nagelkerke's R2 are not bounded or interpreted the same way as the linear-model R2, and the README does not claim they are. Anyone comparing fit across a linear and a logistic model using raw r2() values is comparing incompatible scales, and the package will not stop them. A second limitation is that the README does not document how the generic resolves a model class it does not recognize. There is no statement about the error or fallback behaviour for unsupported objects, so that has to be tested rather than assumed. A third is scope: the documented checks are for regression-style models (count models, mixed models, Bayesian regression). If your work is predictive modelling where the decision metric is held-out error, the package's in-sample and likelihood-based indices are not the tool you want, and the README does not present cross-validated or out-of-sample performance as its focus despite the package name.

How performance differs from yardstick and tidymodels

The closest alternative in the R ecosystem is yardstick, part of tidymodels, and the difference is structural rather than cosmetic. yardstick is built around a consistent metric function applied to a data frame of predictions and observed values, with a fixed set of metrics that behave the same way for every model because they are computed from predictions, not from the fitted object. performance goes the other way: it takes the fitted model object and decides which index is appropriate for that class, which is why r2() can return Tjur's R2 for one model and Nagelkerke's R2 for another. yardstick therefore suits resampling workflows and model comparison on a common metric, while performance suits the situation where you have already fitted heterogeneous models and want a fit summary and diagnostic checks without refitting anything. If your goal is a leaderboard of models scored on the same held-out data, yardstick's approach is the one that keeps the numbers comparable. If your goal is to report that a specific mixed model has a conditional R2 of 0.799 and an adjusted ICC of 0.722, performance is the shorter path, and yardstick does not compute those.

Maintenance, licence and what the release history shows

The repository is not archived, and the recent release list shows v0.18.0 on 2026-08-28, v0.18.1 on 2026-09-01 and v0.18.2 on 2026-09-10, with the last push to main on the same day as the newest release. Three releases inside roughly two weeks is a fast patch cadence, and the version numbers indicate patch-level changes rather than a major API break, though the supplied material does not include changelog contents, so what changed between them cannot be stated. The licence is GPL-3.0. For most R users that is unproblematic, since GPL-3.0 is the common licence across CRAN. If you are embedding the package in a larger distributed application rather than using it for analysis, the copyleft terms are a constraint worth checking with your own legal counsel, which is not something this article can advise on. The upgrade cost itself is low by design: the README's tip about easystats::install_latest() suggests the ecosystem expects users to track the development channel, and the generic function names mean that upgrading does not usually require rewriting call sites, only re-reading which index a given model class now returns.

Editorial conclusion

Adopt performance if you fit more than one model family in R and want r2(), icc() and check_overdispersion() to behave consistently across lm, glm, polr, lme4, rstanarm and brms objects, and if you are willing to read the reference index to learn which specific index each call returns. Do not adopt it as a validation harness for predictive accuracy: the README's own examples show r2() reporting Tjur's R2 for a logistic model and Nagelkerke's R2 for a polr model, which are not interchangeable numbers. Before you cite any value in a paper, verify first which function r2() dispatched to, whether you are reading the conditional or marginal R2 for a mixed model, and what the compatibility interval in the Bayesian output covers.

Official sources

  1. easystats/performance on GitHub
  2. License: GPL-3.0
  3. Project website
  4. README
  5. Releases
Community notes

Community notes