PyMetaAnalysis 0.2.1__tar.gz → 0.4.0__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 (131) hide show
  1. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/CHANGELOG.md +42 -0
  2. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/CITATION.cff +2 -2
  3. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/PKG-INFO +47 -3
  4. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/README.md +45 -1
  5. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/benchmarks/README.md +5 -4
  6. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/benchmarks/benchmark_core.py +14 -2
  7. pymetaanalysis-0.4.0/docs/adr/0003-meta-regression-prediction-intervals.md +70 -0
  8. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/getting-started.md +1 -1
  9. pymetaanalysis-0.4.0/docs/guides/meta-regression.md +366 -0
  10. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/plotting.md +32 -1
  11. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/provenance-reporting.md +1 -1
  12. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/r-interoperability.md +12 -0
  13. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/sensitivity-analysis.md +82 -0
  14. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/zero-events.md +6 -6
  15. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/index.md +9 -6
  16. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/limitations.md +22 -6
  17. pymetaanalysis-0.4.0/docs/methods/statistical-methods.md +540 -0
  18. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/reference/api.md +73 -3
  19. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/reference/report-schema.md +35 -4
  20. pymetaanalysis-0.4.0/docs/reference/results.md +433 -0
  21. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/releasing.md +18 -0
  22. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/validation.md +36 -0
  23. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/examples/README.md +4 -1
  24. pymetaanalysis-0.4.0/examples/meta_regression.ipynb +290 -0
  25. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/mkdocs.yml +2 -0
  26. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/pyproject.toml +1 -1
  27. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/__init__.py +31 -1
  28. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/_version.py +1 -1
  29. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/config.py +18 -0
  30. pymetaanalysis-0.4.0/src/meta_analyze/design_matrix.py +477 -0
  31. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/estimators/__init__.py +12 -0
  32. pymetaanalysis-0.4.0/src/meta_analyze/estimators/meta_regression.py +462 -0
  33. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/__init__.py +2 -1
  34. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/_utils.py +2 -2
  35. pymetaanalysis-0.4.0/src/meta_analyze/plotting/regression.py +128 -0
  36. pymetaanalysis-0.4.0/src/meta_analyze/regression_api.py +430 -0
  37. pymetaanalysis-0.4.0/src/meta_analyze/regression_collinearity.py +304 -0
  38. pymetaanalysis-0.4.0/src/meta_analyze/regression_contrasts.py +365 -0
  39. pymetaanalysis-0.4.0/src/meta_analyze/regression_results.py +454 -0
  40. pymetaanalysis-0.4.0/src/meta_analyze/regression_sensitivity.py +587 -0
  41. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/reporting.py +169 -1
  42. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/README.md +23 -0
  43. pymetaanalysis-0.4.0/tests/reference/generate_meta_regression_collinearity_metafor.R +81 -0
  44. pymetaanalysis-0.4.0/tests/reference/generate_meta_regression_contrasts_metafor.R +96 -0
  45. pymetaanalysis-0.4.0/tests/reference/generate_meta_regression_influence_metafor.R +81 -0
  46. pymetaanalysis-0.4.0/tests/reference/generate_meta_regression_metafor.R +371 -0
  47. pymetaanalysis-0.4.0/tests/reference/meta_regression_boundary_input.csv +9 -0
  48. pymetaanalysis-0.4.0/tests/reference/meta_regression_collinearity_metafor.json +65 -0
  49. pymetaanalysis-0.4.0/tests/reference/meta_regression_contrasts_metafor.json +157 -0
  50. pymetaanalysis-0.4.0/tests/reference/meta_regression_influence_metafor.json +804 -0
  51. pymetaanalysis-0.4.0/tests/reference/meta_regression_input.csv +16 -0
  52. pymetaanalysis-0.4.0/tests/reference/meta_regression_metafor.json +1789 -0
  53. pymetaanalysis-0.4.0/tests/test_meta_regression.py +714 -0
  54. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_numerical_stability.py +85 -0
  55. pymetaanalysis-0.4.0/tests/test_properties.py +296 -0
  56. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_r_references.py +419 -1
  57. pymetaanalysis-0.4.0/tests/test_regression_collinearity.py +288 -0
  58. pymetaanalysis-0.4.0/tests/test_regression_contrasts.py +346 -0
  59. pymetaanalysis-0.4.0/tests/test_regression_influence.py +319 -0
  60. pymetaanalysis-0.4.0/tests/test_regression_plotting.py +200 -0
  61. pymetaanalysis-0.4.0/tests/test_regression_sensitivity.py +273 -0
  62. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_release_readiness.py +23 -17
  63. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_reporting.py +1 -1
  64. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tools/inspect_distribution.py +1 -0
  65. pymetaanalysis-0.2.1/docs/methods/statistical-methods.md +0 -286
  66. pymetaanalysis-0.2.1/docs/reference/results.md +0 -224
  67. pymetaanalysis-0.2.1/tests/test_properties.py +0 -116
  68. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/.github/workflows/ci.yml +0 -0
  69. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/.github/workflows/pages.yml +0 -0
  70. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/.github/workflows/release.yml +0 -0
  71. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/.gitignore +0 -0
  72. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/CONTRIBUTING.md +0 -0
  73. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/LICENSE +0 -0
  74. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/SECURITY.md +0 -0
  75. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/adr/0001-optional-matplotlib.md +0 -0
  76. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/adr/0002-statistical-policy.md +0 -0
  77. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/citation.md +0 -0
  78. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/development.md +0 -0
  79. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/binary-outcomes.md +0 -0
  80. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/continuous-outcomes.md +0 -0
  81. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/generic-effects.md +0 -0
  82. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/input-data.md +0 -0
  83. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/method-selection.md +0 -0
  84. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/installation.md +0 -0
  85. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/stylesheets/extra.css +0 -0
  86. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/examples/quickstart.ipynb +0 -0
  87. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/api.py +0 -0
  88. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/binary_api.py +0 -0
  89. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/continuous_api.py +0 -0
  90. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/data.py +0 -0
  91. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/effect_sizes/__init__.py +0 -0
  92. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/effect_sizes/binary.py +0 -0
  93. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/effect_sizes/continuous.py +0 -0
  94. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/estimators/inverse_variance.py +0 -0
  95. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/estimators/mantel_haenszel.py +0 -0
  96. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/estimators/tau2.py +0 -0
  97. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/exceptions.py +0 -0
  98. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/heterogeneity.py +0 -0
  99. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/forest.py +0 -0
  100. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/funnel.py +0 -0
  101. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/subgroup_forest.py +0 -0
  102. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/provenance.py +0 -0
  103. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/py.typed +0 -0
  104. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/results.py +0 -0
  105. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/sensitivity.py +0 -0
  106. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/subgroups.py +0 -0
  107. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/binary_input.csv +0 -0
  108. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/binary_metafor.json +0 -0
  109. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/binary_sparse_input.csv +0 -0
  110. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/continuous_input.csv +0 -0
  111. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/continuous_metafor.json +0 -0
  112. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/generate_binary_metafor.R +0 -0
  113. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/generate_continuous_metafor.R +0 -0
  114. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/generate_generic_metafor.R +0 -0
  115. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/generate_workflow_metafor.R +0 -0
  116. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/generic_input.csv +0 -0
  117. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/generic_metafor.json +0 -0
  118. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/workflow_input.csv +0 -0
  119. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/workflow_metafor.json +0 -0
  120. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_api.py +0 -0
  121. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_binary.py +0 -0
  122. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_continuous.py +0 -0
  123. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_documentation.py +0 -0
  124. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_estimators.py +0 -0
  125. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_funnel_plot.py +0 -0
  126. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_plotting.py +0 -0
  127. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_reference_results.py +0 -0
  128. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_sensitivity.py +0 -0
  129. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_subgroups.py +0 -0
  130. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tools/check_release.py +0 -0
  131. {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tools/execute_notebooks.py +0 -0
@@ -6,6 +6,48 @@ Changes planned for the next release accumulate under `Unreleased`.
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## 0.4.0 - 2026-07-23
10
+
11
+ ### Added
12
+
13
+ - leave-one-out Meta-regression refits with model-level diagnostics,
14
+ coefficient changes, explicit unidentifiable-deletion records, and preserved
15
+ provenance.
16
+ - exact Meta-regression externally standardized residuals, Cook's distances,
17
+ DFBETAS, transparent screening thresholds, and fixed-version R `metafor`
18
+ cross-software fixtures.
19
+ - Meta-regression term VIF, moderator-level GVIF/GSIF, and weighted,
20
+ column-scaled condition diagnostics with variance-decomposition proportions,
21
+ heuristic-only flags, and R `metafor` cross-software fixtures.
22
+ - explicit named Meta-regression linear contrasts with nonzero null values,
23
+ individual z/t inference, joint chi-squared/F tests, labeled coefficient
24
+ matrices, and R `metafor` cross-software fixtures.
25
+ - opt-in Riley Meta-regression true-effect prediction intervals using
26
+ `t_(k-p-1)`, with explicit residual-df validation, preserved refit
27
+ configuration, and fixed-version R `metafor` boundary references.
28
+
29
+ ## 0.3.0 - 2026-07-22
30
+
31
+ ### Added
32
+
33
+ - pandas-first `meta_regression()` for numeric, explicitly encoded categorical,
34
+ and multiple study-level moderators;
35
+ - common- and mixed-effects weighted regression with generalized DL, PM, and
36
+ REML residual tau-squared estimators;
37
+ - normal, Hartung-Knapp, and safeguarded Hartung-Knapp coefficient inference,
38
+ distribution-explicit moderator tests, residual heterogeneity, pseudo-R²,
39
+ prediction, provenance, and structured reports;
40
+ - optional weighted bubble plots for intercept-containing Meta-regression fits
41
+ with exactly one numeric moderator;
42
+ - independent R `metafor` fixtures covering numeric, categorical,
43
+ multivariable, zero-tau-squared, missing-row, and small-sample cases;
44
+ - an executable Meta-regression notebook plus a multivariable performance
45
+ baseline and expanded property, numerical-stability, and warning tests.
46
+
47
+ ### Changed
48
+
49
+ - report schema 1.2 adds the `meta_regression` report type.
50
+
9
51
  ## 0.2.1 - 2026-07-17
10
52
 
11
53
  ### Fixed
@@ -8,8 +8,8 @@ authors:
8
8
  - family-names: Ding
9
9
  given-names: Zhaobo
10
10
  email: ding.zb@yahoo.com
11
- version: 0.2.1
12
- date-released: 2026-07-17
11
+ version: 0.4.0
12
+ date-released: 2026-07-23
13
13
  repository-code: https://github.com/ZhaoboDing/PyMetaAnalysis
14
14
  url: https://zhaoboding.github.io/PyMetaAnalysis/
15
15
  license: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyMetaAnalysis
3
- Version: 0.2.1
3
+ Version: 0.4.0
4
4
  Summary: A pandas-first, auditable meta-analysis library for Python
5
5
  Project-URL: Documentation, https://zhaoboding.github.io/PyMetaAnalysis/
6
6
  Project-URL: Source, https://github.com/ZhaoboDing/PyMetaAnalysis
@@ -28,7 +28,7 @@ Requires-Dist: actionlint-py>=1.7.12.24; extra == 'dev'
28
28
  Requires-Dist: build>=1.2; extra == 'dev'
29
29
  Requires-Dist: mypy>=1.10; extra == 'dev'
30
30
  Requires-Dist: pandas-stubs>=2.0; extra == 'dev'
31
- Requires-Dist: ruff>=0.6; extra == 'dev'
31
+ Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
32
32
  Requires-Dist: scipy-stubs>=1.10; extra == 'dev'
33
33
  Provides-Extra: docs
34
34
  Requires-Dist: mkdocs>=1.6; extra == 'docs'
@@ -111,6 +111,22 @@ Omit `study=` to use the DataFrame index. Supplying `subgroup=` returns a
111
111
  dedicated result containing group fits, the overall fit, and a formal test for
112
112
  subgroup differences.
113
113
 
114
+ Study-level moderators use the dedicated Meta-regression entry point:
115
+
116
+ ```python
117
+ regression = ma.meta_regression(
118
+ studies,
119
+ effect="effect",
120
+ standard_error="se",
121
+ moderators=["mean_age", "region"],
122
+ categorical={"region": ["Europe", "Asia", "North America"]},
123
+ )
124
+ ```
125
+
126
+ Categorical levels are explicit and ordered; the first value is the treatment-
127
+ coding reference. Moderator coefficients describe study-level associations,
128
+ not individual-level or causal effects.
129
+
114
130
  ## Supported analyses
115
131
 
116
132
  | Input | Effects | Pooling/models |
@@ -118,6 +134,7 @@ subgroup differences.
118
134
  | Effect + sampling variance or standard error | Generic | Common/random inverse variance |
119
135
  | Two-group events + totals | OR, RR, RD | Common MH OR/RR; common/random IV |
120
136
  | Two-group means + SDs + sizes | MD, Hedges' g | Common/random inverse variance |
137
+ | Effect + variance/SE + moderators | Generic | Common/mixed Meta-regression |
121
138
 
122
139
  Random-effects inverse-variance models support REML (default), Paule-Mandel,
123
140
  and DerSimonian-Laird tau-squared estimators. Mean confidence intervals support
@@ -160,11 +177,23 @@ Rows excluded by missing-value or sparse-data policies remain in
160
177
  `study_results` with a stable `row_id`, `included=False`, and an
161
178
  `exclusion_reason`.
162
179
 
163
- ## Sensitivity and plots
180
+ ## Diagnostics, contrasts, and plots
164
181
 
165
182
  ```python
166
183
  leave_one_out = result.leave_one_out().to_dataframe()
167
184
  cumulative = result.cumulative(order="publication_year").to_dataframe()
185
+ regression_deleted = regression.leave_one_out()
186
+ regression_coefficient_changes = regression_deleted.coefficients
187
+ regression_influence = regression.influence()
188
+ flagged_diagnostics = regression_influence.flagged
189
+ collinearity = regression.collinearity()
190
+ term_vif = collinearity.term_vif
191
+ moderator_gvif = collinearity.moderator_gvif
192
+ condition_indices = collinearity.condition_indices
193
+ south_vs_east = regression.contrast(
194
+ {"region[South]": 1.0, "region[East]": -1.0},
195
+ name="South - East",
196
+ )
168
197
 
169
198
  ax = result.forest(show_prediction_interval=True)
170
199
  ax = result.funnel()
@@ -172,6 +201,20 @@ ax = result.funnel()
172
201
 
173
202
  Plotting methods return Matplotlib axes and never call `show()`. Funnel plots
174
203
  are descriptive small-study-effect diagnostics, not proof of publication bias.
204
+ Meta-regression leave-one-out results also expose a long-form coefficient
205
+ change table. Exact influence diagnostics add externally standardized
206
+ residuals, Cook's distance, DFBETAS, and explicit heuristic screening
207
+ thresholds without automatically excluding studies. Meta-regression
208
+ collinearity diagnostics add `metafor`-compatible VIF/GVIF plus weighted,
209
+ column-scaled condition indices and variance-decomposition proportions.
210
+ Their documented references are review aids, not automatic variable-selection
211
+ rules. Explicit named linear contrasts provide individual z/t inference and
212
+ full-rank joint chi-squared/F tests without silently adjusting for multiple
213
+ testing. An eligible
214
+ single-numeric-moderator Meta-regression result additionally provides
215
+ `regression.bubble()` with fitted confidence and optional prediction bands.
216
+ Mixed-effects Meta-regression supports its documented default prediction rule
217
+ and an explicit Riley `t_(k-p-1)` alternative.
175
218
 
176
219
  ## Documentation
177
220
 
@@ -182,6 +225,7 @@ The complete documentation is published at
182
225
  - [Getting started](https://zhaoboding.github.io/PyMetaAnalysis/getting-started/)
183
226
  - [Input data and row decisions](https://zhaoboding.github.io/PyMetaAnalysis/guides/input-data/)
184
227
  - [Generic](https://zhaoboding.github.io/PyMetaAnalysis/guides/generic-effects/), [binary](https://zhaoboding.github.io/PyMetaAnalysis/guides/binary-outcomes/), and [continuous](https://zhaoboding.github.io/PyMetaAnalysis/guides/continuous-outcomes/) guides
228
+ - [Meta-regression](https://zhaoboding.github.io/PyMetaAnalysis/guides/meta-regression/)
185
229
  - [Choosing methods](https://zhaoboding.github.io/PyMetaAnalysis/guides/method-selection/) and [statistical formulas](https://zhaoboding.github.io/PyMetaAnalysis/methods/statistical-methods/)
186
230
  - [Sensitivity analysis](https://zhaoboding.github.io/PyMetaAnalysis/guides/sensitivity-analysis/) and [plotting](https://zhaoboding.github.io/PyMetaAnalysis/guides/plotting/)
187
231
  - [Public API](https://zhaoboding.github.io/PyMetaAnalysis/reference/api/), [result objects](https://zhaoboding.github.io/PyMetaAnalysis/reference/results/), and [report schema](https://zhaoboding.github.io/PyMetaAnalysis/reference/report-schema/)
@@ -62,6 +62,22 @@ Omit `study=` to use the DataFrame index. Supplying `subgroup=` returns a
62
62
  dedicated result containing group fits, the overall fit, and a formal test for
63
63
  subgroup differences.
64
64
 
65
+ Study-level moderators use the dedicated Meta-regression entry point:
66
+
67
+ ```python
68
+ regression = ma.meta_regression(
69
+ studies,
70
+ effect="effect",
71
+ standard_error="se",
72
+ moderators=["mean_age", "region"],
73
+ categorical={"region": ["Europe", "Asia", "North America"]},
74
+ )
75
+ ```
76
+
77
+ Categorical levels are explicit and ordered; the first value is the treatment-
78
+ coding reference. Moderator coefficients describe study-level associations,
79
+ not individual-level or causal effects.
80
+
65
81
  ## Supported analyses
66
82
 
67
83
  | Input | Effects | Pooling/models |
@@ -69,6 +85,7 @@ subgroup differences.
69
85
  | Effect + sampling variance or standard error | Generic | Common/random inverse variance |
70
86
  | Two-group events + totals | OR, RR, RD | Common MH OR/RR; common/random IV |
71
87
  | Two-group means + SDs + sizes | MD, Hedges' g | Common/random inverse variance |
88
+ | Effect + variance/SE + moderators | Generic | Common/mixed Meta-regression |
72
89
 
73
90
  Random-effects inverse-variance models support REML (default), Paule-Mandel,
74
91
  and DerSimonian-Laird tau-squared estimators. Mean confidence intervals support
@@ -111,11 +128,23 @@ Rows excluded by missing-value or sparse-data policies remain in
111
128
  `study_results` with a stable `row_id`, `included=False`, and an
112
129
  `exclusion_reason`.
113
130
 
114
- ## Sensitivity and plots
131
+ ## Diagnostics, contrasts, and plots
115
132
 
116
133
  ```python
117
134
  leave_one_out = result.leave_one_out().to_dataframe()
118
135
  cumulative = result.cumulative(order="publication_year").to_dataframe()
136
+ regression_deleted = regression.leave_one_out()
137
+ regression_coefficient_changes = regression_deleted.coefficients
138
+ regression_influence = regression.influence()
139
+ flagged_diagnostics = regression_influence.flagged
140
+ collinearity = regression.collinearity()
141
+ term_vif = collinearity.term_vif
142
+ moderator_gvif = collinearity.moderator_gvif
143
+ condition_indices = collinearity.condition_indices
144
+ south_vs_east = regression.contrast(
145
+ {"region[South]": 1.0, "region[East]": -1.0},
146
+ name="South - East",
147
+ )
119
148
 
120
149
  ax = result.forest(show_prediction_interval=True)
121
150
  ax = result.funnel()
@@ -123,6 +152,20 @@ ax = result.funnel()
123
152
 
124
153
  Plotting methods return Matplotlib axes and never call `show()`. Funnel plots
125
154
  are descriptive small-study-effect diagnostics, not proof of publication bias.
155
+ Meta-regression leave-one-out results also expose a long-form coefficient
156
+ change table. Exact influence diagnostics add externally standardized
157
+ residuals, Cook's distance, DFBETAS, and explicit heuristic screening
158
+ thresholds without automatically excluding studies. Meta-regression
159
+ collinearity diagnostics add `metafor`-compatible VIF/GVIF plus weighted,
160
+ column-scaled condition indices and variance-decomposition proportions.
161
+ Their documented references are review aids, not automatic variable-selection
162
+ rules. Explicit named linear contrasts provide individual z/t inference and
163
+ full-rank joint chi-squared/F tests without silently adjusting for multiple
164
+ testing. An eligible
165
+ single-numeric-moderator Meta-regression result additionally provides
166
+ `regression.bubble()` with fitted confidence and optional prediction bands.
167
+ Mixed-effects Meta-regression supports its documented default prediction rule
168
+ and an explicit Riley `t_(k-p-1)` alternative.
126
169
 
127
170
  ## Documentation
128
171
 
@@ -133,6 +176,7 @@ The complete documentation is published at
133
176
  - [Getting started](https://zhaoboding.github.io/PyMetaAnalysis/getting-started/)
134
177
  - [Input data and row decisions](https://zhaoboding.github.io/PyMetaAnalysis/guides/input-data/)
135
178
  - [Generic](https://zhaoboding.github.io/PyMetaAnalysis/guides/generic-effects/), [binary](https://zhaoboding.github.io/PyMetaAnalysis/guides/binary-outcomes/), and [continuous](https://zhaoboding.github.io/PyMetaAnalysis/guides/continuous-outcomes/) guides
179
+ - [Meta-regression](https://zhaoboding.github.io/PyMetaAnalysis/guides/meta-regression/)
136
180
  - [Choosing methods](https://zhaoboding.github.io/PyMetaAnalysis/guides/method-selection/) and [statistical formulas](https://zhaoboding.github.io/PyMetaAnalysis/methods/statistical-methods/)
137
181
  - [Sensitivity analysis](https://zhaoboding.github.io/PyMetaAnalysis/guides/sensitivity-analysis/) and [plotting](https://zhaoboding.github.io/PyMetaAnalysis/guides/plotting/)
138
182
  - [Public API](https://zhaoboding.github.io/PyMetaAnalysis/reference/api/), [result objects](https://zhaoboding.github.io/PyMetaAnalysis/reference/results/), and [report schema](https://zhaoboding.github.io/PyMetaAnalysis/reference/report-schema/)
@@ -1,9 +1,10 @@
1
1
  # Core performance baseline
2
2
 
3
- `benchmark_core.py` measures representative generic, binary, and continuous
4
- random-effects fits using deterministic synthetic data. Correctness remains the
5
- primary performance requirement; this benchmark is intended to reveal large
6
- regressions, not to enforce a fragile wall-clock threshold.
3
+ `benchmark_core.py` measures representative generic, binary, continuous, and
4
+ multivariable Meta-regression random-effects fits using deterministic synthetic
5
+ data. Correctness remains the primary performance requirement; this benchmark
6
+ is intended to reveal large regressions, not to enforce a fragile wall-clock
7
+ threshold.
7
8
 
8
9
  Run the default benchmark with:
9
10
 
@@ -34,6 +34,8 @@ def _cases(studies: int) -> dict[str, Callable[[], object]]:
34
34
 
35
35
  generic_effect = rng.normal(0.1, 0.25, size=studies)
36
36
  generic_variance = rng.uniform(0.01, 0.09, size=studies)
37
+ moderator = np.linspace(-1.0, 1.0, studies)
38
+ region = np.resize(np.array(["North", "South", "East"], dtype=object), studies)
37
39
 
38
40
  n_treat = rng.integers(60, 240, size=studies)
39
41
  n_control = rng.integers(60, 240, size=studies)
@@ -75,6 +77,14 @@ def _cases(studies: int) -> dict[str, Callable[[], object]]:
75
77
  model="random",
76
78
  tau2_method="REML",
77
79
  ),
80
+ "meta_regression_multivariable_reml": lambda: ma.meta_regression(
81
+ effect=generic_effect,
82
+ variance=generic_variance,
83
+ moderators={"moderator": moderator, "region": region},
84
+ categorical={"region": ["North", "South", "East"]},
85
+ model="mixed",
86
+ tau2_method="REML",
87
+ ),
78
88
  }
79
89
 
80
90
 
@@ -102,8 +112,10 @@ def main() -> None:
102
112
  parser.add_argument("--number", type=int, default=5)
103
113
  parser.add_argument("--output", type=Path)
104
114
  arguments = parser.parse_args()
105
- if min(arguments.studies, arguments.repeat, arguments.number) < 1:
106
- parser.error("studies, repeat, and number must all be positive")
115
+ if arguments.studies < 5:
116
+ parser.error("studies must be at least 5 for the multivariable benchmark")
117
+ if min(arguments.repeat, arguments.number) < 1:
118
+ parser.error("repeat and number must both be positive")
107
119
 
108
120
  payload = _metadata(arguments)
109
121
  payload["cases"] = {
@@ -0,0 +1,70 @@
1
+ # ADR 0003: Meta-regression prediction-interval choices
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-07-23
5
+
6
+ ## Context
7
+
8
+ For a mixed-effects Meta-regression prediction at design vector `x`, the
9
+ estimated true-effect variance combines residual heterogeneity with
10
+ uncertainty in the fitted mean:
11
+
12
+ ```text
13
+ Var_prediction = tau^2 + x' Cov(beta_hat) x
14
+ ```
15
+
16
+ The critical-value distribution remains a methodological choice. Common
17
+ software offers a default normal-or-t rule and a Riley alternative with one
18
+ fewer residual degree of freedom. Neither approximation removes uncertainty
19
+ from estimating tau-squared, and changing the package default would alter
20
+ existing results.
21
+
22
+ ## Decision
23
+
24
+ `meta_regression()` accepts
25
+ `prediction_interval_method="default" | "riley"` for mixed-effects models.
26
+ The canonical resolved method is stored in
27
+ `result.method.prediction_interval_method`.
28
+
29
+ The default remains `normal_or_t_k_minus_p`:
30
+
31
+ - normal coefficient inference uses a standard normal prediction critical
32
+ value;
33
+ - either Hartung-Knapp mode uses a t critical value with `k-p` degrees of
34
+ freedom.
35
+
36
+ The opt-in Riley method uses a t critical value with `k-p-1` degrees of
37
+ freedom regardless of the coefficient-inference distribution:
38
+
39
+ ```text
40
+ prediction = x' beta_hat
41
+ PI_Riley = prediction +/- t_(k-p-1) *
42
+ sqrt(tau^2 + x' Cov(beta_hat) x)
43
+ ```
44
+
45
+ Riley intervals require `k-p >= 2`. Requesting Riley for a common-effect model
46
+ or a mixed model without enough residual degrees of freedom raises a domain
47
+ error. The selection is preserved by deleted-study refits and reused by
48
+ `predict()` and `bubble()`.
49
+
50
+ Both rules predict the distribution of true effects in a new study at the
51
+ specified moderator values. They do not add an unknown sampling variance for
52
+ a future observed effect.
53
+
54
+ ## Validation
55
+
56
+ The committed fixed-version R `metafor` fixture records default and
57
+ `predtype="Riley"` predictions for normal and Hartung-Knapp inference,
58
+ multivariable moderator values, and a zero-tau-squared boundary. Unit tests
59
+ also check the critical-value formula and invalid degrees of freedom.
60
+ Property-based tests verify symmetry, unchanged mean-effect inference, and
61
+ the Riley interval's greater width relative to the default rule.
62
+
63
+ ## Consequences
64
+
65
+ - existing fits retain their numerical default;
66
+ - the alternative is explicit, auditable, and reproducible;
67
+ - Riley is documented as an alternative approximation rather than an
68
+ automatic small-sample correction;
69
+ - future prediction-interval rules require a separate statistical decision
70
+ and independent reference coverage.
@@ -71,7 +71,7 @@ result.ci
71
71
  result.standard_error
72
72
  result.tau2
73
73
  result.q
74
- result.i2 # a proportion from 0 to 1
74
+ result.i2 # a proportion from 0 to 1
75
75
  result.h2
76
76
  result.i2_method
77
77
  ```