Skip to contents

two models the data cannot distinguish

degen checks whether competing statistical models can be distinguished by data, such as an exponential model and a gamma model with its shape fixed at one, and whether the parameters of a single model can be estimated, such as two means that enter the likelihood only through their sum.

Two models are observationally equivalent when they assign the same probability to every possible dataset, so no amount of data separates them. degen compares their log-likelihood surfaces: for parameter values of one model it searches for values of the other that give the same log-likelihood, in both directions, and reports the largest discrepancy against a tolerance. For a single model it computes the Fisher information, the curvature of the log-likelihood, and reads its eigenvalues: an eigenvalue near zero marks a parameter combination the likelihood is flat in. You can then decide which of the competing models your data can support, or reparameterize the model so each parameter is estimable.

degen supports:

A comparison that finds no discrepancy above the tolerance is numerical evidence of equivalence over the points tested; a parameter value the grid did not cover may still separate the models, and the verdict depends on the tolerance and the number of points. Comparing Models shows how both change it.

Installation

The package compiles C++ code. Install the build tools for your operating system first:

Operating system Build tools
Windows Rtools matching your R version
macOS Run xcode-select --install in Terminal
Debian / Ubuntu Install r-base-dev

Check that R can find the compiler:

pkgbuild::has_build_tools(debug = TRUE)

Then install degen from GitHub:

# install.packages("pak")
pak::pak("gcol33/degen")

degen needs no system libraries beyond the compiler. Its C++ code links only to Rcpp, which is installed from CRAN with the package.

Usage

  1. Define each model with model_spec() or a built-in such as model_spec_exponential().
  2. Test two models for equivalence with equivalence_pair() and compare_surfaces().
  3. Check the parameters of one model with identifiability_check().

A model specification is a log-likelihood function together with the names of its parameters and optional bounds on them. The function’s first argument is the data y, and its other arguments are the parameters. The data is a numeric vector, or whatever the log-likelihood function accepts.

library(degen)

set.seed(1)
y <- rexp(200, rate = 2)

exp_model <- model_spec_exponential()
gamma_model <- model_spec_gamma(par = "rate", known_shape = 1)
pair <- equivalence_pair(exp_model, gamma_model)

compare_surfaces(pair, y, tol = 1e-4)
#> <surface_comparison>
#> Conclusion: Models appear EQUIVALENT
#> Points tested: 200
#> Max discrepancy: 6.79e-06
#> Tolerance: 1.00e-04
#> Method: grid

A gamma distribution with shape one is the exponential distribution, so the largest discrepancy over the 200 points tested stays below the tolerance. With the default tolerance of 1e-6 the discrepancy of the numerical search, about 7e-6, would exceed it.

The second model below has two means that enter the likelihood only through their sum:

sum_model <- model_spec(
  loglik_fn = function(y, a, b) {
    sum(dnorm(y, mean = a + b, sd = 1, log = TRUE))
  },
  par_names = c("a", "b")
)

y2 <- rnorm(100, mean = 5)
identifiability_check(sum_model, y2, par = c(a = 2, b = 3))
#> <identifiability_result>
#> Overall: Model has NON-IDENTIFIABLE parameters
#> 
#> Parameter status:
#>   a: NON-IDENTIFIABLE
#>   b: NON-IDENTIFIABLE
#> 
#> Non-identified directions:
#>   0.71*a -0.71*b
#> 
#> Identified functions:
#>   -0.71*a -0.71*b
#> 
#> Condition number: 4.1e+12
#> Rank: 1 / 2

Neither parameter is identified on its own; their sum is.

Comparing several models

equivalence_classes() compares every pair in a list of models and groups them into classes of equivalent models:

gamma2_model <- model_spec_gamma(par = "rate", known_shape = 2)
models <- list(exponential = exp_model, gamma1 = gamma_model,
               gamma2 = gamma2_model)
equivalence_classes(models, y, tol = 1e-4)

compare_all_pairs() returns the full matrix of pairwise comparisons. find_equivalent_to() lists the models equivalent to one of them. Case Studies applies this to competing ecological models of population counts.

Models from fits and Bayesian draws

model_spec_from_fit() takes the specification from a fitted linear or generalized linear model, so an existing model can be checked without writing its log-likelihood. For Bayesian models, compare_posteriors() compares two models on their posterior draws. Results export to a CSV or JSON file with export_results(). A LaTeX table comes from to_latex().

Documentation

Start with Introduction to degen: observational equivalence and identifiability, with a first comparison, an identifiability check and equivalence classes.

Getting help

Questions and bug reports go to the GitHub issues. A bug report is easiest to act on with a small reproducible example, for instance made with reprex.

Support

“Software is like sex: it’s better when it’s free.” — Linus Torvalds

I’m a PhD student who builds R packages in my free time because I believe good tools should be free and open. I started these projects for my own work and figured others might find them useful too.

If this package saved you some time, buying me a coffee is a nice way to say thanks. It helps with my coffee addiction.

Buy Me A Coffee

License

degen is licensed under MIT.

Citation

To cite degen in publications use:

Colling G (2026). _degen: Detect Observational Equivalence and
Non-Identifiability in Models_. R package version 0.15.0,
<https://github.com/gcol33/degen>.

BibTeX:

@Manual{colling2026degen,
  title = {degen: Detect Observational Equivalence and Non-Identifiability in
Models},
  author = {Gilles Colling},
  year = {2026},
  note = {R package version 0.15.0},
  url = {https://github.com/gcol33/degen},
}

Download the citation as BibTeX or RIS.