Производительность rbind.data.frame

У меня есть список фреймов данных, для которых я уверен, что все они содержат по крайней мере одну строку (на самом деле, некоторые содержат только одну строку, а другие содержат заданное количество строк), и что все они имеют одинаковые столбцы (имена и типов). В случае, если это имеет значение, я также уверен, что в строках нет NA.

Ситуацию можно смоделировать так:

#create one row
onerowdfr<-do.call(data.frame, c(list(), rnorm(100) , lapply(sample(letters[1:2], 100, replace=TRUE), function(x){factor(x, levels=letters[1:2])})))
colnames(onerowdfr)<-c(paste("cnt", 1:100, sep=""), paste("cat", 1:100, sep=""))
#reuse it in a list
someParts<-lapply(rbinom(200, 1, 14/200)*6+1, function(reps){onerowdfr[rep(1, reps),]})

Я установил параметры (рандомизации) так, чтобы они приближались к моей реальной ситуации.

Теперь я хочу объединить все эти кадры в один. Я думал, что использование rbind поможет, вот так:

system.time(
result<-do.call(rbind, someParts)
)

Теперь, в моей системе (которая не особенно медленная) и с указанными выше настройками, это вывод system.time:

   user  system elapsed 
   5.61    0.00    5.62

Почти 6 секунд для повторного связывания 254 (в моем случае) строк из 200 переменных? Конечно, должен быть способ улучшить производительность здесь? В моем коде мне приходится делать подобные вещи очень часто (это является результатом многократного вменения), поэтому мне нужно, чтобы это было как можно быстрее.

6 ответов

Решение

Можете ли вы построить свои матрицы только с числовыми переменными и преобразовать в множитель в конце? rbind намного быстрее на числовых матрицах.

На моей системе, используя фреймы данных:

> system.time(result<-do.call(rbind, someParts))
   user  system elapsed 
  2.628   0.000   2.636 

Вместо этого строим список со всеми числовыми матрицами:

onerowdfr2 <- matrix(as.numeric(onerowdfr), nrow=1)
someParts2<-lapply(rbinom(200, 1, 14/200)*6+1, 
                   function(reps){onerowdfr2[rep(1, reps),]})

результаты намного быстрее rbind,

> system.time(result2<-do.call(rbind, someParts2))
   user  system elapsed 
  0.001   0.000   0.001

РЕДАКТИРОВАТЬ: Вот еще одна возможность; он просто объединяет каждый столбец по очереди.

> system.time({
+   n <- 1:ncol(someParts[[1]])
+   names(n) <- names(someParts[[1]])
+   result <- as.data.frame(lapply(n, function(i) 
+                           unlist(lapply(someParts, `[[`, i))))
+ })
   user  system elapsed 
  0.810   0.000   0.813  

Тем не менее, не так быстро, как с использованием матриц.

РЕДАКТИРОВАТЬ 2:

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

someParts2 <- lapply(someParts, function(x)
                     matrix(unlist(x), ncol=ncol(x)))
result<-as.data.frame(do.call(rbind, someParts2))
a <- someParts[[1]]
f <- which(sapply(a, class)=="factor")
for(i in f) {
  lev <- levels(a[[i]])
  result[[i]] <- factor(as.integer(result[[i]]), levels=seq_along(lev), labels=lev)
}

Время в моей системе:

   user  system elapsed 
   0.090    0.00    0.091 

Не огромный импульс, но обмен rbind за rbind.fill от plyr пакет сбивает около 10% времени работы (с образцом данных на моей машине).

Если вы действительно хотите манипулировать своим data.frameбыстрее, я бы предложил использовать пакет data.table и функция rbindlist(), Я не проводил обширные тесты, но для своего набора данных (3000 кадров данных, 1000 строк х 40 столбцов каждый) rbindlist() занимает всего 20 секунд.

Это примерно на 25% быстрее, но должен быть лучший способ...

system.time({
  N <- do.call(sum, lapply(someParts, nrow))
  SP <- as.data.frame(lapply(someParts[[1]], function(x) rep(x,N)))
  k <- 0
  for(i in 1:length(someParts)) {
    j <- k+1
    k <- k + nrow(someParts[[i]])
    SP[j:k,] <- someParts[[i]]
  }
})

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

Из экокосмического пакета,работает с кусками по 100 кадров данных за раз. По сравнению сэто кажется более эффективным по времени и памяти, чем если вы объединяете список из нескольких сотен кадров данных. При объединении 5000 кадров данных общим размером ~5 ГБ я увидел, что пиковое использование памяти было примерно на 25% меньше.

Другие вопросы по тегам