{"spec_id":"count-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\ncount-basic: Basic Count Plot\nLibrary: letsplot 4.11.0 | Python 3.13.14\nQuality: 89/100 | Updated: 2026-08-11\n\"\"\"\n\nimport os\n\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_bar,\n    geom_hline,\n    geom_text,\n    ggplot,\n    ggsize,\n    labs,\n    scale_fill_manual,\n    scale_y_continuous,\n    theme,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\n\n# Theme tokens (see prompts/default-style-guide.md)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\nBRAND = \"#009E73\"  # Imprint palette position 1 — always first series\n# `muted` semantic anchor (theme-adaptive) de-emphasizes the non-modal bars\n# without introducing a custom hex outside the Imprint palette/anchors.\nBRAND_SOFT = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Data - raw per-observation customer satisfaction survey responses (one row\n# per respondent). Likert order is kept (Excellent -> Very Poor) rather than\n# sorted by frequency: a reader's mental model of the scale matters more\n# here than a strict count ranking.\nresponse_order = [\"Excellent\", \"Good\", \"Average\", \"Poor\", \"Very Poor\"]\ncounts = {\"Excellent\": 45, \"Good\": 78, \"Average\": 52, \"Poor\": 23, \"Very Poor\": 12}\nmodal_response = max(counts, key=counts.get)\nresponses = [r for r, n in counts.items() for _ in range(n)]\ndf = pd.DataFrame({\"Response\": pd.Categorical(responses, categories=response_order, ordered=True)})\ndf[\"Modal\"] = df[\"Response\"] == modal_response\n\ntotal = len(df)\navg_count = total / len(response_order)\n\n# Create count plot - geom_bar()'s default stat='count' tallies the raw\n# observations directly (no manual pre-aggregation). The modal response\n# (\"Good\") renders in full-strength brand green while the rest use the\n# muted anchor -- a second layer of visual emphasis beyond bar height\n# alone, without introducing a second Imprint hue or a legend.\n# Two geom_text(stat='count') layers annotate each bar with its count (bold,\n# larger) and share of the total (lighter, smaller), giving the in-bar\n# labels a deliberate typographic hierarchy instead of one flat style.\nplot = (\n    ggplot(df, aes(x=\"Response\"))\n    + geom_hline(yintercept=avg_count, linetype=\"dashed\", color=INK_SOFT, size=0.6, alpha=0.8)\n    + geom_text(x=4.6, y=avg_count, label=\"avg\", color=INK_SOFT, size=3.8, fontface=\"bold\", vjust=-0.6, hjust=0)\n    + geom_bar(aes(fill=\"Modal\"), width=0.62, color=PAGE_BG, size=0.6, show_legend=False)\n    + geom_text(\n        aes(y=\"..count..\", label=\"..count..\"),\n        stat=\"count\",\n        color=\"white\",\n        size=4.3,\n        fontface=\"bold\",\n        vjust=1,\n        nudge_y=-3.5,\n    )\n    + geom_text(\n        aes(y=\"..count..\", label=\"..sumpct..\"),\n        stat=\"count\",\n        label_format=\"{.0f}%\",\n        color=\"white\",\n        alpha=0.85,\n        size=3.4,\n        vjust=1,\n        nudge_y=-8,\n    )\n    + scale_fill_manual(values={True: BRAND, False: BRAND_SOFT}, guide=\"none\")\n    + labs(\n        x=\"Customer Satisfaction Rating\",\n        y=\"Number of Responses\",\n        title=\"count-basic · python · letsplot · anyplot.ai\",\n        caption=f\"n = {total} survey respondents\",\n    )\n    + scale_y_continuous(expand=[0, 0, 0.16, 0], limits=[0, 90])\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=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=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        axis_line=element_line(color=INK_SOFT),\n        plot_caption=element_text(size=9, color=INK_SOFT),\n    )\n    + ggsize(800, 450)\n)\n\n# Save as PNG (scale 4x for 3200 x 1800 px) and interactive HTML\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}