Pandas read_csv: Loading and Parsing CSV Files

The pd.read_csv() function is the workhorse of data loading in pandas. It does far more than just read comma-separated files—it can handle different separators, parse dates, skip rows, select columns, specify data types, and recover from malformed lines. Mastering its parameters keeps your data import clean and predictable. (pandas 2.3.3 used in examples below.)

Basic CSV Reading

Start with a simple example. Here’s a sample CSV file:

Name,Age,Salary,Start_Date,Notes
Alice,28,75000,2020-01-15,Active
Bob,35,85000,2019-03-20,Active
Charlie,42,95000,2018-05-10,On leave
David,29,72000,2021-07-01,Active
Eve,38,88000,2019-11-15,
Frank,31,78000,2020-12-01,New hire
,45,92000,2017-02-28,Malformed

Read it with pd.read_csv():

import pandas as pd

df = pd.read_csv("sample.csv")
print(df)

Output:

      Name  Age  Salary  Start_Date      Notes
0    Alice   28   75000  2020-01-15     Active
1      Bob   35   85000  2019-03-20     Active
2  Charlie   42   95000  2018-05-10   On leave
3    David   29   72000  2021-07-01     Active
4      Eve   38   88000  2019-11-15        NaN
5    Frank   31   78000  2020-12-01   New hire
6      NaN   45   92000  2017-02-28  Malformed

By default, pandas treats empty cells as NaN and infers the header from the first row.

Checking What You Loaded

Before doing analysis, verify the shape and data types:

print(df.shape)
print(df.info())

Output:

(7, 5)
<class 'pandas.core.frame.DataFrame'>
Data columns (total 5 columns):
 #   Column      Non-Null Count  Dtype 
---  ------      --------------  ----- 
 0   Name        6 non-null      object 
 1   Age         7 non-null      int64 
 2   Salary      7 non-null      int64 
 3   Start_Date  7 non-null      object 
 4   Notes       6 non-null      object

Shape tells you rows and columns. .info() shows data types and missing value counts. This is the first defense against reading the wrong data.

Specifying Data Types

By default, pandas guesses column types. Force specific types with the dtype parameter:

df = pd.read_csv("sample.csv", dtype={'Age': 'int64', 'Salary': 'float64'})
print(df.dtypes)

Output:

Name           object
Age             int64
Salary        float64
Start_Date     object
Notes          object
dtype: object

This is faster than letting pandas infer and safer than fixing types after the fact.

Parsing Date Columns

Dates often read as strings. Use parse_dates to convert them:

df = pd.read_csv("sample.csv", parse_dates=['Start_Date'])
print(df.dtypes)

Output:

Name                  object
Age                    int64
Salary                 int64
Start_Date    datetime64[ns]
Notes                 object
dtype: object

Pass a list of column names or indices. The date parser handles common formats automatically.

Selecting Specific Columns

Don’t load columns you won’t use. The usecols parameter loads only what you need:

df = pd.read_csv("sample.csv", usecols=['Name', 'Salary', 'Start_Date'])
print(df)

Output:

      Name  Salary  Start_Date
0    Alice   75000  2020-01-15
1      Bob   85000  2019-03-20
2  Charlie   95000  2018-05-10
3    David   72000  2021-07-01
4      Eve   88000  2019-11-15
5    Frank   78000  2020-12-01
6      NaN   92000  2017-02-28

This saves memory on large files and speeds up parsing.

Skipping and Limiting Rows

Skip rows at the start or limit the total rows read:

df = pd.read_csv("sample.csv", skiprows=1, nrows=3)
print(df)

Output:

     Alice  28  75000  2020-01-15    Active
0      Bob  35  85000  2019-03-20    Active
1  Charlie  42  95000  2018-05-10  On leave
2    David  29  72000  2021-07-01    Active

skiprows=1 skips the first data row (not the header). nrows=3 reads only 3 rows. Together they’re useful for testing on a large file before processing all of it.

Handling Different Separators

Not all data is comma-separated. Use the sep parameter for other delimiters:

# Semicolon-separated data
df = pd.read_csv("data.csv", sep=";")

# Tab-separated data
df = pd.read_csv("data.csv", sep="\t")

The default is sep=",". Pipe and other delimiters work the same way.

Handling Malformed Lines

By default, read_csv() raises an error on malformed rows. Change behavior with on_bad_lines:

df = pd.read_csv("sample.csv", on_bad_lines='skip')
print(df)

Output:

      Name  Age  Salary  Start_Date      Notes
0    Alice   28   75000  2020-01-15     Active
1      Bob   35   85000  2019-03-20     Active
2  Charlie   42   95000  2018-05-10   On leave
3    David   29   72000  2021-07-01     Active
4      Eve   38   88000  2019-11-15        NaN
5    Frank   31   78000  2020-12-01   New hire
6      NaN   45   92000  2017-02-28  Malformed

Options are 'error' (default), 'warn' (skip and warn), or 'skip' (skip silently).

The index Round-Trip Gotcha

When you write a DataFrame to CSV with .to_csv(), it includes the index by default. Reading it back creates an unwanted Unnamed: 0 column:

# Original data
df = pd.read_csv("sample.csv", nrows=3)

# Write with index (default)
df.to_csv("with_index.csv")

# Read back without index_col
df_back = pd.read_csv("with_index.csv")
print(df_back.columns)
# Index(['Unnamed: 0', 'Name', 'Age', 'Salary', 'Start_Date', 'Notes'], ...)

Fix it either way: write with index=False or read with index_col=0:

# Write without index
df.to_csv("without_index.csv", index=False)

# Read with index_col
df_back = pd.read_csv("with_index.csv", index_col=0)

Common Parameters at a Glance

  • sep: Delimiter (default ',')
  • dtype: Data types for columns
  • parse_dates: Columns to parse as dates
  • usecols: Subset of columns to load
  • skiprows: Rows to skip at the start
  • nrows: Maximum rows to read
  • na_values: Additional strings to treat as NaN
  • on_bad_lines: How to handle malformed rows ('error', 'warn', 'skip')
  • encoding: File encoding (default 'utf-8')

For the full parameter list and examples, see the pandas read_csv() reference.

Next Steps

Once you’ve loaded and inspected your data with .shape and .info(), use pandas indexing and selection to extract what you need for analysis.

How often does a CSV file surprise you after you load it—wrong columns, wrong types, or extra rows?