Ranking Values
Assign ranks to column values with rank(), choose tie-breaking methods, and create percentile bins with pd.cut().
Ranking Values is a free Pandas & NumPy Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Pandas & NumPy Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is Ranking?
Ranking assigns each value in a Series a position based on its relative magnitude — rank 1 for the smallest value, rank n for the largest (or vice versa). Unlike sorting (which reorders rows), rank() adds a new column of ordinal positions alongside the original data. Rankings are used in leaderboards, percentile computation, and certain statistical tests that require ordinal position rather than absolute values.
import pandas as pd
df = pd.DataFrame({
'player': ['Alice', 'Bob', 'Carol', 'Dave', 'Eve'],
'score': [88, 72, 95, 72, 85]
})
df['rank'] = df['score'].rank()
print(df)
# player score rank
# 0 Alice 88 4.0
# 1 Bob 72 1.5 <- tied with Dave
# 2 Carol 95 5.0
# 3 Dave 72 1.5 <- tied with Bob
# 4 Eve 85 3.0Ascending vs Descending Rank
By default, rank() gives rank 1 to the smallest value (ascending). To give rank 1 to the largest (e.g., first place in a competition), pass ascending=False. This mirrors the convention of 'first place = highest score' used in sports, leaderboards, and sales rankings.
import pandas as pd
df = pd.DataFrame({'score': [70, 95, 85, 60, 90]})
# Rank 1 = highest score
df['competition_rank'] = df['score'].rank(ascending=False)
print(df)
# score competition_rank
# 0 70 4.0
# 1 95 1.0
# 2 85 3.0
# 3 60 5.0
# 4 90 2.0Tie-Breaking Methods
When multiple rows have the same value (a tie), the method parameter decides how ranks are assigned. The options are: 'average' (default — tied rows share the average rank), 'min' (all tied rows get the lowest rank), 'max' (all get the highest), 'first' (the row appearing first gets the lower rank), and 'dense' (no gaps in rank numbers after a tie).
import pandas as pd
s = pd.Series([10, 20, 20, 30])
print('average:', s.rank(method='average').tolist()) # [1.0, 2.5, 2.5, 4.0]
print('min: ', s.rank(method='min').tolist()) # [1.0, 2.0, 2.0, 4.0]
print('max: ', s.rank(method='max').tolist()) # [1.0, 3.0, 3.0, 4.0]
print('first: ', s.rank(method='first').tolist()) # [1.0, 2.0, 3.0, 4.0]
print('dense: ', s.rank(method='dense').tolist()) # [1.0, 2.0, 2.0, 3.0]Dense Rank: No Gaps After Ties
The 'dense' method is especially useful when you want a ranking without gaps. With 'min', two entries tied for 2nd place make the next rank 4 (skipping 3). With 'dense', the next rank is always 3, maintaining a continuous sequence. Dense rank is the standard in SQL's DENSE_RANK() window function and is commonly used in leaderboards.
import pandas as pd
df = pd.DataFrame({
'name': ['A', 'B', 'C', 'D', 'E'],
'score': [100, 80, 80, 60, 60]
})
df['dense_rank'] = df['score'].rank(method='dense', ascending=False).astype(int)
print(df)
# name score dense_rank
# 0 A 100 1
# 1 B 80 2
# 2 C 80 2 <- same score, same rank
# 3 D 60 3 <- next rank is 3, not 4
# 4 E 60 3Percentile Rank with pct=True
Setting pct=True in rank() normalises the ranks to the range [0, 1], giving you a percentile rank. A value with percentile rank 0.8 means it is higher than 80% of the values in the column. This is used in finance (percentile performance), grading (score percentiles), and outlier detection.
import pandas as pd
df = pd.DataFrame({'score': [50, 70, 80, 90, 100]})
df['percentile'] = df['score'].rank(pct=True)
print(df)
# score percentile
# 0 50 0.2
# 1 70 0.4
# 2 80 0.6
# 3 90 0.8
# 4 100 1.0Ranking Within Groups
To compute ranks within each group independently (e.g., each department's salary rank), combine groupby() with rank(). Use groupby().rank() which applies the rank function group-by-group and returns a Series aligned with the original DataFrame index — perfect for adding a 'rank within group' column.
import pandas as pd
df = pd.DataFrame({
'dept': ['Eng', 'Eng', 'HR', 'HR', 'Eng'],
'name': ['Alice', 'Bob', 'Carol', 'Dave', 'Eve'],
'salary': [90000, 70000, 55000, 65000, 80000]
})
df['dept_rank'] = df.groupby('dept')['salary'].rank(
method='dense', ascending=False
).astype(int)
print(df.sort_values(['dept', 'dept_rank']))
# dept name salary dept_rank
# 0 Eng Alice 90000 1
# 4 Eng Eve 80000 2
# 1 Eng Bob 70000 3
# 3 HR Dave 65000 1
# 2 HR Carol 55000 2pd.cut() for Equal-Width Bins
pd.cut(series, bins) divides a continuous numeric column into equal-width intervals (bins) and assigns each value to its bin. This converts a continuous variable into a categorical one, useful for histograms, age groups, income brackets, or any discretisation task. The result is a Categorical Series with interval labels.
import pandas as pd
df = pd.DataFrame({'age': [5, 15, 25, 35, 45, 55, 65, 75]})
df['age_group'] = pd.cut(df['age'], bins=[0, 18, 35, 60, 100],
labels=['Child', 'Young Adult', 'Middle Age', 'Senior'])
print(df)
# age age_group
# 0 5 Child
# 1 15 Child
# 2 25 Young Adult
# 3 35 Young Adult
# 4 45 Middle Age
# 5 55 Middle Age
# 6 65 Senior
# 7 75 Seniorpd.qcut() for Equal-Frequency Bins
pd.qcut(series, q) divides values into quantile-based bins where each bin contains approximately the same number of observations. Unlike pd.cut() (equal width), pd.qcut() produces equal-size groups — useful for percentile-based segmentation like decile scores, quintile splits, or quartile groupings.
import pandas as pd
import numpy as np
np.random.seed(0)
df = pd.DataFrame({'score': np.random.randint(40, 100, 20)})
# Split into 4 equal-frequency quartiles
df['quartile'] = pd.qcut(df['score'], q=4, labels=['Q1', 'Q2', 'Q3', 'Q4'])
print(df['quartile'].value_counts().sort_index())
# Q1 5
# Q2 5
# Q3 5
# Q4 5cut() vs qcut() — Key Differences
Use pd.cut() when your bins have a natural domain meaning (e.g., age brackets 0-18, 18-35, 35-60) — the bin widths matter semantically. Use pd.qcut() when you want equal group sizes regardless of where the boundaries fall — for example, dividing customers into top/bottom quartiles. A skewed distribution will produce very unequal groups with cut() but equal groups with qcut().
import pandas as pd
import numpy as np
np.random.seed(0)
# Skewed distribution: most values near 0
skewed = pd.Series(np.random.exponential(scale=5, size=100).round())
# cut with equal width: many empty or tiny upper bins
cut_counts = pd.cut(skewed, bins=4).value_counts().sort_index()
print('cut():', cut_counts.values)
# qcut with equal frequency: always 25 per group
qcut_counts = pd.qcut(skewed, q=4).value_counts().sort_index()
print('qcut():', qcut_counts.values)Adding Rank Number as a Column
A clean pattern for producing a ranked output table is: sort descending, reset the index to 0-based, shift by 1 for human-readable 1-based ranking, and display alongside the original data. This produces a presentation-ready leaderboard with no gaps and clean rank numbers.
import pandas as pd
df = pd.DataFrame({
'team': ['Falcons', 'Eagles', 'Hawks', 'Owls'],
'wins': [12, 9, 12, 7]
})
leaderboard = (
df
.sort_values('wins', ascending=False)
.reset_index(drop=True)
)
leaderboard.index = leaderboard.index + 1
leaderboard.index.name = 'place'
print(leaderboard)Rank as a Feature for Machine Learning
Rank-transformed features are useful in machine learning because they are robust to outliers — a very large or very small value gets a rank near the extremes rather than skewing the feature distribution. Rank transformation is sometimes used as a preprocessing step before tree-based models or when the numeric scale is not meaningful but relative ordering is.
import pandas as pd
import numpy as np
df = pd.DataFrame({
'income': [30000, 50000, 70000, 1_000_000] # outlier at 1M
})
# Add rank as a feature
df['income_rank'] = df['income'].rank(pct=True)
print(df)
# income income_rank
# 0 30000 0.25
# 1 50000 0.50
# 2 70000 0.75
# 3 1000000 1.00
# The outlier has rank 1.0 — extreme but not disproportionateQuick Check
Test your understanding of ranking values in Pandas.
Lesson Recap
In this lesson you learned: rank() assigns ordinal positions to values, method='dense' avoids gaps after ties, pct=True gives percentile ranks in [0,1], and groupby().rank() computes within-group ranks. Use pd.cut() for equal-width bins and pd.qcut() for equal-frequency bins when discretising continuous variables. Next up we set and reset the DataFrame index.
Frequently asked questions
Is the “Ranking Values” lesson free?
Yes — the full text of “Ranking Values” is free to read here on the web, and the Pandas & NumPy Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Pandas & NumPy Academy course, upgrade to CoddyKit PRO.
What will I learn in “Ranking Values”?
Assign ranks to column values with rank(), choose tie-breaking methods, and create percentile bins with pd.cut(). You practise Pandas & NumPy Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Pandas & NumPy Academy?
No prior experience is required. Pandas & NumPy Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Ranking Values” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Pandas & NumPy Academy lesson?
Yes. Every Pandas & NumPy Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.