CSV files are plain-text files used to store tabular data. Each line usually represents a row, and commas separate values.
Example:
name,age,city
Alice,25,London
Bob,30,ParisThis guide covers working with CSV files using Python’s built-in csv module and the popular pandas library.
A CSV file commonly contains:
- A header row
- Rows of data
- Values separated by commas
- Optional quoted values
- Missing or empty values
Example:
id,name,score
1,Alice,88
2,Bob,92
3,Charlie,75CSV files can also use other separators, such as:
name;age;city
Alice;25;LondonThe separator is called the delimiter.
The csv module is built into Python and works well when you need simple, lightweight CSV processing.
import csvSuppose students.csv contains:
name,age,grade
Alice,20,A
Bob,21,BRead the file row by row:
import csv
with open("students.csv", "r", newline="") as file:
reader = csv.reader(file)
for row in reader:
print(row)Output:
['name', 'age', 'grade']
['Alice', '20', 'A']
['Bob', '21', 'B']Values are returned as strings.
with open("students.csv", newline="") as file:
reader = csv.reader(file)
header = next(reader)
print(header)
for row in reader:
print(row)next(reader) reads the first row.
csv.DictReader maps column names to values.
with open("students.csv", newline="") as file:
reader = csv.DictReader(file)
for row in reader:
print(row["name"], row["grade"])Each row behaves like a dictionary:
{
"name": "Alice",
"age": "20",
"grade": "A"
}This is often easier to read than using indexes.
CSV values are read as strings, so convert them when needed.
with open("students.csv", newline="") as file:
reader = csv.DictReader(file)
for row in reader:
age = int(row["age"])
print(age + 1)Common conversions:
age = int(row["age"])
price = float(row["price"])
active = row["active"].lower() == "true"Example:
name,age,city
Alice,20,London
Bob,,ParisCheck for empty values:
with open("students.csv", newline="") as file:
reader = csv.DictReader(file)
for row in reader:
age = row["age"]
if age:
print(int(age))
else:
print("Age missing")Using a default value:
age = int(row["age"] or 0)Use csv.writer.
import csv
rows = [
["name", "age"],
["Alice", 25],
["Bob", 30],
]
with open("people.csv", "w", newline="") as file:
writer = csv.writer(file)
writer.writerows(rows)with open("people.csv", "w", newline="") as file:
writer = csv.writer(file)
writer.writerow(["name", "age"])
writer.writerow(["Alice", 25])
writer.writerow(["Bob", 30])Use csv.DictWriter.
import csv
fields = ["name", "age"]
with open("people.csv", "w", newline="") as file:
writer = csv.DictWriter(file, fieldnames=fields)
writer.writeheader()
writer.writerow({"name": "Alice", "age": 25})Writing multiple dictionaries:
people = [
{"name": "Alice", "age": 25},
{"name": "Bob", "age": 30},
]
with open("people.csv", "w", newline="") as file:
writer = csv.DictWriter(file, fieldnames=["name", "age"])
writer.writeheader()
writer.writerows(people)Use append mode, "a".
with open("people.csv", "a", newline="") as file:
writer = csv.writer(file)
writer.writerow(["Charlie", 35])Do not write the header again when appending.
For semicolon-separated files:
with open("data.csv", newline="") as file:
reader = csv.reader(file, delimiter=";")
for row in reader:
print(row)Writing with a semicolon:
with open("data.csv", "w", newline="") as file:
writer = csv.writer(file, delimiter=";")
writer.writerow(["name", "age"])CSV files can contain commas inside quoted text:
name,address
Alice,"10 Main Street, London"The csv module handles this automatically:
with open("people.csv", newline="") as file:
reader = csv.DictReader(file)
for row in reader:
print(row["address"])For most modern files, use UTF-8:
with open("people.csv", encoding="utf-8", newline="") as file:
reader = csv.reader(file)When exporting files for some spreadsheet applications, this may help:
with open("people.csv", "w", encoding="utf-8-sig", newline="") as file:
writer = csv.writer(file)You can check whether required columns exist:
required = {"name", "age"}
with open("people.csv", newline="") as file:
reader = csv.DictReader(file)
if not required.issubset(reader.fieldnames):
raise ValueError("Missing required columns")Validate individual rows:
for row in reader:
if not row["name"]:
print("Name is missing")
if row["age"] and not row["age"].isdigit():
print("Invalid age")pandas is better for data analysis, filtering, cleaning, grouping, and transforming larger datasets.
pip install pandasImport it:
import pandas as pdimport pandas as pd
df = pd.read_csv("students.csv")
print(df)df is a pandas DataFrame.
Example:
name age grade
0 Alice 20 A
1 Bob 21 B
View the first rows:
df.head()View the last rows:
df.tail()View a specific number of rows:
df.head(10)Get the number of rows and columns:
df.shapeGet column names:
df.columnsGet general information:
df.info()Get numeric summaries:
df.describe()Select one column:
df["name"]Select multiple columns:
df[["name", "grade"]]Using dot notation is sometimes possible:
df.nameBracket notation is safer, especially when column names contain spaces.
Select the first row by position:
df.iloc[0]Select the first five rows:
df.iloc[:5]Select a row by label:
df.loc[0]Select specific rows and columns:
df.loc[0:2, ["name", "grade"]]Filter students older than 20:
df[df["age"] > 20]Filter by text:
df[df["grade"] == "A"]Multiple conditions:
df[(df["age"] > 20) & (df["grade"] == "A")]Use | for OR:
df[(df["grade"] == "A") | (df["grade"] == "B")]Use ~ for NOT:
df[~(df["grade"] == "A")]df[df["city"].isin(["London", "Paris"])]Find names containing "al":
df[df["name"].str.contains("al", case=False, na=False)]Starts with a value:
df[df["name"].str.startswith("A", na=False)]Sort by age:
df.sort_values("age")Sort descending:
df.sort_values("age", ascending=False)Sort by multiple columns:
df.sort_values(["city", "age"])Modify the original DataFrame:
df.sort_values("age", inplace=True)Create a new column:
df["passed"] = df["score"] >= 50Calculate a value:
df["score_percent"] = df["score"] / 100Create a column from text:
df["full_name"] = df["first"] + " " + df["last"]df = df.rename(columns={"old_name": "new_name"})Rename all columns:
df.columns = ["name", "age", "city"]Clean column names:
df.columns = df.columns.str.strip().str.lower()Replace spaces:
df.columns = df.columns.str.replace(" ", "_")Check data types:
df.dtypesConvert a column to integers:
df["age"] = df["age"].astype(int)Convert safely:
df["age"] = pd.to_numeric(df["age"], errors="coerce")Convert dates:
df["date"] = pd.to_datetime(df["date"])Convert to text:
df["name"] = df["name"].astype("string")Check missing values:
df.isna().sum()Remove rows containing missing values:
df.dropna()Remove rows missing a specific column:
df.dropna(subset=["email"])Fill missing values:
df["age"] = df["age"].fillna(0)Fill text values:
df["city"] = df["city"].fillna("Unknown")Fill with the average:
df["score"] = df["score"].fillna(df["score"].mean())Find duplicates:
df.duplicated()Remove duplicates:
df = df.drop_duplicates()Remove duplicates based on selected columns:
df = df.drop_duplicates(subset=["email"])Keep the last duplicate:
df = df.drop_duplicates(subset=["email"], keep="last")Replace one value:
df["city"] = df["city"].replace("NYC", "New York")Replace multiple values:
df["grade"] = df["grade"].replace({
"A+": "A",
"A-": "A"
})Remove extra spaces:
df["name"] = df["name"].str.strip()Convert to lowercase:
df["email"] = df["email"].str.lower()Replace text:
df["phone"] = df["phone"].str.replace("-", "", regex=False)Extract part of a string:
df["domain"] = df["email"].str.split("@").str[-1]Read dates while loading:
df = pd.read_csv("sales.csv", parse_dates=["date"])Convert after loading:
df["date"] = pd.to_datetime(df["date"])Extract date components:
df["year"] = df["date"].dt.year
df["month"] = df["date"].dt.month
df["weekday"] = df["date"].dt.day_name()Filter by date:
df[df["date"] >= "2026-01-01"]Average score by class:
df.groupby("class")["score"].mean()Total sales by product:
df.groupby("product")["sales"].sum()Multiple calculations:
df.groupby("city")["sales"].agg(["sum", "mean", "count"])Group by multiple columns:
df.groupby(["city", "year"])["sales"].sum()Count each category:
df["city"].value_counts()Include missing values:
df["city"].value_counts(dropna=False)Count unique values:
df["city"].nunique()Get unique values:
df["city"].unique()combined = pd.concat([df1, df2], ignore_index=True)combined = pd.concat([df1, df2], axis=1)merged = pd.merge(users, orders, on="user_id")Left join:
merged = pd.merge(users, orders, on="user_id", how="left")Common join types:
inner: Keep matching rowsleft: Keep all rows from the left DataFrameright: Keep all rows from the right DataFrameouter: Keep all rows from both DataFrames
Read a file with a custom delimiter:
df = pd.read_csv("data.csv", sep=";")Use a specific encoding:
df = pd.read_csv("data.csv", encoding="utf-8")Treat certain values as missing:
df = pd.read_csv("data.csv", na_values=["N/A", "unknown", "-"])Read only selected columns:
df = pd.read_csv("data.csv", usecols=["name", "age"])Read a limited number of rows:
df = pd.read_csv("data.csv", nrows=100)Skip rows:
df = pd.read_csv("data.csv", skiprows=2)Use a column as the index:
df = pd.read_csv("data.csv", index_col="id")For very large files, process smaller portions:
for chunk in pd.read_csv("large.csv", chunksize=10000):
print(chunk.shape)Calculate a result across chunks:
total = 0
for chunk in pd.read_csv("sales.csv", chunksize=10000):
total += chunk["amount"].sum()
print(total)df.to_csv("output.csv", index=False)index=False prevents pandas from writing the DataFrame index as an extra column.
Write only selected columns:
df[["name", "score"]].to_csv("scores.csv", index=False)Write with a custom separator:
df.to_csv("output.csv", sep=";", index=False)Write without headers:
df.to_csv("output.csv", header=False, index=False)Append to an existing file:
df.to_csv("output.csv", mode="a", header=False, index=False)import pandas as pd
df = pd.read_csv("sales.csv")
df.columns = df.columns.str.strip().str.lower()
df["date"] = pd.to_datetime(df["date"])
df["amount"] = pd.to_numeric(df["amount"], errors="coerce")
df = df.dropna(subset=["product", "amount"])
df = df.drop_duplicates()
large_sales = df[df["amount"] > 100]
summary = (
df.groupby("product")["amount"]
.sum()
.reset_index()
)
summary.to_csv("sales_summary.csv", index=False)This workflow:
- Loads the CSV
- Cleans column names
- Converts data types
- Removes invalid rows
- Removes duplicates
- Filters data
- Groups and summarizes data
- Exports the result
Use the built-in csv module when:
- The file is small
- You only need to read or write rows
- You want no external dependencies
- You are building a simple script
- You need precise control over individual rows
Use pandas when:
- You need filtering and sorting
- You need data cleaning
- You need grouping and aggregation
- You need date processing
- You need to combine multiple datasets
- You are doing analysis
- The dataset is moderately large
A simple rule:
Simple row processing → csv
Data analysis and transformation → pandas
Problem:
Unnamed: 0
Solution when writing:
df.to_csv("output.csv", index=False)Or remove it after reading:
df = df.drop(columns=["Unnamed: 0"])Check the types:
print(df.dtypes)Convert numeric data:
df["price"] = pd.to_numeric(df["price"], errors="coerce")Try another encoding:
df = pd.read_csv("data.csv", encoding="latin1")If the entire row appears in one column, check the separator:
df = pd.read_csv("data.csv", sep=";")Clean them:
df.columns = df.columns.str.strip()Convert to snake case:
df.columns = (
df.columns.str.strip()
.str.lower()
.str.replace(" ", "_")
)Use proper quoting with the csv module:
import csv
with open("data.csv", newline="") as file:
reader = csv.reader(file)For pandas, quoting is usually handled automatically:
df = pd.read_csv("data.csv")df["date"] = pd.to_datetime(
df["date"],
errors="coerce"
)Invalid dates become missing values.
This automatically closes the file:
with open("data.csv", newline="") as file:
reader = csv.reader(file)This prevents unwanted blank lines, especially on Windows:
open("data.csv", newline="")df.to_csv("output.csv", index=False)print(df.head())
print(df.shape)
print(df.dtypes)
print(df.isna().sum())Use a copy when needed:
filtered = df[df["score"] > 50].copy()Do not assume numbers and dates were loaded correctly:
df["amount"] = pd.to_numeric(df["amount"])
df["date"] = pd.to_datetime(df["date"])For example:
data/raw/input.csv
data/processed/cleaned.csv
Check required columns:
required = {"id", "name", "email"}
if not required.issubset(df.columns):
raise ValueError("Required columns are missing")Assume orders.csv contains:
order_id,product,quantity,price
1,Keyboard,2,25.50
2,Mouse,3,10.00
3,Keyboard,1,25.50Calculate total order value:
import pandas as pd
df = pd.read_csv("orders.csv")
df["total"] = df["quantity"] * df["price"]
print(df)Calculate product totals:
summary = (
df.groupby("product")["total"]
.sum()
.reset_index()
)
print(summary)Export the summary:
summary.to_csv("product_totals.csv", index=False)Expected result:
product total
0 Keyboard 76.50
1 Mouse 30.00
import csv
# Read
with open("data.csv", newline="") as file:
rows = list(csv.reader(file))
# Read dictionaries
with open("data.csv", newline="") as file:
rows = list(csv.DictReader(file))
# Write
with open("data.csv", "w", newline="") as file:
writer = csv.writer(file)
writer.writerow(["name", "age"])
writer.writerow(["Alice", 25])import pandas as pd
# Read
df = pd.read_csv("data.csv")
# Inspect
df.head()
df.info()
df.describe()
# Select
df["name"]
df[["name", "age"]]
# Filter
df[df["age"] > 18]
# Sort
df.sort_values("age")
# Group
df.groupby("city")["sales"].sum()
# Write
df.to_csv("output.csv", index=False)- Understand CSV structure
- Read files with
csv.reader - Read rows with
csv.DictReader - Write and append CSV files
- Learn pandas DataFrames
- Select columns and rows
- Filter and sort data
- Handle missing values
- Convert data types
- Group and summarize data
- Merge multiple files
- Process large CSV files in chunks
- Build complete cleaning and export workflows