Plotting

Forest, subgroup forest, funnel, and Meta-regression bubble plots use optional Matplotlib support. Install it with:

python -m pip install "PyMetaAnalysis[plot]"

Plotting methods return a Matplotlib Axes and never call show(). This makes them suitable for notebooks, scripts, tests, and larger composed figures.

Forest plots

import meta_analyze as ma

trials = dict(
    event_treat=[12, 8, 15, 6, 10, 9],
    n_treat=[120, 95, 140, 80, 110, 100],
    event_control=[18, 11, 19, 10, 16, 12],
    n_control=[118, 100, 145, 82, 115, 105],
    measure="RR",
    method="IV",
    model="random",
)
result = ma.meta_binary(**trials)
ax = result.forest(
    effect_label="Risk ratio",
    pooled_label="Pooled RR",
    show_prediction_interval=True,
    show_weights=True,
)

The plot contains only included studies. Study markers are scaled by normalized model weights, study confidence intervals use the fitted confidence level, and the pooled confidence interval is drawn as a diamond. A random-effects prediction interval is shown when it is available and requested.

Pass an existing axes to compose the plot:

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 5))
result.forest(ax=ax)
fig.tight_layout()

Forest parameters

Parameter Meaning
ax Existing axes; a new one is created when omitted
effect_label X-axis label
pooled_label Label for the pooled row
show_prediction_interval Show an available random-effects interval
show_weights Print normalized study weights
null_value Reference line; defaults to 1 for ratios and 0 otherwise
log_scale Override the default logarithmic ratio axis

OR and RR are modeled on a log scale but displayed as ratios on a logarithmic axis by default. ZCOR is modeled on Fisher's z scale, back-transformed to correlations, and displayed on a linear axis. Other measures use an identity display scale and linear axis. When overriding log_scale=True, all displayed effects and the null value must be strictly positive.

Subgroup forest plots

subgroups = ma.meta_binary(**trials, subgroup=["A", "A", "A", "B", "B", "B"])
ax = subgroups.forest(
    show_prediction_interval=True,
    show_weights=True,
)

The subgroup plot adds subgroup headings, subtotal diamonds, the overall result, and the formal test for subgroup differences. Display-scale and null- line rules match the ordinary forest plot.

Funnel plots

ax = result.funnel(
    confidence_level=0.95,
    show_pseudo_confidence_interval=True,
)

The y-axis is the study standard error and is inverted so more precise studies appear toward the top. The vertical reference is the fitted pooled estimate. Pseudo confidence limits are centered on that estimate and do not include tau-squared.

Add two-sided significance contours around the null effect with:

ax = result.funnel(
    contour_levels=(0.90, 0.95, 0.99),
)

These confidence levels define the visible bands 0.05 < p <= 0.10, 0.01 < p <= 0.05, and p <= 0.01; the central p > 0.10 region remains unshaded. The solid vertical line remains the fitted pooled estimate, while a dotted line marks the null used for the contours. When contours are enabled, their background replaces the ordinary blue pseudo-limit fill so the colors do not mix. The pseudo-limit boundary lines are still drawn unless show_pseudo_confidence_interval=False.

Funnel parameters

Parameter Meaning
ax Existing axes; a new one is created when omitted
effect_label X-axis label
confidence_level Pseudo-limit level; defaults to the fitted level
show_pseudo_confidence_interval Draw pooled pseudo-limit boundaries and, without contours, their shaded region
contour_levels Strictly increasing confidence levels in (0,1); None disables contours
contour_colors One valid Matplotlib color per contour level; defaults to light-to-dark gray
contour_reference Contour null on the display scale; defaults to 1 for ratios and 0 otherwise
show_contour_legend Show the corresponding two-sided p-value bands
warn_on_few_studies Warn when fewer than 10 studies are plotted
log_scale Override the default logarithmic ratio axis

For example, customize the bands and null reference with:

ax = result.funnel(
    contour_levels=(0.90, 0.95),
    contour_colors=("#fee2e2", "#ef4444"),
    contour_reference=1.0,
    show_contour_legend=True,
)

contour_reference uses the displayed effect scale. It must therefore be positive for OR/RR results, whose default is 1, and strictly between -1 and 1 for displayed correlations. Boundaries are calculated on the model scale before the normal display transformation. Contours are always based on sampling standard errors and do not incorporate tau-squared.

Funnel asymmetry can reflect small-study effects, heterogeneity, outcome selection, design differences, chance, or publication processes. It is not by itself evidence of publication bias. Contours help assess whether apparent missing areas are predominantly statistically non-significant, but they do not show that studies are actually missing or determine why asymmetry exists. Use the separately documented classical result.egger_test(), or result.harbord_test() / result.peters_test() for an eligible binary OR analysis, when a formal regression diagnostic is appropriate. None changes the plot or proves a publication mechanism.

For a trim-and-fill result, filled.funnel() uses the same funnel geometry and options while drawing observed studies as solid points and imputed studies as hollow points:

filled = result.trim_and_fill()
ax = filled.funnel(contour_levels=(0.90, 0.95, 0.99))

The legend keeps the two study types distinct. Imputed points are synthetic sensitivity constructs, not newly discovered studies.

Meta-regression bubble plots

An intercept-containing Meta-regression with exactly one numeric moderator provides:

regression = ma.meta_regression(
    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],
    moderators={"dose": [0, 1, 2, 3, 4, 5]},
)
ax = regression.bubble(
    moderator_label="Dose",
    effect_label="Effect",
    show_confidence_interval=True,
    show_prediction_interval=False,
)

Study marker area is proportional to normalized precision weight. The line, mean confidence band, and optional mixed-effects true-effect prediction band are obtained from the fitted model's predict() method.

Parameter Meaning
ax Existing axes; a new one is created when omitted
moderator_label X-axis label; defaults to the moderator name
effect_label Y-axis label; defaults to "Effect"
show_confidence_interval Draw the fitted mean confidence band
show_prediction_interval Draw a mixed-effects true-effect prediction band

Categorical, multivariable, and no-intercept fits are rejected because a marginal plot would require values or averaging rules for other terms. The function does not infer those scientific choices.

Save or display

The caller controls rendering:

ax = result.forest()
ax.figure.savefig("forest.png", dpi=200, bbox_inches="tight")

In a script, call matplotlib.pyplot.show() explicitly when an interactive window is desired.