Unit 03.02: The import is where quality is caught or hidden
The import is not a formality before the analysis. It is where most data quality problems either get caught or get hidden.
Read the types R guessed, then fix the read
read_csv() guesses each column's type from the first rows and reports what it decided. That report is worth reading every time, because a column that should be numeric arriving as text is nearly always evidence of something in the data — a stray marker, a thousands separator, a footnote.
The instinct is to convert afterwards with as.numeric(). That works and it silently turns every unconvertible value into NA, so the evidence disappears. Naming the missing markers in the read itself keeps the decision visible and in one place.
The same argument applies to column names, delimiters and decimal marks: handle them at the read, not three steps later.
This block writes a small file containing a literal n/a, then reads it twice.
suppressPackageStartupMessages(library(readr))
path <- file.path(tempdir(), "visits.csv")
writeLines(c("ward,visits,recorded,notes",
"North,412,2026-01-31,",
"South,388,2026-01-31,short staffed",
"East,n/a,2026-01-31,system down"), path)
# read_csv guesses types from the first rows and tells you what it guessed.
raw <- read_csv(path, show_col_types = FALSE)
cat("Guessed types:", paste(sapply(raw, function(x) class(x)[1]), collapse = ", "), "\n")
cat("visits arrived as character because of the literal 'n/a'.\n\n")
# Naming the missing marker is the fix, and it belongs in the read, not later.
clean <- read_csv(path, na = c("", "n/a"), show_col_types = FALSE)
cat("With na = c('', 'n/a'):", class(clean$visits), "\n")
print(clean)
cat("\nMissing visits:", sum(is.na(clean$visits)), "\n")
The first read guesses character, character, Date, character — visits is text purely because of that one n/a, and any arithmetic on it would fail. Adding na = c('', 'n/a') gives a numeric column, and the printed table shows East's visits as NA with its note preserved. The final count confirms 1 missing value, which is a number the analyst now has to think about rather than one that vanished.
The mistake this prevents
The mistake is wrapping the column in as.numeric() and moving on. Every unparseable value becomes NA without a word, so a file where a third of the rows say 'pending' looks like a file with a third missing at random.
Takeaway
Read the type report on every import. Declare missing markers in the read, and count the NAs immediately afterwards so you know what the import cost you.
