Writing Data to Multiple Formats
Export data frames to CSV, Excel, and RDS with write_csv() and writexl.
Writing Data to Multiple Formats is a free R Academy lesson on CoddyKit. This is lesson 4 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the R Academy learning path, and your progress syncs across the web and the CoddyKit app. The R Academy course includes 4 lessons in total.
Writing Data from R
Reading data is only half the story — you also need to save results. R supports multiple output formats: CSV for universal compatibility, RDS for preserving R objects exactly, Parquet for high-performance analytics, and more. Choose the format based on your audience and use case.
library(readr)
# Create a sample data frame to write
df <- data.frame(
name = c('Alice','Bob','Carol'),
score = c(85.5, 92.0, 78.3),
grade = c('B','A','C'),
passed = c(TRUE, TRUE, TRUE)
)
print(df)
cat('\nWe will save this in multiple formats!')write_csv() — Save as CSV
write_csv(df, 'file.csv') saves a data frame as a comma-separated file. It does NOT write row names by default (unlike write.csv()). The result is always UTF-8 encoded. It invisibly returns the data frame, so it can be used in pipes.
library(readr)
df <- data.frame(
name = c('Alice','Bob','Carol'),
score = c(85, 92, 78)
)
# Write to /tmp for demonstration
write_csv(df, '/tmp/students.csv')
# Verify by reading back
read_csv('/tmp/students.csv', show_col_types=FALSE)write_csv() Options
write_csv() accepts several options: na='NA' controls how NAs are written, append=TRUE appends to an existing file, col_names=FALSE omits the header, and quote controls when to quote fields.
library(readr)
df <- data.frame(
name = c('Alice','Bob'),
city = c('New York','Los,Angeles'), # Contains comma
score = c(85, NA)
)
# na='' writes NA as empty string (common Excel convention)
write_csv(df, '/tmp/test_write.csv', na='')
cat(paste(read_lines('/tmp/test_write.csv'), collapse='\n'))write_tsv() — Tab-Separated Output
write_tsv(df, 'file.tsv') saves as tab-separated. This is preferred when the data contains commas or when the downstream system expects TSV. Same options as write_csv().
library(readr)
df <- data.frame(
address = c('123 Main St, NYC','456 Oak Ave, LA'),
score = c(85, 92)
)
# TSV avoids delimiter conflict with commas in addresses
write_tsv(df, '/tmp/addresses.tsv')
cat(paste(read_lines('/tmp/addresses.tsv'), collapse='\n'))saveRDS() — Save Any R Object
saveRDS(obj, 'file.rds') serializes any R object to disk in R's native binary format. readRDS('file.rds') restores it exactly — preserving column types, factors, attributes, and even model objects. Not human-readable but perfectly portable between R sessions.
library(readr)
df <- data.frame(
name = c('Alice','Bob'),
score = c(85.5, 92.0),
grade = factor(c('B','A'), levels=c('A','B','C','D'))
)
# Save with full type preservation
saveRDS(df, '/tmp/students.rds')
# Restore exactly
df2 <- readRDS('/tmp/students.rds')
print(df2)
print(class(df2$grade)) # factor — preserved!saveRDS() vs save() — Key Difference
saveRDS(obj, 'file.rds') saves a single object without its name. readRDS() lets you assign it any name. save(obj1, obj2, 'file.RData') saves multiple named objects; load() restores them with original names (can overwrite existing variables).
# saveRDS: single object, flexible on load
model <- lm(mpg ~ wt, data=mtcars)
saveRDS(model, '/tmp/my_model.rds')
best_model <- readRDS('/tmp/my_model.rds') # Any name!
cat('R-squared:', round(summary(best_model)$r.squared, 3), '\n')
# save: multiple named objects, fixed names on load
# save(model, mtcars, file='/tmp/workspace.RData')
# load('/tmp/workspace.RData') # Restores 'model' and 'mtcars'write_delim() — Custom Delimiter
write_delim(df, file, delim='|') writes with any delimiter. Use this when the downstream system expects pipe-delimited or other non-standard formats. The same data quality guarantees as write_csv() apply.
library(readr)
df <- data.frame(
id = 1:3,
name = c('Alice','Bob','Carol'),
value = c(100, 200, 150)
)
# Write pipe-delimited
write_delim(df, '/tmp/data.psv', delim='|')
cat(paste(read_lines('/tmp/data.psv'), collapse='\n'))Excel Output with writexl
The writexl package (companion to readxl) writes data frames to .xlsx format: writexl::write_xlsx(df, 'file.xlsx'). For multiple sheets, pass a named list. No Java or Excel installation required.
# writexl::write_xlsx() — example (library not always available)
# library(writexl)
df1 <- data.frame(name=c('Alice','Bob'), score=c(85,92))
df2 <- data.frame(region=c('East','West'), sales=c(100,200))
# Single sheet:
# write_xlsx(df1, '/tmp/students.xlsx')
# Multiple sheets (named list):
# write_xlsx(
# list(students=df1, sales=df2),
# '/tmp/report.xlsx'
# )
cat('writexl writes pure .xlsx without Java or Excel!')Parquet Files with arrow
Apache Parquet is a columnar storage format ideal for large datasets: it compresses well and reads much faster than CSV for column-oriented queries. The arrow package provides write_parquet() and read_parquet().
# arrow::write_parquet() concept
# library(arrow)
# Large CSV -> Parquet (10-100x smaller, 10-100x faster reads)
# df <- read_csv('big_file.csv')
# write_parquet(df, 'big_file.parquet')
# df_back <- read_parquet('big_file.parquet')
# Parquet benefits:
# - Columnar: only reads columns you need
# - Compressed: ~10x smaller than CSV
# - Typed: no type re-guessing on read
# - Partitioned: split by date/region for parallel reads
cat('Parquet is the modern standard for large-scale analytics in R!')Writing Data in a Pipeline
Both write_csv() and saveRDS() return their input invisibly, making them usable inside a pipe without breaking the flow. Insert a write step mid-pipeline with a checkpoint write_csv() for reproducibility.
library(readr)
library(dplyr)
# Write mid-pipeline as a checkpoint
final_result <- data.frame(
region=c('East','West','North'),
sales=c(100,200,80)
) %>%
mutate(pct = round(100*sales/sum(sales),1)) %>%
{. ->> checkpoint_df; .} %>% # Save checkpoint
filter(sales > 90)
write_csv(checkpoint_df, '/tmp/checkpoint.csv')
print(final_result)Format Selection Guide
Choosing the right output format depends on your goal:
- CSV/TSV: universal, human-readable, anyone can open it
- RDS: R-to-R, preserves all types and attributes exactly
- xlsx: business users, Excel consumers
- Parquet: large data, analytics pipelines, production systems
- JSON: APIs, nested/hierarchical data (
jsonlite::toJSON())
library(readr)
df <- data.frame(
id = 1:5,
value = c(10.5, 20.3, 15.7, 30.1, 25.8)
)
# CSV — universal
write_csv(df, '/tmp/data.csv')
# TSV — when commas are in data
write_tsv(df, '/tmp/data.tsv')
# RDS — R-native, type-preserving
saveRDS(df, '/tmp/data.rds')
cat('Files created:\n')
cat('CSV size:', file.size('/tmp/data.csv'), 'bytes\n')
cat('RDS size:', file.size('/tmp/data.rds'), 'bytes\n')Quick Check
What is the key advantage of saveRDS() over write_csv() for saving R data?
Recap: Writing Data
Key takeaways for writing data from R:
write_csv(df, 'file.csv')— comma-separated, no row names, UTF-8write_tsv(df, 'file.tsv')— tab-separated outputwrite_delim(df, file, delim='|')— any delimitersaveRDS(obj, 'file.rds')— binary R format, preserves all typesreadRDS('file.rds')— restore any name;load()restores original nameswritexl::write_xlsx()— Excel output, no Java neededarrow::write_parquet()— columnar format for large analytical datasets
library(readr)
df <- data.frame(
name = c('Alice','Bob','Carol'),
score = c(85.5, 92.0, 78.3),
grade = factor(c('B','A','C'))
)
# CSV: universal but loses factor
write_csv(df, '/tmp/demo.csv')
df_csv <- read_csv('/tmp/demo.csv', show_col_types=FALSE)
cat('CSV grade type:', class(df_csv$grade), '\n') # character
# RDS: preserves factor
saveRDS(df, '/tmp/demo.rds')
df_rds <- readRDS('/tmp/demo.rds')
cat('RDS grade type:', class(df_rds$grade)) # factorLearn R with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 43
- Lessons
- 159
Frequently Asked Questions
Is the “Writing Data to Multiple Formats” lesson free?
Yes — any 3 lessons of the R Academy learning path, including “Writing Data to Multiple Formats”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The R Academy course includes 4 lessons in total.
What will I learn in “Writing Data to Multiple Formats”?
Export data frames to CSV, Excel, and RDS with write_csv() and writexl. You practise R Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start R Academy?
No prior experience is required. R Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 4 of 4.
How long does the “Writing Data to Multiple Formats” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this R Academy lesson?
Yes. Every R Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Reading CSV Files with read_csv()
- Parsing TSV and Fixed-Width Files
- Importing Excel Files with readxl
- Writing Data to Multiple Formats