{"spec_id":"box-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nbox-basic: Basic Box Plot\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-05-28\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    as_discrete,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_boxplot,\n    geom_hline,\n    geom_text,\n    ggplot,\n    ggsave,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_fill_manual,\n    scale_y_continuous,\n    theme,\n    theme_classic,\n)\n\n\nLetsPlot.setup_html()\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\n\n# Data\nnp.random.seed(42)\ndistributions = {\n    \"Engineering\": (85000, 15000),\n    \"Marketing\": (65000, 12000),\n    \"Sales\": (70000, 20000),\n    \"HR\": (55000, 10000),\n    \"Finance\": (75000, 14000),\n}\n\ndata = []\nfor cat, (mean, std) in distributions.items():\n    n = np.random.randint(50, 100)\n    values = np.random.normal(mean, std, n)\n    outliers = np.array([mean + 2.5 * std, mean + 3.0 * std, mean + 3.5 * std])\n    values = np.concatenate([values, outliers])\n    data.extend([(cat, v) for v in values])\n\ndf = pd.DataFrame(data, columns=[\"department\", \"salary\"])\n\n# Median labels per box\nmedians = df.groupby(\"department\")[\"salary\"].median().reset_index()\nmedians.columns = [\"department\", \"median_salary\"]\nmedians[\"label\"] = medians[\"median_salary\"].apply(lambda x: f\"${x:,.0f}\")\n\n# Overall mean reference line\noverall_mean = df[\"salary\"].mean()\n\n# Insight: highest vs lowest median department\nsorted_medians = medians.sort_values(\"median_salary\")\nlow_dept = sorted_medians.iloc[0][\"department\"]\nhigh_dept = sorted_medians.iloc[-1][\"department\"]\npct_diff = (sorted_medians.iloc[-1][\"median_salary\"] - sorted_medians.iloc[0][\"median_salary\"]) / sorted_medians.iloc[\n    0\n][\"median_salary\"]\n\n# Annotation placed in HR column (lowest data range) well above its outliers (~$90k max)\nannot_df = pd.DataFrame(\n    {\n        \"department\": [\"HR\"],\n        \"y\": [overall_mean + 38000],\n        \"lbl\": [f\"Avg: ${overall_mean:,.0f}   |   {high_dept[:3]}. +{pct_diff:.0%} vs {low_dept[:3]}.\"],\n    }\n)\n\n# Plot\ntitle = \"box-basic · python · letsplot · anyplot.ai\"\n\nplot = (\n    ggplot(df, aes(x=as_discrete(\"department\", order=1, order_by=\"..middle..\"), y=\"salary\", fill=\"department\"))\n    + geom_boxplot(\n        alpha=0.85,\n        size=1.2,\n        outlier_size=2.5,\n        outlier_shape=21,\n        outlier_color=INK_SOFT,\n        width=0.78,\n        tooltips=layer_tooltips()\n        .title(\"@department\")\n        .line(\"Median|$@{..middle..}\")\n        .line(\"Q1|$@{..lower..}\")\n        .line(\"Q3|$@{..upper..}\")\n        .line(\"Min|$@{..ymin..}\")\n        .line(\"Max|$@{..ymax..}\"),\n    )\n    + scale_fill_manual(values=IMPRINT_PALETTE)\n    + geom_text(\n        aes(x=\"department\", y=\"median_salary\", label=\"label\"),\n        data=medians,\n        size=4,\n        color=INK,\n        fontface=\"bold\",\n        nudge_y=5000,\n        inherit_aes=False,\n    )\n    + geom_hline(yintercept=overall_mean, color=INK_MUTED, size=0.8, linetype=\"dashed\")\n    + geom_text(\n        aes(x=\"department\", y=\"y\", label=\"lbl\"),\n        data=annot_df,\n        size=3,\n        color=INK_MUTED,\n        fill=\"transparent\",\n        fontface=\"italic\",\n        inherit_aes=False,\n    )\n    + scale_y_continuous(format=\"${,.0f}\")\n    + labs(\n        x=\"Department\",\n        y=\"Annual Salary (USD)\",\n        title=title,\n        subtitle=\"Salary distributions by department, ordered by median\",\n    )\n    + theme_classic()\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=\"transparent\"),\n        plot_title=element_text(size=16, color=INK, face=\"bold\"),\n        plot_subtitle=element_text(size=12, color=INK_SOFT),\n        axis_title=element_text(size=12, color=INK),\n        axis_text=element_text(size=10, color=INK_SOFT),\n        axis_ticks=element_blank(),\n        axis_line=element_blank(),\n        axis_line_x=element_line(color=INK_SOFT),\n        axis_line_y=element_line(color=INK_SOFT),\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_grid_major_y=element_line(color=INK_SOFT, size=0.3),\n        panel_border=element_blank(),\n        legend_position=\"none\",\n        plot_margin=[10, 10, 10, 10],\n    )\n    + ggsize(800, 450)\n)\n\n# Save\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}