bossanova 0.1.0.dev24__tar.gz → 0.1.0.dev25__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.dev25}/.gitignore +2 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/PKG-INFO +1 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/compare/compare.py +25 -3
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/compare/cv.py +89 -39
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/compare/helpers.py +59 -11
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/compare/lrt_compare.py +18 -12
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/__init__.py +2 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/builders/dataframes.py +12 -8
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/builders/resamples.py +3 -5
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/builders/state.py +19 -5
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/schemas.py +3 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/structs/__init__.py +12 -6
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/structs/data.py +38 -9
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/structs/display.py +8 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/structs/explore.py +70 -11
- bossanova-0.1.0.dev25/bossanova/internal/containers/structs/fit.py +106 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/structs/formula.py +10 -7
- bossanova-0.1.0.dev25/bossanova/internal/containers/structs/inference.py +170 -0
- bossanova-0.1.0.dev25/bossanova/internal/containers/structs/marginal.py +99 -0
- bossanova-0.1.0.dev25/bossanova/internal/containers/structs/mixed.py +128 -0
- bossanova-0.1.0.dev25/bossanova/internal/containers/structs/prediction.py +89 -0
- bossanova-0.1.0.dev25/bossanova/internal/containers/structs/simulation.py +50 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/structs/specs.py +17 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/validators.py +155 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/design/z_matrix.py +1 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/fit/__init__.py +13 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/fit/convergence.py +1 -1
- bossanova-0.1.0.dev25/bossanova/internal/fit/grid.py +339 -0
- bossanova-0.1.0.dev25/bossanova/internal/fit/lifecycle.py +127 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/fit/predict.py +16 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/fit/varying.py +19 -6
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/bundle.py +1 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/random_effects.py +9 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/asymptotic.py +1 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/bootstrap.py +59 -85
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/cv.py +134 -11
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/dispatch.py +10 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/permutation.py +10 -24
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/prediction.py +10 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/profile.py +2 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/resample/__init__.py +4 -0
- bossanova-0.1.0.dev25/bossanova/internal/infer/resample/glm_operators.py +226 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/resample/glmer.py +7 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/resample/lm_operators.py +21 -14
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/resample/lmer.py +7 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/simulation.py +3 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/bracket_contrasts.py +2 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/compute.py +3 -5
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/contrasts.py +3 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/emm.py +3 -3
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/inference.py +1 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/joint_tests.py +2 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/resolve.py +1 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/slopes.py +3 -3
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/backend/jax.py +10 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/backend/numpy.py +6 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/backend/protocol.py +9 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/solvers/glm.py +3 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/solvers/heuristics.py +23 -11
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/solvers/initialization.py +11 -8
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/solvers/lambda_sparse.py +33 -8
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/solvers/lambda_template.py +33 -8
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/simulation/__init__.py +3 -0
- bossanova-0.1.0.dev25/bossanova/internal/simulation/lifecycle.py +156 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/README.md +1 -3
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/__init__.py +2 -11
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/predict.py +6 -13
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/profile.py +1 -1
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/ranef.py +2 -2
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/model/core.py +142 -251
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/model/summary.py +315 -46
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/pyproject.toml +4 -3
- bossanova-0.1.0.dev24/bossanova/internal/containers/structs/state.py +0 -886
- 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.dev25}/LICENSE +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/README.md +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/data/README.md +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/data/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/data/advertising.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/data/cake.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/data/chickweight.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/data/credit.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/data/gammas.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/data/mtcars.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/data/penguins.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/data/poker.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/data/sleep.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/data/titanic.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/data/titanic_test.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/data/titanic_train.csv +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/distributions/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/distributions/continuous.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/distributions/discrete.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/distributions/varying.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/expressions.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/compare/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/compare/deviance.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/compare/f_test.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/compare/ic.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/compare/lrt.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/compare/refit.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/builders/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/builders/results.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/builders/specs.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/design/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/design/coding.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/design/names.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/design/reference.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/fit/diagnostics.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/fit/dispatch.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/fit/glm.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/fit/glmer.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/fit/lmer.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/fit/ols.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/contrast_registry.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/contrast_specs.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/design.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/encoding.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/evaluate.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/evaluate_contrast.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/evaluate_newdata.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/evaluate_transforms.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/helpers.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/parse.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/parser/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/parser/expr.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/parser/parser.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/parser/scanner.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/formula/parser/token.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/formula_utils.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/mee.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/params.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/resample/common.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/resample/core.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/resample/results.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/resample/simulate.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/resample_bundle.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/infer/satterthwaite_emm.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/conditions.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/explore.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/explore_parser.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/explore_scanner.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/factors.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/grid.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/matrices.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/transforms.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/marginal/validation.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/backend/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/backend/dispatch.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/batching.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/config.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/convergence.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/differentiation.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/distributions/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/distributions/algebra.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/distributions/base.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/distributions/core.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/distributions/derived.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/distributions/factories.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/distributions/plotting.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/distributions/probability.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/family/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/family/binomial.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/family/create.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/family/gamma.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/family/gaussian.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/family/links.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/family/poisson.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/family/response.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/family/schema.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/family/tdist.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/inference/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/inference/diagnostics.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/inference/estimation.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/inference/hypothesis.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/inference/information_criteria.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/inference/multiplicity.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/inference/profile.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/inference/sandwich.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/inference/satterthwaite.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/inference/wald_variance.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/inference/welch.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/linalg/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/linalg/qr.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/linalg/rank.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/linalg/schur.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/linalg/sparse.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/linalg/svd.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/predict.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/rng.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/rounding.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/solvers/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/solvers/glmer.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/solvers/lambda_builder.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/solvers/lmer.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/solvers/optimize.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/solvers/pirls_sparse.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/solvers/quadrature.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/tolerances.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/transforms.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/variance.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/maths/weights.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/rendering/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/rendering/latex.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/rendering/markdown.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/simulation/dgp/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/simulation/dgp/generate.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/simulation/dgp/glm.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/simulation/dgp/glmer.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/simulation/dgp/lm.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/simulation/dgp/lmer.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/simulation/harness.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/simulation/metrics.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/simulation/model_sim.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/simulation/power.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/compare.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/core.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/core_data.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/core_protocols.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/core_sizing.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/core_viz.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/design.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/helpers.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/mem.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/mem_forest.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/params.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/relationships.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/resamples.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/resid.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/viz/vif.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/model/__init__.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/model/guards.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/model/result.py +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/py.typed +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/tests/bossanova_benchmarks/bootstrap/data/README.md +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/tests/bossanova_benchmarks/insteval/data/README.md +0 -0
- {bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/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",
|
{bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/builders/dataframes.py
RENAMED
|
@@ -62,7 +62,7 @@ def build_params_dataframe(
|
|
|
62
62
|
|
|
63
63
|
- **asymp**: all inference columns (p_value last).
|
|
64
64
|
- **boot**: all inference columns; df = n_resamples.
|
|
65
|
-
- **perm**:
|
|
65
|
+
- **perm**: percentile CIs; df = n_valid_resamples.
|
|
66
66
|
- **cv**: PRE columns only.
|
|
67
67
|
- **None**: term + estimate only.
|
|
68
68
|
|
|
@@ -101,6 +101,8 @@ def build_params_dataframe(
|
|
|
101
101
|
|
|
102
102
|
if method == "perm":
|
|
103
103
|
data[Col.SE] = params_inference.se.tolist()
|
|
104
|
+
data[Col.CI_LOWER] = params_inference.ci_lower.tolist()
|
|
105
|
+
data[Col.CI_UPPER] = params_inference.ci_upper.tolist()
|
|
104
106
|
data[Col.STATISTIC] = params_inference.statistic.tolist()
|
|
105
107
|
data[Col.DF] = params_inference.df.tolist()
|
|
106
108
|
data[Col.P_VALUE] = params_inference.p_value.tolist()
|
|
@@ -224,7 +226,7 @@ def build_varying_corr_dataframe(
|
|
|
224
226
|
|
|
225
227
|
Args:
|
|
226
228
|
varying_spread: Variance component state containing ``rho``
|
|
227
|
-
(dict mapping ``"
|
|
229
|
+
(dict mapping ``"effect1:effect2"`` keys to correlation values).
|
|
228
230
|
|
|
229
231
|
Returns:
|
|
230
232
|
DataFrame with columns ``group``, ``effect1``, ``effect2``, ``corr``.
|
|
@@ -235,10 +237,8 @@ def build_varying_corr_dataframe(
|
|
|
235
237
|
corrs: list[float] = []
|
|
236
238
|
|
|
237
239
|
for key, value in varying_spread.rho.items():
|
|
238
|
-
# rho keys
|
|
239
|
-
|
|
240
|
-
# where corr_part = "Intercept:Days" -> key = "Intercept_Days"
|
|
241
|
-
parts = key.split("_", 1)
|
|
240
|
+
# rho keys use ":" separator: "Intercept:Days", "x_a:x_b"
|
|
241
|
+
parts = key.split(":", 1)
|
|
242
242
|
if len(parts) == 2:
|
|
243
243
|
effect1s.append(parts[0])
|
|
244
244
|
effect2s.append(parts[1])
|
|
@@ -331,8 +331,8 @@ def build_predictions_dataframe(pred: PredictionState) -> pl.DataFrame:
|
|
|
331
331
|
inference, and CV columns.
|
|
332
332
|
|
|
333
333
|
Returns:
|
|
334
|
-
DataFrame with
|
|
335
|
-
and optional CV columns.
|
|
334
|
+
DataFrame with optional grid columns (formula mode), ``fitted``,
|
|
335
|
+
optional ``link``, inference columns, and optional CV columns.
|
|
336
336
|
"""
|
|
337
337
|
data: dict[str, list] = {Col.FITTED: pred.fitted.tolist()}
|
|
338
338
|
if pred.link is not None:
|
|
@@ -348,6 +348,10 @@ def build_predictions_dataframe(pred: PredictionState) -> pl.DataFrame:
|
|
|
348
348
|
pl.Series(Col.CV_FOLD, pred.cv_fold),
|
|
349
349
|
)
|
|
350
350
|
|
|
351
|
+
# Prepend grid columns when formula-mode predictions
|
|
352
|
+
if pred.grid is not None:
|
|
353
|
+
result = pl.concat([pred.grid, result], how="horizontal")
|
|
354
|
+
|
|
351
355
|
return result
|
|
352
356
|
|
|
353
357
|
|
{bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/builders/resamples.py
RENAMED
|
@@ -15,18 +15,16 @@ from bossanova.internal.containers.schemas import (
|
|
|
15
15
|
Col,
|
|
16
16
|
ResamplesRawSchema,
|
|
17
17
|
)
|
|
18
|
-
from bossanova.internal.containers.structs.
|
|
19
|
-
ResamplesState,
|
|
20
|
-
)
|
|
18
|
+
from bossanova.internal.containers.structs.inference import ResamplesState
|
|
21
19
|
|
|
22
20
|
if TYPE_CHECKING:
|
|
23
21
|
import polars as pl
|
|
24
22
|
from numpy.typing import NDArray
|
|
25
23
|
|
|
26
|
-
from bossanova.internal.containers.structs.
|
|
24
|
+
from bossanova.internal.containers.structs.inference import (
|
|
27
25
|
InferenceState,
|
|
28
|
-
MeeState,
|
|
29
26
|
)
|
|
27
|
+
from bossanova.internal.containers.structs.marginal import MeeState
|
|
30
28
|
|
|
31
29
|
__all__ = [
|
|
32
30
|
"build_mee_resamples",
|
{bossanova-0.1.0.dev24 → bossanova-0.1.0.dev25}/bossanova/internal/containers/builders/state.py
RENAMED
|
@@ -7,17 +7,24 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
from bossanova.internal.containers.structs.
|
|
10
|
+
from bossanova.internal.containers.structs.fit import FitState
|
|
11
|
+
from bossanova.internal.containers.structs.inference import (
|
|
11
12
|
CVState,
|
|
12
|
-
FitState,
|
|
13
13
|
InferenceState,
|
|
14
14
|
JointTestState,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
)
|
|
16
|
+
from bossanova.internal.containers.structs.marginal import MeeState
|
|
17
|
+
from bossanova.internal.containers.structs.mixed import (
|
|
18
18
|
VaryingSpreadState,
|
|
19
19
|
VaryingState,
|
|
20
20
|
)
|
|
21
|
+
from bossanova.internal.containers.structs.prediction import (
|
|
22
|
+
PredictionConfig,
|
|
23
|
+
PredictionState,
|
|
24
|
+
)
|
|
25
|
+
from bossanova.internal.containers.structs.simulation import (
|
|
26
|
+
SimulationInferenceState,
|
|
27
|
+
)
|
|
21
28
|
|
|
22
29
|
if TYPE_CHECKING:
|
|
23
30
|
import polars as pl
|
|
@@ -384,11 +391,13 @@ def build_prediction_state(
|
|
|
384
391
|
*,
|
|
385
392
|
link: np.ndarray | None = None,
|
|
386
393
|
X_pred: np.ndarray | None = None,
|
|
394
|
+
config: PredictionConfig | None = None,
|
|
387
395
|
se: np.ndarray | None = None,
|
|
388
396
|
ci_lower: np.ndarray | None = None,
|
|
389
397
|
ci_upper: np.ndarray | None = None,
|
|
390
398
|
interval_type: str | None = None,
|
|
391
399
|
conf_level: float | None = None,
|
|
400
|
+
grid: "pl.DataFrame | None" = None,
|
|
392
401
|
) -> PredictionState:
|
|
393
402
|
"""Build a PredictionState from prediction computation.
|
|
394
403
|
|
|
@@ -397,11 +406,14 @@ def build_prediction_state(
|
|
|
397
406
|
link: Predicted values on link scale (for GLM/GLMM).
|
|
398
407
|
X_pred: Design matrix used for predictions. Stored so that
|
|
399
408
|
``.infer()`` can compute delta-method SEs on the correct X.
|
|
409
|
+
config: Prediction configuration for bootstrap replay.
|
|
400
410
|
se: Standard errors of predictions.
|
|
401
411
|
ci_lower: Lower interval bounds.
|
|
402
412
|
ci_upper: Upper interval bounds.
|
|
403
413
|
interval_type: Type of interval ("confidence" or "prediction").
|
|
404
414
|
conf_level: Confidence level for intervals.
|
|
415
|
+
grid: Grid DataFrame for formula-mode predictions. When present,
|
|
416
|
+
``build_predictions_dataframe()`` prepends these columns.
|
|
405
417
|
|
|
406
418
|
Returns:
|
|
407
419
|
Frozen PredictionState instance.
|
|
@@ -428,11 +440,13 @@ def build_prediction_state(
|
|
|
428
440
|
fitted=fitted,
|
|
429
441
|
link=link,
|
|
430
442
|
X_pred=X_pred,
|
|
443
|
+
config=config,
|
|
431
444
|
se=se,
|
|
432
445
|
ci_lower=ci_lower,
|
|
433
446
|
ci_upper=ci_upper,
|
|
434
447
|
interval_type=interval_type,
|
|
435
448
|
conf_level=conf_level,
|
|
449
|
+
grid=grid,
|
|
436
450
|
)
|
|
437
451
|
|
|
438
452
|
|
|
@@ -256,6 +256,8 @@ ParamsPerm = Schema(
|
|
|
256
256
|
Col.TERM: String,
|
|
257
257
|
Col.ESTIMATE: Float64,
|
|
258
258
|
Col.SE: Float64,
|
|
259
|
+
Col.CI_LOWER: Float64,
|
|
260
|
+
Col.CI_UPPER: Float64,
|
|
259
261
|
Col.STATISTIC: Float64,
|
|
260
262
|
Col.DF: Float64,
|
|
261
263
|
Col.P_VALUE: Float64,
|
|
@@ -512,7 +514,7 @@ VaryingSpreadInfer = Schema(
|
|
|
512
514
|
# Component naming conventions:
|
|
513
515
|
# - "sigma2": Residual variance
|
|
514
516
|
# - "tau2_<group>:<effect>": Random effect variance (e.g., "tau2_subject:Intercept")
|
|
515
|
-
# - "rho_<effect1
|
|
517
|
+
# - "rho_<effect1>:<effect2>": Correlation between random effects
|
|
516
518
|
# - "icc": Intraclass correlation coefficient
|
|
517
519
|
|
|
518
520
|
|