有效转换为R中的向量

Rya*_*ell 5 performance benchmarking r microbenchmark

任何人都可以帮助我提高这个R代码的效率吗?

我正在尝试编写一个函数,将字符串列表更改为字符串向量,或将数字列表更改为数字向量,将类型元素列表更改为某种类型的向量.

我希望能够将列表更改为特定类型的向量,如果它们具有以下属性:

  1. 它们是均匀打字的.列表的每个元素都是"字符"类型,或"复杂"等.

  2. 列表的每个元素都是长度为1.

    as_atomic <- local({
    
        assert_is_valid_elem <- function (elem, mode) {
    
            if (length(elem) != 1 || !is(elem, mode)) {
                stop("")
            }
            TRUE
        }
    
        function (coll, mode) {
    
            if (length(coll) == 0) {
                vector(mode)
            } else {
                # check that the generic vector is composed only
                # of length-one values, and each value has the correct type.
    
                # uses more memory that 'for', but is presumably faster.
                vapply(coll, assert_is_valid_elem, logical(1), mode = mode)
    
                as.vector(coll, mode = mode)
            }
        }
    })
    
    Run Code Online (Sandbox Code Playgroud)

例如,

as_atomic(list(1, 2, 3), 'numeric')
as.numeric(c(1,2,3))

# this fails (mixed types)
as_atomic( list(1, 'a', 2), 'character' )
# ERROR.

# this fails (non-length one element)
as_atomic( list(1, c(2,3,4), 5), 'numeric' )
# ERROR.

# this fails (cannot convert numbers to strings)
as_atomic( list(1, 2, 3), 'character' )
# ERROR.
Run Code Online (Sandbox Code Playgroud)

上面的代码工作正常,但它很慢,我看不到任何方法来优化它而不改变函数的行为.重要的是'as_atomic'函数的行为与它一样重要; 我无法切换到我熟悉的基本功能(例如,unlist),因为我需要为坏列表抛出错误.

require(microbenchmark)

microbenchmark(
    as_atomic( as.list(1:1000), 'numeric'),
    vapply(1:1000, identity, integer(1)),
    unit = 'ns'
)
Run Code Online (Sandbox Code Playgroud)

在我(相当快)的机器上,基准测试的频率约为40Hz,因此在我的代码中,这个功能几乎总是速率限制.vapply控制基准测试的频率约为1650Hz,但仍然很慢.

有没有办法大幅提高这项操作的效率?任何建议表示赞赏.

如果需要任何澄清或编辑,请在下面留言.

编辑:

大家好,

很抱歉这个迟来的回复; 在我尝试重新实现之前,我需要参加考试.

谢谢大家的性能提示.我使用普通的R代码将性能从可怕的40hz提高到更可接受的600hz.

最大的加速来自使用typeof或mode而不是; 这真的加快了紧密的内部检查循环.

我可能不得不咬紧牙关并在rcpp中重写它以获得真正高效的功能.

had*_*ley 7

这个问题有两个部分:

  1. 检查输入是否有效
  2. 将列表强制转换为向量

检查有效输入

首先,我会避免,is()因为它已知很慢.这给了:

check_valid <- function (elem, mode) {
  if (length(elem) != 1) stop("Must be length 1")
  if (mode(elem) != mode) stop("Not desired type")

  TRUE
}
Run Code Online (Sandbox Code Playgroud)

现在我们需要弄清楚循环或应用变量是否更快.我们将以所有输入有效的最坏情况为基准.

worst <- as.list(0:101)

library(microbenchmark)
options(digits = 3)
microbenchmark(
  `for` = for(i in seq_along(worst)) check_valid(worst[[i]], "numeric"),
  lapply = lapply(worst, check_valid, "numeric"),
  vapply = vapply(worst, check_valid, "numeric", FUN.VALUE = logical(1))
)

## Unit: microseconds
##    expr min  lq median  uq  max neval
##     for 278 293    301 318 1184   100
##  lapply 274 282    291 310 1041   100
##  vapply 273 284    288 298 1062   100
Run Code Online (Sandbox Code Playgroud)

这三种方法基本相关.lapply()是非常快,可能是因为它使用了特殊的C技巧

强制列表向量

现在让我们看一下将列表强制转换为向量的几种方法:

change_mode <- function(x, mode) {
  mode(x) <- mode
  x
}

microbenchmark(
  change_mode = change_mode(worst, "numeric"),
  unlist = unlist(worst),
  as.vector = as.vector(worst, "numeric")
)

## Unit: microseconds
##         expr   min    lq median   uq    max neval
##  change_mode 19.13 20.83  22.36 23.9 167.51   100
##       unlist  2.42  2.75   3.11  3.3  22.58   100
##    as.vector  1.79  2.13   2.37  2.6   8.05   100
Run Code Online (Sandbox Code Playgroud)

所以看起来你已经在使用最快的方法了,总费用由支票支配.

替代方法

另一个想法是,我们可以通过循环向量一次来获得更快一点,而不是一次检查和一次强制:

as_atomic_for <- function (x, mode) {
  out <- vector(mode, length(x))

  for (i in seq_along(x)) {
    check_valid(x[[i]], mode)
    out[i] <- x[[i]]
  }

  out
}
microbenchmark(
  as_atomic_for(worst, "numeric")
)

## Unit: microseconds
##                             expr min  lq median  uq  max neval
##  as_atomic_for(worst, "numeric") 497 524    557 685 1279   100
Run Code Online (Sandbox Code Playgroud)

那肯定更糟.

总而言之,我认为这表明如果你想让这个功能更快,你应该尝试在Rcpp中对检查函数进行矢量化.