Altair
Altair exposes a declarative grammar-of-graphics API that compiles to Vega-Lite - charts become JSON specs you can version, diff, and embed on the web.
Search across all documentation pages
Altair exposes a declarative grammar-of-graphics API that compiles to Vega-Lite - charts become JSON specs you can version, diff, and embed on the web.
Quick-reference recipe card - copy-paste ready.
import altair as alt
import pandas as pd
df = pd.read_csv("sales.csv", parse_dates=["ordered_at"])
chart = (
alt.Chart(df)
.mark_line(point=True)
.encode(x="ordered_at:T", y="sum(revenue):Q", color="region:N")
.properties(width=600, height=300, title="Revenue by region")
)
chart.save("revenue.json")When to reach for this:
+, |) with readable compositionimport altair as alt
import pandas as pd
import numpy as np
rng = np.random.default_rng(42)
df = pd.DataFrame(
{
"region": np.repeat(["East", "West"], 150),
"revenue": rng.normal(150, 35, 300).clip(20),
"units": rng.integers(1, 60, 300),
}
)
base = alt.Chart(df).encode(
x=alt.X("revenue:Q", bin=alt.Bin(maxbins=20), title="Revenue"),
color="region:N",
)
hist = base.mark_bar(opacity=0.7).properties(width=400, height=250, title="Distribution")
scatter = (
alt.Chart(df)
.mark_circle(size=60, opacity=0.5)
.encode(
x="units:Q",
y="revenue:Q",
color="region:N",
tooltip=["region", "units", "revenue"],
)
.properties(width=400, height=250, title="Units vs revenue")
)
dashboard = alt.hconcat(hist, scatter).resolve_scale(color="shared")
rule = (
alt.Chart(pd.DataFrame({"y": [150]}))
.mark_rule(color="firebrick", strokeDash=[4, 4])
.encode(y="y:Q")
)
layered = scatter + rule
dashboard.save("dashboard.json")
layered.save("scatter_target.json")What this demonstrates:
+Chart(data) + mark_* + encode channels (x, y, color, tooltip).:Q quantitative, :N nominal, :T temporal, :O ordinal.transform_filter, transform_aggregate) stay in spec.| Shorthand | Type |
|---|---|
:Q | Quantitative |
:N | Nominal (categories) |
:O | Ordered categories |
:T | Temporal |
import altair as alt
alt.data_transformers.disable_max_rows() # only when you accept large embeds
alt.renderers.enable("json") # notebook default varies by versiontransform_fold or melt. Fix: tidy to long format upstream.sort=["Jan", "Feb", ...] or :O with order.
| Alternative | Use When | Don't Use When |
|---|---|---|
| Plotly | Rich built-in interactivity | You want Vega-Lite spec portability |
| matplotlib | Mature print workflows | Declarative JSON is the deliverable |
| ggplot (plotnine) | R ggplot migration | Vega embed is the target |
| seaborn | Fast pandas EDA | Spec serialization matters |
alt.Chart(df).mark_point().encode(x="x", y="y").facet("region:N")alt.Chart(df).transform_filter(alt.datum.revenue > 100).mark_bar().encode(...)hconcat, vconcat, | and & operators layer or concat.resolve_scale aligns axes and legends.pl.DataFrame when supported or .to_pandas().rule = alt.Chart(df).mark_rule(strokeDash=[4,4]).encode(y="mean(revenue):Q")
chart + rulechart.save("out.html") when HTML renderer configured..encode(color=alt.Color("region:N", scale=alt.Scale(scheme="set2")))Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 19, 2026