Быстрое векторизованное слияние списка data.frames по строкам - PullRequest
49 голосов
/ 01 февраля 2011

Большинство вопросов о слиянии data.frame в списках на SO не совсем связаны с тем, что я пытаюсь донести до меня, но вы можете доказать, что я не прав.

У меня есть список data.frames. Я хотел бы "rbind" строк в другой data.frame за строкой. По сути, все первые строки образуют один data.frame, вторые строки - второй data.frame и так далее. Результатом будет список той же длины, что и количество строк в моих исходных данных. Пока что data.frames идентичны по размерам.

Вот некоторые данные, с которыми можно поиграться.

sample.list <- list(data.frame(x = sample(1:100, 10), y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)),
        data.frame(x = sample(1:100, 10), y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)),
        data.frame(x = sample(1:100, 10), y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)),
        data.frame(x = sample(1:100, 10), y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)),
        data.frame(x = sample(1:100, 10), y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)),
        data.frame(x = sample(1:100, 10), y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)),
        data.frame(x = sample(1:100, 10), y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)))

Вот то, что я придумал с хорошим старым циклом.

#solution 1
my.list <- vector("list", nrow(sample.list[[1]]))
for (i in 1:nrow(sample.list[[1]])) {
    for (j in 1:length(sample.list)) {
        my.list[[i]] <- rbind(my.list[[i]], sample.list[[j]][i, ])
    }
}

#solution 2 (so far my favorite)
sample.list2 <- do.call("rbind", sample.list)
my.list2 <- vector("list", nrow(sample.list[[1]]))

for (i in 1:nrow(sample.list[[1]])) {
    my.list2[[i]] <- sample.list2[seq(from = i, to = nrow(sample.list2), by = nrow(sample.list[[1]])), ]
}

Можно ли это улучшить, используя векторизацию без особого вреда для мозга? Правильный ответ, конечно, будет содержать фрагмент кода. «Да» как ответ не считается.

EDIT

#solution 3 (a variant of solution 2 above)
ind <- rep(1:nrow(sample.list[[1]]), times = length(sample.list))
my.list3 <- split(x = sample.list2, f = ind)

СРАВНИТЕЛЬНЫЙ

Я увеличил свой список, добавив больше строк в data.frame. Я сравнил результаты, которые следующие:

#solution 1
system.time(for (i in 1:nrow(sample.list[[1]])) {
    for (j in 1:length(sample.list)) {
        my.list[[i]] <- rbind(my.list[[i]], sample.list[[j]][i, ])
    }
})
   user  system elapsed 
 80.989   0.004  81.210 

# solution 2
system.time(for (i in 1:nrow(sample.list[[1]])) {
    my.list2[[i]] <- sample.list2[seq(from = i, to = nrow(sample.list2), by = nrow(sample.list[[1]])), ]
})
   user  system elapsed 
  0.957   0.160   1.126 

# solution 3
system.time(split(x = sample.list2, f = ind))
   user  system elapsed 
  1.104   0.204   1.332 

# solution Gabor
system.time(lapply(1:nr, bind.ith.rows))
   user  system elapsed 
  0.484   0.000   0.485 

# solution ncray
system.time(alply(do.call("cbind",sample.list), 1,
                .fun=matrix, ncol=ncol(sample.list[[1]]), byrow=TRUE,
                dimnames=list(1:length(sample.list),names(sample.list[[1]]))))
   user  system elapsed 
 11.296   0.016  11.365

Ответы [ 4 ]

46 голосов
/ 01 февраля 2011

Попробуйте это:

bind.ith.rows <- function(i) do.call(rbind, lapply(sample.list, "[", i, TRUE))
nr <- nrow(sample.list[[1]])
lapply(1:nr, bind.ith.rows)
39 голосов
/ 04 октября 2012

Пара решений, которые сделают это быстрее, используя data.table

РЕДАКТИРОВАТЬ - с большим набором данных, показывающим data.table удивительность еще больше.

# here are some sample data 
sample.list <- replicate(10000, data.frame(x = sample(1:100, 10), 
  y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)), simplify = F)

Быстрое решение Габора:

# Solution Gabor
bind.ith.rows <- function(i) do.call(rbind, lapply(sample.list, "[", i, TRUE))
nr <- nrow(sample.list[[1]])
system.time(rowbound <- lapply(1:nr, bind.ith.rows))

##    user  system elapsed 
##   25.87    0.01   25.92 

Функция data.table rbindlist сделает это даже быстрее даже при работе с data.frames)

library(data.table)
fastbind.ith.rows <- function(i) rbindlist(lapply(sample.list, "[", i, TRUE))
system.time(fastbound <- lapply(1:nr, fastbind.ith.rows))

##    user  system elapsed 
##   13.89    0.00   13.89 

A data.table раствор

Вот решение, которое использует data.tables - это split решение для стероидов.

# data.table solution
system.time({
    # change each element of sample.list to a data.table (and data.frame) this
    # is done instaneously by reference
    invisible(lapply(sample.list, setattr, name = "class", 
               value = c("data.table",  "data.frame")))
    # combine into a big data set
    bigdata <- rbindlist(sample.list)
    # add a row index column (by refere3nce)
    index <- as.character(seq_len(nr))
    bigdata[, `:=`(rowid, index)]
    # set the key for binary searches
    setkey(bigdata, rowid)
    # split on this -
    dt_list <- lapply(index, function(i, j, x) x[i = J(i)], x = bigdata)
    # if you want to drop the `row id` column
    invisible(lapply(dt_list, function(x) set(x, j = "rowid", value = NULL)))
    # if you really don't want them to be data.tables run this line
    # invisible(lapply(dt_list, setattr,name = 'class', value =
    # c('data.frame')))
})
################################
##    user  system elapsed    ##
##    0.08    0.00    0.08    ##
################################

Как круто data.table!

Caveat user with rbindlist

rbindlist работает быстро, потому что он не выполняет проверку, которая будет do.call(rbind,....). Например, предполагается, что столбцы фактора имеют те же уровни, что и в первом элементе списка.

5 голосов
/ 01 февраля 2011

Вот моя попытка с plyr, но мне нравится подход Г. Гротендика:

library(plyr)
alply(do.call("cbind",sample.list), 1, .fun=matrix,
        ncol=ncol(sample.list[[1]]), byrow=TRUE,
        dimnames=list(1:length(sample.list),
        names(sample.list[[1]])
      ))
0 голосов
/ 30 мая 2019

addind tidyverse решение:

library(tidyverse)
bind_rows(sample.list)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...