Statistical annotations
add_comparisons() and add_correlation() compute statistics with scipy and draw them as layers.
Every call also builds a structured report - descriptives, exact p-values, and effect sizes - that
save() embeds in the export metadata, so a figure carries its own analysis. Pass report=True to
print it, or save=True to write a text file.
Group comparisons
Section titled “Group comparisons”add_comparisons() classifies test= into pairwise (mannwhitneyu, ttest_ind, ttest_rel,
wilcoxon, tukey_hsd) or omnibus (anova, kruskal, friedman, alexandergovern). Here an
ANOVA with a corner label plus two post-hoc brackets. The corner label sits at the top of the plot,
so pad the y domain and lift the brackets with yStart= to keep everything clear:
import altair as altimport dysonsphere as dsfrom vega_datasets import data
ds.theme()
cars = ds.ensure_polars(data.cars()).drop_nulls(["Miles_per_Gallon"])origins = ["Europe", "Japan", "USA"]
box = alt.Chart(cars).mark_boxplot().encode( x=alt.X("Origin:N", sort=origins, title=None), # Pad the y domain so the corner label clears the stacked brackets below it. y=alt.Y("Miles_per_Gallon:Q", scale=alt.Scale(domain=[0, 75]), title="Miles per gallon"), color=alt.Color("Origin:N", legend=None),)
chart = box + ds.add_comparisons( cars, "Origin", "Miles_per_Gallon", [("Europe", "USA"), ("Japan", "USA")], test="anova", categories=origins, yStart=50,)Pairwise tests and corrections
Section titled “Pairwise tests and corrections”Give a list of pairs; brackets stack automatically. correction= applies Holm or Bonferroni
multiple-comparison adjustment.
import dysonsphere as dsfrom vega_datasets import data
ds.theme()
cars = ds.ensure_polars(data.cars()).drop_nulls(["Horsepower"])origins = ["Europe", "Japan", "USA"]
# Pairwise Mann-Whitney U with Holm correction; brackets stack automatically.chart = ds.mark_strip( cars, "Origin", "Horsepower", origins,) + ds.add_comparisons( cars, "Origin", "Horsepower", [("USA", "Europe"), ("Europe", "Japan"), ("USA", "Japan")], test="mannwhitneyu", correction="holm", categories=origins,)Label styles
Section titled “Label styles”labelStyle="asterisks" renders * / ** / *** / ns; bracketStyle="line" drops the end
ticks.
import dysonsphere as dsfrom vega_datasets import data
ds.theme()
cars = ds.ensure_polars(data.cars()).drop_nulls(["Horsepower"])origins = ["Europe", "Japan", "USA"]
# Asterisk labels (* / ** / *** / ns) and plain-line brackets.chart = ds.mark_strip( cars, "Origin", "Horsepower", origins,) + ds.add_comparisons( cars, "Origin", "Horsepower", [("USA", "Europe"), ("Europe", "Japan")], test="mannwhitneyu", correction="holm", labelStyle="asterisks", bracketStyle="line", categories=origins,)Number notation
Section titled “Number notation”notation= controls the p-value format ("scientific", "e", "power"), and sigFigs= sets the
precision.
import dysonsphere as dsfrom vega_datasets import data
ds.theme()
cars = ds.ensure_polars(data.cars()).drop_nulls(["Horsepower"])origins = ["Europe", "Japan", "USA"]
# Scientific notation for small p-values, 2 significant figures.chart = ds.mark_strip( cars, "Origin", "Horsepower", origins,) + ds.add_comparisons( cars, "Origin", "Horsepower", [("USA", "Japan")], test="ttest_ind", notation="scientific", sigFigs=2, categories=origins,)Both bracketStyle and notation also accept a dict for per-pair control (keys are pairs,
matched regardless of order); notation additionally takes a "test" key for the omnibus label:
import dysonsphere as dsfrom vega_datasets import data
ds.theme()
cars = ds.ensure_polars(data.cars()).drop_nulls(["Horsepower"])origins = ["Europe", "Japan", "USA"]
# bracketStyle and notation accept per-pair dicts (keys matched regardless of# order); the special "test" notation key styles the omnibus/test label.chart = ds.mark_strip( cars, "Origin", "Horsepower", origins,) + ds.add_comparisons( cars, "Origin", "Horsepower", [("USA", "Europe"), ("USA", "Japan")], test="mannwhitneyu", correction="holm", bracketStyle={("USA", "Japan"): "line"}, notation={("USA", "Japan"): "scientific"}, categories=origins,)Omnibus with post-hoc
Section titled “Omnibus with post-hoc”For an omnibus test, the corner label reports the omnibus result and the brackets are filled by the
matching post-hoc (Kruskal-Wallis → Dunn here). omnibusVerbose=True adds the statistic, degrees of
freedom, and effect size.
import altair as altimport dysonsphere as dsfrom vega_datasets import data
# The verbose omnibus label is long - widen the canvas so it fits.ds.theme(chartWidth=200)
cars = ds.ensure_polars(data.cars()).drop_nulls(["Miles_per_Gallon"])origins = ["Europe", "Japan", "USA"]
box = alt.Chart(cars).mark_boxplot().encode( x=alt.X("Origin:N", sort=origins, title=None), # Pad the y domain so the corner label clears the stacked brackets below it. y=alt.Y("Miles_per_Gallon:Q", scale=alt.Scale(domain=[0, 75]), title="Miles per gallon"), color=alt.Color("Origin:N"),)
chart = box + ds.add_comparisons( cars, "Origin", "Miles_per_Gallon", [("Europe", "USA"), ("Japan", "USA")], test="kruskal", omnibusVerbose=True, categories=origins, yStart=50,)Correlation
Section titled “Correlation”add_correlation() adds a coefficient, optional p-value, and (for Pearson) an OLS fit line to a
scatter. The default readout is a bare r = …; rank methods report the coefficient only.
import altair as altimport dysonsphere as dsfrom vega_datasets import data
ds.theme()
cars = ds.ensure_polars(data.cars()).drop_nulls(["Miles_per_Gallon", "Horsepower"])
scatter = alt.Chart(cars).mark_point().encode( x=alt.X("Horsepower:Q"), y=alt.Y("Miles_per_Gallon:Q", title="Miles per gallon"),)
# The default readout is a bare r = ...; Pearson also draws the OLS fit line.chart = scatter + ds.add_correlation(cars, "Horsepower", "Miles_per_Gallon")The readout is composed from independent parts, so you can show exactly what you want:
coefficient="both" gives r and r², includePvalue=True adds P, includeEquation=True adds the
fit line’s equation, and verbose=True is a shortcut that turns all of them on. The longer the
readout, the wider the chart should be (theme(chartWidth=…)):
import altair as altimport dysonsphere as dsfrom vega_datasets import data
# The three-part readout is wide - give it a wider canvas.ds.theme(chartWidth=150)
cars = ds.ensure_polars(data.cars()).drop_nulls(["Miles_per_Gallon", "Horsepower"])
scatter = alt.Chart(cars).mark_point().encode( x=alt.X("Horsepower:Q"), y=alt.Y("Miles_per_Gallon:Q", title="Miles per gallon"),)
# Compose the readout from independent parts: coefficient="both" shows r and r-squared,# includePvalue adds P. The default (coefficient="r") shows just r = ...; verbose=True is a# shortcut that turns all of them on plus the fit equation.chart = scatter + ds.add_correlation( cars, "Horsepower", "Miles_per_Gallon", coefficient="both", includePvalue=True,)Rank correlations (method="spearman" / "kendall") report only the coefficient (ρ / τ) and its
p-value - no fit line, since a straight line is not their model:
import altair as altimport dysonsphere as dsfrom vega_datasets import data
ds.theme()
cars = ds.ensure_polars(data.cars()).drop_nulls(["Miles_per_Gallon", "Weight_in_lbs"])
scatter = alt.Chart(cars).mark_point().encode( x=alt.X("Weight_in_lbs:Q", title="Weight (lbs)"), y=alt.Y("Miles_per_Gallon:Q", title="Miles per gallon"),)
# Rank correlations report the coefficient only - no fit line, since a straight# line is not their model.chart = scatter + ds.add_correlation( cars, "Weight_in_lbs", "Miles_per_Gallon", method="spearman", includePvalue=True, position="bottomLeft",)