R векторных манипуляций с массивами данных

Думаю, в этом вопросе будет гораздо больше людей. У меня есть определенная задача, чтобы сделать наиболее эффективным способом. Мои базовые данные: - временные индексы сигналов купли-продажи - по показаниям времени времени у меня есть ROC (норма изменения) между ближайшими парами buy-sell:

r <- array(data = NA, 
           dim = c(5, 5), 
           dimnames = list(buy_idx = c(1,5,9,12,16), 
                           sell_idx = c(3,7,10,14,19)))
diag(r) <- c(1.04,0.97,1.07,1.21,1.1)

Задача состоит в создании движущегося составного ROC на каждом возможном окне (пары buy-sell), и способ решения моей задачи в настоящее время:

for(i in 2:5){
  r[1:(i-1),i] <- r[1:(i-1),i-1] * r[i,i]
}

Пока я не зацикливаюсь где-то сверху, время моего решения очень приемлемо. Есть ли способ изменить этот цикл на векторное решение? Есть ли хорошие хорошо документированные учебные пособия для изучения векторизованного типа мышления в R? - это было бы гораздо более ценным, чем одноразовое решение!

изменить 20130709:

Следующая задача, очень связанная с предыдущей задачей/примером. Применять налоговую стоимость для каждой транзакции (налог в%). Текущее решение:

diag(r[,]) <- diag(r[,]) * ((1-(tax/100))^2)
for(i in 2:dim(r)[2]){
  r[1:(i-1),i] <- r[1:(i-1),i] * ((1-(tax/100))^(2*(i:2)))
}

Знаете ли вы более эффективный способ? или более правильно, если это не обрабатывает все.

Ответы

Ответ 1

Если d - ваши диагональные элементы, то везде j >= i, r[i,j] есть prod(d[i:j]), которое также можно записать prod(d[1:j]) / prod(d[1:(i-1)]). Следовательно, этот трюк с использованием отношения outer кумулятивного продукта:

d <- c(1.04,0.97,1.07,1.21,1.1)
n <- length(d)
p <- cumprod(c(1, d))
r <- t(outer(p, 1/p, "*"))[-n-1, -1]
r[lower.tri(r)] <- NA

Некоторые тесты показывают, что для некоторых (не для всех) размеров ввода лучше, чем OP:

OP <- function(d) {
   r <- diag(d)
   for(i in 2:length(d)){
     r[1:(i-1),i] <- r[1:(i-1),i-1] * r[i,i]
   }
   r
}

flodel <- function(d) {
   n <- length(d)
   p <- cumprod(c(1, d))
   r <- t(outer(p, 1/p, "*"))[-n-1, -1]
   r[lower.tri(r)] <- NA
   r
}

d <- runif(10)
microbenchmark(OP(d), flodel(d))
# Unit: microseconds
#        expr     min       lq   median      uq     max
# 1 flodel(d)  83.028  85.6135  88.4575  90.153 144.111
# 2     OP(d) 115.993 122.0075 123.4730 126.826 206.892

d <- runif(100)
microbenchmark(OP(d), flodel(d))
# Unit: microseconds
#        expr      min       lq    median       uq      max
# 1 flodel(d)  490.819  545.528  549.6095  566.108  684.043
# 2     OP(d) 1227.235 1260.823 1282.9880 1313.264 3913.322

d <- runif(1000)
microbenchmark(OP(d), flodel(d))
# Unit: milliseconds
#        expr      min        lq    median        uq       max
# 1 flodel(d) 97.78687 106.39425 121.13807 133.99502 154.67168
# 2     OP(d) 53.49014  60.10124  72.56427  85.17864  91.89011

изменить ответ 20130709 дополнение:

Я предполагаю, что tax является скаляром и пусть z <- (1- tax/100)^2. Ваш конечный результат r, умноженный на матрицу z, поднятую при разных степенях. То, что вы хотите избежать, это вычислить эти силы снова и снова. Вот что я буду делать:

pow <- 1L + col(r) - row(r)
pow[lower.tri(pow)] <- NA
tax.mult <- (z^(1:n))[pow]
r <- r * tax.mult

Ответ 2

Я использовал другой метод, который сводится к использованию Reduce. Например, простой пример Reduce для рекурсивных вычислений может быть полезен кому-то:

ожидаемый результат:

> r
       sell_idx
buy_idx    3      7       10       14       19
     1  1.04 1.0088 1.079416 1.306093 1.436703
     5    NA 0.9700 1.037900 1.255859 1.381445
     9    NA     NA 1.070000 1.294700 1.424170
     12   NA     NA       NA 1.210000 1.331000
     16   NA     NA       NA       NA 1.100000

Основной пример с использованием начальных значений диагонали и Reduce

x <- c(1.04,0.97,1.07,1.21,1.1)
Reduce(prod, tail(x,-1), x[1], accumulate=TRUE)

## gives first row of the answer 
## 1.04 / (1.04*0.97) / 1.07 * (1.04*0.97) etc etc etc

[1] 1.040000 1.008800 1.079416 1.306093 1.436703

Зацикливание по длине начальных значений и добавление некоторых NA дают полный результат:

t(
  sapply(1:length(x),
    function(y) c(rep(NA,y-1),Reduce(prod, tail(x,-y), x[y], accumulate=TRUE))
    )
)

Полный результат:

     [,1]   [,2]     [,3]     [,4]     [,5]
[1,] 1.04 1.0088 1.079416 1.306093 1.436703
[2,]   NA 0.9700 1.037900 1.255859 1.381445
[3,]   NA     NA 1.070000 1.294700 1.424170
[4,]   NA     NA       NA 1.210000 1.331000
[5,]   NA     NA       NA       NA 1.100000

Изменить

И так как вышеприведенное Reduce fanciness просто эквивалентно cumprod, альтернативное более простое решение было бы просто:

rbind(
  cumprod(x),
  t(sapply(1:(length(x)-1),function(y) c(rep(NA,y),cumprod(tail(x,-y)))))
)

Ответ 3

Идя в другом направлении от векторизации, здесь применяется подход, который дает прирост скорости (который очень большой для небольших массивов и доходит до 2-3х диапазона для больших):

library(inline)
library(Rcpp)

solver_fn = cxxfunction(signature(x = "numeric"), '
  NumericVector diag(x);

  unsigned n = diag.size();
  std::vector<double> result(n*n);

  result[0] = diag[0];

  unsigned col_shift_old = 0, col_shift = 0;
  for (unsigned col = 1; col < n; ++col) {
    col_shift = col * n;
    for (unsigned row = 0; row <= col; ++row) {
      if (result[row + col_shift_old] == 0)
        result[row + col_shift] = diag[col];
      else
        result[row + col_shift] = result[row + col_shift_old] * diag[col];
    }
    col_shift_old = col_shift;
  }

  return NumericVector(result.begin(), result.end());
', plugin = "Rcpp")

compute_matrix = function(d) {
  matrix(solver_fn(d), ncol = length(d))
}

И вот некоторые тесты:

op = function(d) {
  r = diag(d)
  for (i in 2:length(d)) {
    r[1:(i-1), i] <- r[1:(i-1), i-1] * r[i,i]
  }
  r
}

d = runif(1e4)
system.time(op(d))
# user  system elapsed
#3.456   1.006   4.462
system.time(compute_matrix(d))
# user  system elapsed
#1.001   0.657   1.660

d = runif(1e3)
system.time(op(d))
# user  system elapsed
# 0.04    0.00    0.04
system.time(compute_matrix(d))
# user  system elapsed
#0.008   0.000   0.009

d = runif(1e2)
system.time(for (i in 1:1000) {op(d)})
# user  system elapsed
#1.075   0.000   1.075
system.time(for (i in 1:1000) {compute_matrix(d)})
# user  system elapsed
#0.075   0.000   0.075

Re 20130709:

Просто передайте tax в функцию C++ и сделайте там умножения. Если вы понимаете, как это работает, изменение будет тривиальным.

Ответ 4

Отказ от ответственности: я использовал это в другом ответе. Так что это будет бесстыдный плагин.


Чтобы ответить на то, что кажется вашим общим вопросом, а не на пример, который вы указали, - как преобразовать цикл for в векторное решение --- следующие могут быть несколько полезных указателей:

Рассмотрим структуру объекта, который вы повторяете. Могут быть разные типы, например:

a) Элементы вектора/матрицы. б) Строки/столбцы матрицы. c) Размерность многомерного массива. d) Элементы списка (который внутри себя может быть одним  указанных выше объектов). e) Соответствующие элементы множества списков/векторов.

В каждом случае используемая вами функция может немного отличаться, но стратегия использования одинакова. Кроме того, изучите применимую семью. Различные функции * pply основаны на аналогичной абстракции, но отличаются тем, что они принимают в качестве входных данных и что они выбрасывают в качестве вывода.

В приведенном выше списке случаев, например.

a) Элементы вектора: Ищите уже существующие векторизованные решения (как указано выше), которые являются основной силой в R. В дополнение к этому рассмотрим матричную алгебру. Большинство проблем, которые, как представляется, требуют циклов (или вложенных циклов), могут быть записаны как уравнения в матричной алгебре.

b) Строки/столбцы матрицы: Используйте. Используйте правильное значение для аргумента MARGIN. Similary для c) для массивов с большими размерами.

d) Используйте лапку. Если результат, который вы возвращаете, является "простой" структурой (скалярным или векторным), вы можете рассмотреть sapply, который просто упрощает2array (lapply (...)) и возвращает массив в соответствующих измерениях.

e) Используйте mapply. "M" может выступать за многомерное применение.

Как только вы поняли объект, который вы итерируете, и соответствующий инструмент, упростите свою проблему. Подумайте не об общем объекте, который вы повторяете, а о одном его экземпляре. Например, при итерации по строкам матрицы забудьте о матрице и запомните только строку.

Теперь напишите функцию (или лямбда), которая работает только с одним экземпляром (элементом) вашего iterand и просто "примените" его, используя правильный член семейства * pply.


Вот моя попытка проблемы с помощью cumprod. Это попадает в сладкое пятно размером около 1000 х 1000 матриц, но оно возвращает список, а не матрицу, как вы ожидаете. Однако я не предлагаю это как решение, так как я думаю, что ваше решение в базе R лучше всего следует @eddi в Rcpp. Это всего лишь пример процесса, о котором я говорил выше:

asb <- function (d) lapply(X=seq.int(from=length(d), to=1),
                           FUN=function (k) cumprod(d[seq_len(k)]))