Q&A library Python

Why does my saved CSV have an extra unnamed column?

Python Beginner indexioto-csv

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.

Need this solved on your own data?

The library is free and always will be. When the problem is bigger than an answer, we take on the work.

Start a project