{"spec_id":"count-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\ncount-basic: Basic Count Plot\nLibrary: seaborn 0.13.2 | Python 3.13.14\nQuality: 93/100 | Updated: 2026-08-11\n\"\"\"\n\nimport os\nimport sys\nfrom pathlib import Path\n\n\n# Avoid shadowing by the matplotlib.py file in same directory\nscript_dir = Path(__file__).parent\nold_path = sys.path[:]\nsys.path = [p for p in sys.path if str(p) != str(script_dir)]\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.lines import Line2D\nfrom matplotlib.patches import Patch\nfrom matplotlib.ticker import PercentFormatter\n\n\nsys.path = old_path\n\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\"\nBRAND = \"#009E73\"\nCUM_LINE = \"#C475FD\"  # Imprint palette position 2 - second series (cumulative %)\n\n# Data - Survey responses about preferred programming languages\nnp.random.seed(42)\nlanguages = [\"Python\", \"JavaScript\", \"Java\", \"C++\", \"Go\", \"Rust\", \"TypeScript\", \"Ruby\"]\nweights = [0.28, 0.22, 0.15, 0.10, 0.08, 0.07, 0.06, 0.04]\nn_responses = 500\nresponses = np.random.choice(languages, size=n_responses, p=weights)\n\ndf = pd.DataFrame({\"language\": responses})\n\n# Configure seaborn with theme-adaptive styling\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.10,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\n\n# Count plot sorted by frequency (descending)\ncounts = df[\"language\"].value_counts()\norder = counts.index.tolist()\nsns.countplot(data=df, x=\"language\", order=order, color=BRAND, ax=ax)\n\n# Explicit headroom on the primary axis so the count-label collision check\n# below can reason about label position relative to the cumulative-share line.\ncount_max = counts.max()\nax.set_ylim(0, count_max * 1.15)\n\n# Pareto overlay: cumulative share of responses on a secondary axis, with the\n# classic 80% reference line to call out how few categories dominate the total.\n# twinx() is unavoidable (matplotlib/seaborn has no native dual-axis primitive),\n# but the connector itself is drawn with sns.pointplot rather than a raw\n# matplotlib .plot() call, so the categorical point-estimate machinery (order=,\n# errorbar=, native categorical positioning) stays seaborn-idiomatic instead of\n# generic.\ncum_pct = counts.cumsum() / counts.sum() * 100\nax2 = ax.twinx()\nsns.pointplot(\n    x=order,\n    y=cum_pct.to_numpy(),\n    order=order,\n    color=CUM_LINE,\n    markers=\"o\",\n    linestyles=\"-\",\n    markersize=4,\n    linewidth=2,\n    errorbar=None,\n    ax=ax2,\n)\nax2.axhline(80, color=INK_SOFT, linewidth=1, linestyle=\"--\", alpha=0.6, zorder=2)\n\n# twinx() creates a second, fully-opaque drawing layer that always paints over\n# the first, so count labels are added to ax2 (not ax) — via ax.transData for\n# positioning — to stay legible above the cumulative line rather than under it.\n# Wherever a bar's height and the cumulative-line marker land close together on\n# their respective axis scales, lift that label further above the bar so the\n# text clears the marker instead of sitting on top of it.\ncum_arr = cum_pct.to_numpy()\nfor i, count in enumerate(counts.to_numpy()):\n    count_frac = count / (count_max * 1.15)\n    cum_frac = cum_arr[i] / 105\n    y_offset = 13 if abs(count_frac - cum_frac) < 0.08 else 3\n    ax2.annotate(\n        str(count),\n        xy=(i, count),\n        xycoords=ax.transData,\n        xytext=(0, y_offset),\n        textcoords=\"offset points\",\n        ha=\"center\",\n        va=\"bottom\",\n        fontsize=8,\n        color=INK,\n        zorder=6,\n    )\nax2.set_ylim(0, 105)\nax2.yaxis.set_major_formatter(PercentFormatter())\nax2.set_ylabel(\"Cumulative Share\", fontsize=10, color=INK)\nax2.tick_params(axis=\"y\", labelsize=8, colors=INK_SOFT)\nax2.spines[\"top\"].set_visible(False)\nax2.spines[\"left\"].set_visible(False)\nax2.spines[\"right\"].set_color(INK_SOFT)\n\n# Style\nax.set_xlabel(\"Programming Language\", fontsize=10, color=INK)\nax.set_ylabel(\"Response Count\", fontsize=10, color=INK)\nax.set_title(\"count-basic · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\n\n# Subtle grid on y-axis only\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)\nax.set_axisbelow(True)\n\n# Remove top and right spines for cleaner look\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\n\nlegend_handles = [\n    Patch(facecolor=BRAND, label=\"Responses\"),\n    Line2D([0], [0], color=CUM_LINE, marker=\"o\", markersize=4, linewidth=2, label=\"Cumulative Share\"),\n]\nax.legend(\n    handles=legend_handles,\n    fontsize=8,\n    loc=\"center right\",\n    frameon=True,\n    facecolor=ELEVATED_BG,\n    edgecolor=INK_SOFT,\n    labelcolor=INK,\n)\n\nfig.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}