Lesson 6 · Pandas Full Course
Explore a New Dataset in Pandas Step by Step (Hands-on Project) | Pandas Course #6
In this lesson you'll be able to: Build a DataFrame from your own lists and extend it Run a first-look checklist on an unfamiliar dataset Add, filter and…
- CoursePandas Full Course
- Lesson6 of 65
- Video23 min
- FormatJupyter notebook · 45 code cells
- Data1 dataset
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.
📓 Full notebook
Download .ipynbPandas 06: Hands-on - Creating and Exploring DataFrames#
In this lesson you'll be able to:
- Build a DataFrame from your own lists and extend it
- Run a first-look checklist on an unfamiliar dataset
- Add, filter and sort columns to answer plain questions
- Turn a hunch into a checked finding with real numbers Dataset: restaurant tips (data/pandas/tips.csv)
Warm-up: attributes and a new column#
Lesson 05 used attributes and column arithmetic. Try them on tips.
import pandas as pd
tips = pd.read_csv("data/pandas/tips.csv")
print(tips.shape, tips.size)
print(tips.columns.tolist())
tips["bill_after_tip"] = tips["total_bill"] + tips["tip"]
tips[["total_bill", "tip", "bill_after_tip"]].head(3)
Step 1: build a DataFrame from your own lists#
Problem: copy a few bills into a small table you can analyse.
first = tips.head(4)
log = pd.DataFrame({
"day": first["day"].to_list(),
"size": first["size"].to_list(),
"tip": first["tip"].to_list(),
})
log
log["per_person"] = (log["tip"] / log["size"]).round(2)
log
Your turn#
Add a column group that is True when 3 or more people sat together.
log["group"] = log["size"] >= 3
log[["size", "tip", "group"]]
extra = tips.loc[[4], ["day", "size"]]
log = pd.concat([log, extra], ignore_index=True)
log
Common mistake: a column name with the wrong capital letter
log["Size"].sum()
print(log.columns.tolist())
print(log["size"].sum())
Step 2: a first-look checklist#
Problem: understand an unfamiliar file before trusting it.
- Size, peek, types, gaps, and one random sample
tips = tips.drop(columns="bill_after_tip")
tips.head()
tips.info()
print(tips.isna().sum().sum())
tips.nunique()
tips.sample(5, random_state=42)
Quick check: which day has the most bills in this data?
tips["day"].value_counts()
tips["time"].value_counts()
Step 3: summarise the numbers and categories#
Problem: describe a typical bill, tip and party in a few lines.
tips.describe().round(2)
tips.describe(exclude="number")
tips["size"].value_counts().sort_index()
tips["smoker"].value_counts(normalize=True).round(3)
Your turn#
What is the median tip, and the largest tip ever left?
print(tips["tip"].median())
print(tips["tip"].max())
import matplotlib.pyplot as plt
ax = tips["tip"].plot(kind="hist", bins=20, figsize=(7, 4))
ax.set_title("Tips left on 244 restaurant bills")
ax.set_xlabel("Tip (dollars)")
ax.set_ylabel("Number of bills")
plt.show()
Step 4: add columns that answer questions#
Problem: a $3 tip on $10 is not the same as $3 on $50.
tips["tip_pct"] = tips["tip"] / tips["total_bill"] * 100
tips[["total_bill", "tip", "tip_pct"]].head().round(1)
tips["per_person"] = tips["total_bill"] / tips["size"]
print(round(tips["per_person"].median(), 2))
print(round(tips["tip_pct"].median(), 2))
tips["big_party"] = tips["size"] >= 3
tips["big_party"].value_counts()
Your turn#
Which 3 bills had the highest spend per person?
pp_cols = ["total_bill", "size", "per_person"]
tips.nlargest(3, "per_person")[pp_cols].round(2)
Quick check: what share of bills tipped 20 percent or more?
generous = tips["tip_pct"] >= 20
print(generous.sum(), round(generous.mean(), 3))
Quick check: which single bill had the highest tip percentage?
cols = ["total_bill", "tip", "size", "day", "tip_pct"]
tips.nlargest(3, "tip_pct")[cols].round(1)
Step 5: filter and sort to find specific bills#
Problem: answer 'which bills?' questions, with more than one condition.
dinner = tips[tips["time"] == "Dinner"]
print(len(dinner), round(dinner["tip_pct"].median(), 2))
Common mistake: using
andto combine two conditions
tips[(tips["day"] == "Sat") and (tips["smoker"] == "Yes")]
sat_smoke = (tips["day"] == "Sat") & (tips["smoker"] == "Yes")
print(sat_smoke.sum())
fri_sun = (tips["day"] == "Fri") | (tips["day"] == "Sun")
print(fri_sun.sum())
by_bill = tips.sort_values("total_bill", ascending=False)
by_bill[cols].head(3).round(1)
two_keys = tips.sort_values(["day", "tip_pct"],
ascending=[True, False])
two_keys[cols].head(3).round(1)
Your turn#
Show the 3 lunch bills with the highest tip percentage.
lunch = tips[tips["time"] == "Lunch"]
lunch.nlargest(3, "tip_pct")[cols].round(1)
Step 6: chart a relationship#
Problem: see at a glance whether two columns move together.
ax = tips.plot(kind="scatter", x="total_bill", y="tip",
figsize=(7, 4), alpha=0.6)
ax.set_title("Tip against bill for 244 tables")
ax.set_xlabel("Total bill (dollars)")
ax.set_ylabel("Tip (dollars)")
plt.show()
Quick check: do bigger parties leave a smaller tip percentage? Guess first.
Common mistake: passing a column name to corr
tips["total_bill"].corr("tip")
print(round(tips["total_bill"].corr(tips["tip"]), 3))
print(round(tips["size"].corr(tips["tip_pct"]), 3))
Your turn#
Draw a bar chart of the number of bills on each day.
counts = tips["day"].value_counts()
ax = counts.plot(kind="bar", rot=0, figsize=(7, 4))
ax.set_title("Bills per day")
ax.set_xlabel("Day")
ax.set_ylabel("Number of bills")
plt.show()
Mini-project: do bigger parties tip less generously?#
Plan: compare small and big parties, check each size, then test outliers.
big = tips["big_party"]
print(round(tips.loc[big, "tip"].median(), 2),
round(tips.loc[~big, "tip"].median(), 2))
big_mean = tips.loc[big, "tip_pct"].mean()
small_mean = tips.loc[~big, "tip_pct"].mean()
print(round(big_mean, 2), round(small_mean, 2))
by_size = tips.groupby("size")["tip_pct"].agg(["count", "median"])
by_size.round(1)
big_med = tips.loc[big, "tip_pct"].median()
small_med = tips.loc[~big, "tip_pct"].median()
print(round(big_med, 2), round(small_med, 2))
normal = tips["tip_pct"] < 40
t_big = tips.loc[big & normal, "tip_pct"].mean()
t_small = tips.loc[~big & normal, "tip_pct"].mean()
print((~normal).sum(), round(t_big, 2), round(t_small, 2))
summary = pd.DataFrame({"mean": [small_mean, big_mean],
"median": [small_med, big_med]},
index=["1-2 people", "3+ people"])
ax = summary.plot(kind="bar", rot=0, figsize=(7, 4))
ax.set_title("Tip percentage by party size")
ax.set_xlabel("Party")
ax.set_ylabel("Tip (% of bill)")
plt.show()
gap_mean = small_mean - big_mean
gap_med = small_med - big_med
print(f"Gap: {gap_mean:.1f} pts by mean, {gap_med:.1f} by median")
Recap#
- Build small tables from a dict of lists; extend them with new columns and concat
- First look: shape, head, info, gaps, a seeded sample
- Ratios like tip_pct make unequal rows comparable
- Combine conditions with & and |, never
and/or - Check means against medians before trusting a gap
Challenges#
- Do smokers tip a different percentage? Compare means and medians.
- Add a
weekendcolumn (Sat or Sun) and compare tip_pct. - Build a DataFrame from the last 3 bills and add tip per person.
What's next#
Lesson 07: Importing and Exporting Data (CSV, Excel, JSON, SQL) Save your tables to files and load data from many formats.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



