Grouped Violin Plot with Swarm Overlay — lets-plot

A grouped violin plot with individual data points overlaid as swarm points, showing distributions across two categorical dimensions simultaneously. Multiple violin plots are arranged by category on the x-axis and grouped by a secondary variable using color/hue, with swarm points revealing the underlying raw data. This visualization combines distribution shape visualization with complete data transparency, ideal for comparing how distributions differ across multiple factors.

Grouped Violin Plot with Swarm Overlay rendered with lets-plot

Renders

Python source (lets-plot)

""" anyplot.ai
violin-grouped-swarm: Grouped Violin Plot with Swarm Overlay
Library: letsplot 4.9.0 | Python 3.13.13
Quality: 93/100 | Updated: 2026-05-18
"""

import os

import numpy as np
import pandas as pd
from lets_plot import (
    LetsPlot,
    aes,
    element_blank,
    element_line,
    element_rect,
    element_text,
    geom_point,
    geom_violin,
    ggplot,
    ggsize,
    labs,
    position_dodge,
    position_jitterdodge,
    scale_color_manual,
    scale_fill_manual,
    theme,
    theme_minimal,
)
from lets_plot.export import ggsave


LetsPlot.setup_html()

# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"

IMPRINT = ["#009E73", "#C475FD", "#4467A3", "#BD8233"]

# Data - Drug efficacy across dosage levels and treatment groups
np.random.seed(42)

categories = ["Low Dose", "Medium Dose", "High Dose"]
groups = ["Placebo", "Treatment"]

data = []
for cat in categories:
    for grp in groups:
        if cat == "Low Dose":
            base = 3.0 if grp == "Placebo" else 4.2
            spread = 1.2 if grp == "Placebo" else 0.8
        elif cat == "Medium Dose":
            base = 3.5 if grp == "Placebo" else 6.1
            spread = 1.3 if grp == "Placebo" else 0.9
        else:  # High Dose
            base = 3.8 if grp == "Placebo" else 7.5
            spread = 1.4 if grp == "Placebo" else 1.0

        n = 45
        values = np.random.normal(base, spread, n)
        values = np.clip(values, 1, 10)

        for v in values:
            data.append({"Dosage": cat, "Group": grp, "Efficacy Score": v})

df = pd.DataFrame(data)

# Plot
plot = (
    ggplot(df, aes(x="Dosage", y="Efficacy Score", fill="Group", color="Group"))
    + geom_violin(alpha=0.5, position=position_dodge(width=0.75), size=0.6, trim=False)
    + geom_point(alpha=0.6, size=2.0, position=position_jitterdodge(jitter_width=0.1, dodge_width=0.75))
    + scale_fill_manual(values=IMPRINT[:2])
    + scale_color_manual(values=IMPRINT[:2])
    + labs(
        x="Dosage Level",
        y="Efficacy Score",
        title="violin-grouped-swarm · Python · letsplot · anyplot.ai",
        fill="Group",
        color="Group",
    )
    + theme_minimal()
    + theme(
        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
        panel_grid_major=element_line(color=INK_SOFT, size=0.2),
        panel_grid_minor=element_blank(),
        axis_title=element_text(size=20, color=INK),
        axis_text=element_text(size=16, color=INK_SOFT),
        axis_line=element_line(color=INK_SOFT, size=0.4),
        plot_title=element_text(size=24, color=INK),
        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),
        legend_text=element_text(size=16, color=INK_SOFT),
        legend_title=element_text(size=18, color=INK),
        legend_position="right",
    )
    + ggsize(1600, 900)
)

# Save
ggsave(plot, f"plot-{THEME}.png", path=".", scale=3)
ggsave(plot, f"plot-{THEME}.html", path=".")

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/violin-grouped-swarm/letsplot/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "violin-grouped-swarm",
  "language": "python",
  "library": "letsplot",
  "page": "https://anyplot.ai/violin-grouped-swarm/python/letsplot",
  "hub": "https://anyplot.ai/violin-grouped-swarm",
  "code_json": "https://api.anyplot.ai/specs/violin-grouped-swarm/letsplot/code",
  "spec_json": "https://api.anyplot.ai/specs/violin-grouped-swarm",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/violin-grouped-swarm/python/letsplot/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/violin-grouped-swarm/python/letsplot/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/violin-grouped-swarm/python/letsplot/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/violin-grouped-swarm/python/letsplot/plot-dark.html",
  "quality_score": 93.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Grouped Violin Plot with Swarm Overlay on anyplot.ai.

Other implementations