Reading CSV & Excel Files
Reviewed & published by Brayan K
Spreadsheets run the business world — sales exports, customer lists, financial reports. Python lets you read, transform, and write that tabular data automatically, turning hours of manual spreadsheet work into a script that runs in seconds.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
You'll start with the built-in csv module, then meet openpyxl and pandas for real Excel files.
What You'll Learn in This Lesson
- Reading CSV with csv.reader and csv.DictReader
- Writing CSV with csv.writer and DictWriter
- Why newline='' matters when opening files
- Handling headers, types, and missing values
- Reading Excel files with openpyxl
- When to switch to pandas for power
1 What a CSV File Really Is
A CSV is just a text file. If you open employees.csv in a plain editor, you'll see:
name,department,salary
Ada Lovelace,Engineering,95000
Grace Hopper,Engineering,98000
Katherine Johnson,Research,91000The first line is usually the header (column names). Each following line is a row, with commas separating fields. That's the whole format.
2 Reading: reader vs DictReader
csv.reader gives each row as a list. csv.DictReader uses the header row to give each row as a dict — usually much clearer:
import csv
# --- reader: rows are lists (access by index) ---
with open("employees.csv", newline="") as f:
reader = csv.reader(f)
header = next(reader) # skip/grab the first row
print(header) # ['name', 'department', 'salary']
for row in reader:
print(row[0], "earns", row[2])
# Ada Lovelace earns 95000
# --- DictReader: rows are dicts keyed by header (clearer!) ---
with open("employees.csv", newline="") as f:
for row in csv.DictReader(f):
print(row["name"], "->", row["department"])
# Ada Lovelace -> Engineering✍️ Writing CSV Files
Writing mirrors reading. Use writer for lists or DictWriter for dicts. Remember newline="" here too:
import csv
rows = [
{"name": "Ada", "score": 95},
{"name": "Grace", "score": 98},
{"name": "Katherine", "score": 91},
]
with open("scores.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["name", "score"])
writer.writeheader() # writes: name,score
writer.writerows(rows) # writes all three data rows
# scores.csv now contains:
# name,score
# Ada,95
# Grace,98
# Katherine,91writeheader() writes the column names; writerow() writes one row; writerows() writes a whole list at once.
import csv, io
# We simulate a CSV file using io.StringIO so this runs without a real file.
csv_text = """name,department,salary
Ada Lovelace,Engineering,95000
Grace Hopper,Engineering,98000
Katherine Johnson,Research,91000
Dorothy Vaughan,Research,88000"""
# DictReader works on any file-like object, including StringIO
reader = csv.DictReader(io.StringIO(csv_text))
total = 0
count = 0
for row in reader:
salary = int(row["salary"]) # remember: convert from string!
total += salary
count += 1
print(f'{row["name"]:20} {row["department"]:12} ', salary)
print("-" * 45)
print(f"Average salary: ", round(total / count))
# Average salary: 93000
# ✅ Expected output:
# Ada Lovelace Engineering 95000
# Grace Hopper Engineering 98000
# Katherine Johnson Research 91000
# Dorothy Vaughan Research 88000
# ---------------------------------------------
# Average salary: 930003 Reading Real Excel Files with openpyxl
The csv module can't read .xlsx files — those are zipped XML, not text. The openpyxl library handles them:
An Excel workbook contains one or more worksheets, each a grid of cells addressed like A1, B2. iter_rows(values_only=True) is the cleanest way to loop.
🐼 The Power Tool: pandas
When you need filtering, grouping, or statistics, pandas reads CSV and Excel into a DataFrame — a spreadsheet you can program against:
# pip install pandas openpyxl
import pandas as pd
df = pd.read_csv("employees.csv") # or pd.read_excel("sales.xlsx")
print(df.head()) # first 5 rows, nicely formatted
print(df["salary"].mean()) # average of a whole column
print(df.shape) # (rows, columns) e.g. (4, 3)
# Filtering reads like a sentence:
engineers = df[df["department"] == "Engineering"]
print(engineers["name"].tolist()) # ['Ada Lovelace', 'Grace Hopper']
# Group and aggregate in one line:
print(df.groupby("department")["salary"].mean())
# department
# Engineering 96500.0
# Research 89500.0
# Write back out
df.to_csv("clean.csv", index=False)
df.to_excel("report.xlsx", index=False)Real-World Example: Cleaning a Messy Sales Export
A classic task: read a raw export, fix the data, write a clean version. This runs with the built-in csv module — no extra installs:
import csv, io
raw = """product,price,qty
Widget, 9.99 ,3
Gadget,19.5,
Gizmo, 4.00 ,10"""
reader = csv.DictReader(io.StringIO(raw))
cleaned = []
for row in reader:
name = row["product"].strip()
price = float(row["price"].strip()) # strip spaces, convert
qty = int(row["qty"]) if row["qty"].strip() else 0 # blank -> 0
cleaned.append({
"product": name,
"price": price,
"qty": qty,
"total": round(price * qty, 2),
})
# Print the cleaned report
print(f'{"Product":10}{"Price":>8}{"Qty":>5}{"Total":>10}')
for r in cleaned:
print(f'{r["product"]:10}{r["price"]:>8.2f}{r["qty"]:>5}{r["total"]:>10.2f}')
grand_total = sum(r["total"] for r in cleaned)
print("-" * 33)
print(f'{"GRAND TOTAL":23}{grand_total:>10.2f}')
# Output:
# Product Price Qty Total
# Widget 9.99 3 29.97
# Gadget 19.50 0 0.00
# Gizmo 4.00 10 40.00
# ---------------------------------
# GRAND TOTAL 69.97Real data is messy: stray spaces, blank cells, numbers stored as text. The cleaning loop here (strip, convert, default missing values) is the pattern you'll use again and again.
import csv, io
# Read this CSV and find the top scorer
data = """student,subject,score
Maya,Math,88
Leo,Math,72
Maya,Science,95
Leo,Science,81
Zara,Math,90"""
reader = csv.DictReader(io.StringIO(data))
best = None
for row in reader:
score = int(row["score"])
if best is None or score > best["score"]:
best = {"name": row["student"], "subject": row["subject"], "score": score}
print(f"Top score: {best['name']} got {best['score']} in {best['subject']}")
# Top score: Maya got 95 in Science
# ✅ Expected output:
# Top score: Maya got 95 in Science# 🎯 YOUR TURN — replace each ___ using the hint beside it.
import csv
import io
# A string standing in for a file, so this runs anywhere.
raw = "title,pages,year\nDune,412,1965\nEmma,474,1815\nBee,96,2021\n"
# 1) Rows as dictionaries keyed by the header row — far easier to read
# than csv.reader's plain lists.
reader = csv.___(io.StringIO(raw)) # 👉 replace ___ with DictReader
rows = list(reader)
print("Rows read:", len(rows))
print("Columns:", reader.fieldnames)
for row in rows:
print(row["title"], "-", row["pages"], "pages")
# 2) Every value from a CSV is a STRING, so convert before comparing.
long_ones = [r for r in rows if ___(r["pages"]) > 100] # 👉 replace ___ with int
print("Longer than 100:", [r["title"] for r in long_ones])
out = io.StringIO()
# 3) Writing needs the column names up front, in the order you want them.
# lineterminator is here on purpose: csv writes CRLF (\r\n) by default,
# because that is what RFC 4180 says. Leave it out and every line of the
# file you produce ends in an invisible \r.
writer = csv.DictWriter(out, ___=["title", "pages"], lineterminator="\n") # 👉 replace ___ with fieldnames
# 4) Without this the file has no header row at all.
writer.___() # 👉 replace ___ with writeheader
for r in long_ones:
# 5) One dict, one row.
writer.___({"title": r["title"], "pages": r["pages"]}) # 👉 replace ___ with writerow
print("Written back:")
print(out.getvalue().strip())
total = sum(int(r["pages"]) for r in rows)
print("Total pages:", total)
# ✅ Expected output:
# Rows read: 3
# Columns: ['title', 'pages', 'year']
# Dune - 412 pages
# Emma - 474 pages
# Bee - 96 pages
# Longer than 100: ['Dune', 'Emma']
# Written back:
# title,pages
# Dune,412
# Emma,474
# Total pages: 982🧩 Reorder Challenge
These lines write a CSV file with a header and one data row, but they're scrambled. Find the order:
A) writer.writeheader()
B) import csv
C) writer = csv.DictWriter(f, fieldnames=["name", "age"])
D) writer.writerow({"name": "Ada", "age": 36})
E) with open("out.csv", "w", newline="") as f:Correct order: B → E → C → A → D
import csv
with open("out.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["name", "age"])
writer.writeheader()
writer.writerow({"name": "Ada", "age": 36})Import, open the file (note newline=""), create the writer with field names, write the header, then write the row.
🧠 Quick Recall — Predict the Output
row = {"qty": "5"} # a value read from a CSV
print(row["qty"] + 3)TypeError — CSV values are strings, so "5" + 3 mixes str and int. You must convert first: int(row["qty"]) + 3 gives 8.
header = ["name", "score"]
row = ["Ada", "95"]
record = dict(zip(header, row))
print(record["score"])95 — zip pairs the header with the row and dict() builds a mapping. This is exactly what DictReader does for you under the hood.
import pandas as pd
# df has a "price" column: [10, 20, 30]
prices = [10, 20, 30]
print(sum(prices) / len(prices)) # mimics df["price"].mean()20.0 — the average of 10, 20, 30. In pandas this is just df["price"].mean(), which is why it's so handy for analysis.
❓ Frequently Asked Questions
You can automate the spreadsheet world now!
You can read and write CSV with the built-in module, parse real Excel files with openpyxl, and unleash pandas for analysis. Cleaning a messy export by hand is now a script you run in seconds.
Practice quiz
What does CSV stand for?
- Computed Spreadsheet Values
- Column Sorted Vector
- Comma-Separated Values
- Common Storage View
Answer: Comma-Separated Values. CSV stands for Comma-Separated Values — a plain-text table format.
What is the difference between csv.reader and csv.DictReader?
- reader gives lists; DictReader uses the header row to give dicts
- DictReader gives lists; reader gives dicts
- They are identical
- reader only works on Excel files
Answer: reader gives lists; DictReader uses the header row to give dicts. csv.reader yields each row as a list; csv.DictReader uses the header to yield each row as a dict.
Why should you open a CSV file with newline=""?
- It makes the file smaller
- It is required for UTF-8
- It speeds up reading
- It prevents the csv module from inserting blank rows on Windows
Answer: It prevents the csv module from inserting blank rows on Windows. newline="" stops double newline translation that would otherwise add blank rows on Windows.
What type is every value you read from a CSV, e.g. row["salary"] = "95000"?
- int
- str (a string)
- float
- bool
Answer: str (a string). Every CSV value is a string; you must convert with int() or float() before doing math.
Why should you NOT split CSV lines yourself with line.split(",")?
- Fields can contain commas inside quotes, which naive splitting corrupts
- It's too slow
- split doesn't exist in Python
- It only works on Excel
Answer: Fields can contain commas inside quotes, which naive splitting corrupts. Quoted fields may contain commas; the csv module handles quoting and escaping that split breaks.
Which csv.DictWriter call writes the column names as the first row?
- writer.writerow()
- writer.header()
- writer.writeheader()
- writer.writerows()
Answer: writer.writeheader(). writeheader() writes the field names; writerow/writerows write data rows.
Which library is commonly used to read real .xlsx Excel files?
- The built-in csv module
- openpyxl
- json
- sqlite3
Answer: openpyxl. The csv module can't read .xlsx; openpyxl (or pandas) reads Excel files.
In openpyxl, what does sheet.iter_rows(values_only=True) yield?
- Cell objects
- Column headers only
- A single string per sheet
- Plain cell values rather than Cell objects
Answer: Plain cell values rather than Cell objects. values_only=True yields plain values, which is the cleanest way to loop over rows.
What does pandas read CSV and Excel data into?
- A list of tuples
- A DataFrame
- A Counter
- A plain dict
Answer: A DataFrame. pandas reads tabular data into a DataFrame, a programmable spreadsheet-like structure.
When is the built-in csv module preferable to pandas?
- When you need grouping and statistics
- When reading .xlsx files
- For simple, dependency-free, row-by-row reading and writing
- When joining multiple tables
Answer: For simple, dependency-free, row-by-row reading and writing. Use csv for simple dependency-free row work; reach for pandas when you need analysis or Excel support.
Continue this course
- Previous: HTTP Requests & APIs
- Next: Web Scraping with BeautifulSoup — Extract data from web pages with requests and BeautifulSoup
- Quick reference: Python cheat sheet
Frequently asked questions
What is a CSV file?
CSV stands for Comma-Separated Values. It's a plain-text table where each line is a row and commas separate the columns. It's the universal export format for spreadsheets and databases.
What is the difference between csv.reader and csv.DictReader?
csv.reader gives you each row as a list of strings (indexed by position). csv.DictReader uses the header row as keys, giving each row as a dict so you can write row['name'] instead of row[0]. DictReader is usually clearer.
Why should I pass newline='' when opening a CSV file?
The csv module handles line endings itself. Opening with newline='' prevents the file object from translating newlines too, which would otherwise insert blank rows on Windows. It's the recommended way to open CSV files for reading and writing.
How do I read Excel (.xlsx) files in Python?
The standard library can't read .xlsx. Use the third-party openpyxl library (pip install openpyxl), or pandas (pip install pandas) which reads both CSV and Excel with a single read_excel / read_csv call.
When should I use pandas instead of the csv module?
Use the built-in csv module for simple row-by-row reading and writing with no dependencies. Reach for pandas when you need filtering, grouping, statistics, or to work with both CSV and Excel using the same powerful DataFrame API.