Удивительно и интересно, рассмотрим by
(объектно-ориентированную оболочку для tapply
), которая работает аналогично split
для фреймов данных с добавленной функцией запуска, разделяется на вызов функции. Эквивалентом split
будет возвращение аргумента или вызов identity
.
by(x$Item, x$ID, function(x) x)
by(x$Item, x$ID, identity)
Обратите внимание, возвращаемое значение by
- это объект класса by
, который, по сути, представляет собой список с дополнительными атрибутами.
Используя ваш пример случайного фрейма данных, base::split
не завершился через 1 час, но base::by
показала намного меньше 5 минут на моей машине с 64 ГБ ОЗУ! Обычно я предполагал, что у by
будет больше накладных расходов, если он будет родственником заявленной семьи, но мое мнение может скоро измениться.
50K ROW ПРИМЕР
set.seed(123)
n = 50000 #number of sample data (50k as trial)
x <- data.frame(ID = paste(LETTERS[1:8],sample(1:round(n/3), n, replace = TRUE),sep = ""),
Item= sample(c('apple','orange','lemon','tea','rice'), n, replace=TRUE)
)
system.time( xx <- split(x$Item, x$ID) )
# user system elapsed
# 20.09 0.00 20.09
system.time( xx2 <- by(x$Item, x$ID, identity) )
# user system elapsed
# 1.55 0.00 1.55
all.equal(unlist(xx), unlist(xx2))
# [1] TRUE
identical(unlist(xx), unlist(xx2))
# [1] TRUE
500K ROW ПРИМЕР
set.seed(123)
n = 500000 #number of sample data (500k as trial)
x <- data.frame(ID = paste(LETTERS[1:8],sample(1:round(n/3), n, replace = TRUE),sep = ""),
Item= sample(c('apple','orange','lemon','tea','rice'), n, replace=TRUE)
)
system.time( xx <- split(x$Item, x$ID) )
# DID NOT FINISH AFTER 1 HOUR
system.time( xx2 <- by(x$Item, x$ID, identity) )
# user system elapsed
# 23.00 0.06 23.09
Исходный код показывает, что split.default
может запускать больше процессов на уровне R (в отличие от C или Fortran) с циклом for
с коэффициентом levels
:
getAnywhere(split.data.frame)
function (x, f, drop = FALSE, sep = ".", lex.order = FALSE, ...)
{
if (!missing(...))
.NotYetUsed(deparse(...), error = FALSE)
if (is.list(f))
f <- interaction(f, drop = drop, sep = sep, lex.order = lex.order)
else if (!is.factor(f))
f <- as.factor(f)
else if (drop)
f <- factor(f)
storage.mode(f) <- "integer"
if (is.null(attr(x, "class")))
return(.Internal(split(x, f)))
lf <- levels(f)
y <- vector("list", length(lf))
names(y) <- lf
ind <- .Internal(split(seq_along(x), f))
for (k in lf) y[[k]] <- x[ind[[k]]]
y
}
И наоборот, исходный код для by.data.frame
показывает вызов tapply
, который сам по себе является оберткой для lapply
:
getAnywhere(by.data.frame)
function (data, INDICES, FUN, ..., simplify = TRUE)
{
if (!is.list(INDICES)) {
IND <- vector("list", 1L)
IND[[1L]] <- INDICES
names(IND) <- deparse(substitute(INDICES))[1L]
}
else IND <- INDICES
FUNx <- function(x) FUN(data[x, , drop = FALSE], ...)
nd <- nrow(data)
structure(eval(substitute(tapply(seq_len(nd), IND, FUNx,
simplify = simplify)), data), call = match.call(), class = "by")
}