Index Ai Academy
Phase 0 · Tools · Working with data

Files and CSV data

Read and write files, and turn a spreadsheet export into Python data.

About 25 minutes · Free lesson

Start this lesson

Why it matters

Real data lives in files. Think of CSV exports from ad platforms and CRMs, documents you want a model to summarize, example prompts and replies for testing, and logs of everything your app sent to a model. Before you can analyze, embed or train on any of it, you need to get it into Python. Then you need to write your results back out.

What you'll be able to do

  • Write text to a file and read it back with open() and with
  • Process a file one line at a time
  • Read a CSV export into rows you can calculate with, and write one
  • Build file paths with pathlib

What it covers

  1. Writing and reading a file
  2. One line at a time
  3. CSV: spreadsheets as text
  4. Writing a CSV
  5. Worked example: from export to answer
  6. Three ways to lose an afternoon
  7. Close your files

Writing and reading a file

The academy runs Python inside your browser, with its own small file system. Files you create here live in this sandbox, not on your computer. But the code is exactly what you'd run on your own machine.

Working with a file is like using a notebook: open it, read or write, then close it.

with open("notes.txt", "w") as f:
    f.write("Q4 plan\n")
    f.write("Raise search budget\n")
    f.write("Test new subject lines\n")

with open("notes.txt") as f:
    contents = f.read()

print(contents)
It runs right here in your browser, with nothing to install.

open(path, mode) opens a file and gives you a file object, here named f. The mode says what you'll do with it:

ModeMeaning
"r"read (the default if you leave the mode out)
"w"write: creates the file, or erases an existing one first
"a"append: adds to the end of the file

write doesn't add line breaks for you. \n inside a string is the newline character. It marks the end of a line.

with ... as f: makes sure the file is closed when the indented block ends, even if an error happens partway through. Always open files this way. A file left open can lose data that was written but not yet saved.

Tip: add encoding="utf-8" when you open text files on your own computer, for example open("notes.txt", encoding="utf-8"). Then accents, non-English text and emoji read correctly on every system.

One line at a time

Looping over a file object gives you its lines one by one. Each line still ends with its \n, so .strip() it:

with open("subjects.txt", "w") as f:
    f.write("Last chance: 30% off\n")
    f.write("New arrivals you'll love\n")
    f.write("Your weekly picks\n")

with open("subjects.txt") as f:
    for line in f:
        line = line.strip()
        print(len(line), line)

f.readlines() gives you all the lines as a list instead. Looping is better for big files, because Python only holds one line in memory at a time. A 2 GB log file is fine to loop over. It would be painful to load all at once.

Appending is how logs grow:

with open("log.txt", "w") as f:
    f.write("batch 1 sent\n")

with open("log.txt", "a") as f:
    f.write("batch 2 sent\n")

with open("log.txt") as f:
    print(f.read())
Quick check
You open an existing report with open("report.txt", "w"). What happens to what was already in it?
  1. It's kept, and the file opens read-only
  2. New text is added after it
  3. It's erased as soon as the file is opened
  4. Python raises an error because the file exists
Show the answer

It's erased as soon as the file is opened

"w" starts from an empty file. Use "a" to add to the end instead.

CSV: spreadsheets as text

When you export from Google Ads, a CRM or a spreadsheet, you usually get a CSV (comma-separated values) file. It's plain text: one row per line, with values separated by commas. The first line holds the column names:

campaign,channel,spend,clicks
Spring Sale,search,3120.5,5470
Retargeting,display,1890,2050

You could split each line on commas yourself. But that breaks as soon as a value contains a comma. CSV handles that by putting such values in quotes. Python's csv module understands the rules, so you don't have to.

csv.DictReader reads each row as a dictionary keyed by the column names. So you can ask for row["spend"] by name, instead of remembering it's the third column. One catch: every value comes back as a string, because a CSV file is just text. Convert numbers with float() or int() before doing math.

import csv

with open("campaigns.csv", "w", newline="") as f:
    f.write("campaign,channel,spend,clicks\n")
    f.write("Spring Sale,search,3120.5,5470\n")
    f.write("Retargeting,display,1890,2050\n")
    f.write('"Brand Video, Q4",video,6400,3800\n')

total_spend = 0
with open("campaigns.csv", newline="") as f:
    for row in csv.DictReader(f):
        spend = float(row["spend"])
        total_spend += spend
        print(row["campaign"], "|", row["channel"], "|", spend)

print("Total spend:", total_spend)

It prints the three campaigns, then Total spend: 11410.5. The third campaign's name contains a comma, and the quotes keep it as one value. The newline="" argument lets the csv module handle line endings itself. The Python docs recommend it whenever you open a file for csv.

Quick check
A CSV row is read with csv.DictReader as {'channel': 'email', 'spend': '40'}. What does row["spend"] * 2 give?
  1. '4040'
  2. '80'
  3. An error
  4. 80
Show the answer

'4040'

Every CSV value is a string, and a string times 2 repeats it. Convert first: int(row["spend"]) * 2 is 80.

Try it now

Look at row in the variables panel: {'channel': 'search', 'spend': '120'}. The spend is the text '120', which is why line 9 converts it with int() before adding.

Writing a CSV

csv.writer writes rows from lists. csv.DictWriter writes rows from dictionaries, once you give it the column order:

import csv

rows = [
    {"campaign": "Spring Sale", "roas": 5.08},
    {"campaign": "Brand Video", "roas": 0.45},
]

with open("report.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["campaign", "roas"])
    writer.writeheader()
    for row in rows:
        writer.writerow(row)

with open("report.csv") as f:
    print(f.read())

That file opens cleanly in Excel, Numbers or Google Sheets.

Paths with pathlib

A path is a file's address, like exports/summary.txt. The pathlib module treats paths as objects. That's tidier than gluing strings together:

from pathlib import Path

folder = Path("exports")
folder.mkdir(exist_ok=True)
report = folder / "summary.txt"
report.write_text("ROAS up 12% week over week\n")

print(report)
print(report.exists(), report.suffix, report.name)
print(report.read_text())
for p in folder.iterdir():
    print("Found:", p.name)

What each piece does:

  • The / operator joins path pieces with the right separator for your operating system.
  • write_text and read_text are shortcuts for a quick write or read without open.
  • mkdir(exist_ok=True) creates a folder unless it's already there.

On your own computer, Path.home() / "Downloads" is your Downloads folder.

Quick check
What does this print?
with open("a.txt", "w") as f:
    f.write("one\n")
with open("a.txt", "a") as f:
    f.write("two\n")
with open("a.txt", "w") as f:
    f.write("three\n")
with open("a.txt") as f:
    print(f.read().strip())
  1. one and two, on separate lines
  2. one, two and three, on separate lines
  3. three
  4. An error
Show the answer

three

The last "w" erases the file before writing, so only "three" is left.

Worked example

Worked example: from export to answer

An email platform exported subject lines with sends and opens. Find each open rate and the best subject line.

import csv

with open("email_export.csv", "w", newline="") as f:
    f.write("subject,sends,opens\n")
    f.write("Last chance: 30% off,24000,6240\n")
    f.write("New arrivals you'll love,22000,4180\n")
    f.write("Your weekly picks,23500,3290\n")

best_subject = ""
best_rate = 0
with open("email_export.csv", newline="") as f:
    for row in csv.DictReader(f):
        rate = int(row["opens"]) / int(row["sends"])
        print(f"{rate:.1%}  {row['subject']}")
        if rate > best_rate:
            best_rate = rate
            best_subject = row["subject"]

print("Best:", best_subject)
In the lesson you can change this and run it.

It prints open rates of 26.0%, 19.0% and 14.0%, then Best: Last chance: 30% off.

The first block only creates the export, so the example can run. With a real export you'd skip it. The loop turns the text values into numbers, works out each open rate, and keeps track of the best one so far. {rate:.1%} formats a number as a percentage with one decimal place. The same dozen lines would handle an export with 50,000 rows.

Three ways to lose an afternoon

"w" empties the file first. Opening a file in write mode wipes it the moment the line runs, before you've written anything. To add to the end instead, use "a". When the data matters, write to a new file, and replace the old one only when that has worked.

Everything in a CSV is text. A CSV file has no types. row["score"] gives you "82", and comparing that with the number 70 is an error. Convert first: int(row["score"]) >= 70.

Don't split CSV lines by hand. line.split(",") looks fine until a value has a comma in it, such as "Boots, leather". The csv module understands the quoting rules, so let it do the work.

One more small thing: write doesn't start a new line for you. Add \n yourself, or everything ends up on one long line.

Close your files

Using with open(...) closes the file for you, even if an error happens partway through.

Practice what you just read

  • 7 code examples you can edit and run
  • a practice exercise that checks your code
  • a 5-question quiz
  • 5 review cards for spaced repetition

Start this lesson

Try a quiz question

Why open files with with open(...) as f:?

  1. It closes the file automatically, even if an error happens
  2. It reads the whole file into memory at once, so it's faster
  3. It's required for CSV files, which plain open() can't read
  4. It creates the file if it doesn't exist, which open() can't do

Answer it at the end of the lesson to see if you're right, and why.

Before this lesson