{"spec_id":"histogram-overlapping","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nhistogram-overlapping: Overlapping Histograms\nLibrary: letsplot 4.11.0 | Python 3.13.15\nQuality: 93/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_histogram,\n    geom_text,\n    geom_vline,\n    ggplot,\n    ggsave,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_fill_manual,\n    theme,\n)\n\n\nLetsPlot.setup_html()\n\n# Theme tokens (Imprint palette — theme-adaptive chrome)\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\"\nRULE = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint palette (first series always #009E73)\nCOLORS = [\"#009E73\", \"#C475FD\"]\n\n# Data - comparing response times between two experimental conditions\nnp.random.seed(42)\n\n# Control group - baseline response times (ms)\ncontrol = np.random.normal(loc=450, scale=80, size=200)\n\n# Treatment group - faster response times with intervention\ntreatment = np.random.normal(loc=380, scale=70, size=200)\n\n# Create DataFrame\ndf = pd.DataFrame(\n    {\"response_time\": np.concatenate([control, treatment]), \"group\": [\"Control\"] * 200 + [\"Treatment\"] * 200}\n)\n\nmean_control = control.mean()\nmean_treatment = treatment.mean()\n\n# Explicit bin width + shared boundary so both distributions align on identical edges\nbin_width = 20\nbin_start = np.floor(df[\"response_time\"].min() / bin_width) * bin_width\nedges = np.arange(bin_start, df[\"response_time\"].max() + bin_width, bin_width)\npeak_count = max(np.histogram(control, bins=edges)[0].max(), np.histogram(treatment, bins=edges)[0].max())\n\n# Mean-line callouts, anchored just above the tallest bar for a clear focal point\nannotations = pd.DataFrame(\n    {\n        \"x\": [mean_control, mean_treatment],\n        \"y\": [peak_count * 1.12] * 2,\n        \"label\": [f\"Control  {mean_control:.0f} ms\", f\"Treatment  {mean_treatment:.0f} ms\"],\n    }\n)\n\n# Distinctive lets-plot touch: custom tooltip content for the interactive HTML export\nbar_tooltips = layer_tooltips().line(\"@group\").line(\"Response time|^x ms\").line(\"Count|^y\")\n\n# Theme-adaptive styling\nanyplot_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_grid_major_y=element_line(color=RULE, size=0.3),\n    panel_grid_minor_y=element_blank(),\n    panel_grid_major_x=element_blank(),\n    axis_title=element_text(size=12, color=INK),\n    axis_text=element_text(size=10, color=INK_SOFT),\n    axis_line=element_line(color=INK_SOFT, size=0.3),\n    panel_border=element_blank(),\n    plot_title=element_text(size=16, color=INK),\n    plot_subtitle=element_text(size=11, color=INK_SOFT),\n    legend_background=element_rect(fill=ELEVATED_BG, color=\"\"),\n    legend_text=element_text(size=10, color=INK_SOFT),\n    legend_title=element_text(size=11, color=INK),\n    legend_position=\"top\",\n)\n\n# Overlapping histograms with aligned bins, mean reference lines, and mean-value callouts\nplot = (\n    ggplot(df, aes(x=\"response_time\", fill=\"group\"))\n    + geom_histogram(\n        alpha=0.55,\n        binwidth=bin_width,\n        boundary=bin_start,\n        position=\"identity\",\n        color=PAGE_BG,\n        size=0.3,\n        tooltips=bar_tooltips,\n    )\n    + geom_vline(xintercept=mean_control, color=COLORS[0], linetype=\"dashed\", size=1.1, alpha=0.9)\n    + geom_vline(xintercept=mean_treatment, color=COLORS[1], linetype=\"dashed\", size=1.1, alpha=0.9)\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"label\"),\n        data=annotations.iloc[[0]],\n        color=COLORS[0],\n        size=3.2,\n        hjust=0,\n        nudge_x=8,\n        fontface=\"bold\",\n    )\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"label\"),\n        data=annotations.iloc[[1]],\n        color=COLORS[1],\n        size=3.2,\n        hjust=1,\n        nudge_x=-8,\n        fontface=\"bold\",\n    )\n    + scale_fill_manual(values=COLORS)\n    + labs(\n        x=\"Response Time (ms)\",\n        y=\"Count\",\n        title=\"histogram-overlapping · python · letsplot · anyplot.ai\",\n        subtitle=f\"Intervention shifts the mean left by {mean_control - mean_treatment:.0f} ms\",\n        fill=\"Condition\",\n    )\n    + ggsize(800, 450)\n    + anyplot_theme\n)\n\n# Save as PNG (scale 4x to get 3200 × 1800 px)\nggsave(plot, filename=f\"plot-{THEME}.png\", scale=4)\n\n# Save as HTML for interactivity\nggsave(plot, filename=f\"plot-{THEME}.html\")\n\n# Move files from lets-plot-images subdirectory to current directory\nif os.path.exists(\"lets-plot-images\"):\n    for file in os.listdir(\"lets-plot-images\"):\n        src = os.path.join(\"lets-plot-images\", file)\n        if os.path.isfile(src):\n            shutil.move(src, file)\n    shutil.rmtree(\"lets-plot-images\")\n"}