R to CSV — write.csv · write_csv · fwrite — live generator

Save R to CSV
write.csv(), write_csv()
and fwrite() explained.

Choose your function, set the options — the R code updates live. Full comparison of base R write.csv(), readr write_csv() and data.table fwrite() — parameters, defaults and when to use each. No data leaves your browser.

Live R code generator
3 functions compared
fwrite() for large files
All parameters covered
Always free
R to CSV — configure and copy your write call  No data uploaded
Set your options — the R code updates live below
R — ready to paste
Three functions — which to use

Base R, readr and data.table each handle CSV writing differently. Here is the decision guide.

FunctionPackageRow names by defaultSpeedBest for
write.csv() base R Yes — add row.names=FALSE Baseline Scripts with no external dependencies, small files
write_csv() readr No row names by default 1.5–2× faster tidyverse workflows, consistent UTF-8 output
fwrite() data.table No row names by default 5–20× faster Large files (1M+ rows), multi-threaded performance
write.csv2() base R Yes Baseline European locale — uses ; as separator and , as decimal
write.csv() parameters — complete reference

The parameters you'll need most often, with defaults and when to change them.

ParameterDefaultWhen to change it
row.namesTRUESet row.names=FALSE in almost every case. Without it, base R writes the integer row names (1, 2, 3…) as an unnamed first column in the CSV.
sep','Change to '\t' for TSV or ';' for European locales. Note: write.csv2() uses ';' by default and ',' as decimal mark.
na"NA"Change to "" for empty cells, or "NULL" for database import. In write_csv() the parameter is na= with the same values.
appendFALSESet append=TRUE to add rows to an existing file. Pair with col.names=FALSE (write.csv) or append=TRUE (write_csv) to skip re-writing the header.
fileEncoding""Set fileEncoding="UTF-8" explicitly for cross-platform compatibility. R's default encoding is the system locale — on Windows this is often CP1252, which breaks non-ASCII characters on Mac/Linux.
quoteTRUEBase R quotes all character and factor columns by default. Set quote=FALSE for cleaner output when the data has no commas or newlines in text fields.
Common patterns

Recipes for the write-to-CSV situations that come up most often.

Append rows to an existing CSV
library(readr) output_path <- "output.csv" # First write — include header write_csv(df_first_batch, output_path) # Subsequent writes — append without header write_csv( df_next_batch, output_path, append = TRUE # write_csv handles col.names automatically )
write_csv(append=TRUE) omits the header automatically. With base R write.csv, use append=TRUE and col.names=FALSE together — otherwise the column names re-appear as a data row.
Write large data frame fast with fwrite()
library(data.table) # fwrite() is multi-threaded — typically 5-20x faster than write.csv() fwrite( df, file = "large_output.csv", sep = ",", na = "", row.names = FALSE, nThread = parallel::detectCores() # use all CPU cores ) # Split large file into chunks of 500k rows: chunk_size <- 500000L for (i in seq(1, nrow(df), by = chunk_size)) { chunk <- df[i:min(i + chunk_size - 1, nrow(df)), ] fwrite(chunk, sprintf("output_part_%d.csv", ceiling(i / chunk_size))) }
fwrite() uses parallel compression and writing threads. nThread=parallel::detectCores() uses all available CPU cores. Ideal for data frames over 1 million rows.
Save selected columns with formatted numbers
library(tidyverse) df %>% select(name, email, amount, created_at) %>% mutate( amount = round(amount, 2), # 2 decimal places created_at = format(created_at, "%Y-%m-%d") # ISO date ) %>% write_csv( "clean_export.csv", na = "NULL" )
Pipe the mutate() transformations directly into write_csv() — no need to create an intermediate object. format() on dates prevents write_csv writing full POSIXct timestamps.
Write multiple data frames to separate CSV files
library(tidyverse) # Split by a column and write one CSV per group df %>% group_by(region) %>% group_walk(~ write_csv(.x, paste0("export_", .y$region, ".csv"))) # Or use a named list of data frames: list_of_dfs <- list(sales = df_sales, returns = df_returns) iwalk(list_of_dfs, ~ write_csv(.x, paste0(.y, ".csv")))
group_walk() splits the data frame by group and calls the function for each. iwalk() iterates over a named list — .x is the data frame, .y is the name.
When do you need R to CSV?
Pipeline output

R analysis pipelines — data cleaning, modelling, aggregation — write their final output to CSV for handoff to reporting tools, databases or colleagues who don't use R. write_csv() is the standard final step.

Excel handoff

R results shared with colleagues who use Excel. Use fileEncoding="UTF-8" in write.csv() or write_csv() (handles UTF-8 automatically) to prevent garbled characters when the file is opened on Windows.

Large data export

fwrite() from data.table is 5–20× faster than write.csv() for large data frames. For data frames with millions of rows — genomics, log analysis, survey data — fwrite() is the only practical choice.

Reproducible research

R analysis scripts in academic research write cleaned and processed data to CSV as reproducibility artifacts. The CSV is included alongside the R script so reviewers can verify results without running the full pipeline.

Related CSV tools
Popular searches
r to csv save to csv r r write to csv write csv in r r export dataframe to csv write.csv r write_csv r r save dataframe to csv fwrite r csv r write csv without row names export data to csv in r r save csv tidyverse

Three functions compared,
one live generator. No row.names surprises.

The most common R-to-CSV mistake is forgetting row.names=FALSE in write.csv() — the integer row indices become an unnamed first column in every CSV. The generator above defaults to this correctly for every function. write_csv() and fwrite() skip row names by default.

The comparison table shows the real trade-off: write.csv() for zero dependencies, write_csv() for tidyverse consistency, fwrite() when performance matters. For files over a million rows, fwrite()'s multi-threaded writing is the only practical option.

Three functions covered
write.csv(), write_csv() and fwrite() — with a decision table so you choose the right one.
Live code generator
Switch between functions, set separator, NA string and append mode — the correct call generated instantly.
Four code patterns
Append mode, fwrite() with nThread, column selection with date formatting, and group_walk() multi-file export.
Free, no conditions
No account, no upload. Funded by display advertising only.
Frequently asked questions
How do I save a data frame to CSV in R?
Base R: write.csv(df, 'output.csv', row.names=FALSE). tidyverse: library(readr); write_csv(df, 'output.csv'). data.table: library(data.table); fwrite(df, 'output.csv'). Use write_csv() for most cases — no row names by default and consistent UTF-8 handling.
Why does write.csv() add an extra first column?
That's the row names. Base R's write.csv() writes row names by default — they appear as an unnamed first column (1, 2, 3…) in the CSV. Fix: write.csv(df, 'output.csv', row.names=FALSE). write_csv() and fwrite() skip row names by default.
What is the difference between write.csv and write_csv?
write.csv() is base R — always available, adds row names by default, always uses comma. write_csv() is from readr (tidyverse) — no row names by default, 1.5–2× faster, consistent UTF-8, column types written more reliably. For most use cases write_csv() is preferred unless you need zero package dependencies.
How do I write a large R data frame to CSV fast?
library(data.table); fwrite(df, 'output.csv'). fwrite() is multi-threaded and typically 5–20× faster than write.csv() for large data frames. Set nThread=parallel::detectCores() to use all CPU cores. For a 10M row data frame, fwrite() takes seconds where write.csv() takes minutes.
How do I append rows to an existing CSV in R?
With write_csv(): write_csv(df, 'output.csv', append=TRUE) — automatically skips the header. With write.csv(): write.csv(df, 'output.csv', append=TRUE, col.names=FALSE, row.names=FALSE) — the col.names=FALSE is critical to prevent duplicate headers.