【问题标题】:for loop speeding in RR中的循环加速
【发布时间】:2017-08-10 13:05:04
【问题描述】:

我正在尝试在 R 中为 nrow=300,000 次模拟(在 ncol=30 个变量上)执行以下操作:

投反对票

接受

这是我的代码:

FS_DF <- read.csv("fs.csv", sep = ",")
Y_DF <- read.csv("Y.csv", sep = ",")
CALIBSCENS_DF <- read.csv("calib_scens.csv", sep = ",")
Y_DF$X <- NULL

X_mat <- matrix(1:1, nrow(CALIBSCENS_DF), nrow(FS_DF))

for (irow in 1:nrow(CALIBSCENS_DF)) { 
for (jrow in 1:nrow(FS_DF)) { 
for (krow in 1:ncol(FS_DF)) { 
    X_mat [irow, jrow] <- X_mat[irow, jrow] * (CALIBSCENS_DF[irow, krow] ^ FS_DF[jrow, krow])

}}}

fit <- .lm.fit(X_mat, as.matrix(sapply(Y_DF, as.numeric)))

填充我的 X 矩阵需要很长时间。有人可以建议一种更快的方法来执行此操作。 SCENS_DF、FS_DF 是数据帧。 X_mat 是一个矩阵。

【问题讨论】:

  • 看起来X_mat * (CALIBSCENS_DF ^ FS_DF) 应该可以工作,因为这些是元素操作。
  • 循环在 R 中非常慢。最好使用其中一个应用函数(它在内部也使用循环,但在 C 中实现)。这些函数的一个很好的介绍可以在这个答案中找到:stackoverflow.com/questions/4162363/… 此外,您的代码很慢,因为矩阵的大小没有预定义,这在 R 中也很慢。创建一个预期尺寸的空矩阵,稍后填充它们。
  • 感谢 Imo 和 JereB。我确实将 X_mat 预定义为 X_mat
  • @JereB 不完全正确。见The R Inferno
  • @Rishi,不,我认为你的代码可以写得更好。我试图弄清楚你的代码在做什么。一个可重现的例子真的很有帮助。 CALIBCENS_DFX_matFS_DF 都具有相同的尺寸吗?

标签: r performance loops apply


【解决方案1】:

如果此代码是您的瓶颈并且您使用循环,这始终是cpp 可能产生良好结果的好兆头。我们可以使用Rcpp 使其更容易,并在我们的代码中添加 cpp 函数。

您可以在下面找到我使用 Rcpp 的方法以及针对 minem 方法的一些基准测试,减少了大约 20% 的运行时间(很大程度上取决于矩阵的大小)。

library(Rcpp) # load the Rcpp library

# create some data...
CALIBSCENS_DF <- matrix(2:5, nrow = 2)
FS_DF <- matrix(2:5, nrow = 2)

# create the cpp-function, basically the same as yours, just adapted to cpp
cppFunction("
NumericMatrix cpp_fun(NumericMatrix A, NumericMatrix B) {
    NumericMatrix retMax(A.nrow(), B.nrow());

    long double mult;
    for (int irow = 0; irow < A.nrow(); irow++) {
        for (int jrow = 0; jrow < B.nrow(); jrow++) {
            mult = 1;
            for (int krow = 0; krow < B.ncol(); krow++) {
                mult *= pow(A(irow, krow), B(jrow, krow));
            }
            retMax(irow, jrow) = mult;
        }
    }
    return retMax;
}
")
# execute the function called 'cpp_fun' in R
cpp_mat <- cpp_fun(CALIBSCENS_DF, FS_DF)
cpp_mat
# [,1]  [,2]
# [1,] 1024  8192
# [2,] 5625 84375

将函数与 Minem 显示的结果进行比较

# for comparison, use Minems function
minem_fun <- function(A_mat, B_mat) {
  X <- matrix(1, ncol = nrow(B_mat), nrow = nrow(A_mat))
  for (irow in 1:nrow(A_mat)) {
    for (jrow in 1:nrow(B_mat)) {
      X [irow, jrow] <- prod(A_mat[irow, ] ^ B_mat[jrow, ])
    }
  }
  return(X)
}
minem_mat <- minem_fun(CALIBSCENS_DF, FS_DF)

identical(cpp_mat, minem_mat)
# [1] TRUE

速度基准

library(microbenchmark)
# small data
microbenchmark(
  minem = minem_fun(CALIBSCENS_DF, FS_DF),
  cpp = cpp_fun(CALIBSCENS_DF, FS_DF),
  times = 1000
)
# Unit: microseconds
# expr   min     lq      mean median     uq      max neval
# minem 9.386 10.239 11.198179  10.24 10.667   49.915  1000
# cpp 1.707  2.560  3.954538   2.56  2.987 1098.980  1000


# larger data
n <- 200
CALIB_large <- matrix(rnorm(n^2, mean = 100, sd = 10), nrow = n, ncol = n)
FS_large <- matrix(rnorm(n^2, mean = 2, sd = 0.5), nrow = n, ncol = n)

microbenchmark(
  minem = minem_fun(CALIB_large, FS_large),
  cpp = cpp_fun(CALIB_large, FS_large),
  times = 10
)
# Unit: seconds
# expr      min       lq     mean   median       uq      max neval
# minem 1.192011 1.197783 1.209692 1.201320 1.230812 1.238446    10
# cpp 1.009908 1.019727 1.023600 1.025791 1.028152 1.029427    10

这对你有帮助吗?

【讨论】:

  • Rcpp 看起来很棒。我会试试看。非常感谢大家。
  • 帮助! Get Error: Loading required package: Rcpp Error in cppFunction("\nNumericMatrix cpp_fun(NumericMatrix A, NumericMatrix B) \n{\n\tNumericMatrix retMax(A.nrow(), B.nrow());\n\n long double mult;\n \n\tfor (int irow = 0; irow
  • 您是否正确安装了Rcpp?即,Rcpp::evalCpp("2+2") 是否返回 4?
  • 正确发现。我不。 RStudio 尝试安装 Rtools 来构建它。但是尝试在c驱动器中执行它并失败。错误:安装程序无法创建目录“C:\RBuildTools”。错误 5:访问被拒绝。如何让它安装在我的自定义目录中?谢谢。
  • 您可以尝试按照link手动安装。
【解决方案2】:

看起来我们可以这样删除一个循环:

CALIBSCENS_DF <- matrix(2:5, nrow = 2)
FS_DF <- matrix(2:5, nrow = 2)
X <- matrix(1, ncol = nrow(FS_DF), nrow = nrow(CALIBSCENS_DF))
for (irow in 1:nrow(CALIBSCENS_DF)) { 
  for (jrow in 1:nrow(FS_DF)) { 
      X [irow, jrow] <-
        X[irow, jrow] * prod(CALIBSCENS_DF[irow, ] ^ FS_DF[jrow, ])
    }}
X
#      [,1]  [,2]
# [1,] 1024  8192
# [2,] 5625 84375

【讨论】:

  • 这真的很有帮助。对于 1k 模拟,运行时间从 3 分钟降至 1 分钟。非常感谢。
  • 鉴于X最初始终为1,您可以将代码缩短为X[irow, jrow] &lt;- prod(CALIB...)
【解决方案3】:

这还不是您问题的真正答案,但不适合发表评论。我认为我们需要仔细查看您正在尝试执行的操作,并确定 for 循环是否正在执行您认为的操作。

让我们稍微简化一下代码。让我们有矩阵XCF 并定义循环

for (i in 1:nrow(C)){
  for (j in 1:nrow(F)){
    for (k in 1:ncol(F)){
      X[i, j] <- X[i, j] * C[i, k] ^ F[j, k]
    }
  }
}

现在让我们逐步了解循环迭代时会发生什么

i = 1; j = 1; k = 1      X[1, 1] <- X[1, 1] * C[1, 1] ^ F[1, 1]
i = 1; j = 1; k = 2      X[1, 1] <- X[1, 1] * C[1, 2] ^ F[1, 2]
i = 1; j = 1; k = 3      X[1, 1] <- X[1, 1] * C[1, 3] ^ F[1, 3]
...
i = 1; j = 1; k = 30      X[1, 1] <- X[1, 1] * C[1, 30] ^ F[1, 30]

最终,X[1, 1] 依赖于 C[1, 30]F[1, 30]。您已经完成了 29 次被覆盖的迭代。此时,循环将递增j,您将得到

i = 1; j = 2; k = 1      X[1, 2] <- X[1, 2] * C[1, 1] ^ F[2, 1]
i = 1; j = 2; k = 2      X[1, 2] <- X[1, 2] * C[1, 2] ^ F[2, 2]
i = 1; j = 2; k = 3      X[1, 2] <- X[1, 2] * C[1, 3] ^ F[2, 3]
...
i = 1; j = 2; k = 30      X[1, 2] <- X[1, 2] * C[1, 30] ^ F[2, 30]

所以X[1, 2] 依赖于C[1, 30]F[2, 30]

这是您期望的行为吗?

【讨论】:

  • 是的,我需要用 CALIBSCENS[i,k] ^ FS_DF[j,k] 迭代地填充 X[i,j]。
  • 但是你只需要CF的第30列是否准确,因为那是你的代码正在做的事情。
  • 第二次迭代不是从第一次迭代中获取 X 值。所以我在以后的迭代中使用以前的 C 和 FS。
猜你喜欢
  • 2014-06-06
  • 2023-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多