Summarising a table
describe() is a checklist, not a report — run it on everything.
.describe().value_counts().nunique().agg().count()numeric_onlyWatch it happen
Play it through, or step back and forth yourself.
dfdf["cups"].sum()615df["cups"].mean()102.5df["cups"].max()150df.mean(numeric_only=True)one value per columnEvery aggregation you met in NumPy works on a Series: .sum(), .mean(), .min(), .max(), .std(). On a DataFrame they run per column and hand you back a Series.
The idea
Every aggregation from the NumPy track works here. On a Series they give one number; on a DataFrame they run per column and hand back a Series indexed by column name:
orders["cups"].sum() # one number
orders.sum(numeric_only=True) # one per numeric column
orders.mean(numeric_only=True)numeric_only=True matters on a mixed table — without it, summing a text column either concatenates the strings or raises, depending on the operation.
describe as a checklist
orders.describe() runs eight summaries at once for every numeric column. Its value isn't the numbers themselves — it's that the rows form a checklist for problems you haven't thought to look for yet.
count counts non-missing values. Two columns with different counts means one has gaps, and you found them without going looking.
min and max are where nonsense lives. An age of -1, a price of 999999, a date in 1900 — sentinel values and parsing failures sit at the extremes, and this puts them in front of you.
mean against 50% is your skew check. When they diverge, the distribution has a tail and every average you report afterwards is misleading — that's the lesson-21 argument from the NumPy track, applied automatically to every column.
std of zero means a constant column, which is usually a bug or a column you can drop.
orders.describe() # numeric columns
orders.describe(include="all") # text too: count, unique, top, freq
orders.describe(include="object") # only the text columnsCounting categories
describe is for numbers. For a text or categorical column you want counts:
orders["city"].value_counts() # how many of each
orders["city"].value_counts(normalize=True) # as proportions
orders["city"].value_counts(dropna=False) # count the gaps too
orders["city"].nunique() # 3 — how many distinctvalue_counts sorts by frequency descending, so the most common value is at the top. That's usually what you want — and it's how you spot a category that appears twice under two spellings, which is what lesson 11 was about.
Naming your own summaries
orders.agg(["min", "max", "mean"], numeric_only=True)
orders.agg({"cups": "sum", "revenue": "mean", "rating": "max"}).agg() takes a list to apply several functions, or a dict to apply a different one per column. It also takes your own function, which is how you'd add a percentile or a custom metric to the same table.
All of these skip missing values by default — the same choice lesson 9 made explicit. If you want the gaps to propagate instead, do the arithmetic yourself.
See it run
The lesson's code, ready to run and to fiddle with.
Putting the kettle on…
Starting up…
Worked example
not gradedAlready written and ready to go — press Run to see what it does, then change a number, a column name, anything, and run it again.
tryorders.describe(include="all") — the text columns get different rows.
Your turn
4 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.
Return the summary statistics for the numeric columns of orders.
How many orders came from each city? Return the counts.
How many distinct items appear in orders? Return the number.
Return the total cups and the mean revenue in one call, using .agg() with a dict.
