Use pd.crosstab() with its normalize argument to calculate percentages instead of counts. Choose normalize="index" for percentages within each row, normalize="columns" for percentages within each column, or normalize="all" for each cell’s share of the whole table. Pandas returns proportions such as 0.25; multiply by 100 if you need numeric values on a 0–100 scale.
Choose the denominator that answers your question
A crosstab can show several different kinds of percentage. The table’s rows and columns may look identical in each case, but the denominator changes what each cell means. Pandas documents these normalization options in its crosstab API reference and cross-tabulation guide.
As an Amazon Associate I earn from qualifying purchases.
| Setting | Denominator | Interpretation |
|---|---|---|
normalize="index" |
The total for each row | Within each row category, how observations are distributed across the columns. |
normalize="columns" |
The total for each column | Within each column category, how observations are distributed across the rows. |
normalize="all" or normalize=True |
The total across the entire table | Each cell’s share of all observations. |
For example, if rows represent customer groups and columns represent outcomes, row normalization answers “what outcomes occurred within each group?” Column normalization answers “which groups contributed to each outcome?” Overall normalization answers “what share of all records is in this group-and-outcome combination?” State the denominator in the table title, labels, or explanation so readers do not mistake one conditional percentage for another.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Create row, column, and overall percentages
Assuming df is a pandas DataFrame with categorical columns named group and outcome, use the named normalization option that matches the denominator you want:
#1 Best Overall
import pandas as pd
# Distribution of outcomes within each group
row_pct = pd.crosstab(df["group"], df["outcome"], normalize="index")
# Distribution of groups within each outcome
column_pct = pd.crosstab(df["group"], df["outcome"], normalize="columns")
# Share of the full dataset in each group-and-outcome cell
overall_share = pd.crosstab(df["group"], df["outcome"], normalize="all")
With row normalization, each nonempty row sums to 1; with column normalization, each nonempty column sums to 1; with overall normalization, the table’s values sum to 1. These are proportions, not numbers from 0 to 100. The named strings make the selected denominator explicit in code; the API also accepts numeric and boolean forms.
Convert proportions to numeric percentages
If the next calculation or export needs percentage values on a 0–100 scale, multiply the normalized DataFrame by 100:
Rank #2
row_pct_100 = row_pct.mul(100)
For instance, a proportion of 0.25 becomes 25.0. This changes the stored numeric scale; it does not add a percent sign. If the values will be presented to readers, label them as percentages and make the denominator clear.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Add totals with margins
Set margins=True to include an All row and column. Use margins_name to choose a clearer label, such as Total:
table = pd.crosstab(
df["group"],
df["outcome"],
normalize="index",
margins=True,
margins_name="Total",
)
Pandas normalizes margin values too. Their meaning depends on the selected normalization, so inspect the resulting totals and confirm they match the denominator you intend to report.
Know when a crosstab is counting versus aggregating
With no values argument, pd.crosstab() produces a frequency table. If you supply values, you must also supply aggfunc; pandas then aggregates those values for each category combination. That is not automatically a percentage of observations. Before describing an aggregate as a percentage, define the numerator and denominator that make it meaningful.
For workflows centered on numeric aggregation or reshaping rather than a simple frequency table, pandas.pivot_table may be a better fit.
Recommended Free Tools
Check missing data and category behavior
- Missing categories: Decide whether missing values should count as a category before interpreting the percentages. The
dropnaargument defaults toTrue; the API describes it as excluding columns whose entries are all NA. This is distinct from choosing a normalization denominator. - Unobserved categories: Categorical inputs may include categories with no observed instances, which can affect the shape of the output. Check the table before assuming every displayed category has records.
- Unexpectedly empty output: Pandas documents that an empty DataFrame can result when the inputs have no overlapping indexes. Check that the input Series or columns align and that the categories are what you expect.
These behaviors are described in the pandas.crosstab API reference.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




