Every bank, online shop, and web form exports data as a CSV file — a plain-text table where commas separate the columns. Your scraped prices, your bank statements, your form responses: all CSV. And almost none of it arrives clean.

That's where pandas comes in. It's Python's spreadsheet brain: you load a CSV, and instead of clicking around a screen, you write commands that clean, filter, and summarize the data — repeatably. This guide takes you from "never opened a CSV in Python" to a working data-cleaning script in about ten minutes.

Setup: One Install

pip install pandas

That's the whole setup. pandas is the only third-party package we need — everything else uses Python's standard library.

What a CSV Actually Looks Like

Open a CSV file in any text editor and you'll see something like:

Date , Item ,  Cost,Category
2026-08-01,USB cable, $5.99,Electronics
2026-08-02,Coffee beans, 12.50,Food

First line = column names. Every other line = one row. Commas = the gaps between columns. Messy real-world exports add their own chaos: stray spaces, dollar signs inside numbers, missing values, duplicated rows. All of it shows up in the file above — we'll fix every bit.

Step 1: Load the File and Look Around

import pandas as pd

df = pd.read_csv("messy_expenses.csv")
print(df.shape)      # (rows, columns)
print(df.columns)    # column names
print(df.head())     # first 5 rows

Run that against our messy file and you get:

(6, 4)
Index(['Date ', ' Item ', '  Cost', 'Category'], dtype='object')
     Date   Item    Cost     Category
0  2026-08-01  USB cable   $5.99  Electronics
1  2026-08-02  Coffee beans  12.50  Food
...

df is a DataFrame — pandas' name for a table that lives in memory. It already noticed something useful: the column names have stray spaces ('Date ', ' Item '). That's our first fix.

Step 2: Fix the Column Names

df.columns = [c.strip() for c in df.columns]
print(df.columns)
# Index(['Date', 'Item', 'Cost', 'Category'], dtype='object')

strip() removes whitespace from both ends of a string. One line, and every column name is clean — much easier than renaming them one by one.

Step 3: Fix the Values

Same trick, applied to the data instead of the headers:

df["Item"] = df["Item"].str.strip()

df["Item"] selects one column, and .str gives every string in it the string methods you already know (strip, lower, replace...). This is how pandas handles text: one method call per column, applied to every row at once — no loops.

Step 4: Remove Duplicate Rows

Exports often contain the same row twice (double exports, retried uploads). One call removes them:

before = len(df)
df = df.drop_duplicates()
print(f"{before} -> {len(df)} rows")   # 6 -> 5 rows

By default this compares entire rows. If your duplicate rows differ in one column (say, two purchases of the same item on different dates), tell pandas which columns make a row "the same":

df = df.drop_duplicates(subset=["Item"])   # one row per item

Step 5: Fill the Gaps

Rows where the Category is empty arrived as NaN — pandas' marker for "missing". Decide on a default and fill it:

df["Category"] = df["Category"].fillna("Other")

Now every row has a category. The alternative — df.dropna() — throws away rows with any missing value. Use it when you'd rather lose a few rows than guess.

Step 6: Turn Text into Numbers

The Cost column is useless as long as it says $5.99 — that's text, and you can't sum text. Two small steps fix it:

df["Cost"] = df["Cost"].str.replace("$", "", regex=False).astype(float)
print(df["Cost"].dtype)   # float64

Line by line: .str.replace("$", "") deletes the dollar sign, and .astype(float) converts the rest to real numbers. The regex=False matters more than it looks: str.replace may treat the first argument as a regular expression, and the default has changed across pandas versions — so the same code can behave differently depending on what's installed. The classic trap: in regex, . matches any character, so str.replace("a.c", "X") in regex mode rewrites "abc" and "axc" too. Writing regex=False explicitly means "treat this as plain text" — on every version.

Step 7: Ask Questions

Now the fun part. The data is clean, so answers are one-liners:

# What did I spend on food?
food = df[df["Category"] == "Food"]
print(food)

# What were the three most expensive things?
print(df.sort_values("Cost", ascending=False).head(3))

# Total spend per category?
print(df.groupby("Category")["Cost"].sum())
Category
Electronics     5.99
Food           17.25
Other           8.00
Name: Cost, dtype: float64

Three patterns to memorize: df[condition] filters rows, sort_values orders them, and groupby is the pivot table — group rows by a column, then sum/average/count another. These three cover 80% of everyday data questions.

Step 8: Save the Clean File

df.to_csv("clean_expenses.csv", index=False)

Two things to know: index=False stops pandas from adding an extra number column to your file (people forget this constantly and end up with a mystery first column), and the result is another plain CSV — openable in Excel, Google Sheets, or the next Python script.

The Complete Script

All eight steps together, ready to run:

"""clean-expenses.py — tidy up a messy CSV export."""
import pandas as pd

df = pd.read_csv("messy_expenses.csv")
df.columns = [c.strip() for c in df.columns]          # fix headers
df["Item"] = df["Item"].str.strip()                   # fix values
df = df.drop_duplicates()                             # remove doubles
df["Category"] = df["Category"].fillna("Other")       # fill gaps
df["Cost"] = df["Cost"].str.replace("$", "", regex=False).astype(float)

print(df.sort_values("Cost", ascending=False).head(3))
print(df.groupby("Category")["Cost"].sum())
df.to_csv("clean_expenses.csv", index=False)
print("Saved clean_expenses.csv")

Swap the filename and the fixes to match whatever mess your own export contains — the skeleton stays identical.

Where to Go From Here

CSV is the glue format of automation. The price tracker saves its price history as a CSV, so this guide's skills let you chart your own data. Want Excel instead of CSV at the end? The Excel automation guide shows to_excel(). And once you're comfortable filtering and grouping, the natural next step is combining two files (a price list + a stock list) with pd.merge() — same idea, one more column.

Gotchas (And Their Fixes)

Symptom Cause Fix
str.replace rewrites more than expected Pattern treated as regex; . matches any character Add regex=False
Extra number column appears in saved file pandas wrote its index to_csv(..., index=False)
Column names with spaces Messy source file df.columns = [c.strip() for c in df.columns]
df["Cost"].sum() fails Numbers stored as text Strip symbols, then .astype(float)
Duplicate rows stay after drop_duplicates() Rows differ in one column drop_duplicates(subset=["Column"])
Missing values ruin a summary Real-world data has gaps fillna("Other") or dropna()
Huge file, slow script Loading more than you need pd.read_csv(..., usecols=[...]) to pick columns

One last tip: whenever a CSV misbehaves, open it in a text editor first — not Excel, which politely hides the commas, the encoding, and everything else that's actually wrong.

All code in this article was tested and runs successfully on Python 3.8.10 (Ubuntu 20.04) with pandas 2.0.3 — verified August 2026.