Writing Data: to_csv & to_json
Save a DataFrame as CSV or JSON — why index=False matters, the useful to_csv options, and every to_json orient explained.
After cleaning the IPL data or calculating the Orange Cap table, you need to save the result — to share with a teammate, load into Excel, or send from a backend API. Every read_* function has a matching to_* method on the DataFrame. The two you'll use most are to_csv (for files and spreadsheets) and to_json (for web APIs and JavaScript frontends).
Definition
DataFrame.to_csv()andDataFrame.to_json()are export methods that convert a DataFrame into CSV or JSON text. Given a path (or file-like object) they write that text to it and returnNone; called without a path they return the text as a string.
- Export method — called on the DataFrame itself:
df.to_csv(...), notpd.to_csv(...). - Path or file-like object — a filename like
"orange_cap.csv", or anio.StringIObuffer (which is how we demonstrate writing in the browser). - Without a path — handy for quickly printing what the file would contain.
Syntax
python
df.to_csv(path_or_buf=None, sep=",", index=True, header=True, columns=None,
na_rep="", float_format=None, mode="w", encoding="utf-8")
df.to_json(path_or_buf=None, orient=None, lines=False, indent=None, date_format=None)| Parameter | Meaning | Default |
|---|---|---|
path_or_buf | file path or file-like object; None returns a string | None |
index (to_csv) | write the row index as the first column | True |
sep | separator character | "," |
header | write column names (False to skip) | True |
columns | write only these columns, in this order | all |
na_rep | text to write for missing values | "" |
float_format | format for floats, e.g. "%.2f" | None |
mode | "w" overwrite, "a" append to an existing file | "w" |
orient (to_json) | JSON layout: "columns", "records", "index", "split", "values", "table" | "columns" |
lines (to_json) | one JSON object per line (needs orient="records") | False |
indent (to_json) | spaces of indentation for pretty output | None |
date_format (to_json) | "iso" writes dates as 2024-05-26T00:00:00.000 | epoch milliseconds |
Example — to_csv and the index=False rule
python
import pandas as pd
import io
df = pd.DataFrame({
"player": ["Virat Kohli", "Travis Head", "Sunil Narine"],
"team": ["RCB", "SRH", "KKR"],
"runs": [741, 567, 488],
"strike_rate": [154.7, 191.55, 180.74],
})
print(df.to_csv()) # default: index written too
print(df.to_csv(index=False)) # usually what you wanttext
,player,team,runs,strike_rate
0,Virat Kohli,RCB,741,154.7
1,Travis Head,SRH,567,191.55
2,Sunil Narine,KKR,488,180.74
player,team,runs,strike_rate
Virat Kohli,RCB,741,154.7
Travis Head,SRH,567,191.55
Sunil Narine,KKR,488,180.74
How it works: by default the row index (0, 1, 2) is written as an unnamed first column — notice the line starts with an empty name: ,player,.... When you read that file back, the index returns as a junk column:
python
back = pd.read_csv(io.StringIO(df.to_csv()))
print(back)text
Unnamed: 0 player team runs strike_rate
0 0 Virat Kohli RCB 741 154.70
1 1 Travis Head SRH 567 191.55
2 2 Sunil Narine KKR 488 180.74That Unnamed: 0 column is the most famous pandas annoyance. Rule: use index=False unless your index carries real information (like player names after set_index). If you receive such a file, pd.read_csv(path, index_col=0) puts the column back as the index.
Example — writing into a file-like object
On your computer you would write df.to_csv("orange_cap.csv", index=False) and a file appears next to your notebook. In the browser we write into an io.StringIO "file" and then read its contents:
python
buffer = io.StringIO()
df.to_csv(buffer, index=False)
print(buffer.getvalue())text
player,team,runs,strike_rate
Virat Kohli,RCB,741,154.7
Travis Head,SRH,567,191.55
Sunil Narine,KKR,488,180.74
Example — more to_csv options
python
print(df.to_csv(index=False, sep="|", columns=["player", "runs"]))
print(df.to_csv(index=False, float_format="%.1f"))
missing = pd.DataFrame({"player": ["MS Dhoni", "Jasprit Bumrah"], "runs": [161, None]})
print(missing.to_csv(index=False, na_rep="DNB"))text
player|runs
Virat Kohli|741
Travis Head|567
Sunil Narine|488
player,team,runs,strike_rate
Virat Kohli,RCB,741,154.7
Travis Head,SRH,567,191.6
Sunil Narine,KKR,488,180.7
player,runs
MS Dhoni,161.0
Jasprit Bumrah,DNB
How it works: columns picks (and orders) the columns, sep changes the separator, float_format="%.1f" rounds floats to one decimal only in the file (the DataFrame is unchanged), and na_rep writes a marker instead of an empty cell. mode="a" with header=False appends new rows to an existing file — useful for adding each new match result to a season log.
Example — to_json and its orients
The orient decides the JSON shape. The same two-row table, six ways:
python
df = pd.DataFrame({"player": ["Virat Kohli", "Travis Head"],
"team": ["RCB", "SRH"], "runs": [741, 567]})
for o in ["columns", "records", "index", "split", "values"]:
print(o, "->", df.to_json(orient=o))text
columns -> {"player":{"0":"Virat Kohli","1":"Travis Head"},"team":{"0":"RCB","1":"SRH"},"runs":{"0":741,"1":567}}
records -> [{"player":"Virat Kohli","team":"RCB","runs":741},{"player":"Travis Head","team":"SRH","runs":567}]
index -> {"0":{"player":"Virat Kohli","team":"RCB","runs":741},"1":{"player":"Travis Head","team":"SRH","runs":567}}
split -> {"columns":["player","team","runs"],"index":[0,1],"data":[["Virat Kohli","RCB",741],["Travis Head","SRH",567]]}
values -> [["Virat Kohli","RCB",741],["Travis Head","SRH",567]]| orient | Shape | Best for |
|---|---|---|
"columns" (default) | {column: {index: value}} | round-tripping with read_json |
"records" | [{column: value}, ...] — one object per row | web APIs / JavaScript (most common) |
"index" | {index: {column: value}} | lookup by row label |
"split" | {"columns": [...], "index": [...], "data": [[...]]} | compact, keeps index + columns |
"values" | [[...], [...]] — just the data | charts / matrices |
"table" | {"schema": {...}, "data": [...]} | data + type information |
Add indent=2 for pretty, human-readable JSON, and orient="records", lines=True to write JSON Lines. When the DataFrame has dates, pass date_format="iso" — the default writes dates as epoch milliseconds (e.g. 1716681600000), and pandas 3 warns that this default is deprecated.
Common mistakes
- ❌
df.to_csv("out.csv")and then anUnnamed: 0column on re-reading. ✅df.to_csv("out.csv", index=False). - ❌
pd.to_csv(df, ...). ✅ It is a DataFrame method:df.to_csv(...). - ❌
x = df.to_csv("out.csv")and expecting text inx. ✅ With a path it writes the file and returnsNone; call it without a path to get a string. - ❌ Sending
orient="columns"JSON to a frontend that expects a list of rows. ✅ Useorient="records". - ❌ Dates in JSON look like
1716681600000. ✅ Passdate_format="iso".
Interview tip
Q: Why do you often see an Unnamed: 0 column after reading a CSV?
A: The file was written with to_csv() using the default index=True, so the old index was saved as an unnamed first column. Either write with index=False, or read with index_col=0 to turn that column back into the index.
Key points to remember
- df.to_csv() / df.to_json() write to a path or file-like object; without a path they return a string.
- to_csv writes the index by default — use index=False to avoid an 'Unnamed: 0' column on re-reading.
- to_csv options: sep, columns, header, na_rep, float_format and mode='a' for appending.
- to_json orient controls the shape: 'records' (list of row objects) is the usual choice for APIs.
- 'columns' is the default orient; 'split', 'index', 'values' and 'table' are the other layouts.
- indent=2 makes JSON readable; lines=True with orient='records' writes JSON Lines.
- Use date_format='iso' so dates are written as readable ISO strings, not epoch milliseconds.
Try it yourself
to_csv with and without the index
Write into a file-like object and read it back
to_json orients side by side
Practice: export the MI squad for a website
Revision Flashcards
Tap a card to flip it and see the answer.
