r语言 - 从内存中的文本中提取列



我正在寻找一种快速的方法,从制表符分隔的文本中读取单列,作为内存中的字符向量。

我使用特定于我的字段的文件格式,大致类似于压缩的tsv文件。从这样的文件中读取行的子集是快速和容易的,但由于内存限制,直接读取read.table(),data.table::fread()readr::read_tsv()的数据是不可行的(并且我需要的行是未知的)。

所以,我最终在内存中得到一个字符向量,每行都有一个元素,但是制表符仍然在那里。我对如何从这篇文章中快速提取特定的一列有点困惑。在下面的示例中,提取第三列的最快方法是什么?文本中没有任何"意外",例如注释或引用的名称,但在我的实际情况中,列没有固定的宽度。到目前为止,我发现最快的方法是使用readr::read_tsv()函数。

library(readr)
set.seed(0)
# About 88Mb of memory
n_examples <- 1e6
text <- paste(
as.character(as.hexmode(sample(n_examples))),
as.character(as.hexmode(sample(n_examples))),
as.character(as.hexmode(sample(n_examples))),
as.character(as.hexmode(sample(n_examples))),
sep = "t"
)
fun_read.table <- function(x, i) {
read.table(
text = x, sep = "t", 
colClasses = c("character", "character", "character", "character")
)[[i]]
}
fun_read_tsv <- function(x, i) {
read_tsv(file = I(x), col_select = all_of(i), 
col_types = "cccc", col_names = LETTERS[1:4])[[1]]
}
bm <- bench::mark(
fun_read.table(text, 3),
fun_read_tsv(text, 3), 
min_iterations = 5
)
#> Warning: Some expressions had a GC in every iteration; so filtering is disabled.
print(bm)
#> # A tibble: 2 x 13
#>   expression                   min   median `itr/sec` mem_alloc `gc/sec` n_itr
#>   <bch:expr>              <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int>
#> 1 fun_read.table(text, 3)    1.34s    1.57s     0.619    93.2MB    1.11      5
#> 2 fun_read_tsv(text, 3)   879.36ms 903.72ms     1.10     35.1MB    0.219     5
#> # ... with 6 more variables: n_gc <dbl>, total_time <bch:tm>, result <list>,
#> #   memory <list>, time <list>, gc <list>

下面是我尝试过的一些替代方案,但都不如read_tsv()快。data.table::fread()方法非常慢,因为它首先将输入文本写入临时文件。我还没有设法找出一个基于正则表达式的方法来捕获第三列,所以我不知道这是否会更快。

library(data.table)
#> Warning: package 'data.table' was built under R version 4.1.1
fun_tstrsplit <- function(x, i) {
tstrsplit(x, "t", keep = i)[[1]]
}
fun_fread <- function(x, i) {
fread(
text = x, sep = "t",
colClasses = c("character", "character", "character", "character"),
select = i
)[[1]]
}
fun_scan <- function(x, i) {
ncols <- lengths(regmatches(x[[1]], gregexpr("t", x[[1]]))) + 1
scan(
text = x, sep = "t", what = "", quiet = TRUE
)[seq_along(x) %% ncols == i]
}

由reprex包(v2.0.1)在2018-10-13上创建

使用Rcpp编写的定制函数在这里对我来说工作得最快(比read_tsv快两倍多),并且使用了read_tsv的大约四分之一的内存分配,尽管它涉及一些复制并且可能可以优化。

我也包含了一个使用sub的版本,但这比read_tsv慢,尽管它不需要太多内存。

Rcpp::cppFunction("
std::vector<std::string> fun_rcpp(CharacterVector a, int col) {
if(col < 1) Rcpp::stop("col must be a positive integer");
std::vector<std::string> b = Rcpp::as<std::vector<std::string>>(a);
std::vector<std::string> result(a.size());
for(uint32_t i = 0; i < a.size() ; i++)
{
int n_tabs = 0;
std::string entry = "";
for(uint16_t j = 0; j < b[i].size(); j++)              
{
if(n_tabs == (col - 1) & b[i][j] != '\t') entry.push_back(b[i][j]);
if((b[i][j]) == '\t') n_tabs++;
if(n_tabs == col) break;
}
result[i] = entry;
}
return result;
}
")
fun_sub <- function(x, i)
{
s <- paste0("^", paste0(rep(".*?t", i - 1), collapse = ""), "(.*?)t.*$")
sub(s, "\1", x)
}

这两个函数都给出了预期的输出:

identical(fun_read_tsv(text, 3), fun_rcpp(text, 3))
#> [1] TRUE
identical(fun_read_tsv(text, 3), fun_sub(text, 3))
#> [1] TRUE

和基准测试显示在这里进行比较:

bench::mark(
fun_read.table(text, 3),
fun_read_tsv(text, 3),
fun_sub(text, 3),
fun_rcpp(text, 3),
min_iterations = 5
)
#> # A tibble: 4 x 13
#>   expression                   min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc
#>   <bch:expr>              <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>
#> 1 fun_read.table(text, 3)    1.35s    1.35s     0.738   93.23MB    5.17      1     7
#> 2 fun_read_tsv(text, 3)   788.86ms 792.35ms     1.26    36.04MB    0.314     4     1
#> 3 fun_sub(text, 3)           1.27s    1.29s     0.777    7.63MB    0.194     4     1
#> 4 fun_rcpp(text, 3)       379.02ms 381.17ms     2.62     7.63MB    0.655     4     1
#> # ... with 5 more variables: total_time <bch:tm>, result <list>, memory <list>,
#   time <list>, gc <list>

请注意,Rcpp函数的行为与预期基本一致,如果指定的列数小于1或使用错误的变量类型来选择列,则会发出相应的错误。但是,如果选择的列数大于当前的列数,它将返回一个空字符串向量,而不是抛出错误。如果您想要不同的行为,例如错误或NA

的向量,您可以轻松地为c++函数编写R包装器。

最新更新