bossanova 0.1.0.dev22__tar.gz → 0.1.0.dev24__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.
Files changed (239) hide show
  1. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/PKG-INFO +1 -1
  2. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/emm.py +33 -4
  3. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/simulation/model_sim.py +22 -6
  4. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/README.md +0 -1
  5. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/__init__.py +0 -3
  6. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/helpers.py +1 -43
  7. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/predict.py +198 -11
  8. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/model/core.py +9 -15
  9. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/pyproject.toml +1 -1
  10. bossanova-0.1.0.dev22/bossanova/internal/viz/fit.py +0 -571
  11. bossanova-0.1.0.dev22/bossanova/internal/viz/fit_builders.py +0 -324
  12. bossanova-0.1.0.dev22/bossanova/internal/viz/fit_layers.py +0 -453
  13. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/.gitignore +0 -0
  14. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/LICENSE +0 -0
  15. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/README.md +0 -0
  16. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/__init__.py +0 -0
  17. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/data/README.md +0 -0
  18. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/data/__init__.py +0 -0
  19. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/data/advertising.csv +0 -0
  20. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/data/cake.csv +0 -0
  21. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/data/chickweight.csv +0 -0
  22. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/data/credit.csv +0 -0
  23. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/data/gammas.csv +0 -0
  24. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/data/mtcars.csv +0 -0
  25. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/data/penguins.csv +0 -0
  26. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/data/poker.csv +0 -0
  27. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/data/sleep.csv +0 -0
  28. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/data/titanic.csv +0 -0
  29. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/data/titanic_test.csv +0 -0
  30. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/data/titanic_train.csv +0 -0
  31. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/distributions/__init__.py +0 -0
  32. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/distributions/continuous.py +0 -0
  33. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/distributions/discrete.py +0 -0
  34. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/distributions/varying.py +0 -0
  35. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/expressions.py +0 -0
  36. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/__init__.py +0 -0
  37. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/compare/__init__.py +0 -0
  38. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/compare/compare.py +0 -0
  39. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/compare/cv.py +0 -0
  40. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/compare/deviance.py +0 -0
  41. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/compare/f_test.py +0 -0
  42. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/compare/helpers.py +0 -0
  43. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/compare/ic.py +0 -0
  44. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/compare/lrt.py +0 -0
  45. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/compare/lrt_compare.py +0 -0
  46. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/compare/refit.py +0 -0
  47. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/__init__.py +0 -0
  48. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/builders/__init__.py +0 -0
  49. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/builders/dataframes.py +0 -0
  50. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/builders/resamples.py +0 -0
  51. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/builders/results.py +0 -0
  52. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/builders/specs.py +0 -0
  53. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/builders/state.py +0 -0
  54. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/schemas.py +0 -0
  55. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/structs/__init__.py +0 -0
  56. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/structs/data.py +0 -0
  57. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/structs/display.py +0 -0
  58. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/structs/explore.py +0 -0
  59. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/structs/formula.py +0 -0
  60. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/structs/specs.py +0 -0
  61. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/structs/state.py +0 -0
  62. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/containers/validators.py +0 -0
  63. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/design/__init__.py +0 -0
  64. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/design/coding.py +0 -0
  65. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/design/names.py +0 -0
  66. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/design/reference.py +0 -0
  67. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/design/z_matrix.py +0 -0
  68. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/fit/__init__.py +0 -0
  69. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/fit/convergence.py +0 -0
  70. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/fit/diagnostics.py +0 -0
  71. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/fit/dispatch.py +0 -0
  72. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/fit/glm.py +0 -0
  73. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/fit/glmer.py +0 -0
  74. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/fit/lmer.py +0 -0
  75. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/fit/ols.py +0 -0
  76. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/fit/predict.py +0 -0
  77. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/fit/varying.py +0 -0
  78. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/__init__.py +0 -0
  79. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/bundle.py +0 -0
  80. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/contrast_registry.py +0 -0
  81. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/contrast_specs.py +0 -0
  82. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/design.py +0 -0
  83. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/encoding.py +0 -0
  84. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/evaluate.py +0 -0
  85. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/evaluate_contrast.py +0 -0
  86. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/evaluate_newdata.py +0 -0
  87. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/evaluate_transforms.py +0 -0
  88. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/helpers.py +0 -0
  89. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/parse.py +0 -0
  90. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/parser/__init__.py +0 -0
  91. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/parser/expr.py +0 -0
  92. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/parser/parser.py +0 -0
  93. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/parser/scanner.py +0 -0
  94. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/parser/token.py +0 -0
  95. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/formula/random_effects.py +0 -0
  96. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/__init__.py +0 -0
  97. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/asymptotic.py +0 -0
  98. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/bootstrap.py +0 -0
  99. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/cv.py +0 -0
  100. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/dispatch.py +0 -0
  101. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/formula_utils.py +0 -0
  102. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/mee.py +0 -0
  103. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/params.py +0 -0
  104. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/permutation.py +0 -0
  105. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/prediction.py +0 -0
  106. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/profile.py +0 -0
  107. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/resample/__init__.py +0 -0
  108. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/resample/common.py +0 -0
  109. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/resample/core.py +0 -0
  110. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/resample/glmer.py +0 -0
  111. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/resample/lm_operators.py +0 -0
  112. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/resample/lmer.py +0 -0
  113. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/resample/results.py +0 -0
  114. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/resample/simulate.py +0 -0
  115. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/resample_bundle.py +0 -0
  116. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/satterthwaite_emm.py +0 -0
  117. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/infer/simulation.py +0 -0
  118. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/__init__.py +0 -0
  119. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/bracket_contrasts.py +0 -0
  120. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/compute.py +0 -0
  121. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/conditions.py +0 -0
  122. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/contrasts.py +0 -0
  123. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/explore.py +0 -0
  124. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/explore_parser.py +0 -0
  125. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/explore_scanner.py +0 -0
  126. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/factors.py +0 -0
  127. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/grid.py +0 -0
  128. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/inference.py +0 -0
  129. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/joint_tests.py +0 -0
  130. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/matrices.py +0 -0
  131. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/resolve.py +0 -0
  132. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/slopes.py +0 -0
  133. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/transforms.py +0 -0
  134. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/marginal/validation.py +0 -0
  135. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/__init__.py +0 -0
  136. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/backend/__init__.py +0 -0
  137. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/backend/dispatch.py +0 -0
  138. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/backend/jax.py +0 -0
  139. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/backend/numpy.py +0 -0
  140. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/backend/protocol.py +0 -0
  141. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/batching.py +0 -0
  142. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/config.py +0 -0
  143. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/convergence.py +0 -0
  144. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/differentiation.py +0 -0
  145. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/distributions/__init__.py +0 -0
  146. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/distributions/algebra.py +0 -0
  147. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/distributions/base.py +0 -0
  148. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/distributions/core.py +0 -0
  149. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/distributions/derived.py +0 -0
  150. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/distributions/factories.py +0 -0
  151. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/distributions/plotting.py +0 -0
  152. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/distributions/probability.py +0 -0
  153. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/family/__init__.py +0 -0
  154. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/family/binomial.py +0 -0
  155. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/family/create.py +0 -0
  156. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/family/gamma.py +0 -0
  157. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/family/gaussian.py +0 -0
  158. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/family/links.py +0 -0
  159. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/family/poisson.py +0 -0
  160. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/family/response.py +0 -0
  161. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/family/schema.py +0 -0
  162. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/family/tdist.py +0 -0
  163. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/inference/__init__.py +0 -0
  164. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/inference/diagnostics.py +0 -0
  165. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/inference/estimation.py +0 -0
  166. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/inference/hypothesis.py +0 -0
  167. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/inference/information_criteria.py +0 -0
  168. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/inference/multiplicity.py +0 -0
  169. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/inference/profile.py +0 -0
  170. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/inference/sandwich.py +0 -0
  171. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/inference/satterthwaite.py +0 -0
  172. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/inference/wald_variance.py +0 -0
  173. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/inference/welch.py +0 -0
  174. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/linalg/__init__.py +0 -0
  175. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/linalg/qr.py +0 -0
  176. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/linalg/rank.py +0 -0
  177. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/linalg/schur.py +0 -0
  178. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/linalg/sparse.py +0 -0
  179. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/linalg/svd.py +0 -0
  180. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/predict.py +0 -0
  181. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/rng.py +0 -0
  182. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/rounding.py +0 -0
  183. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/solvers/__init__.py +0 -0
  184. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/solvers/glm.py +0 -0
  185. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/solvers/glmer.py +0 -0
  186. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/solvers/heuristics.py +0 -0
  187. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/solvers/initialization.py +0 -0
  188. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/solvers/lambda_builder.py +0 -0
  189. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/solvers/lambda_sparse.py +0 -0
  190. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/solvers/lambda_template.py +0 -0
  191. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/solvers/lmer.py +0 -0
  192. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/solvers/optimize.py +0 -0
  193. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/solvers/pirls_sparse.py +0 -0
  194. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/solvers/quadrature.py +0 -0
  195. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/tolerances.py +0 -0
  196. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/transforms.py +0 -0
  197. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/variance.py +0 -0
  198. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/maths/weights.py +0 -0
  199. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/rendering/__init__.py +0 -0
  200. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/rendering/latex.py +0 -0
  201. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/rendering/markdown.py +0 -0
  202. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/simulation/__init__.py +0 -0
  203. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/simulation/dgp/__init__.py +0 -0
  204. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/simulation/dgp/generate.py +0 -0
  205. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/simulation/dgp/glm.py +0 -0
  206. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/simulation/dgp/glmer.py +0 -0
  207. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/simulation/dgp/lm.py +0 -0
  208. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/simulation/dgp/lmer.py +0 -0
  209. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/simulation/harness.py +0 -0
  210. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/simulation/metrics.py +0 -0
  211. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/simulation/power.py +0 -0
  212. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/cognition.py +0 -0
  213. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/compare.py +0 -0
  214. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/core.py +0 -0
  215. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/core_data.py +0 -0
  216. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/core_protocols.py +0 -0
  217. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/core_sizing.py +0 -0
  218. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/core_viz.py +0 -0
  219. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/dag.py +0 -0
  220. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/design.py +0 -0
  221. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/lattice.py +0 -0
  222. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/layout.py +0 -0
  223. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/mem.py +0 -0
  224. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/mem_forest.py +0 -0
  225. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/params.py +0 -0
  226. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/profile.py +0 -0
  227. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/ranef.py +0 -0
  228. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/relationships.py +0 -0
  229. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/resamples.py +0 -0
  230. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/resid.py +0 -0
  231. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/internal/viz/vif.py +0 -0
  232. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/model/__init__.py +0 -0
  233. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/model/guards.py +0 -0
  234. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/model/result.py +0 -0
  235. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/model/summary.py +0 -0
  236. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/bossanova/py.typed +0 -0
  237. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/tests/bossanova_benchmarks/bootstrap/data/README.md +0 -0
  238. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/tests/bossanova_benchmarks/insteval/data/README.md +0 -0
  239. {bossanova-0.1.0.dev22 → bossanova-0.1.0.dev24}/tests/bossanova_tests/hypothesis/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bossanova
3
- Version: 0.1.0.dev22
3
+ Version: 0.1.0.dev24
4
4
  Summary: Bridging statistical cultures with some jazz
5
5
  Author: Eshin Jolly
6
6
  License-Expression: MIT
@@ -653,8 +653,24 @@ def _compute_emm_crossed_mem(
653
653
  L_matrix = d[:, np.newaxis] * X_ref
654
654
 
655
655
  grid = pl.DataFrame(grid_data)
656
- if grid_vars:
657
- grid = grid.select(grid_vars + [c for c in grid.columns if c not in grid_vars])
656
+
657
+ # Add scalar-pinned conditions as constant columns (set_categoricals
658
+ # and at_overrides are applied to the design matrix but must also
659
+ # appear in the output grid so users can see the pinned values).
660
+ if resolved.set_categoricals:
661
+ for cond_var, cond_level in resolved.set_categoricals.items():
662
+ if cond_var not in grid.columns:
663
+ grid = grid.with_columns(pl.lit(cond_level).alias(cond_var))
664
+ if resolved.at_overrides:
665
+ for cond_var, cond_val in resolved.at_overrides.items():
666
+ if cond_var not in grid.columns:
667
+ grid = grid.with_columns(pl.lit(cond_val).alias(cond_var))
668
+
669
+ # Reorder: grid_vars + pinned conditions first, then focal
670
+ pin_cols = list(resolved.set_categoricals) + list(resolved.at_overrides)
671
+ leading = grid_vars + [c for c in pin_cols if c not in grid_vars]
672
+ if leading:
673
+ grid = grid.select(leading + [c for c in grid.columns if c not in leading])
658
674
 
659
675
  cond_str = " ~ " + ", ".join(grid_vars) if grid_vars else ""
660
676
  return build_mee_state(
@@ -770,8 +786,21 @@ def _compute_emm_crossed_gcomp(
770
786
  L_matrix = np.einsum("mnp,mn->mp", X_all, d_all) / X_all.shape[1] # (C*K, p)
771
787
 
772
788
  grid = pl.DataFrame(grid_data)
773
- if grid_vars:
774
- grid = grid.select(grid_vars + [c for c in grid.columns if c not in grid_vars])
789
+
790
+ # Add scalar-pinned conditions as constant columns (mirrors MEM path)
791
+ if resolved.set_categoricals:
792
+ for cond_var, cond_level in resolved.set_categoricals.items():
793
+ if cond_var not in grid.columns:
794
+ grid = grid.with_columns(pl.lit(cond_level).alias(cond_var))
795
+ if resolved.at_overrides:
796
+ for cond_var, cond_val in resolved.at_overrides.items():
797
+ if cond_var not in grid.columns:
798
+ grid = grid.with_columns(pl.lit(cond_val).alias(cond_var))
799
+
800
+ pin_cols = list(resolved.set_categoricals) + list(resolved.at_overrides)
801
+ leading = grid_vars + [c for c in pin_cols if c not in grid_vars]
802
+ if leading:
803
+ grid = grid.select(leading + [c for c in grid.columns if c not in leading])
775
804
 
776
805
  cond_str = " ~ " + ", ".join(grid_vars) if grid_vars else ""
777
806
  return build_mee_state(
@@ -96,13 +96,29 @@ def generate_data_from_spec(
96
96
  if isinstance(v, np.ndarray) and v.dtype.kind == "f"
97
97
  ]
98
98
 
99
+ # Build interaction columns from coef keys (e.g. "x1:x2" → x1 * x2)
99
100
  obs_n = sim_spec.n
100
- if continuous_vars:
101
- X = np.column_stack([np.ones(obs_n)] + [data_dict[v] for v in continuous_vars])
102
- else:
103
- X = np.ones((obs_n, 1))
104
-
105
- coef_names = ["Intercept"] + continuous_vars
101
+ interaction_cols: dict[str, np.ndarray] = {}
102
+ for name in sim_spec.coef:
103
+ if ":" not in name:
104
+ continue
105
+ parts = name.split(":")
106
+ # All parts must be sampled continuous variables
107
+ if not all(p in data_dict and data_dict[p].dtype.kind == "f" for p in parts):
108
+ continue
109
+ col = np.ones(obs_n)
110
+ for p in parts:
111
+ col = col * data_dict[p]
112
+ interaction_cols[name] = col
113
+
114
+ # Build X: intercept + main effects + interactions
115
+ interaction_names = list(interaction_cols.keys())
116
+ columns = [np.ones(obs_n)]
117
+ columns.extend(data_dict[v] for v in continuous_vars)
118
+ columns.extend(interaction_cols[name] for name in interaction_names)
119
+ X = np.column_stack(columns) if columns else np.ones((obs_n, 1))
120
+
121
+ coef_names = ["Intercept"] + continuous_vars + interaction_names
106
122
  coef_values = np.array([sim_spec.coef.get(name, 0.0) for name in coef_names])
107
123
 
108
124
  # Linear predictor
@@ -35,7 +35,6 @@ m.plot_resid()
35
35
  | `plot_resid` | Residual diagnostic grid (4 panels) |
36
36
  | `plot_predict` | Marginal predictions across predictor range |
37
37
  | `plot_explore` | Marginal effects/means visualization |
38
- | `plot_fit` | Composite diagnostic panel |
39
38
  | `plot_compare` | Multi-model coefficient comparison |
40
39
  | `plot_dag` | Causal DAG visualization |
41
40
  | `plot_lattice` | Model lattice (Hasse diagram) |
@@ -6,7 +6,6 @@ Post-estimation plots (require .fit()):
6
6
  plot_params: Coefficient forest plot with CIs
7
7
  plot_resid: Residual diagnostic panels (QQ, fitted vs residuals)
8
8
  plot_predict: Prediction plots with confidence bands
9
- plot_fit: Observed vs fitted scatter
10
9
  plot_explore: Marginal effects visualization
11
10
  plot_ranef: Random effects caterpillar plot (mixed models)
12
11
  plot_resamples: Bootstrap/permutation distribution plots
@@ -44,7 +43,6 @@ from bossanova.internal.viz.params import plot_params
44
43
  from bossanova.internal.viz.ranef import plot_ranef
45
44
  from bossanova.internal.viz.resid import plot_resid
46
45
  from bossanova.internal.viz.predict import plot_predict
47
- from bossanova.internal.viz.fit import plot_fit
48
46
  from bossanova.internal.viz.mem import plot_explore
49
47
  from bossanova.internal.viz.compare import plot_compare
50
48
  from bossanova.internal.viz.resamples import plot_resamples
@@ -75,7 +73,6 @@ __all__ = [
75
73
  "plot_dag",
76
74
  "plot_design",
77
75
  "plot_explore",
78
- "plot_fit",
79
76
  "plot_lattice",
80
77
  "plot_params",
81
78
  "plot_predict",
@@ -18,7 +18,6 @@ if TYPE_CHECKING:
18
18
  from matplotlib.axes import Axes
19
19
 
20
20
  __all__ = [
21
- "detect_blup_mode",
22
21
  "detect_ci_columns",
23
22
  "fill_ci_band",
24
23
  "get_grouping_factors",
@@ -59,8 +58,7 @@ def fill_ci_band(
59
58
  ) -> None:
60
59
  """Render sorted CI bands on a matplotlib axes.
61
60
 
62
- This is the shared rendering core used by both ``fit_layers.add_ci_band``
63
- and ``predict.add_ci_band``. It sorts data along x, then calls
61
+ Shared rendering core for CI bands. Sorts data along x, then calls
64
62
  ``ax.fill_between()`` once per hue level (or once if no hue).
65
63
 
66
64
  Args:
@@ -170,43 +168,3 @@ def get_grouping_factors(model: "Any") -> list[str]:
170
168
  return groups
171
169
 
172
170
  return []
173
-
174
-
175
- def detect_blup_mode(
176
- model: "Any",
177
- hue: str | None,
178
- col: str | None,
179
- row: str | None,
180
- blups: bool | None,
181
- ) -> bool:
182
- """Determine if BLUP mode should be enabled for a plot.
183
-
184
- Returns True if:
185
- - ``blups=True`` explicitly, OR
186
- - ``blups=None`` AND model is mixed AND any of ``hue``/``col``/``row``
187
- is a grouping factor (auto-detection).
188
-
189
- Args:
190
- model: A fitted bossanova model.
191
- hue: Hue variable name (or None).
192
- col: Column facet variable name (or None).
193
- row: Row facet variable name (or None).
194
- blups: Explicit BLUP mode setting. None triggers auto-detection.
195
-
196
- Returns:
197
- True if BLUP mode should be enabled.
198
- """
199
- if blups is not None:
200
- return blups
201
-
202
- # Only mixed models have grouping factors
203
- grouping_factors = get_grouping_factors(model)
204
- if not grouping_factors:
205
- return False
206
-
207
- # Check if any faceting/hue variable is a grouping factor
208
- for var in (hue, col, row):
209
- if var and var in grouping_factors:
210
- return True
211
-
212
- return False
@@ -36,6 +36,142 @@ if TYPE_CHECKING:
36
36
  __all__ = ["plot_predict"]
37
37
 
38
38
 
39
+ # ---------------------------------------------------------------------------
40
+ # Formula parsing helpers
41
+ # ---------------------------------------------------------------------------
42
+
43
+ _FORMULA_CHARS = {"~", "@", "(", "["}
44
+
45
+
46
+ def _is_formula(term: str) -> bool:
47
+ """Return True if *term* looks like formula syntax rather than a bare column name.
48
+
49
+ Args:
50
+ term: The user-supplied term string.
51
+
52
+ Returns:
53
+ True when the string contains ``~``, ``@``, or ``(``.
54
+ """
55
+ return bool(_FORMULA_CHARS & set(term))
56
+
57
+
58
+ def _parse_predict_formula(
59
+ formula: str,
60
+ model: "Any",
61
+ ) -> dict[str, Any]:
62
+ """Translate an explore-style formula into :func:`plot_predict` kwargs.
63
+
64
+ Parses ``formula`` using the existing :func:`parse_explore_formula`
65
+ machinery, then maps the resulting :class:`ExploreFormulaSpec` fields
66
+ onto the keyword arguments that :func:`plot_predict` understands.
67
+
68
+ Contrast formulas (``pairwise(treatment)``, ``Drug[A - B]``) are
69
+ rejected with a ``ValueError`` — use :func:`plot_explore` instead.
70
+
71
+ Args:
72
+ formula: An explore-style formula string (e.g. ``"age ~ sex"``).
73
+ model: A fitted bossanova model (used for data access and term
74
+ validation).
75
+
76
+ Returns:
77
+ Dict with keys: ``term``, and optionally ``hue``, ``col``, ``row``,
78
+ ``at``, ``n_points``, ``focal_values``.
79
+
80
+ Raises:
81
+ ValueError: If the formula contains contrasts.
82
+ """
83
+ from bossanova.internal.marginal import parse_explore_formula
84
+
85
+ spec = parse_explore_formula(formula)
86
+
87
+ # Reject contrast formulas — they belong in plot_explore
88
+ if spec.has_contrast:
89
+ raise ValueError(
90
+ f"Contrast formula {formula!r} is not supported by plot_predict(). "
91
+ "Use plot_explore() for contrasts."
92
+ )
93
+ # Also reject RHS bracket contrasts
94
+ if spec.has_rhs_contrasts:
95
+ raise ValueError(
96
+ f"Contrast formula {formula!r} is not supported by plot_predict(). "
97
+ "Use plot_explore() for contrasts."
98
+ )
99
+
100
+ result: dict[str, Any] = {"term": spec.focal_var}
101
+ data = model.data
102
+
103
+ # Handle focal variable modifiers (@range, @[values], @quantile)
104
+ if spec.focal_at_range is not None:
105
+ result["n_points"] = spec.focal_at_range
106
+ elif spec.focal_at_values is not None:
107
+ result["focal_values"] = list(spec.focal_at_values)
108
+ elif spec.focal_at_quantile is not None:
109
+ col_data = data[spec.focal_var].drop_nulls().to_numpy()
110
+ quantiles = np.linspace(0, 1, spec.focal_at_quantile + 2)[1:-1]
111
+ result["focal_values"] = list(np.quantile(col_data, quantiles))
112
+
113
+ # Classify conditions into faceting roles (hue/col/row) or at-pins
114
+ at: dict[str, Any] = {}
115
+ facet_slots: list[str] = [] # hue, col, row — assigned in order
116
+
117
+ for cond in spec.conditions:
118
+ resolved = _resolve_condition_values(cond, data)
119
+ if resolved is not None and len(resolved) == 1:
120
+ # Single pinned value → goes into at dict
121
+ at[cond.var] = resolved[0]
122
+ else:
123
+ # Bare or multi-value condition → assigned to facet slot
124
+ if len(facet_slots) >= 3:
125
+ raise ValueError(
126
+ f"Too many unpinned conditions in formula {formula!r}. "
127
+ "plot_predict supports at most 3 faceting variables "
128
+ "(hue, col, row). Pin additional conditions with @value."
129
+ )
130
+ facet_slots.append(cond.var)
131
+ # If condition has explicit multi-values, inject via at as list
132
+ if resolved is not None:
133
+ at[cond.var] = resolved
134
+
135
+ # Map facet slots to hue → col → row
136
+ facet_keys = ["hue", "col", "row"]
137
+ for key, var in zip(facet_keys, facet_slots):
138
+ result[key] = var
139
+
140
+ if at:
141
+ result["at"] = at
142
+
143
+ return result
144
+
145
+
146
+ def _resolve_condition_values(
147
+ cond: Any,
148
+ data: pl.DataFrame,
149
+ ) -> list | None:
150
+ """Resolve a :class:`Condition` to concrete values or ``None``.
151
+
152
+ Args:
153
+ cond: A ``Condition`` from :func:`parse_explore_formula`.
154
+ data: The model's training data.
155
+
156
+ Returns:
157
+ A list of concrete values if the condition specifies explicit
158
+ values (``at_values``, ``at_range``, ``at_quantile``), or
159
+ ``None`` for bare conditions (use all unique levels).
160
+ """
161
+ if cond.at_values is not None:
162
+ return list(cond.at_values)
163
+ if cond.at_range is not None:
164
+ col_data = data[cond.var].drop_nulls().to_numpy()
165
+ return list(
166
+ np.linspace(np.nanmin(col_data), np.nanmax(col_data), cond.at_range)
167
+ )
168
+ if cond.at_quantile is not None:
169
+ col_data = data[cond.var].drop_nulls().to_numpy()
170
+ quantiles = np.linspace(0, 1, cond.at_quantile + 2)[1:-1]
171
+ return list(np.quantile(col_data, quantiles))
172
+ return None
173
+
174
+
39
175
  def plot_predict(
40
176
  model: "Any",
41
177
  term: str,
@@ -65,9 +201,21 @@ def plot_predict(
65
201
  holding others at reference values (means for continuous, reference level
66
202
  for categorical).
67
203
 
204
+ Accepts either a bare column name or an explore-style formula::
205
+
206
+ plot_predict(m, "age") # bare column
207
+ plot_predict(m, "age ~ sex") # hue by sex
208
+ plot_predict(m, "age ~ sex@Female") # pin sex=Female
209
+ plot_predict(m, "age@range(5)") # 5-point grid
210
+ plot_predict(m, "age@[25,50,75]") # explicit grid values
211
+
212
+ Contrast formulas (``pairwise()``, ``Drug[A - B]``) are not supported —
213
+ use :func:`plot_explore` for those.
214
+
68
215
  Args:
69
216
  model: A fitted bossanova model.
70
- term: The predictor variable to vary. Can be continuous or categorical.
217
+ term: The predictor variable to vary, or an explore-style formula
218
+ string. Can be continuous or categorical.
71
219
  hue: Column name for color encoding (creates separate lines per level).
72
220
  col: Column name for faceting into columns.
73
221
  row: Column name for faceting into rows.
@@ -121,7 +269,6 @@ def plot_predict(
121
269
  viz.plot_predict(m, "Days", show_blups=True)
122
270
 
123
271
  See Also:
124
- plot_fit: Observed vs Predicted scatter plot.
125
272
  plot_explore: Marginal effects/means visualization.
126
273
  """
127
274
  import seaborn as sns
@@ -129,6 +276,22 @@ def plot_predict(
129
276
  if not model._is_fitted:
130
277
  raise RuntimeError("Model must be fitted before plotting predictions")
131
278
 
279
+ # Formula detection: if term contains ~, @, or ( → parse as formula
280
+ focal_values: list[float | str] | None = None
281
+ if _is_formula(term):
282
+ parsed = _parse_predict_formula(term, model)
283
+ term = parsed["term"]
284
+ # Override kwargs only if formula provided them and user didn't
285
+ hue = hue or parsed.get("hue")
286
+ col = col or parsed.get("col")
287
+ row = row or parsed.get("row")
288
+ if parsed.get("n_points") is not None:
289
+ n_points = parsed["n_points"]
290
+ focal_values = parsed.get("focal_values")
291
+ # Merge at dicts: user-supplied at takes precedence
292
+ if parsed.get("at"):
293
+ at = {**parsed["at"], **(at or {})}
294
+
132
295
  style = BOSSANOVA_STYLE
133
296
  if palette is None:
134
297
  palette = style["palette"]
@@ -159,6 +322,7 @@ def plot_predict(
159
322
  conf_int=conf_int,
160
323
  show_blups=show_blups,
161
324
  groups=groups,
325
+ focal_values=focal_values,
162
326
  )
163
327
 
164
328
  # Create the plot
@@ -209,8 +373,32 @@ def build_prediction_data(
209
373
  conf_int: float,
210
374
  show_blups: bool,
211
375
  groups: list[str] | None,
376
+ focal_values: list[float | str] | None = None,
212
377
  ) -> pl.DataFrame:
213
- """Build prediction DataFrame with fitted values and intervals."""
378
+ """Build prediction DataFrame with fitted values and intervals.
379
+
380
+ Args:
381
+ model: A fitted bossanova model.
382
+ data: The model's training data.
383
+ term: The focal predictor variable to vary.
384
+ hue: Column for color encoding.
385
+ col: Column for column faceting.
386
+ row: Column for row faceting.
387
+ at: Dictionary of predictor values to hold fixed. Values can be
388
+ scalars (pinned to one value) or lists (expanded into the grid).
389
+ n_points: Number of points for continuous predictors.
390
+ is_categorical: Whether term is categorical.
391
+ pred_type: Prediction type ("data" or "link").
392
+ interval: Interval type ("confidence", "prediction", or None).
393
+ conf_int: Confidence level.
394
+ show_blups: Whether to show group-specific BLUP lines.
395
+ groups: Which group levels to show for BLUPs.
396
+ focal_values: Explicit values for the focal variable's grid.
397
+ When set, overrides the default linspace grid.
398
+
399
+ Returns:
400
+ Polars DataFrame with prediction grid and fitted values.
401
+ """
214
402
  response_col = get_model_formula(model).split("~")[0].strip()
215
403
  grouping_factors = get_grouping_factors(model)
216
404
  is_mixed = is_mixed_model(model)
@@ -239,8 +427,10 @@ def build_prediction_data(
239
427
  continue # Skip if not showing BLUPs
240
428
 
241
429
  if col_name == term:
242
- # Vary this term
243
- if is_categorical:
430
+ # Vary this term — use focal_values if provided
431
+ if focal_values is not None:
432
+ grid_data[col_name] = list(focal_values)
433
+ elif is_categorical:
244
434
  grid_data[col_name] = data[col_name].unique().sort().to_list()
245
435
  else:
246
436
  col_data = data[col_name].to_numpy()
@@ -249,7 +439,9 @@ def build_prediction_data(
249
439
  np.linspace(np.nanmin(col_data), np.nanmax(col_data), n_points)
250
440
  )
251
441
  elif at and col_name in at:
252
- grid_data[col_name] = [at[col_name]]
442
+ # Support list-valued at entries (expand into grid)
443
+ val = at[col_name]
444
+ grid_data[col_name] = list(val) if isinstance(val, list) else [val]
253
445
  elif col_name in [hue, col, row] and col_name is not None:
254
446
  # Vary grouping variables
255
447
  grid_data[col_name] = data[col_name].unique().sort().to_list()
@@ -472,11 +664,6 @@ def add_ci_band(
472
664
  confidence bounds. Supports both ``ci_lower``/``ci_upper``
473
665
  (preferred) and ``lwr``/``upr`` (legacy) column naming.
474
666
 
475
- Unlike ``fit_layers.add_ci_band``, this version receives
476
- pre-filtered data from ``FacetGrid.map_dataframe()`` and generates
477
- hue colors dynamically via ``plt.cm.tab10`` rather than using a
478
- pre-computed color map.
479
-
480
667
  Delegates rendering to :func:`~bossanova.internal.viz.helpers.fill_ci_band`.
481
668
 
482
669
  Args:
@@ -1968,8 +1968,16 @@ class model:
1968
1968
  def plot_predict(self, term: str, **kwargs: object) -> object:
1969
1969
  """Plot marginal predictions across a predictor range.
1970
1970
 
1971
+ Accepts either a bare column name or an explore-style formula::
1972
+
1973
+ m.plot_predict("age") # bare column
1974
+ m.plot_predict("age ~ sex") # hue by sex
1975
+ m.plot_predict("age ~ sex@Female") # pin sex=Female
1976
+ m.plot_predict("age@range(5)") # 5-point grid
1977
+ m.plot_predict("age@[25,50,75]") # explicit grid values
1978
+
1971
1979
  Args:
1972
- term: Predictor variable to plot predictions for.
1980
+ term: Predictor variable or explore-style formula string.
1973
1981
  **kwargs: Forwarded to ``bossanova.viz.plot_predict``. Key
1974
1982
  options: ``hue``, ``col``, ``at``, ``interval``,
1975
1983
  ``show_data``, ``show_rug``, ``height``, ``aspect``.
@@ -1979,20 +1987,6 @@ class model:
1979
1987
  """
1980
1988
  return self._plot("plot_predict", term, **kwargs)
1981
1989
 
1982
- def plot_fit(self, x: str, **kwargs: object) -> object:
1983
- """Plot fitted model against observed data.
1984
-
1985
- Args:
1986
- x: Predictor variable for the x-axis.
1987
- **kwargs: Forwarded to ``bossanova.viz.plot_fit``. Key
1988
- options: ``hue``, ``col``, ``scatter``, ``fit_line``,
1989
- ``ci``, ``blups``, ``groups``, ``height``.
1990
-
1991
- Returns:
1992
- object: Matplotlib figure.
1993
- """
1994
- return self._plot("plot_fit", x, **kwargs)
1995
-
1996
1990
  def plot_explore(self, specs: str, **kwargs: object) -> object:
1997
1991
  """Plot marginal effects or estimated marginal means.
1998
1992
 
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "bossanova"
3
3
  requires-python = ">=3.11"
4
- version = "0.1.0.dev22"
4
+ version = "0.1.0.dev24"
5
5
  authors = [
6
6
  {name = "Eshin Jolly"},
7
7
  ]