read_csv Parameters in Depth
Control exactly what read_csv loads: index_col, usecols, dtype, parse_dates, na_values, nrows, skiprows, encoding and thousands.
A real IPL matches file can have 20+ columns and thousands of rows, some cells marked - or DNB (did not bat), dates stored as text, and IDs that should never be treated as numbers. Cleaning all of that after loading is possible — but read_csv can do most of it while loading, with a few parameters. Loading only what you need is also faster and uses less memory.
All examples in this topic use this small match-results file:
python
import pandas as pd
import io
matches_csv = """match_id,date,season,team1,team2,winner,venue,margin
1001,2024-03-22,2024,CSK,RCB,CSK,Chennai,6
1002,2024-03-23,2024,PBKS,DC,PBKS,Mohali,4
1003,2024-03-23,2024,KKR,SRH,KKR,Kolkata,4
1004,2024-03-24,2024,RR,LSG,RR,Jaipur,20
1005,2024-03-24,2024,GT,MI,GT,Ahmedabad,6
1006,2024-03-25,2024,RCB,PBKS,RCB,Bengaluru,4
"""Definition
read_csv parameters are keyword arguments that tell pandas which rows and columns to load, what type each column should be, and which text should count as missing, so the DataFrame comes out clean in a single step.
- Which rows —
nrows(only the first n),skiprows(skip some). - Which columns —
usecols(keep only some),index_col(turn one into the row labels). - What type —
dtype(force a type),parse_dates(turn text into real dates),thousands(strip separators from numbers). - What is missing —
na_values(extra markers that mean "no value"),keep_default_na. - How the bytes are decoded —
encoding.
Syntax
python
pd.read_csv(path, index_col=None, usecols=None, dtype=None, parse_dates=None,
na_values=None, keep_default_na=True, nrows=None, skiprows=None,
encoding="utf-8", thousands=None)| Parameter | Meaning | Default |
|---|---|---|
index_col | column name or position to use as the row index | None (new RangeIndex) |
usecols | list of column names (or positions) to load; others are skipped | None (all) |
dtype | one type for all columns, or a dict {"col": type} | None (infer) |
parse_dates | list of columns to convert to datetime64 | None |
na_values | extra strings to treat as missing — a list, or a dict per column | None |
keep_default_na | also treat the built-in markers ("", NA, NaN, null, N/A…) as missing | True |
nrows | read only the first n data rows | None (all) |
skiprows | int → skip that many lines at the top; list → skip those line numbers | None |
encoding | how bytes become characters ("utf-8", "latin-1", "cp1252") | "utf-8" |
thousands | thousands separator inside numbers, e.g. "," | None |
Example — index_col and usecols
python
df = pd.read_csv(io.StringIO(matches_csv), index_col="match_id")
print(df.head(3))
df = pd.read_csv(io.StringIO(matches_csv), usecols=["match_id", "team1", "team2", "winner"])
print(df)text
date season team1 team2 winner venue margin
match_id
1001 2024-03-22 2024 CSK RCB CSK Chennai 6
1002 2024-03-23 2024 PBKS DC PBKS Mohali 4
1003 2024-03-23 2024 KKR SRH KKR Kolkata 4
match_id team1 team2 winner
0 1001 CSK RCB CSK
1 1002 PBKS DC PBKS
2 1003 KKR SRH KKR
3 1004 RR LSG RR
4 1005 GT MI GT
5 1006 RCB PBKS RCBHow it works: index_col="match_id" turns that column into the row labels — notice match_id printed on its own line above the index, and it is no longer a normal column. Now df.loc[1004] finds match 1004 directly. usecols loads only the four listed columns; the others are never even stored in memory. usecols also accepts positions, e.g. usecols=[0, 5].
Example — dtype and parse_dates
python
df = pd.read_csv(io.StringIO(matches_csv))
print(df.dtypes)
df = pd.read_csv(io.StringIO(matches_csv),
dtype={"match_id": "str", "season": "category", "margin": "float64"},
parse_dates=["date"])
print(df.dtypes)
print(df["date"].dt.day_name().tolist())text
match_id int64
date str
season int64
team1 str
team2 str
winner str
venue str
margin int64
dtype: object
match_id str
date datetime64[us]
season category
team1 str
team2 str
winner str
venue str
margin float64
dtype: object
['Friday', 'Saturday', 'Saturday', 'Sunday', 'Sunday', 'Monday']How it works: without help, date is just text (str) — you cannot ask it for a weekday. parse_dates=["date"] converts it to a real datetime column, unlocking the .dt accessor. The dtype dict forces types: match_id as text (IDs are labels, not quantities — you never add two match IDs), season as category (few repeated values → less memory), and margin as float.
Note (pandas 3): parsed dates show as
datetime64[us](microsecond resolution). Older pandas always useddatetime64[ns]. For everyday work they behave the same.
Example — na_values: custom "missing" markers
Scorecards often write - or DNB instead of leaving a cell empty:
python
scores = """player,team,runs,wickets
Virat Kohli,RCB,741,-
Jasprit Bumrah,MI,DNB,20
MS Dhoni,CSK,161,NA
Harshal Patel,PBKS,,24
"""
df = pd.read_csv(io.StringIO(scores))
print(df)
print(df.dtypes)
df = pd.read_csv(io.StringIO(scores), na_values=["-", "DNB"])
print(df)
print(df.dtypes)text
player team runs wickets
0 Virat Kohli RCB 741 -
1 Jasprit Bumrah MI DNB 20
2 MS Dhoni CSK 161 NaN
3 Harshal Patel PBKS NaN 24
player str
team str
runs str
wickets str
dtype: object
player team runs wickets
0 Virat Kohli RCB 741.0 NaN
1 Jasprit Bumrah MI NaN 20.0
2 MS Dhoni CSK 161.0 NaN
3 Harshal Patel PBKS NaN 24.0
player str
team str
runs float64
wickets float64
dtype: objectHow it works: by default, empty cells and markers like NA are already read as NaN. But - and DNB are unknown text, so the whole column became str — you can't even sum the runs. Adding them to na_values turns them into NaN, and the columns become numeric. They are float64 (not int64) because NaN is a float. You can also target columns: na_values={"runs": ["DNB"], "wickets": ["-"]}.
The reverse problem exists too: a team code that is literally "NA" would be read as missing. keep_default_na=False switches the built-in markers off.
Example — nrows and skiprows (a list)
python
print(pd.read_csv(io.StringIO(matches_csv), nrows=2))
print(pd.read_csv(io.StringIO(matches_csv), skiprows=[1, 2]))text
match_id date season team1 team2 winner venue margin
0 1001 2024-03-22 2024 CSK RCB CSK Chennai 6
1 1002 2024-03-23 2024 PBKS DC PBKS Mohali 4
match_id date season team1 team2 winner venue margin
0 1003 2024-03-23 2024 KKR SRH KKR Kolkata 4
1 1004 2024-03-24 2024 RR LSG RR Jaipur 20
2 1005 2024-03-24 2024 GT MI GT Ahmedabad 6
3 1006 2024-03-25 2024 RCB PBKS RCB Bengaluru 4How it works: nrows=2 stops after two data rows — great for peeking at a 2 GB file without loading it all. skiprows=[1, 2] skips file lines 1 and 2 (line 0 is the header, so the header survives and the first two matches are dropped).
Example — encoding and thousands
A file saved by old Windows/Excel software may not be UTF-8. Reading it with the default encoding fails:
python
raw = "player,team\nAnrich Nortjé,DC\nKagiso Rabada,PBKS\n".encode("latin-1")
try:
pd.read_csv(io.BytesIO(raw))
except UnicodeDecodeError as e:
print("UnicodeDecodeError:", e)
print(pd.read_csv(io.BytesIO(raw), encoding="latin-1"))
prize = 'team,prize_money\nKKR,"20,00,00,000"\nSRH,"12,50,00,000"\n'
print(pd.read_csv(io.StringIO(prize), thousands=","))text
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 24: invalid continuation byte
player team
0 Anrich Nortjé DC
1 Kagiso Rabada PBKS
team prize_money
0 KKR 200000000
1 SRH 125000000How it works: the byte 0xe9 is é in Latin-1 but is not valid UTF-8, so pandas stops. Telling it encoding="latin-1" (or "cp1252" for Windows files) decodes it correctly. thousands="," removes the separators inside "20,00,00,000", so prize_money becomes int64 instead of text. (Indian-style grouping works too — pandas just deletes every comma inside the number.)
Quick reference — which parameter fixes which problem?
| Problem after loading | Parameter |
|---|---|
| Too many columns, file is slow | usecols=[...] |
| Only want to peek at a huge file | nrows=1000 |
| A column should be the row labels | index_col="match_id" |
| Dates are plain text | parse_dates=["date"] |
| IDs/PIN codes lose leading zeros or become numbers | dtype={"pin": "str"} |
Numeric column is str because of -, DNB, ? | na_values=["-", "DNB", "?"] |
"NA" is a real value, not missing | keep_default_na=False |
UnicodeDecodeError | encoding="latin-1" or "cp1252" |
Numbers like 1,250 read as text | thousands="," |
Common mistakes
- ❌ A numeric column shows dtype
str, so.sum()fails. ✅ Some cell contains a non-number marker — find it and add it tona_values. - ❌ Expecting
int64for a column with missing values. ✅NaNis a float, so the column becomesfloat64(or usedtype={"runs": "Int64"}— capital I — for a nullable integer). - ❌
skiprows=2when you meant "skip the 2nd data row". ✅ An int skips that many lines from the top; use a list likeskiprows=[2]to skip a specific line. - ❌ Doing date math on a text column. ✅ Use
parse_dates(orpd.to_datetimelater).
Interview tip
Q: How would you load only 3 columns and the first 10,000 rows of a 5 GB CSV?
A: pd.read_csv(path, usecols=["a", "b", "c"], nrows=10_000). Both filters are applied while reading, so pandas never builds the full table in memory. For processing the whole file in pieces, chunksize=100_000 returns an iterator of smaller DataFrames.
Key points to remember
- index_col turns a column into the row index; usecols loads only the listed columns.
- dtype forces column types (a dict per column); IDs are best stored as str, repeated labels as category.
- parse_dates converts text columns into datetime64 so the .dt accessor works.
- na_values adds extra missing-value markers like '-' or 'DNB'; keep_default_na=False disables the built-in ones.
- nrows reads only the first n rows; skiprows takes an int (lines from the top) or a list of line numbers.
- encoding='latin-1' or 'cp1252' fixes UnicodeDecodeError on non-UTF-8 files; thousands=',' cleans '1,250'.
- A numeric column with NaN becomes float64; use 'Int64' for a nullable integer.
Try it yourself
index_col, usecols and nrows
dtype and parse_dates
na_values turns '-' and 'DNB' into NaN
Practice: clean the prize-money file while loading
Revision Flashcards
Tap a card to flip it and see the answer.
