R: R CSV File I/O
Last updated: 2026-08-26
In the first 10 lessons, we focused on data within R, but in real-world projects, 99% of the data is stored in external files. In this lesson, we’ll learn about the most commonly used data format—CSV files. R offers two ways to read and write CSV files: the standard R method
read.csvand the tidyverse methodreadr, with the latter being smarter and faster.
After completing this lesson, you’ll be able to load 1,000 rows of sales data into R in 5 seconds and generate clean reports.
1. What You'll Learn
- What is a CSV file, and why is it used in data science?
- The difference between basic R
read.csvand tidyversereadr read_csv()8 Common Parameters (col_types, na, skip, n_max, etc.)write_csv()Generate Report- Handling Common Issues (Encoding, Missing Values, Delimiters)
- Tips for Reading Large Files (Chunking, Type Optimization)
2. A Pain Point in Data Import
(1) Pain Point: Excel to R conversion causes the system to freeze
Chen is an analyst, and his manager sent him a 100 MB CSV file containing sales data:
id,date,product,region,amount
1,2024-01-15,Cell Phone,Beijing,2999
2,2024-01-15,Computer,Shanghai,5999
3,2024-01-16,Cell Phone,Guangzhou,2899
...
(100,000 rows of)
He read the data using read.csv(), waited 30 seconds, and it took another 10 seconds to print out 100,000 rows of. He also discovered that all five columns were of type chr (numbers treated as strings).
(2) Solution using R
# 1. Use readr Smart Reading(5 s)
library(readr)
sales <- read_csv("sales.csv")
# 2. Automatic Type Inference
# id → integer, date → date, product → character,
# region → character, amount → double
# 3. Read only the rows you need(Speed Up 3x)
sales_sample <- read_csv("sales.csv", n_max = 10000)
# 4. Write clean reports
write_csv(sales_sample, "sales_clean.csv")
1 line of code = 30 seconds → 5 seconds. That’s the power of readr.
3. What is CSV?
(1) CSV = Comma-Separated Values
Name,Age,City
Alice,25,Beijing
Bob,30,Shanghai
Charlie,35,Guangzhou
- Each line = 1 record
Separate fields with
,(or;|\t). - The first row is usually the column names (header)
- Supports string quotes
"..."for handling fields containing commas
(2) Why is CSV so widely used in data science?
| Advantages | Description |
|---|---|
| General | Compatible with Excel, Python, R, and databases |
| Human-readable | Can be viewed directly in Notepad |
| Small file size | 5–10 times smaller than Excel |
| Cross-platform | No garbled characters due to different Excel versions |
| Easy to work with | No Excel formatting or formula noise |
graph LR
A[CSV Documents<br/>name,age,city] --> B[read_csv Smart Reading]
B --> C[tibble Clean DataFrame]
C --> D[write_csv Reports]
C --> E[dplyr Analysis]
C --> F[ggplot2 Visualization]
style A fill:#cce5ff
style B fill:#d4edda
style C fill:#fff3cd
style D fill:#f8d7da
style E fill:#e1d4ff
style F fill:#ffe1d4
4. Basic R vs. tidyverse: Two Approaches to Working with CSV Files
mindmap
root((CSV Read/write<br/>Two Options))
Basics R
read.csv
write.csv
Slow
String Conversion factor
tidyverse
readr
read_csv
write_csv
read_tsv
Advantages
Intelligent Type Inference
Fast 5-10x
Large File Progress Bar
Strings are not converted factor
Selection Recommendations
New Project: tidyverse
Legacy code: read.csv + stringsAsFactors = FALSE
GB Level data: data.table::fread
(1) Comparison of the Two Approaches
| Feature | Base R read.csv |
tidyverse read_csv |
|---|---|---|
| Speed | Slow (R-compatible) | 5–10 times faster (C++ backend) |
| Type Inference | Weak (default: chr) | Smart Inference (int/dbl/date/lgl) |
| Progress Bar | ❌ | ✅ (Works well with large files) |
| String | Convert to factor by default | Keep as string |
| Return Type | data.frame | tibble (more user-friendly) |
| List Processing | Error-Friendly | Automatic Cleaning (spaces, special characters) |
| Missing value | NA |
NA + custom string |
| String | stringsAsFactors = TRUE |
Always FALSE |
(2) Actual Comparison
# Basics R(Common Issues with Legacy Code)
df_old <- read.csv("data.csv")
# Default behavior:Convert all string arrays factor(Pitfall!)
# tidyverse(Recommendations)
library(readr)
df_new <- read_csv("data.csv")
# Intelligent Inference Types,Character retains character
readr for all new projects; for legacy code, use read.csv followed by stringsAsFactors = FALSE.
(3) When Should You Use Basic R?
| Scenario | Recommendation |
|---|---|
| New Project / Write New Code | read_csv (readr) |
| Processing GB-scale massive datasets | data.table::fread (Faster) |
| Maintaining Legacy Code | read.csv and stringsAsFactors = FALSE |
| Tutorial / Simple Script | Either |
5. 8 Common Parameters of read_csv()
(1) Basic Syntax
read_csv(file, col_types = TRUE, col_names = TRUE, na = "NA",
skip = 0, n_max = Inf, locale = default_locale(),
progress = TRUE)
(2) Parameter Quick Reference Table
| Parameter | Function | Default Value |
|---|---|---|
file |
File path or URL | Required |
col_types |
Column Type Specification | TRUE (Auto-inferred) |
col_names |
TRUE/Vector (custom column name) | TRUE (use the first row) |
na |
Missing Value Marker | "NA" |
skip |
Skip the first N lines | 0 |
n_max |
Maximum number of rows to read | Inf (all) |
locale |
Regional Settings (Encoding, Decimal Point) | default_locale() |
progress |
Show progress bar | TRUE |
(3) Hands-On: Common Parameters
library(readr)
# 1. Basic Reading(Intelligent Inference Types)
df <- read_csv("data.csv")
# 2. Specify Column Type(Avoiding Fallacies in Reasoning)
df <- read_csv("data.csv", col_types = cols(
id = col_integer(),
date = col_date(format = "%Y-%m-%d"),
amount = col_double(),
name = col_character()
))
# 3. Skip to the beginning 5 Line Comments
df <- read_csv("data.csv", skip = 5)
# 4. Custom NA Mark(CSV For internal use "NULL" Indicates missing)
df <- read_csv("data.csv", na = c("", "NA", "NULL", "N/A"))
# 5. Read-only before 1000 row(For debugging purposes)
df <- read_csv("data.csv", n_max = 1000)
# 6. Skip Column(Use col_types Set as col_skip())
df <- read_csv("data.csv", col_types = cols(
temp_col = col_skip()
))
(4) A Detailed Explanation of Smart Type Inference
The biggest advantage of readr is automatic inference:
| Sample Data | Inferred as |
|---|---|
1, 2, 3 |
integer |
1.5, 2.3 |
double |
2024-01-15 |
date |
12:30:00 |
time |
TRUE, FALSE |
logical |
Beijing, Shanghai |
character |
# Examples of Inference
sales <- read_csv("sales.csv")
spec(sales) # View the inference results
# cols(
# id = col_integer(),
# date = col_date(format = "%Y-%m-%d"),
# product = col_character(),
# region = col_character(),
# amount = col_double()
# )
6. write_csv(): Output a report
(1) Basic Syntax
write_csv(x, file, na = "NA", append = FALSE, col_names = TRUE)
(2) Practical Application
# 1. Basic Output
write_csv(sales, "sales_clean.csv")
# 2. Output to the specified path
write_csv(sales, "output/2024_sales.csv")
# 3. Append to the existing file(Do not cover)
write_csv(new_data, "sales.csv", append = TRUE, col_names = FALSE)
# 4. Custom NA indicates
write_csv(df, "output.csv", na = "")
(3) ⚠️ write_csv does not preserve data types
When readr writes to CSV, type information is lost (since CSV is plain text). When reading it back, the types must be inferred again:
# Write
write_csv(sales, "sales.csv")
# Read it again
sales_loaded <- read_csv("sales.csv")
# Type by read_csv Re-inference
.rds for internal persistence in R (type preservation)—see Lesson 12.
7. Troubleshooting Common Issues
(1) Garbled Chinese characters
# 1. Read Using a Specified Encoding
df <- read_csv("data.csv", locale = locale(encoding = "UTF-8"))
# Or
df <- read_csv("data.csv", locale = locale(encoding = "GBK"))
# 2. Check the file encoding
guess_encoding("data.csv")
# Returns in most cases "UTF-8" or "GBK"
# 3. Specify the encoding when writing
write_csv(df, "output.csv") # Default UTF-8
(2) The delimiter is not a comma
# 1. Tab-separated(TSV)
df <- read_tsv("data.tsv") # sep = "\t"
# 2. Semicolon-separated(Commonly Used in Europe)
df <- read_csv2("data.csv") # sep = ";"
# 3. Custom Delimiters(e.g. |)
df <- read_delim("data.txt", delim = "|")
(3) Numbers with thousands separators
# European Format:1.234,56(A percentile is .,A decimal is ,)
df <- read_csv("data_eu.csv",
locale = locale(grouping_mark = ".", decimal_mark = ","))
(4) Slow reading of large files
# 1. Read only the required columns(Speed Up 2-3x)
df <- read_csv("big.csv", col_select = c(id, amount))
# 2. Use col_types Skip Column
df <- read_csv("big.csv", col_types = cols(
temp_col_1 = col_skip(),
temp_col_2 = col_skip()
))
# 3. Close the progress bar
df <- read_csv("big.csv", progress = FALSE)
# 4. A Faster Solution:data.table::fread
# fread("big.csv") is 5-10x faster than read_csv
8. Advanced: Column Type Specification cols()
(1) Full Syntax of cols()
col_types = cols(
id = col_integer(), # Integer
name = col_character(), # Character
amount = col_double(), # Double-precision floating-point
date = col_date(), # Date
time = col_time(), # Time
datetime = col_datetime(),# Date and Time
is_active = col_logical(),# Logic
skip = col_skip(), # Skip this column
guess = col_guess() # Let readr infer
)
(2) String Shorthand (More Concise)
# Use "i" "c" "d" "D" "l" Abbreviation
col_types = cols(
id = "i", # integer
name = "c", # character
amount = "d", # double
date = "D", # date
is_active = "l" # logical
)
# All Inferences(Default)
col_types = TRUE
# Using a list(Sequence-Mapped Columns)
col_types = list(
id = col_integer(),
name = col_character()
)
9. Complete Example: Importing and Analyzing Sales Data
Below is an example of a complete workflow that ties together all the concepts covered in this lesson.
▶ Example: Importing Sales Data + Data Cleaning + Generating Reports
# ============================================
# Importing and Processing Sales Data
# Features:Read CSV → Cleaning → Analysis → Generate Report
# ============================================
library(readr)
library(dplyr)
# 1. Preparing Sample Data
sample_data <- tibble(
id = 1:10,
date = as.Date("2024-01-01") + 0:9,
product = c("Cell Phone", "Computer", "Cell Phone", "Tablet", "Cell Phone",
"Computer", "Tablet", "Cell Phone", "Computer", "Cell Phone"),
region = c("Beijing", "Shanghai", "Guangzhou", "Beijing", "Shenzhen",
"Shanghai", "Guangzhou", "Beijing", "Shenzhen", "Shanghai"),
amount = c(2999, 5999, 2899, 3999, 3099,
6299, 3899, 2899, 5999, 3199),
note = c(NA, "Promo", NA, NA, "New Products", NA, NA, "Promo", NA, NA)
)
# Write Example CSV(UTF-8 Coding)
write_csv(sample_data, "sales_demo.csv")
cat("=== The file has been generated ===\n")
# 2. Read CSV(Intelligent Inference)
sales <- read_csv("sales_demo.csv",
na = c("", "NA"),
col_types = cols(
id = col_integer(),
date = col_date(format = "%Y-%m-%d"),
product = col_character(),
region = col_character(),
amount = col_double(),
note = col_character()
))
cat("=== Read the results ===\n")
print(sales)
# 3. Data Quality Check
cat("\n=== Data Structures ===\n")
str(sales)
# 4. Data Analysis
cat("\n=== Sales Statistics ===\n")
summary_stats <- sales |>
group_by(region) |>
summarise(
Number of Orders = n(),
Total Sales = sum(amount),
Average Order = round(mean(amount), 2),
Highest Order = max(amount)
) |>
arrange(desc(Total Sales))
print(summary_stats)
# 5. Find promotional orders
cat("\n=== Promotional Orders ===\n")
promo <- sales |> filter(!is.na(note))
print(promo)
# 6. By Product Category
cat("\n=== Product Sales ===\n")
product_stats <- sales |>
group_by(product) |>
summarise(Sales = n(), Sales = sum(amount))
print(product_stats)
# 7. Generate clean reports
write_csv(summary_stats, "sales_by_region.csv")
write_csv(product_stats, "sales_by_product.csv")
cat("\n=== The report has been generated ===\n")
cat(" - sales_by_region.csv(By Region)\n")
cat(" - sales_by_product.csv(By Product)\n")
# 8. Use RDS Reserved Types(Recommendations)
saveRDS(sales, "sales_clean.rds")
sales_reloaded <- readRDS("sales_clean.rds")
cat("\nRDS Type After Loading:\n")
print(sapply(sales_reloaded, class))
Expected Output (Excerpt):
=== Sales Statistics ===
# A tibble: 4 × 5
region Number of Orders Total Sales Average Order Highest Order
<chr> <int> <dbl> <dbl> <dbl>
1 Beijing 3 9897 3299 3999
2 Shanghai 3 15497 5166. 6299
3 Guangzhou 2 6798 3399 3899
4 Shenzhen 2 9098 4549. 5999
=== Promotional Orders ===
# A tibble: 2 × 6
id date product region amount note
<int> <date> <chr> <chr> <dbl> <chr>
1 2 2024-01-02 Computer Shanghai 5999 Promo
2 8 2024-01-08 Cell Phone Beijing 2899 Promo
❓ FAQ
col_types = cols(...) to explicitly specify the type. After reading the file, use spec(df) to view the inference results, then use type_convert() or re-read the file to correct it.locale = locale(encoding = "UTF-8") or "GBK". If you're unsure of the encoding, try guess_encoding() first to check.col_select Read only the columns you need ② col_types Skip unnecessary columns ③ Disable progress = FALSE. If it’s still slow, use data.table::fread (faster).write_csv can't save the data type?saveRDS() / readRDS() for internal persistence—which preserves data types and is faster (more native to R).read_csv and fread?read_csv is the default in the tidyverse and integrates well with dplyr and ggplot2; fread is faster (the preferred choice for GB-scale data), but requires installing the data.table package. For most projects, read_csv is sufficient.📖 Summary
- CSV is the most versatile data format: plain text, human-readable, cross-platform, and readable by Excel, Python, R, and databases
- Recommended:
readr::read_csv—5–10 times faster than the base Rread.csv, with smart type inference and Tibble compatibility - 8 commonly used parameters:
col_types(type specification),na(NA flag),skip,n_max,col_select,locale,progress,col_names - Automatic type inference: integer / double / date / time / date-time / boolean / string
write_csv()Outputs a report, but does not preserve the data type—usesaveRDS()/readRDS()for internal R persistence- Use
locale = locale(encoding = "UTF-8")for garbled Chinese characters; if the separator is not,, useread_tsvorread_delim - Large file optimization:
col_select+col_types = col_skip()+ disable progress
📝 Exercises
-
Basic Exercise: Create a
tibblecontaining 5 rows and 4 columns (id/name/score/date). Usewrite_csv()to write totest.csv, then useread_csv()to read it back, and verify that the type inference is correct (dateis of type Date). -
Basic Exercise: Read the
test.csvfrom the previous question, usecol_types = cols()to explicitly specify the types of all columns (id: integer, score: double, name: string, date: date), and verify that the types match the explicit specifications. -
Basic Exercise: Create a data frame containing NA values (using
c(1, NA, 3, NA, 5)), export it to CSV, then read it usingna = c("NA", ""), and verify that the NA values are handled correctly. -
Advanced Exercise: Simulate 100 lines of sales data (product/region/amount/date). After importing the data using
read_csv: ① Calculate the total sales by region; ② Identify the 5 orders with the highest amounts; ③ Useread_csvto generate two reports:top5.csvandby_region.csv. -
Challenge: Create a CSV file containing special characters (column names with spaces, strings containing commas, and the
|separator), read it usingread_csvandread_delimrespectively, and compare the differences in the results. Take a screenshot of the console output and save it.