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.
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/CHANGELOG.md +42 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/CITATION.cff +2 -2
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/PKG-INFO +47 -3
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/README.md +45 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/benchmarks/README.md +5 -4
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/benchmarks/benchmark_core.py +14 -2
- pymetaanalysis-0.4.0/docs/adr/0003-meta-regression-prediction-intervals.md +70 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/getting-started.md +1 -1
- pymetaanalysis-0.4.0/docs/guides/meta-regression.md +366 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/plotting.md +32 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/provenance-reporting.md +1 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/r-interoperability.md +12 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/sensitivity-analysis.md +82 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/zero-events.md +6 -6
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/index.md +9 -6
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/limitations.md +22 -6
- pymetaanalysis-0.4.0/docs/methods/statistical-methods.md +540 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/reference/api.md +73 -3
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/reference/report-schema.md +35 -4
- pymetaanalysis-0.4.0/docs/reference/results.md +433 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/releasing.md +18 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/validation.md +36 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/examples/README.md +4 -1
- pymetaanalysis-0.4.0/examples/meta_regression.ipynb +290 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/mkdocs.yml +2 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/pyproject.toml +1 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/__init__.py +31 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/_version.py +1 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/config.py +18 -0
- pymetaanalysis-0.4.0/src/meta_analyze/design_matrix.py +477 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/estimators/__init__.py +12 -0
- pymetaanalysis-0.4.0/src/meta_analyze/estimators/meta_regression.py +462 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/__init__.py +2 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/_utils.py +2 -2
- pymetaanalysis-0.4.0/src/meta_analyze/plotting/regression.py +128 -0
- pymetaanalysis-0.4.0/src/meta_analyze/regression_api.py +430 -0
- pymetaanalysis-0.4.0/src/meta_analyze/regression_collinearity.py +304 -0
- pymetaanalysis-0.4.0/src/meta_analyze/regression_contrasts.py +365 -0
- pymetaanalysis-0.4.0/src/meta_analyze/regression_results.py +454 -0
- pymetaanalysis-0.4.0/src/meta_analyze/regression_sensitivity.py +587 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/reporting.py +169 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/README.md +23 -0
- pymetaanalysis-0.4.0/tests/reference/generate_meta_regression_collinearity_metafor.R +81 -0
- pymetaanalysis-0.4.0/tests/reference/generate_meta_regression_contrasts_metafor.R +96 -0
- pymetaanalysis-0.4.0/tests/reference/generate_meta_regression_influence_metafor.R +81 -0
- pymetaanalysis-0.4.0/tests/reference/generate_meta_regression_metafor.R +371 -0
- pymetaanalysis-0.4.0/tests/reference/meta_regression_boundary_input.csv +9 -0
- pymetaanalysis-0.4.0/tests/reference/meta_regression_collinearity_metafor.json +65 -0
- pymetaanalysis-0.4.0/tests/reference/meta_regression_contrasts_metafor.json +157 -0
- pymetaanalysis-0.4.0/tests/reference/meta_regression_influence_metafor.json +804 -0
- pymetaanalysis-0.4.0/tests/reference/meta_regression_input.csv +16 -0
- pymetaanalysis-0.4.0/tests/reference/meta_regression_metafor.json +1789 -0
- pymetaanalysis-0.4.0/tests/test_meta_regression.py +714 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_numerical_stability.py +85 -0
- pymetaanalysis-0.4.0/tests/test_properties.py +296 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_r_references.py +419 -1
- pymetaanalysis-0.4.0/tests/test_regression_collinearity.py +288 -0
- pymetaanalysis-0.4.0/tests/test_regression_contrasts.py +346 -0
- pymetaanalysis-0.4.0/tests/test_regression_influence.py +319 -0
- pymetaanalysis-0.4.0/tests/test_regression_plotting.py +200 -0
- pymetaanalysis-0.4.0/tests/test_regression_sensitivity.py +273 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_release_readiness.py +23 -17
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_reporting.py +1 -1
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tools/inspect_distribution.py +1 -0
- pymetaanalysis-0.2.1/docs/methods/statistical-methods.md +0 -286
- pymetaanalysis-0.2.1/docs/reference/results.md +0 -224
- pymetaanalysis-0.2.1/tests/test_properties.py +0 -116
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/.github/workflows/ci.yml +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/.github/workflows/pages.yml +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/.github/workflows/release.yml +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/.gitignore +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/CONTRIBUTING.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/LICENSE +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/SECURITY.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/adr/0001-optional-matplotlib.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/adr/0002-statistical-policy.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/citation.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/development.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/binary-outcomes.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/continuous-outcomes.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/generic-effects.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/input-data.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/guides/method-selection.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/installation.md +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/docs/stylesheets/extra.css +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/examples/quickstart.ipynb +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/api.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/binary_api.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/continuous_api.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/data.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/effect_sizes/__init__.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/effect_sizes/binary.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/effect_sizes/continuous.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/estimators/inverse_variance.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/estimators/mantel_haenszel.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/estimators/tau2.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/exceptions.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/heterogeneity.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/forest.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/funnel.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/subgroup_forest.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/provenance.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/py.typed +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/results.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/sensitivity.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/src/meta_analyze/subgroups.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/binary_input.csv +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/binary_metafor.json +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/binary_sparse_input.csv +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/continuous_input.csv +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/continuous_metafor.json +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/generate_binary_metafor.R +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/generate_continuous_metafor.R +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/generate_generic_metafor.R +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/generate_workflow_metafor.R +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/generic_input.csv +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/generic_metafor.json +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/workflow_input.csv +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/reference/workflow_metafor.json +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_api.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_binary.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_continuous.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_documentation.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_estimators.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_funnel_plot.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_plotting.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_reference_results.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_sensitivity.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tests/test_subgroups.py +0 -0
- {pymetaanalysis-0.2.1 → pymetaanalysis-0.4.0}/tools/check_release.py +0 -0
- {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.
|
|
12
|
-
date-released: 2026-07-
|
|
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.
|
|
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
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
|
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,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.
|