簡體   English   中英

有效轉換為R中的向量

[英]Efficient conversion to vectors in R

任何人都可以幫助我提高這個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) } } }) 

例如,

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.

上面的代碼工作正常,但它很慢,我看不到任何方法來優化它而不改變函數的行為。 重要的是'as_atomic'函數的行為與它一樣重要; 我無法切換到我熟悉的基本功能(例如,取消列表),因為我需要為壞列表拋出錯誤。

require(microbenchmark)

microbenchmark(
    as_atomic( as.list(1:1000), 'numeric'),
    vapply(1:1000, identity, integer(1)),
    unit = 'ns'
)

在我(相當快)的機器上,基准測試的頻率約為40Hz,因此在我的代碼中,這個功能幾乎總是速率限制。 vapply控制基准測試的頻率約為1650Hz,但仍然很慢。

有沒有辦法大幅提高這項操作的效率? 任何建議表示贊賞。

如果需要任何澄清或編輯,請在下面留言。

編輯:

大家好,

很抱歉這個遲來的回復; 在我嘗試重新實現之前,我需要參加考試。

謝謝大家的性能提示。 我使用簡單的R代碼將性能從可怕的40hz提升到更可接受的600hz。

最大的加速來自使用typeof或mode而不是; 這真的加快了緊密的內部檢查循環。

我可能不得不咬緊牙關並在rcpp中重寫它以獲得真正高效的功能。

這個問題有兩個部分:

  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
}

現在我們需要弄清楚循環或應用變量是否更快。 我們將以所有輸入有效的最壞情況為基准。

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

這三種方法基本相關。 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

所以看起來你已經在使用最快的方法了,總費用由支票支配。

替代方法

另一個想法是,我們可以通過循環向量一次來獲得更快一點,而不是一次檢查和一次強制:

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

那肯定更糟。

總而言之,我認為這表明如果你想讓這個功能更快,你應該嘗試在Rcpp中對檢查函數進行矢量化。

嘗試:

as_atomic_2 <- function(x, mode) {
  if(!length(unique(vapply(x, typeof, ""))) == 1L) stop("mixed types")
  as.vector(x, mode)
}
as_atomic_2(list(1, 2, 3), 'numeric')
# [1] 1 2 3
as_atomic_2(list(1, 'a', 2), 'character')
# Error in as_atomic_2(list(1, "a", 2), "character") : mixed types
as_atomic_2(list(1, c(2,3,4), 5), 'numeric' )
# Error in as.vector(x, mode) : 
#   (list) object cannot be coerced to type 'double'

microbenchmark(
  as_atomic( as.list(1:1000), 'numeric'),
  as_atomic_2(as.list(1:1000), 'numeric'),
  vapply(1:1000, identity, integer(1)),
  unit = 'ns'
)    
# Unit: nanoseconds
#                                     expr      min       lq     median 
#    as_atomic(as.list(1:1000), "numeric") 23571781 24059432 24747115.5 
#  as_atomic_2(as.list(1:1000), "numeric")  1008945  1038749  1062153.5 
#     vapply(1:1000, identity, integer(1))   719317   762286   778376.5 

定義自己的函數來進行類型檢查似乎是瓶頸。 使用其中一個內置函數可以提高速度。 但是,調用有所改變(盡管可能會改變它)。 以下示例是我能提出的最快版本:

正如使用is.numeric提到的is.numericis.character提供了最大的加速:

as_atomic2 <- function(l, check_type) {
  if (!all(vapply(l, check_type, logical(1)))) stop("")
  r <- unlist(l)
  if (length(r) != length(l)) stop("")
  r
} 

以下是我使用原始界面提出的最快速度:

as_atomic3 <- function(l, type) {
  if (!all(vapply(l, mode, character(length(type))) == type)) stop("")
  r <- unlist(l)
  if (length(r) != length(l)) stop("")
  r
}

針對原始基准:

res <- microbenchmark(
    as_atomic( as.list(1:1000), 'numeric'),
    as_atomic2( as.list(1:1000), is.numeric),
    as_atomic3( as.list(1:1000), 'numeric'),
    unit = 'ns'
)
#                                    expr      min         lq     median         uq      max neval
#   as_atomic(as.list(1:1000), "numeric") 13566275 14399729.0 14793812.0 15093380.5 34037349   100
# as_atomic2(as.list(1:1000), is.numeric)   314328   325977.0   346353.5   369852.5   896991   100
#  as_atomic3(as.list(1:1000), "numeric")   856423   899942.5   967705.5  1023238.0  1598593   100

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM