Повторяющаяся аналитическая парадигма, с которой я встречаюсь в своем исследовании, является потребностью к подмножеству на основе всех значений идентификаторов другой группы, выполняя статистический анализ каждой группы в свою очередь, и помещая результаты в выходную матрицу для последующей обработки / суммирование.
Как я обычно делаю это в R - что-то как следующее:
data.mat <- read.csv("...")
groupids <- unique(data.mat$ID) #Assume there are then 100 unique groups
results <- matrix(rep("NA",300),ncol=3,nrow=100)
for(i in 1:100) {
tempmat <- subset(data.mat,ID==groupids[i])
# Run various stats on tempmat (correlations, regressions, etc), checking to
# make sure this specific group doesn't have NAs in the variables I'm using
# and assign results to x, y, and z, for example.
results[i,1] <- x
results[i,2] <- y
results[i,3] <- z
}
Это заканчивает тем, что работало на меня, но в зависимости от размера данных и количества групп я работаю с, это может занять до трех дней.
Помимо выделения в параллельную обработку, там какой-либо "прием" для того, чтобы заставить что-то вроде этого работать быстрее? Например, преобразовывая циклы во что-то еще (что-то как применение с функцией, содержащей статистику, я хочу работать в цикле), или избавление от необходимости на самом деле присвоить подмножество данных к переменной?
Возможно, это - просто общепринятая истина (или ошибка выборки), но я попробовал подмножество скобками в части моего кода вместо того, чтобы использовать команду подмножества, и это, казалось, обеспечило небольшое увеличение производительности, которое удивило меня. У меня есть некоторый код, который я использовал и производил ниже использования тех же имен объектов как выше:
system.time(for(i in 1:1000){data.mat[data.mat$ID==groupids[i],]})
user system elapsed 361.41 92.62 458.32
system.time(for(i in 1:1000){subset(data.mat,ID==groupids[i])})
user system elapsed 378.44 102.03 485.94
В одном из ответов jorgusch предложил, чтобы я использовал data.table пакет для ускорения моего подмножества. Так, я применил его к проблеме, которую я выполнил ранее на этой неделе. В наборе данных с немногим более, чем 1 500 000 строк, и 4 столбца (идентификатор, Var1, Var2, Var3), я хотел вычислить две корреляции в каждой группе (индексированный "идентификационной" переменной). Существует немного больше чем 50 000 групп. Ниже мой первоначальный код (который очень похож на вышеупомянутое):
data.mat <- read.csv("//home....")
groupids <- unique(data.mat$ID)
results <- matrix(rep("NA",(length(groupids) * 3)),ncol=3,nrow=length(groupids))
for(i in 1:length(groupids)) {
tempmat <- data.mat[data.mat$ID==groupids[i],]
results[i,1] <- groupids[i]
results[i,2] <- cor(tempmat$Var1,tempmat$Var2,use="pairwise.complete.obs")
results[i,3] <- cor(tempmat$Var1,tempmat$Var3,use="pairwise.complete.obs")
}
Я повторно выполняю это прямо сейчас для точной меры того, сколько времени это взяло, но от того, что я помню, я запустил ее выполнение, когда я вошел в офис утром, и она закончилась когда-то во второй половине дня. Часы рисунка 5-7.
Реструктуризация моего кода для использования data.table....
data.mat <- read.csv("//home....")
data.mat <- data.table(data.mat)
testfunc <- function(x,y,z) {
temp1 <- cor(x,y,use="pairwise.complete.obs")
temp2 <- cor(x,z,use="pairwise.complete.obs")
res <- list(temp1,temp2)
res
}
system.time(test <- data.mat[,testfunc(Var1,Var2,Var3),by="ID"])
user system elapsed 16.41 0.05 17.44
При сравнении результатов с помощью data.table тем, которых я получил от использования для цикла к подмножеству, все идентификаторы и запись заканчиваются вручную, они, кажется, дали мне те же ответы (хотя я должен буду проверить это тщательнее). Это надеется быть довольно большим увеличением скорости.
Выполнение кода с помощью подмножеств, наконец законченных снова:
user system elapsed 17575.79 4247.41 23477.00
Я хотел видеть, разработало ли что-нибудь другое использование plyr пакета, который также рекомендовался. Это - мой первый раз с помощью него, таким образом, я, возможно, сделал вещи несколько неэффективно, но это все еще помогло существенно по сравнению с для цикла с подмножеством.
Используя те же переменные и установку как прежде...
data.mat <- read.csv("//home....")
system.time(hmm <- ddply(data.mat,"ID",function(df)c(cor(df$Var1,df$Var2, use="pairwise.complete.obs"),cor(df$Var1,df$Var3,use="pairwise.complete.obs"))))
user system elapsed 250.25 7.35 272.09
Это в значительной степени именно то, что пакет plyr
призван упростить работу. Однако маловероятно, что это сделает вещи намного быстрее - большая часть времени, вероятно, тратится на статистику.
Вы уже предлагали векторизовать и избегать ненужных копий промежуточных результатов, так что вы, безусловно, на правильном пути. Позвольте мне предостеречь вас от того, что делал я, и просто предположим , что векторизация всегда даст вам повышение производительности (как это происходит на других языках, например, Python + NumPy, MATLAB). .
Пример:
# small function to time the results:
time_this = function(...) {
start.time = Sys.time(); eval(..., sys.frame(sys.parent(sys.parent())));
end.time = Sys.time(); print(end.time - start.time)
}
# data for testing: a 10000 x 1000 matrix of random doubles
a = matrix(rnorm(1e7, mean=5, sd=2), nrow=10000)
# two versions doing the same thing: calculating the mean for each row
# in the matrix
x = time_this( for (i in 1:nrow(a)){ mean( a[i,] ) } )
y = time_this( apply(X=a, MARGIN=1, FUN=mean) )
print(x) # returns => 0.5312099
print(y) # returns => 0.661242
Версия «применить» на самом деле медленнее , чем версия «для». (По словам автора Inferno , если вы делаете это, вы не векторизуете, вы «скрываете петли».)
Но вы можете повысить производительность, используя встроенную ins . Ниже я рассчитал ту же операцию, что и две выше, просто используя встроенную функцию rowMeans:
z = time_this(rowMeans(a))
print(z) # returns => 0.03679609
Улучшение на порядок по сравнению с циклом for (и векторизованной версией).
Другие члены семейства apply - не просто оболочки над собственным циклом for.
a = abs(floor(10*rnorm(1e6)))
time_this(sapply(a, sqrt))
# returns => 6.64 secs
time_this(for (i in 1:length(a)){ sqrt(a[i])})
# returns => 1.33 secs
sapply примерно на 5x медленнее по сравнению с циклом for.
Наконец, w / r / t циклы с векторизацией по сравнению с циклами for, я не думаю, что когда-либо буду использовать цикл, если я могу использовать векторизованную функцию - последнее обычно требует меньшего количества нажатий клавиш и это более естественный способ ( для меня) в код, который, я полагаю, является другим видом повышения производительности.
Помимо plyr
, вы можете попробовать использовать пакет foreach
для исключения явного счетчика циклов, но я не Я не знаю, даст ли это вам какие-либо преимущества в производительности.
Foreach
, всегда, дает вам довольно простой интерфейс для параллельной обработки фрагментов, если у вас многоядерная рабочая станция (с doMC
/ многоядерными
пакетами) (проверьте Получение Начнем с doMC и foreach для подробностей), если вы исключите параллельную обработку только потому, что она не очень проста для понимания учащимися. Если это не единственная причина, то plyr
очень хорошее решение ИМХО.
Лично я нахожу plyr не очень простым для понимания. Я предпочитаю data.table, который также быстрее. Например, вы хотите сделать стандартное отклонение столбца my_column для каждого ID.
dt <- datab.table[df] # one time operation...changing format of df to table
result.sd <- dt[,sd(my_column),by="ID"] # result with each ID and SD in second column
Три таких оператора и cbind в конце - это все, что вам нужно. Вы также можете использовать dt do some action только для одного ID без команды subset в новом синтаксисе:
result.sd.oneiD<- dt[ID="oneID",sd(my_column)]
Первое утверждение относится к строкам (i), второе - к столбцам (j).
Если вы считаете, что это легче читать, чем player, и это более гибко, так как вы также можете делать поддомены внутри "подмножества"... В документации описано, что он использует SQL-подобные методы. Например, by - это практически "group by" в SQL. Ну, если вы знаете SQL, вы, вероятно, можете сделать гораздо больше, но это не обязательно для использования пакета. Наконец, он чрезвычайно быстр, поскольку каждая операция не только выполняется параллельно, но и data.table захватывает данные, необходимые для вычисления. Subset, однако, сохраняет уровни всей матрицы и перетаскивает ее через память.