Scatter and line plots with semantics

seaborn · Essentials · 10 min

Showing a relationship split by group in one call, with legend included, is seaborn's core productivity win.

sns.scatterplot and sns.lineplot are the relational plots. Beyond x and y, they accept three semantic mappings:

  • hue: color. Categorical columns get a qualitative palette; numeric columns get a sequential colormap.
  • style: marker shape (scatter) or dash pattern (line), always categorical.
  • size: marker size or line width, usually numeric; tune the range with sizes=(20, 200).

Mapping the same column to both hue and style gives redundant encoding, which keeps the chart readable in grayscale.

The order of categories in colors and legend follows the data unless you pass hue_order=[...], or make the column a pandas Categorical with ordered categories. Pin the order explicitly in reports so colors do not shift between charts.

lineplot sorts by x before connecting points (sort=True), so unsorted input is fine. If there are several y values at the same x, it aggregates them, drawing the mean with a confidence band; that behavior is covered in the aggregation lesson.

legend="brief" (default for numeric hue) shows a sample of values; "full" lists every value; False hides it. Move it later with sns.move_legend(ax, "upper left", bbox_to_anchor=(1, 1)).

Large scatter plots suffer from overplotting; use alpha=0.3, smaller s=, or switch to a 2D histogram (sns.histplot(x=..., y=...)).

Example

import matplotlib
matplotlib.use("Agg")
import seaborn as sns
import pandas as pd
import numpy as np

rng = np.random.default_rng(0)
n = 120
df = pd.DataFrame({
    "area": rng.uniform(30, 150, n),
    "city": rng.choice(["Oslo", "Rome", "Lima"], n),
    "rooms": rng.integers(1, 6, n),
})
df["price"] = df["area"] * 4 + df["rooms"] * 20 + rng.normal(0, 40, n)

ax = sns.scatterplot(data=df, x="area", y="price", hue="city", style="city",
                     size="rooms", sizes=(20, 160),
                     hue_order=["Lima", "Oslo", "Rome"], alpha=0.8)
sns.move_legend(ax, "upper left", bbox_to_anchor=(1, 1))
texts = [t.get_text() for t in ax.get_legend().get_texts()]
print(texts[:4])            # ['city', 'Lima', 'Oslo', 'Rome']
ax.figure.savefig("homes.png", bbox_inches="tight")

Remember

  • hue, style and size add up to three extra variables to a scatter or line plot
  • Fix category order with hue_order or an ordered Categorical
  • Use sns.move_legend to reposition seaborn legends

Watch out for

  • Mapping a high-cardinality column (hundreds of ids) to hue, producing an unreadable legend.

Check yourself

  1. You map the same 'species' column to both hue and style. What is the benefit?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: seaborn.pydata.org/tutorial/relational.html