使用固定宽度读取大数据

21
如何读取格式为固定宽度的大数据?我阅读了this中的问题并尝试了一些技巧,但所有答案都是针对分隔符数据(如.csv),而这不是我的情况。 数据有558MB,我不知道有多少行。
我正在使用:
dados <- read.fwf('TS_MATRICULA_RS.txt', width=c(5, 13, 14, 3, 3, 5, 4, 6, 6, 6, 1, 1, 1, 4, 3, 2, 9, 3, 2, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 4, 11, 9, 2, 3, 9, 3, 2, 9, 9, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1), stringsAsFactors=FALSE, comment.char='', 
    colClasses=c('integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'character', 'character', 'character',
    'integer', 'integer', 'character', 'integer', 'integer', 'character', 'integer', 'character', 'character', 'character', 'character', 'character', 'character',
    'character', 'character', 'character', 'character', 'character', 'character', 'character', 'character', 'character', 'character', 'character', 'character',
    'character', 'character', 'character', 'character', 'character', 'character', 'character', 'character', 'character', 'character', 'character', 'integer',
    'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'character', 'integer', 'integer', 'character', 'character', 'character',
    'character', 'integer', 'character', 'character', 'character', 'character', 'character', 'character', 'character', 'character'), buffersize=180000)

但是读取数据需要30分钟(而且还在计时...)。有什么新的建议吗?

如果您的源文件元素足够“规矩”,也许您可以使用某些文本编辑器进行全局替换,将“,”替换为空格,然后使用read.csv?另一个猜测:使用readLines将文件读入R,然后从stdin运行read.fwf?我从未尝试过这样做,所以可能完全不正确。 - Carl Witthoft
没错,让Notepad++来进行转换吧。或者SciTe,或者几乎任何文本编辑器,就像我和statquant建议的那样。 - Carl Witthoft
我需要在Notepad++中使用任何插件来完成它吗? - Rcoster
1
你有宽度的索引吗?试用substr函数在sqldf中操作。或者创建一个csvkit模式文件,使用csvkit创建CSV文件,并使用data.table库中的dread函数读取CSV文件。 - A5C1D2H2I1M1N2O1R2T1
1
我会尝试稍后更新我的回答,但与此同时,我想分享一个你可能感兴趣的包:iotools - A5C1D2H2I1M1N2O1R2T1
显示剩余14条评论
3个回答

11

没有足够的数据细节,很难给出具体答案,但以下是一些启示来帮助你开始:

首先,如果你在Unix系统上,你可以使用wc命令获取有关文件的一些信息。例如wc -l TS_MATRICULA_RS.txt会告诉你文件中有多少行,wc -L TS_MATRICULA_RS.txt会报告文件中最长行的长度。这可能是有用的。同样,headtail将让你检查文本文件的前10行和后10行。

其次,有一些建议:因为你似乎知道每个字段的宽度,我建议采用以下两种方法之一。

选项1:csvkit + 你喜欢的快速读取大型数据的方法

csvkit是一组用于处理CSV文件的Python工具。其中一个工具是in2csv,它接受一个固定宽度格式的文件和一个“模式”文件,以创建一个正确的CSV文件,可用于其他程序。

模式文件本身是一个CSV文件,有三列:(1)变量名称,(2)起始位置和(3)宽度。一个例子(来自in2csv手册)是:

    column,start,length
    name,0,30 
    birthday,30,10 
    age,40,3

一旦您创建了该文件,就应该能够使用类似以下的内容:

in2csv -f fixed -s path/to/schemafile.csv path/to/TS_MATRICULA_RS.txt > TS_MATRICULA_RS.csv

建议使用"data.table"包中的fread函数读取数据,或者使用sqldf

选项2:sqldfsubstr的结合使用

对于像你这样的大型数据文件,使用sqldf实际上应该非常快,并且您可以通过substr指定要读取的内容。

同样,这将期望您有一个可用的架构文件,就像上面描述的那样。一旦您拥有了架构文件,您可以执行以下操作:

temp <- read.csv("mySchemaFile.csv")

## Construct your "substr" command
GetMe <- paste("select", 
               paste("substr(V1, ", temp$start, ", ",
                     temp$length, ") `", temp$column, "`", 
                     sep = "", collapse = ", "), 
               "from fixed", sep = " ")

## Load "sqldf"
library(sqldf)

## Connect to your file
fixed <- file("TS_MATRICULA_RS.txt")
myDF <- sqldf(GetMe, file.format = list(sep = "_"))

如果您知道宽度,可能可以跳过生成模式文件的步骤。从宽度来看,只需要使用 cumsum 完成一些工作。这里有一个基本示例,构建在 read.fwf 的第一个示例之上:


ff <- tempfile()
cat(file = ff, "123456", "987654", sep = "\n")
read.fwf(ff, widths = c(1, 2, 3))

widths <- c(1, 2, 3)
length <- cumsum(widths)
start <- length - widths + 1
column <- paste("V", seq_along(length), sep = "")

GetMe <- paste("select", 
               paste("substr(V1, ", start, ", ",
                     widths, ") `", column, "`", 
                     sep = "", collapse = ", "), 
               "from fixed", sep = " ")

library(sqldf)

## Connect to your file
fixed <- file(ff)
myDF <- sqldf(GetMe, file.format = list(sep = "_"))
myDF
unlink(ff)

1
点击这里查看一些基准测试结果。我无法让sqldf版本正常工作(出现一个关于没有名为“V1”的列的错误),所以暂时将其排除在外。 - MichaelChirico

11

LaF包非常擅长快速读取固定宽度文件。我每天使用它来加载大约100Mio记录和30列的文件(并不像你有那么多字符列 - 主要是数值数据和一些因子)。而且它非常快速。所以这就是我会做的。

library(LaF)
library(ffbase)
my.data.laf <- laf_open_fwf('TS_MATRICULA_RS.txt', 
                  column_widths=c(5, 13, 14, 3, 3, 5, 4, 6, 6, 6, 1, 1, 1, 4, 3, 2, 9, 3, 2, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 4, 11, 9, 2, 3, 9, 3, 2, 9, 9, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1), stringsAsFactors=FALSE, comment.char='', 
                  column_types=c('integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'categorical', 'categorical', 'categorical',
                               'integer', 'integer', 'categorical', 'integer', 'integer', 'categorical', 'integer', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical',
                               'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical',
                               'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'integer',
                               'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'categorical', 'integer', 'integer', 'categorical', 'categorical', 'categorical',
                               'categorical', 'integer', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical', 'categorical'))
my.data <- laf_to_ffdf(my.data.laf, nrows=1000000)
my.data.in.ram <- as.data.frame(my.data)

顺便说一下,我开始使用LaF包是因为我对read.fwf的缓慢感到烦恼,并且最初用于解析数据的PL/SQL PostgreSQL代码变得难以维护。


1
110021407656 不是一个整数。在 R 中查看 as.integer(110021407656) 的结果以及 as.double(110021407656) 的结果是什么?在上面的代码中将该列的列类型调整为“double”。 - user1600826
1
@Rcoster 嗯嗯……将一个数减去另一个数,得到2^33*13。你有没有可能在这里遇到了2^32的限制?(也就是说你肯定遇到了) - Carl Witthoft
尝试过这个,但它会崩溃(RStudio/R 3.0.0) - Ari B. Friedman
@Ari,你是在使用Rcoster的数据上运行完全相同的代码吗?还是你有自己的代码和数据,这似乎给你带来了麻烦?如果是后者,请分享你的代码和数据。 - user1600826
你能否提供一个可重现的例子? - user1600826
显示剩余3条评论

7

这里提供了一个使用新的包readr的纯R解决方案。该包由Hadley Wickham和RStudio团队创建,于2015年4月发布。更多信息请查看这里。代码非常简单:

library(readr)

my.data.frame <- read_fwf('TS_MATRICULA_RS.txt',
                      fwf_widths(c(5, 13, 14, 3, 3, 5, 4, 6, 6, 6, 1, 1, 1, 4, 3, 2, 9, 3, 2, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 4, 11, 9, 2, 3, 9, 3, 2, 9, 9, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1)),
                      progress = interactive())

read_fwf{readr} 的优点

  • readr 基于 LaF,但是表现出乎意料的更快。它已经被证明是在R中读取定宽文件最快的方法
  • 它比其他替代方案更简单。例如,您不需要担心column_types,因为它们将从输入的前30行中推断。
  • 它带有进度条 ;)

提醒:read_fwf() 函数会修剪所有字符列,这个功能无法关闭。 - Feng Jiang
你所说的“修剪所有字符列”具体是什么意思? - rafa.pereira
1
它将从所有字符列的左/右两侧删除空格。请参见LaF可以关闭它:https://www.rdocumentation.org/packages/LaF/versions/0.6.3/topics/laf_open_fwf。readr中的read_delim也可以关闭它:https://www.rdocumentation.org/packages/readr/versions/1.0.0/topics/read_delim。 - Feng Jiang

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