{"spec_id":"raincloud-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nraincloud-basic: Basic Raincloud Plot\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-05-26\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\n\n\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Reaction times (ms) across experimental conditions; Treatment B is bimodal to\n# showcase what a raincloud reveals that a box plot would hide.\nnp.random.seed(42)\ncontrol = np.random.normal(450, 60, 80)\ntreatment_a = np.random.normal(380, 50, 80)\ntreatment_b = np.concatenate([np.random.normal(350, 30, 50), np.random.normal(480, 40, 30)])\n\ndata = pd.DataFrame(\n    {\n        \"Condition\": [\"Control\"] * len(control)\n        + [\"Treatment A\"] * len(treatment_a)\n        + [\"Treatment B\"] * len(treatment_b),\n        \"Reaction Time\": np.concatenate([control, treatment_a, treatment_b]),\n    }\n)\n\norder = [\"Control\", \"Treatment A\", \"Treatment B\"]\npalette = {c: IMPRINT_PALETTE[i] for i, c in enumerate(order)}\n\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400)\n\n# Cloud — half-violin, clipped to the upper half above each category baseline\nsns.violinplot(\n    data=data,\n    x=\"Reaction Time\",\n    y=\"Condition\",\n    hue=\"Condition\",\n    palette=palette,\n    order=order,\n    hue_order=order,\n    cut=0,\n    inner=None,\n    density_norm=\"width\",\n    width=0.8,\n    linewidth=0.6,\n    bw_adjust=0.6,\n    ax=ax,\n    legend=False,\n)\nfor i, collection in enumerate(ax.collections):\n    for path in collection.get_paths():\n        v = path.vertices\n        v[v[:, 1] < i, 1] = i\n\nn_violin_collections = len(ax.collections)\n\n# Rain — jittered strip points, shifted below the baseline\nsns.stripplot(\n    data=data,\n    x=\"Reaction Time\",\n    y=\"Condition\",\n    hue=\"Condition\",\n    palette=palette,\n    order=order,\n    hue_order=order,\n    jitter=0.07,\n    size=3.5,\n    alpha=0.55,\n    edgecolor=PAGE_BG,\n    linewidth=0.3,\n    ax=ax,\n    legend=False,\n)\nfor collection in ax.collections[n_violin_collections:]:\n    offsets = collection.get_offsets()\n    offsets[:, 1] -= 0.25\n    collection.set_offsets(offsets)\n    collection.set_zorder(2)\n\n# Box plot — outlined boxes on the baseline; palette colours the outline,\n# whiskers, caps, and median in one stroke (no fragile post-hoc recolouring).\nsns.boxplot(\n    data=data,\n    x=\"Reaction Time\",\n    y=\"Condition\",\n    hue=\"Condition\",\n    palette=palette,\n    order=order,\n    hue_order=order,\n    fill=False,\n    width=0.14,\n    showfliers=False,\n    linewidth=1.4,\n    medianprops={\"linewidth\": 2.0, \"solid_capstyle\": \"butt\"},\n    ax=ax,\n    legend=False,\n    zorder=4,\n)\n\nax.set_xlabel(\"Reaction Time (ms)\", fontsize=10, labelpad=6, color=INK)\nax.set_ylabel(\"Condition\", fontsize=10, labelpad=6, color=INK)\nax.set_title(\"raincloud-basic · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", pad=10, color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT, length=3, width=0.6)\nax.grid(True, axis=\"x\", alpha=0.15, linewidth=0.5, color=INK)\nax.set_axisbelow(True)\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nax.spines[\"left\"].set_linewidth(0.6)\nax.spines[\"bottom\"].set_linewidth(0.6)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\n\nax.set_ylim(-0.55, 2.45)\n\n# Data storytelling\nax.annotate(\n    \"Bimodal distribution\\nrevealed by raincloud\",\n    xy=(425, 2.15),\n    xytext=(540, 1.55),\n    fontsize=9,\n    fontweight=\"medium\",\n    color=INK_SOFT,\n    ha=\"left\",\n    va=\"center\",\n    arrowprops={\"arrowstyle\": \"->\", \"color\": INK_MUTED, \"linewidth\": 0.7, \"connectionstyle\": \"arc3,rad=-0.2\"},\n    bbox={\"boxstyle\": \"round,pad=0.35\", \"facecolor\": ELEVATED_BG, \"edgecolor\": INK_SOFT, \"linewidth\": 0.5},\n)\n\nta_median = float(np.median(data.loc[data[\"Condition\"] == \"Treatment A\", \"Reaction Time\"]))\nctrl_median = float(np.median(data.loc[data[\"Condition\"] == \"Control\", \"Reaction Time\"]))\ndiff_ms = ctrl_median - ta_median\n\nax.annotate(\n    f\"~{diff_ms:.0f} ms faster\\nthan Control\",\n    xy=(ta_median, 1.0),\n    xytext=(290, 0.4),\n    fontsize=9,\n    fontweight=\"medium\",\n    color=INK_SOFT,\n    ha=\"center\",\n    va=\"center\",\n    arrowprops={\"arrowstyle\": \"->\", \"color\": INK_MUTED, \"linewidth\": 0.7, \"connectionstyle\": \"arc3,rad=0.25\"},\n    bbox={\"boxstyle\": \"round,pad=0.35\", \"facecolor\": ELEVATED_BG, \"edgecolor\": INK_SOFT, \"linewidth\": 0.5},\n)\n\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}