PyMetaAnalysis 0.2.0__tar.gz → 0.3.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 (115) hide show
  1. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/CHANGELOG.md +29 -0
  2. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/CITATION.cff +2 -2
  3. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/PKG-INFO +46 -19
  4. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/README.md +45 -18
  5. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/benchmarks/README.md +5 -4
  6. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/benchmarks/benchmark_core.py +14 -2
  7. pymetaanalysis-0.3.0/docs/guides/meta-regression.md +220 -0
  8. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/guides/plotting.md +32 -1
  9. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/guides/provenance-reporting.md +1 -1
  10. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/index.md +8 -5
  11. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/limitations.md +14 -5
  12. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/methods/statistical-methods.md +121 -0
  13. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/reference/api.md +63 -3
  14. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/reference/report-schema.md +35 -4
  15. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/reference/results.md +69 -0
  16. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/releasing.md +18 -0
  17. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/validation.md +20 -0
  18. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/examples/README.md +4 -1
  19. pymetaanalysis-0.3.0/examples/meta_regression.ipynb +290 -0
  20. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/mkdocs.yml +1 -0
  21. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/__init__.py +17 -1
  22. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/_version.py +1 -1
  23. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/config.py +18 -0
  24. pymetaanalysis-0.3.0/src/meta_analyze/design_matrix.py +477 -0
  25. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/estimators/__init__.py +12 -0
  26. pymetaanalysis-0.3.0/src/meta_analyze/estimators/meta_regression.py +462 -0
  27. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/plotting/__init__.py +2 -1
  28. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/plotting/_utils.py +2 -2
  29. pymetaanalysis-0.3.0/src/meta_analyze/plotting/regression.py +128 -0
  30. pymetaanalysis-0.3.0/src/meta_analyze/regression_api.py +396 -0
  31. pymetaanalysis-0.3.0/src/meta_analyze/regression_results.py +388 -0
  32. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/reporting.py +169 -1
  33. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/reference/README.md +11 -0
  34. pymetaanalysis-0.3.0/tests/reference/generate_meta_regression_metafor.R +339 -0
  35. pymetaanalysis-0.3.0/tests/reference/meta_regression_boundary_input.csv +9 -0
  36. pymetaanalysis-0.3.0/tests/reference/meta_regression_input.csv +16 -0
  37. pymetaanalysis-0.3.0/tests/reference/meta_regression_metafor.json +1600 -0
  38. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/test_documentation.py +10 -0
  39. pymetaanalysis-0.3.0/tests/test_meta_regression.py +634 -0
  40. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/test_numerical_stability.py +85 -0
  41. pymetaanalysis-0.3.0/tests/test_properties.py +252 -0
  42. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/test_r_references.py +331 -1
  43. pymetaanalysis-0.3.0/tests/test_regression_plotting.py +200 -0
  44. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/test_release_readiness.py +23 -17
  45. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/test_reporting.py +1 -1
  46. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tools/inspect_distribution.py +1 -0
  47. pymetaanalysis-0.2.0/tests/test_properties.py +0 -116
  48. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/.github/workflows/ci.yml +0 -0
  49. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/.github/workflows/pages.yml +0 -0
  50. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/.github/workflows/release.yml +0 -0
  51. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/.gitignore +0 -0
  52. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/CONTRIBUTING.md +0 -0
  53. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/LICENSE +0 -0
  54. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/SECURITY.md +0 -0
  55. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/adr/0001-optional-matplotlib.md +0 -0
  56. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/adr/0002-statistical-policy.md +0 -0
  57. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/citation.md +0 -0
  58. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/development.md +0 -0
  59. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/getting-started.md +0 -0
  60. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/guides/binary-outcomes.md +0 -0
  61. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/guides/continuous-outcomes.md +0 -0
  62. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/guides/generic-effects.md +0 -0
  63. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/guides/input-data.md +0 -0
  64. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/guides/method-selection.md +0 -0
  65. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/guides/r-interoperability.md +0 -0
  66. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/guides/sensitivity-analysis.md +0 -0
  67. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/guides/zero-events.md +0 -0
  68. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/installation.md +0 -0
  69. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/docs/stylesheets/extra.css +0 -0
  70. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/examples/quickstart.ipynb +0 -0
  71. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/pyproject.toml +0 -0
  72. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/api.py +0 -0
  73. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/binary_api.py +0 -0
  74. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/continuous_api.py +0 -0
  75. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/data.py +0 -0
  76. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/effect_sizes/__init__.py +0 -0
  77. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/effect_sizes/binary.py +0 -0
  78. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/effect_sizes/continuous.py +0 -0
  79. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/estimators/inverse_variance.py +0 -0
  80. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/estimators/mantel_haenszel.py +0 -0
  81. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/estimators/tau2.py +0 -0
  82. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/exceptions.py +0 -0
  83. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/heterogeneity.py +0 -0
  84. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/plotting/forest.py +0 -0
  85. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/plotting/funnel.py +0 -0
  86. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/plotting/subgroup_forest.py +0 -0
  87. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/provenance.py +0 -0
  88. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/py.typed +0 -0
  89. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/results.py +0 -0
  90. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/sensitivity.py +0 -0
  91. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/src/meta_analyze/subgroups.py +0 -0
  92. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/reference/binary_input.csv +0 -0
  93. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/reference/binary_metafor.json +0 -0
  94. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/reference/binary_sparse_input.csv +0 -0
  95. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/reference/continuous_input.csv +0 -0
  96. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/reference/continuous_metafor.json +0 -0
  97. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/reference/generate_binary_metafor.R +0 -0
  98. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/reference/generate_continuous_metafor.R +0 -0
  99. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/reference/generate_generic_metafor.R +0 -0
  100. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/reference/generate_workflow_metafor.R +0 -0
  101. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/reference/generic_input.csv +0 -0
  102. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/reference/generic_metafor.json +0 -0
  103. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/reference/workflow_input.csv +0 -0
  104. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/reference/workflow_metafor.json +0 -0
  105. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/test_api.py +0 -0
  106. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/test_binary.py +0 -0
  107. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/test_continuous.py +0 -0
  108. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/test_estimators.py +0 -0
  109. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/test_funnel_plot.py +0 -0
  110. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/test_plotting.py +0 -0
  111. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/test_reference_results.py +0 -0
  112. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/test_sensitivity.py +0 -0
  113. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tests/test_subgroups.py +0 -0
  114. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tools/check_release.py +0 -0
  115. {pymetaanalysis-0.2.0 → pymetaanalysis-0.3.0}/tools/execute_notebooks.py +0 -0
@@ -6,6 +6,35 @@ Changes planned for the next release accumulate under `Unreleased`.
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## 0.3.0 - 2026-07-22
10
+
11
+ ### Added
12
+
13
+ - pandas-first `meta_regression()` for numeric, explicitly encoded categorical,
14
+ and multiple study-level moderators;
15
+ - common- and mixed-effects weighted regression with generalized DL, PM, and
16
+ REML residual tau-squared estimators;
17
+ - normal, Hartung-Knapp, and safeguarded Hartung-Knapp coefficient inference,
18
+ distribution-explicit moderator tests, residual heterogeneity, pseudo-R²,
19
+ prediction, provenance, and structured reports;
20
+ - optional weighted bubble plots for intercept-containing Meta-regression fits
21
+ with exactly one numeric moderator;
22
+ - independent R `metafor` fixtures covering numeric, categorical,
23
+ multivariable, zero-tau-squared, missing-row, and small-sample cases;
24
+ - an executable Meta-regression notebook plus a multivariable performance
25
+ baseline and expanded property, numerical-stability, and warning tests.
26
+
27
+ ### Changed
28
+
29
+ - report schema 1.2 adds the `meta_regression` report type.
30
+
31
+ ## 0.2.1 - 2026-07-17
32
+
33
+ ### Fixed
34
+
35
+ - README documentation and repository links use absolute URLs so they resolve
36
+ correctly when the project description is rendered on PyPI.
37
+
9
38
  ## 0.2.0 - 2026-07-16
10
39
 
11
40
  ### Added
@@ -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.0
12
- date-released: 2026-07-16
11
+ version: 0.3.0
12
+ date-released: 2026-07-22
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.0
3
+ Version: 0.3.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
@@ -51,7 +51,7 @@ Description-Content-Type: text/markdown
51
51
 
52
52
  [![CI](https://github.com/ZhaoboDing/PyMetaAnalysis/actions/workflows/ci.yml/badge.svg)](https://github.com/ZhaoboDing/PyMetaAnalysis/actions/workflows/ci.yml)
53
53
  [![Documentation](https://github.com/ZhaoboDing/PyMetaAnalysis/actions/workflows/pages.yml/badge.svg)](https://zhaoboding.github.io/PyMetaAnalysis/)
54
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
54
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/ZhaoboDing/PyMetaAnalysis/blob/main/LICENSE)
55
55
 
56
56
  PyMetaAnalysis is an early-stage, pandas-first Python library for conventional
57
57
  study-level meta-analysis. It accepts DataFrames, NumPy arrays, and ordinary
@@ -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
@@ -172,24 +189,28 @@ ax = result.funnel()
172
189
 
173
190
  Plotting methods return Matplotlib axes and never call `show()`. Funnel plots
174
191
  are descriptive small-study-effect diagnostics, not proof of publication bias.
192
+ An eligible single-numeric-moderator Meta-regression result additionally
193
+ provides `regression.bubble()` with fitted confidence and optional prediction
194
+ bands.
175
195
 
176
196
  ## Documentation
177
197
 
178
198
  The complete documentation is published at
179
199
  [zhaoboding.github.io/PyMetaAnalysis](https://zhaoboding.github.io/PyMetaAnalysis/).
180
200
 
181
- - [Installation](docs/installation.md)
182
- - [Getting started](docs/getting-started.md)
183
- - [Input data and row decisions](docs/guides/input-data.md)
184
- - [Generic](docs/guides/generic-effects.md), [binary](docs/guides/binary-outcomes.md), and [continuous](docs/guides/continuous-outcomes.md) guides
185
- - [Choosing methods](docs/guides/method-selection.md) and [statistical formulas](docs/methods/statistical-methods.md)
186
- - [Sensitivity analysis](docs/guides/sensitivity-analysis.md) and [plotting](docs/guides/plotting.md)
187
- - [Public API](docs/reference/api.md), [result objects](docs/reference/results.md), and [report schema](docs/reference/report-schema.md)
188
- - [Validation strategy](docs/validation.md) and [scope/limitations](docs/limitations.md)
189
- - [Citation guidance](docs/citation.md)
190
- - [R `meta`/`metafor` mapping](docs/guides/r-interoperability.md)
191
-
192
- An executable [end-to-end notebook](examples/quickstart.ipynb) uses synthetic
201
+ - [Installation](https://zhaoboding.github.io/PyMetaAnalysis/installation/)
202
+ - [Getting started](https://zhaoboding.github.io/PyMetaAnalysis/getting-started/)
203
+ - [Input data and row decisions](https://zhaoboding.github.io/PyMetaAnalysis/guides/input-data/)
204
+ - [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
205
+ - [Meta-regression](https://zhaoboding.github.io/PyMetaAnalysis/guides/meta-regression/)
206
+ - [Choosing methods](https://zhaoboding.github.io/PyMetaAnalysis/guides/method-selection/) and [statistical formulas](https://zhaoboding.github.io/PyMetaAnalysis/methods/statistical-methods/)
207
+ - [Sensitivity analysis](https://zhaoboding.github.io/PyMetaAnalysis/guides/sensitivity-analysis/) and [plotting](https://zhaoboding.github.io/PyMetaAnalysis/guides/plotting/)
208
+ - [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/)
209
+ - [Validation strategy](https://zhaoboding.github.io/PyMetaAnalysis/validation/) and [scope/limitations](https://zhaoboding.github.io/PyMetaAnalysis/limitations/)
210
+ - [Citation guidance](https://zhaoboding.github.io/PyMetaAnalysis/citation/)
211
+ - [R `meta`/`metafor` mapping](https://zhaoboding.github.io/PyMetaAnalysis/guides/r-interoperability/)
212
+
213
+ An executable [end-to-end notebook](https://github.com/ZhaoboDing/PyMetaAnalysis/blob/main/examples/quickstart.ipynb) uses synthetic
193
214
  data to demonstrate analysis, provenance, reporting, sensitivity, and plotting.
194
215
 
195
216
  Build the complete site locally with:
@@ -207,16 +228,22 @@ edge cases, and committed R `metafor` reference fixtures. CI covers Python
207
228
  distribution builds.
208
229
 
209
230
  This is independent cross-software validation, not a formal external
210
- statistical audit. See [validation](docs/validation.md) for exact coverage.
231
+ statistical audit. See
232
+ [validation](https://zhaoboding.github.io/PyMetaAnalysis/validation/) for exact
233
+ coverage.
211
234
 
212
235
  ## Contributing
213
236
 
214
- See [CONTRIBUTING.md](CONTRIBUTING.md) and the full
215
- [development guide](docs/development.md). Statistical changes require formula
237
+ See
238
+ [CONTRIBUTING.md](https://github.com/ZhaoboDing/PyMetaAnalysis/blob/main/CONTRIBUTING.md)
239
+ and the full
240
+ [development guide](https://zhaoboding.github.io/PyMetaAnalysis/development/).
241
+ Statistical changes require formula
216
242
  documentation, boundary tests, and an independent comparison where available.
217
243
 
218
- Security-sensitive reports should follow [SECURITY.md](SECURITY.md).
244
+ Security-sensitive reports should follow
245
+ [SECURITY.md](https://github.com/ZhaoboDing/PyMetaAnalysis/blob/main/SECURITY.md).
219
246
 
220
247
  ## License
221
248
 
222
- [MIT](LICENSE)
249
+ [MIT](https://github.com/ZhaoboDing/PyMetaAnalysis/blob/main/LICENSE)
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/ZhaoboDing/PyMetaAnalysis/actions/workflows/ci.yml/badge.svg)](https://github.com/ZhaoboDing/PyMetaAnalysis/actions/workflows/ci.yml)
4
4
  [![Documentation](https://github.com/ZhaoboDing/PyMetaAnalysis/actions/workflows/pages.yml/badge.svg)](https://zhaoboding.github.io/PyMetaAnalysis/)
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/ZhaoboDing/PyMetaAnalysis/blob/main/LICENSE)
6
6
 
7
7
  PyMetaAnalysis is an early-stage, pandas-first Python library for conventional
8
8
  study-level meta-analysis. It accepts DataFrames, NumPy arrays, and ordinary
@@ -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
@@ -123,24 +140,28 @@ ax = result.funnel()
123
140
 
124
141
  Plotting methods return Matplotlib axes and never call `show()`. Funnel plots
125
142
  are descriptive small-study-effect diagnostics, not proof of publication bias.
143
+ An eligible single-numeric-moderator Meta-regression result additionally
144
+ provides `regression.bubble()` with fitted confidence and optional prediction
145
+ bands.
126
146
 
127
147
  ## Documentation
128
148
 
129
149
  The complete documentation is published at
130
150
  [zhaoboding.github.io/PyMetaAnalysis](https://zhaoboding.github.io/PyMetaAnalysis/).
131
151
 
132
- - [Installation](docs/installation.md)
133
- - [Getting started](docs/getting-started.md)
134
- - [Input data and row decisions](docs/guides/input-data.md)
135
- - [Generic](docs/guides/generic-effects.md), [binary](docs/guides/binary-outcomes.md), and [continuous](docs/guides/continuous-outcomes.md) guides
136
- - [Choosing methods](docs/guides/method-selection.md) and [statistical formulas](docs/methods/statistical-methods.md)
137
- - [Sensitivity analysis](docs/guides/sensitivity-analysis.md) and [plotting](docs/guides/plotting.md)
138
- - [Public API](docs/reference/api.md), [result objects](docs/reference/results.md), and [report schema](docs/reference/report-schema.md)
139
- - [Validation strategy](docs/validation.md) and [scope/limitations](docs/limitations.md)
140
- - [Citation guidance](docs/citation.md)
141
- - [R `meta`/`metafor` mapping](docs/guides/r-interoperability.md)
142
-
143
- An executable [end-to-end notebook](examples/quickstart.ipynb) uses synthetic
152
+ - [Installation](https://zhaoboding.github.io/PyMetaAnalysis/installation/)
153
+ - [Getting started](https://zhaoboding.github.io/PyMetaAnalysis/getting-started/)
154
+ - [Input data and row decisions](https://zhaoboding.github.io/PyMetaAnalysis/guides/input-data/)
155
+ - [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
156
+ - [Meta-regression](https://zhaoboding.github.io/PyMetaAnalysis/guides/meta-regression/)
157
+ - [Choosing methods](https://zhaoboding.github.io/PyMetaAnalysis/guides/method-selection/) and [statistical formulas](https://zhaoboding.github.io/PyMetaAnalysis/methods/statistical-methods/)
158
+ - [Sensitivity analysis](https://zhaoboding.github.io/PyMetaAnalysis/guides/sensitivity-analysis/) and [plotting](https://zhaoboding.github.io/PyMetaAnalysis/guides/plotting/)
159
+ - [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/)
160
+ - [Validation strategy](https://zhaoboding.github.io/PyMetaAnalysis/validation/) and [scope/limitations](https://zhaoboding.github.io/PyMetaAnalysis/limitations/)
161
+ - [Citation guidance](https://zhaoboding.github.io/PyMetaAnalysis/citation/)
162
+ - [R `meta`/`metafor` mapping](https://zhaoboding.github.io/PyMetaAnalysis/guides/r-interoperability/)
163
+
164
+ An executable [end-to-end notebook](https://github.com/ZhaoboDing/PyMetaAnalysis/blob/main/examples/quickstart.ipynb) uses synthetic
144
165
  data to demonstrate analysis, provenance, reporting, sensitivity, and plotting.
145
166
 
146
167
  Build the complete site locally with:
@@ -158,16 +179,22 @@ edge cases, and committed R `metafor` reference fixtures. CI covers Python
158
179
  distribution builds.
159
180
 
160
181
  This is independent cross-software validation, not a formal external
161
- statistical audit. See [validation](docs/validation.md) for exact coverage.
182
+ statistical audit. See
183
+ [validation](https://zhaoboding.github.io/PyMetaAnalysis/validation/) for exact
184
+ coverage.
162
185
 
163
186
  ## Contributing
164
187
 
165
- See [CONTRIBUTING.md](CONTRIBUTING.md) and the full
166
- [development guide](docs/development.md). Statistical changes require formula
188
+ See
189
+ [CONTRIBUTING.md](https://github.com/ZhaoboDing/PyMetaAnalysis/blob/main/CONTRIBUTING.md)
190
+ and the full
191
+ [development guide](https://zhaoboding.github.io/PyMetaAnalysis/development/).
192
+ Statistical changes require formula
167
193
  documentation, boundary tests, and an independent comparison where available.
168
194
 
169
- Security-sensitive reports should follow [SECURITY.md](SECURITY.md).
195
+ Security-sensitive reports should follow
196
+ [SECURITY.md](https://github.com/ZhaoboDing/PyMetaAnalysis/blob/main/SECURITY.md).
170
197
 
171
198
  ## License
172
199
 
173
- [MIT](LICENSE)
200
+ [MIT](https://github.com/ZhaoboDing/PyMetaAnalysis/blob/main/LICENSE)
@@ -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,220 @@
1
+ # Meta-regression
2
+
3
+ Meta-regression relates study effect estimates to one or more study-level
4
+ moderators. It is a natural extension of subgroup analysis, but it does not
5
+ turn aggregate study data into individual-level evidence.
6
+
7
+ !!! warning "Interpretation boundary"
8
+
9
+ A Meta-regression coefficient is a study-level association. It may reflect
10
+ ecological bias, confounding, measurement differences, or post-hoc model
11
+ selection and must not be interpreted as an individual-level causal effect.
12
+ Fits with fewer than ten studies carry an explicit warning.
13
+
14
+ ## Fit a numeric moderator
15
+
16
+ Provide a generic effect plus exactly one of its sampling variance or standard
17
+ error:
18
+
19
+ ```python
20
+ import pandas as pd
21
+ import meta_analyze as ma
22
+
23
+ studies = pd.DataFrame(
24
+ {
25
+ "citation": ["A", "B", "C", "D", "E", "F"],
26
+ "effect": [0.12, 0.25, 0.41, 0.38, 0.62, 0.76],
27
+ "se": [0.18, 0.20, 0.17, 0.23, 0.19, 0.21],
28
+ "mean_age": [42.0, 48.0, 51.0, 55.0, 60.0, 64.0],
29
+ }
30
+ )
31
+
32
+ result = ma.meta_regression(
33
+ studies,
34
+ effect="effect",
35
+ standard_error="se",
36
+ moderators=["mean_age"],
37
+ study="citation",
38
+ model="mixed",
39
+ tau2_method="REML",
40
+ )
41
+
42
+ print(result.summary())
43
+ print(result.coefficients)
44
+ ```
45
+
46
+ Numeric moderators are used exactly as supplied. PyMetaAnalysis does not
47
+ center, scale, transform, or impute them automatically.
48
+
49
+ ## Encode categorical moderators explicitly
50
+
51
+ Every categorical moderator requires an ordered, complete list of levels. The
52
+ first level is the treatment-coding reference:
53
+
54
+ ```python
55
+ result = ma.meta_regression(
56
+ studies,
57
+ effect="effect",
58
+ standard_error="se",
59
+ moderators=["mean_age", "region"],
60
+ categorical={
61
+ "region": ["Europe", "Asia", "North America"],
62
+ },
63
+ )
64
+ ```
65
+
66
+ This produces terms such as `region[Asia]` and `region[North America]`, each
67
+ relative to `Europe`. The reference never depends on row order. Undeclared
68
+ levels, levels absent after exclusions, and string moderators omitted from
69
+ `categorical=` are errors rather than implicit recoding decisions.
70
+
71
+ Formula parsing, automatic interactions, splines, and polynomial terms are not
72
+ implemented. Construct those columns explicitly before fitting when they are
73
+ scientifically prespecified.
74
+
75
+ ## Array-like input
76
+
77
+ Use a mapping when moderators are arrays rather than DataFrame column names:
78
+
79
+ ```python
80
+ result = ma.meta_regression(
81
+ effect=[0.10, 0.32, 0.45, 0.71],
82
+ variance=[0.04, 0.05, 0.06, 0.08],
83
+ moderators={"dose": [0.0, 1.0, 2.0, 3.0]},
84
+ model="common",
85
+ )
86
+ ```
87
+
88
+ A sequence such as `moderators=["mean_age", "region"]` is only meaningful
89
+ with a DataFrame. A mapping may mix DataFrame column selectors and array-like
90
+ values, provided every input has one value per row.
91
+
92
+ ## Models and inference
93
+
94
+ `model="mixed"` is the default. It estimates residual tau-squared after the
95
+ moderators using REML, PM, or DL. `model="common"` fixes residual tau-squared
96
+ at zero and supports normal inference only.
97
+
98
+ | Setting | Coefficient tests and intervals | Joint moderator test |
99
+ | --- | --- | --- |
100
+ | `normal` | z / normal | chi-squared |
101
+ | `hartung_knapp` | t with `k-p` df | F with `k-p` denominator df |
102
+ | `hartung_knapp_adhoc` | safeguarded t with `k-p` df | safeguarded F |
103
+
104
+ The unmodified Hartung-Knapp result warns when its covariance is below the
105
+ classic covariance. The `adhoc` choice explicitly applies a lower-bound scale
106
+ of one. Common-effect models reject both Hartung-Knapp choices.
107
+
108
+ The design matrix must be full rank and leave positive residual degrees of
109
+ freedom (`k > p`). PyMetaAnalysis does not silently drop collinear terms or use
110
+ a pseudo-inverse.
111
+
112
+ ## Inspect the result
113
+
114
+ Meta-regression has no single pooled effect, so `MetaRegressionResult` does not
115
+ provide a scalar `estimate` or `ci`. Inspect its coefficient table instead:
116
+
117
+ ```python
118
+ result.coefficients
119
+ result.coefficient_covariance
120
+ result.global_test
121
+ result.test_moderator("region")
122
+ result.heterogeneity
123
+ result.tau2
124
+ result.tau2_null
125
+ result.pseudo_r2
126
+ result.diagnostics
127
+ ```
128
+
129
+ `global_test` tests all non-intercept terms. `test_moderator(name)` tests every
130
+ encoded term belonging to that original moderator, so a multi-level category
131
+ receives one joint test rather than separate interpretation through dummy-term
132
+ p-values.
133
+
134
+ `study_results` retains every input row and contains fitted values, residuals,
135
+ precision weights, normalized precision weights, and leverage. A regression
136
+ precision weight is not a universal percentage contribution to every
137
+ coefficient.
138
+
139
+ ## Predict at moderator values
140
+
141
+ Prediction replays the fitted numeric and categorical encoding:
142
+
143
+ ```python
144
+ predictions = result.predict(
145
+ pd.DataFrame(
146
+ {
147
+ "mean_age": [50.0, 65.0],
148
+ "region": ["Europe", "Asia"],
149
+ }
150
+ )
151
+ )
152
+ ```
153
+
154
+ Every model returns the fitted mean and its confidence interval. Mixed-effects
155
+ models also return `pi_low` and `pi_high` for the distribution of true effects
156
+ in a new study with those moderators. The interval does not include an
157
+ additional, unknown sampling variance for a future observed estimate. Unknown
158
+ categories and missing prediction inputs are rejected.
159
+
160
+ ## Plot a single numeric moderator
161
+
162
+ After installing the `plot` extra, an intercept-containing model with exactly
163
+ one numeric moderator can be displayed as a weighted bubble plot:
164
+
165
+ ```python
166
+ ax = result.bubble(
167
+ moderator_label="Mean age (years)",
168
+ effect_label="Treatment effect",
169
+ show_confidence_interval=True,
170
+ show_prediction_interval=True,
171
+ )
172
+ ```
173
+
174
+ Bubble area is proportional to normalized fitted precision weight. The fitted
175
+ line and interval bands reuse `result.predict()`, including the selected normal
176
+ or Hartung-Knapp covariance and critical value. A prediction band is available
177
+ only for mixed-effects models.
178
+
179
+ PyMetaAnalysis rejects bubble plots for categorical, multiple-moderator, or
180
+ no-intercept fits. Drawing a marginal or partial-effect line for those models
181
+ requires explicit choices for the other moderator values, and the library does
182
+ not silently choose them.
183
+
184
+ ## Residual heterogeneity and pseudo-R²
185
+
186
+ The result reports residual `QE`, I-squared, H-squared, and tau-squared. For an
187
+ intercept-containing mixed model, PyMetaAnalysis also refits the same included
188
+ rows without moderators and reports:
189
+
190
+ ```text
191
+ pseudo-R² = max(0, 1 - tau²_model / tau²_null)
192
+ ```
193
+
194
+ The raw value is retained as `pseudo_r2_raw`. If the null-model tau-squared is
195
+ zero, pseudo-R² is undefined; if moderators increase estimated tau-squared,
196
+ the public value is truncated to zero and the negative raw value is retained
197
+ with a warning. Pseudo-R² is not the proportion of outcome variance explained
198
+ in ordinary individual-level regression.
199
+
200
+ ## Missing values, provenance, and reports
201
+
202
+ `missing="raise"` identifies every missing effect, uncertainty, study label,
203
+ or moderator field by row. `missing="drop"` excludes the entire row from every
204
+ model calculation while retaining it in `study_results` with all applicable
205
+ reasons.
206
+
207
+ ```python
208
+ result.provenance
209
+ result.method_details()
210
+ result.report().to_json()
211
+ ```
212
+
213
+ Provenance records moderator input roles and categorical treatment coding.
214
+ Reports include coefficients, their covariance, residual heterogeneity,
215
+ moderator tests, encoding, diagnostics, row decisions, and the study-level
216
+ interpretation warning.
217
+
218
+ See [statistical methods](../methods/statistical-methods.md#meta-regression) for
219
+ the equations and [scope and limitations](../limitations.md) before using the
220
+ model for consequential work.
@@ -1,6 +1,7 @@
1
1
  # Plotting
2
2
 
3
- Forest, subgroup forest, and funnel plots use optional Matplotlib support.
3
+ Forest, subgroup forest, funnel, and Meta-regression bubble plots use optional
4
+ Matplotlib support.
4
5
  Install it with:
5
6
 
6
7
  ```console
@@ -96,6 +97,36 @@ selection, design differences, chance, or publication processes. It is not by
96
97
  itself evidence of publication bias. PyMetaAnalysis currently provides the
97
98
  plot but not formal asymmetry tests.
98
99
 
100
+ ## Meta-regression bubble plots
101
+
102
+ An intercept-containing Meta-regression with exactly one numeric moderator
103
+ provides:
104
+
105
+ ```python
106
+ ax = regression.bubble(
107
+ moderator_label="Dose",
108
+ effect_label="Effect",
109
+ show_confidence_interval=True,
110
+ show_prediction_interval=False,
111
+ )
112
+ ```
113
+
114
+ Study marker area is proportional to normalized precision weight. The line,
115
+ mean confidence band, and optional mixed-effects true-effect prediction band
116
+ are obtained from the fitted model's `predict()` method.
117
+
118
+ | Parameter | Meaning |
119
+ | --- | --- |
120
+ | `ax` | Existing axes; a new one is created when omitted |
121
+ | `moderator_label` | X-axis label; defaults to the moderator name |
122
+ | `effect_label` | Y-axis label; defaults to `"Effect"` |
123
+ | `show_confidence_interval` | Draw the fitted mean confidence band |
124
+ | `show_prediction_interval` | Draw a mixed-effects true-effect prediction band |
125
+
126
+ Categorical, multivariable, and no-intercept fits are rejected because a
127
+ marginal plot would require values or averaging rules for other terms. The
128
+ function does not infer those scientific choices.
129
+
99
130
  ## Save or display
100
131
 
101
132
  The caller controls rendering:
@@ -100,7 +100,7 @@ The payload contains:
100
100
  - provenance and warnings;
101
101
  - row-level study results.
102
102
 
103
- Report schema 1.1 records the resolved heterogeneity definition as
103
+ Report schema 1.2 records the resolved heterogeneity definition as
104
104
  `heterogeneity.i2_method`.
105
105
 
106
106
  The complete key-level contract, including the separately versioned
@@ -18,8 +18,9 @@ effects, exclusions, weights, method choices, and diagnostics.
18
18
  | Effect estimates and sampling variances | `meta_analysis()` | Generic inverse variance |
19
19
  | Events and totals for two groups | `meta_binary()` | OR, RR, RD |
20
20
  | Means, SDs, and sample sizes for two groups | `meta_continuous()` | MD, Hedges' g |
21
+ | Effect estimates, uncertainty, and study-level moderators | `meta_regression()` | Generic Meta-regression |
21
22
 
22
- All three functions accept an optional `subgroup=` column or array. Supplying
23
+ The three pooling functions accept an optional `subgroup=` column or array. Supplying
23
24
  it returns a dedicated subgroup result containing each group, the overall
24
25
  analysis, and a formal test for subgroup differences.
25
26
 
@@ -52,6 +53,7 @@ shows how to inspect its output. Continue with the guide matching your input:
52
53
  - [generic effects and variances](guides/generic-effects.md);
53
54
  - [binary outcomes](guides/binary-outcomes.md);
54
55
  - [continuous outcomes](guides/continuous-outcomes.md).
56
+ - [study-level Meta-regression](guides/meta-regression.md).
55
57
 
56
58
  Read [input data and row decisions](guides/input-data.md) before building a
57
59
  pipeline around exclusions or mixed DataFrame/array inputs.
@@ -82,10 +84,11 @@ lists unsupported methods explicitly.
82
84
 
83
85
  ## Project status
84
86
 
85
- PyMetaAnalysis 0.2.0 adds direct generic standard-error input to the initial
86
- public analysis scope. It has not undergone a formal external statistical
87
- audit. Pin the package version for consequential work and independently check
88
- important analyses. See the repository
87
+ PyMetaAnalysis 0.2.1 fixes documentation and repository links in the PyPI
88
+ project description. Version 0.2.0 added direct generic standard-error input
89
+ to the initial public analysis scope. The project has not undergone a formal
90
+ external statistical audit. Pin the package version for consequential work
91
+ and independently check important analyses. See the repository
89
92
  [changelog](https://github.com/ZhaoboDing/PyMetaAnalysis/blob/main/CHANGELOG.md)
90
93
  and [contribution guide](development.md). For manuscripts and archived
91
94
  analyses, see [citing PyMetaAnalysis](citation.md).