【问题标题】:Fastest way to map multiple character columns to numerical values将多个字符列映射到数值的最快方法
【发布时间】:2019-10-30 17:34:08
【问题描述】:

我有一个算法,可以在每次迭代时计算某些组的平均值(这些组不会只改变它们的值)。

数值表 -

d1 <- data.frame(x = sample(LETTERS, N, replace = TRUE), 
                  y1=rnorm(N))
head(d1)
#   x         y1
# 1 H -0.7852538
# 2 G -0.6739159
# 3 V -1.7783771
# 4 L -0.2849846
# 5 I -0.1760284
# 6 V -0.2785826

我可以计算平均值(通过多种方式:dplyr、data.table 和 tapply)。我有另一个 data.frame 由两列和组名组成。

d2 <- data.frame('group.high' = sample(LETTERS, N * 2, replace = TRUE), 
                 'group.low' = sample(LETTERS, N * 2, replace = TRUE))
head(d2)
#   group.high group.low
# 1          U         L
# 2          K         J
# 3          C         Q
# 4          Q         A
# 5          Q         U
# 6          K         W

我想将基于d1 的每个组的平均值添加到列mean.highmean.better

到目前为止,我已经尝试了dplyrdata.table 中的两个选项。我不得不在其中任何一个中使用 left_join 两次。它们的速度相似。

microbenchmark(
  dplyr = {
  means <- tapply(d1$y1, INDEX = d1$x, FUN = mean)
  ### Solution 1 
  dplyr.d2 <- left_join(d2,data.frame('group.high' = names(means), 
                                      'mean.high' = means, stringsAsFactors = FALSE) ) %>%  
    left_join(., data.frame('group.low' = names(means), 
                            'mean.low' = means, stringsAsFactors = FALSE))},
  data.table = {
  ### Solution 2 
  d1    <- as.data.table(d1)
  d2    <- as.data.table(d2)
  means <- d1[ ,.(means = mean(y1)), by = x]
  new.d2 <- data.table::merge.data.table(x = d2, y = means, by.x = 'group.high', by.y = 'x')
  data.table.d2 <- data.table::merge.data.table(x = new.d2, y = means, by.x = 'group.low', by.y = 'x')
  }
)

Unit: milliseconds
       expr     min       lq     mean  median       uq      max neval cld
      dplyr 34.0837 36.88650 53.22239 42.9227 47.50660 231.5066   100  a 
 data.table 40.2071 47.70735 87.46804 51.2517 59.05385 258.4999   100   b

有没有更好的方法?如何加快计算速度?

如 cmets 中所述,更新值有一个迭代过程。这是一个例子。

N <- 10000

iterFuncDplyr <- function(d1, d2) { 
  dplyr.d2 <- left_join(d2,data.frame('group.high' = names(means), 
                                      'mean.high' = means, stringsAsFactors = FALSE) ) %>%  
    left_join(., data.frame('group.low' = names(means), 
                            'mean.low' = means, stringsAsFactors = FALSE))
  return(var(d1$y1))
}

iterFuncData <- function(d1, d2) { 
  means <- d1[ ,.(means = mean(y1)), by = x]
  new.d2 <- data.table:::merge.data.table(x = d2, y = means, by.x = 'group.high', by.y = 'x')
  data.table.d2 <- data.table:::merge.data.table(x = new.d2, y = means, by.x = 'group.low', by.y = 'x')
  return(var(d1$y1))
}


d1 <- data.frame(x = sample(LETTERS, N, replace = TRUE), 
                 y1=rnorm(N))

d2 <- data.frame('group.high' = sample(LETTERS, N * 2, replace = TRUE), 
                 'group.low' = sample(LETTERS, N * 2, replace = TRUE))

library(data.table)
library(dplyr)

microbenchmark::microbenchmark(dplyr = {
temp.val <- 0 

for (i in 1:10) {
  d1$y1 <- temp.val + rnorm(N)
  temp.val <- iterFuncDplyr(d1, d2)
}},
data.table = {
d1    <- as.data.table(d1)
d2    <- as.data.table(d2)

temp.val <- 0 

for (i in 1:10) {
  d1$y1 <- temp.val + rnorm(N)
  temp.val <- iterFuncData(d1, d2)
}
}
)

Unit: milliseconds
       expr      min       lq     mean   median       uq      max neval
      dplyr 46.22904 50.67959 52.78275 51.96358 53.34825 108.2874   100
 data.table 63.81111 67.13257 70.85537 69.85712 72.72446 127.4228   100

【问题讨论】:

  • N = 10,000 似乎是合理的。
  • 另外,我不知道每次迭代的值是如何变化的,但data.table 会更快......
  • 您能澄清一下每次迭代有哪些变化吗?我的印象是d1y1 列发生了变化,但d1$x 保持不变。 d2 每次迭代都会改变吗?您是每次迭代都向d2 添加新列,还是每次只更新mean.highmean.better
  • 真的,如果您将示例更新为执行 5 次迭代会有所帮助。然后我们可以对该过程进行基准测试。或许可以用一个 N = 10 的小例子来说明,但是当我们进入基准测试时,我们可以达到 N = 10000。
  • 一般来说,dx &lt;- as.data.table(dx) 调用效率低/不推荐。就做setDT(dx)

标签: r join dplyr data.table


【解决方案1】:

您可以对命名向量 means 进行子集化以创建新列并匹配您的输出:

means <- tapply(d1$y1, INDEX = d1$x, FUN = mean)
d2$mean.high <- means[d2$group.high]
d2$mean.low <- means[d2$group.low]

identical(as.matrix(d2), as.matrix(d3)) #factor vs character, used d3 w/ benchmark
[1] TRUE

Unit: microseconds
       expr    min      lq      mean   median       uq     max neval
      dplyr 4868.2 5316.25  5787.123  5524.15  5892.70 12187.3   100
 data.table 8254.4 9606.60 10438.424 10118.35 10771.75 20966.5   100
     subset  481.2  529.40   651.194   550.35   582.55  7849.9   100

基准代码

d3 <- d2

microbenchmark::microbenchmark( # N = 10000
  dplyr = {
    means <- tapply(d1$y1, INDEX = d1$x, FUN = mean)
    ### Solution 1 
    dplyr.d2 <- left_join(d2,data.frame('group.high' = names(means), 
                                        'mean.high' = means, stringsAsFactors = FALSE) ) %>%  
      left_join(., data.frame('group.low' = names(means), 
                              'mean.low' = means, stringsAsFactors = FALSE))},
  data.table = {
    ### Solution 2 
    d1    <- as.data.table(d1)
    d2    <- as.data.table(d2)
    means <- d1[ ,.(means = mean(y1)), by = x]
    new.d2 <- data.table::merge.data.table(x = d2, y = means, by.x = 'group.high', by.y = 'x')
    data.table.d2 <- data.table::merge.data.table(x = new.d2, y = means, by.x = 'group.low', by.y = 'x')
  },
  subset = {
    means <- tapply(d1$y1, INDEX = d1$x, FUN = mean)
    d3$mean.high <- means[d2$group.high]
    d3$mean.low <- means[d2$group.low]

  }
)

【讨论】:

  • 我想出了一个非常相似的方法,但使用了data.table,它似乎需要 N 比 10,000 大很多才能变得更快。
  • @sindri_baldur,就个人而言,如果您有时间,我很乐意将其视为答案。我想这对于将来遇到这个问题并且 N > 10,000 的人可能会有用。我将在今天晚些时候更新我的基准测试,以包含更多使用 replicate 的迭代。
【解决方案2】:

这是一个与 Andrew 非常相似的答案,但依赖于 data.table 而不是 tapply()(对于非常大的 N,这似乎更快)。

library(data.table)
# Create a named vector "means"
means <- setDT(d1)[, mean(y1), by = x][, setNames(V1, x)]
setDT(d2)[, c("mean.high.means", "mean.low.means") := 
              .(means[as.character(group.high)], means[as.character(group.low)])]

输出:

        group.high group.low mean.high.means mean.low.means
     1:          Z         W     0.017032792   0.0091625547
     2:          A         A     0.013796137   0.0137961371
     3:          V         S    -0.011570159   0.0004560325
     4:          D         X     0.005475629   0.0200984250
     5:          U         H    -0.008249901   0.0054537833
    ---                                                    
199996:          H         K     0.005453783   0.0079905631
199997:          A         T     0.013796137  -0.0068537963
199998:          W         U     0.009162555  -0.0082499015
199999:          T         V    -0.006853796  -0.0115701585
200000:          G         J     0.014829259   0.0206598470

可重现的数据:

N = 1e5
set.seed(1) 
d1 <- data.frame(
  x  = sample(LETTERS, N, replace = TRUE), 
  y1 = rnorm(N)
) 
d2 <- data.frame(
  group.high = sample(LETTERS, N * 2, replace = TRUE), 
  group.low  = sample(LETTERS, N * 2, replace = TRUE)
)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-08
    • 2019-12-13
    • 1970-01-01
    • 1970-01-01
    • 2021-11-25
    • 2018-07-09
    • 2017-04-23
    相关资源
    最近更新 更多