Разработайте более эффективный для l oop, который относится к строке n-1 - PullRequest
4 голосов
/ 08 мая 2020

Мне нужно запустить logi c, где шаг «n» основан на результате шага «n-1», поэтому я реализовал logi c в a для l oop. Вот код:

library(data.table)
df<-as.data.table(matrix(rexp(100000, rate=.1), ncol=1000))
weight<-as.data.table(matrix(rexp(100, rate=10), ncol=1))

for (row in 1:nrow(weight))
{
  if (row > 2){

    # from second row start the logic
    # We create weighted averages of variables values: value(n-1)* (1-weight) + value(n) * weight
    df[row] <- 
      df[row-1,] * as.numeric(1 - weight[row]) + df[row,] * as.numeric(weight[row])

  }
}

Однако на выполнение требуется очень много времени, потому что data.table, в действительности, состоит из 1098 столбцов и 200 тыс. Строк.

Есть ли у кого-нибудь идеи, как разработать более эффективное решение?

1 Ответ

4 голосов
/ 08 мая 2020

Позвольте мне объяснить свою историю, как go я бы увеличил производительность такой задачи.

Базовое время

Сначала я воссоздаю ваши данные (немного меньше) и измеряю время, необходимое для его запуска:

library(data.table)
library(tictoc) # for timing only

# easier way to create a data.table
NROWS <- 100
NCOLS <- 100
set.seed(123)

df_orig <- data.table(matrix(rexp(NROWS * NCOLS, rate = 0.1), ncol = NCOLS))
wt <- data.table(V1 = rexp(NROWS, rate = 10))


df1 <- copy(df_orig)
tic()
for (r in 1:nrow(wt)) {
  if (r >= 2) { # assuming you mean >= 2 not >= 3 (:= >2)
    # from second row start the logic
    # We create weighted averages of variables values: value(n-1)* (1-weight) + value(n) * weight
    df1[r, ] <- df1[r-1,] * as.numeric(1 - wt[r]) + df1[r,] * as.numeric(wt[r])
  }
}
toc()
#> 1.274 sec elapsed

Создано 08.05.2020 с помощью пакета реплекс (v0.3.0)

Модель 2

Потом смотрю способы улучшить код. Например, as.numeric() может быть дорогим, как и чек if () в l oop. Давайте удалим это, но убедимся, что результаты остались прежними.

# no if check in the loop and replace as.numeric with [[1]]
# loop only from 2
df2 <- copy(df_orig)
tic()
for (r in 2:nrow(wt)) {
  df2[r, ] <- df2[r - 1, ] * (1 - wt[r][[1]]) + df2[r, ] * wt[r][[1]]
}
toc()
#> 1.149 sec elapsed

# check that the results are identical
all.equal(df1, df2)
#> [1] TRUE

Создано 08.05.2020 с помощью пакета репекс (v0.3.0)

Немного лучше, но мы еще не достигли этого.

Модель матрицы

В целом data.table - отличный способ повысить скорость, но такой доступ лучше всего реализовать в базовой структуре, т.е. matrix().

Итак, давайте сделаем это:

# Matrix based
mdf <- as.matrix(df_orig)
mwt <- as.matrix(wt)

tic()
for (r in 2:nrow(wt)) {
  mdf[r, ] <- mdf[r - 1, ] * (1 - mwt[r, ]) + mdf[r, ] * mwt[r, 1]
}
toc()
#> 0.005 sec elapsed

df3 <- data.table(mdf)

all.equal(df3, df1)
#> [1] TRUE

Создано 08.05.2020 с помощью пакета реплекс (v0.3.0)

Похоже, это неплохое ускорение!

Но есть еще кое-что ...

Rcpp Модель

Особенно в таких задачах Rcpp и c++ более громоздки для написания, но обеспечивают прекрасное ускорение. Здесь мы используем матричную библиотеку Armadillo для c++ и ее Rcpp привязок RcppArmadillo.

Преобразование вашего кода в Rcpp дает это:

# using rcpp
rcpp_code <- "// [[Rcpp::depends(RcppArmadillo)]]
#include <RcppArmadillo.h>
// [[Rcpp::export]]
arma::mat my_rcpp_fun(arma::mat data, arma::mat weights) {
  const int len = weights.size();

  // Cpp starts indexing at 0, so 1 is the second row!
  for (int i = 1; i < len; i++) {
    data.row(i) = data.row(i - 1) * (1 - weights.row(i)(0)) + data.row(i) * weights.row(i)(0);
  }
  return data;
}
"

Rcpp::sourceCpp(code = rcpp_code)
mdf2 <- as.matrix(df_orig)
mwt2 <- as.matrix(wt)

tic()
mdf4 <- my_rcpp_fun(mdf2, mwt2)
toc()
#> 0.002 sec elapsed

df4 <- data.table(mdf4)
all.equal(df4, df1)
#> [1] TRUE

Создано 2020-05-08 пакетом REPEX (v0.3.0)

От 1,274 с до 0,002 s звучит хорошо, правда?!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...