提高数据框架模拟的性能

时间:2017-09-29 14:37:51

标签: r performance simulation resampling

在编码时,我经常只是编码它在我脑海中的表现。虽然我认为我从一开始就学习了有效的R编码(例如,避免for ... if循环),但我的解决方案并不总是真正受性能驱动。不幸的是,有时知道什么是最有效的代码至关重要 - 我想学习它!

目前我正在模拟组合成列表的多个数据帧。在模拟之后,我需要第二个数据帧,其中包含整个列表中所有列的均值和SD。 ('Simulation'在这里意味着某些变量正在从其他数据帧进行模拟/重采样,其他变量只是随机正常或具有特定b_0的二项分布值。为了简洁起见,我省去了第一部分重新采样这里。)

我的代码(参见下面的例子)完美地产生了预期的结果,但它似乎是第一个,有点慢(我说的是真实的几个小时),第二个,高度RAM消耗(为此我临时减少了列表中模拟dfs的数量。)

对于模拟我知道在函数中定义data.frame可能是一个问题,但我不知道如何更好地做到这一点。对于均值/ SD数据帧,我只能说它甚至更慢。

如何提高代码的性能?任何人都可能另外提供一些关于这种性能提升的基本规则(或相关信息来源)吗?

(我正在使用R 3.x / 64和Win 7/64 AMD FX(tm)-8350八核处理器,4 GHz,16 GB机器。运行时CPU保持相当酷,RAM呻吟声极限。)

这里我给出一个示例代码,其中包含评论中的测量系统时间:

# definitions
r <- 1e5 # number of rows
n <- 1e3 # number of dfs

# simulation of the list  
library(dplyr)
system.time(list <- lapply(1:n, function(i){       # 59.05 sec
  data.frame(a = rbinom(r, 1, .375)) %>%
    mutate(
      b = rnorm(r, 0, 2),
      c = .42 * rnorm(r, 0, 6),
      d = rbinom(r, 11, c(1:11)/11),
      e = rbinom(r, 1, .1),
      f = .02 * rnorm(r, 0, 5))
}))

# df w/ means & sds
system.time(list.s <- data.frame(                  # 73.20 sec
  list.mean = round(rowMeans(sapply(list, colMeans)), 2),
  list.sd = round(sapply(do.call(rbind, list), sd), 2)))

1 个答案:

答案 0 :(得分:1)

扩展Rolands评论,您可以预先创建大量人口数据,然后为每个“样本”/迭代简单地对其进行子集化。例如:

## create large population data:

s <- 1e6 # probably big enough for this problem
set.seed(12)
d <- matrix(NA, nrow = s, ncol = 6) #..
# using matrix is more efficient than data.frame
d[,1] <- rbinom(s, 1, .375)
d[,2] <- rnorm(s, 0, 2)
d[,3] <- .42 * rnorm(s, 0, 6)
d[,4] <- rbinom(s, 11, c(1:11)/11)
d[,5] <- rbinom(s, 1, .1)
d[,6] <- .02 * rnorm(s, 0, 5)
head(d)
#      [,1]        [,2]      [,3] [,4] [,5]        [,6]
# [1,]    0  0.73853351  1.097805    1    0 -0.06233008
# [2,]    1 -0.05311206  4.447807    2    0 -0.01117972
# [3,]    1  1.71576276 -3.619708    6    0  0.02962562
# [4,]    0  1.92188205 -1.062585    2    0  0.03195146
# [5,]    0 -1.41097404  1.706067    2    0 -0.07751285
# [6,]    0  4.19130890  2.663374    8    0 -0.02316172


r <- 1e4 # number of rows
n <- 1e2 # number of dfs

si <- replicate(n, sample.int(s, r)) # get indexes for each sample 

# loop trougth samples and subset data:
nSamples <- lapply(1:n, function(x) {
  d[si[, x],]
  })

# and calculate colMeans:
list.mean2 = round(rowMeans(sapply(nSamples, colMeans)), 3)
list.mean2
# [1]  0.376  0.000 -0.003  5.999  0.100  0.000

与您的结果进行比较:

require(dplyr)
list1 <- lapply(1:n, function(i){
  data.frame(a = rbinom(r, 1, .375)) %>%
    mutate(
      b = rnorm(r, 0, 2),
      c = .42 * rnorm(r, 0, 6),
      d = rbinom(r, 11, c(1:11)/11),
      e = rbinom(r, 1, .1),
      f = .02 * rnorm(r, 0, 5))
})

list.mean1 = round(rowMeans(sapply(list1, colMeans)), 3)
list.mean1
# a      b      c      d      e      f 
# 0.375 -0.002  0.004  6.001  0.100  0.000 

我们可以看到平均值的估计与这个小的n值非常相似。

P.S。因为'list'是基本R函数,所以不应该用该名称命名变量!

让我们将两种方法都包含在测试时间的函数中:

mySim <- function(s, r, n) {
  d <- matrix(NA, nrow = s, ncol = 6)
  d[,1] <- rbinom(s, 1, .375)
  d[,2] <- rnorm(s, 0, 2)
  d[,3] <- .42 * rnorm(s, 0, 6)
  d[,4] <- rbinom(s, 11, c(1:11)/11)
  d[,5] <- rbinom(s, 1, .1)
  d[,6] <- .02 * rnorm(s, 0, 5)
  si <- lapply(1:n, function(x) sample.int(s, r))
  nSamples <- lapply(si, function(x) {
    d[x,]
  })
  list.mean2 = rowMeans(sapply(nSamples, colMeans))
  list.mean2
}

yourSim <- function(r, n) {
  require(dplyr)
  list1 <- lapply(1:n, function(i){
    data.frame(a = rbinom(r, 1, .375)) %>%
      mutate(
        b = rnorm(r, 0, 2),
        c = .42 * rnorm(r, 0, 6),
        d = rbinom(r, 11, c(1:11)/11),
        e = rbinom(r, 1, .1),
        f = .02 * rnorm(r, 0, 5))
  })
  list.mean1 = rowMeans(sapply(list1, colMeans))
  list.mean1
}

system.time(mySim(1e6, 1e4, 1e2)) # ~ 0.6 sek
system.time(yourSim(1e4, 1e2)) # ~ 1.5 sek

# if s = 1e7 :
system.time(mySim(1e7, 1e4, 1e2)) # ~ 4.53 sek

我们可以看到,为小n和r值创建大量人口数据并不会提高速度。

让我们将s作为1e6(100万),但你应该自己调查一下 已经足够了。

如果我们为更大的'r'和'n'值主持时间安排:

system.time(r1 <- mySim(1e6, 1e5, 1e3)) # ~ 20 sek
system.time(r2 <- yourSim(1e5, 1e3)) # ~ 60 sek

round(r1, 3)
# [1]  0.376 -0.003 -0.002  6.001  0.100  0.00
round(r2, 3)
# a     b     c     d     e     f 
# 0.375 0.000 0.000 6.000 0.100 0.000 

关于计算SD: 也许你想在'matrixStats'包中使用'rowSds()'或'colSds()'?

另外我建议您调查Rcpp包,这对于加速代码更有用。