Lesson 10
Violin Plots
Combine a box plot with a density curve in sns.violinplot, split each violin by a second group, and choose what is drawn inside with the inner argument.
A box plot hides the shape inside the box: two very different distributions can have the same five numbers. A violin plot keeps the shape. Each side is a density curve, mirrored, and a thin box plot sits inside.
A first violin
Where the violin is wide, many rows have that value; where it is thin, few do. The dark bar inside is the interquartile range and the white dot is the median, just like a box plot.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
rng = np.random.default_rng(26)
study = pd.DataFrame({
"track": rng.choice(["Python", "Data", "Web"], 120),
"mode": rng.choice(["online", "classroom"], 120),
"week": rng.integers(1, 7, 120),
})
study["hours"] = (rng.uniform(1, 6, 120) + study["week"] * 0.8).round(1)
study["score"] = (35 + study["hours"] * 4.5 + rng.normal(0, 7, 120)).clip(0, 100).round()
sns.violinplot(data=study, x="track", y="score", order=["Python", "Data", "Web"])
plt.show()
print(study.groupby("track")["score"].median())Output:
track
Data 68.0
Python 65.0
Web 63.0
Name: score, dtype: float64What goes inside
inner chooses the middle mark: "box" is the default, "quart" draws three quartile lines, "point" and "stick" draw every row, and None leaves the violin empty.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
rng = np.random.default_rng(26)
study = pd.DataFrame({
"track": rng.choice(["Python", "Data", "Web"], 120),
"mode": rng.choice(["online", "classroom"], 120),
"week": rng.integers(1, 7, 120),
})
study["hours"] = (rng.uniform(1, 6, 120) + study["week"] * 0.8).round(1)
study["score"] = (35 + study["hours"] * 4.5 + rng.normal(0, 7, 120)).clip(0, 100).round()
fig, axes = plt.subplots(1, 4, figsize=(14, 3.5), sharey=True)
for ax, inner in zip(axes, ["box", "quart", "stick", None]):
sns.violinplot(data=study, x="mode", y="score", inner=inner, ax=ax)
ax.set_title(f"inner={inner!r}")
plt.show()
print(study.groupby("mode")["score"].quantile([0.25, 0.5, 0.75]).unstack())Output:
0.25 0.50 0.75
mode
classroom 59.00 66.0 74.0
online 55.25 65.0 70.0Split violins
With a two-level hue and split=True, each violin shows one group on the left half and the other on the right, which makes the comparison direct. inner="quart" keeps the quartiles on both halves.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
rng = np.random.default_rng(26)
study = pd.DataFrame({
"track": rng.choice(["Python", "Data", "Web"], 120),
"mode": rng.choice(["online", "classroom"], 120),
"week": rng.integers(1, 7, 120),
})
study["hours"] = (rng.uniform(1, 6, 120) + study["week"] * 0.8).round(1)
study["score"] = (35 + study["hours"] * 4.5 + rng.normal(0, 7, 120)).clip(0, 100).round()
sns.violinplot(data=study, x="track", y="score", hue="mode", split=True, inner="quart")
plt.show()
print(study.pivot_table(index="track", columns="mode", values="score", aggfunc="median"))Output:
mode classroom online
track
Data 68.0 68.0
Python 63.0 66.5
Web 66.5 63.0Tame the edges and widths
Like kdeplot, violins can reach past the data; cut=0 stops them at the smallest and largest values. density_norm="count" makes bigger groups wider, so the Web track, with the most rows, stands out.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
rng = np.random.default_rng(26)
study = pd.DataFrame({
"track": rng.choice(["Python", "Data", "Web"], 120),
"mode": rng.choice(["online", "classroom"], 120),
"week": rng.integers(1, 7, 120),
})
study["hours"] = (rng.uniform(1, 6, 120) + study["week"] * 0.8).round(1)
study["score"] = (35 + study["hours"] * 4.5 + rng.normal(0, 7, 120)).clip(0, 100).round()
sns.violinplot(data=study, x="track", y="score", cut=0, density_norm="count")
plt.show()
print(study["track"].value_counts())Output:
track
Web 51
Python 35
Data 34
Name: count, dtype: int64With fewer than about twenty rows per group, the curve is mostly guesswork. Use a box plot or draw the points instead.
Key takeaways
-
A violin is a mirrored density curve with a small box plot inside
-
Wide means common, thin means rare
-
inner= picks the middle mark; split=True halves a two-level hue
-
cut=0 stops at the data; density_norm="count" widens bigger groups
-
Previous: Box Plots
Loading playground…
Check your understanding
Violin Plots Quiz
What does a violin plot add to a box plot?
1 / 10