Menu

Read CSV Files in R (read.csv and read_csv)

How to import CSV files into R with read.csv() - the arguments that matter, the working-directory trap that causes most failures, and when readr::read_csv is worth it.

This page includes runnable editors - edit, run, and see output instantly.

read.csv() Turns a CSV File Into a Data Frame

Reading a CSV in R is one function call, built into the language - no package required:

sales <- read.csv("sales.csv")

read.csv() reads the file, uses the first line as column names, guesses a type for each column, and returns a data frame. That's the whole story for a well-behaved file.

You can watch it work without any file at all, because read.csv() also accepts raw CSV text through the text argument - the same parser, fed a string instead of a filename:

str() is the first thing to run on anything you import: it shows every column with its guessed type. Here name came in as character, score as integer, passed as logical - all guessed from the values. For a quick visual check, head(students) shows the first rows and nrow(students) counts them. If a column you expected to be numeric shows up as chr, something in that column isn't a number - a stray comment, a "N/A", a thousands separator - and that's the moment to fix it, not three plots later.

The Arguments That Matter

read.csv() has dozens of parameters; you'll use about five.

header - whether the first line holds column names. Defaults to TRUE. If your file starts straight with data, pass header = FALSE and R names the columns V1, V2, ...

sep - the field separator, default ",". Much of Europe writes CSVs with semicolons as separators, because the comma is the decimal mark there. R ships a preconfigured variant for exactly that: read.csv2() uses sep = ";" and dec = ",":

The heights come out as proper numbers - read.csv2 knew 1,63 meant one point six three. If you'd used plain read.csv on this file, you'd get one mangled column, because nothing splits on a comma-that-is-a-decimal correctly.

na.strings - which strings count as missing. By default only "NA" becomes a missing value. Real exports write missing data as empty cells, "missing", "-", "NULL" - and unless you declare those, they arrive as text and drag the whole column to character:

With the right na.strings, the empties become real NAs and score stays numeric. Without it, "missing" is just a word, and the column is text.

skip - lines to ignore before the data starts. Exports love putting a title line or two above the actual table; skip = 2 jumps over them.

colClasses - force column types instead of letting R guess. The classic victim is anything with leading zeros - zip codes, phone numbers, product IDs - which R's guesser reads as numbers, destroying the zeros:

00501 became 501. The data was correct in the file and wrong in R, silently. colClasses = c("integer", "character") tells the parser what each column is, in order, and the zeros survive.

One argument you no longer need: stringsAsFactors. For most of R's history, read.csv() converted every text column into a factor unless you said otherwise, which caused an enormous amount of confusion. Since R 4.0 the default is FALSE - text stays text. You'll still see stringsAsFactors = FALSE scattered through older tutorials; on a current R it's harmless but redundant.

File Paths: The #1 Reason read.csv Fails

The error every R learner meets in week one:

Error in file(file, "rt") : cannot open the connection
In addition: Warning message:
cannot open file 'sales.csv': No such file or directory

The file exists - you're looking at it in your file manager. But R isn't looking there. A bare filename like "sales.csv" is resolved relative to R's working directory, and that is usually not the folder your file is in. Two lines diagnose it:

getwd()                    # where R is actually looking
file.exists("sales.csv")   # FALSE means the path is wrong, full stop

If file.exists() says FALSE, no amount of re-running will help - fix the path. Either give the full path (read.csv("C:/Users/ada/data/sales.csv") - forward slashes work on Windows too, and are safer than backslashes) or move R's working directory to the data folder with setwd(). What the working directory is, how projects manage it, and why setwd() in scripts is frowned upon is covered in working directory.

Make file.exists() a reflex. It converts "mysterious import failure" into "typo in a path" in one second.

Reading a CSV Straight From a URL

read.csv() accepts a URL anywhere it accepts a filename, and downloads the file for you:

url <- "https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv"
penguins <- read.csv(url)
str(penguins)

This is handy for tutorials and public datasets - no download step, no path problems. The trade-off is that your script now depends on the network and on that URL staying alive; for anything you'll re-run often, download once and read the local copy.

readr::read_csv - the Faster, Chattier Alternative

The tidyverse ships its own CSV reader in the readr package: read_csv() (underscore, not dot). It's worth knowing because most modern R code you'll read uses it:

install.packages("readr")   # once
library(readr)              # every session

flights <- read_csv("flights.csv")

The practical differences from read.csv():

  • Speed - noticeably faster on files with hundreds of thousands of rows.
  • It returns a tibble - a modernized data frame that prints a compact preview instead of flooding your console.
  • It reports its guesses - after reading, it prints each column's detected type, so a column that parsed as character when you expected numbers is visible immediately instead of being a surprise later.
  • It never mangles column names - read.csv() rewrites a header like first name to first.name; read_csv() keeps it as-is.

For a small file either function is fine. Reach for read_csv() when files get big or when the rest of your script is tidyverse anyway. Everything above about paths, separators (read_csv2() exists too), and missing-value strings (na =) carries over with slightly different argument names.

When you're done analyzing, the trip back out - exporting your results to a file - is write.csv.

What You Take Away

  • df <- read.csv("file.csv") reads a CSV into a data frame; check it immediately with str() and head().
  • read.csv(text = "...") parses a CSV string directly - great for experiments and small examples.
  • The arguments that earn their keep: header, sep (or read.csv2 for semicolons), na.strings, skip, colClasses.
  • "Cannot open file" means the working directory isn't where you think it is - getwd() and file.exists() settle it instantly.
  • readr::read_csv() is the faster tidyverse version: tibbles, reported column types, untouched names.

Next up: files that aren't plain text at all - reading Excel spreadsheets with the readxl package.

Frequently Asked Questions

How do you read a CSV file in R?

With read.csv("file.csv") - it's built in, no package needed. It returns a data frame with one column per CSV column, using the first line as column names. Assign the result to a variable: df <- read.csv("sales.csv"), then check it with str(df) and head(df).

Why does read.csv say 'cannot open file: No such file or directory'?

R is looking for the file relative to its working directory, which is probably not the folder your file is in. Run getwd() to see where R is looking and file.exists("file.csv") to confirm the miss. Fix it with a full path, or by setting the working directory to the file's folder.

What is the difference between read.csv and read_csv in R?

read.csv() is base R - always available, returns a plain data frame. read_csv() comes from the readr package - it's faster on large files, returns a tibble, never touches your column names, and prints the column types it guessed so you can catch bad parses immediately. For small files either is fine; for big files or tidyverse pipelines, use read_csv().

How do you read a semicolon-separated CSV in R?

Use read.csv2("file.csv") - it's read.csv preconfigured for the European convention: semicolon as the separator and comma as the decimal mark. Or pass sep = ";" (and dec = "," if needed) to plain read.csv().

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED