Как векторизовать тройные вложенные циклы?

Я занимался поиском похожих проблем, и у меня есть смутное представление о том, что мне делать: векторизовать все или использовать apply() семейство. Но я новичок в программировании на R, и оба вышеперечисленных метода довольно запутаны.

Вот мой исходный код:

x<-rlnorm(100,0,1.6)
j=0
k=0
i=0
h=0
lambda<-rep(0,200)
sum1<-rep(0,200)
constjk=0
wj=0
wk=0
for (h in 1:200)
{
   lambda[h]=2+h/12.5
   N=ceiling(lambda[h]*max(x))
   for (j in 0:N)
   {
      wj=(sum(x<=(j+1)/lambda[h])-sum(x<=j/lambda[h]))/100
      for (k in 0:N)
      {
         constjk=dbinom(k, j + k, 0.5)
         wk=(sum(x<=(k+1)/lambda[h])-sum(x<=k/lambda[h]))/100
         sum1[h]=sum1[h]+(lambda[h]/2)*constjk*wk*wj
      }
   }
}

Позвольте мне немного объяснить. Я хочу собрать 200 значений sum1 (это первый цикл), и для каждого значения sum1 это сумма (lambda[h]/2)*constjk*wk*wj, то есть двух других циклов. Самое утомительное то, что N меняется с h, поэтому я понятия не имею, как векторизовать j-петлю и k-петлю. Но, конечно, я могу векторизовать h-петлю с помощью lambda<-seq() и N<-ceiling(), и это лучшее, что я могу сделать. Есть ли способ еще больше упростить код?


person Nicholas Humphrey    schedule 09.11.2012    source источник
comment
Это первый, в основном я хочу рассчитать разницу между двумя edfs, таким образом, /100.   -  person Nicholas Humphrey    schedule 09.11.2012
comment
Значения лямбда и N можно вычислить вне цикла с помощью векторных команд. Вот об этом здесь. Зная значения N и лямбда, вы, вероятно, сможете ускорить вычисление wj после этого, но ненамного. (wj может быть двумя маленькими sapply вне цикла только для суммы (x ‹ = j), а затем после этого векторизованных операций). Возможно, появится более сильный ответ, используя outer для вычислений constjk и wk.   -  person John    schedule 09.11.2012
comment
В общем, следуйте этому правилу: если ваш j-й расчет зависит от результата (j-1) расчета, вы не можете векторизовать. Если это не так, вы можете.   -  person Carl Witthoft    schedule 09.11.2012


Ответы (2)


Ваш код можно идеально веркторизировать с помощью 3 вложенных вызовов sapply. Неискушенному глазу это может быть трудно читать, но суть его в том, что вместо добавления одного значения за раз к sum1[h] мы вычисляем все члены, созданные самым внутренним циклом, за один раз и суммируем их.

Хотя это векторизованное решение быстрее, чем ваш тройной цикл for, улучшение не является существенным. Если вы планируете использовать его много раз, я предлагаю вам реализовать его на C или Fortran (с обычными for циклами), что намного увеличивает скорость. Остерегайтесь, однако, что он имеет высокую временную сложность и будет плохо масштабироваться с увеличением значений lambda, в конечном итоге достигнув точки, когда невозможно будет выполнить вычисления в разумные сроки независимо от реализации.

lambda <- 2 + 1:200/12.5
sum1 <- sapply(lambda, function(l){
    N <- ceiling(l*max(x))
    sum(sapply(0:N, function(j){
        wj <- (sum(x <= (j+1)/l) - sum(x <= j/l))/100
        sum(sapply(0:N, function(k){
            constjk <- dbinom(k, j + k, 0.5)
            wk <- (sum(x <= (k+1)/l) - sum(x <= k/l))/100
            l/2*constjk*wk*wj
        }))
    }))
})

Кстати, вам не нужно предопределять такие переменные, как h, j, k, wj и wk. Тем более, что не при векторизации, так как присвоение им значений внутри функций, переданных в sapply, создаст наложенные локальные переменные с тем же именем (т.е. игнорируя те, которые вы предварительно определили).

person Backlin    schedule 09.11.2012
comment
Большое спасибо! Это очень помогает, так как теперь я могу медленно преобразовать все остальные коды в sapply(). - person Nicholas Humphrey; 10.11.2012

Давайте завернем вашу симуляцию в функцию и установим время:

sim1 <- function(num=20){
  set.seed(42)
  x<-rlnorm(100,0,1.6)
  j=0
  k=0
  i=0
  h=0
  lambda<-rep(0,num)
  sum1<-rep(0,num)
  constjk=0
  wj=0
  wk=0

  for (h in 1:num)
  {
    lambda[h]=2+h/12.5
    N=ceiling(lambda[h]*max(x))
    for (j in 0:N)
    {
      wj=(sum(x<=(j+1)/lambda[h])-sum(x<=j/lambda[h]))/100
      for (k in 0:N)
      {
        set.seed(42)
        constjk=dbinom(k, j + k, 0.5)
        wk=(sum(x<=(k+1)/lambda[h])-sum(x<=k/lambda[h]))/100
        sum1[h]=sum1[h]+(lambda[h]/2)*constjk*wk*wj
      }
    }
  }

  sum1
}

system.time(res1 <- sim1())
#   user  system elapsed 
#    5.4     0.0     5.4

Теперь давайте сделаем это быстрее:

sim2 <- function(num=20){
  set.seed(42) #to make it reproducible
  x <- rlnorm(100,0,1.6)

  h <- 1:num
  sum1 <- numeric(num)
  lambda <- 2+1:num/12.5
  N <- ceiling(lambda*max(x))

  #functions for wj and wk
  wjfun <- function(x,j,lambda,h){
    (sum(x<=(j+1)/lambda[h])-sum(x<=j/lambda[h]))/100
  }
  wkfun <- function(x,k,lambda,h){
    (sum(x<=(k+1)/lambda[h])-sum(x<=k/lambda[h]))/100
  }

  #function to calculate values of sum1
  fun1 <- function(N,h,x,lambda) {
    sum1 <- 0
    set.seed(42) #to make it reproducible
    #calculate constants using outer
    const <- outer(0:N[h],0:N[h],FUN=function(j,k) dbinom(k, j + k, 0.5))
    wk <- numeric(N[h]+1)
    #loop only once to calculate wk
    for (k in 0:N[h]){
      wk[k+1] <- (sum(x<=(k+1)/lambda[h])-sum(x<=k/lambda[h]))/100 
    }

    for (j in 0:N[h])
    {
      wj <- (sum(x<=(j+1)/lambda[h])-sum(x<=j/lambda[h]))/100
      for (k in 0:N[h])
      {
        sum1 <- sum1+(lambda[h]/2)*const[j+1,k+1]*wk[k+1]*wj
      }
    }
    sum1
  }

  for (h in 1:num)
  {
    sum1[h] <- fun1(N,h,x,lambda)
  }  
  sum1
}

system.time(res2 <- sim2())
#user  system elapsed 
#1.25    0.00    1.25 

all.equal(res1,res2)
#[1] TRUE

Тайминги для кода @Backlin`s (с 20 взаимодействиями) для сравнения:

   user  system elapsed 
   3.30    0.00    3.29 

Если это все еще слишком медленно, и вы не можете или не хотите использовать другой язык, есть также возможность распараллеливания. Насколько я вижу, внешний цикл смущающе параллелен. Есть несколько хороших и простых пакетов для распараллеливания.

person Roland    schedule 09.11.2012
comment
Если у вас есть время попробовать реализацию C++ - person Backlin; 09.11.2012
comment
... и если вы достаточно свободно владеете C++. - person Roland; 09.11.2012
comment
Спасибо! Я думаю, что он уже достаточно быстр (на самом деле я обнаружил, что он работает намного быстрее на компьютерах в лаборатории, чем на моем ноутбуке, что является хорошим знаком, так как мне не нужно менять много кода сейчас). - person Nicholas Humphrey; 10.11.2012