PyMetaAnalysis 0.7.0__tar.gz → 0.8.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.7.0 → pymetaanalysis-0.8.0}/CHANGELOG.md +20 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/CITATION.cff +2 -2
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/PKG-INFO +12 -3
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/README.md +11 -2
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/getting-started.md +25 -1
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/binary-outcomes.md +21 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/plotting.md +46 -3
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/r-interoperability.md +66 -0
- pymetaanalysis-0.8.0/docs/guides/small-study-effects.md +257 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/index.md +10 -9
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/limitations.md +22 -5
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/methods/statistical-methods.md +109 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/reference/api.md +43 -3
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/reference/results.md +72 -1
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/validation.md +28 -6
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/mkdocs.yml +1 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/__init__.py +4 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/_version.py +1 -1
- pymetaanalysis-0.8.0/src/meta_analyze/plotting/funnel.py +444 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/results.py +49 -2
- pymetaanalysis-0.8.0/src/meta_analyze/small_study_effects.py +961 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/README.md +22 -9
- pymetaanalysis-0.8.0/tests/reference/generate_harbord_small_study_effects_meta.R +73 -0
- pymetaanalysis-0.8.0/tests/reference/generate_peters_small_study_effects_meta.R +71 -0
- pymetaanalysis-0.8.0/tests/reference/generate_small_study_effects_metafor.R +56 -0
- pymetaanalysis-0.8.0/tests/reference/harbord_small_study_effects_meta.json +24 -0
- pymetaanalysis-0.8.0/tests/reference/peters_small_study_effects_input.csv +13 -0
- pymetaanalysis-0.8.0/tests/reference/peters_small_study_effects_meta.json +25 -0
- pymetaanalysis-0.8.0/tests/reference/small_study_effects_input.csv +13 -0
- pymetaanalysis-0.8.0/tests/reference/small_study_effects_metafor.json +20 -0
- pymetaanalysis-0.8.0/tests/test_funnel_plot.py +499 -0
- pymetaanalysis-0.8.0/tests/test_harbord_small_study_effects.py +330 -0
- pymetaanalysis-0.8.0/tests/test_peters_small_study_effects.py +301 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_r_references.py +24 -1
- pymetaanalysis-0.8.0/tests/test_small_study_effects.py +264 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tools/check_release.py +30 -0
- pymetaanalysis-0.7.0/src/meta_analyze/plotting/funnel.py +0 -168
- pymetaanalysis-0.7.0/tests/test_funnel_plot.py +0 -218
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/.github/workflows/ci.yml +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/.github/workflows/pages.yml +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/.github/workflows/release.yml +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/.gitignore +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/CONTRIBUTING.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/LICENSE +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/SECURITY.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/benchmarks/README.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/benchmarks/benchmark_core.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/adr/0001-optional-matplotlib.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/adr/0002-statistical-policy.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/adr/0003-meta-regression-prediction-intervals.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/adr/0004-hartung-knapp-prediction-intervals.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/adr/0005-mantel-haenszel-risk-difference.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/adr/0006-peto-odds-ratio.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/adr/0007-fisher-z-correlation.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/citation.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/development.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/continuous-outcomes.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/correlation-outcomes.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/generic-effects.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/input-data.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/meta-regression.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/method-selection.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/provenance-reporting.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/sensitivity-analysis.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/zero-events.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/installation.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/reference/report-schema.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/releasing.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/stylesheets/extra.css +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/examples/README.md +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/examples/meta_regression.ipynb +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/examples/quickstart.ipynb +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/pyproject.toml +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/api.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/binary_api.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/config.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/continuous_api.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/correlation_api.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/data.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/design_matrix.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/effect_sizes/__init__.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/effect_sizes/binary.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/effect_sizes/continuous.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/effect_sizes/correlation.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/estimators/__init__.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/estimators/inverse_variance.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/estimators/mantel_haenszel.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/estimators/meta_regression.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/estimators/peto.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/estimators/tau2.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/exceptions.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/heterogeneity.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/plotting/__init__.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/plotting/_utils.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/plotting/forest.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/plotting/regression.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/plotting/subgroup_forest.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/provenance.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/py.typed +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/regression_api.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/regression_collinearity.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/regression_contrasts.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/regression_results.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/regression_sensitivity.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/reporting.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/sensitivity.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/subgroups.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/binary_input.csv +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/binary_metafor.json +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/binary_sparse_input.csv +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/continuous_input.csv +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/continuous_metafor.json +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/correlation_input.csv +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/correlation_metafor.json +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_binary_metafor.R +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_continuous_metafor.R +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_correlation_metafor.R +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_generic_metafor.R +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_meta_regression_collinearity_metafor.R +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_meta_regression_contrasts_metafor.R +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_meta_regression_influence_metafor.R +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_meta_regression_metafor.R +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_workflow_metafor.R +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generic_input.csv +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generic_metafor.json +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/meta_regression_boundary_input.csv +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/meta_regression_collinearity_metafor.json +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/meta_regression_contrasts_metafor.json +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/meta_regression_influence_metafor.json +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/meta_regression_input.csv +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/meta_regression_metafor.json +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/workflow_input.csv +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/workflow_metafor.json +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_api.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_binary.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_continuous.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_correlation.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_documentation.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_estimators.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_meta_regression.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_numerical_stability.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_plotting.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_properties.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_reference_results.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_regression_collinearity.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_regression_contrasts.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_regression_influence.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_regression_plotting.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_regression_sensitivity.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_release_readiness.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_reporting.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_sensitivity.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_subgroups.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tools/execute_notebooks.py +0 -0
- {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tools/inspect_distribution.py +0 -0
|
@@ -6,6 +6,26 @@ Changes planned for the next release accumulate under `Unreleased`.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 0.8.0 - 2026-09-04
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `MetaAnalysisResult.funnel()` supports optional contour-enhanced two-sided
|
|
14
|
+
significance regions with validated levels, display-scale null references,
|
|
15
|
+
configurable colors and legend, while preserving pooled pseudo-limit lines.
|
|
16
|
+
- `MetaAnalysisResult.egger_test()` implements the classical standard-error
|
|
17
|
+
Egger regression test for funnel-plot asymmetry with multiplicative
|
|
18
|
+
dispersion, t inference, an explicitly labeled limit estimate, applicability
|
|
19
|
+
warnings, and an independent R `metafor` reference fixture.
|
|
20
|
+
- `MetaAnalysisResult.harbord_test()` implements the Harbord efficient-score
|
|
21
|
+
test for two-group binary odds ratios, without continuity correction, with
|
|
22
|
+
multiplicative dispersion, t inference, and an independent R `meta`
|
|
23
|
+
reference fixture.
|
|
24
|
+
- `MetaAnalysisResult.peters_test()` implements the Peters regression test for
|
|
25
|
+
two-group binary odds ratios, reusing the recorded study-level continuity
|
|
26
|
+
correction and validating the slope, limit estimate, and multiplicative
|
|
27
|
+
dispersion against an independent R `meta` reference fixture.
|
|
28
|
+
|
|
9
29
|
## 0.7.0 - 2026-09-02
|
|
10
30
|
|
|
11
31
|
### 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.
|
|
12
|
-
date-released: 2026-09-
|
|
11
|
+
version: 0.8.0
|
|
12
|
+
date-released: 2026-09-04
|
|
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.8.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
|
|
@@ -195,6 +195,9 @@ Rows excluded by missing-value or sparse-data policies remain in
|
|
|
195
195
|
```python
|
|
196
196
|
leave_one_out = result.leave_one_out().to_dataframe()
|
|
197
197
|
cumulative = result.cumulative(order="publication_year").to_dataframe()
|
|
198
|
+
egger = result.egger_test()
|
|
199
|
+
harbord = binary_or_result.harbord_test()
|
|
200
|
+
peters = binary_or_result.peters_test()
|
|
198
201
|
regression_deleted = regression.leave_one_out()
|
|
199
202
|
regression_coefficient_changes = regression_deleted.coefficients
|
|
200
203
|
regression_influence = regression.influence()
|
|
@@ -210,10 +213,16 @@ south_vs_east = regression.contrast(
|
|
|
210
213
|
|
|
211
214
|
ax = result.forest(show_prediction_interval=True)
|
|
212
215
|
ax = result.funnel()
|
|
216
|
+
contour_ax = result.funnel(contour_levels=(0.90, 0.95, 0.99))
|
|
213
217
|
```
|
|
214
218
|
|
|
215
219
|
Plotting methods return Matplotlib axes and never call `show()`. Funnel plots
|
|
216
|
-
are descriptive small-study-effect diagnostics
|
|
220
|
+
are descriptive small-study-effect diagnostics; optional significance contours
|
|
221
|
+
show where two-sided p-value bands fall relative to the null. Classical Egger
|
|
222
|
+
regression is available for general effects, while Harbord and Peters provide
|
|
223
|
+
outcome-specific alternatives for two-group binary odds ratios. All include
|
|
224
|
+
explicit applicability cautions. Neither a funnel plot nor any test proves
|
|
225
|
+
publication bias.
|
|
217
226
|
Meta-regression leave-one-out results also expose a long-form coefficient
|
|
218
227
|
change table. Exact influence diagnostics add externally standardized
|
|
219
228
|
residuals, Cook's distance, DFBETAS, and explicit heuristic screening
|
|
@@ -240,7 +249,7 @@ The complete documentation is published at
|
|
|
240
249
|
- [Generic](https://zhaoboding.github.io/PyMetaAnalysis/guides/generic-effects/), [binary](https://zhaoboding.github.io/PyMetaAnalysis/guides/binary-outcomes/), [continuous](https://zhaoboding.github.io/PyMetaAnalysis/guides/continuous-outcomes/), and [correlation](https://zhaoboding.github.io/PyMetaAnalysis/guides/correlation-outcomes/) guides
|
|
241
250
|
- [Meta-regression](https://zhaoboding.github.io/PyMetaAnalysis/guides/meta-regression/)
|
|
242
251
|
- [Choosing methods](https://zhaoboding.github.io/PyMetaAnalysis/guides/method-selection/) and [statistical formulas](https://zhaoboding.github.io/PyMetaAnalysis/methods/statistical-methods/)
|
|
243
|
-
- [Sensitivity analysis](https://zhaoboding.github.io/PyMetaAnalysis/guides/sensitivity-analysis/) and [plotting](https://zhaoboding.github.io/PyMetaAnalysis/guides/plotting/)
|
|
252
|
+
- [Sensitivity analysis](https://zhaoboding.github.io/PyMetaAnalysis/guides/sensitivity-analysis/), [small-study effects](https://zhaoboding.github.io/PyMetaAnalysis/guides/small-study-effects/), and [plotting](https://zhaoboding.github.io/PyMetaAnalysis/guides/plotting/)
|
|
244
253
|
- [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/)
|
|
245
254
|
- [Validation strategy](https://zhaoboding.github.io/PyMetaAnalysis/validation/) and [scope/limitations](https://zhaoboding.github.io/PyMetaAnalysis/limitations/)
|
|
246
255
|
- [Citation guidance](https://zhaoboding.github.io/PyMetaAnalysis/citation/)
|
|
@@ -145,6 +145,9 @@ Rows excluded by missing-value or sparse-data policies remain in
|
|
|
145
145
|
```python
|
|
146
146
|
leave_one_out = result.leave_one_out().to_dataframe()
|
|
147
147
|
cumulative = result.cumulative(order="publication_year").to_dataframe()
|
|
148
|
+
egger = result.egger_test()
|
|
149
|
+
harbord = binary_or_result.harbord_test()
|
|
150
|
+
peters = binary_or_result.peters_test()
|
|
148
151
|
regression_deleted = regression.leave_one_out()
|
|
149
152
|
regression_coefficient_changes = regression_deleted.coefficients
|
|
150
153
|
regression_influence = regression.influence()
|
|
@@ -160,10 +163,16 @@ south_vs_east = regression.contrast(
|
|
|
160
163
|
|
|
161
164
|
ax = result.forest(show_prediction_interval=True)
|
|
162
165
|
ax = result.funnel()
|
|
166
|
+
contour_ax = result.funnel(contour_levels=(0.90, 0.95, 0.99))
|
|
163
167
|
```
|
|
164
168
|
|
|
165
169
|
Plotting methods return Matplotlib axes and never call `show()`. Funnel plots
|
|
166
|
-
are descriptive small-study-effect diagnostics
|
|
170
|
+
are descriptive small-study-effect diagnostics; optional significance contours
|
|
171
|
+
show where two-sided p-value bands fall relative to the null. Classical Egger
|
|
172
|
+
regression is available for general effects, while Harbord and Peters provide
|
|
173
|
+
outcome-specific alternatives for two-group binary odds ratios. All include
|
|
174
|
+
explicit applicability cautions. Neither a funnel plot nor any test proves
|
|
175
|
+
publication bias.
|
|
167
176
|
Meta-regression leave-one-out results also expose a long-form coefficient
|
|
168
177
|
change table. Exact influence diagnostics add externally standardized
|
|
169
178
|
residuals, Cook's distance, DFBETAS, and explicit heuristic screening
|
|
@@ -190,7 +199,7 @@ The complete documentation is published at
|
|
|
190
199
|
- [Generic](https://zhaoboding.github.io/PyMetaAnalysis/guides/generic-effects/), [binary](https://zhaoboding.github.io/PyMetaAnalysis/guides/binary-outcomes/), [continuous](https://zhaoboding.github.io/PyMetaAnalysis/guides/continuous-outcomes/), and [correlation](https://zhaoboding.github.io/PyMetaAnalysis/guides/correlation-outcomes/) guides
|
|
191
200
|
- [Meta-regression](https://zhaoboding.github.io/PyMetaAnalysis/guides/meta-regression/)
|
|
192
201
|
- [Choosing methods](https://zhaoboding.github.io/PyMetaAnalysis/guides/method-selection/) and [statistical formulas](https://zhaoboding.github.io/PyMetaAnalysis/methods/statistical-methods/)
|
|
193
|
-
- [Sensitivity analysis](https://zhaoboding.github.io/PyMetaAnalysis/guides/sensitivity-analysis/) and [plotting](https://zhaoboding.github.io/PyMetaAnalysis/guides/plotting/)
|
|
202
|
+
- [Sensitivity analysis](https://zhaoboding.github.io/PyMetaAnalysis/guides/sensitivity-analysis/), [small-study effects](https://zhaoboding.github.io/PyMetaAnalysis/guides/small-study-effects/), and [plotting](https://zhaoboding.github.io/PyMetaAnalysis/guides/plotting/)
|
|
194
203
|
- [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/)
|
|
195
204
|
- [Validation strategy](https://zhaoboding.github.io/PyMetaAnalysis/validation/) and [scope/limitations](https://zhaoboding.github.io/PyMetaAnalysis/limitations/)
|
|
196
205
|
- [Citation guidance](https://zhaoboding.github.io/PyMetaAnalysis/citation/)
|
|
@@ -160,10 +160,34 @@ After installing the `plot` extra:
|
|
|
160
160
|
```python
|
|
161
161
|
ax = result.forest(show_prediction_interval=True)
|
|
162
162
|
ax = result.funnel()
|
|
163
|
+
contour_ax = result.funnel(contour_levels=(0.90, 0.95, 0.99))
|
|
163
164
|
```
|
|
164
165
|
|
|
165
166
|
Both methods return a Matplotlib `Axes` and never call `show()`. This keeps them
|
|
166
|
-
usable in notebooks, scripts, tests, and composed figures.
|
|
167
|
+
usable in notebooks, scripts, tests, and composed figures. Funnel contours show
|
|
168
|
+
two-sided significance regions around the null effect; they do not estimate
|
|
169
|
+
missing studies.
|
|
170
|
+
|
|
171
|
+
The classical Egger regression test is available separately from plotting:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
egger = result.egger_test()
|
|
175
|
+
print(egger)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
It tests for funnel-plot asymmetry or small-study effects, not publication bias
|
|
179
|
+
itself. Read [small-study effects](guides/small-study-effects.md) before using or
|
|
180
|
+
interpreting it.
|
|
181
|
+
|
|
182
|
+
For a two-group binary odds-ratio result, the outcome-specific Harbord and
|
|
183
|
+
Peters tests are also available:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
harbord = binary_or_result.harbord_test()
|
|
187
|
+
peters = binary_or_result.peters_test()
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
They require the retained event and group-size counts from `meta_binary()`.
|
|
167
191
|
|
|
168
192
|
See [plotting](guides/plotting.md) for every parameter, display-scale rules,
|
|
169
193
|
axes composition, and interpretation cautions.
|
|
@@ -120,6 +120,27 @@ calculations. See [zero-event studies](zero-events.md) for details.
|
|
|
120
120
|
Use `method="IV"` for random-effects RD or when inverse-variance common-effect
|
|
121
121
|
pooling is the prespecified estimator.
|
|
122
122
|
|
|
123
|
+
## Harbord and Peters small-study-effect tests for odds ratios
|
|
124
|
+
|
|
125
|
+
An OR result retains the original two-group counts needed for both
|
|
126
|
+
outcome-specific regressions:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
harbord = odds_ratio_result.harbord_test()
|
|
130
|
+
peters = odds_ratio_result.peters_test()
|
|
131
|
+
print(harbord)
|
|
132
|
+
print(peters)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Call these only when the result was fitted with `measure="OR"`. Harbord uses
|
|
136
|
+
null efficient scores and no continuity correction; Peters reconstructs study
|
|
137
|
+
log odds ratios with the recorded study-level correction. Both are independent
|
|
138
|
+
of the source MH, IV, random-effects IV, or Peto pooling choice. They diagnose
|
|
139
|
+
different forms of small-study association and do not prove publication bias
|
|
140
|
+
or replace the pooled estimate. See
|
|
141
|
+
[small-study effects](small-study-effects.md) for the complete method and
|
|
142
|
+
interpretation contract.
|
|
143
|
+
|
|
123
144
|
## Input validation
|
|
124
145
|
|
|
125
146
|
Event counts and sample sizes must be finite, integer-valued, and non-negative;
|
|
@@ -83,6 +83,22 @@ appear toward the top. The vertical reference is the fitted pooled estimate.
|
|
|
83
83
|
Pseudo confidence limits are centered on that estimate and do not include
|
|
84
84
|
tau-squared.
|
|
85
85
|
|
|
86
|
+
Add two-sided significance contours around the null effect with:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
ax = result.funnel(
|
|
90
|
+
contour_levels=(0.90, 0.95, 0.99),
|
|
91
|
+
)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
These confidence levels define the visible bands `0.05 < p <= 0.10`,
|
|
95
|
+
`0.01 < p <= 0.05`, and `p <= 0.01`; the central `p > 0.10` region remains
|
|
96
|
+
unshaded. The solid vertical line remains the fitted pooled estimate, while a
|
|
97
|
+
dotted line marks the null used for the contours. When contours are enabled,
|
|
98
|
+
their background replaces the ordinary blue pseudo-limit fill so the colors
|
|
99
|
+
do not mix. The pseudo-limit boundary lines are still drawn unless
|
|
100
|
+
`show_pseudo_confidence_interval=False`.
|
|
101
|
+
|
|
86
102
|
### Funnel parameters
|
|
87
103
|
|
|
88
104
|
| Parameter | Meaning |
|
|
@@ -90,14 +106,41 @@ tau-squared.
|
|
|
90
106
|
| `ax` | Existing axes; a new one is created when omitted |
|
|
91
107
|
| `effect_label` | X-axis label |
|
|
92
108
|
| `confidence_level` | Pseudo-limit level; defaults to the fitted level |
|
|
93
|
-
| `show_pseudo_confidence_interval` | Draw
|
|
109
|
+
| `show_pseudo_confidence_interval` | Draw pooled pseudo-limit boundaries and, without contours, their shaded region |
|
|
110
|
+
| `contour_levels` | Strictly increasing confidence levels in `(0,1)`; `None` disables contours |
|
|
111
|
+
| `contour_colors` | One valid Matplotlib color per contour level; defaults to light-to-dark gray |
|
|
112
|
+
| `contour_reference` | Contour null on the display scale; defaults to 1 for ratios and 0 otherwise |
|
|
113
|
+
| `show_contour_legend` | Show the corresponding two-sided p-value bands |
|
|
94
114
|
| `warn_on_few_studies` | Warn when fewer than 10 studies are plotted |
|
|
95
115
|
| `log_scale` | Override the default logarithmic ratio axis |
|
|
96
116
|
|
|
117
|
+
For example, customize the bands and null reference with:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
ax = result.funnel(
|
|
121
|
+
contour_levels=(0.90, 0.95),
|
|
122
|
+
contour_colors=("#fee2e2", "#ef4444"),
|
|
123
|
+
contour_reference=0.0,
|
|
124
|
+
show_contour_legend=True,
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`contour_reference` uses the displayed effect scale. It must therefore be
|
|
129
|
+
positive for OR/RR results, whose default is `1`, and strictly between `-1`
|
|
130
|
+
and `1` for displayed correlations. Boundaries are calculated on the model
|
|
131
|
+
scale before the normal display transformation. Contours are always based on
|
|
132
|
+
sampling standard errors and do not incorporate tau-squared.
|
|
133
|
+
|
|
97
134
|
Funnel asymmetry can reflect small-study effects, heterogeneity, outcome
|
|
98
135
|
selection, design differences, chance, or publication processes. It is not by
|
|
99
|
-
itself evidence of publication bias.
|
|
100
|
-
|
|
136
|
+
itself evidence of publication bias. Contours help assess whether apparent
|
|
137
|
+
missing areas are predominantly statistically non-significant, but they do not
|
|
138
|
+
show that studies are actually missing or determine why asymmetry exists. Use
|
|
139
|
+
the separately documented classical
|
|
140
|
+
[`result.egger_test()`](small-study-effects.md), or `result.harbord_test()` /
|
|
141
|
+
`result.peters_test()` for an eligible binary OR analysis, when a formal
|
|
142
|
+
regression diagnostic is appropriate. None changes the plot or proves a
|
|
143
|
+
publication mechanism.
|
|
101
144
|
|
|
102
145
|
## Meta-regression bubble plots
|
|
103
146
|
|
|
@@ -25,6 +25,10 @@ fixtures used by this project.
|
|
|
25
25
|
| Meta-regression collinearity | `regression.collinearity()` | `vif()` plus weighted design diagnostics | — |
|
|
26
26
|
| Meta-regression linear contrasts | `regression.contrast(...)` | `anova(..., X=..., rhs=...)` | — |
|
|
27
27
|
| Cumulative analysis | `result.cumulative()` | `cumul()` | `metacum()` |
|
|
28
|
+
| Contour-enhanced funnel plot | `result.funnel(contour_levels=(...))` | `funnel(level=c(...), refline=...)` | `funnel(contour.levels=c(...), ref=...)` |
|
|
29
|
+
| Classical Egger test | `result.egger_test()` | `regtest(..., model="lm", predictor="sei")` | `metabias(..., method.bias="Egger")` |
|
|
30
|
+
| Harbord binary-OR test | `result.harbord_test()` | manual documented score regression | `metabias(..., method.bias="Harbord")` |
|
|
31
|
+
| Peters binary-OR test | `result.peters_test()` | manual documented WLS | `metabias(..., method.bias="Peters")` |
|
|
28
32
|
|
|
29
33
|
PyMetaAnalysis intentionally has no `metabin`, `metacont`, or `rma` aliases.
|
|
30
34
|
One documented Python entry point per input shape keeps result types and
|
|
@@ -212,6 +216,66 @@ metagen(
|
|
|
212
216
|
Check the R package's explicit ad hoc HK option before treating the last call
|
|
213
217
|
as numerically equivalent.
|
|
214
218
|
|
|
219
|
+
## Classical Egger regression
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
egger = result.egger_test()
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
corresponds to the classical `metafor` configuration:
|
|
226
|
+
|
|
227
|
+
```r
|
|
228
|
+
regtest(fit, model = "lm", predictor = "sei")
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
PyMetaAnalysis does not currently expose `metafor`'s default
|
|
232
|
+
`model="rma"` version. The returned `intercept` is the tested asymmetry
|
|
233
|
+
coefficient, while `limit_estimate` is the extrapolated effect as the standard
|
|
234
|
+
error tends to zero. Review the dedicated
|
|
235
|
+
[small-study-effects guide](small-study-effects.md) before treating similarly
|
|
236
|
+
named R functions as numerically interchangeable.
|
|
237
|
+
|
|
238
|
+
## Harbord regression for binary odds ratios
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
harbord = binary_or_result.harbord_test()
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
corresponds to:
|
|
245
|
+
|
|
246
|
+
```r
|
|
247
|
+
metabias(binary_or_fit, method.bias = "Harbord")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
PyMetaAnalysis calculates the null efficient score `Z` and score variance `V`
|
|
251
|
+
directly from retained treatment/control counts. It fits the standardized form
|
|
252
|
+
`Z/sqrt(V)` on `sqrt(V)`, which is algebraically equivalent to the
|
|
253
|
+
`V`-weighted `Z/V` on `1/sqrt(V)` regression used by R `meta`. The intercept is
|
|
254
|
+
the tested asymmetry coefficient, with multiplicative dispersion and
|
|
255
|
+
`t_(k-2)` inference. Study-level continuity corrections and the source pooling
|
|
256
|
+
method do not enter the score calculation.
|
|
257
|
+
|
|
258
|
+
## Peters regression for binary odds ratios
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
peters = binary_or_result.peters_test()
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
corresponds to:
|
|
265
|
+
|
|
266
|
+
```r
|
|
267
|
+
metabias(binary_or_fit, method.bias = "Peters")
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
PyMetaAnalysis requires an OR result created by `meta_binary()` because Peters
|
|
271
|
+
regression needs the original treatment/control counts. It reconstructs
|
|
272
|
+
conventional continuity-corrected study log odds ratios, uses inverse total
|
|
273
|
+
sample size as predictor and `S*F/N` weights, and applies multiplicative
|
|
274
|
+
dispersion with `t_(k-2)` inference. `slope` is the tested coefficient;
|
|
275
|
+
`limit_estimate` is the extrapolated log OR at infinite total sample size.
|
|
276
|
+
The source common/random and MH/IV/Peto pooling choice does not enter this
|
|
277
|
+
separate regression.
|
|
278
|
+
|
|
215
279
|
## Primary R references
|
|
216
280
|
|
|
217
281
|
- [`metafor::rma.uni`](https://wviechtb.github.io/metafor/reference/rma.uni.html)
|
|
@@ -219,7 +283,9 @@ as numerically equivalent.
|
|
|
219
283
|
- [`metafor::rma.mh`](https://wviechtb.github.io/metafor/reference/rma.mh.html)
|
|
220
284
|
- [`metafor::rma.peto`](https://wviechtb.github.io/metafor/reference/rma.peto.html)
|
|
221
285
|
- [`metafor::escalc`](https://wviechtb.github.io/metafor/reference/escalc.html)
|
|
286
|
+
- [`metafor::regtest`](https://wviechtb.github.io/metafor/reference/regtest.html)
|
|
222
287
|
- [`meta::metagen`](https://search.r-project.org/CRAN/refmans/meta/html/metagen.html)
|
|
223
288
|
- [`meta::metabin`](https://search.r-project.org/CRAN/refmans/meta/html/metabin.html)
|
|
224
289
|
- [`meta::metacont`](https://search.r-project.org/CRAN/refmans/meta/html/metacont.html)
|
|
225
290
|
- [`meta::metacor`](https://search.r-project.org/CRAN/refmans/meta/html/metacor.html)
|
|
291
|
+
- [`meta::metabias`](https://search.r-project.org/CRAN/refmans/meta/html/metabias.html)
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Small-study effects and regression tests
|
|
2
|
+
|
|
3
|
+
PyMetaAnalysis provides classical Egger regression plus Harbord and Peters
|
|
4
|
+
tests for two-group binary odds ratios as companions to the descriptive funnel
|
|
5
|
+
plot. They diagnose funnel-plot asymmetry or small-study effects; none is a
|
|
6
|
+
direct test for publication bias.
|
|
7
|
+
|
|
8
|
+
## Run the classical Egger test
|
|
9
|
+
|
|
10
|
+
Start from any fitted `MetaAnalysisResult`:
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
import meta_analyze as ma
|
|
14
|
+
|
|
15
|
+
result = ma.meta_analysis(
|
|
16
|
+
studies,
|
|
17
|
+
effect="effect",
|
|
18
|
+
standard_error="standard_error",
|
|
19
|
+
model="random",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
egger = result.egger_test()
|
|
23
|
+
print(egger)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Only rows with `included=True` enter the regression. The original pooling
|
|
27
|
+
model, tau-squared estimate, confidence-interval method, weights, and result
|
|
28
|
+
object are not changed. The Egger calculation is the same whether the source
|
|
29
|
+
result was fitted as common or random effects because this first API implements
|
|
30
|
+
only the classical regression form.
|
|
31
|
+
|
|
32
|
+
Pair the numerical result with the plot:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
ax = result.funnel(contour_levels=(0.90, 0.95, 0.99))
|
|
36
|
+
print(egger.statistic, egger.df, egger.pvalue)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The optional contours mark two-sided significance regions around the null
|
|
40
|
+
effect. They can make it easier to see whether an apparently missing part of
|
|
41
|
+
the funnel lies mainly in a non-significant region, but they do not establish
|
|
42
|
+
that studies are missing.
|
|
43
|
+
|
|
44
|
+
## Harbord test for binary odds ratios
|
|
45
|
+
|
|
46
|
+
For an analysis fitted from retained two-group counts with
|
|
47
|
+
`meta_binary(..., measure="OR")`, Harbord regression uses the efficient score
|
|
48
|
+
and its variance:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
odds_ratios = ma.meta_binary(
|
|
52
|
+
trials,
|
|
53
|
+
event_treat="event_treat",
|
|
54
|
+
n_treat="n_treat",
|
|
55
|
+
event_control="event_control",
|
|
56
|
+
n_control="n_control",
|
|
57
|
+
measure="OR",
|
|
58
|
+
method="MH",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
harbord = odds_ratios.harbord_test()
|
|
62
|
+
print(harbord.statistic, harbord.df, harbord.pvalue)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`harbord.intercept` is the tested asymmetry coefficient in the standardized
|
|
66
|
+
score regression. `harbord.limit_estimate` is its efficient-score limit
|
|
67
|
+
coefficient; `display_limit_estimate` and `display_limit_ci` exponentiate that
|
|
68
|
+
coefficient. Treat these as regression outputs, not replacements for the
|
|
69
|
+
pooled estimate.
|
|
70
|
+
|
|
71
|
+
Harbord derives null efficient scores directly from raw treatment/control
|
|
72
|
+
counts. It does not use continuity-corrected study log odds ratios, so changing
|
|
73
|
+
the source study-level correction does not change the diagnostic. It is also
|
|
74
|
+
independent of whether the source analysis used MH, inverse variance,
|
|
75
|
+
random-effects inverse variance, or Peto pooling. Single-arm zero-event studies
|
|
76
|
+
remain usable when their total event and non-event margins are positive.
|
|
77
|
+
|
|
78
|
+
The result records the standardized-score response, square-root score-variance
|
|
79
|
+
predictor, equivalent weighting convention, residual dispersion, scaled-design
|
|
80
|
+
condition number, and the fact that no continuity correction is used.
|
|
81
|
+
|
|
82
|
+
## Peters test for binary odds ratios
|
|
83
|
+
|
|
84
|
+
When the analysis was fitted from retained two-group counts with
|
|
85
|
+
`meta_binary(..., measure="OR")`, use the outcome-specific Peters regression:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
odds_ratios = ma.meta_binary(
|
|
89
|
+
trials,
|
|
90
|
+
event_treat="event_treat",
|
|
91
|
+
n_treat="n_treat",
|
|
92
|
+
event_control="event_control",
|
|
93
|
+
n_control="n_control",
|
|
94
|
+
measure="OR",
|
|
95
|
+
method="MH",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
peters = odds_ratios.peters_test()
|
|
99
|
+
print(peters.statistic, peters.df, peters.pvalue)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The test reconstructs conventional study log odds ratios from the retained
|
|
103
|
+
four-cell counts and the analysis's recorded study-level continuity correction.
|
|
104
|
+
Its result is therefore independent of whether the source pooled estimate used
|
|
105
|
+
MH, inverse variance, random-effects inverse variance, or Peto. If the source
|
|
106
|
+
used Peto, a note makes the different study-effect construction explicit.
|
|
107
|
+
|
|
108
|
+
The tested coefficient is `peters.slope`, the slope of log OR on inverse total
|
|
109
|
+
sample size. `peters.limit_estimate` is the extrapolated log OR as total sample
|
|
110
|
+
size tends to infinity; `display_limit_estimate` and `display_limit_ci` are on
|
|
111
|
+
the OR scale. These are regression extrapolations, not replacements for the
|
|
112
|
+
pooled estimate. The result also records `residual_dispersion`,
|
|
113
|
+
`weight_method`, the continuity-correction contract, corrected-study count,
|
|
114
|
+
and the scaled-design condition number.
|
|
115
|
+
|
|
116
|
+
Peters regression is unavailable for generic effects whose original four-cell
|
|
117
|
+
counts are no longer known, and for RR, RD, continuous, correlation, or
|
|
118
|
+
diagnostic-accuracy analyses.
|
|
119
|
+
|
|
120
|
+
## Inspect the Egger result
|
|
121
|
+
|
|
122
|
+
The tested coefficient is the intercept in the standardized-normal-deviate
|
|
123
|
+
form of the Egger regression:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
egger.intercept
|
|
127
|
+
egger.intercept_standard_error
|
|
128
|
+
egger.intercept_ci
|
|
129
|
+
egger.statistic
|
|
130
|
+
egger.df
|
|
131
|
+
egger.pvalue
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The result also exposes the extrapolated effect as the standard error tends to
|
|
135
|
+
zero:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
egger.limit_estimate
|
|
139
|
+
egger.limit_standard_error
|
|
140
|
+
egger.limit_ci
|
|
141
|
+
egger.display_limit_estimate
|
|
142
|
+
egger.display_limit_ci
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`limit_estimate` remains on the analysis model scale. Its display counterpart
|
|
146
|
+
is exponentiated for OR/RR and back-transformed with `tanh` for Fisher's z
|
|
147
|
+
correlations. It is an extrapolated regression intercept, not an automatically
|
|
148
|
+
bias-corrected replacement for the fitted pooled estimate.
|
|
149
|
+
|
|
150
|
+
`to_dict()` returns a detached mapping containing the coefficients, intervals,
|
|
151
|
+
test, method identifiers, condition number, scales, and warnings:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
payload = egger.to_dict()
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Pass an explicit confidence level for the two coefficient intervals, or omit
|
|
158
|
+
it to reuse the fitted analysis level:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
egger_90 = result.egger_test(confidence_level=0.90)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Changing the confidence level does not change the coefficient estimates, test
|
|
165
|
+
statistic, or p-value.
|
|
166
|
+
|
|
167
|
+
## Statistical form
|
|
168
|
+
|
|
169
|
+
The classical equation is:
|
|
170
|
+
|
|
171
|
+
```text
|
|
172
|
+
y_i / s_i = alpha + beta * (1 / s_i) + error_i
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
where `y_i` is the effect and `s_i` is its standard error. The two-sided test
|
|
176
|
+
is `H0: alpha = 0` and uses a t distribution with `k-2` degrees of freedom.
|
|
177
|
+
The fitted `beta` is the limit estimate.
|
|
178
|
+
|
|
179
|
+
PyMetaAnalysis evaluates the algebraically equivalent weighted regression of
|
|
180
|
+
`y_i` on `s_i`, using inverse sampling-variance weights and a multiplicative
|
|
181
|
+
residual-dispersion estimate. The implementation scales the weights and design
|
|
182
|
+
columns for numerical stability without changing the coefficients or their
|
|
183
|
+
covariance. This contract corresponds to:
|
|
184
|
+
|
|
185
|
+
```r
|
|
186
|
+
metafor::regtest(
|
|
187
|
+
effect,
|
|
188
|
+
variance,
|
|
189
|
+
model = "lm",
|
|
190
|
+
predictor = "sei"
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The random/mixed-effects regression version of `metafor::regtest()` is a
|
|
195
|
+
different model and is not silently substituted.
|
|
196
|
+
|
|
197
|
+
## Applicability checks
|
|
198
|
+
|
|
199
|
+
At least three included studies are mathematically required. PyMetaAnalysis
|
|
200
|
+
returns a result for `3 <= k < 10` but records a warning because funnel-
|
|
201
|
+
asymmetry tests generally have low power with fewer than ten studies. The
|
|
202
|
+
[Cochrane Handbook](https://www.cochrane.org/authors/handbooks-and-manuals/handbook/current/chapter-13#section-13-3-4-4)
|
|
203
|
+
uses ten studies as a rule of thumb and also advises against testing when study
|
|
204
|
+
standard errors are all similar.
|
|
205
|
+
|
|
206
|
+
Exactly or numerically non-identifiable standard errors produce an error
|
|
207
|
+
instead of an unstable coefficient. For less extreme cases, inspect the study
|
|
208
|
+
size distribution and `egger.condition_number`; the library does not invent a
|
|
209
|
+
universal cutoff for “enough” variation.
|
|
210
|
+
|
|
211
|
+
The classical Egger test is particularly problematic for some effect measures
|
|
212
|
+
because the effect and its standard error can be inherently associated.
|
|
213
|
+
PyMetaAnalysis therefore records an additional warning for:
|
|
214
|
+
|
|
215
|
+
- odds ratios, for which binary-outcome alternatives such as Harbord or Peters
|
|
216
|
+
may be preferable;
|
|
217
|
+
- standardized mean differences, for which the same association can produce
|
|
218
|
+
distorted funnel plots.
|
|
219
|
+
|
|
220
|
+
Harbord and Peters regression are available for OR analyses created by
|
|
221
|
+
`meta_binary()`. A generic effect labeled `GENERIC` cannot reveal the original
|
|
222
|
+
two-group counts, so neither binary-specific method accepts it.
|
|
223
|
+
|
|
224
|
+
For Harbord regression, the efficient-score variances must vary enough to
|
|
225
|
+
identify the asymmetry intercept. Every included study must have positive total
|
|
226
|
+
events and non-events. The method uses raw counts and never applies the source
|
|
227
|
+
continuity correction. At least three studies are mathematically required, and
|
|
228
|
+
the same fewer-than-ten warning applies.
|
|
229
|
+
|
|
230
|
+
For Peters regression, total sample sizes must vary enough to identify the
|
|
231
|
+
slope. The method uses `S*F/N` weights, where `S` and `F` are the raw total
|
|
232
|
+
events and non-events. Its continuity correction affects only the reconstructed
|
|
233
|
+
study log OR, not these marginal-count weights. At least three studies are
|
|
234
|
+
mathematically required, and the same fewer-than-ten warning applies.
|
|
235
|
+
|
|
236
|
+
## Interpretation
|
|
237
|
+
|
|
238
|
+
A small p-value indicates evidence of the association defined by the selected
|
|
239
|
+
diagnostic: effect with standard error for Egger, standardized efficient score
|
|
240
|
+
with score precision for Harbord, or log OR with inverse total sample size for
|
|
241
|
+
Peters. Possible explanations include genuine heterogeneity, design or
|
|
242
|
+
population differences, selective outcome reporting, other non-reporting
|
|
243
|
+
mechanisms, artefactual associations, and chance.
|
|
244
|
+
|
|
245
|
+
A large p-value does not demonstrate symmetry or exclude missing evidence,
|
|
246
|
+
especially with few studies. A small p-value does not establish publication
|
|
247
|
+
bias. Interpret the test alongside the funnel plot, heterogeneity, study
|
|
248
|
+
characteristics, protocol information, and sensitivity analyses. Contour-
|
|
249
|
+
enhanced funnels add useful significance context, but neither locate missing
|
|
250
|
+
studies nor identify the mechanism behind asymmetry. The
|
|
251
|
+
[original Egger paper](https://doi.org/10.1136/bmj.315.7109.629) and
|
|
252
|
+
[`metafor::regtest`](https://wviechtb.github.io/metafor/reference/regtest.html)
|
|
253
|
+
provide the methodological and software references for the Egger
|
|
254
|
+
implementation. The Harbord and Peters implementations follow the documented
|
|
255
|
+
[`meta::metabias`](https://search.r-project.org/CRAN/refmans/meta/html/metabias.html)
|
|
256
|
+
contract and the corresponding Harbord et al. (2006) and Peters et al. (2006)
|
|
257
|
+
methods cited there.
|
|
@@ -65,8 +65,9 @@ Binary analyses with sparse data should also review
|
|
|
65
65
|
[zero-event studies](guides/zero-events.md).
|
|
66
66
|
|
|
67
67
|
After fitting a model, continue with [sensitivity analysis](guides/sensitivity-analysis.md)
|
|
68
|
-
and [
|
|
69
|
-
|
|
68
|
+
and [small-study effects](guides/small-study-effects.md) to assess stability and
|
|
69
|
+
funnel asymmetry. Use [provenance and reporting](guides/provenance-reporting.md)
|
|
70
|
+
to create an auditable export.
|
|
70
71
|
|
|
71
72
|
The [statistical methods](methods/statistical-methods.md) page is the formula-
|
|
72
73
|
level implementation contract. [Validation](validation.md) explains the R
|
|
@@ -86,13 +87,13 @@ lists unsupported methods explicitly.
|
|
|
86
87
|
|
|
87
88
|
## Project status
|
|
88
89
|
|
|
89
|
-
PyMetaAnalysis 0.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
undergone a formal external statistical audit. Pin the package version
|
|
95
|
-
consequential work and independently check important analyses. See the
|
|
90
|
+
PyMetaAnalysis 0.8.0 adds classical Egger regression plus Harbord and Peters
|
|
91
|
+
tests for two-group odds ratios, together with optional contour-enhanced funnel
|
|
92
|
+
plots. These small-study-effect diagnostics include explicit applicability
|
|
93
|
+
warnings, documented interpretation limits, and independent R reference
|
|
94
|
+
fixtures where corresponding implementations are available. The project has
|
|
95
|
+
not undergone a formal external statistical audit. Pin the package version
|
|
96
|
+
for consequential work and independently check important analyses. See the
|
|
96
97
|
repository
|
|
97
98
|
[changelog](https://github.com/ZhaoboDing/PyMetaAnalysis/blob/main/CHANGELOG.md)
|
|
98
99
|
and [contribution guide](development.md). For manuscripts and archived
|