3. 数据导入与导出:readr 和 readxl 包读取 CSV、Excel、文本文件
做特征工程,第一步就是把数据拉进 R 里。这个问题我见过太多新手卡在导入环节——明明文件在桌面,代码就是报错。其实大部分时候不是代码错了,是你对包的脾性不了解。
这一章我们只聊两个包:readr 和 readxl。为什么不用 base R 的 read.csv()?说白了,base 那套太慢了。我之前处理一个 2GB 的 CSV,read.csv() 跑了快两分钟,换成 read_csv() 十几秒就结束了——类型推断还更准。
3.1 readr:CSV 和文本文件的正确姿势
readr 是 tidyverse 的核心成员。它最讨喜的地方是什么?你不需要老惦记着 stringsAsFactors = FALSE。默认字符串就是字符串,不会自动转因子。我刚切到 tidyverse 那会儿,这省了我大量 debug 时间。
3.1.1 基本读取:read_csv()
最常用的函数。看个例子:
library(readr)
data <- read_csv("data/train.csv")
结果会打印列名和类型,顺便告诉你用了多少种推断方式。我个人习惯加上 col_types 参数,尤其是在生产脚本里——指定类型可以避免字符列被当成数字。
| 参数 | 作用 | 我常用的值 |
|---|---|---|
col_types | 手动指定每列类型 | cols(Col1 = col_double(), Col2 = col_character()) |
skip | 跳过前 N 行(比如注释行) | skip = 3 |
na | 指定哪些字符串算缺失值 | na = c("", "NA", "NULL") |
locale | 处理编码、日期格式 | locale(encoding = "UTF-8") |
read_csv(..., n_max = 100) 看看前 100 行,确认列名和类型没问题再全量读。
3.1.2 自定义分隔符:read_delim()
有些文本文件用制表符、分号甚至竖线分隔。这时候用 read_delim()。我遇到过一个项目,日志文件用 | 做分隔,字段里还带着逗号。当时我就这么写:
log_data <- read_delim("server.log", delim = "|",
col_names = c("time", "level", "msg"),
escape_backslash = TRUE)
注意 escape_backslash 参数。如果你的字段里带有引号或者反斜杠,记得打开它,不然解析会莫名其妙地错位。
3.1.3 固定宽度文件:read_fwf()
处理基因数据或者旧式会计记录的时候,你可能会碰到固定宽度的文本。读它也不难:
fwf_data <- read_fwf("positions.txt",
fwf_widths(c(10, 12, 8),
col_names = c("chr", "start", "end")))
或者用 fwf_positions() 指定起始和结束位置,更容易调试。
3.2 readxl:Excel 文件不再是噩梦
以前用 xlsx 包,还得装 Java,每次报错都让人崩溃。后来我全面换成了 readxl,无依赖、纯 C++,舒服多了。
3.2.1 读单个 sheet:read_excel()
library(readxl)
sales <- read_excel("report.xlsx", sheet = "Jan2025")
sheet 可以传名字也可以传数字索引。我有一个习惯:先 excel_sheets("report.xlsx") 预览所有 sheet 名字,再决定读哪个。曾经有一次我团队同事直接传数字 2,结果两个月后源文件多了一个隐藏 sheet,索引全乱了——从此我只用名字。
3.2.2 跳过无用行 & 指定范围
Excel 表头有时候特别乱,前几行是标题、logo 甚至空行。用 skip 和 n_max:
clean_data <- read_excel("messy.xlsx", skip = 3, n_max = 1000)
如果你想只读取某个范围,用 range 参数。比如 range = "A1:F50",不常用的功能但偶尔救命。
3.2.3 列类型推断
Excel 里日期列经常被读成数字(Excel 的序列号)。你可以在 read_excel() 里指定 col_types,类似 readr:
# "date" 会让 readxl 按日期解析
orders <- read_excel("orders.xlsx",
col_types = c("text", "date", "numeric", "skip"))
这里的 "skip" 表示跳过该列。如果你有一列完全没用,直接 "skip" 能省内存。我记得有一次处理一个 50MB 的 Excel,每列都读进来,R 内存飙到 3GB。后来只读需要的 4 列,内存降到 600MB。嗯,这个经验花了一上午才教训到。
openxlsx 或者先另存为 .xlsx。
3.3 数据导出:写回 CSV 和 Excel
特征工程做完,总要保存中间结果。导出时我优先用 readr 的 write_csv(),而不是 base 的 write.csv()——后者默认写行号,还会把字符列自动加引号,文件体积大。看对比:
# 推荐
write_csv(features, "features.csv")
# 不推荐
write.csv(features, "features.csv", row.names = FALSE)
如果要写 Excel,我一般用 writexl 包,它和 readxl 互补:
library(writexl)
write_xlsx(list(training = train, testing = test), "output.xlsx")
这个 list 的用法特别适合分 sheet 导出。我常把特征处理前的数据和处理后的数据放在同一个 Excel 的不同 sheet 里,给业务方 review,他们不用打开两个文件。
3.4 对比总结:readr / readxl vs. 传统方案
| 特性 | readr / readxl | base R / xlsx |
|---|---|---|
| 速度(大文件) | 快(C++ 底层) | 慢(R 级别解析) |
| 类型推断 | 自动,可手动覆盖 | 粗放,需手调 |
| 内存使用 | 更高效 | 容易膨胀 |
| 字符串默认行为 | 保持字符,不转因子 | 自动转因子 |
| Excel 依赖 | 无 Java 依赖 | 需 Java/JDK |
| 进度条 | 有(大文件可见) | 无 |
核心结论:只要不是写一个超简单的脚本,我都建议用 readr + readxl。它们更稳定、更容易 debug,长期维护成本低很多。
3.5 知识流程图:数据导入导出的技术路线
这张图展示了数据从外部文件到特征工程的全路径。你不需要记住所有函数,只要记住:CSV 和 txt 找 readr,Excel 找 readxl。其他都是参数微调。等你多踩两次坑,自然就形成自己的模板了。