PyMetaAnalysis 0.2.1__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.
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/CHANGELOG.md +22 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/CITATION.cff +2 -2
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/PKG-INFO +22 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/README.md +21 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/benchmarks/README.md +5 -4
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/benchmarks/benchmark_core.py +14 -2
- pymetaanalysis-0.3.0/docs/guides/meta-regression.md +220 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/guides/plotting.md +32 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/guides/provenance-reporting.md +1 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/index.md +3 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/limitations.md +13 -4
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/methods/statistical-methods.md +121 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/reference/api.md +63 -3
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/reference/report-schema.md +35 -4
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/reference/results.md +69 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/releasing.md +18 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/validation.md +20 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/examples/README.md +4 -1
- pymetaanalysis-0.3.0/examples/meta_regression.ipynb +290 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/mkdocs.yml +1 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/__init__.py +17 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/_version.py +1 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/config.py +18 -0
- pymetaanalysis-0.3.0/src/meta_analyze/design_matrix.py +477 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/estimators/__init__.py +12 -0
- pymetaanalysis-0.3.0/src/meta_analyze/estimators/meta_regression.py +462 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/plotting/__init__.py +2 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/plotting/_utils.py +2 -2
- pymetaanalysis-0.3.0/src/meta_analyze/plotting/regression.py +128 -0
- pymetaanalysis-0.3.0/src/meta_analyze/regression_api.py +396 -0
- pymetaanalysis-0.3.0/src/meta_analyze/regression_results.py +388 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/reporting.py +169 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/reference/README.md +11 -0
- pymetaanalysis-0.3.0/tests/reference/generate_meta_regression_metafor.R +339 -0
- pymetaanalysis-0.3.0/tests/reference/meta_regression_boundary_input.csv +9 -0
- pymetaanalysis-0.3.0/tests/reference/meta_regression_input.csv +16 -0
- pymetaanalysis-0.3.0/tests/reference/meta_regression_metafor.json +1600 -0
- pymetaanalysis-0.3.0/tests/test_meta_regression.py +634 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/test_numerical_stability.py +85 -0
- pymetaanalysis-0.3.0/tests/test_properties.py +252 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/test_r_references.py +331 -1
- pymetaanalysis-0.3.0/tests/test_regression_plotting.py +200 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/test_release_readiness.py +23 -17
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/test_reporting.py +1 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tools/inspect_distribution.py +1 -0
- pymetaanalysis-0.2.1/tests/test_properties.py +0 -116
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/.github/workflows/ci.yml +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/.github/workflows/pages.yml +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/.github/workflows/release.yml +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/.gitignore +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/CONTRIBUTING.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/LICENSE +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/SECURITY.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/adr/0001-optional-matplotlib.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/adr/0002-statistical-policy.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/citation.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/development.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/getting-started.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/guides/binary-outcomes.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/guides/continuous-outcomes.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/guides/generic-effects.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/guides/input-data.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/guides/method-selection.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/guides/r-interoperability.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/guides/sensitivity-analysis.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/guides/zero-events.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/installation.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/docs/stylesheets/extra.css +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/examples/quickstart.ipynb +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/pyproject.toml +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/api.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/binary_api.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/continuous_api.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/data.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/effect_sizes/__init__.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/effect_sizes/binary.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/effect_sizes/continuous.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/estimators/inverse_variance.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/estimators/mantel_haenszel.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/estimators/tau2.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/exceptions.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/heterogeneity.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/plotting/forest.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/plotting/funnel.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/plotting/subgroup_forest.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/provenance.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/py.typed +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/results.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/sensitivity.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/src/meta_analyze/subgroups.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/reference/binary_input.csv +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/reference/binary_metafor.json +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/reference/binary_sparse_input.csv +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/reference/continuous_input.csv +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/reference/continuous_metafor.json +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/reference/generate_binary_metafor.R +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/reference/generate_continuous_metafor.R +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/reference/generate_generic_metafor.R +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/reference/generate_workflow_metafor.R +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/reference/generic_input.csv +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/reference/generic_metafor.json +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/reference/workflow_input.csv +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/reference/workflow_metafor.json +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/test_api.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/test_binary.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/test_continuous.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/test_documentation.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/test_estimators.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/test_funnel_plot.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/test_plotting.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/test_reference_results.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/test_sensitivity.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tests/test_subgroups.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tools/check_release.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.3.0}/tools/execute_notebooks.py +0 -0
|
@@ -6,6 +6,28 @@ 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
|
+
|
|
9
31
|
## 0.2.1 - 2026-07-17
|
|
10
32
|
|
|
11
33
|
### 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.
|
|
12
|
-
date-released: 2026-07-
|
|
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.
|
|
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
|
|
@@ -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,6 +189,9 @@ 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
|
|
|
@@ -182,6 +202,7 @@ The complete documentation is published at
|
|
|
182
202
|
- [Getting started](https://zhaoboding.github.io/PyMetaAnalysis/getting-started/)
|
|
183
203
|
- [Input data and row decisions](https://zhaoboding.github.io/PyMetaAnalysis/guides/input-data/)
|
|
184
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/)
|
|
185
206
|
- [Choosing methods](https://zhaoboding.github.io/PyMetaAnalysis/guides/method-selection/) and [statistical formulas](https://zhaoboding.github.io/PyMetaAnalysis/methods/statistical-methods/)
|
|
186
207
|
- [Sensitivity analysis](https://zhaoboding.github.io/PyMetaAnalysis/guides/sensitivity-analysis/) and [plotting](https://zhaoboding.github.io/PyMetaAnalysis/guides/plotting/)
|
|
187
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/)
|
|
@@ -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,6 +140,9 @@ 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
|
|
|
@@ -133,6 +153,7 @@ The complete documentation is published at
|
|
|
133
153
|
- [Getting started](https://zhaoboding.github.io/PyMetaAnalysis/getting-started/)
|
|
134
154
|
- [Input data and row decisions](https://zhaoboding.github.io/PyMetaAnalysis/guides/input-data/)
|
|
135
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/)
|
|
136
157
|
- [Choosing methods](https://zhaoboding.github.io/PyMetaAnalysis/guides/method-selection/) and [statistical formulas](https://zhaoboding.github.io/PyMetaAnalysis/methods/statistical-methods/)
|
|
137
158
|
- [Sensitivity analysis](https://zhaoboding.github.io/PyMetaAnalysis/guides/sensitivity-analysis/) and [plotting](https://zhaoboding.github.io/PyMetaAnalysis/guides/plotting/)
|
|
138
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/)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Core performance baseline
|
|
2
2
|
|
|
3
|
-
`benchmark_core.py` measures representative generic, binary, and
|
|
4
|
-
random-effects fits using deterministic synthetic
|
|
5
|
-
primary performance requirement; this benchmark
|
|
6
|
-
regressions, not to enforce a fragile wall-clock
|
|
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
|
|
106
|
-
parser.error("studies
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
@@ -17,13 +17,16 @@ deferred functionality explicit.
|
|
|
17
17
|
- independent subgroup fits and a formal subgroup-differences test;
|
|
18
18
|
- leave-one-out and cumulative repeated-fit workflows;
|
|
19
19
|
- forest and descriptive funnel plots;
|
|
20
|
-
- structured provenance and reports
|
|
20
|
+
- structured provenance and reports;
|
|
21
|
+
- study-level Meta-regression with numeric and explicitly encoded categorical
|
|
22
|
+
moderators, generalized DL/PM/REML, and normal/Hartung-Knapp inference.
|
|
21
23
|
|
|
22
24
|
## Not currently implemented
|
|
23
25
|
|
|
24
26
|
- Peto odds ratios;
|
|
25
27
|
- Mantel-Haenszel risk differences or random-effects MH pooling;
|
|
26
|
-
-
|
|
28
|
+
- formula parsing, automatic interactions/splines, stepwise moderator
|
|
29
|
+
selection, multiplicity correction, or arbitrary linear contrasts;
|
|
27
30
|
- multilevel, multivariate, network, dose-response, diagnostic-accuracy, or
|
|
28
31
|
individual-participant-data meta-analysis;
|
|
29
32
|
- robust variance estimation or dependent-effect clustering;
|
|
@@ -51,8 +54,14 @@ of heterogeneity and should not be interpreted as interchangeable decision
|
|
|
51
54
|
rules. A non-significant Q test is not evidence of homogeneity.
|
|
52
55
|
|
|
53
56
|
Subgroup analyses estimate tau-squared independently within each random-
|
|
54
|
-
effects subgroup.
|
|
55
|
-
|
|
57
|
+
effects subgroup. This differs from fitting a Meta-regression with categorical
|
|
58
|
+
moderators and a single residual tau-squared.
|
|
59
|
+
|
|
60
|
+
Meta-regression uses aggregate study-level moderators. Coefficients are
|
|
61
|
+
susceptible to ecological bias, confounding, measurement error, low power, and
|
|
62
|
+
post-hoc model selection. They do not establish individual-level associations
|
|
63
|
+
or causal effects. The package warns below ten studies but does not prohibit a
|
|
64
|
+
scientifically prespecified, full-rank model when `k > p`.
|
|
56
65
|
|
|
57
66
|
Funnel plots are descriptive. Their pseudo confidence limits exclude tau-
|
|
58
67
|
squared, and asymmetry does not establish publication bias.
|
|
@@ -161,6 +161,127 @@ proportion internally and is formatted as a percentage in human-readable
|
|
|
161
161
|
output. With one study, Q is zero and its p-value, I-squared, and H-squared are
|
|
162
162
|
unavailable.
|
|
163
163
|
|
|
164
|
+
## Meta-regression
|
|
165
|
+
|
|
166
|
+
For `k` study effects, let `X` be the full-rank `k`-by-`p` design matrix. It
|
|
167
|
+
contains an intercept by default plus numeric moderator columns and treatment-
|
|
168
|
+
coded categorical terms:
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
y = X beta + u + epsilon
|
|
172
|
+
epsilon ~ N(0, V), V = diag(v_i)
|
|
173
|
+
u ~ N(0, tau^2 I)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Given residual tau-squared:
|
|
177
|
+
|
|
178
|
+
```text
|
|
179
|
+
W = diag(1 / (v_i + tau^2))
|
|
180
|
+
B = (X' W X)^(-1)
|
|
181
|
+
beta_hat = B X' W y
|
|
182
|
+
P = W - W X B X' W
|
|
183
|
+
residual df = k - p
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The implementation uses stable linear solves rather than forming an explicit
|
|
187
|
+
matrix inverse. A rank-deficient design or `k <= p` is rejected; terms are not
|
|
188
|
+
silently removed and a pseudo-inverse is not used.
|
|
189
|
+
|
|
190
|
+
### Residual tau-squared
|
|
191
|
+
|
|
192
|
+
For generalized DL, define `W0` and `P0` at tau-squared zero:
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
QE0 = y' P0 y
|
|
196
|
+
C = trace(P0)
|
|
197
|
+
tau^2_DL = max(0, (QE0 - (k-p)) / C)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
PM solves:
|
|
201
|
+
|
|
202
|
+
```text
|
|
203
|
+
y' P(tau^2) y = k - p
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
REML solves the restricted score:
|
|
207
|
+
|
|
208
|
+
```text
|
|
209
|
+
y' P(tau^2)^2 y - trace(P(tau^2)) = 0
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
All estimators are constrained to non-negative tau-squared. PM and REML use a
|
|
213
|
+
bracketed scalar root; a non-positive equation at zero returns the boundary.
|
|
214
|
+
Failure raises `ConvergenceError` without estimator fallback.
|
|
215
|
+
|
|
216
|
+
### Coefficient and moderator inference
|
|
217
|
+
|
|
218
|
+
Normal inference uses covariance `B`, z coefficient tests, and a chi-squared
|
|
219
|
+
Wald test for one or more moderator terms.
|
|
220
|
+
|
|
221
|
+
For both Hartung-Knapp choices:
|
|
222
|
+
|
|
223
|
+
```text
|
|
224
|
+
q = y' P(tau^2) y / (k-p)
|
|
225
|
+
Cov_HK(beta_hat) = q B
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Coefficient tests use a t distribution with `k-p` degrees of freedom. Joint
|
|
229
|
+
tests divide their Wald statistic by the number of tested terms and use an F
|
|
230
|
+
distribution with that numerator df and `k-p` denominator df.
|
|
231
|
+
`hartung_knapp_adhoc` replaces `q` with `max(1, q)`; unmodified
|
|
232
|
+
`hartung_knapp` retains `q` and warns when it is below one.
|
|
233
|
+
|
|
234
|
+
The global moderator test covers every non-intercept term.
|
|
235
|
+
`test_moderator(name)` covers every encoded term for one original moderator,
|
|
236
|
+
so a multi-level category receives a joint test.
|
|
237
|
+
|
|
238
|
+
### Residual heterogeneity and pseudo-R-squared
|
|
239
|
+
|
|
240
|
+
Residual heterogeneity uses:
|
|
241
|
+
|
|
242
|
+
```text
|
|
243
|
+
QE = y' P0 y
|
|
244
|
+
QE ~ chi-square_(k-p) under the null
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Common models derive residual I-squared and H-squared from QE. Mixed models
|
|
248
|
+
use:
|
|
249
|
+
|
|
250
|
+
```text
|
|
251
|
+
v_typical = (k-p) / trace(P0)
|
|
252
|
+
I^2_residual = tau^2 / (tau^2 + v_typical)
|
|
253
|
+
H^2_residual = 1 + tau^2 / v_typical
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
For mixed models with an intercept, the same rows are refitted with an
|
|
257
|
+
intercept-only design and the same tau-squared estimator:
|
|
258
|
+
|
|
259
|
+
```text
|
|
260
|
+
R^2_raw = 1 - tau^2_model / tau^2_null
|
|
261
|
+
R^2 = max(0, R^2_raw)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
The raw value is retained. Pseudo-R-squared is unavailable when the null
|
|
265
|
+
tau-squared is zero and is not equivalent to ordinary regression R-squared.
|
|
266
|
+
|
|
267
|
+
### Meta-regression prediction
|
|
268
|
+
|
|
269
|
+
For a new design vector `x0`:
|
|
270
|
+
|
|
271
|
+
```text
|
|
272
|
+
fitted = x0' beta_hat
|
|
273
|
+
Var_mean = x0' Cov(beta_hat) x0
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Mean-effect intervals use the selected normal or `t_(k-p)` critical value. A
|
|
277
|
+
mixed model additionally reports a true-effect prediction interval:
|
|
278
|
+
|
|
279
|
+
```text
|
|
280
|
+
fitted +/- critical * sqrt(tau^2 + Var_mean)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
It does not include a sampling variance for a future observed effect.
|
|
284
|
+
|
|
164
285
|
## Binary study effects
|
|
165
286
|
|
|
166
287
|
For a treatment/control 2-by-2 table:
|