Menu

R 语言读取 CSV 文件(read.csv 和 read_csv)

如何用 read.csv() 把 CSV 文件导入 R:真正重要的几个参数,导致大多数失败的工作目录陷阱,以及什么时候值得用 readr::read_csv。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

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 = ",")。

Coddy 编程语言插图

和 Coddy 一起学编程

开始