Menu

Tutorial15 min read

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

By Francesco VillasmuntaUpdated June 12, 2026
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.

No spam. Unsubscribe anytime.
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

SituationBetter choice
Distribution may be bimodalViolin — box plots hide multiple peaks
Audience wants quartiles and outliersBox plot
Small sample (n < 20)Box or dot plot — density is unreliable
Two subgroups per categorySplit violin

Common mistakes and fixes

  • Density extends past real data — set cut=0 so 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 hue to the same column and pass legend=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 Plotivy
Tags:#violin plot#seaborn#matplotlib#python#distribution

Found this helpful? Share it with your network.

FV
Francesco Villasmunta

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 author

Visualize your own data

Apply the techniques from this article to your own datasets. Upload CSV, Excel, or paste data directly.

Start Analyzing - Free