К чему с более высокими характеристиками альтернативно для циклов для данных подмножества идентификатором группы?

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

Как я обычно делаю это в 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 тем, которых я получил от использования для цикла к подмножеству, все идентификаторы и запись заканчиваются вручную, они, кажется, дали мне те же ответы (хотя я должен буду проверить это тщательнее). Это надеется быть довольно большим увеличением скорости.

Обновление 2:

Выполнение кода с помощью подмножеств, наконец законченных снова:

   user     system   elapsed  
17575.79  4247.41   23477.00

Обновление 3:

Я хотел видеть, разработало ли что-нибудь другое использование 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  
7
задан smci 29 May 2018 в 10:06
поделиться

4 ответа

Это в значительной степени именно то, что пакет plyr призван упростить работу. Однако маловероятно, что это сделает вещи намного быстрее - большая часть времени, вероятно, тратится на статистику.

6
ответ дан 6 December 2019 в 19:35
поделиться

Вы уже предлагали векторизовать и избегать ненужных копий промежуточных результатов, так что вы, безусловно, на правильном пути. Позвольте мне предостеречь вас от того, что делал я, и просто предположим , что векторизация всегда даст вам повышение производительности (как это происходит на других языках, например, 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, я не думаю, что когда-либо буду использовать цикл, если я могу использовать векторизованную функцию - последнее обычно требует меньшего количества нажатий клавиш и это более естественный способ ( для меня) в код, который, я полагаю, является другим видом повышения производительности.

2
ответ дан 6 December 2019 в 19:35
поделиться

Помимо plyr , вы можете попробовать использовать пакет foreach для исключения явного счетчика циклов, но я не Я не знаю, даст ли это вам какие-либо преимущества в производительности.

Foreach , всегда, дает вам довольно простой интерфейс для параллельной обработки фрагментов, если у вас многоядерная рабочая станция (с doMC / многоядерными пакетами) (проверьте Получение Начнем с doMC и foreach для подробностей), если вы исключите параллельную обработку только потому, что она не очень проста для понимания учащимися. Если это не единственная причина, то plyr очень хорошее решение ИМХО.

3
ответ дан 6 December 2019 в 19:35
поделиться

Лично я нахожу 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, однако, сохраняет уровни всей матрицы и перетаскивает ее через память.

2
ответ дан 6 December 2019 в 19:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: