Skip to the converter
Pandas to CSV

Guide

How to Save a Pandas DataFrame to CSV Without the Index

Open a fresh export in Excel and the first column is a run of numbers nobody asked for — 0, 1, 2, … in front of name, age,city. That column is the DataFrame's index, and pandas writes it becauseto_csv() defaults to index=True.

One argument turns it off:

df.to_csv("output.csv", index=False)
The whole fix: index=False keeps the row index out of the file.

index=False is a write-time switch. It decides what lands in the file and changes nothing about the DataFrame in memory — writing pandas to CSV without the index is a matter of overriding that one default. The converter below opens with exactly this configuration — download the file or copy the call it generates.

Why pandas adds an index column

Every DataFrame has an index — the labels down the left side when you print it. For a freshly built frame it is a RangeIndex: 0 through n−1, generated for you. When pandas serialises to CSV it writes those labels as the first column, because dropping data silently would violate the same principle that keepsread_csv() able to round-trip a file. The signature says so plainly:index=True.

The consequence is that the index column arrives whether it carries meaning or not. When the labels are dates, IDs or categories, that column is the point. When they are a forgotten RangeIndex, it is noise that breaks the next import — a colleague's script reads it as a column named Unnamed: 0, or the sheet simply looks wrong.

Before and after: what index=False changes

Written with pandas defaults — header row plus index column:

,name,age,city
0,Alice,25,Delhi
1,Bob,30,Mumbai
2,Charlie,28,Chandigarh
index=True (the default): the empty first header cell is the unnamed index.

Written with index=False:

name,age,city
Alice,25,Delhi
Bob,30,Mumbai
Charlie,28,Chandigarh
index=False: the file starts with the real columns, nothing else.

The data rows are identical. The only difference is whether the labels travelling with the frame are carried into the file — and the empty header cell that goes with them.

Try it in the converter

The tool opens with the sample above and the index turned off, so the preview and the generated code already show the file you want. Tick Add row numbers to reproduce the problem — the extra column appears in the preview and in the download — then untick it. The call updates live; copy it straight into your script.

Add your data

Comma-separated (CSV)
Upload a file, paste your data, or use Pandas code.

You can also paste data copied from a spreadsheet.

Try an example:

Your data stays in your browser. Nothing is uploaded.

CSV options

Every control maps to a real to_csv() parameter, and the defaults are tuned for clean CSV output.
Basic options
Name for the downloaded file and written in the generated code.
Keep the first row as column names (header=True).
Include the DataFrame index as the first column.
Commas are the usual choice for CSV files.
UTF-8 is standard. No encoding= argument needed.
More options— formatting, columns, quoting, line endings
Formatting
Choose how decimal numbers should be written. Integer columns are left as integers.
Choose how missing values should appear in the CSV.
Applies strftime format to date columns.
Columns
All 3 columns

Selected columns are written in order as columns=[…] in generated code.

Advanced CSV settings
Controls which fields are wrapped in quotes.
Character used to quote a field. Default is a double quote.
Needed when quoting is disabled and a value contains the separator.
End-of-line character at the end of each row.
Header name for the row numbers column when enabled.
Append adds rows to an existing file instead of replacing it.
Character used for decimal points (use "," for European formats).

Your CSV is ready

CSV ready·3 rows · 3 columns·65 B·UTF-8
Preview of the first 3 of 3 rows.
nameagecity
Alice25Delhi
Bob30Mumbai
Charlie28Chandigarh

Showing all 3 rows.

Long values are clipped to keep rows readable — hover to see full cell. The downloaded output.csv contains every value in full.

Pandas code

The df.to_csv() call that reproduces these settings in Python.
df.to_csv("output.csv", index=False)

Assumes your DataFrame is named df and that pandas is already imported.

Everything runs in this tab. Nothing is uploaded — the CSV you download is assembled here.

Four fixes for an unwanted index column

Fix 1 — write the file with index=False

The fix for the export itself. Put index=False in the call that writes the file and the column never reaches it:df.to_csv("output.csv", index=False). For a pandas-to-CSV export without the index, this single argument is the whole fix — and it is the only one most code needs.

Fix 2 — remove the index from the DataFrame with reset_index(drop=True)

Use this when the index should not exist anywhere — not in the file and not in the frame your next line of code touches:

df = df.reset_index(drop=True)
df.to_csv("output.csv", index=False)

The distinction matters: index=False changes only the file;reset_index() changes the DataFrame. And callingreset_index() without drop=True does the opposite of removing the index — it pushes the old labels into a new column, so writing afterwards can hand you the data twice. drop=True is what actually discards them.

Fix 3 — clean up a CSV that already has the column

The file on disk already contains the stray first column. Read it back into the index and rewrite without it:

df = pd.read_csv("output.csv", index_col=0)   # first column back into the index
df.to_csv("output.csv", index=False)           # rewrite, this time without it
index_col=0 is what tells read_csv() the leading column is the index, not data.

If the column was never an index to begin with — it is simply junk — drop it by name. pandas names an unnamed header cell Unnamed: 0 on the way back in, sodf.drop(columns=["Unnamed: 0"]) removes it anddf.to_csv(..., index=False) keeps it gone.

Fix 4 — keep the index, but name it

Sometimes the column is right and only the empty header cell is wrong. Name it instead of deleting it:

df.to_csv("output.csv", index_label="row_id")
row_id,name,age,city — the index column, labelled.

index_label only applies when the index is being written, so it combines withindex=True (the default) and never with index=False. The same control sits in the converter under More options → Index column name.

When you should keep the index

If the labels are real data — timestamps, order IDs, a date-indexed series — write them. The file should carry them, and the reader should be told how to get them back:

df.to_csv("output.csv")                       # index=True keeps the labels
back = pd.read_csv("output.csv", index_col=0)  # restored on the way in
A round trip that preserves the index: written on the way out, read with index_col=0.

Turn index=False on that frame and the round trip silently degrades: the file has no labels, so read_csv() builds a fresh RangeIndex from 0. The numbers may look identical — the original identity of each row is gone. Keep the index when it means something; drop it when it is just a counter.

Common mistakes

  • Calling reset_index() without drop=True and expecting the column to vanish. It moves the index into a column instead, and the default write can then produce two extra columns where you started with one.
  • Turning the index off once and assuming it is off for good.index=False belongs in every writing call — it is not a setting on the DataFrame. Keep it in the function that exports, not in an interactive edit.
  • Hand-deleting the column in Excel after each export. The file will be regenerated the same way every time; fix the call that writes it.
  • Renaming Unnamed: 0 after the fact. That works once, butindex_label prevents the unnamed cell from existing in the first place.
  • Assuming index=False survives the round trip. The labels are not in the file, so reading it back cannot restore them — write them when you need them later.

Frequently asked questions

Why does my CSV have an extra column of numbers?

Because to_csv() writes the DataFrame index by default: pandas sets index=True in the signature. With a plain RangeIndex that shows up as an unnamed first column of 0, 1, 2, … sitting in front of your real data.

Does index=False change my DataFrame?

No. It only changes what is written to the file. The DataFrame in memory keeps its index, so a later export without index=False will put the column straight back — the fix belongs in the call, not in a one-off edit.

What's the difference between index=False and reset_index()?

They act at different layers. index=False keeps the index in the DataFrame and leaves it out of the file. reset_index() moves the index into the DataFrame as a regular column — so writing afterwards with pandas defaults can give you the data twice. Use reset_index(drop=True) when you want the index gone from the DataFrame as well as the file.

How do I remove the index column from a CSV I already saved?

Read it back with the column pulled into the index, then rewrite: df = pd.read_csv("output.csv", index_col=0) followed by df.to_csv("output.csv", index=False). If the file was never meant to have that column at all, drop it by name instead: df.drop(columns=["Unnamed: 0"], inplace=True).

How do I keep the index but give it a name?

Pass index_label: df.to_csv("output.csv", index_label="row_id") writes row_id as the header of the index column instead of leaving the first header cell empty.

Why is my index gone after reading the file back?

A CSV written with index=False does not contain the original labels. pd.read_csv() builds a fresh RangeIndex numbered from 0. If the index matters, write with index=True and read with pd.read_csv("output.csv", index_col=0) to restore it.

Related guides

Reviewed against the pandas 3.0.6 DataFrame.to_csv() reference — 6 October 2026.