Lesson 13 · Pandas Full Course
Sort and Rank Data in Pandas: sort_values, nlargest and rank | Pandas Course #13
In this lesson you'll be able to: Sort rows by one or several columns, in any direction Control where missing values land, and sort by the index Put…
- CoursePandas Full Course
- Lesson13 of 65
- FormatJupyter notebook · 42 code cells
- Data2 datasets
What you'll learn
Datasets used in this lesson
Save these next to the notebook. In Google Colab, upload them with the 📁 icon on the left first.
- titanic.csv preview58.9 KB
- gapminder.csv preview80.2 KB
📓 Full notebook
Download .ipynbPandas 13: Sorting and Ranking Data#
In this lesson you'll be able to:
- Sort rows by one or several columns, in any direction
- Control where missing values land, and sort by the index
- Put categories in a logical order instead of A to Z
- Pull top-N lists and rank values, overall and within groups Data: the Titanic passenger list and Gapminder country statistics
Warm-up: tidy names from last lesson#
In lesson 12 we renamed columns. A quick rename gets us started.
import pandas as pd
df = pd.read_csv("data/pandas/titanic.csv")
df = df.rename(columns={"Pclass": "Class", "SibSp": "Siblings"})
df[["Name", "Class", "Siblings", "Fare"]].head(3)
Sort by one column#
Who paid the least, and who paid the most? Sorting answers that instantly.
cheapest = df.sort_values("Fare")
cheapest[["Name", "Class", "Fare"]].head()
priciest = df.sort_values("Fare", ascending=False)
priciest[["Name", "Class", "Fare"]].head()
Quick check: after those two sorts, what order is
dfin now?
print(df["PassengerId"].head(3).tolist())
print(priciest["PassengerId"].head(3).tolist())
Common mistake: a typo in the column name.
df.sort_values("fare")
print([c for c in df.columns if "fare" in c.lower()])
df.sort_values("Fare").shape
Common mistake: saving the result of
inplace=True.
by_fare = df.copy()
result = by_fare.sort_values("Fare", inplace=True)
result.head()
by_fare = df.sort_values("Fare", ascending=False, ignore_index=True)
by_fare[["Name", "Fare"]].head(3)
Sort by several columns#
Break ties with a second column, and choose a direction for each.
by_class_age = df.sort_values(["Class", "Age"])
by_class_age[["Name", "Class", "Age"]].head()
mixed = df.sort_values(["Class", "Fare"], ascending=[True, False])
mixed[["Name", "Class", "Fare"]].head()
Common mistake: giving
ascendingfewer values thanby.
df.sort_values(["Class", "Fare"], ascending=[True])
fixed = df.sort_values(["Class", "Fare"], ascending=[True, False])
fixed[["Class", "Fare"]].head(3)
Your turn: list the oldest survivors first, showing name and age.
oldest_survivors = df.sort_values(["Survived", "Age"],
ascending=[False, False])
oldest_survivors[["Name", "Survived", "Age"]].head()
Missing values, the index and custom keys#
Choose where gaps go, restore the original order, and sort by a rule.
by_age = df.sort_values("Age")
by_age[["Name", "Age"]].tail(3)
gaps_first = df.sort_values("Age", na_position="first")
gaps_first[["Name", "Age"]].head(3)
restored = by_age.sort_index()
restored[["PassengerId", "Name"]].head(3)
df["Embarked"].value_counts().sort_index()
df.sort_index(axis=1).columns[:6].tolist()
by_name_len = df.sort_values("Name", key=lambda s: s.str.len())
short = by_name_len["Name"].head(3)
pd.DataFrame({"name": short, "chars": short.str.len()})
Quick check: which passenger has the longest name on board?
longest = by_name_len["Name"].tail(1).item()
print(longest)
print(len(longest), "characters")
Sort categories in a logical order#
The Titanic called at Southampton, Cherbourg, then Queenstown. A to Z gets that wrong.
route = ["S", "C", "Q"]
port_type = pd.CategoricalDtype(categories=route, ordered=True)
df["Port"] = df["Embarked"].astype(port_type)
df["Port"].dtype
df.groupby("Port", observed=True)["Fare"].mean().round(2)
by_port = df.sort_values(["Port", "Fare"], ascending=[True, False])
by_port[["Name", "Port", "Fare"]].head(3)
Your turn: sort the classes as words, Upper, Middle, Lower, using a
key.
names = {1: "Upper", 2: "Middle", 3: "Lower"}
order = {"Upper": 0, "Middle": 1, "Lower": 2}
df["Deck"] = df["Class"].map(names)
by_deck = df.sort_values("Deck", key=lambda s: s.map(order))
by_deck["Deck"].drop_duplicates().tolist()
Top-N lists with nlargest and nsmallest#
When you only need the top few rows, skip the full sort.
df.nlargest(5, "Age")[["Name", "Class", "Age"]]
df.nsmallest(3, "Fare")[["Name", "Fare"]]
print((df["Fare"] == 0).sum(), "passengers paid 0")
free = df.nsmallest(3, "Fare", keep="all")
print(len(free), "rows with keep=all")
Your turn: which 3 passengers paid the most within third class?
third = df[df["Class"] == 3]
third.nlargest(3, "Fare")[["Name", "Siblings", "Fare"]]
Rank values and handle ties#
Ranking gives every row a position number. Ties need a rule.
ages = df["Age"].head(5)
pd.DataFrame({"age": ages,
"average": ages.rank(ascending=False),
"min": ages.rank(ascending=False, method="min"),
"dense": ages.rank(ascending=False, method="dense"),
"first": ages.rank(ascending=False, method="first")})
df["Fare_rank"] = df["Fare"].rank(ascending=False, method="min")
cols = ["Name", "Fare", "Fare_rank"]
df.sort_values("Fare_rank")[cols].head()
dense = df["Fare"].rank(ascending=False, method="dense")
print(dense.max(), "dense rank levels")
print(df["Fare"].nunique(), "different fares")
df["Fare_pct"] = df["Fare"].rank(pct=True).round(2)
df[["Name", "Fare", "Fare_pct"]].head()
grp = df.groupby("Class")["Fare"]
df["Class_rank"] = grp.rank(ascending=False, method="min")
top1 = df[df["Class_rank"] == 1]
print(len(top1), "passengers share a rank of 1")
top1.drop_duplicates("Class")[["Name", "Class", "Fare"]]
Your turn: rank ages within each sex. Who is the oldest woman?
age_rank = df.groupby("Sex")["Age"].rank(ascending=False,
method="min")
women = df[(df["Sex"] == "female") & (age_rank == 1)]
women[["Name", "Class", "Age"]]
Quick check: is rank 1 in third class a cheap or an expensive ticket overall?
top1.drop_duplicates("Class")[["Class", "Fare", "Fare_pct"]]
Mini-project: who climbed the life expectancy table?#
Rank 142 countries by life expectancy in 1952 and 2007. Who rose furthest?
gap = pd.read_csv("data/pandas/gapminder.csv")
wide = gap.pivot(index="country", columns="year", values="lifeExp")
life = wide[[1952, 2007]]
life.sort_values(2007, ascending=False).head(3).round(1)
ranks = life.rank(ascending=False, method="min").astype(int)
ranks["climb"] = ranks[1952] - ranks[2007]
ranks.sort_values(2007).head(3)
ranks.nlargest(5, "climb")
ranks.nsmallest(5, "climb")
top = ranks.nlargest(10, "climb")
cont = gap.drop_duplicates("country").set_index("country")
cont.loc[top.index, "continent"].value_counts()
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(8, 4))
top["climb"].sort_values().plot.barh(ax=ax, color="seagreen")
ax.set_title("Biggest climbs in life expectancy rank, 1952-2007")
ax.set_xlabel("Places gained")
ax.set_ylabel("Country")
plt.show()
latest = gap[gap["year"] == 2007].copy()
by_cont = latest.groupby("continent")["lifeExp"]
latest["cont_rank"] = by_cont.rank(ascending=False, method="min")
leaders = latest[latest["cont_rank"] == 1]
leaders[["continent", "country", "lifeExp"]].sort_values("lifeExp")
Finding: 8 of the 10 biggest climbers are in Asia; the biggest fallers are in southern Africa.
Recap#
sort_valuesreturns a new, sorted copy;ascendingsets the direction- Pass lists to
byandascendingto sort on several columns na_position,sort_indexandkeycontrol gaps, order and rules- Ordered categoricals sort in a logical order, not A to Z
nlargest/nsmallestfor top lists;rankfor positions and ties
Challenges#
- Rank Gapminder countries by GDP per person within each continent in 2007.
- Sort the Titanic by
Port, thenClass, thenAgeoldest first. - Which 5 countries had the largest population in 1952 and in 2007?
What's next#
Lesson 14: Advanced Slicing and Cross-Section Operations. Sorted data is ready for slicing by label ranges and index levels.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



