如何按行条件将大型CSV文件读入R?

11

我有一个大型的CSV文件,大约1500万行,大小约为3G。

我想通过分块读取这个文件到R中,每次只选择符合特定条件的行。

例如,其中一个列名叫做产品类型,所以我只需要将一种类型的产品读入R,然后处理它并输出结果,之后再转到另一种产品类型...

到目前为止,我已经了解了不同的方法,例如将大文件上传到数据库中,或者使用colbycol逐列读取,或者使用ff逐块读取行...

是否有纯R解决方案可以解决我的问题?


2
“纯R”是指不使用任何包吗?如果是,为什么?这是因为你现在的设置有限制还是只是一种思维锻炼?如果您可以考虑使用包,那么“sqldf”可能是一个可行的选择。 - A5C1D2H2I1M1N2O1R2T1
这里有一个非常好的答案由@eddi提供帮助,假设你愿意使用命令行工具而不仅仅是 base :: R - Simon O'Hanlon
1
如果我们不使用R语言的解决方案,也可以看看csvkit。csvkit - A5C1D2H2I1M1N2O1R2T1
如果你的数据适合内存,我会使用 data.table ,特别是 fread ,如果不适合,才考虑其他方法。 - eddi
data.table 需要先将整个文件读入内存吗? - linus
软件包 sqldfRSQLite 不需要任何设置,只需安装即可使用。就像软件包 data.table 一样,是的,fread 可以读取所有内容。 - ROLO
3个回答

20
您可以使用RSQLite软件包:
library(RSQLite)
# Create/Connect to a database
con <- dbConnect("SQLite", dbname = "sample_db.sqlite")

# read csv file into sql database
# Warning: this is going to take some time and disk space, 
#   as your complete CSV file is transferred into an SQLite database.
dbWriteTable(con, name="sample_table", value="Your_Big_CSV_File.csv", 
    row.names=FALSE, header=TRUE, sep = ",")

# Query your data as you like
yourData <- dbGetQuery(con, "SELECT * FROM sample_table LIMIT 10")

dbDisconnect(con)

下次访问数据时,您可以省略dbWriteTable,因为SQLite表存储在磁盘上。

注意:将CSV数据写入SQLite文件时不会先将所有数据加载到内存中。因此,最终使用的内存将仅限于查询返回的数据量。


5

只使用R语言可以实现以下操作:

  1. 打开文件连接
  2. 如果有表头,则读取表头信息
  3. 使用 read.csv 函数读取文件中的一行,指定 colClassesnrows=1
  4. 对读取的这一行进行测试,如果符合条件,则将其添加到数据框中
  5. 重复第4步,直到读取完整个文件
  6. 关闭文件连接

虽然以上方法可行,但我认为并不推荐。更好的做法是将数据加载到数据库中,然后从R语言中查询数据库。


1
每次读取一行将非常低效,最好每次读取10,000行(或更多)。 - hadley
1
@Hadley,我的理解是,在幕后R会读取几行数据,然后只在你要求时给出你要求的数量,当你要求更多行时,它会从内部缓冲区中提供它们,而不是重新从磁盘读取(直到你用完缓冲区)。但是,我可能是错的。 - Greg Snow
我非常确定那不是那样的,但你永远不知道。 - hadley
@Hadley,这篇文章:http://tolstoy.newcastle.edu.au/R/help/05/12/18001.html 表明操作系统会进行缓存。无论如何,我仍然建议OP使用数据库方法而不是直接读取。 - Greg Snow

0

您也可以使用JDBC来实现这一点。让我们创建一个示例CSV文件。

write.table(x=mtcars, file="mtcars.csv", sep=",", row.names=F, col.names=T) # create example csv file

从此链接http://sourceforge.net/projects/csvjdbc/files/latest/download下载并保存CSV JDBC驱动程序,然后设置驱动程序。

> library(RJDBC)

> path.to.jdbc.driver <- "jdbc//csvjdbc-1.0-18.jar"
> drv <- JDBC("org.relique.jdbc.csv.CsvDriver", path.to.jdbc.driver)
> conn <- dbConnect(drv, sprintf("jdbc:relique:csv:%s", getwd())) # replace getwd() with location of csv file

让我们来看一下mtcars数据集中的前3行:

> head(dbGetQuery(conn, "select * from mtcars"), 3)
   mpg cyl disp  hp drat    wt  qsec vs am gear carb
1   21   6  160 110  3.9  2.62 16.46  0  1    4    4
2   21   6  160 110  3.9 2.875 17.02  0  1    4    4
3 22.8   4  108  93 3.85  2.32 18.61  1  1    4    1

接下来,让我们看一下gear列所取的不同值及其相应的计数:
> dbGetQuery(conn, "select gear, count(*) from mtcars group by gear")
  GEAR COUNT(*)
1    4       12
2    3       15
3    5        5

现在您可以使用where子句来过滤数据,仅选择gear值为5的行来组成查询:
> dbGetQuery(conn, "select * from mtcars where gear = '5'")
   mpg cyl  disp  hp drat    wt qsec vs am gear carb
1   26   4 120.3  91 4.43  2.14 16.7  0  1    5    2
2 30.4   4  95.1 113 3.77 1.513 16.9  1  1    5    2
3 15.8   8   351 264 4.22  3.17 14.5  0  1    5    4
4 19.7   6   145 175 3.62  2.77 15.5  0  1    5    6
5   15   8   301 335 3.54  3.57 14.6  0  1    5    8

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接