将字符向量列表转换为数字向量列表的快速方法

时间:2018-04-04 14:40:51

标签: r rcpp stringr

我想快速将字符向量列表转换为数字向量列表。我试图避免purrr::map()lapply()等。我最终得到了stringr操作输出中的字符向量列表。我愿意使用Rcpp或R&C的内部C语言。这是filesstrings包的内容。 C ++标准库提供了stod()中定义的<string>,但它的行为不像as.numeric(),例如它转换了&#34; 12a&#34;数字12,但我喜欢as.numeric()为此返回NA的方式。以下是我现在的做法。

nums_as_chars <- stringr::str_extract_all(c("a1b2", "c3d4e5", "xyz"), "\\d")
nums_as_chars
#> [[1]]
#> [1] "1" "2"
#> 
#> [[2]]
#> [1] "3" "4" "5"
#> 
#> [[3]]
#> character(0)
nums <- purrr::map(nums_as_chars, as.numeric)
nums
#> [[1]]
#> [1] 1 2
#> 
#> [[2]]
#> [1] 3 4 5
#> 
#> [[3]]
#> numeric(0)

reprex package(v0.2.0)创建于2018-04-04。

5 个答案:

答案 0 :(得分:4)

好的,所以我创建了一个更真实的示例,其中包含3000个字符串来进行一些分析,并尝试了我能想到的各种方法。

character_vector <- rep(c("a1b2", "c3d4e5", "xyz"), 1000)


try_1 <- function(chars){
  extracted_numbers <- stringr::str_extract_all(chars, "\\d")
  lapply(extracted_numbers, as.numeric)
}

try_2 <- function(chars){
  extracted_numbers <- stringr::str_extract_all(chars, "\\d")
  purrr::map(extracted_numbers, as.numeric)
}

try_3 <- function(chars){
  extracted_numbers <- stringr::str_extract_all(chars, "\\d")
  relist(as.numeric(unlist(extracted_numbers)), extracted_numbers)
}

try_4 <- function(chars){
  convert_fun <- function(x){as.numeric(stringr::str_extract_all(x, "\\d")[[1]])}
  lapply(chars, convert_fun)
}

# if you don't need to keep the list ...
try_5 <- function(chars){
  extracted_numbers <- stringr::str_extract_all(chars, "\\d")
  suppressWarnings(as.numeric(unlist(extracted_numbers)))
}


microbenchmark::microbenchmark(try_1(character_vector),
                               try_2(character_vector),
                               try_3(character_vector),
                               try_4(character_vector),
                               try_5(character_vector))
#> Unit: milliseconds
#>                     expr        min         lq       mean     median
#>  try_1(character_vector)   2.701769   2.866486   3.304917   3.005177
#>  try_2(character_vector)   3.936557   4.295735   4.872892   4.391737
#>  try_3(character_vector)  12.441844  13.317455  15.759840  14.250013
#>  try_4(character_vector) 183.180143 187.789907 191.298661 190.073565
#>  try_5(character_vector)   1.846848   1.964761   2.090801   2.026860
#>          uq        max neval
#>    3.275250  10.569255   100
#>    4.726425  17.007687   100
#>   16.995679  49.983457   100
#>  193.012754 215.532544   100
#>    2.105214   4.396379   100

请注意,单位是毫秒,对于3000个条目,执行3000列表需要lapply 3毫秒。这对我来说似乎不合理。

purrr::map解决方案非常接近lapply,然后@roland解决方案更长,然后我的第一个想法是很多更糟糕。如果你不关心 list 结构(我想你会这么做),那么你可以减少到2毫秒。

答案 1 :(得分:2)

您没有提供任何适合合理基准的内容。所以,自己测试一下:

relist(as.numeric(unlist(nums_as_chars)),
       nums_as_chars)
#[[1]]
#[1] 1 2
#
#[[2]]
#[1] 3 4 5
#
#[[3]]
#numeric(0)

答案 2 :(得分:2)

  

C ++标准库提供了stod()中定义的<string>,但它的行为与as.numeric()不同,例如它将"12a"转换为数字12 ,但我喜欢as.numeric()为此返回NA的方式。

这部分很容易解决:只需use the second parameter of the function验证输入已被消耗:

Rcpp::NumericVector as_numeric(std::string const& str) {
    std::size_t pos;
    double value = std::stod(&str[0], &pos);
    return NumericVector::create(pos == str.size() ? value : NA_REAL);
}
〉 as_numeric('12')
[1] 12

〉 as_numeric('12a')
[1] NA

......显然,这应该是为了表现而进行矢量化。

答案 3 :(得分:1)

受@ Konrad的回答启发,我使用Rcpp编码了以下内容。

NumericVector char_to_num(CharacterVector x) {
  std::size_t n = x.size();
  if (n == 0) return NumericVector(0);
  NumericVector out(n);
  for (std::size_t i = 0; i != n; ++i) {
    std::string x_i(x[i]);
    double number = NA_REAL;
    try {
      std::size_t pos;
      number = std::stod(x_i, &pos);
      number = ((pos == x_i.size()) ? number : NA_REAL);
    } catch (const std::invalid_argument& e) {
      ;  // do nothing
    }
    out[i] = number;
  }
  return out;
}

// [[Rcpp::export]]
List lst_char_to_num(List x) {
  std::size_t n = x.size();
  List out(n);
  for (std::size_t i = 0; i != n; ++i)
    out[i] = char_to_num(x[i]);
  return out;
}

lst_char_to_num()原来是最好的答案。我将它与目前为止我最喜欢的答案进行比较,这些答案来自@rmflight try1try2try3。到目前为止try1是最快的(在一个大数据集上,这是我担心的)。我已经完成了stringr操作,因为我想纯粹评估列表转换的速度。

character_vector <- rep(c("a1b2", "c3d4e5", "xyz"), 1000)
extracted_numbers <- stringr::str_extract_all(character_vector, "\\d")

try_1 <- function(char_list) {
  lapply(char_list, as.numeric)
}

try_2 <- function(char_list) {
  purrr::map(char_list, as.numeric)
}

try_3 <- function(char_list) {
  relist(as.numeric(unlist(char_list)), char_list)
}

microbenchmark::microbenchmark(try_1(extracted_numbers),
                               try_2(extracted_numbers),
                               try_3(extracted_numbers),
                               lst_char_to_num(extracted_numbers),
                               times = 1000)

Unit: microseconds
                              expr       min         lq       mean     median        uq        max neval  cld
          try_1(extracted_numbers)  1068.823  1334.9060  1518.7589  1477.7825  1559.791   5318.318  1000  b  
          try_2(extracted_numbers)  2029.832  2581.6655  2974.4126  2856.8560  3057.930   9846.862  1000   c 
          try_3(extracted_numbers) 10015.929 12261.6405 14043.5922 13188.8465 14802.795 165217.152  1000    d
lst_char_to_num(extracted_numbers)   500.858   681.5895   827.5021   765.9505   830.311   6744.985  1000 a   

答案 4 :(得分:-1)

这是一个基本解决方案,对于小例子来说更快,但对于@ rmflight扩展基准测试来说速度较慢:

this.takePicture.bind(this);

仅供参考,另一种解决方案,但速度较慢:

this.takePicture