Unit 03.06: CSV for people, RDS for the next script
Cleaning takes an hour. Saving the result properly takes ten seconds, and choosing the wrong format costs you the hour again.
CSV for people, RDS for the next script
CSV is text. Anything can read it, which is why it is the right format for handing data to a colleague or another tool. But text has no types: everything is written as characters and guessed again on the way back in. Factors lose their level order, integers may return as doubles, dates depend on the format surviving.
RDS is R's own format. saveRDS() writes the object exactly as it is, and readRDS() returns exactly that object โ types, factor levels, attributes and all. Nothing outside R can read it.
So the choice follows the consumer: if the next reader is a person or another tool, CSV; if it is the next script in your own pipeline, RDS.
This block saves the same tibble both ways and compares what comes back.
suppressPackageStartupMessages({library(readr); library(tibble)})
clean <- tibble(
ward = factor(c("North", "South"), levels = c("South", "North")),
recorded = as.Date(c("2026-01-31", "2026-02-28")),
visits = c(412L, 388L)
)
dir <- file.path(tempdir(), "data-clean")
dir.create(dir, showWarnings = FALSE)
csv <- file.path(dir, "clean.csv")
rds <- file.path(dir, "clean.rds")
write_csv(clean, csv)
saveRDS(clean, rds)
from_csv <- read_csv(csv, show_col_types = FALSE)
from_rds <- readRDS(rds)
cat("Original ward column :", class(clean$ward), "\n")
cat("After CSV round trip :", class(from_csv$ward), "\n")
cat("After RDS round trip :", class(from_rds$ward), "\n\n")
cat("Factor level order kept by RDS:",
identical(levels(clean$ward), levels(from_rds$ward)), "\n")
cat("Integer stayed integer in RDS :", identical(class(clean$visits),
class(from_rds$visits)), "\n\n")
cat("CSV for humans and other tools. RDS when the next R script must see\n")
cat("exactly the object you saved.\n")
The ward column starts as a factor. After the CSV round trip it is character โ the levels and their order are gone. After the RDS round trip it is still a factor, and the deliberate level order (South before North) survives intact. The integer column also stays integer through RDS. The factor order is the one that quietly ruins charts, because it determines the order of bars.
The mistake this prevents
The mistake is writing the cleaned table to CSV, reading it back in the next script, and re-doing the type work every time โ usually slightly differently, so two scripts disagree about the same file.
Takeaway
Save cleaned data as RDS for your own pipeline and export CSV when a human or another tool needs it. If you must round-trip through CSV, restore the types explicitly on the way back in.
