Sensitivity analysis¶
Sensitivity workflows repeatedly refit the stored model. They help reveal how the pooled estimate and heterogeneity depend on individual studies or on the order in which evidence accumulated. They do not automatically diagnose why a study is influential or replace protocol-specified sensitivity analyses.
Leave-one-out analysis¶
Call leave_one_out() on any fitted MetaAnalysisResult:
import pandas as pd
import meta_analyze as ma
studies = pd.DataFrame(
{
"effect": [0.12, 0.35, -0.08, 0.21, 0.42, 0.28],
"variance": [0.04, 0.06, 0.03, 0.05, 0.07, 0.045],
"publication_year": [2001, 2004, 2004, 2008, 2011, 2015],
"dose": [0, 1, 2, 3, 4, 5],
"region": ["A", "A", "A", "B", "B", "B"],
}
)
result = ma.meta_analysis(studies, effect="effect", variance="variance")
influence = result.leave_one_out()
print(influence.to_dataframe())
The returned LeaveOneOutResult contains:
original, the fitted result supplied to the workflow;results, one omission-aligned immutable refit orNoneper included study;table,summary(), andto_dataframe(), which return defensive copies;failed, the rows for reduced models that could not be estimated;warnings, for workflow-level notes.
The table identifies omitted_row_id and omitted_study and reports each
refit's estimate, standard error, confidence interval, tau-squared, Q,
I-squared, and H-squared. It also records refit_success, error_type, and
error_message. An unestimable deletion remains aligned in results as
None, retains a table row with unavailable numeric values, and does not stop
the remaining deletions.
Originally excluded rows are never omission candidates. Common-effect analysis requires at least two included studies so each refit retains one. Random-effects analysis requires at least three so every refit retains the two studies needed to estimate tau-squared.
Meta-regression leave-one-out analysis¶
MetaRegressionResult.leave_one_out() repeats the fitted regression while
omitting each included study:
regression = ma.meta_regression(
studies, effect="effect", variance="variance", moderators=["dose"]
)
diagnostics = regression.leave_one_out()
print(diagnostics.table)
print(diagnostics.coefficients)
The returned MetaRegressionLeaveOneOutResult retains original and a
results tuple aligned with the omitted-study rows. Its model table reports
the refitted residual heterogeneity, global moderator test, condition number,
and warnings. Its long-form coefficient table reports every refitted term and
estimate_change, defined as the deleted estimate minus the full-model
estimate.
Deleting a study can make a categorical level disappear or otherwise make the
design matrix unidentifiable. That deletion is retained with
refit_success=False, an exception type and message, unavailable numeric
fields, and None in the matching results position. Other deletions continue
to be fitted. Use diagnostics.failed to inspect these rows. A failed deletion
does not by itself label that study as influential; it shows that the fitted
design depends on the study for identifiability.
The original model must have at least k >= p + 2, so deleting one study can
still leave more studies than coefficients. Each successful refit re-estimates
tau-squared and coefficient inference with the original model settings.
Meta-regression influence diagnostics¶
MetaRegressionResult.influence() builds on the same exact deleted-model
refits:
influence = regression.influence()
print(influence.table)
print(influence.dfbetas)
print(influence.flagged)
MetaRegressionInfluenceResult contains the underlying leave_one_out
workflow and its omission-aligned results. The case-level table adds:
- the deleted residual, its standard error, and the externally standardized residual;
- Cook's distance based on the full-model coefficient covariance;
- the largest absolute DFBETAS value across fitted terms;
- the original leverage and normalized precision weight;
- separate outlier and influence screening flags.
The long-form dfbetas table records the unstandardized dfbeta as
full-model minus deleted-model coefficient, its deletion-based standard-error
reference, the resulting DFBETAS value, and its threshold flag for each term.
Failed deletion fits retain their exception type and message in table, use
NaN diagnostics, and retain one unavailable row per term in dfbetas.
The screening references are explicit in both the result attributes and table:
abs(externally_standardized_residual) > z(0.975)is a pointwise, asymptotic potential-outlier reference;- Cook's distance is flagged above the median of a chi-squared distribution
with
pdegrees of freedom; abs(DFBETAS) > 1flags a coefficient-specific change.
potentially_influential combines only the Cook's-distance and DFBETAS rules;
flagged also includes the residual outlier screen. These cutoffs are
heuristic diagnostics, not hypothesis-test conclusions, multiplicity-adjusted
decision rules, or instructions to exclude a study. Inspect subject-matter
differences, data quality, protocol decisions, and model specification before
acting on them.
Cumulative analysis¶
By default, cumulative analysis follows input order:
cumulative = result.cumulative()
print(cumulative.to_dataframe())
Supply a DataFrame column name or one-dimensional array-like to define another stable order:
cumulative = result.cumulative(
order="publication_year",
ascending=True,
)
A string selector must identify exactly one namespace. If the same column name
exists in both the original source data and the result study table, it is
ambiguous and raises InvalidStudyDataError. Pass the intended Series or array
explicitly, for example order=studies["publication_year"].
CumulativeMetaAnalysisResult.results contains the estimable prefix fits, and
final returns the last fit. Its table records the rows and study labels added
at each step together with the same principal statistics as leave-one-out.
If an otherwise eligible prefix is not estimable, it is skipped with an
explicit warning. Its pending studies are included in the next estimable
prefix row, so the cumulative path and final all-study fit remain complete.
When multiple studies have the same order value, collapse=True adds the tied
studies in one step:
cumulative = result.cumulative(
order="publication_year",
collapse=True,
)
Random-effects cumulative analysis starts at the first two-study prefix. The
unavailable single-study prefix is not silently fitted as a common-effect
model, and the boundary is recorded in cumulative.warnings.
Reused analysis settings¶
Every refit reuses the original result's:
- common- or random-effects model;
- inverse-variance, Mantel-Haenszel, or Peto pooling method;
- tau-squared and confidence-interval methods;
- confidence level, absolute tolerance, and iteration limit;
- missing-data policy;
- binary continuity corrections and RD zero-variance policy, the continuous effect-size convention, or the Fisher's z correlation convention.
Meta-regression refits additionally reuse the intercept choice, inference method, moderator order, and complete explicit categorical level definitions.
Derived results retain provenance that maps their local calculations back to
the original row_id values.
Repeated-fit tables include i2_method so a random-effects path cannot be
mistaken for a Q-based inconsistency series.
Subgroup results¶
The same methods are available on SubgroupMetaAnalysisResult:
subgroups = ma.meta_analysis(
studies, effect="effect", variance="variance", subgroup="region"
)
subgroup_influence = subgroups.leave_one_out()
subgroup_cumulative = subgroups.cumulative(order="publication_year")
These return composite objects with .groups and .overall workflows. Each
subgroup is refitted independently. The paths do not reinterpret different
numbers of subgroup steps as a new sequence of subgroup-differences tests.
Interpretation¶
Large changes after omitting a study warrant inspection of its population, design, outcome definition, risk of bias, and numerical leverage. They are not by themselves a reason to exclude the study. Likewise, a cumulative trend can describe the historical evidence path but does not remove time-related changes in methods, populations, or publication processes.