{"spec_id":"box-grouped","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nbox-grouped: Grouped Box Plot\nLibrary: matplotlib 3.11.1 | Python 3.13.15\nQuality: 92/100 | Updated: 2026-08-18\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\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# Imprint palette positions 1-3 (three subcategories)\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\"]\n\n# Data - Employee performance scores across departments and experience levels\nnp.random.seed(42)\n\ncategories = [\"Sales\", \"Engineering\", \"Marketing\", \"Support\"]\nsubcategories = [\"Junior\", \"Mid-Level\", \"Senior\"]\n\n# Generate realistic performance data with varying distributions per department\ndata = {}\nfor cat_idx, cat in enumerate(categories):\n    data[cat] = {}\n    for sub_idx, sub in enumerate(subcategories):\n        # Vary base performance by department (Sales lower, Support higher)\n        dept_offset = cat_idx * 5\n        base = 55 + sub_idx * 12 + dept_offset\n        variance = 15 - sub_idx * 3\n        n_points = np.random.randint(30, 60)\n        scores = np.random.normal(base, variance, n_points)\n        # Add outliers to some groups\n        if np.random.random() > 0.6:\n            outliers = np.random.choice([base - 25, base + 25], size=np.random.randint(1, 3))\n            scores = np.concatenate([scores, outliers])\n        data[cat][sub] = np.clip(scores, 0, 100)\n\n# Order departments by overall median score, best to worst, for a clearer narrative\ncategories = sorted(categories, key=lambda cat: -np.median(np.concatenate(list(data[cat].values()))))\n\n# Create plot\ntitle = \"box-grouped · python · matplotlib · anyplot.ai\"\ntitle_fontsize = round(12 * 67 / len(title)) if len(title) > 67 else 12\n\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Calculate positions for grouped boxes\nn_categories = len(categories)\nn_subcategories = len(subcategories)\nbox_width = 0.25\ngroup_gap = 0.4\n\n# Plot boxes for each subcategory, marking the mean alongside the median\nfor sub_idx, sub in enumerate(subcategories):\n    positions = []\n    box_data = []\n    for cat_idx, cat in enumerate(categories):\n        pos = cat_idx * (n_subcategories * box_width + group_gap) + sub_idx * box_width\n        positions.append(pos)\n        box_data.append(data[cat][sub])\n\n    bp = ax.boxplot(\n        box_data,\n        positions=positions,\n        widths=box_width * 0.8,\n        patch_artist=True,\n        showfliers=True,\n        showmeans=True,\n        flierprops={\"marker\": \"o\", \"markerfacecolor\": IMPRINT_PALETTE[sub_idx], \"markersize\": 6.5, \"alpha\": 0.7},\n        medianprops={\"color\": INK, \"linewidth\": 1.5},\n        meanprops={\n            \"marker\": \"D\",\n            \"markerfacecolor\": PAGE_BG,\n            \"markeredgecolor\": INK,\n            \"markersize\": 7,\n            \"markeredgewidth\": 1,\n        },\n        whiskerprops={\"color\": INK_SOFT, \"linewidth\": 1},\n        capprops={\"color\": INK_SOFT, \"linewidth\": 1},\n        boxprops={\"linewidth\": 1},\n    )\n\n    # Color the boxes with the Imprint palette\n    for patch in bp[\"boxes\"]:\n        patch.set_facecolor(IMPRINT_PALETTE[sub_idx])\n        patch.set_alpha(0.85)\n        patch.set_edgecolor(INK_SOFT)\n\n# Set x-axis tick positions and labels\ncenter_positions = [\n    cat_idx * (n_subcategories * box_width + group_gap) + (n_subcategories - 1) * box_width / 2\n    for cat_idx in range(n_categories)\n]\nax.set_xticks(center_positions)\nax.set_xticklabels(categories, fontsize=8, color=INK)\n# Sharpen the best-to-worst narrative: bold the top-performing department as a focal point\nax.get_xticklabels()[0].set_fontweight(\"bold\")\n\n# Labels and title\nax.set_xlabel(\"Department\", fontsize=10, color=INK)\nax.set_ylabel(\"Performance Score (0-100)\", fontsize=10, color=INK)\nax.set_title(title, fontsize=title_fontsize, fontweight=\"medium\", color=INK)\n\n# Tick params\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT)\n\n# Legend (diamond marker explains the mean; box color explains experience level)\nlegend_patches = [\n    plt.Rectangle((0, 0), 1, 1, facecolor=IMPRINT_PALETTE[i], edgecolor=INK_SOFT, alpha=0.85)\n    for i in range(len(subcategories))\n]\nleg = ax.legend(legend_patches, subcategories, title=\"Experience Level\", loc=\"upper right\", fontsize=8)\nif leg:\n    leg.get_frame().set_facecolor(ELEVATED_BG)\n    leg.get_frame().set_edgecolor(INK_SOFT)\n    leg.get_title().set_color(INK_SOFT)\n    leg.get_title().set_fontsize(8)\n    plt.setp(leg.get_texts(), color=INK_SOFT)\n\n# Grid (y-axis only, subtle)\nax.yaxis.grid(True, alpha=0.12, linewidth=0.8, color=INK)\nax.set_axisbelow(True)\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    ax.spines[s].set_linewidth(1)\n\n# Y-axis limits — extra headroom above the data ceiling so the legend never crowds\n# whiskers/fliers, even on data regenerations with taller spreads\nax.set_ylim(0, 122)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}