{"spec_id":"violin-split","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nviolin-split: Split Violin Plot\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-08\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom matplotlib.patches import Patch\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Okabe-Ito palette\nCOLOR_CONTROL = \"#009E73\"  # Position 1 - brand green\nCOLOR_TREATMENT = \"#C475FD\"  # Position 2 - vermillion\n\n# Data - Test score distributions for control vs treatment groups across school levels\nnp.random.seed(42)\n\nschool_levels = [\"Elementary\", \"Middle School\", \"High School\", \"College\"]\nn_per_group = 180\n\ndata = {\"category\": [], \"value\": [], \"split_group\": []}\n\n# Generate realistic test score distributions\nlevel_params = {\n    \"Elementary\": {\"control_mean\": 72, \"control_std\": 12, \"treatment_mean\": 76, \"treatment_std\": 11},\n    \"Middle School\": {\"control_mean\": 68, \"control_std\": 14, \"treatment_mean\": 73, \"treatment_std\": 13},\n    \"High School\": {\"control_mean\": 65, \"control_std\": 16, \"treatment_mean\": 71, \"treatment_std\": 14},\n    \"College\": {\"control_mean\": 70, \"control_std\": 11, \"treatment_mean\": 75, \"treatment_std\": 10},\n}\n\nfor level in school_levels:\n    params = level_params[level]\n\n    # Control group - normal distribution\n    control_scores = np.random.normal(params[\"control_mean\"], params[\"control_std\"], n_per_group)\n    control_scores = np.clip(control_scores, 20, 100)\n\n    # Treatment group - slightly right-skewed\n    treatment_scores = np.random.normal(params[\"treatment_mean\"], params[\"treatment_std\"], n_per_group)\n    treatment_scores = np.clip(treatment_scores, 20, 100)\n\n    data[\"category\"].extend([level] * (n_per_group * 2))\n    data[\"value\"].extend(list(control_scores) + list(treatment_scores))\n    data[\"split_group\"].extend([\"Control\"] * n_per_group + [\"Treatment\"] * n_per_group)\n\n# Create figure\nfig, ax = plt.subplots(figsize=(16, 9), facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Create split violins\npositions = np.arange(len(school_levels))\n\nfor i, level in enumerate(school_levels):\n    # Get data for this school level\n    mask_control = [\n        j\n        for j, (c, g) in enumerate(zip(data[\"category\"], data[\"split_group\"], strict=False))\n        if c == level and g == \"Control\"\n    ]\n    mask_treatment = [\n        j\n        for j, (c, g) in enumerate(zip(data[\"category\"], data[\"split_group\"], strict=False))\n        if c == level and g == \"Treatment\"\n    ]\n\n    control_vals = [data[\"value\"][j] for j in mask_control]\n    treatment_vals = [data[\"value\"][j] for j in mask_treatment]\n\n    # Create violin for control (left side)\n    vp_control = ax.violinplot(\n        [control_vals], positions=[i], widths=0.8, showmeans=False, showmedians=False, showextrema=False\n    )\n\n    # Clip to left half\n    for body in vp_control[\"bodies\"]:\n        m = np.mean(body.get_paths()[0].vertices[:, 0])\n        body.get_paths()[0].vertices[:, 0] = np.clip(body.get_paths()[0].vertices[:, 0], -np.inf, m)\n        body.set_facecolor(COLOR_CONTROL)\n        body.set_edgecolor(INK_SOFT)\n        body.set_linewidth(1.5)\n        body.set_alpha(0.8)\n\n    # Create violin for treatment (right side)\n    vp_treatment = ax.violinplot(\n        [treatment_vals], positions=[i], widths=0.8, showmeans=False, showmedians=False, showextrema=False\n    )\n\n    # Clip to right half\n    for body in vp_treatment[\"bodies\"]:\n        m = np.mean(body.get_paths()[0].vertices[:, 0])\n        body.get_paths()[0].vertices[:, 0] = np.clip(body.get_paths()[0].vertices[:, 0], m, np.inf)\n        body.set_facecolor(COLOR_TREATMENT)\n        body.set_edgecolor(INK_SOFT)\n        body.set_linewidth(1.5)\n        body.set_alpha(0.8)\n\n    # Add quartile markers\n    q1_c, med_c, q3_c = np.percentile(control_vals, [25, 50, 75])\n    q1_t, med_t, q3_t = np.percentile(treatment_vals, [25, 50, 75])\n\n    # Control side (left) - median and quartiles\n    ax.hlines(med_c, i - 0.28, i - 0.02, colors=INK, linewidth=3, zorder=3)\n    ax.hlines([q1_c, q3_c], i - 0.18, i - 0.02, colors=INK, linewidth=1.5, zorder=3)\n\n    # Treatment side (right) - median and quartiles\n    ax.hlines(med_t, i + 0.02, i + 0.28, colors=INK, linewidth=3, zorder=3)\n    ax.hlines([q1_t, q3_t], i + 0.02, i + 0.18, colors=INK, linewidth=1.5, zorder=3)\n\n# Styling\nax.set_xticks(positions)\nax.set_xticklabels(school_levels, fontsize=16, color=INK_SOFT)\nax.set_xlabel(\"School Level\", fontsize=20, color=INK)\nax.set_ylabel(\"Test Score\", fontsize=20, color=INK)\nax.set_title(\"violin-split · matplotlib · anyplot.ai\", fontsize=24, fontweight=\"medium\", color=INK)\nax.tick_params(axis=\"both\", labelsize=16, colors=INK_SOFT)\n\n# Spine styling\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nfor s in (\"left\", \"bottom\"):\n    ax.spines[s].set_color(INK_SOFT)\n\n# Grid - subtle y-axis only\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK_SOFT)\nax.set_axisbelow(True)\n\n# Legend\nlegend_elements = [\n    Patch(facecolor=COLOR_CONTROL, edgecolor=INK_SOFT, alpha=0.8, label=\"Control\"),\n    Patch(facecolor=COLOR_TREATMENT, edgecolor=INK_SOFT, alpha=0.8, label=\"Treatment\"),\n]\nleg = ax.legend(handles=legend_elements, fontsize=16, loc=\"upper right\", framealpha=0.9)\nleg.get_frame().set_facecolor(ELEVATED_BG)\nleg.get_frame().set_edgecolor(INK_SOFT)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=300, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}