07 · Reading Data¶
Base R: read.csv()¶
R ships with a CSV reader in base R — no extra packages required:
# sample.csv
# name,age,city
# Alice,23,Boston
# Bob,25,Chicago
# Carol,22,Seattle
people <- read.csv("sample.csv")
people
# name age city
# 1 Alice 23 Boston
# 2 Bob 25 Chicago
# 3 Carol 22 Seattle
str(people)
# 'data.frame': 3 obs. of 3 variables:
# $ name: chr "Alice" "Bob" "Carol"
# $ age : int 23 25 22
# $ city: chr "Boston" "Chicago" "Seattle"
read.csv() handles the common case well: it assumes a header row, comma
separators, and infers each column's type automatically.
Useful read.csv() arguments¶
read.csv("sample.csv", header = TRUE) # header = TRUE is the default
read.csv("no_header.csv", header = FALSE) # no header row in the file
read.csv("sample.csv", stringsAsFactors = FALSE) # character stays character (default since R 4.0)
read.csv("euro.csv", sep = ";") # semicolon-separated file
read.csv("sample.csv", na.strings = c("", "NA", "N/A")) # custom missing-value markers
stringsAsFactors
Before R 4.0, read.csv() converted character columns to factor
(categorical) by default, which surprised a lot of people. Since R 4.0,
the default is stringsAsFactors = FALSE, so text columns load as plain
character unless you ask for factors — the behavior you'll see if
you're running a current R version.
The readr package: read_csv()¶
The readr package (part of the tidyverse) provides a faster, more predictable alternative with clearer type reporting:
install.packages("readr") # one-time, see Module 9
library(readr)
people <- read_csv("sample.csv")
# Rows: 3 Columns: 3
# -- Column specification --------
# Delimiter: ","
# chr (2): name, city
# dbl (1): age
#
# i Use `spec()` to retrieve the full column specification for this data.
# i Specify the column types or set `show_col_types = FALSE` to quiet this message.
people
# # A tibble: 3 x 3
# name age city
# <chr> <dbl> <chr>
# 1 Alice 23 Boston
# 2 Bob 25 Chicago
# 3 Carol 22 Seattle
read_csv() returns a tibble — a modern variant of a data frame with
nicer printing (shows types under each column header, truncates long output)
and slightly stricter behavior. Everything you learned about data frames in
Module 6 still applies; a tibble is a data frame
under the hood.
read.csv() vs. read_csv()¶
read.csv() (base R) |
read_csv() (readr) |
|
|---|---|---|
| Requires a package? | No | Yes (readr) |
| Return type | data.frame |
tibble (also a data.frame) |
| Speed on large files | Slower | Faster |
| String handling | Character by default (R 4.0+) | Character by default |
| Column type report | Silent | Printed automatically |
| Missing-value markers | na.strings = c("NA") |
na = c("NA") |
For Level 1, either is fine; readr becomes the default choice once you
adopt more of the tidyverse in Level 2.
Writing data back out¶
write.csv(people, "output.csv", row.names = FALSE)
# row.names = FALSE avoids an extra unnamed index column in the file
# readr equivalent
readr::write_csv(people, "output.csv") # never adds a row-names column
Reading from a URL¶
Both functions can read directly from a URL, which is handy for public datasets without a manual download step:
url <- "https://raw.githubusercontent.com/someuser/somerepo/main/data.csv"
remote_data <- read.csv(url)
Inspecting what you just loaded¶
Whichever function you use, the same checks are worth running immediately after loading any new file:
dim(people) # rows, columns
str(people) # column types
head(people) # first 6 rows
summary(people) # quick stats per column
anyNA(people) # TRUE if the data has any missing values at all
How It Actually Works¶
read.csv() works by opening a file connection and running R's C-level
tokenizer over it line by line: it splits each line on the separator,
buffers the raw strings for each column, and only after seeing every row
does it try to infer each column's type (numeric, integer, logical,
character) by attempting to parse the accumulated strings — which is why
a single stray non-numeric value anywhere in a column forces the whole
column to fall back to character. This two-pass-ish behavior (collect
then coerce) is also why huge base-R read.csv() calls are memory-hungry:
the raw string buffer and the final typed columns briefly coexist.
readr::read_csv() takes a different, faster approach: it reads a sample
of the first ~1000 rows to guess column types up front (hence the column
specification it prints), then streams the rest of the file parsing
directly into the guessed types using compiled C++ (via Rcpp) rather than
R-level loops — no full re-scan-and-coerce pass, and no factor conversion
step to worry about. That's the mechanical reason it's both faster and
more predictable on large files.
🔀 See this in another language¶
- MATLAB — Working with Data Files
- JavaScript — DOM Basics & Events
- Rust — Error Handling Basics (Option, Result)
Exercise¶
Create a small CSV file scores.csv by hand (a text editor or cat() from
R) with columns student,subject,score and at least 6 rows across 2
subjects. Load it with read.csv(), print str() of the result, filter to
rows where score >= 80, and write the filtered result to a new file
passing.csv with write.csv(..., row.names = FALSE).