Using Libraries

Summarizing Data with pandas


Learning Objectives

  • You can compute column totals and averages with pandas.
  • You can use groupby to summarize rows by category.
  • You can sort rows by a column and grouped summaries by their values.
  • You can join a session table with a small subject-information table.
  • You can use pd.to_datetime before date-based summaries.

You already know what summaries are. A summary turns many rows into a smaller fact: total minutes, average session length, minutes by subject, the longest sessions, recent sessions.

Pandas gives names to these operations. Start with a small DataFrame:

import pandas as pd
sessions = [
{"subject": "math", "minutes": 45, "date": "2026-05-10"},
{"subject": "history", "minutes": 30, "date": "2026-05-11"},
{"subject": "math", "minutes": 60, "date": "2026-05-12"},
]
df = pd.DataFrame(sessions)

Overall summaries come from a column:

print(df["minutes"].sum())
print(df["minutes"].mean())
print(df["minutes"].max())

The next exercise sequence practices the routine behind these lines: select the minutes column, summarize it, and decide what to return for an empty DataFrame.

Summaries by Subject

The main new operation is groupby:

totals = df.groupby("subject")["minutes"].sum()
print(totals)

Read this from left to right:

  1. Start with the DataFrame.
  2. Group rows by the subject column.
  3. In each group, look at the minutes column.
  4. Sum the minutes.

The result is a Series whose labels are subjects and whose values are totals.

averages = df.groupby("subject")["minutes"].mean()
counts = df.groupby("subject")["minutes"].count()

This is much shorter than the nested-loop version from Summarizing Your Data, but it is the same idea: collect rows with the same subject and summarize each collection.

The same grouped-summary routine works for averages as well as totals.

Average minutes by subject

0 / 10 points

Define a function named average_minutes_by_subject(sessions).

sessions is a list of dictionaries with subject, minutes, and date keys.

Example input:

sessions = [
{"subject": "math", "minutes": 45, "date": "2026-05-10"},
{"subject": "history", "minutes": 30, "date": "2026-05-11"},
{"subject": "math", "minutes": 60, "date": "2026-05-12"},
]

Use pandas groupby to group rows by subject, calculate the averages with the .mean() method, and convert the result into a regular Python dictionary with .to_dict().

Example return value:

{"history": 30.0, "math": 52.5}

For local testing, you can copy the study_sessions.csv block from the pandas DataFrame chapter into a local file named study_sessions.csv, then use pd.read_csv("study_sessions.csv").to_dict("records") to make a larger sessions list. The grader still passes sessions directly to your function.

If sessions is empty, return {}.

Do not ask for user input and do not print inside the function.

Split, Apply, Combine

You will often see groupby explained as split, apply, combine.

For:

df.groupby("subject")["minutes"].sum()

pandas:

  1. splits the rows into groups with the same subject,
  2. applies the sum operation to the minutes in each group,
  3. and combines the results into one summary object.

This mental model helps when the expression gets longer. If the output is strange, ask which step is wrong. Did the split use the right column? Did the apply step summarize the right values? Did the combined result need to be converted before printing?

You can also ask pandas for more than one summary:

summary = df.groupby("subject")["minutes"].agg(["count", "sum", "mean"])
print(summary)

This produces a small table with one row per subject and columns for count, sum, and mean. The project checkpoint keeps the required helpers simpler, but this is the same family of operation.

Worked Summary: Rebuilding an Earlier Question

In Summarizing Your Data, a typical question was: “How many minutes have I studied per subject, from most to least?” By the end of Structuring Your Data, the study log answered that question by looping over Session objects.

With loops, the core looked like this:

totals = {}
for session in sessions:
subject = session.subject
minutes = session.minutes
totals[subject] = totals.get(subject, 0) + minutes

For this sample:

sessions = [
Session("math", 45, "2026-05-10"),
Session("history", 30, "2026-05-11"),
Session("math", 60, "2026-05-12"),
Session("programming", 25, "2026-05-12"),
]

the loop result is:

{"math": 105, "history": 30, "programming": 25}

The pandas version starts by converting those sessions to records:

records = []
for session in sessions:
records.append(session.to_dict())
df = pd.DataFrame(records)

Then split by subject and sum the minutes:

totals = df.groupby("subject")["minutes"].sum()
print(totals)

One possible printed output is:

subject
history 30
math 105
programming 25
Name: minutes, dtype: int64

This is not a dictionary yet. It is a pandas Series with subject labels. If the dashboard wants the subjects from most minutes to least minutes, sort the values:

totals = totals.sort_values(ascending=False)
print(totals)

Output:

subject
math 105
history 30
programming 25
Name: minutes, dtype: int64

Finally, if the rest of your code wants a regular dictionary:

totals_dict = totals.to_dict()
print(totals_dict)

Output:

{'math': 105, 'history': 30, 'programming': 25}

That is the full path: records to DataFrame, DataFrame to grouped Series, grouped Series to sorted Series, sorted Series to plain dictionary. Writing it in pieces makes each shape visible.

Sorting Rows Versus Sorting Summaries

Sorting rows answers a different question from sorting a summary.

This asks “Which individual sessions were longest?”

longest_rows = df.sort_values("minutes", ascending=False)
print(longest_rows.head(3))

This asks “Which subjects have the most total minutes?”

subject_totals = df.groupby("subject")["minutes"].sum()
subject_totals = subject_totals.sort_values(ascending=False)
print(subject_totals)

Both use sort_values, but the object being sorted is different. In the first version, each row is still one study session. In the second version, each row of the result is one subject summary.

When choosing a pandas operation, name the question first. “Longest sessions” points to sorting the original rows. “Most studied subjects” points to grouping first, then sorting the grouped totals.

Sort rows

0 / 5 points

Which expression sorts session rows from longest to shortest?

A Second CSV: Subject Information

Sometimes the information you need is split across two tables. The session table records what happened:

subject,minutes,date
math,45,2026-05-01
history,30,2026-05-02
programming,60,2026-05-02

A second table can describe the subjects themselves. Create a file named subjects.csv in the same folder as study_sessions.csv. Copy the CSV text below into that file:

subject,category,weekly_goal_minutes
math,quantitative,180
history,humanities,120
programming,technical,240
biology,science,150
writing,communication,120

Now you can read both files:

import pandas as pd
sessions = pd.read_csv("study_sessions.csv")
subjects = pd.read_csv("subjects.csv")

The column that connects the files is subject. Pandas calls this operation a merge:

joined = sessions.merge(subjects, on="subject", how="left")
print(joined.head())

The joined DataFrame still has one row per study session, but each row now also has the subject’s category and weekly goal. .head() shows the first five rows:

subject minutes date category weekly_goal_minutes
0 math 45 2026-05-01 quantitative 180
1 history 30 2026-05-02 humanities 120
2 programming 60 2026-05-02 technical 240
3 math 25 2026-05-03 quantitative 180
4 writing 40 2026-05-04 communication 120

The how="left" part means “keep every row from the sessions table, and add matching information from the subjects table when it exists.” For this sample data, every subject has a matching row in subjects.csv.

After the merge, you can summarize by a column that did not exist in the original session file:

category_totals = joined.groupby("category")["minutes"].sum()
print(category_totals.sort_values(ascending=False))

This is a small example of why pandas is useful in real work. One table rarely contains every useful detail. Being able to combine tables lets you ask questions like “How much time did I spend on technical subjects?” without manually copying category names into every session row.

Practice that routine before moving on: join the session table to the subject table, then summarize the joined data.

Date Summaries

Dates should be converted before date-based operations. Store the converted values in a local Series so the original table keeps its date strings:

dates = pd.to_datetime(df["date"])

Then comparisons work across the whole column:

today = pd.Timestamp("2026-05-15")
start = today - pd.Timedelta(days=6)
recent_mask = (dates >= start) & (dates <= today)
recent = df[recent_mask]
print(recent)

pd.Timestamp is pandas’ date-and-time value, and pd.Timedelta is pandas’ duration value. They play the same roles here as date and timedelta did in the standard-library date chapter, but they work naturally with pandas columns. The parentheses make each comparison happen before the two masks are combined.

Recent sessions with pandas

0 / 5 points

Arrange the lines to create a recent-session DataFrame for the seven-day window ending on May 15, 2026. Assume df already exists and has subject, minutes, and date columns.

Recent sessions with pandas

0 / 10 points

Define a function named recent_sessions(df, today_text).

df is a pandas DataFrame with subject, minutes, and date columns. today_text is an ISO date string such as "2026-05-15".

The tests pass a DataFrame shaped like this:

df = pd.DataFrame([
{"subject": "math", "minutes": 45, "date": "2026-05-08"},
{"subject": "history", "minutes": 30, "date": "2026-05-09"},
{"subject": "math", "minutes": 60, "date": "2026-05-15"},
{"subject": "programming", "minutes": 25, "date": "2026-05-16"},
])

Return a DataFrame containing the sessions in the seven-day window ending on today_text, including both endpoints.

For example, if today_text is "2026-05-15", include sessions from "2026-05-09" through "2026-05-15".

With the example DataFrame, recent_sessions(df, "2026-05-15") should contain the history row from May 9 and the math row from May 15, in that order.

Convert df["date"] to a local date Series with pd.to_datetime inside the function. Use that Series to build the window with pd.Timestamp and pd.Timedelta, combine the two date masks with &, and filter the original DataFrame. Keep the matching rows and their original values in their original order.

For local testing, you can copy the study_sessions.csv block from the pandas DataFrame chapter into a local file named study_sessions.csv, then use df = pd.read_csv("study_sessions.csv"). The grader still passes df directly to your function.

If df has no rows, return an empty DataFrame with the same columns. Do not ask for user input and do not print inside the function.

Common Mistake: and Instead of &

This looks reasonable, but it does not work with pandas Series:

recent_mask = dates >= start and dates <= today

Python’s and expects one truth value on the left and one truth value on the right. But dates >= start produces one Boolean value per row. Pandas does not know how to collapse that whole Series of Booleans into one yes-or-no answer.

Use & to combine masks element by element:

recent_mask = (dates >= start) & (dates <= today)

The parentheses are not decoration. They make each comparison happen before the masks are combined.

Sessions between dates

0 / 10 points

Define sessions_between_dates(df, start_text, end_text).

df is a pandas DataFrame with subject, minutes, and date columns. start_text and end_text are ISO date strings such as "2026-05-09".

Convert df["date"] to a local date Series with pd.to_datetime. Combine two pandas masks with &, then return the rows whose dates are between the two endpoints, including both endpoints. Filter the original DataFrame so the returned rows retain their original values and order.

Example DataFrame:

df = pd.DataFrame([
{"subject": "math", "minutes": 45, "date": "2026-05-08"},
{"subject": "history", "minutes": 30, "date": "2026-05-09"},
{"subject": "math", "minutes": 60, "date": "2026-05-15"},
{"subject": "programming", "minutes": 25, "date": "2026-05-16"},
])

For this DataFrame, sessions_between_dates(df, "2026-05-09", "2026-05-15") should return the history row and the second math row, in that order. If no rows match, return an empty DataFrame with the same columns.

For local testing, you can copy the study_sessions.csv block from the pandas DataFrame chapter into a local file and read it with pd.read_csv("study_sessions.csv"). The grader still passes df directly to your function.

Do not ask for user input and do not print inside the function.

Comparing the Loop Version and the pandas Version

The loop version makes every step visible. The pandas version names a common operation. Both are valuable.

Loop version:

totals = {}
for session in sessions:
subject = session.subject
minutes = session.minutes
totals[subject] = totals.get(subject, 0) + minutes

This is the class-based shape from Structuring Your Data: sessions is a list of Session objects.

Pandas version, starting from a DataFrame that has already been created:

totals = df.groupby("subject")["minutes"].sum().to_dict()

The pandas version is not magic. It is a compact expression of the same summary idea. You should be able to explain the result in ordinary language before relying on it.

Compare loops and pandas summaries

0 / 10 points

In a short explanation (about 4-6 sentences), compare the loop-based way of computing total minutes by subject with this pandas version:

totals = df.groupby("subject")["minutes"].sum().to_dict()

Explain what the pandas line does and why understanding the loop version is still useful.

A Debugging Pattern for Summaries

When a pandas summary looks wrong, debug it in layers:

print(df.head())
print(df["subject"])
print(df["minutes"])

Then inspect the grouped result before converting it:

totals = df.groupby("subject")["minutes"].sum()
print(totals)

Only after that, convert or sort:

totals = totals.sort_values(ascending=False)
print(totals.to_dict())

This is slower to type than one perfect chain, but it mirrors how you debugged loops earlier in the course. First inspect the input, then inspect the intermediate result, then inspect the final result.

If the grouped result is wrong, the bug is before .to_dict(). If the grouped result is right but the printed dashboard is wrong, the bug is after the summary helper. Separating those layers keeps pandas from becoming a black box.

Check Your Understanding

  1. What does df.groupby("subject")["minutes"].sum() compute?
  2. Why might you sort grouped totals after computing them?
  3. Why does pandas use & instead of and for combining masks?
  4. Why might a helper return totals.to_dict() instead of returning the pandas Series?

AI Study Aid

Create a chapter diagram

We're looking into to what extent AI-generated diagrams could help with studying.

Use this study aid to generate an AI-generated visual summary of the material you just studied. Each diagram style emphasizes the content in a different way, so you can choose the focus that feels most useful.

Using the diagram generator is voluntary and does not affect your course progress. We encourage you to try it out and see if it helps, but it's totally up to you! Your answers help us understand how to make better study aids in the future.

Diagrams tried: 0