Summarizing Data Records

Introduction

Congratulations and welcome to Practical Data Processing with Python Collections! Look at how far our toolkit has come: we tallied and grouped values with dictionaries, reshaped lists with comprehensions, and turned messy raw text into clean, typed records.

Every one of those steps was preparation. Records are not the goal; they are the raw material for answers. A manager looking at a list of sales does not want to read four dictionaries; they want to know the total, the average, and which item performed best and worst. That final translation, from structured data to a readable summary, is what this lesson is about.

By the end of this lesson, we will be able to:

  • Extract a single numeric field from a list of records into a flat list
  • Compute totals and averages with sum() and len()
  • Find the highest and lowest records, names included, using max() and min() with a key function
  • Print a tidy, currency-formatted report

The Records We Will Summarize

Let us begin with the data. Our example is a small list of sales transactions, where each record is a dictionary pairing a product name with the amount of money it brought in.

Python
sales = [
    {"product": "Notebook", "amount": 45.00},
    {"product": "Pen", "amount": 12.50},
    {"product": "Folder", "amount": 30.00},
    {"product": "Marker", "amount": 18.75},
]

This is exactly the shape a parser produces: a list of records that all share the same keys. Two details make summarizing possible here:

  • Consistent keys: every record has both "product" and "amount", so we can safely ask each one for the same field without guarding against missing keys.
  • Real numbers: "amount" holds floats such as 45.00, not strings such as "45.00". Numbers can be added and compared; text cannot, at least not in the way we want.

Extracting a Numeric Column with a Comprehension

sum() needs an iterable of numeric values, not a list of dictionaries, so our first move is to pull the "amount" field out of every record into a flat list. That extraction is precisely what a comprehension does best.

Python
amounts = [sale["amount"] for sale in sales]

As you may recall from the list comprehension tools we have already used, we read this as "for each sale in sales, give me sale["amount"]". The result is the flat list [45.0, 12.5, 30.0, 18.75], in the same order as the original records.

Two things are worth noticing here. First, 45.00 prints as 45.0 because Python stores floats without trailing zeros; we will fix the appearance later. Second, the comprehension builds a new list and leaves sales completely untouched, which matters because we still need those dictionaries when we want the product names.

It is also worth knowing that sum(), like most built-in tools that process a sequence of values, actually accepts any iterable, not only a list. A generator expression such as sum(sale["amount"] for sale in sales) would compute the very same total without ever building an intermediate list in memory. We build the amounts list here anyway because we are about to reuse those plain numbers again right away, first for the average and later in this lesson.

Totals and Averages with sum() and len()

With a list of plain numbers in hand, the total is a single built-in call. The average follows from tools we already know.

Python
total = sum(amounts)
average = total / len(amounts)

sum() walks amounts and adds every element, giving 106.25. The average is simply the total divided by the number of values, so len(amounts) supplies the count and gives 106.25/4=26.5625106.25 / 4 = 26.5625.

That second value hints at a problem: nobody wants to read 26.5625 dollars in a report, so a formatting step is coming shortly. There is also a practical guard to remember: if the record list is empty, len(amounts) is 0, and dividing by zero stops the program. In production code, we would check for an empty list before computing the average.

Finding Extreme Records with max(), min(), and a Key Function

Now for a subtler question. We could call max(amounts) and get 45.0, but that number arrives orphaned: it no longer knows which product earned it. To keep the name attached, we ask for the biggest record instead.

Python
highest = max(sales, key=lambda sale: sale["amount"])
lowest = min(sales, key=lambda sale: sale["amount"])

Here max() and min() iterate directly over the dictionaries in sales, and key tells them which value to compare. Two pieces of this line are new, so let us take them slowly.

The key argument answers a question max() cannot answer on its own: dictionaries have no natural order, so Python needs to be told what to measure each record by. Whatever we pass as key is a small function that Python calls once for each record, and the value it hands back is what gets compared. The record whose key value is largest is the one max() returns, whole and unchanged.

The lambda is how we write that small function right where we need it, without giving it a name. Reading lambda sale: sale["amount"] left to right: lambda announces a tiny function, sale is the name it gives to the one record it receives, and sale["amount"] is the value it hands back. In plain words: "given one sale, look at its amount." Nothing else in this course asks us to write functions; this one-line form is all key needs. The table below shows what it produces for each record:

RecordKey valueResult
{"product": "Notebook", ...}45.0winner for max
{"product": "Pen", ...}12.5winner for min
{"product": "Folder", ...}30.0
{"product": "Marker", ...}18.75

As the table shows, highest becomes the whole Notebook dictionary and lowest becomes the whole Pen dictionary, so both the amount and the product name remain within reach.

Formatting Numbers for a Readable Report

Our statistics are correct but not yet presentable. F-strings accept a format specifier after a colon, and :.2f means "show this number as a decimal with exactly two digits after the point."

Python
print(f"Total: ${total:.2f}")
print(f"Average: ${average:.2f}")
print(f"Highest: {highest['product']} (${highest['amount']:.2f})")

Each line pairs a literal $ with a formatted number: total prints as $106.25, and average becomes $26.56, rounded for display only. The variable average still holds 26.5625 in memory; :.2f changes the text, never the stored value.

The third line shows a detail that trips up nearly everyone: since the f-string itself is wrapped in double quotes, the key inside it must use single quotes, as in highest['product']. Using the same quote character would end the string early and break the line.

Assembling and Reading the Full Report

We now have every piece, so let us print the report. Six print() calls give us a title, a count, and the four statistics.

Python
# Produce a formatted report
print("Sales Report")
print(f"Transactions: {len(sales)}")
print(f"Total: ${total:.2f}")
print(f"Average: ${average:.2f}")
print(f"Highest: {highest['product']} (${highest['amount']:.2f})")
print(f"Lowest: {lowest['product']} (${lowest['amount']:.2f})")

The title is a plain string, len(sales) counts the records directly, and the last two lines read a name and an amount from the same dictionary. Running the complete program produces:

text
Sales Report
Transactions: 4
Total: $106.25
Average: $26.56
Highest: Notebook ($45.00)
Lowest: Pen ($12.50)

Notice Highest: Notebook ($45.00): the stored value was 45.0, yet :.2f pads it to two decimals so every amount lines up as currency. The same specifier trimmed 26.5625 down to $26.56.

Common Pitfalls

Aggregation code fails in a handful of predictable ways, and recognizing them early saves real debugging time.

  • Omitting key: max(sales) tries to compare whole dictionaries against each other and raises a TypeError.
  • Empty record lists: total / len(amounts) raises a ZeroDivisionError when there are no records, so check the length first.
  • Untyped values: if a parser left amounts as strings, sum() raises a TypeError; conversion belongs upstream, as we saw in the last lesson.
  • Misreading :.2f: it formats the printed text only, so a value rounded for display is still unrounded in memory.
  • Quote collisions: writing {highest["amount"]} inside a double-quoted f-string closes the string too soon; use single quotes for the key.

The typed-values pitfall is the most common of all because it links parsing and aggregation: parsing quality directly determines whether aggregation works.

Conclusion and Next Steps

We just built a complete summarizing pipeline in four moves: extract a numeric column from the records with a comprehension, aggregate it into a total and an average with sum() and len(), locate the extreme records with max() and min() plus a key function so the names travel along with the numbers, and format everything into lines a person can actually read. This pattern scales without changes: swap "amount" for any numeric field, and the same six lines report on temperatures, scores, or response times.

The practices ahead follow that exact path: pulling a field out of records and totaling it, computing the average, finding the top and bottom records with a key function, and finally assembling the multi-line report yourself. Together, these skills take messy raw text all the way to a polished summary: counting, grouping, filtering, parsing, and aggregating. Nicely done, and let us put that reporting pattern into practice!

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal