3 数据读取与写入:readr、readxl、haven 包实战
好,咱们进入第三章。前两章我们把 R 和 RStudio 环境搭好了,也把基本的变量类型摸了个透。但做质控分析,数据是根本。你手里可能有 Excel 表格、CSV 日志、SPSS 的问卷结果,甚至 SAS 的临床试验数据。怎么把它们弄进 R 里?
说实话,我在工厂里见过太多人用 read.csv() 碰到乱码,气得砸键盘。其实 R 的基础函数能用,但不够快、不够稳。咱们今天要讲的三个包——readr、readxl、haven——才是工业级的数据搬运工。我个人习惯,接手任何项目的第一件事就是把这几个包装上。
3.1 三大包的定位和选择
你想想看,数据文件格式五花八门,如果一个包试图支持所有格式,反而容易出问题。专业的事交给专业的包:
| 包名 | 主要处理格式 | 核心理念 |
|---|---|---|
| readr | CSV、TSV、固定宽度文件 | 快、准、不猜类型(或猜得更聪明) |
| readxl | .xls、.xlsx | 无需安装 Java/Perl,原生读取 Excel |
| haven | .sav (SPSS)、.dta (Stata)、.sas7bdat (SAS) | 保留标签、值标签和缺失值定义 |
readr 属于 tidyverse 的基石,它用 C++ 重写了底层解析引擎。我以前用 utils::read.csv() 读 500MB 的制造日志,等得都能泡杯咖啡;换成 readr::read_csv() 后,30 秒搞定。
haven 包直接读 .sav 文件,连问卷的“非常满意=5”这种值标签都帮你保留下来。
下面我们把三种数据读取方式逐个拆开讲。
3.2 使用 readr 读取 CSV 和文本数据
咱们先处理最普遍的 CSV。read_csv() 语法很直观,但有几个关键参数我得重点说一下:
library(readr)
# 最简单的调用
df <- read_csv("data/生产批次记录.csv")
# 指定列类型,避免自动猜测犯错
df <- read_csv("data/批次数据.csv",
col_types = cols(
批次号 = col_character(),
生产日期 = col_date(format = "%Y-%m-%d"),
合格率 = col_double(),
缺陷数 = col_integer()
),
locale = locale(encoding = "GBK")
)
注意第二个例子里的 col_types。为什么要手动指定?因为 readr 虽然猜类型很聪明,但它有极限。比如批次号是 “001” 开头,默认会猜成数字,变成 1。这在质控溯源时会出大问题。
还有 locale,这是处理中文乱码的关键。国内很多工厂的 CSV 是从老旧系统导出来的,编码是 GB2312 或 GBK。不指定 encoding = "GBK",读进来全是乱码。碰到这种情况怎么办?试试 locale = locale(encoding = "GB18030"),兼容性更好。
readr 还支持 TSV(制表符分隔) 和 固定宽度文件。我检查一个案例时遇到过供应商提供的 .prn 日志,就是固定宽度的。 read_fwf() 这时候就派上用场了:
# 固定宽度文件,需要指定每列的起始和结束位置
df <- read_fwf("设备日志.prn",
fwf_positions(start = c(1, 11, 21, 31),
end = c(10, 20, 30, 40)),
col_names = c("时间", "温度", "压力", "状态"))
如果文件里本身有分隔线,也可以用 fwf_empty() 自动猜测列宽,不过稳妥起见,我通常手动指定。
3.3 使用 readxl 读取 Excel 文件
Excel 在质控部门简直是“准系统”。很多人用它记录巡检数据、校准记录、不合格品台账。但 readxl 有一个限制:它只读数据,不读公式和宏。换句话说,如果某个单元格是 =SUM(A1:A10),它读到的会是计算结果,不是公式本身。这其实正是我们需要的——我们只关心最终数值。
基本的读取方法:
library(readxl)
# 读取第一个 sheet
df <- read_excel("质检日报.xlsx")
# 指定 sheet 名称或索引
df <- read_excel("质检日报.xlsx", sheet = "3月第一周")
# 跳过开头几行(很多报表有表头或合并标题)
df <- read_excel("质检日报.xlsx", sheet = 1, skip = 2)
read_excel() 使用 Sheet 1 的第一行作为列名。但现实中的报表往往花里胡哨:前几行是公司 logo、报告编号、审批签字。这时候 skip 参数就非常重要了。
我记得有个项目,对方发来的 Excel 模板前 3 行是合并单元格的标题,第 4 行才是真正的字段名。如果我不加 skip = 3,读进来全 NAN,浪费了半天排查。
mutate_at() 或在后续分析中做类型转换,不要在参数里过度限制 col_types。
另外,Excel 文件里面经常存放多个工作表。你可以用 excel_sheets() 查看所有 sheet 名称:
excel_sheets("过程能力报告.xlsx")
3.4 使用 haven 读取 SPSS 和 SAS 数据
大型制药企业、医疗器械公司和临床研究机构,仍然大量使用 SPSS 和 SAS。这些文件的二进制结构很不透明,haven 包把它们包装成了 R 能识别的 tibble,并且保留了“变量标签”、“值标签”这些元数据。
3.4.1 读取 SPSS 数据 (.sav)
library(haven)
df <- read_sav("客户满意度调查.sav")
# 查看变量标签
attributes(df$满意程度)
# 值标签通常被保留为 labelled 类
# 可以用 as_factor() 转换为因子
df <- df %>% mutate(满意程度 = as_factor(满意程度))
你可能会发现 df$满意程度 显示的是 “5”、“4” 这类数字,而不是 “非常满意”、“满意”。这是因为 haven 默认保留了原始数值。要想显示标签,用 as_factor() 转一下。
3.4.2 读取 SAS 数据 (.sas7bdat 和 .sas7bcat)
SAS 的问题稍微复杂点。它通常有两个关键文件:数据文件 (.sas7bdat) 和格式目录 (.sas7bcat)。格式目录里定义了变量标签和值标签。只读数据文件,你会得到和 SPSS 类似的 labelled 对象,但缺少标签信息。
# 读取 SAS 数据文件
df <- read_sas("临床试验结果.sas7bdat")
# 如果格式目录在另一路径
df <- read_sas("临床试验结果.sas7bdat",
catalog_file = "格式目录.sas7bcat")
3.4.3 读取 Stata 数据 (.dta)
中国质控圈用 Stata 的少,但高校经管院系和一些咨询公司用得不少。如果你碰到了,用 read_dta():
df <- read_dta("审计抽样数据.dta")
# Stata 也常用 labelled 变量,处理方式与 SPSS 类似
3.5 写入数据
数据读进来处理完了,肯定要写回去或者存成中间结果。这三个包都提供了对应的写入函数:
| 包 | 写入函数 | 典型用法 |
|---|---|---|
| readr | write_csv() / write_tsv() |
write_csv(df, "处理结果.csv") |
| readxl | write_xlsx() |
write_xlsx(list(Sheet1=df1, Sheet2=df2), "多工作表输出.xlsx") |
| haven | write_sav() / write_dta() |
write_sav(df, "回传数据.sav") |
我个人有个习惯:用 write_csv() 时总是加上 na = "" 参数,把缺失值写成空白而不是 “NA” 字符串。因为很多老旧 ERP 系统只认空格不认 “NA”。
write_csv(df, "上报数据.csv", na = "")
另外,如果要导出一个带多个工作表的 Excel 文件,write_xlsx() 支持将 命名列表 作为输入,名称就是工作表名,非常实用。
write_csv(..., write_options = list(digits = 4)) 控制小数位数。不过目前 write_csv 没有直接参数,我的做法是先 mutate(across(where(is.double), ~ round(., 2))) 再写。
3.6 知识结构图:数据读取流程
我把今天的内容整理成了一张流程图。你可以按照这个思路,根据文件扩展名和来源,选择正确的包和参数。
牢记: 读进来的数据,先做三件事——glimpse() 看结构、summary() 看分布、head() 看前几行。确认列名、类型、缺失值都没问题,再进行后续的质控计算。
3.7 日常避坑指南
最后分享几条我自己踩过的坑,希望能帮你节省时间:
- 文件路径不要用中文名称。R 在不同操作系统上对中文路径支持不稳定,尽量用英文或数字命名。
- 遇到编码问题先检查文件。用记事本打开 CSV,另存为时可以看到当前编码。如果全是乱码,试试
encoding = "UTF-8-BOM"或encoding = "GB18030"。 - Excel 里的合并单元格要小心。合并区域中只有左上角有值,其余单元格为空。读取前尽量在 Excel 里取消合并,或者用
tidyr::fill()填充。 - haven 读出来的 labelled 列不要直接运算。比如你拿到 “缺陷等级” 列,显示 1、2、3,但 1 可能代表 “严重”。直接求均值没有意义,先用
as_factor()转换,或者用zap_labels()剥离标签再运算。 - 大文件处理推荐使用 readr。它使用了内存映射技术,可以读入比内存还大的文件,前提是操作系统支持。如果遇到 10GB 以上的 CSV,考虑
read_csv_chunked()分块读取。
好了,第三章就到这儿。数据读取是质控分析的起点,磨刀不误砍柴工,把这一步做稳了,后面写任何统计代码都不会因为数据格式问题卡壳。