read.csv() 把 CSV 文件变成数据框
在 R 中读取 CSV 只需要调用一个函数,它内置在语言中,不需要任何包:
sales <- read.csv("sales.csv")
read.csv() 读取文件,把第一行作为列名,为每一列推断类型,然后返回一个数据框。对于格式规范的文件,这就是全部了。
你甚至不需要任何文件就能看到它的效果,因为 read.csv() 也可以通过 text 参数接受原始 CSV 文本,解析器还是同一个,只是传入的是字符串而不是文件名:
导入任何数据后,第一件事就是运行 str():它会显示每一列以及推断出的类型。这里 name 被读成字符型,score 是整数,passed 是逻辑型,全都是根据值推断出来的。想快速看一眼,head(students) 会显示前几行,nrow(students) 会统计行数。如果你以为是数值的列显示成了 chr,说明那一列里有不是数字的东西:一条多余的注释、一个 "N/A"、一个千位分隔符。这时就该修复它,而不是等到画了三张图之后。
真正重要的参数
read.csv() 有几十个参数,你会用到的大约只有五个。
header:第一行是否是列名,默认为 TRUE。如果你的文件一开始就是数据,传入 header = FALSE,R 会把列命名为 V1、V2……
sep:字段分隔符,默认为 ","。欧洲很多地方写 CSV 时用分号作分隔符,因为逗号在那里是小数点。R 正好为此提供了一个预先配置好的版本:read.csv2() 使用 sep = ";" 和 dec = ",":
身高被正确地读成了数字,read.csv2 知道 1,63 的意思是 1.63。如果对这个文件用普通的 read.csv,你会得到一个乱掉的列,因为把小数点逗号当作分隔符来拆分,怎么拆都不对。
na.strings:哪些字符串算作缺失值。默认只有 "NA" 会变成缺失值。实际导出的数据会把缺失值写成空单元格、"missing"、"-"、"NULL",除非你声明它们,否则它们会作为文本读进来,把整列都拖成字符型:
设置了正确的 na.strings,空值就变成了真正的 NA,score 保持数值型。不设置的话,"missing" 只是一个单词,整列都变成了文本。
skip:数据开始之前要忽略的行数。导出文件很喜欢在真正的表格上方放一两行标题;skip = 2 就能跳过它们。
colClasses:强制指定列的类型,而不是让 R 去猜。最典型的受害者是带前导零的数据,邮编、电话号码、产品 ID,R 的类型推断会把它们读成数字,零就没了:
00501 变成了 501。文件中的数据是对的,到了 R 里却悄悄地错了。colClasses = c("integer", "character") 按顺序告诉解析器每一列是什么类型,零就保住了。
有一个参数你已经不需要了:stringsAsFactors。在 R 的大部分历史中,除非你另外指定,read.csv() 都会把每个文本列转换成因子,这造成了大量的困惑。从 R 4.0 起,默认值变成了 FALSE,文本就保持为文本。在一些老教程里你仍然会到处看到 stringsAsFactors = FALSE;在当前版本的 R 中,它无害,但多余。
文件路径:read.csv 失败的头号原因
每个 R 学习者第一周都会遇到的错误:
Error in file(file, "rt") : cannot open the connection
In addition: Warning message:
cannot open file 'sales.csv': No such file or directory
文件明明存在,你就在文件管理器里看着它。但 R 并不在那里找。像 "sales.csv" 这样单独的文件名,是相对于 R 的工作目录来解析的,而工作目录通常不是你的文件所在的文件夹。两行代码就能诊断出来:
getwd() # where R is actually looking
file.exists("sales.csv") # FALSE means the path is wrong, full stop
如果 file.exists() 返回 FALSE,再怎么重新运行也没用,去改路径。要么给出完整路径(read.csv("C:/Users/ada/data/sales.csv"),正斜杠在 Windows 上也能用,而且比反斜杠更安全),要么用 setwd() 把 R 的工作目录切换到数据文件夹。工作目录是什么、项目如何管理它,以及为什么不提倡在脚本里用 setwd(),详见工作目录。
把 file.exists() 变成条件反射。它能在一秒钟内把“神秘的导入失败”变成“路径里有个拼写错误”。
直接从 URL 读取 CSV
凡是能用文件名的地方,read.csv() 都能用 URL,它会帮你下载文件:
url <- "https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv"
penguins <- read.csv(url)
str(penguins)
这对教程和公开数据集很方便:不需要下载步骤,也没有路径问题。代价是你的脚本现在依赖网络,也依赖这个 URL 一直有效;对于需要经常重新运行的脚本,下载一次,然后读取本地副本。
readr::read_csv:更快、话更多的替代方案
tidyverse 在 readr 包中提供了自己的 CSV 读取函数:read_csv()(是下划线,不是点)。值得了解它,因为你会读到的大多数现代 R 代码都用它:
install.packages("readr") # once
library(readr) # every session
flights <- read_csv("flights.csv")
和 read.csv() 相比,实际的区别有:
- 速度:处理几十万行的文件时明显更快。
- 返回 tibble:一种现代化的数据框,打印时只显示紧凑的预览,而不会刷满整个控制台。
- 会报告它的推断:读取后,它会打印每一列检测到的类型,所以如果某一列被解析成了字符型,而你期望的是数字,你立刻就能看到,而不是之后才吃惊。
- 从不改动列名:
read.csv()会把first name这样的表头改写成first.name;read_csv()则原样保留。
小文件用哪个函数都可以。当文件变大,或者脚本的其余部分本来就用 tidyverse 时,就用 read_csv()。上面讲到的路径、分隔符(也有 read_csv2())和缺失值字符串(na =),在这里同样适用,只是参数名略有不同。
分析完成后,把结果导出到文件的反向操作见 write.csv。
要点回顾
df <- read.csv("file.csv")把 CSV 读入数据框;读完立刻用str()和head()检查。read.csv(text = "...")直接解析 CSV 字符串,非常适合做实验和小示例。- 真正有用的参数:
header、sep(分号分隔就用read.csv2)、na.strings、skip、colClasses。 - “Cannot open file” 意味着工作目录不在你以为的地方,
getwd()和file.exists()能立刻搞清楚。 readr::read_csv()是更快的 tidyverse 版本:返回 tibble,报告列类型,不改动列名。
下一篇:根本不是纯文本的文件,用 readxl 包读取 Excel 电子表格。
常见问题
R 中怎么读取 CSV 文件?
用 read.csv("file.csv"),它是内置的,不需要任何包。它返回一个数据框,CSV 的每一列对应一列,第一行作为列名。把结果赋给一个变量:df <- read.csv("sales.csv"),然后用 str(df) 和 head(df) 检查一下。
为什么 read.csv 报错 “cannot open file: No such file or directory”?
R 是相对于它的工作目录来查找文件的,而工作目录很可能不是你的文件所在的文件夹。运行 getwd() 看看 R 在哪里找,再用 file.exists("file.csv") 确认确实找不到。解决办法是使用完整路径,或者把工作目录设置为文件所在的文件夹。
R 中 read.csv 和 read_csv 有什么区别?
read.csv() 属于基础 R,随时可用,返回普通的数据框。read_csv() 来自 readr 包:处理大文件更快,返回 tibble,从不改动你的列名,还会打印它推断出的列类型,让你能立刻发现解析错误。小文件用哪个都行;大文件或 tidyverse 流程中,用 read_csv()。
R 中怎么读取用分号分隔的 CSV?
用 read.csv2("file.csv"),它就是按欧洲习惯预先配置好的 read.csv:分号作为分隔符,逗号作为小数点。也可以给普通的 read.csv() 传入 sep = ";"(需要时再加上 dec = ",")。