Why does my saved CSV have an extra unnamed column?
Short answer
to_csv writes the DataFrame index as a column by default, which reappears as Unnamed 0 when you read the file back. Pass index=False when saving, or index_col=0 when reading.
The short version
to_csv writes the index as the first column by default. When you read that file back, the index becomes an ordinary column named Unnamed: 0. Pass index=False when saving, and the problem never arises.
The cause and the fix
import pandas as pd
df = pd.DataFrame({"city": ["Leeds"], "amount": [10]})
df.to_csv("out.csv") # writes: ,city,amount -> 0,Leeds,10
df.to_csv("out.csv", index=False) # writes: city,amount -> Leeds,10
The first form writes the index (0, 1, 2...) as an unnamed leading column. Reading it back gives you a column called Unnamed: 0 full of row numbers you never wanted.
Reading a file that already has it
If you have inherited such a file, tell the reader that the first column is the index.
pd.read_csv("out.csv", index_col=0)
Or read it normally and drop the column.
pd.read_csv("out.csv").drop(columns=["Unnamed: 0"])
When you should keep the index
index=False is right when the index is just a row counter, which is the usual case. But if the index carries meaning, such as dates in a time series or a meaningful key you set deliberately, then it is data and should be written.
ts.to_csv("series.csv") # keep the dates
pd.read_csv("series.csv", index_col=0, parse_dates=True)
The question to ask is simply: does the index hold information that is not already in a column? If not, do not write it.
The compounding problem
The nastiest version of this is a pipeline that reads and writes CSV repeatedly without index=False. Each round trip adds another unnamed column, so after three passes you have Unnamed: 0, Unnamed: 0.1 and Unnamed: 0.2 cluttering the file. Seeing those names is a sure sign of a pipeline saving its index by mistake.
Other useful to_csv arguments
df.to_csv(
"out.csv",
index=False,
float_format="%.2f", # limit decimals in the output
na_rep="", # how missing values appear
encoding="utf-8",
)
float_format affects only the file, not your data, which is the right place to control precision.
Or avoid CSV entirely
If the file is only being passed between steps of your own pipeline, Parquet stores the index handling and every dtype correctly, and never produces a stray column. CSV's lack of a schema is the underlying reason this problem exists at all.