在R中嵌套for循环更有效

时间:2016-05-09 16:05:16

标签: r nested-loops equivalence

我正在研究一个研究项目,我想确定两个分布的等价性。我目前正在使用Mann-Whitney等效性测试,我正在运行的代码(如下)由Stefan Wellek撰写的“测试统计假设的等效性和非劣效性”一书(2010)。在运行我的数据之前,我正在使用具有相同均值和标准差的随机正态分布来测试此代码。我的问题是有三个嵌套的for循环,当运行更大的分布大小时(如下例所示),代码需要永远运行。如果我只需要运行它就不会出现这样的问题,但我正在进行模拟测试并创建功率曲线,因此我需要运行此代码的多次迭代(大约10,000次)。目前,根据我改变分布大小的方式,运行10,000次迭代需要数天。

任何有助于提高性能的帮助都将非常感激。

x <- rnorm(n=125, m=3, sd=1)
y <- rnorm(n=500, m=3, sd=1)

alpha <- 0.05
m <- length(x)
n <- length(y)
eps1_ <- 0.2 #0.1382 default
eps2_ <- 0.2 #0.2602 default

eqctr <- 0.5 + (eps2_-eps1_)/2 
eqleng <- eps1_ + eps2_

wxy <- 0
pihxxy <- 0
pihxyy <- 0

for (i in 1:m)
 for (j in 1:n)
  wxy <- wxy + trunc(0.5*(sign(x[i] - y[j]) + 1))

for (i in 1:m)
 for (j1 in 1:(n-1))
  for (j2 in (j1+1):n)
    pihxyy <- pihxyy + trunc(0.5*(sign(x[i] - max(y[j1],y[j2])) + 1))

for (i1 in 1:(m-1))
 for (i2 in (i1+1):m)
  for (j in 1:n)
    pihxxy <- pihxxy + trunc(0.5*(sign(min(x[i1],x[i2]) - y[j]) + 1))

wxy <- wxy / (m*n)
pihxxy <- pihxxy*2 / (m*(m-1)*n)
pihxyy <- pihxyy*2 / (n*(n-1)*m)
sigmah <- sqrt((wxy-(m+n-1)*wxy**2+(m-1)*pihxxy+(n-1)*pihxyy)/(m*n))

crit <- sqrt(qchisq(alpha,1,(eqleng/2/sigmah)**2))

if (abs((wxy-eqctr)/sigmah) >= crit) rej <- 1
if (abs((wxy-eqctr)/sigmah) < crit)  rej <- 0

if (is.na(sigmah) || is.na(crit)) rej <- 1

MW_Decision <- rej

cat(" ALPHA =",alpha,"  M =",m,"  N =",n,"  EPS1_ =",eps1_,"  EPS2_ =",eps2_,
  "\n","WXY =",wxy,"  SIGMAH =",sigmah,"  CRIT =",crit,"  REJ=",MW_Decision)

2 个答案:

答案 0 :(得分:4)

您可以使用outer代替第一个双循环:

set.seed(42)

f1 <- function(x,y) {
 wxy <- 0
 for (i in 1:m)
  for (j in 1:n)
   wxy <- wxy + trunc(0.5*(sign(x[i] - y[j]) + 1))
 wxy
}

f2 <- function(x,y) sum(outer(x,y, function(x,y) trunc(0.5*(sign(x-y)+1))))

f1(x,y)
[1] 32041
f2(x,y)
[1] 32041

你获得大约50倍的加速:

library(microbenchmark)
microbenchmark(f1(x,y),f2(x,y))
Unit: milliseconds
     expr        min         lq     median         uq      max neval
 f1(x, y) 138.223841 142.586559 143.642650 145.754241 183.0024   100
 f2(x, y)   1.846927   2.194879   2.677827   3.141236  21.1463   100

其他循环比较棘手。

答案 1 :(得分:4)

请参阅下面的修改以获得更好的建议

提高速度的一个简单建议是byte compile你的代码。

例如,我将代码包装到从alpha <- 0.05行开始的函数中并在我的笔记本电脑上运行它。只需字节编译当前代码,它的运行速度就快了两倍。

set.seed(1234)
x <- rnorm(n=125, m=3, sd=1)
y <- rnorm(n=500, m=3, sd=1)

# f1 <- function(x,y){ ...your code...}

system.time(f1(x, y))
#   user  system elapsed 
# 33.249   0.008  33.278 

library(compiler)
f2 <- cmpfun(f1)

system.time(f2(x, y))

#   user  system elapsed 
# 17.162   0.002  17.170 

修改

我应该补充一点,这是一种不同语言比R更好的东西。你看过Rcppinline包吗?

我很想知道如何使用它们,所以我认为这是一个很好的机会。

以下是使用inline包和Fortran对代码的调整(因为我比C更熟悉)。这一点并不难(只要你知道Fortran或C);我只是按照cfunction中列出的示例。

首先,让我们重新编写你的循环并编译它们:

library(inline)

# Fortran code for first loop
loop1code <- "
   integer i,  j1,  j2
   real*8 tmp
   do i = 1, m
      do j1 = 1, n-1
         do j2 = j1+1, n
            tmp = x(i) - max(y(j1),y(j2))
            if (tmp > 0.) pihxyy = pihxyy + 1
         end do
      end do
   end do
"    
# Compile the code and turn loop into a function
loop1fun <- cfunction(sig = signature(x="numeric", y="numeric", pihxyy="integer", m="integer", n="integer"), dim=c("(m)", "(n)", "", "", ""), loop1code, language="F95")

# Fortran code for second loop
loop2code <- "
   integer i1, i2,  j
   real*8 tmp
   do i1 = 1, m-1
      do i2 = i1+1, m
         do j = 1, n
            tmp = min(x(i1), x(i2)) - y(j)
            if (tmp > 0.) pihxxy = pihxxy + 1
         end do
      end do
   end do
"    
# Compile the code and turn loop into a function
loop2fun <- cfunction(sig = signature(x="numeric", y="numeric", pihxxy="integer", m="integer", n="integer"), dim=c("(m)", "(n)", "", "", ""), loop2code, language="F95")

现在让我们创建一个使用这些功能的新功能。所以它不会太长,我只是简单地描述了我从代码中修改的关键部分:

f3 <- function(x, y){

  # ... code ...

# Remove old loop
## for (i in 1:m)
##  for (j1 in 1:(n-1))
##   for (j2 in (j1+1):n)
##     pihxyy <- pihxyy + trunc(0.5*(sign(x[i] - max(y[j1],y[j2])) + 1))

# Call new function from compiled code instead
pihxyy <- loop1fun(x, y, pihxyy, m, n)$pihxyy

# Remove second loop
## for (i1 in 1:(m-1))
##  for (i2 in (i1+1):m)
##   for (j in 1:n)
##     pihxxy <- pihxxy + trunc(0.5*(sign(min(x[i1],x[i2]) - y[j]) + 1))

# Call new compiled function for second loop
pihxxy <- loop2fun(x, y, pihxxy, m, n)$pihxxy

# ... code ...
}

现在我们运行它,瞧,我们得到了巨大的速度提升! :)

system.time(f3(x, y))
#   user  system elapsed 
    0.12    0.00    0.12 

我确实检查过它与你的代码有相同的结果,但你可能想要运行一些额外的测试以防万一。