{"spec_id":"histogram-overlapping","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nhistogram-overlapping: Overlapping Histograms\nLibrary: plotnine 0.15.8 | Python 3.13.15\nQuality: 89/100 | Updated: 2026-08-18\n\"\"\"\n\nimport os\n\n# Add site-packages to path before current directory to avoid local plotnine.py shadowing\nimport site\nimport sys\n\n\nsite_packages = site.getsitepackages()\nsys.path = site_packages + [p for p in sys.path if p not in site_packages and p not in (\"\", \".\")]\n\nimport numpy as np\nimport pandas as pd\nimport plotnine as pn\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 = [\"#009E73\", \"#C475FD\", \"#4467A3\"]\n\n# Data - Response times (ms) for three user groups\nnp.random.seed(42)\n\n# New users: higher response times, more spread\nnew_users = np.random.normal(loc=450, scale=120, size=150)\n\n# Regular users: moderate response times\nregular_users = np.random.normal(loc=320, scale=80, size=200)\n\n# Power users: faster response times, tighter distribution\npower_users = np.random.normal(loc=220, scale=50, size=180)\n\n# Combine into a DataFrame, ordering groups fastest-to-slowest (Power -> Regular -> New)\n# so the legend and palette assignment tell the response-time story at a glance.\ndf = pd.DataFrame(\n    {\n        \"response_time\": np.concatenate([power_users, regular_users, new_users]),\n        \"user_group\": (\n            [\"Power Users\"] * len(power_users) + [\"Regular Users\"] * len(regular_users) + [\"New Users\"] * len(new_users)\n        ),\n    }\n)\ngroup_order = [\"Power Users\", \"Regular Users\", \"New Users\"]\ndf[\"user_group\"] = pd.Categorical(df[\"user_group\"], categories=group_order, ordered=True)\n\n# Theme-adaptive elements — L-shaped frame (axis lines, no panel border), y-axis-only grid\nanyplot_theme = pn.theme(\n    plot_background=pn.element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=pn.element_rect(fill=PAGE_BG, color=None),\n    panel_grid_major_x=pn.element_blank(),\n    panel_grid_major_y=pn.element_line(color=INK, size=0.3, alpha=0.15),\n    panel_grid_minor=pn.element_blank(),\n    axis_title=pn.element_text(color=INK, size=10),\n    axis_text=pn.element_text(color=INK_SOFT, size=8),\n    axis_line_x=pn.element_line(color=INK_SOFT, size=0.8),\n    axis_line_y=pn.element_line(color=INK_SOFT, size=0.8),\n    plot_title=pn.element_text(color=INK, size=12, face=\"bold\"),\n    legend_background=pn.element_rect(fill=ELEVATED_BG, color=INK_SOFT, size=0.8),\n    legend_text=pn.element_text(color=INK_SOFT, size=8),\n    legend_title=pn.element_text(color=INK, size=9),\n    figure_size=(8, 4.5),\n    text=pn.element_text(size=7, family=\"sans\"),\n)\n\n# Plot — per-group edge colors (instead of a uniform background edge) keep each\n# distribution separable even inside the triple-overlap zone.\nplot = (\n    pn.ggplot(df, pn.aes(x=\"response_time\", fill=\"user_group\", color=\"user_group\"))\n    + pn.geom_histogram(alpha=0.6, bins=30, position=\"identity\", size=0.6)\n    + pn.scale_fill_manual(values=IMPRINT)\n    + pn.scale_color_manual(values=IMPRINT)\n    + pn.labs(\n        x=\"Response Time (ms)\",\n        y=\"Frequency\",\n        title=\"histogram-overlapping · python · plotnine · anyplot.ai\",\n        fill=\"User Group\",\n        color=\"User Group\",\n    )\n    + pn.theme_minimal()\n    + anyplot_theme\n    + pn.guides(color=pn.guide_legend(override_aes={\"alpha\": 1}))\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}