PyMetaAnalysis 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/CHANGELOG.md +20 -0
  2. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/CITATION.cff +2 -2
  3. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/PKG-INFO +29 -6
  4. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/README.md +27 -4
  5. pymetaanalysis-0.4.0/docs/adr/0003-meta-regression-prediction-intervals.md +70 -0
  6. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/getting-started.md +1 -1
  7. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/guides/meta-regression.md +149 -3
  8. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/guides/r-interoperability.md +12 -0
  9. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/guides/sensitivity-analysis.md +82 -0
  10. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/guides/zero-events.md +6 -6
  11. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/index.md +6 -5
  12. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/limitations.md +11 -4
  13. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/methods/statistical-methods.md +134 -1
  14. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/reference/api.md +14 -4
  15. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/reference/results.md +142 -2
  16. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/releasing.md +9 -9
  17. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/validation.md +17 -1
  18. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/mkdocs.yml +1 -0
  19. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/pyproject.toml +1 -1
  20. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/__init__.py +14 -0
  21. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/_version.py +1 -1
  22. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/regression_api.py +37 -3
  23. pymetaanalysis-0.4.0/src/meta_analyze/regression_collinearity.py +304 -0
  24. pymetaanalysis-0.4.0/src/meta_analyze/regression_contrasts.py +365 -0
  25. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/regression_results.py +67 -1
  26. pymetaanalysis-0.4.0/src/meta_analyze/regression_sensitivity.py +587 -0
  27. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/README.md +13 -1
  28. pymetaanalysis-0.4.0/tests/reference/generate_meta_regression_collinearity_metafor.R +81 -0
  29. pymetaanalysis-0.4.0/tests/reference/generate_meta_regression_contrasts_metafor.R +96 -0
  30. pymetaanalysis-0.4.0/tests/reference/generate_meta_regression_influence_metafor.R +81 -0
  31. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/generate_meta_regression_metafor.R +39 -7
  32. pymetaanalysis-0.4.0/tests/reference/meta_regression_collinearity_metafor.json +65 -0
  33. pymetaanalysis-0.4.0/tests/reference/meta_regression_contrasts_metafor.json +157 -0
  34. pymetaanalysis-0.4.0/tests/reference/meta_regression_influence_metafor.json +804 -0
  35. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/meta_regression_metafor.json +190 -1
  36. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_meta_regression.py +81 -1
  37. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_properties.py +44 -0
  38. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_r_references.py +88 -0
  39. pymetaanalysis-0.4.0/tests/test_regression_collinearity.py +288 -0
  40. pymetaanalysis-0.4.0/tests/test_regression_contrasts.py +346 -0
  41. pymetaanalysis-0.4.0/tests/test_regression_influence.py +319 -0
  42. pymetaanalysis-0.4.0/tests/test_regression_sensitivity.py +273 -0
  43. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/.github/workflows/ci.yml +0 -0
  44. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/.github/workflows/pages.yml +0 -0
  45. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/.github/workflows/release.yml +0 -0
  46. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/.gitignore +0 -0
  47. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/CONTRIBUTING.md +0 -0
  48. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/LICENSE +0 -0
  49. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/SECURITY.md +0 -0
  50. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/benchmarks/README.md +0 -0
  51. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/benchmarks/benchmark_core.py +0 -0
  52. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/adr/0001-optional-matplotlib.md +0 -0
  53. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/adr/0002-statistical-policy.md +0 -0
  54. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/citation.md +0 -0
  55. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/development.md +0 -0
  56. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/guides/binary-outcomes.md +0 -0
  57. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/guides/continuous-outcomes.md +0 -0
  58. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/guides/generic-effects.md +0 -0
  59. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/guides/input-data.md +0 -0
  60. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/guides/method-selection.md +0 -0
  61. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/guides/plotting.md +0 -0
  62. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/guides/provenance-reporting.md +0 -0
  63. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/installation.md +0 -0
  64. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/reference/report-schema.md +0 -0
  65. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/docs/stylesheets/extra.css +0 -0
  66. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/examples/README.md +0 -0
  67. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/examples/meta_regression.ipynb +0 -0
  68. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/examples/quickstart.ipynb +0 -0
  69. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/api.py +0 -0
  70. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/binary_api.py +0 -0
  71. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/config.py +0 -0
  72. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/continuous_api.py +0 -0
  73. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/data.py +0 -0
  74. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/design_matrix.py +0 -0
  75. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/effect_sizes/__init__.py +0 -0
  76. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/effect_sizes/binary.py +0 -0
  77. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/effect_sizes/continuous.py +0 -0
  78. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/estimators/__init__.py +0 -0
  79. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/estimators/inverse_variance.py +0 -0
  80. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/estimators/mantel_haenszel.py +0 -0
  81. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/estimators/meta_regression.py +0 -0
  82. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/estimators/tau2.py +0 -0
  83. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/exceptions.py +0 -0
  84. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/heterogeneity.py +0 -0
  85. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/__init__.py +0 -0
  86. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/_utils.py +0 -0
  87. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/forest.py +0 -0
  88. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/funnel.py +0 -0
  89. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/regression.py +0 -0
  90. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/plotting/subgroup_forest.py +0 -0
  91. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/provenance.py +0 -0
  92. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/py.typed +0 -0
  93. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/reporting.py +0 -0
  94. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/results.py +0 -0
  95. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/sensitivity.py +0 -0
  96. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/src/meta_analyze/subgroups.py +0 -0
  97. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/binary_input.csv +0 -0
  98. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/binary_metafor.json +0 -0
  99. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/binary_sparse_input.csv +0 -0
  100. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/continuous_input.csv +0 -0
  101. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/continuous_metafor.json +0 -0
  102. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/generate_binary_metafor.R +0 -0
  103. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/generate_continuous_metafor.R +0 -0
  104. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/generate_generic_metafor.R +0 -0
  105. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/generate_workflow_metafor.R +0 -0
  106. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/generic_input.csv +0 -0
  107. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/generic_metafor.json +0 -0
  108. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/meta_regression_boundary_input.csv +0 -0
  109. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/meta_regression_input.csv +0 -0
  110. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/workflow_input.csv +0 -0
  111. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/reference/workflow_metafor.json +0 -0
  112. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_api.py +0 -0
  113. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_binary.py +0 -0
  114. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_continuous.py +0 -0
  115. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_documentation.py +0 -0
  116. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_estimators.py +0 -0
  117. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_funnel_plot.py +0 -0
  118. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_numerical_stability.py +0 -0
  119. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_plotting.py +0 -0
  120. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_reference_results.py +0 -0
  121. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_regression_plotting.py +0 -0
  122. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_release_readiness.py +0 -0
  123. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_reporting.py +0 -0
  124. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_sensitivity.py +0 -0
  125. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tests/test_subgroups.py +0 -0
  126. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tools/check_release.py +0 -0
  127. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.0}/tools/execute_notebooks.py +0 -0
  128. {pymetaanalysis-0.3.0 → pymetaanalysis-0.4.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.4.0 - 2026-07-23
10
+
11
+ ### Added
12
+
13
+ - leave-one-out Meta-regression refits with model-level diagnostics,
14
+ coefficient changes, explicit unidentifiable-deletion records, and preserved
15
+ provenance.
16
+ - exact Meta-regression externally standardized residuals, Cook's distances,
17
+ DFBETAS, transparent screening thresholds, and fixed-version R `metafor`
18
+ cross-software fixtures.
19
+ - Meta-regression term VIF, moderator-level GVIF/GSIF, and weighted,
20
+ column-scaled condition diagnostics with variance-decomposition proportions,
21
+ heuristic-only flags, and R `metafor` cross-software fixtures.
22
+ - explicit named Meta-regression linear contrasts with nonzero null values,
23
+ individual z/t inference, joint chi-squared/F tests, labeled coefficient
24
+ matrices, and R `metafor` cross-software fixtures.
25
+ - opt-in Riley Meta-regression true-effect prediction intervals using
26
+ `t_(k-p-1)`, with explicit residual-df validation, preserved refit
27
+ configuration, and fixed-version R `metafor` boundary references.
28
+
9
29
  ## 0.3.0 - 2026-07-22
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.3.0
12
- date-released: 2026-07-22
11
+ version: 0.4.0
12
+ date-released: 2026-07-23
13
13
  repository-code: https://github.com/ZhaoboDing/PyMetaAnalysis
14
14
  url: https://zhaoboding.github.io/PyMetaAnalysis/
15
15
  license: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyMetaAnalysis
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: A pandas-first, auditable meta-analysis library for Python
5
5
  Project-URL: Documentation, https://zhaoboding.github.io/PyMetaAnalysis/
6
6
  Project-URL: Source, https://github.com/ZhaoboDing/PyMetaAnalysis
@@ -28,7 +28,7 @@ Requires-Dist: actionlint-py>=1.7.12.24; extra == 'dev'
28
28
  Requires-Dist: build>=1.2; extra == 'dev'
29
29
  Requires-Dist: mypy>=1.10; extra == 'dev'
30
30
  Requires-Dist: pandas-stubs>=2.0; extra == 'dev'
31
- Requires-Dist: ruff>=0.6; extra == 'dev'
31
+ Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
32
32
  Requires-Dist: scipy-stubs>=1.10; extra == 'dev'
33
33
  Provides-Extra: docs
34
34
  Requires-Dist: mkdocs>=1.6; extra == 'docs'
@@ -177,11 +177,23 @@ Rows excluded by missing-value or sparse-data policies remain in
177
177
  `study_results` with a stable `row_id`, `included=False`, and an
178
178
  `exclusion_reason`.
179
179
 
180
- ## Sensitivity and plots
180
+ ## Diagnostics, contrasts, and plots
181
181
 
182
182
  ```python
183
183
  leave_one_out = result.leave_one_out().to_dataframe()
184
184
  cumulative = result.cumulative(order="publication_year").to_dataframe()
185
+ regression_deleted = regression.leave_one_out()
186
+ regression_coefficient_changes = regression_deleted.coefficients
187
+ regression_influence = regression.influence()
188
+ flagged_diagnostics = regression_influence.flagged
189
+ collinearity = regression.collinearity()
190
+ term_vif = collinearity.term_vif
191
+ moderator_gvif = collinearity.moderator_gvif
192
+ condition_indices = collinearity.condition_indices
193
+ south_vs_east = regression.contrast(
194
+ {"region[South]": 1.0, "region[East]": -1.0},
195
+ name="South - East",
196
+ )
185
197
 
186
198
  ax = result.forest(show_prediction_interval=True)
187
199
  ax = result.funnel()
@@ -189,9 +201,20 @@ ax = result.funnel()
189
201
 
190
202
  Plotting methods return Matplotlib axes and never call `show()`. Funnel plots
191
203
  are descriptive small-study-effect diagnostics, not proof of publication bias.
192
- An eligible single-numeric-moderator Meta-regression result additionally
193
- provides `regression.bubble()` with fitted confidence and optional prediction
194
- bands.
204
+ Meta-regression leave-one-out results also expose a long-form coefficient
205
+ change table. Exact influence diagnostics add externally standardized
206
+ residuals, Cook's distance, DFBETAS, and explicit heuristic screening
207
+ thresholds without automatically excluding studies. Meta-regression
208
+ collinearity diagnostics add `metafor`-compatible VIF/GVIF plus weighted,
209
+ column-scaled condition indices and variance-decomposition proportions.
210
+ Their documented references are review aids, not automatic variable-selection
211
+ rules. Explicit named linear contrasts provide individual z/t inference and
212
+ full-rank joint chi-squared/F tests without silently adjusting for multiple
213
+ testing. An eligible
214
+ single-numeric-moderator Meta-regression result additionally provides
215
+ `regression.bubble()` with fitted confidence and optional prediction bands.
216
+ Mixed-effects Meta-regression supports its documented default prediction rule
217
+ and an explicit Riley `t_(k-p-1)` alternative.
195
218
 
196
219
  ## Documentation
197
220
 
@@ -128,11 +128,23 @@ Rows excluded by missing-value or sparse-data policies remain in
128
128
  `study_results` with a stable `row_id`, `included=False`, and an
129
129
  `exclusion_reason`.
130
130
 
131
- ## Sensitivity and plots
131
+ ## Diagnostics, contrasts, and plots
132
132
 
133
133
  ```python
134
134
  leave_one_out = result.leave_one_out().to_dataframe()
135
135
  cumulative = result.cumulative(order="publication_year").to_dataframe()
136
+ regression_deleted = regression.leave_one_out()
137
+ regression_coefficient_changes = regression_deleted.coefficients
138
+ regression_influence = regression.influence()
139
+ flagged_diagnostics = regression_influence.flagged
140
+ collinearity = regression.collinearity()
141
+ term_vif = collinearity.term_vif
142
+ moderator_gvif = collinearity.moderator_gvif
143
+ condition_indices = collinearity.condition_indices
144
+ south_vs_east = regression.contrast(
145
+ {"region[South]": 1.0, "region[East]": -1.0},
146
+ name="South - East",
147
+ )
136
148
 
137
149
  ax = result.forest(show_prediction_interval=True)
138
150
  ax = result.funnel()
@@ -140,9 +152,20 @@ ax = result.funnel()
140
152
 
141
153
  Plotting methods return Matplotlib axes and never call `show()`. Funnel plots
142
154
  are descriptive small-study-effect diagnostics, not proof of publication bias.
143
- An eligible single-numeric-moderator Meta-regression result additionally
144
- provides `regression.bubble()` with fitted confidence and optional prediction
145
- bands.
155
+ Meta-regression leave-one-out results also expose a long-form coefficient
156
+ change table. Exact influence diagnostics add externally standardized
157
+ residuals, Cook's distance, DFBETAS, and explicit heuristic screening
158
+ thresholds without automatically excluding studies. Meta-regression
159
+ collinearity diagnostics add `metafor`-compatible VIF/GVIF plus weighted,
160
+ column-scaled condition indices and variance-decomposition proportions.
161
+ Their documented references are review aids, not automatic variable-selection
162
+ rules. Explicit named linear contrasts provide individual z/t inference and
163
+ full-rank joint chi-squared/F tests without silently adjusting for multiple
164
+ testing. An eligible
165
+ single-numeric-moderator Meta-regression result additionally provides
166
+ `regression.bubble()` with fitted confidence and optional prediction bands.
167
+ Mixed-effects Meta-regression supports its documented default prediction rule
168
+ and an explicit Riley `t_(k-p-1)` alternative.
146
169
 
147
170
  ## Documentation
148
171
 
@@ -0,0 +1,70 @@
1
+ # ADR 0003: Meta-regression prediction-interval choices
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-07-23
5
+
6
+ ## Context
7
+
8
+ For a mixed-effects Meta-regression prediction at design vector `x`, the
9
+ estimated true-effect variance combines residual heterogeneity with
10
+ uncertainty in the fitted mean:
11
+
12
+ ```text
13
+ Var_prediction = tau^2 + x' Cov(beta_hat) x
14
+ ```
15
+
16
+ The critical-value distribution remains a methodological choice. Common
17
+ software offers a default normal-or-t rule and a Riley alternative with one
18
+ fewer residual degree of freedom. Neither approximation removes uncertainty
19
+ from estimating tau-squared, and changing the package default would alter
20
+ existing results.
21
+
22
+ ## Decision
23
+
24
+ `meta_regression()` accepts
25
+ `prediction_interval_method="default" | "riley"` for mixed-effects models.
26
+ The canonical resolved method is stored in
27
+ `result.method.prediction_interval_method`.
28
+
29
+ The default remains `normal_or_t_k_minus_p`:
30
+
31
+ - normal coefficient inference uses a standard normal prediction critical
32
+ value;
33
+ - either Hartung-Knapp mode uses a t critical value with `k-p` degrees of
34
+ freedom.
35
+
36
+ The opt-in Riley method uses a t critical value with `k-p-1` degrees of
37
+ freedom regardless of the coefficient-inference distribution:
38
+
39
+ ```text
40
+ prediction = x' beta_hat
41
+ PI_Riley = prediction +/- t_(k-p-1) *
42
+ sqrt(tau^2 + x' Cov(beta_hat) x)
43
+ ```
44
+
45
+ Riley intervals require `k-p >= 2`. Requesting Riley for a common-effect model
46
+ or a mixed model without enough residual degrees of freedom raises a domain
47
+ error. The selection is preserved by deleted-study refits and reused by
48
+ `predict()` and `bubble()`.
49
+
50
+ Both rules predict the distribution of true effects in a new study at the
51
+ specified moderator values. They do not add an unknown sampling variance for
52
+ a future observed effect.
53
+
54
+ ## Validation
55
+
56
+ The committed fixed-version R `metafor` fixture records default and
57
+ `predtype="Riley"` predictions for normal and Hartung-Knapp inference,
58
+ multivariable moderator values, and a zero-tau-squared boundary. Unit tests
59
+ also check the critical-value formula and invalid degrees of freedom.
60
+ Property-based tests verify symmetry, unchanged mean-effect inference, and
61
+ the Riley interval's greater width relative to the default rule.
62
+
63
+ ## Consequences
64
+
65
+ - existing fits retain their numerical default;
66
+ - the alternative is explicit, auditable, and reproducible;
67
+ - Riley is documented as an alternative approximation rather than an
68
+ automatic small-sample correction;
69
+ - future prediction-interval rules require a separate statistical decision
70
+ and independent reference coverage.
@@ -71,7 +71,7 @@ result.ci
71
71
  result.standard_error
72
72
  result.tau2
73
73
  result.q
74
- result.i2 # a proportion from 0 to 1
74
+ result.i2 # a proportion from 0 to 1
75
75
  result.h2
76
76
  result.i2_method
77
77
  ```
@@ -136,6 +136,23 @@ precision weights, normalized precision weights, and leverage. A regression
136
136
  precision weight is not a universal percentage contribution to every
137
137
  coefficient.
138
138
 
139
+ ## Leave-one-out sensitivity
140
+
141
+ Use exact deleted-model refits to inspect dependence on individual studies:
142
+
143
+ ```python
144
+ diagnostics = result.leave_one_out()
145
+
146
+ print(diagnostics.table)
147
+ print(diagnostics.coefficients)
148
+ ```
149
+
150
+ Every successful deletion re-estimates residual tau-squared, coefficients, and
151
+ inference with the same resolved model configuration. Deletions that make the
152
+ design unidentifiable are retained explicitly instead of aborting the other
153
+ refits. See [sensitivity analysis](sensitivity-analysis.md) for the result
154
+ contract and interpretation limits.
155
+
139
156
  ## Predict at moderator values
140
157
 
141
158
  Prediction replays the fitted numeric and categorical encoding:
@@ -157,6 +174,16 @@ in a new study with those moderators. The interval does not include an
157
174
  additional, unknown sampling variance for a future observed estimate. Unknown
158
175
  categories and missing prediction inputs are rejected.
159
176
 
177
+ The default prediction rule follows coefficient inference: it uses a normal
178
+ critical value with `inference_method="normal"` and `t_(k-p)` with either
179
+ Hartung-Knapp method. Set `prediction_interval_method="riley"` while fitting to
180
+ use a `t_(k-p-1)` prediction critical value instead. Riley therefore needs at
181
+ least two residual degrees of freedom and is usually wider, especially in
182
+ small samples. It is an explicit alternative approximation, not an automatic
183
+ claim of superior coverage. The selected canonical method is retained in
184
+ `result.method.prediction_interval_method` and reused by `predict()` and
185
+ `bubble()`.
186
+
160
187
  ## Plot a single numeric moderator
161
188
 
162
189
  After installing the `plot` extra, an intercept-containing model with exactly
@@ -172,9 +199,9 @@ ax = result.bubble(
172
199
  ```
173
200
 
174
201
  Bubble area is proportional to normalized fitted precision weight. The fitted
175
- line and interval bands reuse `result.predict()`, including the selected normal
176
- or Hartung-Knapp covariance and critical value. A prediction band is available
177
- only for mixed-effects models.
202
+ line and interval bands reuse `result.predict()`, including the selected
203
+ coefficient covariance and prediction-interval rule. A prediction band is
204
+ available only for mixed-effects models.
178
205
 
179
206
  PyMetaAnalysis rejects bubble plots for categorical, multiple-moderator, or
180
207
  no-intercept fits. Drawing a marginal or partial-effect line for those models
@@ -197,6 +224,125 @@ the public value is truncated to zero and the negative raw value is retained
197
224
  with a warning. Pseudo-R² is not the proportion of outcome variance explained
198
225
  in ordinary individual-level regression.
199
226
 
227
+ ## Inspect deleted-study influence
228
+
229
+ Use exact deletion diagnostics when a study appears unusual or the fitted
230
+ association may depend strongly on one row:
231
+
232
+ ```python
233
+ influence = result.influence()
234
+
235
+ influence.table
236
+ influence.dfbetas
237
+ influence.flagged
238
+ ```
239
+
240
+ Every included study is omitted once, with tau-squared and coefficient
241
+ inference re-estimated under the original settings. The result reports the
242
+ externally standardized deleted residual, Cook's distance, and term-specific
243
+ DFBETAS values. Failed reduced models remain visible rather than being silently
244
+ dropped.
245
+
246
+ The result also exposes its numerical screening thresholds. These thresholds
247
+ identify rows for review; they do not prove that a study is erroneous,
248
+ authorize automatic exclusion, or correct for trying multiple model
249
+ specifications. See [sensitivity analysis](sensitivity-analysis.md) for the
250
+ full output and interpretation contract.
251
+
252
+ ## Inspect moderator collinearity
253
+
254
+ Use `collinearity()` to inspect coefficient inflation and the geometry of the
255
+ fitted weighted design:
256
+
257
+ ```python
258
+ collinearity = result.collinearity()
259
+
260
+ collinearity.term_vif
261
+ collinearity.moderator_gvif
262
+ collinearity.condition_indices
263
+ collinearity.variance_proportions
264
+ collinearity.concerning_dimensions
265
+ ```
266
+
267
+ `term_vif` contains one VIF and its square-root standard-error inflation factor
268
+ (`sif`) for every encoded non-intercept term. `moderator_gvif` keeps all dummy
269
+ terms for one categorical moderator together and reports its GVIF plus the
270
+ dimension-adjusted `gsif = gvif ** (1 / (2 * term_count))`. This grouping makes
271
+ a multi-level moderator comparable without pretending that each treatment-
272
+ coded term is a separate scientific variable.
273
+
274
+ The condition table uses the fitted inverse-variance weights and normalizes
275
+ each weighted design column before singular-value decomposition. It is
276
+ therefore invariant to positive changes of moderator units. The result retains
277
+ the original unweighted, scale-dependent design condition number separately as
278
+ `raw_condition_number`.
279
+
280
+ The fixed references of condition index greater than 30 and variance
281
+ proportion greater than 0.5 are conventional screening heuristics. A
282
+ `concerning` dimension must meet the condition-index reference and concentrate
283
+ more than half of the coefficient variance for at least two terms. The library
284
+ does not attach automatic cutoffs to VIF/GVIF, remove variables, refit models,
285
+ or turn these diagnostics into evidence for a preferred specification.
286
+
287
+ ## Test explicit linear contrasts
288
+
289
+ Inspect `result.design_info.term_names`, then define a scientific comparison by
290
+ term name rather than coefficient position:
291
+
292
+ ```python
293
+ south_vs_east = result.contrast(
294
+ {
295
+ "region[South]": 1.0,
296
+ "region[East]": -1.0,
297
+ },
298
+ name="South - East",
299
+ )
300
+
301
+ south_vs_east.table
302
+ south_vs_east.contrast_matrix
303
+ south_vs_east.joint_test
304
+ ```
305
+
306
+ The default null hypothesis is `C beta = 0`. Use `rhs=` for a nonzero null:
307
+
308
+ ```python
309
+ age_threshold = result.contrast(
310
+ {"mean_age": 1.0},
311
+ name="Age slope equals 0.02",
312
+ rhs=0.02,
313
+ )
314
+ ```
315
+
316
+ For several prespecified hypotheses, pass a named mapping of mappings:
317
+
318
+ ```python
319
+ contrasts = result.contrast(
320
+ {
321
+ "South - East": {
322
+ "region[South]": 1.0,
323
+ "region[East]": -1.0,
324
+ },
325
+ "Age slope": {"mean_age": 1.0},
326
+ },
327
+ rhs={
328
+ "South - East": 0.0,
329
+ "Age slope": 0.02,
330
+ },
331
+ )
332
+ ```
333
+
334
+ A DataFrame is also accepted: rows are named contrasts and columns are fitted
335
+ term names. Unspecified fitted terms receive weight zero. Unknown terms,
336
+ nonfinite weights, zero rows, duplicate names, and rank-deficient multi-row
337
+ contrast matrices are errors rather than implicit repairs.
338
+
339
+ Each row reports the contrast estimate, standard error, null value,
340
+ estimate-minus-null, z or t statistic, unadjusted p-value, and confidence
341
+ interval for `C beta`. A full-row-rank set also receives a joint chi-squared or
342
+ F test in `joint_test`. Individual p-values are deliberately not adjusted for
343
+ multiple testing; prespecify the contrast family and apply an external
344
+ multiplicity procedure when the scientific protocol requires one.
345
+
200
346
  ## Missing values, provenance, and reports
201
347
 
202
348
  `missing="raise"` identifies every missing effect, uncertainty, study label,
@@ -19,6 +19,9 @@ fixtures used by this project.
19
19
  | Continuous group summaries | `meta_continuous()` | `escalc()` then `rma.uni()` | `metacont()` |
20
20
  | Subgroups | `subgroup=` on a high-level call | separate fits or a moderator model | `subgroup=` |
21
21
  | Leave-one-out | `result.leave_one_out()` | `leave1out()` for supported fits | `metainf()` |
22
+ | Meta-regression influence | `regression.influence()` | `influence()`, `rstudent()`, `cooks.distance()`, `dfbetas()` | — |
23
+ | Meta-regression collinearity | `regression.collinearity()` | `vif()` plus weighted design diagnostics | — |
24
+ | Meta-regression linear contrasts | `regression.contrast(...)` | `anova(..., X=..., rhs=...)` | — |
22
25
  | Cumulative analysis | `result.cumulative()` | `cumul()` | `metacum()` |
23
26
 
24
27
  PyMetaAnalysis intentionally has no `metabin`, `metacont`, or `rma` aliases.
@@ -88,6 +91,14 @@ Eligible random-effects fits include the documented HTS prediction interval.
88
91
  R packages offer additional prediction-interval choices, so matching the mean
89
92
  interval does not by itself guarantee a matching prediction interval.
90
93
 
94
+ Meta-regression uses a separate prediction rule. Its default corresponds to
95
+ `predict(fit)` in `metafor`: normal inference uses a normal critical value and
96
+ Hartung-Knapp inference uses `t_(k-p)`. PyMetaAnalysis
97
+ `prediction_interval_method="riley"` corresponds to
98
+ `predict(fit, predtype="Riley")`, using `t_(k-p-1)` for the true-effect
99
+ prediction interval while leaving the mean-effect confidence interval
100
+ unchanged.
101
+
91
102
  ## Sparse binary studies
92
103
 
93
104
  The closest names are:
@@ -171,6 +182,7 @@ as numerically equivalent.
171
182
  ## Primary R references
172
183
 
173
184
  - [`metafor::rma.uni`](https://wviechtb.github.io/metafor/reference/rma.uni.html)
185
+ - [`metafor::predict.rma`](https://wviechtb.github.io/metafor/reference/predict.rma.html)
174
186
  - [`metafor::rma.mh`](https://wviechtb.github.io/metafor/reference/rma.mh.html)
175
187
  - [`metafor::escalc`](https://wviechtb.github.io/metafor/reference/escalc.html)
176
188
  - [`meta::metagen`](https://search.r-project.org/CRAN/refmans/meta/html/metagen.html)
@@ -31,6 +31,81 @@ requires at least two included studies so each refit retains one. Random-effects
31
31
  analysis requires at least three so every refit retains the two studies needed
32
32
  to estimate tau-squared.
33
33
 
34
+ ### Meta-regression leave-one-out analysis
35
+
36
+ `MetaRegressionResult.leave_one_out()` repeats the fitted regression while
37
+ omitting each included study:
38
+
39
+ ```python
40
+ diagnostics = regression.leave_one_out()
41
+
42
+ print(diagnostics.table)
43
+ print(diagnostics.coefficients)
44
+ ```
45
+
46
+ The returned `MetaRegressionLeaveOneOutResult` retains `original` and a
47
+ `results` tuple aligned with the omitted-study rows. Its model table reports
48
+ the refitted residual heterogeneity, global moderator test, condition number,
49
+ and warnings. Its long-form coefficient table reports every refitted term and
50
+ `estimate_change`, defined as the deleted estimate minus the full-model
51
+ estimate.
52
+
53
+ Deleting a study can make a categorical level disappear or otherwise make the
54
+ design matrix unidentifiable. That deletion is retained with
55
+ `refit_success=False`, an exception type and message, unavailable numeric
56
+ fields, and `None` in the matching `results` position. Other deletions continue
57
+ to be fitted. Use `diagnostics.failed` to inspect these rows. A failed deletion
58
+ does not by itself label that study as influential; it shows that the fitted
59
+ design depends on the study for identifiability.
60
+
61
+ The original model must have at least `k >= p + 2`, so deleting one study can
62
+ still leave more studies than coefficients. Each successful refit re-estimates
63
+ tau-squared and coefficient inference with the original model settings.
64
+
65
+ ### Meta-regression influence diagnostics
66
+
67
+ `MetaRegressionResult.influence()` builds on the same exact deleted-model
68
+ refits:
69
+
70
+ ```python
71
+ influence = regression.influence()
72
+
73
+ print(influence.table)
74
+ print(influence.dfbetas)
75
+ print(influence.flagged)
76
+ ```
77
+
78
+ `MetaRegressionInfluenceResult` contains the underlying `leave_one_out`
79
+ workflow and its omission-aligned `results`. The case-level `table` adds:
80
+
81
+ - the deleted residual, its standard error, and the externally standardized
82
+ residual;
83
+ - Cook's distance based on the full-model coefficient covariance;
84
+ - the largest absolute DFBETAS value across fitted terms;
85
+ - the original leverage and normalized precision weight;
86
+ - separate outlier and influence screening flags.
87
+
88
+ The long-form `dfbetas` table records the unstandardized `dfbeta` as
89
+ full-model minus deleted-model coefficient, its deletion-based standard-error
90
+ reference, the resulting DFBETAS value, and its threshold flag for each term.
91
+ Failed deletion fits retain their exception type and message in `table`, use
92
+ `NaN` diagnostics, and retain one unavailable row per term in `dfbetas`.
93
+
94
+ The screening references are explicit in both the result attributes and table:
95
+
96
+ - `abs(externally_standardized_residual) > z(0.975)` is a pointwise,
97
+ asymptotic potential-outlier reference;
98
+ - Cook's distance is flagged above the median of a chi-squared distribution
99
+ with `p` degrees of freedom;
100
+ - `abs(DFBETAS) > 1` flags a coefficient-specific change.
101
+
102
+ `potentially_influential` combines only the Cook's-distance and DFBETAS rules;
103
+ `flagged` also includes the residual outlier screen. These cutoffs are
104
+ heuristic diagnostics, not hypothesis-test conclusions, multiplicity-adjusted
105
+ decision rules, or instructions to exclude a study. Inspect subject-matter
106
+ differences, data quality, protocol decisions, and model specification before
107
+ acting on them.
108
+
34
109
  ## Cumulative analysis
35
110
 
36
111
  By default, cumulative analysis follows input order:
@@ -80,6 +155,9 @@ Every refit reuses the original result's:
80
155
  - binary continuity corrections and RD zero-variance policy, or the continuous
81
156
  effect-size convention.
82
157
 
158
+ Meta-regression refits additionally reuse the intercept choice, inference
159
+ method, moderator order, and complete explicit categorical level definitions.
160
+
83
161
  Derived results retain provenance that maps their local calculations back to
84
162
  the original `row_id` values.
85
163
 
@@ -106,3 +184,7 @@ design, outcome definition, risk of bias, and numerical leverage. They are not
106
184
  by themselves a reason to exclude the study. Likewise, a cumulative trend can
107
185
  describe the historical evidence path but does not remove time-related changes
108
186
  in methods, populations, or publication processes.
187
+
188
+ The current Meta-regression workflow reports exact deleted-model fits and
189
+ coefficient changes. It does not yet calculate Cook's distance, DFBETAS, or an
190
+ automatic influential-study flag.
@@ -9,8 +9,8 @@ corrections from the pooled Mantel-Haenszel correction.
9
9
  For study-level OR/RR effects, the default settings are:
10
10
 
11
11
  ```python
12
- continuity_correction=0.5
13
- correction_scope="only_zero_studies"
12
+ continuity_correction = 0.5
13
+ correction_scope = "only_zero_studies"
14
14
  ```
15
15
 
16
16
  The correction is added to every cell of an included study containing at least
@@ -43,7 +43,7 @@ variance. This includes double-zero, double-all, and opposite-boundary tables.
43
43
  The default policy retains these studies:
44
44
 
45
45
  ```python
46
- rd_zero_variance="correct"
46
+ rd_zero_variance = "correct"
47
47
  ```
48
48
 
49
49
  The RD itself remains the raw treatment risk minus control risk. Corrected
@@ -51,7 +51,7 @@ counts are used only to form a positive sampling variance. To exclude all such
51
51
  studies before pooling, Q, tau-squared, and weight calculations, use:
52
52
 
53
53
  ```python
54
- rd_zero_variance="exclude"
54
+ rd_zero_variance = "exclude"
55
55
  ```
56
56
 
57
57
  Excluded rows remain in `result.study_results` with
@@ -63,8 +63,8 @@ RD-specific; setting `exclude` for OR or RR is rejected.
63
63
  Exact common-effect Mantel-Haenszel OR/RR pooling uses raw tables by default:
64
64
 
65
65
  ```python
66
- mh_continuity_correction=None
67
- mh_correction_scope="only_zero_studies"
66
+ mh_continuity_correction = None
67
+ mh_correction_scope = "only_zero_studies"
68
68
  ```
69
69
 
70
70
  `continuity_correction` still controls the study-level effects used for display
@@ -84,11 +84,12 @@ lists unsupported methods explicitly.
84
84
 
85
85
  ## Project status
86
86
 
87
- PyMetaAnalysis 0.2.1 fixes documentation and repository links in the PyPI
88
- project description. Version 0.2.0 added direct generic standard-error input
89
- to the initial public analysis scope. The project has not undergone a formal
90
- external statistical audit. Pin the package version for consequential work
91
- and independently check important analyses. See the repository
87
+ PyMetaAnalysis 0.4.0 extends Meta-regression with exact leave-one-out and
88
+ influence diagnostics, VIF/GVIF and weighted condition diagnostics, explicit
89
+ linear contrasts, and opt-in Riley prediction intervals. The project has not
90
+ undergone a formal external statistical audit. Pin the package version for
91
+ consequential work and independently check important analyses. See the
92
+ repository
92
93
  [changelog](https://github.com/ZhaoboDing/PyMetaAnalysis/blob/main/CHANGELOG.md)
93
94
  and [contribution guide](development.md). For manuscripts and archived
94
95
  analyses, see [citing PyMetaAnalysis](citation.md).
@@ -19,22 +19,29 @@ deferred functionality explicit.
19
19
  - forest and descriptive funnel plots;
20
20
  - structured provenance and reports;
21
21
  - study-level Meta-regression with numeric and explicitly encoded categorical
22
- moderators, generalized DL/PM/REML, and normal/Hartung-Knapp inference.
22
+ moderators, generalized DL/PM/REML, normal/Hartung-Knapp inference, and
23
+ exact leave-one-out refits plus externally standardized residual, Cook's
24
+ distance, DFBETAS, VIF/GVIF, weighted condition diagnostics, and explicit
25
+ linear contrasts;
26
+ - default normal/`t_(k-p)` and opt-in Riley `t_(k-p-1)` true-effect
27
+ prediction intervals for mixed-effects Meta-regression.
23
28
 
24
29
  ## Not currently implemented
25
30
 
26
31
  - Peto odds ratios;
27
32
  - Mantel-Haenszel risk differences or random-effects MH pooling;
28
33
  - formula parsing, automatic interactions/splines, stepwise moderator
29
- selection, multiplicity correction, or arbitrary linear contrasts;
34
+ selection, automatic pairwise contrasts, or multiplicity correction;
30
35
  - multilevel, multivariate, network, dose-response, diagnostic-accuracy, or
31
36
  individual-participant-data meta-analysis;
32
37
  - robust variance estimation or dependent-effect clustering;
33
38
  - single proportions, incidence rates, correlations, or survival outcomes;
34
39
  - Knapp-Hartung variants beyond the two documented choices;
35
- - alternative prediction-interval methods;
40
+ - prediction-interval methods beyond the documented default and Riley rules;
36
41
  - formal funnel-asymmetry, trim-and-fill, selection-model, or publication-bias
37
42
  procedures;
43
+ - Meta-regression DFFITS, covariance ratios, influence plots, or simulated and
44
+ re-estimated VIF variants;
38
45
  - automatic conversion from confidence intervals, p-values, or raw papers to
39
46
  study effects;
40
47
  - risk-of-bias assessment, certainty grading, protocol management, or study
@@ -78,7 +85,7 @@ workflow must version those artifacts separately.
78
85
 
79
86
  ## Stability and review status
80
87
 
81
- The package version is currently `0.2.1`. Public APIs and serialized schemas
88
+ The package version is currently `0.4.0`. Public APIs and serialized schemas
82
89
  may change during the 0.x series. Pin versions in analysis environments and
83
90
  inspect changelog/schema updates before upgrading.
84
91