Summarizing Data with pandas
Learning Objectives
- You can compute column totals and averages with pandas.
- You can use
groupbyto 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_datetimebefore 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:
- Start with the DataFrame.
- Group rows by the
subjectcolumn. - In each group, look at the
minutescolumn. - 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:
- splits the rows into groups with the same subject,
- applies the sum operation to the minutes in each group,
- 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) + minutesFor 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:
subjecthistory 30math 105programming 25Name: minutes, dtype: int64This 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:
subjectmath 105history 30programming 25Name: minutes, dtype: int64Finally, 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,datemath,45,2026-05-01history,30,2026-05-02programming,60,2026-05-02A 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_minutesmath,quantitative,180history,humanities,120programming,technical,240biology,science,150writing,communication,120Now 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_minutes0 math 45 2026-05-01 quantitative 1801 history 30 2026-05-02 humanities 1202 programming 60 2026-05-02 technical 2403 math 25 2026-05-03 quantitative 1804 writing 40 2026-05-04 communication 120The 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 <= todayPython’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) + minutesThis 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
- What does
df.groupby("subject")["minutes"].sum()compute? - Why might you sort grouped totals after computing them?
- Why does pandas use
&instead ofandfor combining masks? - 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.