bossanova 0.1.0.dev24__tar.gz → 0.1.0.dev26__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/.gitignore +2 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/PKG-INFO +1 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/compare/compare.py +25 -3
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/compare/cv.py +89 -39
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/compare/helpers.py +59 -11
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/compare/lrt_compare.py +18 -12
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/containers/__init__.py +2 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/containers/builders/dataframes.py +136 -26
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/containers/builders/resamples.py +3 -5
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/containers/builders/state.py +23 -5
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/containers/schemas.py +13 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/containers/structs/__init__.py +12 -6
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/containers/structs/data.py +38 -9
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/containers/structs/display.py +8 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/containers/structs/explore.py +70 -11
- bossanova-0.1.0.dev26/bossanova/internal/containers/structs/fit.py +106 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/containers/structs/formula.py +10 -7
- bossanova-0.1.0.dev26/bossanova/internal/containers/structs/inference.py +173 -0
- bossanova-0.1.0.dev26/bossanova/internal/containers/structs/marginal.py +99 -0
- bossanova-0.1.0.dev26/bossanova/internal/containers/structs/mixed.py +128 -0
- bossanova-0.1.0.dev26/bossanova/internal/containers/structs/prediction.py +89 -0
- bossanova-0.1.0.dev26/bossanova/internal/containers/structs/simulation.py +50 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/containers/structs/specs.py +17 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/containers/validators.py +155 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/design/z_matrix.py +1 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/fit/__init__.py +13 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/fit/convergence.py +1 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/fit/diagnostics.py +18 -0
- bossanova-0.1.0.dev26/bossanova/internal/fit/grid.py +339 -0
- bossanova-0.1.0.dev26/bossanova/internal/fit/lifecycle.py +127 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/fit/predict.py +16 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/fit/varying.py +132 -12
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/bundle.py +1 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/random_effects.py +9 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/asymptotic.py +1 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/bootstrap.py +59 -85
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/cv.py +140 -13
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/dispatch.py +10 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/permutation.py +10 -24
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/prediction.py +10 -1
- bossanova-0.1.0.dev26/bossanova/internal/infer/profile.py +215 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/resample/__init__.py +4 -0
- bossanova-0.1.0.dev26/bossanova/internal/infer/resample/glm_operators.py +226 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/resample/glmer.py +7 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/resample/lm_operators.py +21 -14
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/resample/lmer.py +7 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/simulation.py +3 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/bracket_contrasts.py +2 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/compute.py +3 -5
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/contrasts.py +3 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/emm.py +3 -3
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/inference.py +1 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/joint_tests.py +2 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/resolve.py +1 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/slopes.py +3 -3
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/backend/jax.py +10 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/backend/numpy.py +6 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/backend/protocol.py +9 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/inference/__init__.py +8 -8
- bossanova-0.1.0.dev26/bossanova/internal/maths/inference/profile.py +685 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/solvers/glm.py +3 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/solvers/heuristics.py +23 -11
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/solvers/initialization.py +13 -10
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/solvers/lambda_sparse.py +33 -8
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/solvers/lambda_template.py +33 -8
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/simulation/__init__.py +3 -0
- bossanova-0.1.0.dev26/bossanova/internal/simulation/lifecycle.py +156 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/README.md +1 -3
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/__init__.py +2 -11
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/predict.py +6 -13
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/profile.py +1 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/ranef.py +2 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/model/core.py +151 -256
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/model/summary.py +317 -47
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/pyproject.toml +4 -3
- bossanova-0.1.0.dev24/bossanova/internal/containers/structs/state.py +0 -886
- bossanova-0.1.0.dev24/bossanova/internal/infer/profile.py +0 -194
- bossanova-0.1.0.dev24/bossanova/internal/maths/inference/profile.py +0 -626
- bossanova-0.1.0.dev24/bossanova/internal/viz/cognition.py +0 -276
- bossanova-0.1.0.dev24/bossanova/internal/viz/dag.py +0 -488
- bossanova-0.1.0.dev24/bossanova/internal/viz/lattice.py +0 -467
- bossanova-0.1.0.dev24/bossanova/internal/viz/layout.py +0 -565
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/LICENSE +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/README.md +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/data/README.md +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/data/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/data/advertising.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/data/cake.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/data/chickweight.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/data/credit.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/data/gammas.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/data/mtcars.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/data/penguins.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/data/poker.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/data/sleep.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/data/titanic.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/data/titanic_test.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/data/titanic_train.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/distributions/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/distributions/continuous.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/distributions/discrete.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/distributions/varying.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/expressions.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/compare/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/compare/deviance.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/compare/f_test.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/compare/ic.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/compare/lrt.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/compare/refit.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/containers/builders/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/containers/builders/results.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/containers/builders/specs.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/design/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/design/coding.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/design/names.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/design/reference.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/fit/dispatch.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/fit/glm.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/fit/glmer.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/fit/lmer.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/fit/ols.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/contrast_registry.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/contrast_specs.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/design.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/encoding.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/evaluate.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/evaluate_contrast.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/evaluate_newdata.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/evaluate_transforms.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/helpers.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/parse.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/parser/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/parser/expr.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/parser/parser.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/parser/scanner.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/formula/parser/token.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/formula_utils.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/mee.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/params.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/resample/common.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/resample/core.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/resample/results.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/resample/simulate.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/resample_bundle.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/infer/satterthwaite_emm.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/conditions.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/explore.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/explore_parser.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/explore_scanner.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/factors.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/grid.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/matrices.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/transforms.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/marginal/validation.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/backend/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/backend/dispatch.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/batching.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/config.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/convergence.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/differentiation.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/distributions/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/distributions/algebra.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/distributions/base.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/distributions/core.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/distributions/derived.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/distributions/factories.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/distributions/plotting.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/distributions/probability.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/family/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/family/binomial.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/family/create.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/family/gamma.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/family/gaussian.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/family/links.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/family/poisson.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/family/response.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/family/schema.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/family/tdist.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/inference/diagnostics.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/inference/estimation.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/inference/hypothesis.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/inference/information_criteria.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/inference/multiplicity.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/inference/sandwich.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/inference/satterthwaite.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/inference/wald_variance.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/inference/welch.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/linalg/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/linalg/qr.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/linalg/rank.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/linalg/schur.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/linalg/sparse.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/linalg/svd.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/predict.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/rng.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/rounding.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/solvers/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/solvers/glmer.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/solvers/lambda_builder.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/solvers/lmer.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/solvers/optimize.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/solvers/pirls_sparse.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/solvers/quadrature.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/tolerances.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/transforms.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/variance.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/maths/weights.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/rendering/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/rendering/latex.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/rendering/markdown.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/simulation/dgp/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/simulation/dgp/generate.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/simulation/dgp/glm.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/simulation/dgp/glmer.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/simulation/dgp/lm.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/simulation/dgp/lmer.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/simulation/harness.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/simulation/metrics.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/simulation/model_sim.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/simulation/power.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/compare.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/core.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/core_data.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/core_protocols.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/core_sizing.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/core_viz.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/design.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/helpers.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/mem.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/mem_forest.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/params.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/relationships.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/resamples.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/resid.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/internal/viz/vif.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/model/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/model/guards.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/model/result.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/bossanova/py.typed +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/tests/bossanova_benchmarks/bootstrap/data/README.md +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/tests/bossanova_benchmarks/insteval/data/README.md +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev26}/tests/bossanova_tests/hypothesis/README.md +0 -0
|
@@ -60,6 +60,7 @@ def compare(
|
|
|
60
60
|
seed: int | None = None,
|
|
61
61
|
metric: str = "mse",
|
|
62
62
|
digits: int | None = None,
|
|
63
|
+
holdout_group: str | None = None,
|
|
63
64
|
) -> pl.DataFrame:
|
|
64
65
|
"""Compare nested statistical models.
|
|
65
66
|
|
|
@@ -71,6 +72,20 @@ def compare(
|
|
|
71
72
|
- lmer/glmer: Likelihood ratio test
|
|
72
73
|
- cv: Cross-validation with Nadeau-Bengio corrected t-test
|
|
73
74
|
|
|
75
|
+
Cross-type comparisons (e.g., glm vs glmer) are supported when models
|
|
76
|
+
share the same family. The fixed-only model is treated as nested within
|
|
77
|
+
the mixed model via zero variance components.
|
|
78
|
+
|
|
79
|
+
| Model Pair | Hypothesis Test | AIC/BIC | CV |
|
|
80
|
+
|------------|-----------------|---------|-----|
|
|
81
|
+
| lm vs lm | F-test | Yes | Yes |
|
|
82
|
+
| glm vs glm | Deviance | Yes | Yes |
|
|
83
|
+
| lmer vs lmer | LRT | Yes | Yes |
|
|
84
|
+
| glmer vs glmer | LRT | Yes | Yes |
|
|
85
|
+
| lm vs lmer | LRT | Yes | Yes |
|
|
86
|
+
| glm vs glmer | LRT | Yes | Yes |
|
|
87
|
+
| Different families | — | Yes | Yes |
|
|
88
|
+
|
|
74
89
|
Args:
|
|
75
90
|
*models: Two or more fitted model objects.
|
|
76
91
|
method: Comparison method. Options:
|
|
@@ -78,7 +93,7 @@ def compare(
|
|
|
78
93
|
- "f": F-test (for lm)
|
|
79
94
|
- "lrt": Likelihood ratio test (for mixed models)
|
|
80
95
|
- "deviance": Deviance test (for glm)
|
|
81
|
-
- "cv": Cross-validation comparison (
|
|
96
|
+
- "cv": Cross-validation comparison (any model type)
|
|
82
97
|
- "aic": AIC comparison with delta-AIC and Akaike weights
|
|
83
98
|
- "bic": BIC comparison with delta-BIC and Schwarz weights
|
|
84
99
|
sort: If True, sort models by complexity before comparing.
|
|
@@ -234,7 +249,8 @@ def compare(
|
|
|
234
249
|
|
|
235
250
|
# Infer method if auto
|
|
236
251
|
if method == "auto":
|
|
237
|
-
|
|
252
|
+
type_set = {m._model_type for m in model_list}
|
|
253
|
+
method = resolve_method(model_list[0], model_types=type_set)
|
|
238
254
|
|
|
239
255
|
# For LRT with refit=True, refit REML models with ML
|
|
240
256
|
if method == "lrt" and refit:
|
|
@@ -248,7 +264,13 @@ def compare(
|
|
|
248
264
|
elif method == "deviance":
|
|
249
265
|
result = compare_deviance(model_list, test=test)
|
|
250
266
|
elif method == "cv":
|
|
251
|
-
result = compare_cv(
|
|
267
|
+
result = compare_cv(
|
|
268
|
+
model_list,
|
|
269
|
+
cv=cv,
|
|
270
|
+
seed=seed,
|
|
271
|
+
metric=metric,
|
|
272
|
+
holdout_group=holdout_group,
|
|
273
|
+
)
|
|
252
274
|
elif method == "aic":
|
|
253
275
|
result = compare_aic(model_list)
|
|
254
276
|
elif method == "bic":
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
Compares models using k-fold (or LOO) cross-validation with the corrected
|
|
4
4
|
variance estimator from Nadeau & Bengio (2003) to account for overlapping
|
|
5
5
|
training sets.
|
|
6
|
+
|
|
7
|
+
Supports group-aware splitting via ``holdout_group`` to prevent data leakage
|
|
8
|
+
from random-effects structure.
|
|
6
9
|
"""
|
|
7
10
|
|
|
8
11
|
from typing import Any
|
|
@@ -12,11 +15,10 @@ import polars as pl
|
|
|
12
15
|
from scipy import stats
|
|
13
16
|
|
|
14
17
|
from bossanova.internal.containers.schemas import Col, ComparisonCv
|
|
15
|
-
from bossanova.internal.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
generate_loo_indices,
|
|
18
|
+
from bossanova.internal.infer.cv import (
|
|
19
|
+
generate_group_kfold_splits,
|
|
20
|
+
generate_kfold_splits,
|
|
21
|
+
resolve_holdout_group_ids,
|
|
20
22
|
)
|
|
21
23
|
|
|
22
24
|
__all__ = [
|
|
@@ -44,8 +46,6 @@ def compute_cv_fold_score(
|
|
|
44
46
|
Returns:
|
|
45
47
|
Test set error for this fold.
|
|
46
48
|
"""
|
|
47
|
-
model_type = get_model_type(model)
|
|
48
|
-
|
|
49
49
|
# Get original data
|
|
50
50
|
data = model.data
|
|
51
51
|
formula = model.formula
|
|
@@ -57,12 +57,9 @@ def compute_cv_fold_score(
|
|
|
57
57
|
# Refit on training data using unified model()
|
|
58
58
|
from bossanova.model import model as model_cls
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
).fit()
|
|
64
|
-
else:
|
|
65
|
-
raise ValueError(f"CV comparison not supported for model type: {model_type}")
|
|
60
|
+
new_model = model_cls(
|
|
61
|
+
formula, train_data, family=model.family, link=model.link
|
|
62
|
+
).fit()
|
|
66
63
|
|
|
67
64
|
# Predict on test data
|
|
68
65
|
new_model.predict(newdata=test_data)
|
|
@@ -84,58 +81,112 @@ def compute_cv_fold_score(
|
|
|
84
81
|
raise ValueError(f"Unknown metric: {metric}. Use 'mse', 'rmse', or 'mae'.")
|
|
85
82
|
|
|
86
83
|
|
|
84
|
+
def _resolve_compare_splits(
|
|
85
|
+
models: list[Any],
|
|
86
|
+
cv: int | str,
|
|
87
|
+
seed: int | None,
|
|
88
|
+
holdout_group: str | None,
|
|
89
|
+
) -> tuple[list[tuple[np.ndarray, np.ndarray]], int]:
|
|
90
|
+
"""Resolve fold splits for model comparison.
|
|
91
|
+
|
|
92
|
+
Checks all models for RE structure and resolves group-aware splitting.
|
|
93
|
+
For cross-type comparisons (lm vs lmer), the mixed model's grouping
|
|
94
|
+
governs splits for all models.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
models: List of fitted models.
|
|
98
|
+
cv: Number of folds or ``"loo"``.
|
|
99
|
+
seed: Random seed.
|
|
100
|
+
holdout_group: User-specified grouping column, or None.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Tuple of (splits list, effective integer k).
|
|
104
|
+
"""
|
|
105
|
+
import warnings
|
|
106
|
+
|
|
107
|
+
n = models[0]._bundle.n
|
|
108
|
+
|
|
109
|
+
# Find the first model with RE structure (for cross-type comparisons)
|
|
110
|
+
group_ids = None
|
|
111
|
+
for m in models:
|
|
112
|
+
group_ids = resolve_holdout_group_ids(holdout_group, m.data, m._spec, m._bundle)
|
|
113
|
+
if group_ids is not None:
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
if group_ids is not None:
|
|
117
|
+
n_groups = len(np.unique(group_ids))
|
|
118
|
+
|
|
119
|
+
if cv == "loo":
|
|
120
|
+
splits = generate_group_kfold_splits(group_ids, k=n_groups, seed=seed)
|
|
121
|
+
return splits, n_groups
|
|
122
|
+
|
|
123
|
+
k = int(cv)
|
|
124
|
+
if k > n_groups:
|
|
125
|
+
warnings.warn(
|
|
126
|
+
f"k={k} exceeds number of groups ({n_groups}); using k={n_groups}",
|
|
127
|
+
stacklevel=4,
|
|
128
|
+
)
|
|
129
|
+
k = n_groups
|
|
130
|
+
splits = generate_group_kfold_splits(group_ids, k=k, seed=seed)
|
|
131
|
+
return splits, k
|
|
132
|
+
|
|
133
|
+
# No group structure — naive splits
|
|
134
|
+
if cv == "loo":
|
|
135
|
+
from bossanova.internal.infer.resample.core import generate_loo_indices
|
|
136
|
+
|
|
137
|
+
return generate_loo_indices(n), n
|
|
138
|
+
|
|
139
|
+
k = int(cv)
|
|
140
|
+
return generate_kfold_splits(n, k, seed=seed), k
|
|
141
|
+
|
|
142
|
+
|
|
87
143
|
def compare_cv(
|
|
88
144
|
models: list[Any],
|
|
89
145
|
cv: int | str = 5,
|
|
90
146
|
seed: int | None = None,
|
|
91
147
|
metric: str = "mse",
|
|
148
|
+
holdout_group: str | None = None,
|
|
92
149
|
) -> pl.DataFrame:
|
|
93
150
|
"""Compare models using cross-validation with Nadeau-Bengio corrected t-test.
|
|
94
151
|
|
|
95
152
|
Uses the corrected variance estimator from Nadeau & Bengio (2003) to account
|
|
96
153
|
for the non-independence of CV folds (overlapping training sets).
|
|
97
154
|
|
|
155
|
+
When models have random effects, uses group-aware fold splitting to prevent
|
|
156
|
+
data leakage. For crossed random effects, ``holdout_group`` must be specified.
|
|
157
|
+
|
|
98
158
|
Args:
|
|
99
|
-
models: List of fitted models
|
|
159
|
+
models: List of fitted models to compare.
|
|
100
160
|
cv: Number of folds, or "loo" for leave-one-out.
|
|
101
161
|
seed: Random seed for reproducible fold splits.
|
|
102
162
|
metric: Error metric ("mse", "rmse", "mae").
|
|
163
|
+
holdout_group: Column name to use for group-aware fold splitting.
|
|
164
|
+
Required for crossed random effects. Can reference any column
|
|
165
|
+
in the model's data, even if not in the formula.
|
|
103
166
|
|
|
104
167
|
Returns:
|
|
105
|
-
DataFrame with columns:
|
|
106
|
-
|
|
107
|
-
- PRE: Proportional reduction in error
|
|
108
|
-
- t_stat: Nadeau-Bengio corrected t-statistic
|
|
109
|
-
- cv_score: Mean CV score (error)
|
|
110
|
-
- cv_se: Standard error of CV score
|
|
111
|
-
- diff: Difference from reference model (first model)
|
|
112
|
-
- diff_se: Corrected standard error of difference
|
|
113
|
-
- p_value: Two-sided p-value
|
|
168
|
+
DataFrame with columns: model, PRE, t_stat, cv_score, cv_se,
|
|
169
|
+
diff, diff_se, p_value.
|
|
114
170
|
|
|
115
171
|
Notes:
|
|
116
172
|
The Nadeau-Bengio correction accounts for the fact that k-fold CV
|
|
117
173
|
training sets overlap by approximately (k-2)/(k-1) fraction.
|
|
118
174
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
p-value: 2 * (1 - t.cdf(|t|, df=k-1))
|
|
175
|
+
With group-aware splits, fold sizes may be unequal. The correction
|
|
176
|
+
uses the mean n_test/n_train ratio across folds.
|
|
122
177
|
|
|
123
178
|
References:
|
|
124
179
|
Nadeau & Bengio (2003) "Inference for the Generalization Error"
|
|
125
180
|
"""
|
|
126
181
|
n_models = len(models)
|
|
127
|
-
n = models[0]._bundle.n
|
|
128
182
|
|
|
129
183
|
# Generate shared fold splits for fair comparison
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
splits = generate_kfold_indices(rng, n, k, shuffle=True)
|
|
137
|
-
n_train = len(splits[0][0])
|
|
138
|
-
n_test = len(splits[0][1])
|
|
184
|
+
splits, k = _resolve_compare_splits(models, cv, seed, holdout_group)
|
|
185
|
+
|
|
186
|
+
# Compute mean n_test/n_train ratio for Nadeau-Bengio correction
|
|
187
|
+
# (handles unequal fold sizes from group splits)
|
|
188
|
+
ratios = [len(test) / len(train) for train, test in splits]
|
|
189
|
+
mean_ratio = float(np.mean(ratios))
|
|
139
190
|
|
|
140
191
|
# Collect per-fold scores for each model
|
|
141
192
|
fold_scores: dict[int, list[float]] = {i: [] for i in range(n_models)}
|
|
@@ -183,9 +234,8 @@ def compare_cv(
|
|
|
183
234
|
mean_diff = np.mean(diff_vals)
|
|
184
235
|
var_diff = np.var(diff_vals, ddof=1)
|
|
185
236
|
|
|
186
|
-
# Nadeau-Bengio correction
|
|
187
|
-
|
|
188
|
-
var_corrected = var_diff * (1 / k + n_test / n_train)
|
|
237
|
+
# Nadeau-Bengio correction with mean ratio for unequal folds
|
|
238
|
+
var_corrected = var_diff * (1 / k + mean_ratio)
|
|
189
239
|
se_corrected = np.sqrt(var_corrected)
|
|
190
240
|
|
|
191
241
|
# t-statistic and p-value
|
|
@@ -10,6 +10,7 @@ from typing import Any
|
|
|
10
10
|
|
|
11
11
|
__all__ = [
|
|
12
12
|
"check_nested",
|
|
13
|
+
"get_model_family_group",
|
|
13
14
|
"get_model_type",
|
|
14
15
|
"resolve_method",
|
|
15
16
|
"sort_models_by_complexity",
|
|
@@ -34,6 +35,24 @@ def get_model_type(model: Any) -> str:
|
|
|
34
35
|
return model._model_type
|
|
35
36
|
|
|
36
37
|
|
|
38
|
+
def get_model_family_group(model: Any) -> str:
|
|
39
|
+
"""Get the family group for cross-type compatibility checking.
|
|
40
|
+
|
|
41
|
+
Models in the same family group can be compared via LRT because
|
|
42
|
+
the fixed-only model is nested within the mixed model (zero variance
|
|
43
|
+
components). Models in different family groups have incompatible
|
|
44
|
+
likelihoods and cannot be compared via hypothesis tests.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
model: A fitted model instance.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Family group string: 'gaussian' for lm/lmer, or the family name
|
|
51
|
+
(e.g. 'binomial', 'poisson', 'gamma') for glm/glmer.
|
|
52
|
+
"""
|
|
53
|
+
return model._spec.family
|
|
54
|
+
|
|
55
|
+
|
|
37
56
|
# =============================================================================
|
|
38
57
|
# Validation and Sorting Helpers
|
|
39
58
|
# =============================================================================
|
|
@@ -73,12 +92,31 @@ def validate_models(
|
|
|
73
92
|
if not getattr(m, "_is_fitted", False):
|
|
74
93
|
m.fit()
|
|
75
94
|
|
|
76
|
-
# Check
|
|
95
|
+
# Check type compatibility
|
|
77
96
|
model_types = [get_model_type(m) for m in models]
|
|
78
97
|
if len(set(model_types)) > 1:
|
|
79
|
-
|
|
80
|
-
|
|
98
|
+
# Cross-type comparisons are allowed for:
|
|
99
|
+
# - lm + lmer (both Gaussian) via LRT
|
|
100
|
+
# - glm + glmer (same family) via LRT
|
|
101
|
+
# - Any combination via AIC/BIC/CV (non-hypothesis methods)
|
|
102
|
+
#
|
|
103
|
+
# Disallowed cross-type pairs (different likelihoods):
|
|
104
|
+
# - lm + glm, lm + glmer, glm + lmer, lmer + glmer
|
|
105
|
+
type_set = set(model_types)
|
|
106
|
+
family_groups = {get_model_family_group(m) for m in models}
|
|
107
|
+
|
|
108
|
+
is_valid_cross_type = len(family_groups) == 1 and (
|
|
109
|
+
type_set <= {"lm", "lmer"} or type_set <= {"glm", "glmer"}
|
|
81
110
|
)
|
|
111
|
+
is_ic_or_cv = method in ("aic", "bic", "cv")
|
|
112
|
+
|
|
113
|
+
if not is_valid_cross_type and not is_ic_or_cv:
|
|
114
|
+
raise ValueError(
|
|
115
|
+
f"Cannot compare models of types {', '.join(model_types)} "
|
|
116
|
+
f"with method='{method}'. Cross-type hypothesis tests require "
|
|
117
|
+
"compatible families (lm+lmer or glm+glmer with same family). "
|
|
118
|
+
"Use method='aic', 'bic', or 'cv' for non-nested comparison."
|
|
119
|
+
)
|
|
82
120
|
|
|
83
121
|
# Check same response
|
|
84
122
|
responses = [m._spec.response_var for m in models]
|
|
@@ -95,10 +133,13 @@ def validate_models(
|
|
|
95
133
|
)
|
|
96
134
|
|
|
97
135
|
# For LRT, check that all models use ML estimation (unless refit=True)
|
|
98
|
-
|
|
99
|
-
inferred_method =
|
|
136
|
+
type_set = set(model_types)
|
|
137
|
+
inferred_method = (
|
|
138
|
+
method if method != "auto" else resolve_method(models[0], model_types=type_set)
|
|
139
|
+
)
|
|
140
|
+
has_mixed = any(t in ("lmer", "glmer") for t in model_types)
|
|
100
141
|
|
|
101
|
-
if inferred_method == "lrt" and
|
|
142
|
+
if inferred_method == "lrt" and has_mixed and not refit:
|
|
102
143
|
methods = [m._spec.method for m in models]
|
|
103
144
|
reml_models = [m for m, meth in zip(models, methods) if meth == "reml"]
|
|
104
145
|
if reml_models:
|
|
@@ -147,23 +188,30 @@ def sort_models_by_complexity(models: tuple[Any, ...]) -> list[Any]:
|
|
|
147
188
|
return sorted(models, key=get_total_params)
|
|
148
189
|
|
|
149
190
|
|
|
150
|
-
def resolve_method(model: Any) -> str:
|
|
191
|
+
def resolve_method(model: Any, *, model_types: set[str] | None = None) -> str:
|
|
151
192
|
"""Infer the appropriate comparison method for a model type.
|
|
152
193
|
|
|
153
|
-
|
|
154
|
-
|
|
194
|
+
For same-type comparisons, reads ``model._model_type`` to determine
|
|
195
|
+
the method. For cross-type comparisons (lm+lmer or glm+glmer),
|
|
196
|
+
returns ``"lrt"`` since the fixed-only model is nested within the
|
|
197
|
+
mixed model via zero variance components.
|
|
155
198
|
|
|
156
199
|
Args:
|
|
157
200
|
model: A fitted model object with a ``_model_type`` attribute
|
|
158
201
|
(one of ``"lm"``, ``"glm"``, ``"lmer"``, ``"glmer"``).
|
|
202
|
+
model_types: Set of all model types being compared. When provided,
|
|
203
|
+
enables cross-type method resolution.
|
|
159
204
|
|
|
160
205
|
Returns:
|
|
161
|
-
Comparison method: ``"f"`` for lm, ``"lrt"`` for lmer/glmer
|
|
162
|
-
``"deviance"`` for glm.
|
|
206
|
+
Comparison method: ``"f"`` for lm, ``"lrt"`` for lmer/glmer
|
|
207
|
+
(and cross-type lm+lmer or glm+glmer), ``"deviance"`` for glm.
|
|
163
208
|
|
|
164
209
|
Raises:
|
|
165
210
|
ValueError: If the model type cannot be mapped to a comparison method.
|
|
166
211
|
"""
|
|
212
|
+
# Cross-type: lm+lmer or glm+glmer → always LRT
|
|
213
|
+
if model_types is not None and len(model_types) > 1:
|
|
214
|
+
return "lrt"
|
|
167
215
|
|
|
168
216
|
if model._model_type == "lm":
|
|
169
217
|
return "f"
|
|
@@ -16,7 +16,8 @@ from bossanova.internal.containers.schemas import (
|
|
|
16
16
|
from bossanova.internal.maths.config import is_singular
|
|
17
17
|
from bossanova.internal.maths.inference import compute_aic as compute_aic
|
|
18
18
|
from bossanova.internal.maths.inference import compute_bic as _compute_bic
|
|
19
|
-
from bossanova.internal.
|
|
19
|
+
from bossanova.internal.maths.family import ESTIMATED_DISPERSION_FAMILIES
|
|
20
|
+
from bossanova.internal.compare.helpers import check_nested
|
|
20
21
|
|
|
21
22
|
__all__ = [
|
|
22
23
|
"compare_lrt",
|
|
@@ -25,23 +26,28 @@ __all__ = [
|
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
def get_n_params(model: Any) -> int:
|
|
28
|
-
"""Get total number of parameters for
|
|
29
|
+
"""Get total number of estimated parameters for LRT comparison.
|
|
30
|
+
|
|
31
|
+
Handles all model types (lm, glm, lmer, glmer) for cross-type
|
|
32
|
+
LRT comparisons where a fixed-only model is nested within a mixed
|
|
33
|
+
model via zero variance components.
|
|
34
|
+
|
|
35
|
+
Parameter counting follows R's ``logLik()`` convention:
|
|
36
|
+
|
|
37
|
+
- Fixed effects (p)
|
|
38
|
+
- Variance parameters (len(theta), mixed models only)
|
|
39
|
+
- Dispersion/sigma (+1 for Gaussian and other estimated-dispersion families)
|
|
29
40
|
|
|
30
41
|
Args:
|
|
31
|
-
model: A fitted
|
|
42
|
+
model: A fitted model instance.
|
|
32
43
|
|
|
33
44
|
Returns:
|
|
34
|
-
Total parameter count
|
|
45
|
+
Total parameter count.
|
|
35
46
|
"""
|
|
36
47
|
n_fixed = model._bundle.p
|
|
37
|
-
n_theta = len(model._fit.theta)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
model_type = get_model_type(model)
|
|
41
|
-
if model_type == "lmer":
|
|
42
|
-
return n_fixed + n_theta + 1
|
|
43
|
-
else: # glmer
|
|
44
|
-
return n_fixed + n_theta
|
|
48
|
+
n_theta = len(model._fit.theta) if model._fit.theta is not None else 0
|
|
49
|
+
n_disp = 1 if model._spec.family in ESTIMATED_DISPERSION_FAMILIES else 0
|
|
50
|
+
return n_fixed + n_theta + n_disp
|
|
45
51
|
|
|
46
52
|
|
|
47
53
|
def compare_lrt(models: list[Any]) -> pl.DataFrame:
|
|
@@ -41,6 +41,7 @@ from bossanova.internal.containers.structs import (
|
|
|
41
41
|
MathDisplay,
|
|
42
42
|
MeeState,
|
|
43
43
|
ModelSpec,
|
|
44
|
+
PredictionConfig,
|
|
44
45
|
PredictionState,
|
|
45
46
|
ProfileState,
|
|
46
47
|
REInfo,
|
|
@@ -106,6 +107,7 @@ __all__ = [
|
|
|
106
107
|
"MathDisplay",
|
|
107
108
|
"MeeState",
|
|
108
109
|
"ModelSpec",
|
|
110
|
+
"PredictionConfig",
|
|
109
111
|
"PredictionState",
|
|
110
112
|
"ProfileState",
|
|
111
113
|
"REInfo",
|