2025, Nov 14 17:00

Plotting categorical frequencies in Seaborn: why histplot shows all bars as 1 and when to use barplot or countplot

Learn why Seaborn histplot shows equal-height bars with pre-aggregated counts, and how to fix it by using barplot or countplot to plot categorical frequencies

Plotting categorical frequencies from a pre-aggregated list: why histplot shows all bars as 1 and how to fix it

When you have a long list of survey responses like A through I plus blanks as None and you want a frequency chart in Seaborn, it’s easy to reach for histplot. The catch: if you pre-aggregate counts first and then feed that table to histplot, every bar ends up with height 1. The chart looks right in terms of axes, but the information is lost in the wrong plotting primitive.

Problem demonstration

The following snippet builds a frequency table from a values list using Counter and then calls histplot. The visual renders with equal-height bars because histplot expects raw observations and performs its own counting.

import matplotlib.pyplot as mpl
from collections import Counter as Tally
import seaborn as sbn
import pandas as pnd
# Sample input mirroring a larger real-world list
survey_answers = ['E', None, 'B', 'H', 'I', 'A', None, None, 'D',
                  'C', 'E', 'I', 'C', 'B', None, 'A', None, 'E',
                  'F', 'H', 'A', 'D', 'A', 'A', 'F', 'A', 'C',
                  'C', 'H', 'E', None, 'B', 'E', 'I', 'G', 'A',
                  'I', 'A', 'B', 'I']
sbn.set_theme(style="darkgrid")
freq_map = pnd.DataFrame.from_dict(Tally(survey_answers), orient='index').reset_index()
freq_map.columns = ['Choice', 'Frequency']
# Wrong tool for pre-aggregated counts: produces all bars with height 1
sbn.histplot(data=freq_map, x='Choice')
mpl.show()

Why this happens

histplot is designed to aggregate raw data for you. It bins or counts observations based on the underlying values it receives. In this setup, you’ve already summarized the data into a two-column table of categories and their frequencies. Passing that pre-aggregated table to histplot means Seaborn sees one row per category and treats each as a single observation, so every bar equals 1.

Correct approach for pre-aggregated counts

Use barplot for pre-computed frequencies and sort the index to keep the categories in alphabetical order. That way, you explicitly map categories to their counts on the y-axis.

# Build and sort the aggregated table alphabetically by category
chart_data = (
    pnd.DataFrame.from_dict(Tally(survey_answers), orient='index')
      .sort_index()
      .reset_index()
)
chart_data.columns = ['Choice', 'Frequency']
# Correct tool: barplot for pre-aggregated data
sbn.barplot(data=chart_data, x='Choice', y='Frequency')
mpl.show()

If you’re not aggregating beforehand and you have the raw list, skip the DataFrame entirely and let Seaborn handle the counting with countplot.

# Directly count from the raw list
sbn.countplot(survey_answers)
mpl.show()

Why this distinction matters

Choosing a plotting function that matches the state of your data prevents silent misinterpretations. Pre-aggregated data should be visualized with a bar chart that maps category to count; raw observations should go to functions that compute counts internally. The right choice keeps your pipeline transparent and your chart faithful to the data.

Takeaways

If you already have a frequency table, reach for barplot and pass both the category and the frequency columns, sorting by index to get alphabetical order when needed. If you only have the raw list of responses, countplot is the most direct and reliable path. A quick mental check—“am I giving the plotter raw observations or summarized counts?”—will save you from the all-bars-equal pitfall.