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 →

Pie and bar charts with a top-N trim

A pie chart shows how one whole is divided between a few categories, and a top-N trim keeps only the largest few of them, which makes the chart readable and changes what its percentages mean.

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

Counting categories with value_counts produced a Series of counts with the labels in its index. Both chart types here are drawn straight from that Series, and both have a trap in them.

A pie chart, the top-N trim and the observation · from the EDA and Feature Engineering video · 19:23 to 24:21

Syntax: ax.pie(values, labels=..., autopct=...) draws a pie from a list of numbers. autopct='%1.2f%%' writes each slice's percentage with two decimals. Series.plot.bar(ax=...) draws the same numbers as bars.

python
counts = df['City'].value_counts()      # a Series, biggest first
ax.pie(counts.values, labels=counts.index)          # every category
ax.pie(counts.values[:5], labels=counts.index[:5],  # the five biggest
       autopct='%1.2f%%')
counts.head(5).plot.bar(ax=ax)           # the same five as bars

Loading the Zomato data

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
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')

Drawing every category, and why it fails

The honest first attempt is to give the pie every category. Zomato's table covers 15 countries, and 14 of them are small. The chart is drawn correctly and is of no use: the small slices collapse into slivers and their labels overlap into a grey smear.

ExampleFifteen slices, one of which is 90 percent of the pie
counts = df['Country'].value_counts()
print("slices to draw:", len(counts))
print("smallest four counts:", counts.tail(4).to_dict())

fig, ax = plt.subplots(figsize=(6, 5))
ax.pie(counts.values, labels=counts.index)
ax.set_title('Records per country, all 15 countries')
fig.savefig('chart.svg')
A pie chart of all 15 countries in which India fills almost the whole circle and the other fourteen countries are slivers whose labels crowd together on the right side.

Canada is 4 rows, and Singapore, Qatar and Sri Lanka are 20 each. No circle can show a slice of 4 next to India's 8,652. The chart is not wrong; the question was wrong, because it asked for 15 answers from a picture that can carry about five.

Trimming to the top five

Slicing the Series with head(5) keeps the five biggest groups. Here the question moves to cities, where the trim is the difference between a readable chart and a smear: the table has 141 cities. autopct writes the percentage inside each slice.

ExampleFive slices with their percentages
cities = df['City'].value_counts().head(5)
print("cities in the table:", df['City'].nunique())
print(cities.to_string())
print()
print("share of these five cities, in percent:")
print((cities / cities.sum() * 100).round(2).to_string())

fig, ax = plt.subplots(figsize=(6, 5))
ax.pie(cities.values, labels=cities.index, autopct='%1.2f%%')
ax.set_title('Share of the five busiest cities')
fig.savefig('chart.svg')
A pie chart of the five busiest cities with New Delhi filling 68.87 percent of the circle, Gurgaon 14.07 percent, Noida 13.59 percent, Faridabad 3.16 percent and Ghaziabad 0.31 percent.

What the percentages on a trimmed pie mean

The slices read 68.87, 14.07, 13.59, 3.16 and 0.31, and they add up to 100. They are shares of the five cities drawn, not of the dataset, because pie divides each value by the sum of the values it was given. New Delhi is 68.87 percent of these five cities and 57.30 percent of all 9,551 rows. Both numbers are right and they answer different questions.

ExampleThe same five counts against two denominators
cities = df['City'].value_counts().head(5)
print("share of the five drawn, in percent:")
print((cities / cities.sum() * 100).round(2).to_string())
print()
print("share of all", len(df), "rows, in percent:")
print((cities / len(df) * 100).round(2).to_string())
print()
print("rows left out of the chart:", int(len(df) - cities.sum()))
  • New Delhi is 68.87 percent of the trim and 57.30 percent of the table. Say which denominator a percentage uses whenever the chart does not show every row.
  • 1,604 rows are not in the chart. They are the other 136 cities, and a reader cannot tell from the pie that they exist.
  • A trimmed pie needs a title that says it is trimmed. The title above names the five busiest cities, which is the claim the chart can support.

The same counts as bars

A bar chart needs no trim to stay readable and it keeps a scale, so a reader can see that New Delhi is five times Gurgaon rather than guessing from an angle. The cost is that the shares are no longer written on the chart.

ExampleFive counts as bars, on a scale
cities = df['City'].value_counts().head(5)

fig, ax = plt.subplots(figsize=(7, 4))
cities.plot.bar(ax=ax, color='#9370DB')
ax.set_ylabel('restaurants')
ax.set_title('Restaurants in the five busiest cities')
print("tallest bar:", int(cities.max()), "   shortest bar:", int(cities.min()))
fig.savefig('chart.svg')
A bar chart of the five busiest cities: New Delhi towers at 5473 restaurants, Gurgaon 1118, Noida 1080, Faridabad 251 and Ghaziabad 25, read against a numeric axis.

Pie chart vs bar chart

Pie chartBar chart
Readsshares of one wholeamounts on a scale
Categories it can carryabout fivetwenty or more
Shows the actual countsno, percentagesyes, on the axis
Needs a top-N trimalmost alwaysrarely
Breaks whenthe categories are many or nearly equalthe labels are long, so use plot.barh()
Good fortwo or three shares in a reportcomparing counts and spotting a dominant group

Where the trim is the right move

  • A column with a long tail. 141 cities, 1,825 cuisine listings: the top five or ten carry the story and the rest is noise.
  • A chart for a slide. Five slices with their percentages is a chart someone can read in two seconds.
  • Before encoding a high cardinality column. The same top-N idea becomes a feature engineering technique in One hot encoding with many categories.
Watch out. A top-N trim silently changes the denominator of every percentage on the chart, because pie normalises whatever values it is handed. A slice labelled 68.87 percent on a trimmed pie is not 68.87 percent of the dataset. Either put the trim in the title, or add a slice for the rest with counts.head(5).tolist() + [counts[5:].sum()].
Try it yourself
  • Change head(5) to head(3) in the city pie and read how the percentages move.
  • Add a slice for everything else: build the values as list(cities.values) + [len(df) - cities.sum()] with a label of 'other cities', and compare the percentages with the trimmed pie's.
  • Draw the cuisine counts as a pie with 10 slices and decide whether the horizontal bar chart was the better answer.

Slow is fine. Stopping is the only problem.