bossanova 0.1.0.dev12__tar.gz → 0.1.0.dev14__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.dev12 → bossanova-0.1.0.dev14}/.gitignore +1 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/PKG-INFO +1 -1
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/builders/specs.py +12 -52
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/schemas.py +36 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/structs/data.py +3 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/structs/explore.py +29 -2
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/bundle.py +77 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/common/formula_utils.py +43 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/compare/compare.py +14 -4
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/formula/parse.py +127 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/marginal/compute.py +74 -11
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/marginal/conditions.py +4 -3
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/marginal/contrasts.py +4 -11
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/marginal/explore.py +218 -78
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/simulation/__init__.py +10 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/simulation/harness.py +211 -118
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/simulation/metrics.py +29 -0
- bossanova-0.1.0.dev14/bossanova/internal/operations/simulation/power.py +244 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/model/core.py +224 -6
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/model/summary.py +23 -9
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/pyproject.toml +1 -1
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/LICENSE +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/README.md +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/data/README.md +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/data/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/data/advertising.csv +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/data/cake.csv +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/data/chickweight.csv +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/data/credit.csv +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/data/gammas.csv +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/data/mtcars.csv +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/data/penguins.csv +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/data/poker.csv +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/data/sleep.csv +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/data/titanic.csv +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/data/titanic_test.csv +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/data/titanic_train.csv +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/distributions/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/distributions/continuous.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/distributions/discrete.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/distributions/varying.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/expressions.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/builders/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/builders/data.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/builders/dataframes.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/builders/resamples.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/builders/results.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/builders/state.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/structs/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/structs/display.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/structs/formula.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/structs/specs.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/structs/state.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/validators.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/backend/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/backend/dispatch.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/backend/jax.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/backend/numpy.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/backend/protocol.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/batching.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/config.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/convergence.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/design/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/design/coding.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/design/names.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/design/reference.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/design/z_matrix.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/differentiation.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/distributions/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/distributions/algebra.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/distributions/base.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/distributions/core.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/distributions/derived.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/distributions/factories.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/distributions/plotting.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/distributions/probability.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/family/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/family/binomial.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/family/create.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/family/gamma.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/family/gaussian.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/family/links.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/family/poisson.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/family/response.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/family/schema.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/family/tdist.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/inference/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/inference/contrasts.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/inference/diagnostics.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/inference/estimation.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/inference/hypothesis.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/inference/information_criteria.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/inference/multiplicity.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/inference/profile.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/inference/sandwich.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/inference/satterthwaite.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/inference/wald_variance.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/inference/welch.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/linalg/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/linalg/qr.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/linalg/schur.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/linalg/sparse.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/linalg/svd.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/predict.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/rng.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/solvers/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/solvers/glm.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/solvers/glmer.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/solvers/heuristics.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/solvers/initialization.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/solvers/lambda_builder.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/solvers/lambda_sparse.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/solvers/lambda_template.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/solvers/lmer.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/solvers/optimize.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/solvers/pirls_sparse.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/solvers/quadrature.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/tolerances.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/transforms.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/variance.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/maths/weights.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/common/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/common/data_utils.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/common/factors.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/compare/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/compare/cv.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/compare/deviance.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/compare/f_test.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/compare/helpers.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/compare/lrt.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/compare/lrt_compare.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/compare/refit.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/contrasts.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/convergence.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/diagnostics.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/fit/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/fit/dispatch.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/fit/glm.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/fit/glmer.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/fit/lmer.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/fit/ols.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/fit/rank.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/formula/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/formula/design.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/formula/encoding.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/formula/evaluate.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/formula/evaluate_newdata.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/formula/evaluate_transforms.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/formula/helpers.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/formula/parser/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/formula/parser/expr.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/formula/parser/parser.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/formula/parser/scanner.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/formula/parser/token.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/formula/random_effects.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/infer/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/infer/asymptotic.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/infer/bootstrap.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/infer/cv.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/infer/mee.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/infer/params.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/infer/permutation.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/infer/prediction.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/infer/profile.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/infer/resample_bundle.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/infer/satterthwaite_emm.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/infer/simulation.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/marginal/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/marginal/emm.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/marginal/grid.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/marginal/inference.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/marginal/joint_tests.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/marginal/slopes.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/marginal/validation.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/predict.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/profile.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/rendering/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/rendering/latex.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/resample/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/resample/common.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/resample/core.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/resample/glm.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/resample/glmer.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/resample/lm.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/resample/lm_bca.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/resample/lm_operators.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/resample/lmer.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/resample/mixed.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/resample/results.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/resample/utils.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/simulation/dgp/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/simulation/dgp/generate.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/simulation/dgp/glm.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/simulation/dgp/glmer.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/simulation/dgp/lm.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/simulation/dgp/lmer.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/simulation/model_sim.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/transforms.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/varying.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/README.md +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/cognition.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/compare.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/core.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/core_data.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/core_protocols.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/core_sizing.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/core_viz.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/dag.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/design.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/fit.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/fit_builders.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/fit_layers.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/helpers.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/lattice.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/layout.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/mem.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/params.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/predict.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/profile.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/ranef.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/relationships.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/resamples.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/resid.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/viz/vif.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/model/__init__.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/model/guards.py +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/py.typed +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/tests/bossanova_benchmarks/bootstrap/data/README.md +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/tests/bossanova_benchmarks/insteval/data/README.md +0 -0
- {bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/tests/bossanova_tests/hypothesis/README.md +0 -0
{bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/builders/specs.py
RENAMED
|
@@ -145,9 +145,9 @@ def build_model_spec_from_formula(
|
|
|
145
145
|
) -> ModelSpec:
|
|
146
146
|
"""Build ModelSpec by parsing a formula string and resolving defaults.
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
Parses the formula into an AST to extract response variable, fixed terms,
|
|
149
149
|
and random effect terms without requiring data. Validates formula syntax
|
|
150
|
-
|
|
150
|
+
and resolves estimation method from family and RE presence.
|
|
151
151
|
|
|
152
152
|
Args:
|
|
153
153
|
formula: R-style model formula (e.g., ``"y ~ x + (1|group)"``).
|
|
@@ -173,67 +173,27 @@ def build_model_spec_from_formula(
|
|
|
173
173
|
if "~" not in formula:
|
|
174
174
|
raise ValueError(f"Invalid formula: {formula!r}. Formula must contain '~'.")
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
response = strip_backticks(lhs.strip())
|
|
178
|
-
|
|
179
|
-
# Handle LHS transforms like rank(y), log(y), zscore(rank(y)) — unwrap all layers
|
|
180
|
-
while True:
|
|
181
|
-
lhs_func_match = re.match(r"^([a-zA-Z_]\w*)\((.+)\)$", response)
|
|
182
|
-
if not lhs_func_match:
|
|
183
|
-
break
|
|
184
|
-
response = strip_backticks(lhs_func_match.group(2).strip())
|
|
185
|
-
|
|
186
|
-
if not response:
|
|
187
|
-
raise ValueError(
|
|
188
|
-
f"Invalid formula: {formula!r}. No response variable specified."
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
# Expand || and / syntax before validation (these are valid lme4 syntax
|
|
192
|
-
# that the Scanner/Parser doesn't handle directly)
|
|
193
|
-
from bossanova.internal.operations.formula.parse import (
|
|
194
|
-
expand_double_verts,
|
|
195
|
-
expand_nested_syntax,
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
expanded, _ = expand_double_verts(formula)
|
|
199
|
-
expanded, _ = expand_nested_syntax(expanded)
|
|
200
|
-
|
|
201
|
-
# Validate expanded formula syntax via the real parser
|
|
202
|
-
from bossanova.internal.operations.formula.parser import Parser, Scanner
|
|
176
|
+
from bossanova.internal.operations.formula.parse import extract_formula_structure
|
|
203
177
|
|
|
204
178
|
try:
|
|
205
|
-
|
|
206
|
-
Parser(tokens).parse()
|
|
179
|
+
structure = extract_formula_structure(formula)
|
|
207
180
|
except Exception as exc:
|
|
208
181
|
raise ValueError(f"Invalid formula: {formula!r}. {exc}") from exc
|
|
209
182
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
re_pattern = r"\([^)]*\|[^)]*\)"
|
|
215
|
-
random_terms = tuple(re.findall(re_pattern, expanded_rhs))
|
|
216
|
-
has_re = len(random_terms) > 0
|
|
217
|
-
|
|
218
|
-
# Extract fixed terms by removing random effects from RHS
|
|
219
|
-
fixed_rhs = re.sub(re_pattern, "", expanded_rhs)
|
|
220
|
-
raw_terms = [strip_backticks(t.strip()) for t in fixed_rhs.split("+") if t.strip()]
|
|
221
|
-
|
|
222
|
-
# Build fixed terms list (add Intercept unless suppressed with 0 or -1)
|
|
223
|
-
has_intercept = not any(t in ("0", "-1") for t in raw_terms)
|
|
224
|
-
fixed_terms: list[str] = ["Intercept"] if has_intercept else []
|
|
225
|
-
fixed_terms.extend(t for t in raw_terms if t not in ("0", "-1", "1"))
|
|
226
|
-
fixed_terms_tuple = tuple(fixed_terms) if fixed_terms else ("Intercept",)
|
|
183
|
+
if structure.response_var is None:
|
|
184
|
+
raise ValueError(
|
|
185
|
+
f"Invalid formula: {formula!r}. No response variable specified."
|
|
186
|
+
)
|
|
227
187
|
|
|
228
188
|
return build_model_spec(
|
|
229
189
|
formula=formula,
|
|
230
190
|
family=family,
|
|
231
191
|
link=link,
|
|
232
192
|
method=method,
|
|
233
|
-
has_random_effects=
|
|
234
|
-
response_var=
|
|
235
|
-
fixed_terms=
|
|
236
|
-
random_terms=
|
|
193
|
+
has_random_effects=structure.has_random_effects,
|
|
194
|
+
response_var=strip_backticks(structure.response_var),
|
|
195
|
+
fixed_terms=structure.fixed_term_names,
|
|
196
|
+
random_terms=structure.random_terms_raw,
|
|
237
197
|
)
|
|
238
198
|
|
|
239
199
|
|
|
@@ -34,6 +34,7 @@ __all__ = [
|
|
|
34
34
|
"ParamsBoot",
|
|
35
35
|
"ParamsCv",
|
|
36
36
|
"ParamsPerm",
|
|
37
|
+
"PowerSummaryCols",
|
|
37
38
|
"PredictionsAsymp",
|
|
38
39
|
"PredictionsBase",
|
|
39
40
|
"PredictionsCv",
|
|
@@ -164,6 +165,20 @@ class Col:
|
|
|
164
165
|
SIM_MEAN: str = "sim_mean"
|
|
165
166
|
SIM_SD: str = "sim_sd"
|
|
166
167
|
|
|
168
|
+
# -- Power analysis --
|
|
169
|
+
POWER: str = "power"
|
|
170
|
+
POWER_CI_LOWER: str = "power_ci_lower"
|
|
171
|
+
POWER_CI_UPPER: str = "power_ci_upper"
|
|
172
|
+
TRUE_VALUE: str = "true_value"
|
|
173
|
+
COVERAGE: str = "coverage"
|
|
174
|
+
BIAS: str = "bias"
|
|
175
|
+
RMSE: str = "rmse"
|
|
176
|
+
MEAN_SE: str = "mean_se"
|
|
177
|
+
EMPIRICAL_SE: str = "empirical_se"
|
|
178
|
+
N_SIMS: str = "n_sims"
|
|
179
|
+
N_FAILED: str = "n_failed"
|
|
180
|
+
N: str = "n"
|
|
181
|
+
|
|
167
182
|
# -- CV diagnostics --
|
|
168
183
|
CV_RMSE: str = "cv_rmse"
|
|
169
184
|
CV_MAE: str = "cv_mae"
|
|
@@ -277,6 +292,27 @@ AugmentedDataCols = (Col.FITTED, Col.RESID, Col.HAT, Col.STD_RESID, Col.COOKSD)
|
|
|
277
292
|
SimulationsInferCols = (Col.SIM_MEAN, Col.SIM_SD, "sim_q025", "sim_q975")
|
|
278
293
|
|
|
279
294
|
|
|
295
|
+
# =============================================================================
|
|
296
|
+
# Power analysis columns (rows = grid points × terms)
|
|
297
|
+
# =============================================================================
|
|
298
|
+
|
|
299
|
+
# Note: Grid columns (n, sigma, coef values) are dynamic — only metric columns are fixed
|
|
300
|
+
PowerSummaryCols = (
|
|
301
|
+
Col.TERM,
|
|
302
|
+
Col.TRUE_VALUE,
|
|
303
|
+
Col.POWER,
|
|
304
|
+
Col.POWER_CI_LOWER,
|
|
305
|
+
Col.POWER_CI_UPPER,
|
|
306
|
+
Col.COVERAGE,
|
|
307
|
+
Col.BIAS,
|
|
308
|
+
Col.RMSE,
|
|
309
|
+
Col.MEAN_SE,
|
|
310
|
+
Col.EMPIRICAL_SE,
|
|
311
|
+
Col.N_SIMS,
|
|
312
|
+
Col.N_FAILED,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
280
316
|
# =============================================================================
|
|
281
317
|
# .effects Schemas (rows = grid points for EMM/marginal effects)
|
|
282
318
|
# =============================================================================
|
{bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/structs/data.py
RENAMED
|
@@ -196,6 +196,9 @@ class DataBundle:
|
|
|
196
196
|
# Random effects metadata
|
|
197
197
|
re_metadata: REMetadata | None = field(default=None)
|
|
198
198
|
|
|
199
|
+
# Binary response level mapping (e.g. ("No", "Yes") means No→0, Yes→1)
|
|
200
|
+
response_levels: tuple[str, ...] | None = field(default=None)
|
|
201
|
+
|
|
199
202
|
# Rank deficiency info (None means full rank or not yet checked)
|
|
200
203
|
rank_info: RankInfo | None = field(default=None)
|
|
201
204
|
|
{bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/containers/structs/explore.py
RENAMED
|
@@ -44,14 +44,41 @@ class ExploreFormula:
|
|
|
44
44
|
means use n_levels - 1, i.e., maximum degree).
|
|
45
45
|
conditions: Tuple of Condition objects specifying conditioning variables.
|
|
46
46
|
focal_at_values: Specific values to evaluate the focal variable at
|
|
47
|
-
(e.g., from ``Days[0, 3, 6, 9]`` syntax). None means use all levels.
|
|
47
|
+
(e.g., from ``Days@[0, 3, 6, 9]`` syntax). None means use all levels.
|
|
48
|
+
focal_at_range: Number of evenly-spaced values across the focal variable's
|
|
49
|
+
range (e.g., from ``Days@range(5)`` syntax). None means not set.
|
|
50
|
+
focal_at_quantile: Number of quantile values for the focal variable
|
|
51
|
+
(e.g., from ``Days@quantile(3)`` syntax). None means not set.
|
|
48
52
|
"""
|
|
49
53
|
|
|
50
54
|
focal_var: str = field(validator=validators.instance_of(str))
|
|
51
55
|
contrast_type: str | None = field(default=None)
|
|
52
56
|
contrast_degree: int | None = field(default=None)
|
|
53
57
|
conditions: tuple[Condition, ...] = field(factory=tuple, converter=tuple)
|
|
54
|
-
focal_at_values: tuple[float, ...] | None = field(default=None)
|
|
58
|
+
focal_at_values: tuple[float | str, ...] | None = field(default=None)
|
|
59
|
+
focal_at_range: int | None = field(default=None)
|
|
60
|
+
focal_at_quantile: int | None = field(default=None)
|
|
61
|
+
|
|
62
|
+
def __attrs_post_init__(self) -> None:
|
|
63
|
+
"""Validate at most one focal at-spec is set."""
|
|
64
|
+
n_set = sum(
|
|
65
|
+
x is not None
|
|
66
|
+
for x in (self.focal_at_values, self.focal_at_range, self.focal_at_quantile)
|
|
67
|
+
)
|
|
68
|
+
if n_set > 1:
|
|
69
|
+
raise ValueError(
|
|
70
|
+
"At most one of focal_at_values, focal_at_range, focal_at_quantile "
|
|
71
|
+
"may be set."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def has_focal_at(self) -> bool:
|
|
76
|
+
"""Return True if any focal at-spec is set."""
|
|
77
|
+
return (
|
|
78
|
+
self.focal_at_values is not None
|
|
79
|
+
or self.focal_at_range is not None
|
|
80
|
+
or self.focal_at_quantile is not None
|
|
81
|
+
)
|
|
55
82
|
|
|
56
83
|
@property
|
|
57
84
|
def has_contrast(self) -> bool:
|
|
@@ -71,6 +71,17 @@ def build_bundle_from_data(
|
|
|
71
71
|
if response_var not in data.columns:
|
|
72
72
|
raise ValueError(f"Response variable '{response_var}' not found in data")
|
|
73
73
|
|
|
74
|
+
# Auto-encode string/categorical response for binomial family
|
|
75
|
+
response_levels: tuple[str, ...] | None = None
|
|
76
|
+
if spec.family == "binomial":
|
|
77
|
+
import polars as pl
|
|
78
|
+
|
|
79
|
+
response_dtype = data[response_var].dtype
|
|
80
|
+
if response_dtype in (pl.String, pl.Categorical) or isinstance(
|
|
81
|
+
response_dtype, pl.Enum
|
|
82
|
+
):
|
|
83
|
+
data, response_levels = _encode_binary_response(data, response_var)
|
|
84
|
+
|
|
74
85
|
# Functional formula API: parse -> build -> return learned spec
|
|
75
86
|
formula_spec = parse_formula(
|
|
76
87
|
formula,
|
|
@@ -150,6 +161,7 @@ def build_bundle_from_data(
|
|
|
150
161
|
re_metadata=re_metadata,
|
|
151
162
|
factor_levels={k: list(v) for k, v in formula_spec.factors.items()},
|
|
152
163
|
contrast_types=dict(formula_spec.contrast_types),
|
|
164
|
+
response_levels=response_levels,
|
|
153
165
|
rank_info=rank_info,
|
|
154
166
|
)
|
|
155
167
|
|
|
@@ -352,6 +364,48 @@ def _raise_missing_detail(
|
|
|
352
364
|
)
|
|
353
365
|
|
|
354
366
|
|
|
367
|
+
def _encode_binary_response(
|
|
368
|
+
data: pl.DataFrame,
|
|
369
|
+
response_var: str,
|
|
370
|
+
) -> tuple[pl.DataFrame, tuple[str, ...]]:
|
|
371
|
+
"""Auto-encode a string/categorical response for binomial models.
|
|
372
|
+
|
|
373
|
+
Sorts unique levels alphabetically (matching R's ``factor()`` ordering)
|
|
374
|
+
and maps the first level to 0 and the second to 1.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
data: Input DataFrame with a string/categorical response column.
|
|
378
|
+
response_var: Name of the response column.
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
Tuple of (modified DataFrame with numeric response, level mapping).
|
|
382
|
+
The level mapping is ``(level_for_0, level_for_1)``.
|
|
383
|
+
|
|
384
|
+
Raises:
|
|
385
|
+
ValueError: If the response column does not have exactly 2 unique levels.
|
|
386
|
+
"""
|
|
387
|
+
import polars as pl
|
|
388
|
+
|
|
389
|
+
col = data[response_var]
|
|
390
|
+
levels = sorted(col.cast(pl.String).unique().drop_nulls().to_list())
|
|
391
|
+
if len(levels) != 2:
|
|
392
|
+
raise ValueError(
|
|
393
|
+
f"Binomial family with string response requires exactly 2 levels, "
|
|
394
|
+
f"but '{response_var}' has {len(levels)}: {levels}. "
|
|
395
|
+
f"Use a numeric 0/1 response or a two-level factor."
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
# Encode: first alphabetically → 0, second → 1
|
|
399
|
+
encoded = (
|
|
400
|
+
pl.when(pl.col(response_var).cast(pl.String) == levels[1])
|
|
401
|
+
.then(1.0)
|
|
402
|
+
.otherwise(0.0)
|
|
403
|
+
.alias(response_var)
|
|
404
|
+
)
|
|
405
|
+
data = data.with_columns(encoded)
|
|
406
|
+
return data, tuple(levels)
|
|
407
|
+
|
|
408
|
+
|
|
355
409
|
def _validate_response(family: str, y: np.ndarray, response_var: str) -> None:
|
|
356
410
|
"""Family-specific response variable validation.
|
|
357
411
|
|
|
@@ -373,6 +427,15 @@ def _validate_response(family: str, y: np.ndarray, response_var: str) -> None:
|
|
|
373
427
|
f"Check your data or use a different family (e.g., gaussian)."
|
|
374
428
|
)
|
|
375
429
|
|
|
430
|
+
if family == "binomial":
|
|
431
|
+
if np.any(y < 0) or np.any(y > 1):
|
|
432
|
+
warnings.warn(
|
|
433
|
+
f"Binomial family expects response values in [0, 1], "
|
|
434
|
+
f"but '{response_var}' has values outside this range. "
|
|
435
|
+
f"This matches R's behavior (non-integer successes warning).",
|
|
436
|
+
stacklevel=4,
|
|
437
|
+
)
|
|
438
|
+
|
|
376
439
|
|
|
377
440
|
def _reindex_groups_after_na_drop(
|
|
378
441
|
*,
|
|
@@ -514,6 +577,20 @@ def _build_random_effects(
|
|
|
514
577
|
build_random_effects_from_spec,
|
|
515
578
|
)
|
|
516
579
|
|
|
580
|
+
# Ensure String columns known as factors are Enum before RE construction.
|
|
581
|
+
# build_design_matrices handles this for the FE path, but the RE path
|
|
582
|
+
# receives the original DataFrame. Without this, factor() calls in RE
|
|
583
|
+
# terms (e.g. "(factor(x)|group)") crash in encode_categorical.
|
|
584
|
+
import polars as pl
|
|
585
|
+
|
|
586
|
+
if formula_spec.factors:
|
|
587
|
+
casts = []
|
|
588
|
+
for col_name, levels in formula_spec.factors.items():
|
|
589
|
+
if col_name in data.columns and data[col_name].dtype == pl.String:
|
|
590
|
+
casts.append(pl.col(col_name).cast(pl.Enum(levels)))
|
|
591
|
+
if casts:
|
|
592
|
+
data = data.with_columns(casts)
|
|
593
|
+
|
|
517
594
|
re_info = build_random_effects_from_spec(formula_spec, data)
|
|
518
595
|
if re_info is None:
|
|
519
596
|
return None, None
|
|
@@ -4,6 +4,7 @@ import re
|
|
|
4
4
|
|
|
5
5
|
__all__ = [
|
|
6
6
|
"build_ablated_formula",
|
|
7
|
+
"parse_value",
|
|
7
8
|
"remove_predictor_from_formula",
|
|
8
9
|
]
|
|
9
10
|
|
|
@@ -55,6 +56,48 @@ def build_ablated_formula(
|
|
|
55
56
|
return f"{response_var} ~ {' + '.join(rhs_parts)}"
|
|
56
57
|
|
|
57
58
|
|
|
59
|
+
def parse_value(s: str) -> float | str:
|
|
60
|
+
"""Parse a single value as float or string.
|
|
61
|
+
|
|
62
|
+
Tries to parse as float first; falls back to unquoted or quoted string.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
s: String to parse.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Parsed value as float or string.
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
ValueError: If the string is empty.
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
>>> parse_value("3.14")
|
|
75
|
+
3.14
|
|
76
|
+
>>> parse_value("hello")
|
|
77
|
+
'hello'
|
|
78
|
+
>>> parse_value("'quoted'")
|
|
79
|
+
'quoted'
|
|
80
|
+
"""
|
|
81
|
+
s = s.strip()
|
|
82
|
+
if not s:
|
|
83
|
+
raise ValueError("Empty value")
|
|
84
|
+
|
|
85
|
+
# Try to parse as number
|
|
86
|
+
try:
|
|
87
|
+
return float(s)
|
|
88
|
+
except ValueError:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
# Remove quotes if present
|
|
92
|
+
if (s.startswith('"') and s.endswith('"')) or (
|
|
93
|
+
s.startswith("'") and s.endswith("'")
|
|
94
|
+
):
|
|
95
|
+
return s[1:-1]
|
|
96
|
+
|
|
97
|
+
# Return as-is (unquoted string, e.g., factor level)
|
|
98
|
+
return s
|
|
99
|
+
|
|
100
|
+
|
|
58
101
|
def remove_predictor_from_formula(formula: str, predictor: str) -> str:
|
|
59
102
|
"""Remove a predictor term from a formula string.
|
|
60
103
|
|
{bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/compare/compare.py
RENAMED
|
@@ -56,6 +56,7 @@ def compare(
|
|
|
56
56
|
cv: int | str = 5,
|
|
57
57
|
seed: int | None = None,
|
|
58
58
|
metric: str = "mse",
|
|
59
|
+
precision: int | None = 4,
|
|
59
60
|
) -> pl.DataFrame:
|
|
60
61
|
"""Compare nested statistical models.
|
|
61
62
|
|
|
@@ -89,6 +90,8 @@ def compare(
|
|
|
89
90
|
cv: For CV comparison only. Number of folds or "loo" for leave-one-out.
|
|
90
91
|
seed: For CV comparison only. Random seed for reproducible splits.
|
|
91
92
|
metric: For CV comparison only. Error metric ("mse", "rmse", "mae").
|
|
93
|
+
precision: Number of decimal places to round float columns to.
|
|
94
|
+
Default is 4. Pass ``None`` to return full (unrounded) precision.
|
|
92
95
|
|
|
93
96
|
Returns:
|
|
94
97
|
DataFrame with comparison results. Columns depend on method:
|
|
@@ -220,14 +223,21 @@ def compare(
|
|
|
220
223
|
|
|
221
224
|
# Dispatch to appropriate comparison function
|
|
222
225
|
if method == "f":
|
|
223
|
-
|
|
226
|
+
result = compare_f_test(model_list)
|
|
224
227
|
elif method == "lrt":
|
|
225
|
-
|
|
228
|
+
result = compare_lrt(model_list)
|
|
226
229
|
elif method == "deviance":
|
|
227
|
-
|
|
230
|
+
result = compare_deviance(model_list, test=test)
|
|
228
231
|
elif method == "cv":
|
|
229
|
-
|
|
232
|
+
result = compare_cv(model_list, cv=cv, seed=seed, metric=metric)
|
|
230
233
|
else:
|
|
231
234
|
raise ValueError(
|
|
232
235
|
f"Unknown method: {method}. Use 'auto', 'f', 'lrt', 'deviance', or 'cv'."
|
|
233
236
|
)
|
|
237
|
+
|
|
238
|
+
# Round float columns to requested precision
|
|
239
|
+
if precision is not None:
|
|
240
|
+
float_cols = [c for c in result.columns if result[c].dtype == pl.Float64]
|
|
241
|
+
result = result.with_columns(pl.col(float_cols).round(precision))
|
|
242
|
+
|
|
243
|
+
return result
|
{bossanova-0.1.0.dev12 → bossanova-0.1.0.dev14}/bossanova/internal/operations/formula/parse.py
RENAMED
|
@@ -5,6 +5,8 @@ from __future__ import annotations
|
|
|
5
5
|
import re
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
+
from attrs import frozen
|
|
9
|
+
|
|
8
10
|
from bossanova.internal.operations.formula.parser import Parser, Scanner
|
|
9
11
|
from bossanova.internal.operations.formula.parser.expr import (
|
|
10
12
|
Binary,
|
|
@@ -28,8 +30,10 @@ if TYPE_CHECKING:
|
|
|
28
30
|
|
|
29
31
|
__all__ = [
|
|
30
32
|
"FormulaError",
|
|
33
|
+
"FormulaStructure",
|
|
31
34
|
"expand_double_verts",
|
|
32
35
|
"expand_nested_syntax",
|
|
36
|
+
"extract_formula_structure",
|
|
33
37
|
"parse_formula",
|
|
34
38
|
]
|
|
35
39
|
|
|
@@ -235,6 +239,129 @@ def extract_rhs_terms(
|
|
|
235
239
|
return rhs_terms, re_terms, has_intercept
|
|
236
240
|
|
|
237
241
|
|
|
242
|
+
@frozen
|
|
243
|
+
class FormulaStructure:
|
|
244
|
+
"""Data-free formula structure extracted from an AST.
|
|
245
|
+
|
|
246
|
+
Contains the same structural information as a full ``parse_formula()``
|
|
247
|
+
call but without requiring data for categorical detection. Used by
|
|
248
|
+
``build_model_spec_from_formula()`` to replace regex-based extraction.
|
|
249
|
+
|
|
250
|
+
Attributes:
|
|
251
|
+
response_var: Response variable name, or None for RHS-only formulas.
|
|
252
|
+
response_transform: Tuple of LHS transforms (innermost-first),
|
|
253
|
+
or None if no transforms.
|
|
254
|
+
fixed_term_names: Human-readable fixed-effect term names
|
|
255
|
+
(e.g. ``["Intercept", "x", "group"]``).
|
|
256
|
+
has_intercept: Whether the formula includes an intercept.
|
|
257
|
+
has_random_effects: Whether the formula contains ``|`` terms.
|
|
258
|
+
random_terms_raw: Raw string representations of RE terms
|
|
259
|
+
(e.g. ``["(1|subject)"]``).
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
response_var: str | None
|
|
263
|
+
response_transform: tuple[str, ...] | None
|
|
264
|
+
fixed_term_names: tuple[str, ...]
|
|
265
|
+
has_intercept: bool
|
|
266
|
+
has_random_effects: bool
|
|
267
|
+
random_terms_raw: tuple[str, ...]
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def extract_formula_structure(formula: str) -> FormulaStructure:
|
|
271
|
+
"""Extract formula structure from a formula string without data.
|
|
272
|
+
|
|
273
|
+
Parses the formula into an AST and walks it to extract response
|
|
274
|
+
variable, fixed-effect term names, intercept presence, and
|
|
275
|
+
random-effect term strings. Does not require data (no categorical
|
|
276
|
+
detection).
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
formula: R-style formula string (e.g. ``"y ~ x + (1|group)"``).
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
FormulaStructure with extracted information.
|
|
283
|
+
|
|
284
|
+
Raises:
|
|
285
|
+
FormulaError: If formula syntax is invalid.
|
|
286
|
+
"""
|
|
287
|
+
# Known transforms that can appear on the LHS
|
|
288
|
+
_KNOWN_RESPONSE_TRANSFORMS = set(STATEFUL_TRANSFORMS) | {"log", "log10", "sqrt"}
|
|
289
|
+
|
|
290
|
+
# Pre-process: expand || and / syntax
|
|
291
|
+
expanded, _ = expand_double_verts(formula)
|
|
292
|
+
expanded, _ = expand_nested_syntax(expanded)
|
|
293
|
+
|
|
294
|
+
# Tokenize and parse
|
|
295
|
+
tokens = Scanner(expanded).scan(add_intercept=False)
|
|
296
|
+
ast = Parser(tokens, expanded).parse()
|
|
297
|
+
|
|
298
|
+
response_var: str | None = None
|
|
299
|
+
response_transform: tuple[str, ...] | None = None
|
|
300
|
+
|
|
301
|
+
if isinstance(ast, Binary) and ast.operator.kind == "TILDE":
|
|
302
|
+
# Extract response from LHS
|
|
303
|
+
if isinstance(ast.left, Call) and isinstance(ast.left.callee, Variable):
|
|
304
|
+
chain: list[str] = []
|
|
305
|
+
node = ast.left
|
|
306
|
+
while isinstance(node, Call) and isinstance(node.callee, Variable):
|
|
307
|
+
func_name = node.callee.name.lexeme
|
|
308
|
+
if func_name not in _KNOWN_RESPONSE_TRANSFORMS:
|
|
309
|
+
raise FormulaError(
|
|
310
|
+
f"Unknown response transform '{func_name}'. "
|
|
311
|
+
f"Supported: {sorted(_KNOWN_RESPONSE_TRANSFORMS)}",
|
|
312
|
+
formula=formula,
|
|
313
|
+
)
|
|
314
|
+
if not node.args:
|
|
315
|
+
raise FormulaError(
|
|
316
|
+
f"{func_name}() requires an argument",
|
|
317
|
+
formula=formula,
|
|
318
|
+
)
|
|
319
|
+
chain.append(func_name)
|
|
320
|
+
node = node.args[0]
|
|
321
|
+
var_name = extract_name(node)
|
|
322
|
+
if var_name is None:
|
|
323
|
+
raise FormulaError(
|
|
324
|
+
f"{chain[-1]}() argument must be a variable name",
|
|
325
|
+
formula=formula,
|
|
326
|
+
)
|
|
327
|
+
response_var = var_name
|
|
328
|
+
response_transform = tuple(reversed(chain))
|
|
329
|
+
else:
|
|
330
|
+
response_var = extract_name(ast.left)
|
|
331
|
+
rhs_terms, re_terms, has_intercept = extract_rhs_terms(ast.right)
|
|
332
|
+
else:
|
|
333
|
+
rhs_terms, re_terms, has_intercept = extract_rhs_terms(ast)
|
|
334
|
+
|
|
335
|
+
# Extract human-readable fixed term names from AST nodes
|
|
336
|
+
fixed_names: list[str] = []
|
|
337
|
+
if has_intercept:
|
|
338
|
+
fixed_names.append("Intercept")
|
|
339
|
+
for term in rhs_terms:
|
|
340
|
+
name = extract_name(term)
|
|
341
|
+
# For Call nodes like factor(x), extract the argument name
|
|
342
|
+
if name is None and isinstance(term, Call) and term.args:
|
|
343
|
+
name = extract_name(term.args[0])
|
|
344
|
+
if name is not None and name not in ("0", "1", "-1"):
|
|
345
|
+
fixed_names.append(name)
|
|
346
|
+
|
|
347
|
+
# Extract RE term strings from the original expanded formula
|
|
348
|
+
re_pattern = r"\([^)]*\|[^)]*\)"
|
|
349
|
+
_, expanded_rhs = expanded.split("~", 1) if "~" in expanded else ("", expanded)
|
|
350
|
+
random_terms_raw = tuple(re.findall(re_pattern, expanded_rhs))
|
|
351
|
+
|
|
352
|
+
if not fixed_names:
|
|
353
|
+
fixed_names = ["Intercept"]
|
|
354
|
+
|
|
355
|
+
return FormulaStructure(
|
|
356
|
+
response_var=response_var,
|
|
357
|
+
response_transform=response_transform,
|
|
358
|
+
fixed_term_names=tuple(fixed_names),
|
|
359
|
+
has_intercept=has_intercept,
|
|
360
|
+
has_random_effects=len(re_terms) > 0,
|
|
361
|
+
random_terms_raw=random_terms_raw,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
|
|
238
365
|
# =============================================================================
|
|
239
366
|
# Formula pre-processing (|| and / syntax expansion)
|
|
240
367
|
# =============================================================================
|