How to Make a Violin Plot in Python (Seaborn & Matplotlib Guide)

A violin plot shows the full distribution of a variable across groups by mirroring a kernel density estimate around a central axis. Where a box plot collapses the data to five summary numbers, a violin reveals shape — skew, multiple peaks, and where the data actually concentrates. That makes it the right choice when distribution shape carries the scientific message. This guide builds one in seaborn (the fast path) and matplotlib (the precise path), and covers the options that matter for real figures.
New to the idea? Start with what is a violin plot, then come back here for the code.
Density, not bins
The width at any height is a kernel density estimate — wider means more data concentrated there.
Reveals multimodality
Two bulges mean two peaks — the exact structure a box plot would hide behind a single median.
cut=0 for honesty
By default the density extends past your data. Set cut=0 to stop it at the real min and max.
1. A basic violin plot in seaborn
Seaborn plots violins directly from a tidy DataFrame. Pass the grouping column to both x and hue with legend=False to color each group without a redundant legend. Notice how group B's two bulges expose a bimodal distribution that a box plot would have flattened.
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
rng = np.random.default_rng(42)
df = pd.DataFrame({
"score": np.concatenate([
rng.normal(70, 8, 120),
np.concatenate([rng.normal(60, 4, 60), rng.normal(82, 4, 60)]), # bimodal
rng.normal(75, 12, 120),
]),
"group": np.repeat(["A", "B", "C"], 120),
})
fig, ax = plt.subplots(figsize=(6.2, 4.6))
sns.violinplot(data=df, x="group", y="score", hue="group",
palette=["#6b7280", "#7c3aed", "#ec4899"], legend=False, ax=ax)
ax.set(xlabel="", ylabel="Score")
sns.despine()
plt.tight_layout()2. Control the inside of the violin
The inner argument decides what is drawn inside each violin: "box" for a mini box plot, "quart" for quartile lines, "point" or "stick" for raw observations, or None for a clean shape. Pair it with cut=0 so the density does not imply values beyond your data range.
import seaborn as sns
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(6.2, 4.6))
sns.violinplot(data=df, x="group", y="score", hue="group", legend=False,
inner="box", # mini box plot inside each violin
cut=0, # do not extend density past the data range
palette="mako", ax=ax)
ax.set(xlabel="", ylabel="Score")
sns.despine()
plt.tight_layout()3. Overlay the raw data points
For small samples, a smooth density can over-promise. Set inner=None and overlay a strip plot so reviewers see the actual observations behind the shape. This is the most defensible form of a violin plot for experimental data.
Try it
Try it now: compare your groups with the right chart
Generate box, violin, or bar charts directly from your dataset and choose the clearest visual for your paper.
Generate comparison charts →Newsletter
Get a weekly Python plotting tip
One concise tip each week for cleaner, faster scientific figures. Built for researchers who publish.
import seaborn as sns
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(6.2, 4.6))
sns.violinplot(data=df, x="group", y="score", hue="group", legend=False,
inner=None, palette="Greys", cut=0, ax=ax)
sns.stripplot(data=df, x="group", y="score", color="#7c3aed",
size=2.5, jitter=0.12, alpha=0.5, ax=ax)
ax.set(xlabel="", ylabel="Score")
sns.despine()
plt.tight_layout()4. Split violins for two-group comparison
When every category has exactly two subgroups (placebo vs treatment, male vs female), a split violin puts one distribution on each half of the same shape. This doubles the information density without cluttering the axis. Use split=True with a two-level hue.
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
rng = np.random.default_rng(7)
n = 150
df2 = pd.DataFrame({
"value": np.concatenate([
rng.normal(50, 8, n), rng.normal(54, 8, n), # site 1
rng.normal(60, 8, n), rng.normal(72, 8, n), # site 2
]),
"site": np.repeat(["Site 1", "Site 2"], 2 * n),
"arm": np.tile(np.repeat(["Placebo", "Treatment"], n), 2),
})
fig, ax = plt.subplots(figsize=(6.6, 4.6))
sns.violinplot(data=df2, x="site", y="value", hue="arm", split=True,
gap=0.1, inner="quart", palette=["#6b7280", "#7c3aed"], ax=ax)
ax.set(xlabel="", ylabel="Response")
ax.legend(title="", frameon=False)
sns.despine()
plt.tight_layout()5. Pure matplotlib (no seaborn)
If you cannot add seaborn as a dependency, matplotlib has its own ax.violinplot(). It takes a list of arrays and returns the artist collections, so you style the bodies yourself. Use showmedians=True to mark the center.
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(42)
data = [rng.normal(loc, 8, 200) for loc in (70, 66, 78)]
labels = ["A", "B", "C"]
colors = ["#6b7280", "#7c3aed", "#ec4899"]
fig, ax = plt.subplots(figsize=(6.2, 4.6))
parts = ax.violinplot(data, showmedians=True)
for body, color in zip(parts["bodies"], colors):
body.set_facecolor(color)
body.set_alpha(0.65)
ax.set_xticks(range(1, len(labels) + 1))
ax.set_xticklabels(labels)
ax.set_ylabel("Score")
ax.spines[["top", "right"]].set_visible(False)
plt.tight_layout()Violin plot vs box plot: which to use
| Situation | Better choice |
|---|---|
| Distribution may be bimodal | Violin — box plots hide multiple peaks |
| Audience wants quartiles and outliers | Box plot |
| Small sample (n < 20) | Box or dot plot — density is unreliable |
| Two subgroups per category | Split violin |
Common mistakes and fixes
- Density extends past real data — set
cut=0so the violin stops at the observed min and max. - Violin on tiny samples — KDE invents smooth shape from few points. Below ~20 per group, overlay raw points or switch to a box plot.
- Over-smoothed or spiky shape — tune
bw_adjust(smaller is bumpier, larger is smoother) to match the data. - Redundant legend — when coloring by the x variable, set
hueto the same column and passlegend=False. - No explanation of width — state in the caption that width encodes estimated density, since not all readers know the convention.
Frequently asked questions
What does the width of a violin plot mean?
Width at a given value is proportional to the estimated probability density there: wider sections contain more data. Unlike a histogram, the estimate is smooth (kernel density estimation) rather than binned, which is why violins reveal subtle multimodality.
How do I add a box plot inside the violin?
In seaborn, pass inner="box". This draws a miniature box plot (median and IQR) inside each violin, combining summary statistics with the full density in a single glyph.
When should I avoid a violin plot?
Avoid violins for very small samples, where the density estimate is unstable and can mislead. Prefer a box plot or a dot plot, or overlay the raw points so the shape is anchored to real data.
Upload your data and type "violin plot of score by group with the raw points overlaid and cut at the data range," and Plotivy writes the seaborn or matplotlib code for you — styling and journal export included.
Generate a violin plot in PlotivyRelated chart guides
Apply this tutorial directly in the chart gallery with ready-to-run prompts and examples.
Technique guides scientists read next
scipy.signal.find_peaks guide
Tune prominence and width parameters for robust peak extraction.
Savitzky-Golay smoothing
Reduce noise while preserving peak shape and position.
PCA visualization workflow
Move from high-dimensional measurements to interpretable components.
ANOVA with post-hoc brackets
Add statistically correct pairwise significance annotations.
Found this helpful? Share it with your network.
Experimental Physicist & Photonics Researcher
Hands-on experience in silicon photonics, semiconductor fabrication (DRIE/ICP-RIE), optical simulation, and data-driven analysis. Built Plotivy to help researchers focus on discoveries instead of data struggles.
More about the authorVisualize your own data
Apply the techniques from this article to your own datasets. Upload CSV, Excel, or paste data directly.