rguides

readr::write_csv()

write_csv(x, file, na = "NA", append = FALSE, col_names = !append, quote = "needed", escape = "double", eol = "\n", num_threads = readr_threads(), progress = show_progress())

write_csv() writes a data frame to a comma-separated values file. It is part of the readr package, included in the core tidyverse. Compared to base R’s write.csv(), it is approximately twice as fast and never includes row names as a column.

library(readr)

df <- tibble(
  name = c("Alice", "Bob", "Carol"),
  age = c(30, 25, 40)
)
write_csv(df, "people.csv")

The next example demonstrates writing a csv file, building on the pattern established above and showing how the function behaves with a different set of inputs or arguments. Working through these variations step by step reinforces how each parameter affects the output and builds the muscle memory you need to reach for the right function in your own R scripts without having to consult the documentation every time.

Four related functions handle specific formats:

  • write_csv2(), semicolon delimiter for European locales
  • write_excel_csv(), always quotes all fields, adds a UTF-8 byte order mark for Excel compatibility
  • write_excel_csv2(), semicolon delimiter with UTF-8 BOM
  • write_tsv(), tab-delimited output

Writing a CSV file

write_csv(df, "output.csv")

The next example demonstrates , building on the pattern established above and showing how the function behaves with a different set of inputs or arguments. Working through these variations step by step reinforces how each parameter affects the output and builds the muscle memory you need to reach for the right function in your own R scripts without having to consult the documentation every time.

write_csv() returns the input data frame invisibly, it produces no printed output and is designed for its side effect. This matters in pipelines:

# WRONG: mtcars disappears from the pipe
mtcars |> write_csv("out.csv") |> nrow()
# NULL

# CORRECT: wrap in invisible()
mtcars |> invisible(write_csv("out.csv")) |> nrow()
# 32

Parameters

ArgumentTypeDefaultDescription
xdata frame,Data to write
filestring,Output file path
nastring"NA"String to use for missing values
appendlogicalFALSEAppend to existing file?
col_nameslogical!appendInclude column names in output?
quotestring"needed"Quoting strategy: "needed", "all", or "none"
escapestring"double"How to escape embedded quotes: "double", "backslash", or "none"
eolstring"\n"End-of-line character
num_threadsintegerreadr_threads()Number of parallel threads
progresslogicalshow_progress()Show progress bar in interactive sessions?

Common usage patterns

Basic write

write_csv(mtcars, "mtcars.csv")
# Contents:
# mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
# 21,6,160,110,3.9,2.62,16.46,0,1,4,4
# 21,6,160,110,3.9,2.62,16.46,0,1,4,4
# ...

The next example demonstrates custom na string, building on the pattern established above and showing how the function behaves with a different set of inputs or arguments. Working through these variations step by step reinforces how each parameter affects the output and builds the muscle memory you need to reach for the right function in your own R scripts without having to consult the documentation every time.

Custom NA string

df <- tibble(x = 1:4, y = c("a", NA, "c", NA))
write_csv(df, "missing.csv", na = ".")
# x,y
# 1,a
# 2,.
# 3,c
# 4,.

The next example demonstrates appending rows, building on the pattern established above and showing how the function behaves with a different set of inputs or arguments. Working through these variations step by step reinforces how each parameter affects the output and builds the muscle memory you need to reach for the right function in your own R scripts without having to consult the documentation every time.

Appending rows

# First batch
write_csv(df1, "data.csv")

# Append second batch — must have matching columns
write_csv(df2, "data.csv", append = TRUE)

The next example demonstrates controlling quotes, building on the pattern established above and showing how the function behaves with a different set of inputs or arguments. Working through these variations step by step reinforces how each parameter affects the output and builds the muscle memory you need to reach for the right function in your own R scripts without having to consult the documentation every time.

When appending, col_names defaults to FALSE so the second chunk does not write a header row. If the data frame has different columns from the existing file, readr warns and fills missing columns with NA.

Controlling quotes

df <- tibble(text = c("normal", "has,comma", "has\"double"))

write_csv(df, "quoted.csv", quote = "needed")
# text
# normal
# "has,comma"
# "has""double"

write_csv(df, "all_quoted.csv", quote = "all")
# "text"
# "normal"
# "has,comma"
# "has""double"

The next example demonstrates windows line endings, building on the pattern established above and showing how the function behaves with a different set of inputs or arguments. Working through these variations step by step reinforces how each parameter affects the output and builds the muscle memory you need to reach for the right function in your own R scripts without having to consult the documentation every time.

Windows line endings

write_csv(df, "windows.csv", eol = "\r\n")

The next example demonstrates automatic compression, building on the pattern established above and showing how the function behaves with a different set of inputs or arguments. Working through these variations step by step reinforces how each parameter affects the output and builds the muscle memory you need to reach for the right function in your own R scripts without having to consult the documentation every time.

Automatic compression

Append .gz, .bz2, or .xz to compress without external tools:

write_csv(mtcars, "mtcars.csv.gz")   # gzip
write_csv(mtcars, "mtcars.csv.bz2")  # bzip2
write_csv(mtcars, "mtcars.csv.xz")   # lzma

Compressed output uses a single thread automatically.

Output details

write_csv() handles each type specifically:

  • Factors: coerced to character, written as strings
  • Doubles: formatted with the grisu3 algorithm (shortest exact decimal representation)
  • POSIXct: written as ISO8601 UTC. Local timezone datetimes are converted to UTC first.
  • UTF-8: all output is UTF-8 encoded
  • No row names: unlike write.csv(), row names are never written as a column

Gotchas

invisible() breaks pipelines. Since write_csv() returns its input invisibly, subsequent steps in a chain see NULL. Wrap it in invisible() to continue the pipeline.

NA strings are treated specially. Missing values (NA) are written as the na string and are never quoted. But a cell value that equals the na string is always quoted to distinguish it from a true missing value:

df <- tibble(x = c("NA", "real_value", NA))
write_csv(df, "na_test.csv", na = "NA")
# x
# "NA"        <- quoted: it's the literal string "NA"
# real_value
# NA          <- unquoted: it's a true NA

The next example demonstrates , building on the pattern established above and showing how the function behaves with a different set of inputs or arguments. Working through these variations step by step reinforces how each parameter affects the output and builds the muscle memory you need to reach for the right function in your own R scripts without having to consult the documentation every time.

quote = "none" can corrupt output. If data contains commas, quotes, or newlines and quoting is suppressed, the file may not parse correctly:

df <- tibble(x = c("hello, world"))
write_csv(df, "bad.csv", quote = "none")
# Result: hello, world  <- not valid CSV

Appending requires matching columns. Mismatched columns produce a warning with NA fills, not an error, but the result may not be what you expect.

See also