bossanova 0.1.0.dev25__tar.gz → 0.1.0.dev27__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 (243) hide show
  1. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/PKG-INFO +1 -1
  2. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/builders/dataframes.py +124 -18
  3. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/builders/state.py +4 -0
  4. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/schemas.py +10 -1
  5. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/structs/inference.py +3 -0
  6. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/fit/diagnostics.py +18 -0
  7. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/fit/varying.py +113 -6
  8. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/cv.py +6 -2
  9. bossanova-0.1.0.dev27/bossanova/internal/infer/profile.py +215 -0
  10. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/compute.py +29 -9
  11. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/inference/__init__.py +8 -8
  12. bossanova-0.1.0.dev27/bossanova/internal/maths/inference/profile.py +685 -0
  13. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/solvers/initialization.py +2 -2
  14. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/model/core.py +15 -9
  15. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/model/summary.py +14 -13
  16. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/pyproject.toml +1 -1
  17. bossanova-0.1.0.dev25/bossanova/internal/infer/profile.py +0 -194
  18. bossanova-0.1.0.dev25/bossanova/internal/maths/inference/profile.py +0 -626
  19. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/.gitignore +0 -0
  20. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/LICENSE +0 -0
  21. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/README.md +0 -0
  22. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/__init__.py +0 -0
  23. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/data/README.md +0 -0
  24. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/data/__init__.py +0 -0
  25. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/data/advertising.csv +0 -0
  26. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/data/cake.csv +0 -0
  27. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/data/chickweight.csv +0 -0
  28. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/data/credit.csv +0 -0
  29. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/data/gammas.csv +0 -0
  30. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/data/mtcars.csv +0 -0
  31. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/data/penguins.csv +0 -0
  32. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/data/poker.csv +0 -0
  33. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/data/sleep.csv +0 -0
  34. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/data/titanic.csv +0 -0
  35. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/data/titanic_test.csv +0 -0
  36. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/data/titanic_train.csv +0 -0
  37. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/distributions/__init__.py +0 -0
  38. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/distributions/continuous.py +0 -0
  39. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/distributions/discrete.py +0 -0
  40. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/distributions/varying.py +0 -0
  41. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/expressions.py +0 -0
  42. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/__init__.py +0 -0
  43. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/compare/__init__.py +0 -0
  44. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/compare/compare.py +0 -0
  45. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/compare/cv.py +0 -0
  46. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/compare/deviance.py +0 -0
  47. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/compare/f_test.py +0 -0
  48. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/compare/helpers.py +0 -0
  49. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/compare/ic.py +0 -0
  50. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/compare/lrt.py +0 -0
  51. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/compare/lrt_compare.py +0 -0
  52. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/compare/refit.py +0 -0
  53. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/__init__.py +0 -0
  54. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/builders/__init__.py +0 -0
  55. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/builders/resamples.py +0 -0
  56. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/builders/results.py +0 -0
  57. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/builders/specs.py +0 -0
  58. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/structs/__init__.py +0 -0
  59. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/structs/data.py +0 -0
  60. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/structs/display.py +0 -0
  61. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/structs/explore.py +0 -0
  62. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/structs/fit.py +0 -0
  63. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/structs/formula.py +0 -0
  64. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/structs/marginal.py +0 -0
  65. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/structs/mixed.py +0 -0
  66. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/structs/prediction.py +0 -0
  67. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/structs/simulation.py +0 -0
  68. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/structs/specs.py +0 -0
  69. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/containers/validators.py +0 -0
  70. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/design/__init__.py +0 -0
  71. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/design/coding.py +0 -0
  72. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/design/names.py +0 -0
  73. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/design/reference.py +0 -0
  74. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/design/z_matrix.py +0 -0
  75. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/fit/__init__.py +0 -0
  76. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/fit/convergence.py +0 -0
  77. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/fit/dispatch.py +0 -0
  78. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/fit/glm.py +0 -0
  79. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/fit/glmer.py +0 -0
  80. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/fit/grid.py +0 -0
  81. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/fit/lifecycle.py +0 -0
  82. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/fit/lmer.py +0 -0
  83. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/fit/ols.py +0 -0
  84. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/fit/predict.py +0 -0
  85. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/__init__.py +0 -0
  86. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/bundle.py +0 -0
  87. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/contrast_registry.py +0 -0
  88. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/contrast_specs.py +0 -0
  89. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/design.py +0 -0
  90. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/encoding.py +0 -0
  91. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/evaluate.py +0 -0
  92. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/evaluate_contrast.py +0 -0
  93. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/evaluate_newdata.py +0 -0
  94. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/evaluate_transforms.py +0 -0
  95. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/helpers.py +0 -0
  96. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/parse.py +0 -0
  97. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/parser/__init__.py +0 -0
  98. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/parser/expr.py +0 -0
  99. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/parser/parser.py +0 -0
  100. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/parser/scanner.py +0 -0
  101. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/parser/token.py +0 -0
  102. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/formula/random_effects.py +0 -0
  103. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/__init__.py +0 -0
  104. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/asymptotic.py +0 -0
  105. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/bootstrap.py +0 -0
  106. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/dispatch.py +0 -0
  107. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/formula_utils.py +0 -0
  108. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/mee.py +0 -0
  109. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/params.py +0 -0
  110. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/permutation.py +0 -0
  111. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/prediction.py +0 -0
  112. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/resample/__init__.py +0 -0
  113. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/resample/common.py +0 -0
  114. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/resample/core.py +0 -0
  115. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/resample/glm_operators.py +0 -0
  116. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/resample/glmer.py +0 -0
  117. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/resample/lm_operators.py +0 -0
  118. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/resample/lmer.py +0 -0
  119. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/resample/results.py +0 -0
  120. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/resample/simulate.py +0 -0
  121. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/resample_bundle.py +0 -0
  122. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/satterthwaite_emm.py +0 -0
  123. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/infer/simulation.py +0 -0
  124. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/__init__.py +0 -0
  125. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/bracket_contrasts.py +0 -0
  126. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/conditions.py +0 -0
  127. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/contrasts.py +0 -0
  128. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/emm.py +0 -0
  129. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/explore.py +0 -0
  130. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/explore_parser.py +0 -0
  131. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/explore_scanner.py +0 -0
  132. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/factors.py +0 -0
  133. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/grid.py +0 -0
  134. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/inference.py +0 -0
  135. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/joint_tests.py +0 -0
  136. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/matrices.py +0 -0
  137. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/resolve.py +0 -0
  138. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/slopes.py +0 -0
  139. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/transforms.py +0 -0
  140. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/marginal/validation.py +0 -0
  141. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/__init__.py +0 -0
  142. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/backend/__init__.py +0 -0
  143. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/backend/dispatch.py +0 -0
  144. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/backend/jax.py +0 -0
  145. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/backend/numpy.py +0 -0
  146. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/backend/protocol.py +0 -0
  147. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/batching.py +0 -0
  148. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/config.py +0 -0
  149. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/convergence.py +0 -0
  150. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/differentiation.py +0 -0
  151. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/distributions/__init__.py +0 -0
  152. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/distributions/algebra.py +0 -0
  153. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/distributions/base.py +0 -0
  154. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/distributions/core.py +0 -0
  155. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/distributions/derived.py +0 -0
  156. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/distributions/factories.py +0 -0
  157. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/distributions/plotting.py +0 -0
  158. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/distributions/probability.py +0 -0
  159. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/family/__init__.py +0 -0
  160. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/family/binomial.py +0 -0
  161. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/family/create.py +0 -0
  162. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/family/gamma.py +0 -0
  163. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/family/gaussian.py +0 -0
  164. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/family/links.py +0 -0
  165. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/family/poisson.py +0 -0
  166. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/family/response.py +0 -0
  167. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/family/schema.py +0 -0
  168. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/family/tdist.py +0 -0
  169. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/inference/diagnostics.py +0 -0
  170. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/inference/estimation.py +0 -0
  171. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/inference/hypothesis.py +0 -0
  172. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/inference/information_criteria.py +0 -0
  173. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/inference/multiplicity.py +0 -0
  174. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/inference/sandwich.py +0 -0
  175. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/inference/satterthwaite.py +0 -0
  176. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/inference/wald_variance.py +0 -0
  177. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/inference/welch.py +0 -0
  178. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/linalg/__init__.py +0 -0
  179. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/linalg/qr.py +0 -0
  180. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/linalg/rank.py +0 -0
  181. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/linalg/schur.py +0 -0
  182. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/linalg/sparse.py +0 -0
  183. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/linalg/svd.py +0 -0
  184. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/predict.py +0 -0
  185. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/rng.py +0 -0
  186. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/rounding.py +0 -0
  187. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/solvers/__init__.py +0 -0
  188. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/solvers/glm.py +0 -0
  189. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/solvers/glmer.py +0 -0
  190. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/solvers/heuristics.py +0 -0
  191. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/solvers/lambda_builder.py +0 -0
  192. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/solvers/lambda_sparse.py +0 -0
  193. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/solvers/lambda_template.py +0 -0
  194. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/solvers/lmer.py +0 -0
  195. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/solvers/optimize.py +0 -0
  196. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/solvers/pirls_sparse.py +0 -0
  197. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/solvers/quadrature.py +0 -0
  198. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/tolerances.py +0 -0
  199. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/transforms.py +0 -0
  200. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/variance.py +0 -0
  201. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/maths/weights.py +0 -0
  202. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/rendering/__init__.py +0 -0
  203. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/rendering/latex.py +0 -0
  204. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/rendering/markdown.py +0 -0
  205. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/simulation/__init__.py +0 -0
  206. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/simulation/dgp/__init__.py +0 -0
  207. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/simulation/dgp/generate.py +0 -0
  208. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/simulation/dgp/glm.py +0 -0
  209. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/simulation/dgp/glmer.py +0 -0
  210. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/simulation/dgp/lm.py +0 -0
  211. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/simulation/dgp/lmer.py +0 -0
  212. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/simulation/harness.py +0 -0
  213. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/simulation/lifecycle.py +0 -0
  214. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/simulation/metrics.py +0 -0
  215. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/simulation/model_sim.py +0 -0
  216. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/simulation/power.py +0 -0
  217. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/README.md +0 -0
  218. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/__init__.py +0 -0
  219. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/compare.py +0 -0
  220. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/core.py +0 -0
  221. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/core_data.py +0 -0
  222. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/core_protocols.py +0 -0
  223. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/core_sizing.py +0 -0
  224. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/core_viz.py +0 -0
  225. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/design.py +0 -0
  226. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/helpers.py +0 -0
  227. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/mem.py +0 -0
  228. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/mem_forest.py +0 -0
  229. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/params.py +0 -0
  230. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/predict.py +0 -0
  231. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/profile.py +0 -0
  232. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/ranef.py +0 -0
  233. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/relationships.py +0 -0
  234. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/resamples.py +0 -0
  235. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/resid.py +0 -0
  236. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/internal/viz/vif.py +0 -0
  237. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/model/__init__.py +0 -0
  238. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/model/guards.py +0 -0
  239. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/model/result.py +0 -0
  240. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/bossanova/py.typed +0 -0
  241. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/tests/bossanova_benchmarks/bootstrap/data/README.md +0 -0
  242. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/tests/bossanova_benchmarks/insteval/data/README.md +0 -0
  243. {bossanova-0.1.0.dev25 → bossanova-0.1.0.dev27}/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.dev25
3
+ Version: 0.1.0.dev27
4
4
  Summary: Bridging statistical cultures with some jazz
5
5
  Author: Eshin Jolly
6
6
  License-Expression: MIT
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  from typing import TYPE_CHECKING
12
12
 
13
+ import numpy as np
13
14
  import polars as pl
14
15
 
15
16
  from bossanova.internal.containers.builders.results import (
@@ -183,35 +184,140 @@ def build_varying_params_dataframe(
183
184
  def build_varying_spread_dataframe(
184
185
  varying_spread: VaryingSpreadState,
185
186
  ) -> pl.DataFrame:
186
- """Build the ``.varying_spread`` DataFrame from variance components.
187
+ """Build long-form ``.varying_spread`` DataFrame from variance components.
188
+
189
+ Produces a tidy DataFrame with one row per (term, statistic) pair.
190
+ Variance terms (sigma2, tau2) get both ``variance`` and ``std_dev``
191
+ rows. Correlations get a ``corr`` row. ICC gets an ``icc`` row.
187
192
 
188
193
  Args:
189
194
  varying_spread: Variance component state with components DataFrame
190
195
  and optional CI information.
191
196
 
192
197
  Returns:
193
- DataFrame with ``component``, ``estimate``, and optional
194
- ``ci_lower``, ``ci_upper``, ``ci_method`` columns.
198
+ DataFrame with columns ``term``, ``statistic``, ``estimate``,
199
+ and optional ``ci_lower``, ``ci_upper``, ``ci_method``.
195
200
  """
196
- result = varying_spread.components.clone()
201
+ has_ci = varying_spread.has_inference
202
+ terms: list[str] = []
203
+ statistics: list[str] = []
204
+ estimates: list[float] = []
205
+ ci_lowers: list[float] = []
206
+ ci_uppers: list[float] = []
207
+ ci_methods: list[str] = []
208
+
209
+ # Process components with sigma2 deferred to the end so Residual rows
210
+ # appear last, after all random effect terms.
211
+ comp_names = varying_spread.components[Col.COMPONENT].to_list()
212
+ deferred_sigma2: float | None = None
213
+
214
+ for comp_name in comp_names:
215
+ estimate = varying_spread.components.filter(pl.col(Col.COMPONENT) == comp_name)[
216
+ Col.ESTIMATE
217
+ ].item()
218
+
219
+ if comp_name == Col.SIGMA2:
220
+ deferred_sigma2 = estimate
221
+ elif comp_name.startswith(Col.TAU2_PREFIX):
222
+ # "tau2_Subject:Intercept" → "Subject | Intercept"
223
+ term = comp_name[len(Col.TAU2_PREFIX) :].replace(":", " | ")
224
+ _add_variance_rows(
225
+ term,
226
+ estimate,
227
+ comp_name,
228
+ varying_spread,
229
+ has_ci,
230
+ terms,
231
+ statistics,
232
+ estimates,
233
+ ci_lowers,
234
+ ci_uppers,
235
+ ci_methods,
236
+ )
237
+ elif comp_name.startswith(Col.RHO_PREFIX):
238
+ # "rho_Intercept:Days" → "Intercept:Days"
239
+ term = comp_name[len(Col.RHO_PREFIX) :]
240
+ terms.append(term)
241
+ statistics.append("corr")
242
+ estimates.append(estimate)
243
+ if has_ci:
244
+ ci_lowers.append(varying_spread.ci_lower.get(comp_name, float("nan")))
245
+ ci_uppers.append(varying_spread.ci_upper.get(comp_name, float("nan")))
246
+ ci_methods.append(varying_spread.ci_method or "")
247
+ elif comp_name == Col.ICC:
248
+ terms.append("ICC")
249
+ statistics.append("icc")
250
+ estimates.append(estimate)
251
+ if has_ci:
252
+ ci_lowers.append(varying_spread.ci_lower.get(comp_name, float("nan")))
253
+ ci_uppers.append(varying_spread.ci_upper.get(comp_name, float("nan")))
254
+ ci_methods.append(varying_spread.ci_method or "")
255
+
256
+ # Append Residual rows last
257
+ if deferred_sigma2 is not None:
258
+ _add_variance_rows(
259
+ "Residual",
260
+ deferred_sigma2,
261
+ Col.SIGMA2,
262
+ varying_spread,
263
+ has_ci,
264
+ terms,
265
+ statistics,
266
+ estimates,
267
+ ci_lowers,
268
+ ci_uppers,
269
+ ci_methods,
270
+ )
197
271
 
198
- if varying_spread.has_inference:
199
- ci_lower_vals = []
200
- ci_upper_vals = []
201
- ci_method_vals = []
272
+ data: dict[str, list] = {
273
+ Col.TERM: terms,
274
+ Col.METRIC: statistics,
275
+ Col.ESTIMATE: estimates,
276
+ }
277
+ if has_ci:
278
+ data[Col.CI_LOWER] = ci_lowers
279
+ data[Col.CI_UPPER] = ci_uppers
280
+ data[Col.CI_METHOD] = ci_methods
202
281
 
203
- for comp_name in result[Col.COMPONENT].to_list():
204
- ci_lower_vals.append(varying_spread.ci_lower.get(comp_name, float("nan")))
205
- ci_upper_vals.append(varying_spread.ci_upper.get(comp_name, float("nan")))
206
- ci_method_vals.append(varying_spread.ci_method or "")
282
+ return pl.DataFrame(data)
207
283
 
208
- result = result.with_columns(
209
- pl.Series(Col.CI_LOWER, ci_lower_vals),
210
- pl.Series(Col.CI_UPPER, ci_upper_vals),
211
- pl.Series(Col.CI_METHOD, ci_method_vals),
212
- )
213
284
 
214
- return result
285
+ def _add_variance_rows(
286
+ term: str,
287
+ variance: float,
288
+ comp_name: str,
289
+ varying_spread: VaryingSpreadState,
290
+ has_ci: bool,
291
+ terms: list[str],
292
+ statistics: list[str],
293
+ estimates: list[float],
294
+ ci_lowers: list[float],
295
+ ci_uppers: list[float],
296
+ ci_methods: list[str],
297
+ ) -> None:
298
+ """Append variance and std_dev rows for a single term."""
299
+ sd = np.sqrt(variance) if variance >= 0 else 0.0
300
+
301
+ # Variance row
302
+ terms.append(term)
303
+ statistics.append("variance")
304
+ estimates.append(variance)
305
+ if has_ci:
306
+ ci_lowers.append(varying_spread.ci_lower.get(comp_name, float("nan")))
307
+ ci_uppers.append(varying_spread.ci_upper.get(comp_name, float("nan")))
308
+ ci_methods.append(varying_spread.ci_method or "")
309
+
310
+ # Std dev row
311
+ terms.append(term)
312
+ statistics.append("std_dev")
313
+ estimates.append(sd)
314
+ if has_ci:
315
+ # Convert variance CIs to SD CIs
316
+ var_lo = varying_spread.ci_lower.get(comp_name, float("nan"))
317
+ var_hi = varying_spread.ci_upper.get(comp_name, float("nan"))
318
+ ci_lowers.append(np.sqrt(var_lo) if var_lo >= 0 else float("nan"))
319
+ ci_uppers.append(np.sqrt(var_hi) if var_hi >= 0 else float("nan"))
320
+ ci_methods.append(varying_spread.ci_method or "")
215
321
 
216
322
 
217
323
  def build_varying_corr_dataframe(
@@ -452,6 +452,7 @@ def build_prediction_state(
452
452
 
453
453
  def build_cv_state(
454
454
  k: int,
455
+ mse: float,
455
456
  rmse: float,
456
457
  mae: float,
457
458
  r_squared: float,
@@ -471,6 +472,7 @@ def build_cv_state(
471
472
 
472
473
  Args:
473
474
  k: Number of folds used.
475
+ mse: Mean squared error (out-of-sample).
474
476
  rmse: Root mean squared error.
475
477
  mae: Mean absolute error.
476
478
  r_squared: Coefficient of determination.
@@ -491,6 +493,7 @@ def build_cv_state(
491
493
  Examples:
492
494
  >>> state = build_cv_state(
493
495
  ... k=10,
496
+ ... mse=0.274,
494
497
  ... rmse=0.523,
495
498
  ... mae=0.412,
496
499
  ... r_squared=0.891,
@@ -498,6 +501,7 @@ def build_cv_state(
498
501
  """
499
502
  return CVState(
500
503
  k=k,
504
+ mse=mse,
501
505
  rmse=rmse,
502
506
  mae=mae,
503
507
  r_squared=r_squared,
@@ -98,6 +98,7 @@ class Col:
98
98
  NULL_DEVIANCE: str = "null_deviance"
99
99
  DEVIANCE: str = "deviance"
100
100
  DISPERSION: str = "dispersion"
101
+ MSE: str = "mse"
101
102
  PSEUDO_RSQUARED: str = "pseudo_rsquared"
102
103
  RSQUARED_MARGINAL: str = "rsquared_marginal"
103
104
  RSQUARED_CONDITIONAL: str = "rsquared_conditional"
@@ -112,6 +113,7 @@ class Col:
112
113
 
113
114
  # -- Varying (random effects) --
114
115
  COMPONENT: str = "component"
116
+ METRIC: str = "metric"
115
117
  GROUP: str = "group"
116
118
  LEVEL: str = "level"
117
119
  EFFECT1: str = "effect1"
@@ -177,6 +179,8 @@ class Col:
177
179
  N: str = "n"
178
180
 
179
181
  # -- CV diagnostics --
182
+ CV_MSE: str = "cv_mse"
183
+ CV_MSE_SD: str = "cv_mse_sd"
180
184
  CV_RMSE: str = "cv_rmse"
181
185
  CV_MAE: str = "cv_mae"
182
186
  CV_RSQUARED: str = "cv_rsquared"
@@ -408,6 +412,7 @@ DiagnosticsGaussian = Schema(
408
412
  Col.NULL_DEVIANCE: Float64,
409
413
  Col.DEVIANCE: Float64,
410
414
  Col.DISPERSION: Float64,
415
+ Col.MSE: Float64,
411
416
  Col.PSEUDO_RSQUARED: Float64,
412
417
  Col.AIC: Float64,
413
418
  Col.BIC: Float64,
@@ -423,6 +428,7 @@ DiagnosticsGlm = Schema(
423
428
  Col.NULL_DEVIANCE: Float64,
424
429
  Col.DEVIANCE: Float64,
425
430
  Col.DISPERSION: Float64,
431
+ Col.MSE: Float64,
426
432
  Col.PSEUDO_RSQUARED: Float64,
427
433
  Col.AIC: Float64,
428
434
  Col.BIC: Float64,
@@ -437,6 +443,7 @@ DiagnosticsLmer = Schema(
437
443
  Col.DF_RESID: Float64,
438
444
  Col.SIGMA: Float64,
439
445
  Col.DEVIANCE: Float64,
446
+ Col.MSE: Float64,
440
447
  Col.RSQUARED_MARGINAL: Float64,
441
448
  Col.RSQUARED_CONDITIONAL: Float64,
442
449
  Col.ICC: Float64,
@@ -447,10 +454,12 @@ DiagnosticsLmer = Schema(
447
454
  )
448
455
 
449
456
  # CV-augmented columns (added by .infer(how='cv') after .fit())
450
- DiagnosticsCvCols = (Col.CV_RMSE, Col.CV_MAE, Col.CV_RSQUARED, Col.CV_K)
457
+ DiagnosticsCvCols = (Col.CV_MSE, Col.CV_RMSE, Col.CV_MAE, Col.CV_RSQUARED, Col.CV_K)
451
458
 
452
459
  # Prediction CV columns (added by .predict().infer(how='cv'))
453
460
  DiagnosticsPredCvCols = (
461
+ Col.CV_MSE,
462
+ Col.CV_MSE_SD,
454
463
  Col.CV_RMSE,
455
464
  Col.CV_RMSE_SD,
456
465
  Col.CV_MAE,
@@ -98,6 +98,9 @@ class CVState:
98
98
  """
99
99
 
100
100
  k: int = field(validator=[validators.instance_of(int), validators.gt(0)])
101
+ mse: float = field(
102
+ validator=[validators.instance_of((int, float)), validators.ge(0)]
103
+ )
101
104
  rmse: float = field(
102
105
  validator=[validators.instance_of((int, float)), validators.ge(0)]
103
106
  )
@@ -267,6 +267,8 @@ def _lm_diagnostics(
267
267
  fit.null_deviance if fit.null_deviance is not None else float(np.sum(y**2))
268
268
  )
269
269
 
270
+ mse = ss_res / n
271
+
270
272
  return {
271
273
  Col.RSQUARED: [rsquared],
272
274
  Col.RSQUARED_ADJ: [rsquared_adj],
@@ -276,6 +278,7 @@ def _lm_diagnostics(
276
278
  Col.NULL_DEVIANCE: [null_deviance],
277
279
  Col.DEVIANCE: [deviance],
278
280
  Col.DISPERSION: [sigma**2],
281
+ Col.MSE: [mse],
279
282
  Col.PSEUDO_RSQUARED: [
280
283
  1.0 - deviance / null_deviance if null_deviance > 0 else float("nan")
281
284
  ],
@@ -313,10 +316,17 @@ def _glm_diagnostics(
313
316
  else float("nan")
314
317
  )
315
318
 
319
+ import numpy as np
320
+
321
+ mse = (
322
+ float(np.mean(fit.residuals**2)) if fit.residuals is not None else float("nan")
323
+ )
324
+
316
325
  return {
317
326
  Col.NULL_DEVIANCE: [null_deviance],
318
327
  Col.DEVIANCE: [deviance],
319
328
  Col.DISPERSION: [dispersion],
329
+ Col.MSE: [mse],
320
330
  Col.PSEUDO_RSQUARED: [pseudo_rsquared],
321
331
  }
322
332
 
@@ -385,9 +395,14 @@ def _mixed_diagnostics(
385
395
  var_group_resid = var_random + var_resid
386
396
  icc = var_random / var_group_resid if var_group_resid > 0 else 0.0
387
397
 
398
+ mse = (
399
+ float(np.mean(fit.residuals**2)) if fit.residuals is not None else float("nan")
400
+ )
401
+
388
402
  return {
389
403
  Col.SIGMA: [sigma],
390
404
  Col.DEVIANCE: [deviance],
405
+ Col.MSE: [mse],
391
406
  Col.RSQUARED_MARGINAL: [rsquared_marginal],
392
407
  Col.RSQUARED_CONDITIONAL: [rsquared_conditional],
393
408
  Col.ICC: [icc],
@@ -403,12 +418,15 @@ def _add_cv_metrics(data: dict[str, list], cv: CVState) -> None:
403
418
  """
404
419
  import numpy as np
405
420
 
421
+ data[Col.CV_MSE] = [cv.mse]
406
422
  data[Col.CV_RMSE] = [cv.rmse]
407
423
  data[Col.CV_MAE] = [cv.mae]
408
424
  data[Col.CV_RSQUARED] = [cv.r_squared]
409
425
  data[Col.CV_K] = [cv.k]
410
426
 
411
427
  if cv.fold_metrics:
428
+ if "mse" in cv.fold_metrics:
429
+ data[Col.CV_MSE_SD] = [float(np.std(cv.fold_metrics["mse"]))]
412
430
  if "rmse" in cv.fold_metrics:
413
431
  data[Col.CV_RMSE_SD] = [float(np.std(cv.fold_metrics["rmse"]))]
414
432
  if "mae" in cv.fold_metrics:
@@ -253,10 +253,112 @@ def compute_varying_state(
253
253
  )
254
254
 
255
255
 
256
+ def _get_predictor_column(
257
+ re_name: str,
258
+ X: NDArray[np.floating] | None,
259
+ X_names: tuple[str, ...] | None,
260
+ ) -> NDArray[np.floating] | None:
261
+ """Look up the data column for a random effect predictor.
262
+
263
+ Returns None for ``"Intercept"`` (caller uses all-ones logic) or when
264
+ the predictor cannot be found in the design matrix.
265
+ """
266
+ if re_name == "Intercept" or X is None or X_names is None:
267
+ return None
268
+ if re_name in X_names:
269
+ return X[:, X_names.index(re_name)]
270
+ return None
271
+
272
+
273
+ def _compute_johnson_icc(
274
+ tau2: dict[str, float],
275
+ rho: dict[str, float],
276
+ sigma2: float,
277
+ re_meta: REInfo,
278
+ X: NDArray[np.floating] | None,
279
+ X_names: tuple[str, ...] | None,
280
+ ) -> float | None:
281
+ """Compute ICC using Johnson (2014, PLoS ONE, eq. 10).
282
+
283
+ For each grouping factor, computes the mean random effect variance
284
+ accounting for predictor distributions when random slopes are present:
285
+
286
+ contribution_g = Σ_i Σ_j Cov(b_i, b_j) · E[X_i · X_j]
287
+
288
+ For intercept-only factors this simplifies to τ00. For slope models
289
+ it incorporates E[X] and E[X²] from the data.
290
+
291
+ Falls back to sum(τ00) / (sum(τ00) + σ²) when data is unavailable.
292
+ """
293
+ group_names = list(re_meta.grouping_vars)
294
+ if not group_names:
295
+ return None
296
+
297
+ # Get per-factor RE names
298
+ re_structures_list = re_meta.metadata.get("re_structures_list", None)
299
+ if re_structures_list is not None and len(re_structures_list) == len(group_names):
300
+ _, names_per_group = per_factor_re_info(re_meta, group_names)
301
+ else:
302
+ # Single-factor model: all random names belong to the one group
303
+ names_per_group = {group_names[0]: re_meta.random_names or ["Intercept"]}
304
+
305
+ var_random = 0.0
306
+
307
+ for group_name in group_names:
308
+ factor_names = names_per_group[group_name]
309
+ k = len(factor_names)
310
+
311
+ # Build covariance matrix for this factor
312
+ cov = np.zeros((k, k))
313
+ for i, name_i in enumerate(factor_names):
314
+ key_i = f"{group_name}:{name_i}"
315
+ cov[i, i] = tau2.get(key_i, 0.0)
316
+
317
+ # Fill off-diagonal from rho
318
+ # NOTE: rho keys are "RE1:RE2" without group prefix. When two groups
319
+ # share the same slope predictor names, the last group's rho wins.
320
+ # This is a known limitation of the current rho dict structure.
321
+ if k > 1:
322
+ for i in range(k):
323
+ for j in range(i + 1, k):
324
+ rho_key = f"{factor_names[i]}:{factor_names[j]}"
325
+ if rho_key in rho:
326
+ cov_ij = rho[rho_key] * np.sqrt(cov[i, i] * cov[j, j])
327
+ cov[i, j] = cov_ij
328
+ cov[j, i] = cov_ij
329
+
330
+ # Build E[X_i · X_j] matrix
331
+ e_xx = np.zeros((k, k))
332
+ cols: list[NDArray[np.floating] | None] = [
333
+ _get_predictor_column(name, X, X_names) for name in factor_names
334
+ ]
335
+ for i in range(k):
336
+ for j in range(k):
337
+ ci = cols[i]
338
+ cj = cols[j]
339
+ if ci is not None and cj is not None:
340
+ e_xx[i, j] = np.mean(ci * cj)
341
+ elif ci is None and factor_names[i] == "Intercept":
342
+ # Intercept is all 1s: E[1 · X_j]
343
+ e_xx[i, j] = np.mean(cj) if cj is not None else 1.0
344
+ elif cj is None and factor_names[j] == "Intercept":
345
+ # E[X_i · 1]
346
+ e_xx[i, j] = np.mean(ci) if ci is not None else 1.0
347
+
348
+ var_random += float(np.sum(cov * e_xx))
349
+
350
+ if var_random <= 0:
351
+ return None
352
+ return var_random / (var_random + sigma2)
353
+
354
+
256
355
  def compute_varying_spread_state(
257
356
  theta: NDArray[np.floating],
258
357
  sigma: float,
259
358
  re_meta: REInfo,
359
+ *,
360
+ X: NDArray[np.floating] | None = None,
361
+ X_names: tuple[str, ...] | None = None,
260
362
  ) -> VaryingSpreadState:
261
363
  """Compute VaryingSpreadState (variance components) from theta parameters.
262
364
 
@@ -264,10 +366,17 @@ def compute_varying_spread_state(
264
366
  correlations (rho), and intraclass correlation (ICC) from the fitted
265
367
  theta vector using the random effects structure.
266
368
 
369
+ ICC is computed using Johnson (2014, PLoS ONE, eq. 10) which accounts
370
+ for predictor distributions when random slopes are present. When ``X``
371
+ and ``X_names`` are provided, the formula uses E[X] and E[X²] from
372
+ the data; otherwise falls back to summing intercept variances only.
373
+
267
374
  Args:
268
375
  theta: Variance component parameters from the fitted model.
269
376
  sigma: Residual standard deviation from the fitted model.
270
377
  re_meta: Random effects metadata (grouping vars, structure, etc.).
378
+ X: Fixed-effects design matrix (n × p), used for ICC computation.
379
+ X_names: Column names for ``X``, used to look up slope predictors.
271
380
 
272
381
  Returns:
273
382
  VaryingSpreadState container with components DataFrame and
@@ -313,12 +422,8 @@ def compute_varying_spread_state(
313
422
  tau2_key = name.removesuffix("_sd")
314
423
  tau2[tau2_key] = value**2
315
424
 
316
- # Compute ICC (for intercept-only models)
317
- # ICC = tau² / (tau² + sigma²)
318
- icc: float | None = None
319
- total_tau2 = sum(tau2.values())
320
- if total_tau2 > 0:
321
- icc = total_tau2 / (total_tau2 + sigma2)
425
+ # Compute ICC using Johnson (2014) formula
426
+ icc = _compute_johnson_icc(tau2, rho, sigma2, re_meta, X, X_names)
322
427
 
323
428
  # Build components DataFrame
324
429
  components_data: dict[str, list] = {Col.COMPONENT: [], Col.ESTIMATE: []}
@@ -403,6 +508,8 @@ def build_mixed_post_fit_state(
403
508
  theta=fit.theta,
404
509
  sigma=sigma,
405
510
  re_meta=bundle.re_metadata,
511
+ X=bundle.X,
512
+ X_names=bundle.X_names,
406
513
  )
407
514
 
408
515
  # Emit convergence warnings if any issues detected
@@ -485,8 +485,10 @@ def _compute_fold_metrics(
485
485
  'sensitivity', 'specificity', 'f1', 'auc'.
486
486
  """
487
487
  residuals = y_test - y_pred
488
+ mse = float(np.mean(residuals**2))
488
489
  metrics: dict[str, float] = {
489
- "rmse": float(np.sqrt(np.mean(residuals**2))),
490
+ "mse": mse,
491
+ "rmse": float(np.sqrt(mse)),
490
492
  "mae": float(np.mean(np.abs(residuals))),
491
493
  }
492
494
 
@@ -644,7 +646,8 @@ def compute_cv_metrics(
644
646
  resid_valid = oos_residuals[valid_mask]
645
647
 
646
648
  # Overall out-of-sample metrics
647
- rmse = float(np.sqrt(np.mean(resid_valid**2)))
649
+ mse = float(np.mean(resid_valid**2))
650
+ rmse = float(np.sqrt(mse))
648
651
  mae = float(np.mean(np.abs(resid_valid)))
649
652
 
650
653
  # R² computed on out-of-sample predictions
@@ -666,6 +669,7 @@ def compute_cv_metrics(
666
669
 
667
670
  return build_cv_state(
668
671
  k=k_int,
672
+ mse=mse,
669
673
  rmse=rmse,
670
674
  mae=mae,
671
675
  r_squared=r_squared,