为了账号安全,请及时绑定邮箱和手机立即绑定

读取固定宽度文件的更快方法

/ 猿问

读取固定宽度文件的更快方法

慕盖茨9453107 2019-11-30 10:41:08

我处理许多需要读入R的固定宽度文件(即,没有分隔符)。因此,通常有一个列宽定义,用于将字符串解析为变量。我可以使用read.fwf它毫无问题地读取数据。但是,对于大文件,这可能需要很长时间。对于最近的数据集,这需要800秒才能读取具有约500,000行和143个变量的数据集。


seer9 <- read.fwf("~/data/rawdata.txt", 

  widths = cols,

  header = FALSE,

  buffersize = 250000,

  colClasses = "character",

  stringsAsFactors = FALSE))

freaddata.tableR 的软件包中的s非常适合解决大多数数据读取问题,但它不解析固定宽度的文件。但是,我可以将每行读为单个字符串(〜500,000行,1列)。这需要3-5秒。(我喜欢data.table。)


seer9 <- fread("~/data/rawdata.txt", colClasses = "character",

               sep = "\n", header = FALSE, verbose = TRUE)

在SO上有很多关于如何解析文本文件的好帖子。见JHoward的建议在这里,创建起始和终止列的矩阵,并substr分析数据。请参阅此处使用的GSee建议strsplit。我不知道如何使用此数据。(此外,迈克尔·史密斯(Michael Smith)在data.table邮件列表上提出了一些建议,这些建议sed超出了我的实现能力。)现在,使用fread,substr()我可以在25到30秒内完成整个操作。请注意,最后强制到data.table需要花费大量时间(5秒?)。


end_col <- cumsum(cols)

start_col <- end_col - cols + 1

start_end <- cbind(start_col, end_col) # matrix of start and end positions

text <- lapply(seer9, function(x) {

        apply(start_end, 1, function(y) substr(x, y[1], y[2])) 

        })

dt <- data.table(text$V1)

setnames(dt, old = 1:ncol(dt), new = seervars)

我想知道的是,是否可以进一步改善?我知道不是唯一需要读取固定宽度文件的人,因此,如果可以更快地读取固定宽度的文件,则可以容忍更大的文件(数百万行)的加载。我尝试使用parallel与mclapply和data.table代替的lapply,但这些并没有改变任何东西。(可能是由于我对R的经验不足。)我想可以编写一个Rcpp函数来真正快速地完成此操作,但这超出了我的技能范围。另外,我可能没有使用lapply并正确应用。


我的data.table实现(带有magrittr链接)花费相同的时间:


text <- seer9[ , apply(start_end, 1, function(y) substr(V1, y[1], y[2]))] %>% 

  data.table(.)

谁能提出建议以提高速度?还是说它尽可能地好?


这是在R中创建类似data.table的代码(而不是链接到实际数据)。它应该具有331个字符和500,000行。有空格可以模拟数据中缺少的字段,但这不是空格分隔的数据。(如果有人有兴趣,我正在读取SEER原始数据。)还包括列宽(cols)和变量名(seervars),以防对他人有所帮助。这些是SEER数据的实际列和变量定义。


seer9 <-

  data.table(rep((paste0(paste0(letters, 1000:1054, " ", collapse = ""), " ")),

                 500000))


cols = c(8,10,1,2,1,1,1,3,4,3,2,2,4,4,1,4,1,4,1,1,1,1,3,2,2,1,2,2,13,2,4,1,1,1,1,3,3,3,2,3,3,3,3,3,3,3,2,2,2,2,1,1,1,1,1,6,6,6,2,1,1,2,1,1,1,1,1,2,2,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,7,5,4,10,3,3,2,2,2,3,1,1,1,1,2,2,1,1,2,1,9,5,5,1,1,1,2,2,1,1,1,1,1,1,1,1,2,3,3,3,3,3,3,1,4,1,4,1,1,3,3,3,3,2,2,2,2)

查看完整描述

3 回答

?
波斯汪

既然(在此与其他有关有效读取固定宽度文件的主要问题之间)在报价中提供了大量读取此类文件的选项,我认为应该进行一些基准测试。


我将使用以下大型文件(400 MB)进行比较。它只是一堆随机字符,具有随机定义的字段和宽度:


set.seed(21394)

wwidth = 400L

rrows = 1000000


#creating the contents at random

contents = 

  write.table(replicate(rrows, paste0(sample(letters, wwidth, replace = TRUE),

                                      collapse = "")), file="testfwf.txt",

              quote = FALSE, row.names = FALSE, col.names = FALSE)


#defining the fields & writing a dictionary

n_fields = 40L

endpoints = unique(c(1L, sort(sample(wwidth, n_fields - 1L)), wwidth + 1L))

cols = ist(beg = endpoints[-(n_fields + 1L)],

             end = endpoints[-1L] - 1L)


dict = data.frame(column = paste0("V", seq_len(length(endpoints)) - 1L)),

                  start = endpoints[-length(endpoints)] - 1,

                  length = diff(endpoints))


write.csv(dict, file = "testdic.csv", quote = FALSE, row.names = FALSE)

我将比较这两个线程之间提到的五个方法(如果作者愿意的话,我还会添加一些其他方法):基本版本(read.fwf),将结果in2csv传递到fread(@AnandaMahto的建议),Hadley的新readr(read_fwf),使用LaF/ ffbase(@jwijffls的建议),以及问题作者(@MarkDanese)fread与stri_subfrom 结合提出的建议的改进版本(简化版)stringi。


这是基准代码:


library(data.table)

library(stringi)

library(readr)

library(LaF); library(ffbase)

library(microbenchmark)


microbenchmark(times = 5L,

               utils = read.fwf("testfwf.txt", diff(endpoints), header = FALSE),

               in2csv = 

                 fread(paste("in2csv -f fixed -s",

                             "~/Desktop/testdic.csv",

                             "~/Desktop/testfwf.txt")),

               readr = read_fwf("testfwf.txt", fwf_widths(diff(endpoints))),

               LaF = {

                 my.data.laf = 

                   laf_open_fwf('testfwf.txt', column_widths=diff(endpoints),

                                column_types = rep("character", 

                                                   length(endpoints) - 1L))

                 my.data = laf_to_ffdf(my.data.laf, nrows = rrows)

                 as.data.frame(my.data)},

               fread = fread(

                 "testfwf.txt", header = FALSE, sep = "\n"

                 )[ , lapply(seq_len(length(cols$beg)),

                             function(ii) 

                               stri_sub(V1, cols$beg[ii], cols$end[ii]))])

并输出:


# Unit: seconds

#    expr       min        lq      mean    median        uq       max neval cld

#   utils 423.76786 465.39212 499.00109 501.87568 543.12382 560.84598     5   c

#  in2csv  67.74065  68.56549  69.60069  70.11774  70.18746  71.39210     5 a  

#   readr  10.57945  11.32205  15.70224  14.89057  19.54617  22.17298     5 a  

#     LaF 207.56267 236.39389 239.45985 237.96155 238.28316 277.09798     5  b 

#   fread  14.42617  15.44693  26.09877  15.76016  20.45481  64.40581     5 a  

因此,readr和fread+ 似乎stri_sub是最快的竞争产品;内置read.fwf是明显的失败者。


请注意,readr这里的真正优点是您可以预先指定列类型。之后,fread您必须输入convert。


编辑:添加一些替代品

在@AnandaMahto的建议下,我包括更多选项,包括一个似乎是新的赢家!为了节省时间,我在新的比较中排除了上面最慢的选项。这是新的代码:


library(iotools)


microbenchmark(times = 5L,

               readr = read_fwf("testfwf.txt", fwf_widths(diff(endpoints))),

               fread = fread(

                 "testfwf.txt", header = FALSE, sep = "\n"

                 )[ , lapply(seq_len(length(cols$beg)),

                             function(ii) 

                               stri_sub(V1, cols$beg[ii], cols$end[ii]))],

               iotools = input.file("testfwf.txt", formatter = dstrfw, 

                                    col_types = rep("character",

                                                    length(endpoints) - 1L), 

                                    widths = diff(endpoints)),

               awk = fread(paste(

                 "awk -v FIELDWIDTHS='", 

                 paste(diff(endpoints), collapse = " "), 

                 "' -v OFS=', ' '{$1=$1 \"\"; print}' < ~/Desktop/testfwf.txt", 

                 collapse = " "), header = FALSE))

和新的输出:


# Unit: seconds

#     expr       min        lq      mean    median        uq       max neval cld

#    readr  7.892527  8.016857 10.293371  9.527409  9.807145 16.222916     5  a 

#    fread  9.652377  9.696135  9.796438  9.712686  9.807830 10.113160     5  a 

#  iotools  5.900362  7.591847  7.438049  7.799729  7.845727  8.052579     5  a 

#      awk 14.440489 14.457329 14.637879 14.472836 14.666587 15.152156     5   b

因此,它看起来iotools非常快而且非常一致。


查看完整回答
反对 2019-11-30
?
慕田峪9158850

您可以使用该LaF程序包,该程序包用于处理较大的固定宽度文件(也太大而无法容纳到内存中)。要使用它,您首先需要使用打开文件laf_open_fwf。然后,您可以像对普通数据框一样索引结果对象以读取所需的数据。在下面的示例中,我读取了整个文件,但您也可以读取特定的列和/或行:


library(LaF)

laf <- laf_open_fwf("foo.dat", column_widths = cols, 

  column_types=rep("character", length(cols)),

  column_names = seervars)

seer9 <- laf[,]

您使用5000行(而不是500,000行)的示例使用花费了28秒,使用花费了read.fwf1.6秒LaF。


另外,使用50,000行(而不是500,000行)的示例在我的机器上使用258秒,使用read.fwf7秒LaF。


查看完整回答
反对 2019-11-30
?
噜噜哒

我不确定您使用的是什么操作系统,但这在Linux中对我来说很简单:


步骤1:创建用于awk将文件转换为csv的命令


如果您还计划在其他软件中使用数据,则可以将其存储到实际的csv文件中。


myCommand <- paste(

  "awk -v FIELDWIDTHS='", 

  paste(cols, collapse = " "), 

  "' -v OFS=',' '{$1=$1 \"\"; print}' < ~/rawdata.txt", 

  collapse = " ")

步骤2:fread直接在刚创建的命令上使用


seer9 <- fread(myCommand)

我没有计时,因为显然使用的系统比您和Jan慢:-)


查看完整回答
反对 2019-11-30
  • 3 回答
  • 0 关注
  • 130 浏览

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信