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.
Files changed (155) hide show
  1. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/CHANGELOG.md +20 -0
  2. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/CITATION.cff +2 -2
  3. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/PKG-INFO +12 -3
  4. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/README.md +11 -2
  5. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/getting-started.md +25 -1
  6. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/binary-outcomes.md +21 -0
  7. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/plotting.md +46 -3
  8. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/r-interoperability.md +66 -0
  9. pymetaanalysis-0.8.0/docs/guides/small-study-effects.md +257 -0
  10. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/index.md +10 -9
  11. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/limitations.md +22 -5
  12. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/methods/statistical-methods.md +109 -0
  13. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/reference/api.md +43 -3
  14. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/reference/results.md +72 -1
  15. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/validation.md +28 -6
  16. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/mkdocs.yml +1 -0
  17. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/__init__.py +4 -0
  18. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/_version.py +1 -1
  19. pymetaanalysis-0.8.0/src/meta_analyze/plotting/funnel.py +444 -0
  20. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/results.py +49 -2
  21. pymetaanalysis-0.8.0/src/meta_analyze/small_study_effects.py +961 -0
  22. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/README.md +22 -9
  23. pymetaanalysis-0.8.0/tests/reference/generate_harbord_small_study_effects_meta.R +73 -0
  24. pymetaanalysis-0.8.0/tests/reference/generate_peters_small_study_effects_meta.R +71 -0
  25. pymetaanalysis-0.8.0/tests/reference/generate_small_study_effects_metafor.R +56 -0
  26. pymetaanalysis-0.8.0/tests/reference/harbord_small_study_effects_meta.json +24 -0
  27. pymetaanalysis-0.8.0/tests/reference/peters_small_study_effects_input.csv +13 -0
  28. pymetaanalysis-0.8.0/tests/reference/peters_small_study_effects_meta.json +25 -0
  29. pymetaanalysis-0.8.0/tests/reference/small_study_effects_input.csv +13 -0
  30. pymetaanalysis-0.8.0/tests/reference/small_study_effects_metafor.json +20 -0
  31. pymetaanalysis-0.8.0/tests/test_funnel_plot.py +499 -0
  32. pymetaanalysis-0.8.0/tests/test_harbord_small_study_effects.py +330 -0
  33. pymetaanalysis-0.8.0/tests/test_peters_small_study_effects.py +301 -0
  34. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_r_references.py +24 -1
  35. pymetaanalysis-0.8.0/tests/test_small_study_effects.py +264 -0
  36. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tools/check_release.py +30 -0
  37. pymetaanalysis-0.7.0/src/meta_analyze/plotting/funnel.py +0 -168
  38. pymetaanalysis-0.7.0/tests/test_funnel_plot.py +0 -218
  39. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/.github/workflows/ci.yml +0 -0
  40. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/.github/workflows/pages.yml +0 -0
  41. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/.github/workflows/release.yml +0 -0
  42. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/.gitignore +0 -0
  43. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/CONTRIBUTING.md +0 -0
  44. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/LICENSE +0 -0
  45. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/SECURITY.md +0 -0
  46. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/benchmarks/README.md +0 -0
  47. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/benchmarks/benchmark_core.py +0 -0
  48. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/adr/0001-optional-matplotlib.md +0 -0
  49. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/adr/0002-statistical-policy.md +0 -0
  50. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/adr/0003-meta-regression-prediction-intervals.md +0 -0
  51. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/adr/0004-hartung-knapp-prediction-intervals.md +0 -0
  52. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/adr/0005-mantel-haenszel-risk-difference.md +0 -0
  53. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/adr/0006-peto-odds-ratio.md +0 -0
  54. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/adr/0007-fisher-z-correlation.md +0 -0
  55. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/citation.md +0 -0
  56. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/development.md +0 -0
  57. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/continuous-outcomes.md +0 -0
  58. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/correlation-outcomes.md +0 -0
  59. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/generic-effects.md +0 -0
  60. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/input-data.md +0 -0
  61. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/meta-regression.md +0 -0
  62. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/method-selection.md +0 -0
  63. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/provenance-reporting.md +0 -0
  64. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/sensitivity-analysis.md +0 -0
  65. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/guides/zero-events.md +0 -0
  66. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/installation.md +0 -0
  67. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/reference/report-schema.md +0 -0
  68. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/releasing.md +0 -0
  69. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/docs/stylesheets/extra.css +0 -0
  70. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/examples/README.md +0 -0
  71. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/examples/meta_regression.ipynb +0 -0
  72. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/examples/quickstart.ipynb +0 -0
  73. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/pyproject.toml +0 -0
  74. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/api.py +0 -0
  75. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/binary_api.py +0 -0
  76. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/config.py +0 -0
  77. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/continuous_api.py +0 -0
  78. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/correlation_api.py +0 -0
  79. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/data.py +0 -0
  80. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/design_matrix.py +0 -0
  81. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/effect_sizes/__init__.py +0 -0
  82. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/effect_sizes/binary.py +0 -0
  83. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/effect_sizes/continuous.py +0 -0
  84. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/effect_sizes/correlation.py +0 -0
  85. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/estimators/__init__.py +0 -0
  86. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/estimators/inverse_variance.py +0 -0
  87. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/estimators/mantel_haenszel.py +0 -0
  88. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/estimators/meta_regression.py +0 -0
  89. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/estimators/peto.py +0 -0
  90. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/estimators/tau2.py +0 -0
  91. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/exceptions.py +0 -0
  92. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/heterogeneity.py +0 -0
  93. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/plotting/__init__.py +0 -0
  94. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/plotting/_utils.py +0 -0
  95. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/plotting/forest.py +0 -0
  96. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/plotting/regression.py +0 -0
  97. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/plotting/subgroup_forest.py +0 -0
  98. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/provenance.py +0 -0
  99. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/py.typed +0 -0
  100. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/regression_api.py +0 -0
  101. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/regression_collinearity.py +0 -0
  102. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/regression_contrasts.py +0 -0
  103. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/regression_results.py +0 -0
  104. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/regression_sensitivity.py +0 -0
  105. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/reporting.py +0 -0
  106. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/sensitivity.py +0 -0
  107. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/src/meta_analyze/subgroups.py +0 -0
  108. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/binary_input.csv +0 -0
  109. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/binary_metafor.json +0 -0
  110. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/binary_sparse_input.csv +0 -0
  111. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/continuous_input.csv +0 -0
  112. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/continuous_metafor.json +0 -0
  113. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/correlation_input.csv +0 -0
  114. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/correlation_metafor.json +0 -0
  115. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_binary_metafor.R +0 -0
  116. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_continuous_metafor.R +0 -0
  117. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_correlation_metafor.R +0 -0
  118. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_generic_metafor.R +0 -0
  119. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_meta_regression_collinearity_metafor.R +0 -0
  120. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_meta_regression_contrasts_metafor.R +0 -0
  121. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_meta_regression_influence_metafor.R +0 -0
  122. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_meta_regression_metafor.R +0 -0
  123. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generate_workflow_metafor.R +0 -0
  124. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generic_input.csv +0 -0
  125. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/generic_metafor.json +0 -0
  126. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/meta_regression_boundary_input.csv +0 -0
  127. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/meta_regression_collinearity_metafor.json +0 -0
  128. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/meta_regression_contrasts_metafor.json +0 -0
  129. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/meta_regression_influence_metafor.json +0 -0
  130. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/meta_regression_input.csv +0 -0
  131. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/meta_regression_metafor.json +0 -0
  132. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/workflow_input.csv +0 -0
  133. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/reference/workflow_metafor.json +0 -0
  134. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_api.py +0 -0
  135. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_binary.py +0 -0
  136. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_continuous.py +0 -0
  137. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_correlation.py +0 -0
  138. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_documentation.py +0 -0
  139. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_estimators.py +0 -0
  140. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_meta_regression.py +0 -0
  141. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_numerical_stability.py +0 -0
  142. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_plotting.py +0 -0
  143. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_properties.py +0 -0
  144. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_reference_results.py +0 -0
  145. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_regression_collinearity.py +0 -0
  146. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_regression_contrasts.py +0 -0
  147. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_regression_influence.py +0 -0
  148. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_regression_plotting.py +0 -0
  149. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_regression_sensitivity.py +0 -0
  150. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_release_readiness.py +0 -0
  151. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_reporting.py +0 -0
  152. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_sensitivity.py +0 -0
  153. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tests/test_subgroups.py +0 -0
  154. {pymetaanalysis-0.7.0 → pymetaanalysis-0.8.0}/tools/execute_notebooks.py +0 -0
  155. {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.7.0
12
- date-released: 2026-09-02
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.7.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, not proof of publication bias.
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, not proof of publication bias.
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 the shaded pseudo-limit region |
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. PyMetaAnalysis currently provides the
100
- plot but not formal asymmetry tests.
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 [provenance and reporting](guides/provenance-reporting.md) to assess stability
69
- and create an auditable export.
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.7.0 adds independent Pearson-correlation pooling on Fisher's
90
- z scale, including common- and random-effects fits, back-transformed displays,
91
- subgroups, sensitivity workflows, plots, reports, and independent R
92
- references. It also tightens categorical moderator and method-option contracts
93
- and expands small-sample and Q-profile validation. The project has not
94
- undergone a formal external statistical audit. Pin the package version for
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