【发布时间】:2014-09-03 03:37:47
【问题描述】:
我使用许多需要读取到 R 中的固定宽度文件(即没有分隔字符)。因此,通常有一个列宽的定义来将字符串解析为变量。我可以使用read.fwf 毫无问题地读取数据。但是,对于大文件,这可能需要 很长 时间。对于最近的数据集,这需要 800 秒才能读取具有约 500,000 行和 143 个变量的数据集。
seer9 <- read.fwf("~/data/rawdata.txt",
widths = cols,
header = FALSE,
buffersize = 250000,
colClasses = "character",
stringsAsFactors = FALSE))
R 中data.table 包中的fread 非常适合解决大多数数据读取问题,只是它不解析固定宽度的文件。但是,我可以将每一行读取为单个字符串(约 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。我无法弄清楚如何使用这些数据进行操作。 (另外,Michael Smith 对涉及 sed 的 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 和 apply。
我的 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 在不到 7 秒的时间内从原始 .txt 文件中读取了整个文件。也许有更快的方法,但我怀疑任何事情都可以做得更好。令人惊叹的包装。
2015 年 7 月 27 日更新 只是想对此提供一个小更新。我使用了新的 readr 包,我可以在 5 秒内使用 readr::read_fwf 读取整个文件。
seer9_readr <- read_fwf("path_to_data/COLRECT.TXT",
col_positions = fwf_widths(cols))
此外,更新后的 stringi::stri_sub 函数的速度至少是 base::substr() 的两倍。因此,在上面的代码中,使用 fread 读取文件(大约 4 秒),然后使用 apply 来解析每一行,使用 stringi::stri_sub 提取 143 个变量大约需要 8 秒,而使用 base::substr 需要 19 秒。所以, fread 加上 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(.)
2015 年 12 月 10 日更新:
另请参阅 @MichaelChirico 的 answer below,他添加了一些出色的基准测试和 iotools 包。
【问题讨论】:
-
并行读取文件不会有帮助。瓶颈是文件 IO。 (当然,当数据分布在多台机器/硬盘上时除外。)
-
@JanvanderLaan,他可以使用
fread()在 5 秒内将所有数据读入 ram。并行解析 500k 字符串是我认为的问题。 -
@bdemarest 是的,你是对的。对于使用
fread和substr的代码,子串的解析确实是瓶颈,可以并行完成。
标签: r substring data.table apply lapply