Предложения по избеганию циклов for в R

Я пытаюсь избежать использования циклов for() для своей проблемы. Скажем, у меня есть два вектора, для простоты: x1 <- c(1,10,30) и x2 <- c(11,31,40). Эти векторы содержат контрольные точки, которые указывают на определенные интервалы в моем df с переменными, каждая из которых имеет, в данном случае, 40 наблюдений. Итак:
df(x1[1]:x2[1]) будет первыми десятью наблюдениями. df(x1[2]:x2[2]) будут следующими 20 наблюдениями, причем последнее (30,40) представляет последние 10. Я хочу рассчитать несколько статистических данных, включая, например, mean, std и variance для каждого из интервалов. for()-loops сделают свое дело, но это очень медленно. Я смотрел на функции apply, но не могу понять. mean(df[x1:x2]) также не работает, так как просто принимает первое значение для x1 и x2.

Какие-либо предложения?

--тстев


person tstev    schedule 06.02.2015    source источник
comment
Cna вы поместите свой входной data.frame ?   -  person Colonel Beauvel    schedule 06.02.2015
comment
@Colonel Beauvel, это просто произвольный пример, чтобы упростить проблему. Но скажем, кадр данных выглядит так, если бы меня интересовала только одна переменная: df <- as.data.frame(sample(40))   -  person tstev    schedule 06.02.2015
comment
С разницей :)   -  person Colonel Beauvel    schedule 06.02.2015
comment
Имейте в виду, что замена циклов for на функции типа apply автоматически не ускоряет работу (например, см. than-a-for-loop-in-r" title="почему метод применения медленнее, чем цикл for в r">stackoverflow.com/questions/5533246/). С другой стороны, векторизованные решения, вероятно, будут.   -  person blindjesse    schedule 06.02.2015


Ответы (4)


Хорошая возможность использовать Map с полезным each из пакета plyr:

library(plyr)

Map(function(u,v) each(mean, sd, var)(df[u:v,1]), x1, x2)

#[[1]]
#    mean        sd       var
#17.90000  10.15929 103.21111  

#[[2]]
#    mean        sd       var
#19.14286  12.18313 148.42857

#[[3]]
#    mean        sd       var 
#24.81818  10.78720 116.36364

Данные:

x1 <- c(1,10,30)
x2 <- c(10,30,40)
set.seed(3)
df <- as.data.frame(sample(40))
person Colonel Beauvel    schedule 06.02.2015
comment
большое спасибо за предложение! Я хотел бы выбрать два ответа. Я собираюсь использовать решение @Benoit, потому что мне его легче понять. Я не уверен, что происходит с функцией Map. Я нахожу, что документации не хватает. Но это, вероятно, из-за того, что я только начал с R. :) В любом случае большое спасибо! - person tstev; 06.02.2015
comment
Map принимает два вектора x1 и x2. Затем примените функцию к x1[1] и x2[1]. Затем перемещается вверх и применяет функцию к x1[2] и x2[2] и так далее. - person Colonel Beauvel; 06.02.2015
comment
Ах, теперь я вижу. Протестировал его с моим фактическим набором данных, и на самом деле это здорово! С Map() мне не понадобились бы отдельные операторы для каждой статистики, которую я хотел бы. - person tstev; 06.02.2015
comment
Именно с помощью Map и каждый отлично функциональное программирование, очень компактно! - person Colonel Beauvel; 06.02.2015
comment
Очень верно! Это также быстрее, чем apply при тестировании с пакетом microbenchmark. Учитывая, что я буду получать значительно больший набор данных, как только мои скрипты станут надежными, Map() окажется очень полезным :D - person tstev; 06.02.2015

Я склонен против использования apply в строках data.frame (поскольку любой неверный шаг преобразует все в класс символов). Мне пришлось сделать что-то очень похожее на то, что вы просите в другом коде, и я выбрал mapply.

Он делает «что-то» с первым элементом 2 (или более) векторов/списков, затем делает то же самое «что-то» со вторым элементом тех же векторов/списков и т. д. «Что-то», конечно, определяется первым аргумент -- функция, похожая на другие *apply функции.

set.seed(42)
x1 <- c(1,10,30)
x2 <- c(11,31,40)
df <- as.data.frame(sample(40))
ret <- mapply(function(a,b) df[a:b,], x1, x2)
ret
## [[1]]
##  [1] 37 40 11 31 24 19 26  5 22 32 14
## [[2]]
##  [1] 32 14 21 27  7 13 36 25  3 38 12 35 23 18 17  2  8  6 29 30 10 15
## [[3]]
##  [1] 10 15 39  4 33  1 28 34  9 16 20

Отсюда было бы тривиально применить любые другие статистические сводки, которые вы хотите:

sapply(ret, function(x) c(mean=mean(x), sd=sd(x)))
##          [,1]     [,2]     [,3]
## mean 23.72727 19.13636 19.00000
## sd   10.95528 11.14107 12.87633

(Или вы всегда можете расширить вызов mapply для прямого вызова этих других функций.)

РЕДАКТИРОВАНИЕ №1:

Как предложил @docendo discimus, Mapmapply с SIMPLIFY=FALSE) немного быстрее. Для сравнения:

set.seed(3)
x1 <- c(1,11,31)
x2 <- c(10,30,40)
df1 <- data.frame(V1 = sample(40))
df2 <- df1[,,drop = FALSE]
df3 <- df1[,,drop = FALSE]
grp <- rep(seq_along(x1), (x2-x1) + 1L)
df2 <- cbind(df2, grp)

library(data.table)
library(dplyr)
library(microbenchmark)

microbenchmark(dt=setDT(df1)[, list(mean(V1), sd(V1), var(V1)), by = grp],
               dplyr=df2 %>% group_by(grp) %>% summarise_each(funs(mean, sd, var)),
               mapplyT=mapply(function(a,b) { x <- df3[a:b,]; c(mean(x), sd(x), var(x)); }, x1, x2, SIMPLIFY=TRUE),
               mapplyF=mapply(function(a,b) { x <- df3[a:b,]; c(mean(x), sd(x), var(x)); }, x1, x2, SIMPLIFY=FALSE),
               Map=Map(function(a,b) { x <- df3[a:b,]; c(mean(x), sd(x), var(x)); }, x1, x2))
## Unit: microseconds
##     expr      min        lq      mean    median        uq      max neval
##       dt  925.964 1006.9570 1176.5629 1081.4810 1184.7870 2582.434   100
##    dplyr 1843.449 1967.0590 2154.9829 2042.2515 2185.2745 3839.960   100
##  mapplyT  208.398  237.8500  272.8850  260.8315  286.2685  511.846   100
##  mapplyF  187.424  208.6205  237.6805  225.1320  247.2215  445.801   100
##      Map  191.441  215.7610  240.9025  231.6025  258.6005  441.785   100

Я сделал явные глубокие копии data.frame, потому что setDT изменил data.frame на месте (следовательно, его эффективность), но mapply и Map не смогли справиться с data.table. (Я вставил mean,sd,var в свои вызовы mapply, чтобы сравнивать яблоки с яблоками.)

РЕДАКТИРОВАНИЕ №2:

Предыдущие бенчмарки выглядят впечатляюще и убедительно, но не отображают накладные расходы на вызовы по сравнению с эффективностью механизмов обработки больших данных. Вот еще один опыт с большим количеством данных.

Когда отдельные подмножества довольно велики, т. е. меньше «фрагментов» из исходного data.frame, производительность стремится уравновеситься. Здесь я контролирую размер фрагмента с помощью k:

n <- 4000
k <- 100
x1 <- c(1, sort(sample(n, size = n/k - 1)))
x2 <- c(x1[-1] - 1, n)
df1 <- data.frame(V1 = sample(n))
df2 <- df1[,,drop = FALSE]
df3 <- df1[,,drop = FALSE]
grp <- rep(seq_along(x1), (x2-x1) + 1L)
df2 <- cbind(df2, grp)

microbenchmark(dt=setDT(df1)[, list(mean(V1), sd(V1), var(V1)), by = grp],
               dplyr=df2 %>% group_by(grp) %>% summarise_each(funs(mean, sd, var)),
               mapplyT=mapply(function(a,b) { x <- df3[a:b,]; c(mean(x), sd(x), var(x)); }, x1, x2, SIMPLIFY=TRUE),
               mapplyF=mapply(function(a,b) { x <- df3[a:b,]; c(mean(x), sd(x), var(x)); }, x1, x2, SIMPLIFY=FALSE),
               Map=Map(function(a,b) { x <- df3[a:b,]; c(mean(x), sd(x), var(x)); }, x1, x2))
## Unit: milliseconds
##     expr      min       lq     mean   median       uq      max neval
##       dt 2.133063 2.297282 2.549046 2.435618 2.655842 4.305396   100
##    dplyr 2.145558 2.401482 2.643981 2.552090 2.720102 4.374118   100
##  mapplyT 2.599392 2.775883 3.135473 2.926045 3.156978 5.430832   100
##  mapplyF 2.498540 2.738398 3.079050 2.882535 3.094057 7.041340   100
##      Map 2.624382 2.725680 3.158272 2.894808 3.184869 6.533956   100

Однако, если размер фрагмента уменьшается, уже хорошо работающий dplyr выходит вперед с большим отрывом:

n <- 4000
k <- 10
x1 <- c(1, sort(sample(n, size = n/k - 1)))
x2 <- c(x1[-1] - 1, n)
df1 <- data.frame(V1 = sample(n))
df2 <- df1[,,drop = FALSE]
df3 <- df1[,,drop = FALSE]
grp <- rep(seq_along(x1), (x2-x1) + 1L)
df2 <- cbind(df2, grp)

microbenchmark(dt=setDT(df1)[, list(mean(V1), sd(V1), var(V1)), by = grp],
               dplyr=df2 %>% group_by(grp) %>% summarise_each(funs(mean, sd, var)),
               mapplyT=mapply(function(a,b) { x <- df3[a:b,]; c(mean(x), sd(x), var(x)); }, x1, x2, SIMPLIFY=TRUE),
               mapplyF=mapply(function(a,b) { x <- df3[a:b,]; c(mean(x), sd(x), var(x)); }, x1, x2, SIMPLIFY=FALSE),
               Map=Map(function(a,b) { x <- df3[a:b,]; c(mean(x), sd(x), var(x)); }, x1, x2))
## Unit: milliseconds
##     expr       min       lq      mean    median        uq       max neval
##       dt 11.494443 12.45187 14.163123 13.716532 14.655883 62.424668   100
##    dplyr  2.729696  3.05501  3.286876  3.148276  3.324098  4.832414   100
##  mapplyT 25.195579 27.67426 28.488846 28.319758 29.247729 32.897811   100
##  mapplyF 25.455742 27.42816 28.713237 28.038622 28.958785 76.587224   100
##      Map 25.184870 27.32730 28.737281 28.198155 28.768237 77.830470   100

Если вы заметили, dplyr заняло примерно столько же времени для меньшего набора данных, как и для большего. Хороший.

Есть три вида лжи: ложь, наглая ложь и статистика. (Бенджамин Дизраэли) Это в равной степени относится и к эталонным тестам.

person r2evans    schedule 06.02.2015
comment
Я думаю, что было бы немного быстрее, используя Map вместо mapply или указав simplify = FALSE внутри mapply (поскольку вам все равно нужен список взамен). - person talat; 06.02.2015
comment
По моим тестам, SIMPLIFY=TRUE сокращает время примерно на 18% на больших кадрах данных, вы абсолютно правы. Смотрите мою правку. - person r2evans; 06.02.2015
comment
Хорошая редакция! Увидев тесты, я удалил свой ответ :) - person talat; 06.02.2015
comment
Будьте осторожны с тестами; это просто показывает, как все работает с очень маленькими наборами данных. Все выглядит немного иначе, когда исходный data.frame и векторы больше. Предстоит еще одно редактирование. - person r2evans; 06.02.2015
comment
Правда, это была и моя вторая мысль. С другой стороны, в исходном примере были перекрывающиеся интервалы, что отличается от того, что я сделал в своем ответе. - person talat; 06.02.2015
comment
Отличные объяснения! Большое спасибо! Я собираюсь потратить некоторое время, чтобы понять это и применить к своим данным. - person tstev; 09.02.2015

Вот решение вашей проблемы:

x1 <- c(1,10,30)
x2 <- c(10,30,40)

df <- as.data.frame(sample(40))
df2 <- data.frame(x1,x2)

apply(df2,1, function(x) mean(df[x[1]:x[2],]))

Просто замените mean() на sd() или var(), чтобы получить стандартное отклонение или дисперсию. Не забудьте аргумент na.rm=TRUE, если у вас отсутствуют данные в df.

person Benoit    schedule 06.02.2015
comment
Большое спасибо! Я предполагаю, что это также возможно с матрицами. Насколько я понимаю, R работает «быстрее» с матрицами или списками, а не с data.frames. - person tstev; 06.02.2015

Может быть, вместо цикла for вы могли бы применить дважды? Желаемое вычисление можно завернуть в функцию (в моем примере это compute_mean), а затем можно вызывать эту функцию на парах индексов из x1 и x2. Учитывая, что x1 и x2 имеют одинаковую длину, это легко сделать с помощью lapply

x1 <- c(1,10,30)
x2 <- c(10,30,40)
df <- as.data.frame(sample(40))

compute_mean <- function(df, ind1, ind2, i){
    result <- apply( df[c(ind1[i]:ind2[i]), , drop = F], 2, mean )
    return(result)
}

unlist(lapply(c(1:length(x1)), function(x){
    out <- compute_mean(df = df, ind1 = x1, ind2 = x2, i = x)
    return(out)
}))
person Sergei    schedule 06.02.2015