Basic Count Plot — Seaborn

A count plot displays the frequency of observations in each category of a categorical variable using vertical bars. Unlike a basic bar chart that requires pre-computed values, a count plot automatically counts occurrences from raw data. This makes it ideal for quick exploratory analysis of categorical distributions without manual aggregation.

Basic Count Plot rendered with Seaborn

Renders

Python source (Seaborn)

""" anyplot.ai
count-basic: Basic Count Plot
Library: seaborn 0.13.2 | Python 3.13.14
Quality: 93/100 | Updated: 2026-08-11
"""

import os
import sys
from pathlib import Path


# Avoid shadowing by the matplotlib.py file in same directory
script_dir = Path(__file__).parent
old_path = sys.path[:]
sys.path = [p for p in sys.path if str(p) != str(script_dir)]

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from matplotlib.lines import Line2D
from matplotlib.patches import Patch
from matplotlib.ticker import PercentFormatter


sys.path = old_path


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
BRAND = "#009E73"
CUM_LINE = "#C475FD"  # Imprint palette position 2 - second series (cumulative %)

# Data - Survey responses about preferred programming languages
np.random.seed(42)
languages = ["Python", "JavaScript", "Java", "C++", "Go", "Rust", "TypeScript", "Ruby"]
weights = [0.28, 0.22, 0.15, 0.10, 0.08, 0.07, 0.06, 0.04]
n_responses = 500
responses = np.random.choice(languages, size=n_responses, p=weights)

df = pd.DataFrame({"language": responses})

# Configure seaborn with theme-adaptive styling
sns.set_theme(
    style="ticks",
    rc={
        "figure.facecolor": PAGE_BG,
        "axes.facecolor": PAGE_BG,
        "axes.edgecolor": INK_SOFT,
        "axes.labelcolor": INK,
        "text.color": INK,
        "xtick.color": INK_SOFT,
        "ytick.color": INK_SOFT,
        "grid.color": INK,
        "grid.alpha": 0.10,
        "legend.facecolor": ELEVATED_BG,
        "legend.edgecolor": INK_SOFT,
    },
)

# Plot
fig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)

# Count plot sorted by frequency (descending)
counts = df["language"].value_counts()
order = counts.index.tolist()
sns.countplot(data=df, x="language", order=order, color=BRAND, ax=ax)

# Explicit headroom on the primary axis so the count-label collision check
# below can reason about label position relative to the cumulative-share line.
count_max = counts.max()
ax.set_ylim(0, count_max * 1.15)

# Pareto overlay: cumulative share of responses on a secondary axis, with the
# classic 80% reference line to call out how few categories dominate the total.
# twinx() is unavoidable (matplotlib/seaborn has no native dual-axis primitive),
# but the connector itself is drawn with sns.pointplot rather than a raw
# matplotlib .plot() call, so the categorical point-estimate machinery (order=,
# errorbar=, native categorical positioning) stays seaborn-idiomatic instead of
# generic.
cum_pct = counts.cumsum() / counts.sum() * 100
ax2 = ax.twinx()
sns.pointplot(
    x=order,
    y=cum_pct.to_numpy(),
    order=order,
    color=CUM_LINE,
    markers="o",
    linestyles="-",
    markersize=4,
    linewidth=2,
    errorbar=None,
    ax=ax2,
)
ax2.axhline(80, color=INK_SOFT, linewidth=1, linestyle="--", alpha=0.6, zorder=2)

# twinx() creates a second, fully-opaque drawing layer that always paints over
# the first, so count labels are added to ax2 (not ax) — via ax.transData for
# positioning — to stay legible above the cumulative line rather than under it.
# Wherever a bar's height and the cumulative-line marker land close together on
# their respective axis scales, lift that label further above the bar so the
# text clears the marker instead of sitting on top of it.
cum_arr = cum_pct.to_numpy()
for i, count in enumerate(counts.to_numpy()):
    count_frac = count / (count_max * 1.15)
    cum_frac = cum_arr[i] / 105
    y_offset = 13 if abs(count_frac - cum_frac) < 0.08 else 3
    ax2.annotate(
        str(count),
        xy=(i, count),
        xycoords=ax.transData,
        xytext=(0, y_offset),
        textcoords="offset points",
        ha="center",
        va="bottom",
        fontsize=8,
        color=INK,
        zorder=6,
    )
ax2.set_ylim(0, 105)
ax2.yaxis.set_major_formatter(PercentFormatter())
ax2.set_ylabel("Cumulative Share", fontsize=10, color=INK)
ax2.tick_params(axis="y", labelsize=8, colors=INK_SOFT)
ax2.spines["top"].set_visible(False)
ax2.spines["left"].set_visible(False)
ax2.spines["right"].set_color(INK_SOFT)

# Style
ax.set_xlabel("Programming Language", fontsize=10, color=INK)
ax.set_ylabel("Response Count", fontsize=10, color=INK)
ax.set_title("count-basic · python · seaborn · anyplot.ai", fontsize=12, fontweight="medium", color=INK)
ax.tick_params(axis="both", labelsize=8, colors=INK_SOFT)

# Subtle grid on y-axis only
ax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)
ax.set_axisbelow(True)

# Remove top and right spines for cleaner look
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["left"].set_color(INK_SOFT)
ax.spines["bottom"].set_color(INK_SOFT)

legend_handles = [
    Patch(facecolor=BRAND, label="Responses"),
    Line2D([0], [0], color=CUM_LINE, marker="o", markersize=4, linewidth=2, label="Cumulative Share"),
]
ax.legend(
    handles=legend_handles,
    fontsize=8,
    loc="center right",
    frameon=True,
    facecolor=ELEVATED_BG,
    edgecolor=INK_SOFT,
    labelcolor=INK,
)

fig.tight_layout()
plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG)

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/count-basic/seaborn/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "count-basic",
  "language": "python",
  "library": "seaborn",
  "page": "https://anyplot.ai/count-basic/python/seaborn",
  "hub": "https://anyplot.ai/count-basic",
  "code_json": "https://api.anyplot.ai/specs/count-basic/seaborn/code",
  "spec_json": "https://api.anyplot.ai/specs/count-basic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/count-basic/python/seaborn/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/count-basic/python/seaborn/plot-dark.png",
  "quality_score": 93.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Basic Count Plot on anyplot.ai.

Other implementations