2. Aesthetics & scales
Column mappings versus literals, controlling axes, and everything about ticks. Every snippet on this page is executed on every build — the figures are its output.
Mapped vs. literal aesthetics
Name a column inside aes(...) and the
aesthetic is mapped — plotlet builds a scale and a
legend entry per level. Pass the same aesthetic as a bare kwarg and it
is a literal that applies to every mark. The spelling decides:
aes(color="species") maps the column,
color="#534AB7" is just that color — even if a
column shares the name.
import math
raw = pt.load_dataset("penguins")
keep = [i for i in range(len(raw["species"]))
if not math.isnan(raw["flipper_length_mm"][i])]
df = {k: [raw[k][i] for i in keep]
for k in ("species", "flipper_length_mm", "body_mass_g")}
left = pt.chart(df, aes(x="flipper_length_mm", y="body_mass_g"),
title="mapped", legend=True,
data_width=240, data_height=180)
left.add_scatter(aes(color="species"), size=2.5, alpha=0.7)
right = pt.chart(df, aes(x="flipper_length_mm", y="body_mass_g"),
title="literal", data_width=240, data_height=180)
right.add_scatter(color="#534AB7", size=2.5, alpha=0.5)
c = left | right
Size and style channels
Beyond position and color, columns can drive marker
size — the radius in px (aes(size=...), with
sizes=(lo, hi) on the artist to bound the range) —
and marker shape
(aes(style=...)). Channels compose — one column
each.
import random
rng = random.Random(2)
n = 36
df = {"x": [rng.uniform(0, 10) for _ in range(n)],
"y": [rng.uniform(0, 10) for _ in range(n)],
"mass": [rng.uniform(5, 50) for _ in range(n)],
"group": [["alpha", "beta", "gamma"][i % 3] for i in range(n)]}
c = pt.chart(df, aes(x="x", y="y", size="mass",
color="group", style="group"),
title="color + size + style", xlabel="x", ylabel="y",
legend=True, data_width=400, data_height=240)
c.add_scatter(sizes=(2, 8))
Categorical order
Categorical axes default to first-seen order.
xscale("category", order=[...]) pins the order you
mean.
tips = pt.load_dataset("tips")
c = pt.chart(tips, aes(x="day", y="tip"), ylabel="tip ($)")
c.xscale("category", order=["Thur", "Fri", "Sat", "Sun"])
c.add_boxplot()
Log scales
xscale("log") /
yscale("log") switch an axis to log10. The
power10 tick formatter writes exponents properly.
xs = [1, 3, 10, 30, 100, 300, 1000, 3000, 10000]
df = {"x": xs, "y": [x ** 0.5 for x in xs]}
c = pt.chart(df, aes(x="x", y="y"), xlabel="dose", ylabel="response",
gridlines=True)
c.xscale("log")
c.xticks(format="power10")
c.add_line()
c.add_scatter(size=3)
Limits
Axes autoscale to the data with a small expansion
so extremes don’t sit on the frame. xlim= /
ylim= (as chart kwargs or method calls) pin exact
ranges.
import math
xs = [i * 0.2 for i in range(60)]
df = {"x": xs, "y": [0.5 + 0.6 * math.sin(x) for x in xs]}
c = pt.chart(df, aes(x="x", y="y"),
title="ylim=(0, 1) clamps the frame",
ylim=(0, 1), data_width=380, data_height=160)
c.add_line()
Tick control
xticks / yticks take a
list of positions (empty list hides the axis entirely), plus
rotation=, side= (“top” /
“right”), direction="in", and
marks=False / labels=False to hide marks or
labels independently.
import math
xs = [i * 0.2 for i in range(40)]
df = {"x": xs, "y": [math.sin(x) for x in xs]}
c = pt.chart(df, aes(x="x", y="y"), data_width=380, data_height=170,
title="ticks: explicit positions, rotated, right side")
c.add_line()
c.xticks([0, math.pi, 2 * math.pi], rotation=45)
c.yticks(side="right")
Tick formatters
Named formatters — comma,
money, percent, si,
scientific, power10 — attach per axis
with format=. Register your own with
pt.register_formatter.
df = {"quarter": ["Q1", "Q2", "Q3", "Q4"],
"revenue": [1_200_000, 1_850_000, 1_600_000, 2_400_000]}
c = pt.chart(df, aes(x="quarter", y="revenue"), title="revenue",
data_width=340, data_height=180)
c.add_bar(fill="#4C72B0")
c.yticks(format="si")
Gridlines and spines
gridlines=True draws the dotted grid.
c.spines(top=False, right=False) drops frame sides for the
open look.
import math
xs = [i * 0.2 for i in range(40)]
df = {"x": xs, "y": [math.sin(x) for x in xs]}
c = pt.chart(df, aes(x="x", y="y"), gridlines=True,
data_width=380, data_height=170,
title="open frame + grid")
c.spines(top=False, right=False)
c.add_line()