R 数据导入实战
导入数据需要选对解析器、保留列原本的类型,并在分析前检查得到的对象。本文覆盖能够放入内存的本地分隔符文件、Excel、JSON、RData 和 RDS,不涉及数据库查询和流式处理。若不熟悉基础操作,先读 R 向量、数据框与缺失值。
内联文本和临时文件示例无需项目数据即可使用。data/data-import/ 下的路径只是示意,不是随文提供的数据集:请替换成自己的文件,并在写入前确认父目录存在。相对路径以 getwd() 为起点。基础 R 读取函数无需额外包;使用 readr、data.table、readxl 或 jsonlite 的示例要求已经安装对应包。
分隔符文件
这是最通用的数据格式,必须得会。这里的“分隔符”指用来切分数据的字符,最常见的是逗号(,)和制表符(\t),分别对应 CSV 和 TSV。
CSV
CSV 文件通常以 .csv 结尾。扩展名不影响内容,但方便肉眼识别,也能让软件自动识别格式。比如一份学生成绩表可能长这样:
student,chinese,math,english
stu1,99,100,98
stu2,60,50,88
R 提供 read.table() 来读取分隔符文件。下面演示直接从字符串读取:
stu <- read.table(text = "
student,chinese,math,english
stu1,99,100,98
stu2,60,50,88
", header = TRUE, sep = ",")
stu
class(stu)
通常数据都在本地文件里。用 read.table() 读 CSV 文件:
cars <- read.table(file = "data/data-import/mtcars.csv", header = TRUE, sep = ",")
先查看前几行,随后仍需检查类型和完整数据:
head(cars)
如果是纯 CSV,直接用 read.csv() 更省事,它默认就是逗号分隔且带表头:
cars2 <- read.csv(file = "data/data-import/mtcars.csv")
head(cars2)
先保留数据含义,再计算
类型推断可能把编号 001 变成数值 1。引号内的逗号属于字段内容,引号外的逗号才分隔字段;直接按字符串逗号切分会丢掉这一区别。已知格式时,应显式指定类型和缺失值标记:
csv <- 'id,score,note
001,10,"good, complete"
002,,pending'
scores <- read.csv(
text = csv,
colClasses = c("character", "numeric", "character"),
na.strings = c("", "NA"),
check.names = FALSE
)
stopifnot(
identical(names(scores), c("id", "score", "note")),
nrow(scores) == 2L,
identical(scores$id, c("001", "002")),
is.na(scores$score[2]),
!anyDuplicated(scores$id),
all(is.na(scores$score) | (scores$score >= 0 & scores$score <= 100))
)
str(scores)
colSums(is.na(scores))
结果有两行三列,第二个分数是缺失,不是零。唯一性和范围检查表达的是这个例子的字段约定,不是所有 CSV 都必须遵守的规则。只看 head() 发现不了文件末尾的异常值。实际文件还应检查预期行数、必需列、单位和日期解释。文件确实采用 UTF-8 时,可用 fileEncoding = "UTF-8" 指定解码;猜一个编码不能修复已经损坏的文本。read.csv2() 适用于常见的分号分隔、逗号作小数点的变体。read.table() 默认把 # 视为注释起点,而 read.csv() 默认禁用注释;若 # 是数据,使用 comment.char = ""。
用 readr 和 data.table 提速
readr 和 data.table::fread() 提供其他读取方式。应对照解析选项、返回类型、内存用量,以及自己数据上的实测耗时,不要假定某个读取器总是更快。
先创建一个临时 CSV,让各读取器使用相同输入:
temp_csv <- tempfile(fileext = ".csv")
writeLines(c("id,value", "1,10", "2,20"), temp_csv)
z1 <- read.csv(temp_csv)
使用 readr
z2 <- readr::read_csv(temp_csv, show_col_types = FALSE)
使用 data.table
z3 <- data.table::fread(temp_csv)
注意,这三种方式返回的对象类型(class)是不一样的:
class(z1)
class(z2)
class(z3)
unlink(temp_csv)
用 readr 时,推断类型不可靠就指定 col_types,并用 readr::problems(z2) 查看解析失败记录。show_col_types = FALSE 只隐藏类型提示,不能代替检查。成功读入的数据仍可能有错误类型、自动修正的列名或意外缺失值。这里的例子都把数据载入内存,更快的解析器并不会消除内存容量限制。
TSV 及其他变体
TSV 用制表符分隔,导入时把 sep 改成 "\t" 即可:
mt <- read.table("data/data-import/mtcars.tsv", sep = "\t", header = TRUE)
mt
用 readr 读 TSV 有专用函数:
mt2 <- readr::read_tsv("data/data-import/mtcars.tsv")
mt2
用 data.table 读:
mt3 <- data.table::fread("data/data-import/mtcars.tsv")
mt3
Excel
Excel 常用于存储数据;R 中可用 readxl 读取:
library(readxl)
mt_excel <- read_excel("data/data-import/mtcars.xlsx")
head(mt_excel)
如果要读特定的 Sheet,先查一下有哪些 Sheet:
excel_path <- "data/data-import/example.xlsx"
readxl::excel_sheets(excel_path)
iris <- readxl::read_excel(excel_path, sheet = "iris")
head(iris)
注:代码里的路径和 Sheet 名只是示例,实际使用时请替换成你项目里真实存在的文件。
除了 Sheet 名,还要检查所选区域、表头行和列类型。readxl::read_excel() 支持 range、skip、col_types 和 na;例如,推断会丢失含义的编号列可以指定为 "text"。空单元格默认是缺失值。如果 Excel 已经把 001 保存成数值 1,只用显示格式补零,那么读成文本也无法自动恢复原本想表达的编号。
JSON
JSON 是轻量级的数据交换格式。jsonlite 用于在 JSON 与 R 对象之间转换:
jsonlite::toJSON(letters)
jsonlite::toJSON(c(a = 1L, b = 2.0))
jsonlite::toJSON(data.frame(a = 1:3, b = 2:4))
jsonlite::toJSON(list(a = 1L, b = 2:5, c = c(TRUE, FALSE), d = NULL))
把 JSON 存到文件里:
jsonlite::write_json(list(a = 1L, b = 2:5, c = c(TRUE, FALSE), d = NULL), path = "data/data-import/example.json")
从文件读 JSON:
jsonlite::read_json("data/data-import/example.json")
jsonlite::read_json("data/data-import/example.json", simplifyVector = TRUE)
read_json() 默认返回嵌套列表;simplifyVector = TRUE 尝试把兼容的结构简化成向量或数据框。先用 str() 检查,再决定能否按表格处理。字段不存在、JSON null 和空数组的含义不同,应根据分析目的决定如何映射为缺失值。JSON 不能保留所有 R 类和属性,因此需要精确保留 R 对象结构时,不能普遍替代 RDS。
R 原生数据文件
用 R 自己的格式(RData 和 RDS)存对象,既快又方便。
RData
RData 可以存多个命名对象。因为 load() 会把对象写进环境,如果不想污染全局工作区,建议指定一个独立环境:
d1 <- head(mtcars)
d2 <- head(iris)
save(d1, d2, file = "data/data-import/example.RData")
loaded <- new.env(parent = emptyenv())
load("data/data-import/example.RData", envir = loaded)
ls(loaded)
RDS
RDS 专门存单个对象,加载时可以随便改名:
saveRDS(mtcars, file = "data/data-import/mtcars.rds")
mtcars_rename <- readRDS("data/data-import/mtcars.rds")
head(mtcars_rename)
RDS 保存一个对象,但这个对象本身可以是装有许多对象的列表。readRDS() 返回对象,供调用者赋值;load() 则把存储的名称恢复到环境中,并返回这些名称。上面的独立环境能防止意外覆盖名称,却不是安全沙箱。只应反序列化来自可信来源的 R 文件。
常见问题与解决方案
从剪贴板读取
这里的 "clipboard" 连接是 Windows 用法,不是跨平台路径;其他平台的剪贴板访问取决于会话和可用设施,参见 R 连接文档。要让分析可重复,最好把输入保存成文件:
data <- read.table('clipboard', header=TRUE)
逐行读取
用 readLines() 按行读文件:
fil <- tempfile(fileext = ".data")
cat("TITLE extra line", "2 3 5 7", "", "11 13 17", file = fil, sep = "\n")
readLines(fil, n = -1)
unlink(fil) # 清理临时文件
固定宽度文件
如果是固定宽度格式(Fixed-width),用 read.fwf() 或 readr::read_fwf() 来读。