Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Functions

compare

compare(*models: Any, method: str = 'auto', sort: bool = True, test: str = 'chisq', refit: bool = False, cv: int | str = 5, seed: int | None = None, metric: str = 'mse', digits: int | None = None, holdout_group: str | None = None) -> pl.DataFrame

Compare nested statistical models.

Performs sequential hypothesis tests comparing nested models. The appropriate test method is inferred from model type:

Cross-type comparisons (e.g., glm vs glmer) are supported when models share the same family. The fixed-only model is treated as nested within the mixed model via zero variance components.

Model PairHypothesis TestAIC/BICCV
lm vs lmF-testYesYes
glm vs glmDevianceYesYes
lmer vs lmerLRTYesYes
glmer vs glmerLRTYesYes
lm vs lmerLRTYesYes
glm vs glmerLRTYesYes
Different familiesYesYes

Parameters:

NameTypeDescriptionDefault
*modelsAnyTwo or more fitted model objects.()
methodstrComparison method. Options: - “auto”: Infer from model type (default) - “f”: F-test (for lm) - “lrt”: Likelihood ratio test (for mixed models) - “deviance”: Deviance test (for glm) - “cv”: Cross-validation comparison (any model type) - “aic”: AIC comparison with delta-AIC and Akaike weights - “bic”: BIC comparison with delta-BIC and Schwarz weights‘auto’
sortboolIf True, sort models by complexity before comparing. This ensures proper nesting order. Default True.True
refitboolIf True and models are lmer/glmer with REML estimation, automatically refit with ML for valid LRT comparison. Original models are not mutated. Default False. Note: REML models are accepted without refitting when all models share the same fixed effects (valid for comparing random effects structures only).False
teststrFor GLM deviance comparisons only. Options: - “chisq”: Chi-squared test (default) - “f”: F-test (for quasi-families with estimated dispersion)‘chisq’
cvint | strFor CV comparison only. Number of folds or “loo” for leave-one-out.5
seedint | NoneFor CV comparison only. Random seed for reproducible splits.None
metricstrFor CV comparison only. Error metric (“mse”, “rmse”, “mae”).‘mse’
digitsint | NoneNumber of significant figures for float columns. Default None uses the global setting from set_display_digits() (4 by default). Pass 0 to disable rounding entirely.None

Returns:

TypeDescription
DataFrameDataFrame with comparison results. Columns depend on method:
DataFrameFor F-test (lm): - model: Formula string - PRE: Proportional reduction in error - F: F-statistic - rss: Residual sum of squares - ss: Sum of squares explained - df: Degrees of freedom for comparison - df_resid: Residual degrees of freedom - p_value: p-value
DataFrameFor deviance test (glm): - model: Formula string - chi2 (or F): Test statistic - dev_diff: Deviance reduction - deviance: Residual deviance - df: Degrees of freedom for comparison - df_resid: Residual degrees of freedom - p_value: p-value
DataFrameFor LRT (lmer/glmer): - model: Formula string - chi2: Likelihood ratio chi-squared statistic - npar: Number of parameters - AIC: Akaike Information Criterion - BIC: Bayesian Information Criterion - loglik: Log-likelihood - deviance: Deviance (-2 * loglik) - df: Degrees of freedom for comparison - p_value: p-value
DataFrameFor CV comparison: - model: Formula string - PRE: Proportional reduction in error - t_stat: Corrected t-statistic - cv_score: Mean CV error - cv_se: Standard error of CV error - diff: Difference from reference (first model) - diff_se: Nadeau-Bengio corrected standard error - p_value: Two-sided p-value
DataFrameFor AIC/BIC comparison: - model: Formula string - npar: Number of estimated parameters - loglik: Log-likelihood - deviance: Deviance (-2 * loglik) - AIC/BIC: Information criteria - delta_AIC/delta_BIC: Difference from best model - weight: Akaike/Schwarz weight (model probability)

Examples:

from bossanova import model, compare, load_dataset
mtcars = load_dataset("mtcars")
sleepstudy = load_dataset("sleepstudy")

# Models are auto-fitted if needed (calls .fit() with defaults)
compare(model("mpg ~ 1", mtcars), model("mpg ~ wt", mtcars))

# Equivalent to explicit .fit() calls:
compare(model("mpg ~ 1", mtcars).fit(), model("mpg ~ wt", mtcars).fit())

# Model objects are fitted in-place, so they're usable after compare()
compact = model("mpg ~ 1", mtcars)
full = model("mpg ~ wt", mtcars)
compare(compact, full)
full.params  # Works - model was auto-fitted

# glm example (deviance test)
compare(
    model("am ~ 1", mtcars, family="binomial"),
    model("am ~ wt", mtcars, family="binomial"),
)

# lmer example (likelihood ratio test)
compare(
    model("Reaction ~ Days + (1|Subject)", sleepstudy).fit(method="ML"),
    model("Reaction ~ Days + (Days|Subject)", sleepstudy).fit(method="ML"),
)

# CV example (Nadeau-Bengio corrected)
compare(
    model("mpg ~ 1", mtcars),
    model("mpg ~ wt", mtcars),
    method="cv", cv=5, seed=42,
)

Notes: For single-parameter comparisons in lm models: F = t^2 where t is the t-statistic for the added parameter. This identity is fundamental and verified in parity tests.

For GLM, the deviance difference follows a chi-squared distribution: chi2 = deviance_compact - deviance_augmented with df = df_compact - df_augmented degrees of freedom.

For mixed models (lmer/glmer), the LRT statistic is: chi2 = 2 * (loglik_augmented - loglik_compact) with df = npar_augmented - npar_compact degrees of freedom. REML models are accepted when all models share the same fixed effects (comparing random effects structures only). Otherwise, ML estimation is required — use refit=True to auto-refit.

A warning is emitted when any model has singular (boundary) variance components, as the chi-squared p-values may be conservative. See Self & Liang (1987).

For CV comparison, the Nadeau-Bengio correction accounts for overlapping training sets in k-fold CV: var_corrected = var(diff) * (1/k + n_test/n_train) This prevents the underestimation of variance that occurs with naive paired t-tests on CV folds.

See Also:

load_dataset

load_dataset(name: DatasetName) -> pl.DataFrame

Load a sample dataset as a Polars DataFrame.

Datasets are bundled with the package and loaded directly from package resources. No network access required.

Parameters:

NameTypeDescriptionDefault
nameDatasetNameName of the dataset to load. Use show_datasets() to see available options.required

Returns:

TypeDescription
DataFrameThe requested dataset as a Polars DataFrame.

Examples:

from bossanova.data import load_dataset

# Load sleep study data for mixed models
sleep = load_dataset("sleep")
print(sleep.head())

# Load mtcars for regression examples
mtcars = load_dataset("mtcars")

show_datasets

show_datasets() -> pl.DataFrame

Show available datasets with descriptions.

Returns:

TypeDescription
DataFrameDataFrame with columns: name, description.

Examples:

from bossanova.data import show_datasets
show_datasets()
# shape: (12, 2)
# ┌───────────────┬─────────────────────────────────┐
# │ name          ┆ description                     │
# │ ---           ┆ ---                             │
# │ str           ┆ str                             │
# ╞═══════════════╪═════════════════════════════════╡
# │ sleep         ┆ Sleep study data (reaction ...  │
# │ mtcars        ┆ Motor Trend Car Road Tests ...  │
# │ ...           ┆ ...                             │
# └───────────────┴─────────────────────────────────┘

to_markdown

to_markdown(df: pl.DataFrame, path: str | Path | None = None, caption: str | None = None) -> str

Convert a Polars DataFrame to a markdown table, optionally saving to file.

Standalone utility for DataFrames not wrapped in ModelResult — such as results from compare(), model.jointtest(), model.vif(), model.to_odds_ratio(), etc.

For ModelResult objects (returned by .fit(), .infer(), etc.), use result.to_markdown() instead.

Parameters:

NameTypeDescriptionDefault
dfDataFramePolars DataFrame to convert.required
pathstr | Path | NoneOptional file path. If given, writes the markdown and creates parent directories automatically.None
captionstr | NoneOptional table caption (Table: ... Quarto syntax).None

Returns:

TypeDescription
strPipe-delimited markdown table as a string.

Examples:

>>> from bossanova import compare, to_markdown
>>> to_markdown(compare(m1, m2), "comparison.md", caption="Model Comparison")
>>> md = to_markdown(m.jointtest())  # string only