Topic 04

CSV: Spreadsheet Data as Text

Hands-on

Every bank, shop and spreadsheet program can export a CSV file: rows of values separated by commas, with a first row that names the columns. CSV stands for comma-separated values, and it is the most common way data leaves one program and enters another. Python's csv module reads it straight into the list of dictionaries from Chapter 6, and your own bank statement becomes data a program can answer questions about.

Think of a spreadsheet with its grid lines rubbed out. The commas are where the lines used to be, and each line of text is a row.

What a CSV Is

A CSV file is plain text. Open one in a text editor, not a spreadsheet, and you see exactly this. Save it as statement.csv in your python-course folder: a month of spending, as a bank might export it, with spending as negative amounts and income as positive.

statement.csv
date,description,amount,category
2026-09-01,Grocery store,-54.30,food
2026-09-02,Monthly bus pass,-45.00,transport
2026-09-03,Coffee,-4.20,food
2026-09-05,Salary,2400.00,income
2026-09-07,Electricity bill,-61.75,home
2026-09-09,Cinema,-12.50,fun

Why Not Just Split on Commas?

It is tempting to read each line and split it on commas. That works until a value contains a comma of its own. A CSV writer handles that by putting the value in double quotes, and a plain split cuts straight through the quotes. The line below should give four values; splitting it gives five, with the description torn in half.

Splitting a quoted value by hand
>>> line = '2026-09-12,"Dinner, drinks",-38.00,fun'
>>> line.split(",")
['2026-09-12', '"Dinner', ' drinks"', '-38.00', 'fun']
>>> len(line.split(","))
5

The csv module understands quoting, and every other corner of the format, so there is no reason to write your own.

csv.DictReader

csv.DictReader reads a CSV file and hands you each row as a dictionary. The keys come from the header row, so row["amount"] is the amount of that row, whatever column it is in. The header row itself is used for the keys and is not returned as data. One detail matters: open the file with newline="", which lets the csv module handle line endings itself, as its documentation asks.

first_row.py
import csv

with open("statement.csv", encoding="utf-8", newline="") as f:
    for row in csv.DictReader(f):
        print(row)
        break
Output
{'date': '2026-09-01', 'description': 'Grocery store', 'amount': '-54.30', 'category': 'food'}

Everything Is Text

Look closely at that output: the amount is '-54.30', in quotes. Every value in a CSV arrives as text, exactly like the answer from input in Chapter 2. To do arithmetic, convert it. It is money, so Decimal(row["amount"]) makes it an exact Decimal straight from the text, which is precisely the way Chapter 2 recommended.

The Statement, Totalled

Now the whole job. The program reads every row, skips income, and for spending adds the amount to the overall total and to its category's total. Amounts in the file are negative for spending, so the program flips the sign with a minus to count money spent as a positive number. The answers match Chapters 4 and 6: 177.75 spent in total, 58.50 of it on food.

statement.py
import csv
from decimal import Decimal

spent = Decimal("0")
by_category = {}

with open("statement.csv", encoding="utf-8", newline="") as f:
    for row in csv.DictReader(f):
        amount = Decimal(row["amount"])
        if amount >= 0:
            continue
        cost = -amount
        spent = spent + cost
        category = row["category"]
        by_category[category] = by_category.get(category, Decimal("0")) + cost

print(f"Spent in total: {spent}")
for category, total in by_category.items():
    print(f"  {category}: {total}")
Output
Spent in total: 177.75
  food: 58.50
  transport: 45.00
  home: 61.75
  fun: 12.50

Two tools from earlier chapters do the heavy lifting here. continue skips the rest of the round for income rows. And by_category.get(category, Decimal("0")) returns the category's running total, or zero the first time the category appears, which replaces the if ... not in check from Chapter 6 with a single line.

One row, two views
In the file
2026-09-03,Coffee,-4.20,food
In Python
date: 2026-09-03 · description: Coffee · amount: "-4.20" · category: food
Common Confusions
  • "Numbers in a CSV arrive as numbers." Every field arrives as text. Convert it: int, float, or Decimal for money.
  • "Splitting each line on commas is good enough." It breaks the first time a value contains a comma. The csv module handles quoting correctly.
  • "The header row is the first row of data." DictReader uses it for the keys and does not return it as data.
Why It Matters
  • CSV is how data moves between spreadsheets, banks, shops and programs. With this page you can read almost all of it.
  • Your own bank export is now something you can ask questions of in a dozen lines.

Knowledge Check

What does csv.DictReader do with the header row?

  • Returns it as the first row of data, like any other
  • Uses it as the keys for every row's dictionary
  • Ignores it completely and numbers the columns instead
  • Checks that every row has exactly the same values

What type is row["amount"] when read by DictReader?

  • A Decimal, because the value looks like money
  • A float, because the text contains a decimal point
  • A str, like every value read from a CSV
  • An int, because the amount is a whole number of cents

Why does line.split(",") go wrong on 2026-09-12,"Dinner, drinks",-38.00,fun?

  • Split cannot handle text that contains a minus sign
  • CSV files must be read with open, not split
  • It also cuts at the comma inside the quoted description
  • The line is too long for split to process in one go

Why does the statement program write cost = -amount?

  • Spending is negative in the file, and it counts spending as positive
  • Because Decimal cannot add negative numbers together
  • To remove the income rows from the calculation
  • Because the file stores its amounts in the wrong order

You got correct