{"spec_id":"box-grouped","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nbox-grouped: Grouped Box Plot\nLibrary: letsplot 4.11.0 | Python 3.13.15\nQuality: 87/100 | Updated: 2026-08-18\n\"\"\"\n\nimport os\nimport shutil\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_boxplot,\n    geom_point,\n    ggplot,\n    ggsave,\n    ggsize,\n    labs,\n    position_dodge,\n    scale_fill_manual,\n    theme,\n    theme_minimal,\n)\n\n\nLetsPlot.setup_html()\n\n# Theme tokens (Imprint palette)\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\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\"]\n\n# Data: Employee performance scores by department and experience level\nnp.random.seed(42)\n\ndepartments = [\"Engineering\", \"Marketing\", \"Sales\", \"Operations\"]\nexperience_levels = [\"Junior\", \"Mid-Level\", \"Senior\"]\n\ndata = []\nfor dept in departments:\n    for exp in experience_levels:\n        n = 40\n        if exp == \"Junior\":\n            base = 50 + np.random.choice([0, 5, 10])\n            spread = 12\n        elif exp == \"Mid-Level\":\n            base = 60 + np.random.choice([0, 3, 6])\n            spread = 10\n        else:\n            base = 72 + np.random.choice([0, 2, 4])\n            spread = 8\n\n        dept_offset = {\"Engineering\": 3, \"Marketing\": 0, \"Sales\": -2, \"Operations\": 1}[dept]\n        values = np.random.normal(base + dept_offset, spread, n)\n\n        if np.random.random() > 0.5:\n            outlier_low = base - 3 * spread + np.random.randn(2) * 2\n            outlier_high = base + 3 * spread + np.random.randn(2) * 2\n            values = np.concatenate([values, outlier_low, outlier_high])\n\n        for v in values:\n            data.append({\"Department\": dept, \"Experience\": exp, \"Performance Score\": v})\n\ndf = pd.DataFrame(data)\ndf[\"Experience\"] = pd.Categorical(df[\"Experience\"], categories=experience_levels, ordered=True)\ndf[\"Department\"] = pd.Categorical(df[\"Department\"], categories=departments, ordered=True)\n\n# Per-group means overlaid as diamonds so the box (median/IQR) and the mean\n# read as two distinct summaries instead of one flat shape per group.\nmeans = df.groupby([\"Department\", \"Experience\"], observed=True)[\"Performance Score\"].mean().reset_index()\n\n# Plot\ndodge = position_dodge(width=0.7)\nplot = (\n    ggplot(df, aes(x=\"Department\", y=\"Performance Score\", fill=\"Experience\"))\n    + geom_boxplot(alpha=0.85, outlier_size=1.3, outlier_alpha=0.6, width=0.7, size=0.6, position=dodge)\n    + geom_point(\n        aes(x=\"Department\", y=\"Performance Score\", group=\"Experience\"),\n        data=means,\n        position=dodge,\n        shape=18,\n        size=3,\n        color=ELEVATED_BG,\n        inherit_aes=False,\n        show_legend=False,\n    )\n    + scale_fill_manual(values=IMPRINT)\n    + labs(\n        title=\"box-grouped · python · letsplot · anyplot.ai\",\n        x=\"Department\",\n        y=\"Performance Score\",\n        fill=\"Experience Level\",\n    )\n    + theme_minimal()\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_grid_major_x=element_blank(),\n        panel_grid_major_y=element_line(color=INK_SOFT, size=0.3),\n        panel_grid_minor_x=element_blank(),\n        panel_grid_minor_y=element_blank(),\n        plot_title=element_text(size=16, face=\"bold\", color=INK),\n        axis_title=element_text(size=12, color=INK),\n        axis_text=element_text(size=10, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_title=element_text(size=11, color=INK),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        legend_position=\"right\",\n    )\n    + ggsize(800, 450)\n)\n\n# Save\nggsave(plot, f\"plot-{THEME}.png\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\")\n\n# Move files from lets-plot-images to current directory\nshutil.move(f\"lets-plot-images/plot-{THEME}.png\", f\"plot-{THEME}.png\")\nshutil.move(f\"lets-plot-images/plot-{THEME}.html\", f\"plot-{THEME}.html\")\n"}