EDA and Feature Engineeringpandas 3.0 · scikit-learn 1.9 · seaborn 0.13 · Python 3.12+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
72 small wins to finish your pathNext lesson →

Count plots and bar plots in seaborn

sns.countplot counts the rows of a category itself and draws the bars, while sns.barplot draws bars from numbers you have already worked out, and both take their columns as keyword arguments only.

Last updated: 12 Oct, 2026 · pandas 3.0 · seaborn 0.13 · matplotlib 3.11

Answering a question with groupby produced a frame of counts and pandas drew it. seaborn draws the same thing with a legend, a colour per category and a count it can work out for itself, which is why most charts in the wild are seaborn charts.

A seaborn bar plot, figure size and hue · from the EDA and Feature Engineering video · 35:45 to 39:32

Syntax: both functions take data= and then the column names as x=, y= and hue=. A list of colours goes in palette=, and a palette needs a hue, so pass legend=False when the hue repeats the x column.

python
sns.countplot(data=df, x='Rating text')              # seaborn counts the rows
sns.barplot(data=frame, x='band', y='count')         # you supply the numbers
sns.barplot(data=frame, x='band', y='count',
            hue='colour', palette=['red', 'green'])  # a colour per hue value
sns.countplot(data=df, x='c', hue='c', legend=False) # palette with no extra legend

Loading the Zomato data and seaborn

Every example here starts from the merged Zomato frame. The zip recipe is the one Reading a CSV that is not UTF-8 built, and the join on the country code is the one Merging a lookup table built. Run this block first, then the example under it.

python
import io, zipfile, urllib.request
import pandas as pd, seaborn as sns
import matplotlib
matplotlib.use('svg')
import matplotlib.pyplot as plt

url = ("https://raw.githubusercontent.com/krishnaik06/"
       "5-Days-Live-EDA-and-Feature-Engineering/main/Zomatodataset.zip")
zf = zipfile.ZipFile(io.BytesIO(urllib.request.urlopen(url).read()))
with zf.open('zomato.csv') as fh:
    df = pd.read_csv(fh, encoding='latin-1')
with zf.open('Country-Code.xlsx') as fh:
    codes = pd.read_excel(fh)
df = pd.merge(df, codes, on='Country Code', how='left')

The positional call that used to work

Until seaborn 0.12 the columns could be passed as the first two positional arguments, so older code reads sns.barplot('band', 'count', data=frame). The signature now takes data first, so the old line hands data twice and raises. It is the most common error when running a notebook from that era.

ExamplePositional columns raise today
import pandas as pd
import seaborn as sns

frame = pd.DataFrame({'band': ['Poor', 'Average'], 'count': [186, 3737]})
sns.barplot('band', 'count', data=frame)

The fix is to name them: sns.barplot(data=frame, x='band', y='count'). The same change applies to countplot, boxplot and the rest of the axes level functions.

Drawing counts you worked out yourself

barplot expects one row per bar, with the label in one column and the height in another. The groupby that produced the rating bands gives exactly that, one row per aggregate rating. hue='Rating color' colours each bar by the band it belongs to, and dodge=False keeps the bars in one row rather than splitting them per hue.

ExampleOne bar per aggregate rating, coloured by its band
ratings = (df.groupby(['Aggregate rating', 'Rating color'])
             .size().reset_index().rename(columns={0: 'Rating Count'}))
print(ratings.head(4).to_string(index=False))
print("bars to draw:", len(ratings))

colours = {'White': 'blue', 'Red': 'red', 'Orange': 'orange',
           'Yellow': 'yellow', 'Green': 'green', 'Dark Green': 'darkgreen'}
fig, ax = plt.subplots(figsize=(10, 4))
sns.barplot(data=ratings, x='Aggregate rating', y='Rating Count',
            hue='Rating color', palette=colours, dodge=False, ax=ax)
ax.set_title('How many restaurants hold each aggregate rating')
fig.savefig('chart.svg')
A seaborn bar plot with one bar per aggregate rating from 0.0 to 4.9, coloured by rating band: a blue bar of 2148 at 0.0 stands alone, the orange Average bars peak around 3.1 and 3.2, and the dark green Excellent bars at the right are short.

What the coloured bars show that the band chart did not

  • The blue bar at 0.0 is a column of its own. 2,148 restaurants with no rating sit at zero, separated from every rated restaurant by a gap from 0.1 to 1.7.
  • The rated restaurants form one hump. It rises from 1.8, peaks in the orange Average band and falls away through green to dark green.
  • The colours are the data's own. Rating color is a column in the table, so the palette maps a real category to a real colour rather than decorating the chart.

The hump is not a bell curve. It is one hump with a long tail and a spike standing outside it, which is the kind of shape Distributions with histograms, KDE and box plots measures properly.

Letting seaborn do the counting

countplot takes the raw frame and counts the rows per category itself, so no groupby is needed. order= fixes the order of the bars, which otherwise follows the order the values appear in the data. Passing hue the same column as x gives one colour per bar, and legend=False drops the legend that would then repeat the axis labels.

Examplecountplot counts the rows itself
order = ['Not rated', 'Poor', 'Average', 'Good', 'Very Good', 'Excellent']
print(df['Rating text'].value_counts()[order].to_string())

fig, ax = plt.subplots(figsize=(7, 4))
sns.countplot(data=df, x='Rating text', order=order,
              hue='Rating text', hue_order=order, legend=False, ax=ax)
ax.set_title('Restaurants per rating band, counted by seaborn')
fig.savefig('chart.svg')
A seaborn count plot of the six rating bands in rating order, each a different colour: Not rated 2148, Poor 186, Average 3737, Good 2100, Very Good 1079 and Excellent 301.

The bars are the same six numbers the first chart of this part drew with pandas. What seaborn adds is the counting, the ordering and the colour per category; what it costs is a dependency and a signature that changed.

countplot vs barplot

countplotbarplot
You supplythe raw frame and one columna frame of labels and heights
Bar height isthe number of rowsthe mean of y per x, or the single value you give it
Error barsnonedrawn by default; pass errorbar=None to remove them
Order of barsorder= or the order found in the dataorder= or the order of the rows
Use it forhow many rows per categorya number you have already summarised

The error bars are the surprise in barplot: given several rows per x value it averages them and draws a confidence interval. One row per x value, as above, leaves nothing to average and no interval is drawn.

Where seaborn is the better choice

  • A category coloured by a second category. hue does in one argument what an unstack and a grouped bar chart do in three lines.
  • A chart for a report. The default theme, axis labels and legend are closer to publishable than the matplotlib defaults.
  • Counting without summarising first. countplot on a raw column is the fastest look at a categorical feature's balance, which is what What imbalance does to a model starts from.
Watch out. Passing palette= without a hue warns in seaborn 0.13 and stops working in 0.14. Assign the same column to hue and pass legend=False to keep the colours and lose the duplicate legend. The same release removed positional x and y, so every seaborn call in old code needs its arguments named.
Try it yourself
  • Remove dodge=False from the bar plot and read what happens to the bar widths.
  • Draw sns.countplot(data=df, x='Price range') and write the one sentence it supports.
  • Add hue='Has Online delivery' to the count plot of Price range and read which price range delivers most.

Every expert started right here.