Q&A library Python

How do I calculate each row's percentage of the total in pandas?

Python Beginner groupbypercentageshare

Short answer

Divide the column by its own sum for a share of the overall total, or by a group total from transform for a share within each group. Multiply by 100 for a percentage.

The short version

Divide the column by its own sum to get each row's share of the overall total. To get each row's share of its group's total, divide by a group total produced with groupby(...).transform("sum"). Multiply by 100 if you want a percentage rather than a proportion.

Share of the overall total

import pandas as pd

orders = pd.DataFrame({
    "city": ["Leeds", "Leeds", "Bath"],
    "amount": [5, 9, 6],
})

orders["share"] = orders["amount"] / orders["amount"].sum()
orders["percent"] = (orders["share"] * 100).round(1)
print(orders["percent"].tolist())   # [25.0, 45.0, 30.0]

The percentages sum to 100, as they should.

Share within each group

This is the more common real need: what proportion of its own city's total does each order represent.

group_total = orders.groupby("city")["amount"].transform("sum")
orders["share_of_city"] = orders["amount"] / group_total

transform("sum") returns the group total aligned to every original row, so the division lines up correctly. Within each city, these shares sum to 1.

Why transform and not agg

Using agg (or plain groupby().sum()) gives one row per city, indexed by city, which does not align with your original rows. Assigning it back produces NaN or a misalignment. transform exists precisely to broadcast a group-level value back onto every row, which is what a per-row share requires.

The zero-denominator trap

If a total is zero, the division produces infinity or NaN rather than an error, and those values then propagate silently through anything downstream.

orders["share"] = orders["amount"] / group_total.replace(0, pd.NA)

Replacing a zero denominator with a missing value makes the undefined result explicit as NaN, which you can then handle deliberately rather than discovering an inf in a chart.

Formatting for display

Round only at the point of display, not in the stored data, so that rounding errors do not accumulate through further calculations.

orders["percent"] = (orders["share"] * 100).round(1)

Be aware that rounded percentages sometimes sum to 99.9 or 100.1, which is normal and worth a footnote rather than a fudge.

Percentages of a crosstab

For a table of shares by two categories, crosstab does it in one call with normalize, which is cleaner than dividing by hand.

pd.crosstab(orders["city"], orders["amount"] > 5, normalize="index")

Enjoying these?

Go from piecing together answers to real fluency.

Our courses take you from looking things up to doing the work with confidence, step by step on real data.

Browse the courses