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")
小技巧:如果你的 CSV 文件很大,先 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。嗯,这个经验花了一上午才教训到。

警告:readxl 目前不支持 .xlsb 格式(二进制 Excel)。如果你遇到这种文件,考虑用 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 / readxlbase R / xlsx
速度(大文件)快(C++ 底层)慢(R 级别解析)
类型推断自动,可手动覆盖粗放,需手调
内存使用更高效容易膨胀
字符串默认行为保持字符,不转因子自动转因子
Excel 依赖无 Java 依赖需 Java/JDK
进度条有(大文件可见)无

核心结论:只要不是写一个超简单的脚本,我都建议用 readr + readxl。它们更稳定、更容易 debug,长期维护成本低很多。

3.5 知识流程图:数据导入导出的技术路线

数据导入导出流程 CSV 文件 Excel 文件 (.xlsx) 文本文件 (TXT/日志) 导入包 readr 包 readxl 包 可配合 tidyverse 核心函数 read_csv / read_delim / read_fwf read_excel / write_csv / write_xlsx R 数据框 (tibble) 特征工程预处理 💡 注意:readr 默认使用 UTF-8 编码,若遇到乱码可指定 locale(encoding = "GBK") readxl 不支持 .xlsb;导出 Excel 时用 writexl::write_xlsx 可写多 sheet

这张图展示了数据从外部文件到特征工程的全路径。你不需要记住所有函数,只要记住:CSV 和 txt 找 readr,Excel 找 readxl。其他都是参数微调。等你多踩两次坑,自然就形成自己的模板了。