LESSON 140 · DEEPER PYTHON30 min

Build a Python Survey Response Summarizer

Normalize optional survey answers and compute sorted choice counts plus an optional rating average.

Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons

Video lesson: Build a Python Survey Response Summarizer

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    We have three survey rows. One respondent chose Python and gave a rating, one skipped the choice entirely, and one typed the same choice with extra spaces and different capitalization. How many votes should the summary show? Before we calculate anything, decide what counts as an answer. The project runs on a local list of dictionaries, with no external service or private survey upload. That makes it safe to experiment with missing values and invalid data. Our target output has two fields: a mapping of normalized choice counts and an average rating when at least one valid rating exists. Those fields answer different questions and should be computed from different

  2. 00:41

    subsets of the input. A survey export is not guaranteed to contain a response for every question. Someone may leave choice blank, type extra spaces, or use different capitalization. A useful summary counts actual choices after normalization and reports the average of valid numerical ratings. This project uses a list of dictionaries in memory, so it works in the browser without uploading anyone's answers. We deliberately keep counts and average separate: a missing choice should not invent a vote, and a missing rating should not enter the denominator. The broken implementation indexes the choice key in every row.

  3. 01:24

    It works for the first respondent, then stops at the empty dictionary with KeyError. The traceback points to the indexing expression, not to Counter itself. It would be easy to add a dummy choice value to every test row and make the error disappear, but that would hide the real input shape. Survey questions are often optional. The code must represent that policy intentionally. Even without the missing row, counting the raw strings would split Python and a spaced lower-case version into separate labels. The broken run therefore exposes two distinct problems: assuming a key always exists, and treating formatting differences as different responses.

  4. 02:06

    The broken program indexes row['choice'] for every record. When a respondent skips that question, it raises KeyError and produces no summary at all. Even if every record had a key, 'Python', ' python ', and 'PYTHON' would be counted as different choices without normalization. Diagnose the raw keys, values, and types before tallying. Missing data is not the same as the string 'None' or a numerical zero. A summary must state what it omits rather than silently converting absent values into a real answer. The diagnosis walks the records and prints each row's keys, the raw choice, and its stripped, case-folded value.

  5. 02:48

    This is more informative than adding a broad exception handler around the entire report. For the missing row, get returns an empty string. For the spaced response, strip removes boundary whitespace and casefold normalizes capitalization. We can now state a rule: count a choice only if meaningful text remains after normalization. Ratings need their own rule. A missing or null choice should be skipped. A missing rating should be absent from the average, while a supplied value outside the agreed one-to-five integer range should raise an error. Silent coercion would make it hard to spot a bad export. Use Counter for normalized nonempty choices.

  6. 03:30

    Strip surrounding whitespace, apply casefold to make case-insensitive groups, and increment only when text remains. For ratings, accept integers one through five while explicitly rejecting bool, because bool is a subclass of int in Python. Store valid ratings in a separate list and use statistics.mean when that list is nonempty. Return None when no ratings were supplied. Sort the count mapping for stable display and tests. Validation errors should identify malformed supplied ratings rather than dropping them without notice. The fixed function uses Counter for choices and a separate list for ratings.

  7. 04:10

    Each nonempty normalized choice increments its count. Each supplied rating is checked as an actual integer between one and five. We use type(value) is int because Python booleans behave like integers in some contexts, yet True is not a legitimate survey rating. The mean is computed only when at least one rating was observed; otherwise the result is None. The count mapping is sorted before returning it, giving a stable presentation order that is useful for the webpage and tests. The sample now combines both Python spellings into a count of two and averages the two real ratings. Read the loop as two independent pipelines.

  8. 04:52

    The choice pipeline obtains optional text, normalizes it, and counts only a nonempty result. The rating pipeline checks whether a value was supplied, verifies its exact type and allowed range, and adds it to a separate list. At return time, dict(sorted(counts.items())) makes output predictable, while mean(ratings) if ratings else None preserves the difference between an unobserved average and a numerical value. Notice that we never divide by the total number of survey rows. That number includes respondents who did not rate, so using it as the denominator would bias the result. This function is small enough to audit yet captures several common data-cleaning

  9. 05:34

    decisions. The verification run tries a blank-only survey and a boolean rating. Blank and missing choices produce no vote, and the absence of ratings produces None instead of a fabricated zero. A boolean rating triggers ValueError, which makes the malformed supplied value visible to the caller. Try adding a fourth response with a new choice and no rating: the count changes, but the average should not. If this were a real product, we would also explain its normalization and missing-data policy next to every chart. On the lesson page, complete the function, run the practical checker, and then use

  10. 06:16

    the quiz to explain why missing, blank, zero, and invalid are not interchangeable. The verification run includes an entirely blank survey, an explicit null choice, and a record with no rating. It should report an empty count mapping and no average, not divide by zero or show a fabricated zero-star result. For real survey data, consider privacy, sample selection, and whether response categories should be restricted to a known list. This tutorial focuses on the data-handling mechanics. The task checker tests normalization, missing answers, and an invalid boolean rating, so the same code can be trusted for those specified cases rather than just

  11. 06:56

    one happy-path sample.

Understand the concept

A survey export is not guaranteed to contain a response for every question. Someone may leave choice blank, type extra spaces, or use different capitalization. A useful summary counts actual choices after normalization and reports the average of valid numerical ratings. This project uses a list of dictionaries in memory, so it works in the browser without uploading anyone's answers. We deliberately keep counts and average separate: a missing choice should not invent a vote, and a missing rating should not enter the denominator.

The broken program indexes row['choice'] for every record. When a respondent skips that question, it raises KeyError and produces no summary at all. Even if every record had a key, 'Python', ' python ', and 'PYTHON' would be counted as different choices without normalization. Diagnose the raw keys, values, and types before tallying. Missing data is not the same as the string 'None' or a numerical zero. A summary must state what it omits rather than silently converting absent values into a real answer.

Use Counter for normalized nonempty choices. Strip surrounding whitespace, apply casefold to make case-insensitive groups, and increment only when text remains. For ratings, accept integers one through five while explicitly rejecting bool, because bool is a subclass of int in Python. Store valid ratings in a separate list and use statistics.mean when that list is nonempty. Return None when no ratings were supplied. Sort the count mapping for stable display and tests. Validation errors should identify malformed supplied ratings rather than dropping them without notice.

The verification run includes an entirely blank survey, an explicit null choice, and a record with no rating. It should report an empty count mapping and no average, not divide by zero or show a fabricated zero-star result. For real survey data, consider privacy, sample selection, and whether response categories should be restricted to a known list. This tutorial focuses on the data-handling mechanics. The task checker tests normalization, missing answers, and an invalid boolean rating, so the same code can be trusted for those specified cases rather than just one happy-path sample.

  • Optional keys need deliberate handling
  • Normalize before counting
  • Missing ratings do not enter the mean
  • Reject bool when a true integer rating is required

See it step by step

Read the code, predict the output, then compare it with the result.

01. Normalize equivalent answers

PYTHON
answers = ['Python', ' python ', 'PYTHON', '']
print([answer.strip().casefold() for answer in answers])
EXPECTED OUTPUT
['python', 'python', 'python', '']

Whitespace and capitalization differences collapse to one label, while blank remains blank.

02. Count nonempty choices

PYTHON
from collections import Counter
answers = ['Python', ' python ', '']
print(dict(Counter(a.strip().casefold() for a in answers if a.strip())))
EXPECTED OUTPUT
{'python': 2}

Only actual nonempty choices contribute to the tally.

03. Separate missing ratings

PYTHON
from statistics import mean
ratings = [5, None, 3]
observed = [value for value in ratings if value is not None]
print(mean(observed))
EXPECTED OUTPUT
4

The missing response does not become a zero or enter the divisor.

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Inspect raw answers

A survey export is not guaranteed to contain a response for every question. Someone may leave choice blank, type extra spaces, or use different capitalization. A useful summary counts actual choices after normalization and reports the average of valid numerical ratings. This project uses a list of dictionaries in memory, so it works in the browser without uploading anyone's answers. We deliberately keep counts and average separate: a missing choice should not invent a vote, and a missing rating should not enter the denominator.

Run the normalization list and identify which strings become equivalent. Decide whether blank should be represented as a category or excluded; this lesson excludes it. In a real survey, document that policy next to the chart.

PYTHON
answers = ['Python', ' python ', 'PYTHON', '']
print([answer.strip().casefold() for answer in answers])
EXPECTED OUTPUT
['python', 'python', 'python', '']
Follow the reasoning
  1. Normalize the sample answers.
  2. Explain what is omitted.
02 / 03

Normalize and count choices

The broken program indexes row['choice'] for every record. When a respondent skips that question, it raises KeyError and produces no summary at all. Even if every record had a key, 'Python', ' python ', and 'PYTHON' would be counted as different choices without normalization. Diagnose the raw keys, values, and types before tallying. Missing data is not the same as the string 'None' or a numerical zero. A summary must state what it omits rather than silently converting absent values into a real answer.

Counter accepts an iterator of labels. Filter empty strings before counting, then sort the final mapping if output order matters for a stable webpage or test. A tally is only as meaningful as its normalization policy.

PYTHON
from collections import Counter
answers = ['Python', ' python ', '']
print(dict(Counter(a.strip().casefold() for a in answers if a.strip())))
EXPECTED OUTPUT
{'python': 2}
Follow the reasoning
  1. Count nonempty labels.
  2. Sort the output mapping.
03 / 03

Keep rating coverage visible

Use Counter for normalized nonempty choices. Strip surrounding whitespace, apply casefold to make case-insensitive groups, and increment only when text remains. For ratings, accept integers one through five while explicitly rejecting bool, because bool is a subclass of int in Python. Store valid ratings in a separate list and use statistics.mean when that list is nonempty. Return None when no ratings were supplied. Sort the count mapping for stable display and tests. Validation errors should identify malformed supplied ratings rather than dropping them without notice.

A missing rating is not a zero-star rating. Build the list of observed ratings first, validate their types and range, and report None when the list is empty. This avoids a divide-by-zero error and a misleading numerical result.

PYTHON
from statistics import mean
ratings = [5, None, 3]
observed = [value for value in ratings if value is not None]
print(mean(observed))
EXPECTED OUTPUT
4
Follow the reasoning
  1. Average only observed ratings.
  2. Reject an invalid supplied value.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnReturn a dictionary with sorted normalized choice counts and average_rating. Skip missing, null, or blank choices, ignore absent ratings, reject supplied ratings outside integer 1–5 including bool, and use None when no rating exists.
Need a hint?

Use row.get('choice'); treat None as blank, then strip and casefold text; collect only valid integer ratings before calling statistics.mean.

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.Why should ' Python ' and 'python' count together?

    02.What average should be returned when no rating exists?

    03.Why reject True as a rating even though isinstance(True, int) is true?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Indexing an optional key

    COMMON MISTAKE
    row['choice']
    FIX
    row.get('choice')

    What happens: A skipped answer raises KeyError.
    Missing survey answers are expected input, not exceptional corruption.

    Mixing missing with zero

    COMMON MISTAKE
    sum(ratings) / len(rows)
    FIX
    mean(ratings) if ratings else None

    What happens: Unrated responses lower the average or an empty export divides by zero.
    The denominator should count valid observed ratings only.

    Browse this lesson’s problem fixes

    Sources and further reading