我使用了很多需要读入R的固定宽度文件(即没有分隔符)。因此,通常会有一个列宽的定义来将字符串解析为变量。我可以使用read.fwf
来读取数据而不会出现问题。但是,对于大型文件,这可能需要长时间。对于最近的数据集,这需要800秒来读取具有~500,000行和143个变量的数据集。
seer9 <- read.fwf("~/data/rawdata.txt",
widths = cols,
header = FALSE,
buffersize = 250000,
colClasses = "character",
stringsAsFactors = FALSE))
R中fread
包中的 data.table
非常适合解决大多数数据读取问题,除非它不解析固定宽度文件。但是,我可以将每一行读作单个字符串(~500,000行,1列)。这需要3-5秒。 (我喜欢data.table。)
seer9 <- fread("~/data/rawdata.txt", colClasses = "character",
sep = "\n", header = FALSE, verbose = TRUE)
在SO上有很多关于如何解析文本文件的好帖子。请参阅JHoward的建议here,以创建开始和结束列的矩阵,并substr
来解析数据。请参阅GSee的建议here以使用strsplit
。我无法弄清楚如何使用这些数据。 (另外,迈克尔·史密斯对涉及sed
的{。{1}}的data.table邮件列表提出了一些建议,这些建议超出了implement.的能力范围。现在,使用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行。有一些空间可以模拟数据中缺少的字段,但这是 NOT 空格分隔数据。 (我正在阅读原始的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)
seervars <- c("CASENUM", "REG", "MAR_STAT", "RACE", "ORIGIN", "NHIA", "SEX", "AGE_DX", "YR_BRTH", "PLC_BRTH", "SEQ_NUM", "DATE_mo", "DATE_yr", "SITEO2V", "LATERAL", "HISTO2V", "BEHO2V", "HISTO3V", "BEHO3V", "GRADE", "DX_CONF", "REPT_SRC", "EOD10_SZ", "EOD10_EX", "EOD10_PE", "EOD10_ND", "EOD10_PN", "EOD10_NE", "EOD13", "EOD2", "EOD4", "EODCODE", "TUMOR_1V", "TUMOR_2V", "TUMOR_3V", "CS_SIZE", "CS_EXT", "CS_NODE", "CS_METS", "CS_SSF1", "CS_SSF2", "CS_SSF3", "CS_SSF4", "CS_SSF5", "CS_SSF6", "CS_SSF25", "D_AJCC_T", "D_AJCC_N", "D_AJCC_M", "D_AJCC_S", "D_SSG77", "D_SSG00", "D_AJCC_F", "D_SSG77F", "D_SSG00F", "CSV_ORG", "CSV_DER", "CSV_CUR", "SURGPRIM", "SCOPE", "SURGOTH", "SURGNODE", "RECONST", "NO_SURG", "RADIATN", "RAD_BRN", "RAD_SURG", "SS_SURG", "SRPRIM02", "SCOPE02", "SRGOTH02", "REC_NO", "O_SITAGE", "O_SEQCON", "O_SEQLAT", "O_SURCON", "O_SITTYP", "H_BENIGN", "O_RPTSRC", "O_DFSITE", "O_LEUKDX", "O_SITBEH", "O_EODDT", "O_SITEOD", "O_SITMOR", "TYPEFUP", "AGE_REC", "SITERWHO", "ICDOTO9V", "ICDOT10V", "ICCC3WHO", "ICCC3XWHO", "BEHANAL", "HISTREC", "BRAINREC", "CS0204SCHEMA", "RAC_RECA", "RAC_RECY", "NHIAREC", "HST_STGA", "AJCC_STG", "AJ_3SEER", "SSG77", "SSG2000", "NUMPRIMS", "FIRSTPRM", "STCOUNTY", "ICD_5DIG", "CODKM", "STAT_REC", "IHS", "HIST_SSG_2000", "AYA_RECODE", "LYMPHOMA_RECODE", "DTH_CLASS", "O_DTH_CLASS", "EXTEVAL", "NODEEVAL", "METSEVAL", "INTPRIM", "ERSTATUS", "PRSTATUS", "CSSCHEMA", "CS_SSF8", "CS_SSF10", "CS_SSF11", "CS_SSF13", "CS_SSF15", "CS_SSF16", "VASINV", "SRV_TIME_MON", "SRV_TIME_MON_FLAG", "SRV_TIME_MON_PA", "SRV_TIME_MON_FLAG_PA", "INSREC_PUB", "DAJCC7T", "DAJCC7N", "DAJCC7M", "DAJCC7STG", "ADJTM_6VALUE", "ADJNM_6VALUE", "ADJM_6VALUE", "ADJAJCCSTG")
更新 LaF在原始.txt文件中用不到7秒的时间完成了整个读取。也许有更快的方式,但我怀疑任何事情都可以做得更好。惊人的包装。
2015年7月27日更新 只想提供一个小的更新。我使用了新的readr包,并且能够使用readr :: read_fwf在5秒内读取整个文件。
seer9_readr <- read_fwf("path_to_data/COLRECT.TXT",
col_positions = fwf_widths(cols))
此外,更新的stringi :: stri_sub函数至少是base :: substr()的两倍。因此,在上面的代码中使用fread来读取文件(大约4秒),然后应用于解析每一行,使用stringi :: stri_sub提取143个变量大约需要8秒,而对于base :: substr则需要19。所以,fread plus stri_sub仍然只有大约12秒的运行时间。还不错。
seer9 <- fread("path_to_data/COLRECT.TXT",
colClasses = "character",
sep = "\n",
header = FALSE)
text <- seer9[ , apply(start_end, 1, function(y) substr(V1, y[1], y[2]))] %>%
data.table(.)
请参阅@MichaelChirico的answer below,他已经添加了一些很棒的基准测试和iotools软件包。
答案 0 :(得分:30)
您可以使用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行)的示例使用read.fwf
需要28秒,使用LaF
需要1.6秒。
添加您使用read.fwf
使用50,000行(而不是500,000行)的示例需要258秒,而在我的计算机上使用LaF
需要7秒。
答案 1 :(得分:28)
现在有(在other major question之间关于固定宽度文件的有效读取)有相当多的选项可供阅读此类文件,我认为一些基准测试是合适的。
我将使用以下大面积(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
),管道{{1使用in2csv
/ fread
来readr
(@ AnandaMahto的建议),Hadley的新read_fwf
(LaF
) (@ jwijffls&#39;建议),以及问题作者(@MarkDanese)建议的改进(简化)版本,将ffbase
与fread
中的stri_sub
结合起来。
以下是基准测试代码:
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
,您之后必须输入转换。
在@ AnandaMahto的建议中,我提供了更多选项,包括一个似乎是新赢家的选项!为了节省时间,我在新的比较中排除了上面最慢的选项。这是新代码:
fread
新输出:
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
非常快且非常一致。
答案 2 :(得分:3)
昨天我为这种事写了一个解析器,但它是针对头文件的一种非常特殊的输入,所以我将向你展示如何格式化你的列宽以便能够使用它。
首先下载the tool in question。
如果您使用的是OS X Mavericks(我在其上编译),您可以从bin
目录下载二进制文件,或者转到src
并使用clang++ csv_iterator.cpp parse.cpp main.cpp -o flatfileparser
进行编译。 / p>
平面文件解析器需要两个文件,一个CSV头文件,其中每五个元素指定变量宽度(同样,这是由于我非常特殊的应用程序),您可以使用以下方法生成:
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)
writeLines(sapply(c(-1, cols), function(x) paste0(',,,,', x)), '~/tmp/header.csv')
并将生成的~/tmp/header.csv
复制到与flatfileparser
相同的目录中。将平面文件移动到同一目录,您可以在平面文件上运行它:
./flatfileparser header.csv yourflatfile
将产生yourflatfile.csv
。使用管道(Bash中的>>
)手动添加上面的标题。
使用Hadley的实验fastread package将文件名传递给fastread::read_csv
,产生data.frame
。我不相信他支持fwf
文件,尽管它已经开始了。
答案 3 :(得分:2)
我不确定您使用的是哪种操作系统,但这对我来说非常直接适用于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更慢的系统: - )