Skip to the converter
Pandas to CSV

Guide

How to Save a Pandas DataFrame to a CSV File

The whole operation is one method call on the DataFrame. Giveto_csv() a path, and the frame is written to disk as a CSV file:

df.to_csv("output.csv", index=False)
Creates output.csv in the current working directory and returns None.

That is how to save a Pandas DataFrame to CSV in the simplest case: the first argument is the destination, index=False keeps the row numbers out of the file, and everything else has a sensible default. The rest of this page covers the decisions that come next — where the file lands, what it contains, and how to confirm it is right.

The df.to_csv() call, argument by argument

The signature startsto_csv(path_or_buf=None, *, sep=",", na_rep="", float_format=None, columns=None, header=True, index=True, index_label=None, mode="w", …)— one positional destination, then keyword-only options. The destination accepts a filename string, a pathlib path, or an open file object. Omit it entirely and pandas stops writing files: the CSV text comes back as a str instead.

Not every argument gets an example below: the formatting ones — quoting,float_format, compression, lineterminator anddate_format — are worked through in theto_csv() options cookbook.

Two defaults shape every export. index=True writes the DataFrame's index as the first column, which is rarely what a shared CSV wants — seepandas to CSV without index for why and when to override it. header=True writes the column names as the first row, which is almost always what you want.

Saving to a file: filename and folder

A bare filename — "output.csv" — is written into the process's current working directory. Run the same script from a different folder and the file appears in a different place, which is the usual cause of "I saved it but cannot find it". Printos.getcwd() to see where that is, or use a path that does not depend on it:

df.to_csv("/home/me/exports/output.csv", index=False)   # absolute
df.to_csv("exports/output.csv", index=False)              # relative to the cwd

Creating parent directories before saving: pandas creates the target file, but it never creates missing directories. Calling df.to_csv("reports/2026/sales.csv")when reports/2026/ does not exist immediately raises:

FileNotFoundError: [Errno 2] No such file or directory: 'reports/2026/sales.csv'
pandas will not automatically create missing folders along the path.

The standard, modern Python pattern uses standard library pathlib.Path to ensure the destination folder exists before writing:

from pathlib import Path
import pandas as pd

df = pd.DataFrame({"product": ["A", "B"], "revenue": [1200, 3400]})

output_path = Path("reports/2026/q4/sales.csv")

# Create parent directory (and all ancestor folders) before saving
output_path.parent.mkdir(parents=True, exist_ok=True)

# to_csv accepts a Path object directly
df.to_csv(output_path, index=False)
mkdir(parents=True, exist_ok=True) creates missing folders idempotently.

Three details make this snippet robust:

  • output_path.parent targets the directory containing the file (reports/2026/q4), preventing you from accidentally creating a directory named after your CSV file.
  • parents=True behaves like mkdir -p in Unix: it creates every missing intermediate folder in the hierarchy, not just the leaf directory.
  • exist_ok=True prevents Python from raising FileExistsError if the directory already exists, making the script safe to run repeatedly.

to_csv() natively accepts pathlib.Path instances, so there is no need to convert output_path back to a string.

Try the workflow in the converter

The tool below opens on the Pandas code tab with a small DataFrame literal loaded — the same frame the examples on this page use. It parses in the browser, shows the preview, and generates the df.to_csv() call for the current options. Download the CSV to inspect the file, or copy the code into your script.

Add your data

Pandas DataFrame
Upload a file, paste your data, or use Pandas code.

Advanced: paste a supported pd.DataFrame(...) expression.

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 4 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 · 4 columns·88 B·UTF-8
Preview of the first 3 of 3 rows.
nameagecityscore
Alice25Delhi91.5
Bob30Mumbai87.25
Charlie28Chandigarh94.75

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.

The file is assembled in this tab and the code is generated by the same page — nothing is uploaded.

Overwriting and appending

mode="w" is the default: writing to a path that already exists replaces it, in full, with no prompt and no backup. Nothing is merged. If you meant to add rows to an existing file instead, that is mode="a" — a different problem with its own trap around header rows, covered inhow to append to a CSV file with Pandas.

Encoding and delimiter for the target app

The default encoding is UTF-8, which is correct almost everywhere. The notable exception is Excel on Windows, which needs a byte-order mark to render non-ASCII characters —df.to_csv("output.csv", index=False, encoding="utf-8-sig") writes one. The default separator is a comma; locales that use commas as decimal separators commonly read semicolon-delimited files, written with sep=";" and read back the same way.

Saving only selected columns

columns takes the subset to write, in the order you list it — the DataFrame itself is untouched:

df.to_csv("output.csv", index=False, columns=["name", "city"])
Only these two columns reach the file; the names must match the DataFrame exactly.

Saving only the first N rows

to_csv() has no nrows — that argument belongs toread_csv(), where it caps how much is read in. The writer has no equivalent: it writes whatever DataFrame it is given. To export a prefix, select the rows first and hand the result to to_csv():

df.head(100).to_csv("output.csv", index=False)
The first 100 data rows, plus the header row.

head(n) takes the first n rows. For a slice that is not a prefix — rows 50 to 150, say — positional slicing does the same job and produces the same file for the same rows:

df.iloc[:100].to_csv("output.csv", index=False)    # same as head(100)
df.iloc[50:150].to_csv("output.csv", index=False)   # a slice by position

The row count is just the number inside the selection, so exporting a different amount means changing 100 — there is no separate limit to keep in step. Both forms leave the DataFrame itself untouched; they write a smaller file, not a shorter frame.

Getting the CSV as text instead of a file

Leave the path out and the method returns the CSV text:csv_text = df.to_csv(index=False). Passing a path makes pandas write the file and return None — useful to remember before writingif df.to_csv(...):, which is falsy even when the write succeeded.

Verify the file you wrote

The cheapest check is a round trip through the reader:

df.to_csv("output.csv", index=False)
back = pd.read_csv("output.csv")

assert list(back.columns) == list(df.columns)
assert len(back) == len(df)
Column names and row count are the two things that most often go wrong.

For a one-off script, opening the file is enough — but the assert above catches the quiet failures: a stale file left behind because the script crashed before writing, a wrong delimiter, or an index column that snuck in because index=False was left off.

Common mistakes

  • Letting the index ride along. index=True is the pandas default; most shared files want index=False. Seethe without-index guide for the full picture.
  • Assuming the directory comes with the file. pandas raisesFileNotFoundError when the parent folder is missing — create it first.
  • Expecting to be asked before overwriting. mode="w"truncates silently. Keep intermediate files under different names if you need history.
  • Saving over a file another program holds open. Windows locks it and the write fails with PermissionError — close the workbook first. A read-only file or a folder without write access fails the same way; the FAQ above covers telling the causes apart.
  • Judging success by the return value. None means it worked, not that it did not. Check the file or read it back.
  • Passing your own file handle without newline="". When you supply an open file rather than a path, open it with newline="" or line endings get translated twice.

Frequently asked questions

Which arguments does df.to_csv() actually need?

One: the path. df.to_csv("output.csv") writes the file with every other argument at its default. Everything else — index, header, separator, encoding — only appears in the call when you want to change what the defaults produce.

Where does the file get written?

Relative paths resolve against the current working directory of the Python process: the folder you launched the script from, or the notebook's folder. Print os.getcwd() to see it, or pass an absolute path such as "/home/me/exports/output.csv" to remove the ambiguity.

Why do I get FileNotFoundError when the filename is fine?

pandas creates the file, not the directory around it. df.to_csv("exports/output.csv") fails if exports/ does not exist. Create it first: from pathlib import Path; Path("exports").mkdir(parents=True, exist_ok=True), or os.makedirs("exports", exist_ok=True).

Why does writing fail with PermissionError?

PermissionError means the operating system refused the write, and the cause is not always the same. The common one on Windows is a file lock: Excel, an antivirus scanner, or a sync tool such as OneDrive holds output.csv open, so pandas cannot replace it. Close the file in whatever has it, or write to a temporary name and move it into place afterwards. The other frequent cause is filesystem permissions — the account running the script may not have write access to the folder, or the existing file may be marked read-only. There is no single fix: identify which of the two you have before changing code.

Did my file actually get written? How do I check?

Not by the return value: to_csv() returns None on success, so "if df.to_csv(...) is truthy" is never true. Check the file itself — Path("output.csv").exists(), or read it back with pd.read_csv("output.csv") and compare the columns you expect.

Do I need to close the file after to_csv()?

No, when you pass a path. pandas opens and closes the file itself. Only a handle you opened yourself needs closing — and if you pass a file object, open it with newline="" so line endings are not translated twice.

Specialized guides

The two problems that come up next have their own pages, each with a converter already configured for the task:

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