Функции группировки (tapply, by, aggregate) и семейство *apply
Всякий раз, когда я хочу сделать что-то "сопоставить" py в R, я обычно пытаюсь использовать функцию в apply
семьи.
Однако я никогда не понимал различий между ними - как {sapply
, lapply
и т. д.} примените функцию к вводу / групповому вводу, как будет выглядеть вывод, или даже к тому, каким может быть ввод - поэтому я часто просто просматриваю их все, пока не получу то, что хочу.
Может кто-нибудь объяснить, как использовать какой, когда?
Мое текущее (вероятно, неправильное / неполное) понимание...
sapply(vec, f)
: вход является вектором. вывод представляет собой вектор / матрицу, где элементi
являетсяf(vec[i])
, давая вам матрицу, еслиf
имеет многоэлементный выходlapply(vec, f)
: такой же какsapply
, но вывод это список?apply(matrix, 1/2, f)
: вход является матрицей. выходной вектор, где элементi
это f(строка / столбец матрицы)tapply(vector, grouping, f)
: output - это матрица / массив, где элемент в матрице / массиве - это значениеf
в группировкеg
вектора иg
толкается к именам строк / столбцовby(dataframe, grouping, f)
: позволятьg
быть группировкой. применятьf
для каждого столбца группы / dataframe. довольно распечатать группировку и значениеf
в каждом столбце.aggregate(matrix, grouping, f)
: похожий наby
, но вместо того, чтобы печатать вывод, агрегат вставляет все в информационный фрейм.
Дополнительный вопрос: я до сих пор не научился plyr или изменить форму plyr
или же reshape
заменить все это полностью?
12 ответов
R имеет много * применяет функции, которые умело описаны в файлах справки (например, ?apply
). Однако их достаточно, чтобы начинающим пользователям было трудно решить, какой из них подходит для их ситуации, или даже запомнить их все. У них может быть общее чувство, что "я должен использовать здесь функцию *apply", но поначалу может быть сложно держать их все прямо.
Несмотря на тот факт (отмечено в других ответах), что большая часть функциональности семейства *apply покрыта чрезвычайно популярной plyr
пакет, базовые функции остаются полезными и стоит знать.
Этот ответ призван служить своего рода указателем для новых пользователей, чтобы помочь им направить их к правильной функции *apply для их конкретной проблемы. Обратите внимание, это не предназначено просто для регургитации или замены документации R! Надежда состоит в том, что этот ответ поможет вам решить, какая * применимая функция подходит для вашей ситуации, а затем вам предстоит изучить ее дальше. За одним исключением, различия в производительности не будут устранены.
apply - применять, когда вы хотите применить функцию к строкам или столбцам матрицы (и многомерных аналогов); как правило, не рекомендуется для фреймов данных, так как он сначала будет приведен к матрице.
# Two dimensional matrix M <- matrix(seq(1,16), 4, 4) # apply min to rows apply(M, 1, min) [1] 1 2 3 4 # apply max to columns apply(M, 2, max) [1] 4 8 12 16 # 3 dimensional array M <- array( seq(32), dim = c(4,4,2)) # Apply sum across each M[*, , ] - i.e Sum across 2nd and 3rd dimension apply(M, 1, sum) # Result is one-dimensional [1] 120 128 136 144 # Apply sum across each M[*, *, ] - i.e Sum across 3rd dimension apply(M, c(1,2), sum) # Result is two-dimensional [,1] [,2] [,3] [,4] [1,] 18 26 34 42 [2,] 20 28 36 44 [3,] 22 30 38 46 [4,] 24 32 40 48
Если вы хотите, чтобы сумма / сумма столбца или столбца для двумерной матрицы была, изучите высоко оптимизированный, молниеносный
colMeans
,rowMeans
,colSums
,rowSums
,lapply - когда вы хотите применить функцию к каждому элементу списка по очереди и получить список обратно.
Это рабочая лошадка многих других * применимых функций. Отогните их код, и вы часто найдете
lapply
под.x <- list(a = 1, b = 1:3, c = 10:100) lapply(x, FUN = length) $a [1] 1 $b [1] 3 $c [1] 91 lapply(x, FUN = sum) $a [1] 1 $b [1] 6 $c [1] 5005
sapply - когда вы хотите применить функцию к каждому элементу списка по очереди, но вы хотите вернуть вектор, а не список.
Если вы обнаружите, что печатаете
unlist(lapply(...))
остановись и посмотриsapply
,x <- list(a = 1, b = 1:3, c = 10:100) # Compare with above; a named vector, not a list sapply(x, FUN = length) a b c 1 3 91 sapply(x, FUN = sum) a b c 1 6 5005
В более продвинутых использованиях
sapply
при необходимости он попытается привести результат к многомерному массиву. Например, если наша функция возвращает векторы одинаковой длины,sapply
будем использовать их как столбцы матрицы:sapply(1:5,function(x) rnorm(3,x))
Если наша функция возвращает 2-мерную матрицу,
sapply
будет делать то же самое, обрабатывая каждую возвращенную матрицу как один длинный вектор:sapply(1:5,function(x) matrix(x,2,2))
Если мы не укажем
simplify = "array"
, в этом случае он будет использовать отдельные матрицы для построения многомерного массива:sapply(1:5,function(x) matrix(x,2,2), simplify = "array")
Каждое из этих поведений, конечно, зависит от нашей функции, возвращающей векторы или матрицы одинаковой длины или размера.
vapply - когда вы хотите использовать
sapply
но, возможно, нужно выжать еще немного скорости из вашего кода.За
vapply
в основном вы даете R пример того, что будет возвращать ваша функция, что может сэкономить некоторое время, приводя возвращаемые значения к размеру одного атомарного вектора.x <- list(a = 1, b = 1:3, c = 10:100) #Note that since the advantage here is mainly speed, this # example is only for illustration. We're telling R that # everything returned by length() should be an integer of # length 1. vapply(x, FUN = length, FUN.VALUE = 0L) a b c 1 3 91
mapply - для случаев, когда у вас есть несколько структур данных (например, векторы, списки), и вы хотите применить функцию к первым элементам каждого, а затем ко вторым элементам каждого и т. д., приведя результат к вектору / массиву, как в
sapply
,Это многовариантно в том смысле, что ваша функция должна принимать несколько аргументов.
#Sums the 1st elements, the 2nd elements, etc. mapply(sum, 1:5, 1:5, 1:5) [1] 3 6 9 12 15 #To do rep(1,4), rep(2,3), etc. mapply(rep, 1:4, 4:1) [[1]] [1] 1 1 1 1 [[2]] [1] 2 2 2 [[3]] [1] 3 3 [[4]] [1] 4
Карта - Обертка для
mapply
сSIMPLIFY = FALSE
, так что гарантированно вернуть список.Map(sum, 1:5, 1:5, 1:5) [[1]] [1] 3 [[2]] [1] 6 [[3]] [1] 9 [[4]] [1] 12 [[5]] [1] 15
rapply - для случаев, когда вы хотите применить функцию к каждому элементу структуры вложенного списка, рекурсивно.
Чтобы дать вам некоторое представление о том, как необычно
rapply
Я забыл об этом, когда впервые опубликовал этот ответ! Очевидно, я уверен, что многие люди используют его, но YMMV.rapply
лучше всего иллюстрируется пользовательской функцией для применения:# Append ! to string, otherwise increment myFun <- function(x){ if(is.character(x)){ return(paste(x,"!",sep="")) } else{ return(x + 1) } } #A nested list structure l <- list(a = list(a1 = "Boo", b1 = 2, c1 = "Eeek"), b = 3, c = "Yikes", d = list(a2 = 1, b2 = list(a3 = "Hey", b3 = 5))) # Result is named vector, coerced to character rapply(l, myFun) # Result is a nested list like l, with values altered rapply(l, myFun, how="replace")
tapply - для случаев, когда вы хотите применить функцию к подмножествам вектора, а подмножества определяются другим вектором, обычно фактором.
Черная овца семейства * применяет, родов. Использование в файле справки фразы "рваный массив" может быть немного запутанным, но на самом деле это довольно просто.
Вектор:
x <- 1:20
Фактор (одинаковой длины!), Определяющий группы:
y <- factor(rep(letters[1:5], each = 4))
Сложите значения в
x
в каждой подгруппе, определеннойy
:tapply(x, y, sum) a b c d e 10 26 42 58 74
Более сложные примеры могут быть обработаны, когда подгруппы определяются уникальными комбинациями списка из нескольких факторов.
tapply
по духу похожа на функции split-apply-объединить, которые распространены в R (aggregate
,by
,ave
,ddply
и т. д.) Отсюда и его статус овец.
На примечании стороны, вот как различные plyr
функции соответствуют основанию *apply
функции (от вступления до документа plyr с веб-страницы plyr http://had.co.nz/plyr/)
Base function Input Output plyr function
---------------------------------------
aggregate d d ddply + colwise
apply a a/l aaply / alply
by d l dlply
lapply l l llply
mapply a a/l maply / mlply
replicate r a/l raply / rlply
sapply l a laply
Одна из целей plyr
заключается в обеспечении согласованных соглашений об именах для каждой из функций, кодирующих входные и выходные типы данных в имени функции. Это также обеспечивает последовательность в выводе, в этом выводе от dlply()
легко проходим ldply()
производить полезную продукцию и т. д.
Концептуально, обучение plyr
не сложнее, чем понять основание *apply
функции.
plyr
а также reshape
функции заменили почти все эти функции в моем повседневном использовании. Но также из вступления к документу Plyr:
Связанные функции
tapply
а такжеsweep
не имеют соответствующей функции вplyr
и остаются полезными.merge
полезно для объединения резюме с исходными данными.
Из слайда 21 http://www.slideshare.net/hadley/plyr-one-data-analytic-strategy:
(Надеюсь, понятно, что apply
соответствует @ Хэдли aaply
а также aggregate
соответствует @ Хэдли ddply
и т.д. Слайд 20 того же слайдшера прояснит, если вы не получили его с этого изображения.)
(слева ввод, сверху вывод)
Сначала начните с отличного ответа Джорана - сомнительно, что все может быть лучше.
Тогда следующая мнемоника может помочь вспомнить различия между ними. В то время как некоторые из них очевидны, другие могут быть не так - для них вы найдете оправдание в обсуждениях Джорана.
мнемоника
lapply
это применение списка, которое действует на список или вектор и возвращает список.sapply
это простойlapply
(функция по умолчанию возвращает вектор или матрицу, когда это возможно)vapply
является проверенным применением (позволяет предварительно указать тип возвращаемого объекта)rapply
является рекурсивным применением для вложенных списков, то есть списков в спискахtapply
является тегом применить, где теги идентифицируют подмножестваapply
является универсальным: применяет функцию к строкам или столбцам матрицы (или, в более общем случае, к измерениям массива)
Создание правильного фона
При использовании apply
семья все еще чувствует себя немного чуждой вам, возможно, вам не хватает ключевой точки зрения.
Эти две статьи могут помочь. Они обеспечивают необходимую основу для мотивации методов функционального программирования, которые предоставляются apply
семейство функций.
Пользователи Lisp сразу распознают эту парадигму. Если вы не знакомы с Lisp, то, как только вы разберетесь с FP, вы получите мощную точку зрения для использования в R - и apply
будет иметь больше смысла.
- Advanced R: функциональное программирование, Хэдли Уикхем
- Простое функциональное программирование на R, Майкл Бартон
Поскольку я понял, что (очень отличные) ответы на этот пост отсутствие by
а также aggregate
объяснения. Вот мой вклад.
ОТ
by
Функция, как указано в документации, может быть, как "обертка" для tapply
, Сила by
возникает, когда мы хотим вычислить задачу, которая tapply
не могу справиться Одним из примеров является этот код:
ct <- tapply(iris$Sepal.Width , iris$Species , summary )
cb <- by(iris$Sepal.Width , iris$Species , summary )
cb
iris$Species: setosa
Min. 1st Qu. Median Mean 3rd Qu. Max.
2.300 3.200 3.400 3.428 3.675 4.400
--------------------------------------------------------------
iris$Species: versicolor
Min. 1st Qu. Median Mean 3rd Qu. Max.
2.000 2.525 2.800 2.770 3.000 3.400
--------------------------------------------------------------
iris$Species: virginica
Min. 1st Qu. Median Mean 3rd Qu. Max.
2.200 2.800 3.000 2.974 3.175 3.800
ct
$setosa
Min. 1st Qu. Median Mean 3rd Qu. Max.
2.300 3.200 3.400 3.428 3.675 4.400
$versicolor
Min. 1st Qu. Median Mean 3rd Qu. Max.
2.000 2.525 2.800 2.770 3.000 3.400
$virginica
Min. 1st Qu. Median Mean 3rd Qu. Max.
2.200 2.800 3.000 2.974 3.175 3.800
Если мы напечатаем эти два объекта, ct
а также cb
у нас "по сути" есть одинаковые результаты, и единственные различия заключаются в том, как они показаны и разные class
атрибуты соответственно by
за cb
а также array
за ct
,
Как я уже сказал, сила by
возникает, когда мы не можем использовать tapply
; следующий код является одним из примеров:
tapply(iris, iris$Species, summary )
Error in tapply(iris, iris$Species, summary) :
arguments must have same length
R говорит, что аргументы должны иметь одинаковую длину, скажем "мы хотим вычислить summary
всей переменной в iris
вдоль фактора Species
": но R просто не может этого сделать, потому что не знает, как с этим справиться.
С by
Функция R отправляет конкретный метод для data frame
класс, а затем пусть summary
Функция работает, даже если длина первого аргумента (и тип тоже) отличаются.
bywork <- by(iris, iris$Species, summary )
bywork
iris$Species: setosa
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
Min. :4.300 Min. :2.300 Min. :1.000 Min. :0.100 setosa :50
1st Qu.:4.800 1st Qu.:3.200 1st Qu.:1.400 1st Qu.:0.200 versicolor: 0
Median :5.000 Median :3.400 Median :1.500 Median :0.200 virginica : 0
Mean :5.006 Mean :3.428 Mean :1.462 Mean :0.246
3rd Qu.:5.200 3rd Qu.:3.675 3rd Qu.:1.575 3rd Qu.:0.300
Max. :5.800 Max. :4.400 Max. :1.900 Max. :0.600
--------------------------------------------------------------
iris$Species: versicolor
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
Min. :4.900 Min. :2.000 Min. :3.00 Min. :1.000 setosa : 0
1st Qu.:5.600 1st Qu.:2.525 1st Qu.:4.00 1st Qu.:1.200 versicolor:50
Median :5.900 Median :2.800 Median :4.35 Median :1.300 virginica : 0
Mean :5.936 Mean :2.770 Mean :4.26 Mean :1.326
3rd Qu.:6.300 3rd Qu.:3.000 3rd Qu.:4.60 3rd Qu.:1.500
Max. :7.000 Max. :3.400 Max. :5.10 Max. :1.800
--------------------------------------------------------------
iris$Species: virginica
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
Min. :4.900 Min. :2.200 Min. :4.500 Min. :1.400 setosa : 0
1st Qu.:6.225 1st Qu.:2.800 1st Qu.:5.100 1st Qu.:1.800 versicolor: 0
Median :6.500 Median :3.000 Median :5.550 Median :2.000 virginica :50
Mean :6.588 Mean :2.974 Mean :5.552 Mean :2.026
3rd Qu.:6.900 3rd Qu.:3.175 3rd Qu.:5.875 3rd Qu.:2.300
Max. :7.900 Max. :3.800 Max. :6.900 Max. :2.500
это действительно работает, и результат очень удивителен. Это объект класса by
что вдоль Species
(скажем, для каждого из них) вычисляет summary
каждой переменной.
Обратите внимание, что если первый аргумент data frame
отправленная функция должна иметь метод для этого класса объектов. Например, мы используем этот код с mean
функция у нас будет этот код, который вообще не имеет смысла:
by(iris, iris$Species, mean)
iris$Species: setosa
[1] NA
-------------------------------------------
iris$Species: versicolor
[1] NA
-------------------------------------------
iris$Species: virginica
[1] NA
Warning messages:
1: In mean.default(data[x, , drop = FALSE], ...) :
argument is not numeric or logical: returning NA
2: In mean.default(data[x, , drop = FALSE], ...) :
argument is not numeric or logical: returning NA
3: In mean.default(data[x, , drop = FALSE], ...) :
argument is not numeric or logical: returning NA
ОБЩИЙ
aggregate
можно рассматривать как еще один другой способ использования tapply
если мы будем использовать его таким образом.
at <- tapply(iris$Sepal.Length , iris$Species , mean)
ag <- aggregate(iris$Sepal.Length , list(iris$Species), mean)
at
setosa versicolor virginica
5.006 5.936 6.588
ag
Group.1 x
1 setosa 5.006
2 versicolor 5.936
3 virginica 6.588
Два непосредственных различия в том, что второй аргумент aggregate
должен быть список в то время как tapply
может (не обязательно) быть списком и что вывод aggregate
является фреймом данных, в то время как один из tapply
является array
,
Сила aggregate
является то, что он может легко обрабатывать подмножества данных с subset
аргумент и что у него есть методы для ts
объекты и formula
также.
Эти элементы делают aggregate
с этим легче работать tapply
в некоторых ситуациях. Вот несколько примеров (доступно в документации):
ag <- aggregate(len ~ ., data = ToothGrowth, mean)
ag
supp dose len
1 OJ 0.5 13.23
2 VC 0.5 7.98
3 OJ 1.0 22.70
4 VC 1.0 16.77
5 OJ 2.0 26.06
6 VC 2.0 26.14
Мы можем добиться того же с tapply
но синтаксис немного сложнее, а вывод (в некоторых случаях) менее читабелен:
att <- tapply(ToothGrowth$len, list(ToothGrowth$dose, ToothGrowth$supp), mean)
att
OJ VC
0.5 13.23 7.98
1 22.70 16.77
2 26.06 26.14
Есть другие времена, когда мы не можем использовать by
или же tapply
и мы должны использовать aggregate
,
ag1 <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, mean)
ag1
Month Ozone Temp
1 5 23.61538 66.73077
2 6 29.44444 78.22222
3 7 59.11538 83.88462
4 8 59.96154 83.96154
5 9 31.44828 76.89655
Мы не можем получить предыдущий результат с tapply
в одном вызове, но мы должны рассчитать среднее по Month
для каждого элемента, а затем объединить их (также обратите внимание, что мы должны вызвать na.rm = TRUE
, поскольку formula
методы aggregate
функция по умолчанию имеет na.action = na.omit
):
ta1 <- tapply(airquality$Ozone, airquality$Month, mean, na.rm = TRUE)
ta2 <- tapply(airquality$Temp, airquality$Month, mean, na.rm = TRUE)
cbind(ta1, ta2)
ta1 ta2
5 23.61538 65.54839
6 29.44444 79.10000
7 59.11538 83.90323
8 59.96154 83.96774
9 31.44828 76.90000
в то время как с by
мы просто не можем этого добиться, на самом деле следующий вызов функции возвращает ошибку (но, скорее всего, это связано с предоставленной функцией, mean
):
by(airquality[c("Ozone", "Temp")], airquality$Month, mean, na.rm = TRUE)
В других случаях результаты совпадают, и различия заключаются только в объекте класса (а затем в том, как он показан / напечатан, а не только в примере, например, как его установить):
byagg <- by(airquality[c("Ozone", "Temp")], airquality$Month, summary)
aggagg <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, summary)
Предыдущий код достигает той же цели и результатов, в какой-то момент, какой инструмент использовать, зависит только от личных вкусов и потребностей; предыдущие два объекта имеют очень разные потребности с точки зрения поднабора.
Есть много отличных ответов, которые обсуждают различия в вариантах использования для каждой функции. Ни один из ответов не обсуждает различия в производительности. Это разумно, потому что различные функции ожидают различного ввода и производят различный вывод, но большинство из них имеют общую общую цель - оценивать по сериям / группам. Мой ответ будет сосредоточен на производительности. Из-за вышеизложенного, создание входов из векторов включено в синхронизацию, а также apply
функция не измеряется.
Я проверил две разные функции sum
а также length
однажды. Протестированный объем составляет 50 М на входе и 50 К на выходе. Я также включил два популярных в настоящее время пакета, которые не были широко использованы в то время, когда задавался вопрос, data.table
а также dplyr
, И то, и другое определенно стоит посмотреть, если вы стремитесь к хорошей производительности.
library(dplyr)
library(data.table)
set.seed(123)
n = 5e7
k = 5e5
x = runif(n)
grp = sample(k, n, TRUE)
timing = list()
# sapply
timing[["sapply"]] = system.time({
lt = split(x, grp)
r.sapply = sapply(lt, function(x) list(sum(x), length(x)), simplify = FALSE)
})
# lapply
timing[["lapply"]] = system.time({
lt = split(x, grp)
r.lapply = lapply(lt, function(x) list(sum(x), length(x)))
})
# tapply
timing[["tapply"]] = system.time(
r.tapply <- tapply(x, list(grp), function(x) list(sum(x), length(x)))
)
# by
timing[["by"]] = system.time(
r.by <- by(x, list(grp), function(x) list(sum(x), length(x)), simplify = FALSE)
)
# aggregate
timing[["aggregate"]] = system.time(
r.aggregate <- aggregate(x, list(grp), function(x) list(sum(x), length(x)), simplify = FALSE)
)
# dplyr
timing[["dplyr"]] = system.time({
df = data_frame(x, grp)
r.dplyr = summarise(group_by(df, grp), sum(x), n())
})
# data.table
timing[["data.table"]] = system.time({
dt = setnames(setDT(list(x, grp)), c("x","grp"))
r.data.table = dt[, .(sum(x), .N), grp]
})
# all output size match to group count
sapply(list(sapply=r.sapply, lapply=r.lapply, tapply=r.tapply, by=r.by, aggregate=r.aggregate, dplyr=r.dplyr, data.table=r.data.table),
function(x) (if(is.data.frame(x)) nrow else length)(x)==k)
# sapply lapply tapply by aggregate dplyr data.table
# TRUE TRUE TRUE TRUE TRUE TRUE TRUE
# print timings
as.data.table(sapply(timing, `[[`, "elapsed"), keep.rownames = TRUE
)[,.(fun = V1, elapsed = V2)
][order(-elapsed)]
# fun elapsed
#1: aggregate 109.139
#2: by 25.738
#3: dplyr 18.978
#4: tapply 17.006
#5: lapply 11.524
#6: sapply 11.326
#7: data.table 2.686
Несмотря на все отличные ответы здесь, есть еще 2 базовые функции, которые заслуживают упоминания, полезные outer
функция и неизвестность eapply
функция
внешний
outer
это очень полезная функция, скрытая как более приземленная. Если вы прочитали помощь для outer
в его описании говорится:
The outer product of the arrays X and Y is the array A with dimension
c(dim(X), dim(Y)) where element A[c(arrayindex.x, arrayindex.y)] =
FUN(X[arrayindex.x], Y[arrayindex.y], ...).
из-за этого кажется, что это полезно только для вещей типа линейной алгебры. Тем не менее, он может быть использован как mapply
применить функцию к двум векторам входов. Разница в том, что mapply
будет применять функцию к первым двум элементам, а затем ко вторым двум и т. д., тогда как outer
будет применять функцию к каждой комбинации одного элемента из первого вектора и одного из второго. Например:
A<-c(1,3,5,7,9)
B<-c(0,3,6,9,12)
mapply(FUN=pmax, A, B)
> mapply(FUN=pmax, A, B)
[1] 1 3 6 9 12
outer(A,B, pmax)
> outer(A,B, pmax)
[,1] [,2] [,3] [,4] [,5]
[1,] 1 3 6 9 12
[2,] 3 3 6 9 12
[3,] 5 5 6 9 12
[4,] 7 7 7 9 12
[5,] 9 9 9 9 12
Я лично использовал это, когда у меня есть вектор значений и вектор условий, и я хочу посмотреть, какие значения соответствуют каким условиям.
eapply
eapply
как lapply
за исключением того, что вместо применения функции к каждому элементу в списке, она применяет функцию к каждому элементу в среде. Например, если вы хотите найти список пользовательских функций в глобальной среде:
A<-c(1,3,5,7,9)
B<-c(0,3,6,9,12)
C<-list(x=1, y=2)
D<-function(x){x+1}
> eapply(.GlobalEnv, is.function)
$A
[1] FALSE
$B
[1] FALSE
$C
[1] FALSE
$D
[1] TRUE
Честно говоря, я не очень этим пользуюсь, но если вы создаете много пакетов или создаете много сред, это может пригодиться.
Стоит упомянуть ave
, ave
является tapply
дружелюбный кузен. Он возвращает результаты в форме, которую вы можете подключить прямо к фрейму данных.
dfr <- data.frame(a=1:20, f=rep(LETTERS[1:5], each=4))
means <- tapply(dfr$a, dfr$f, mean)
## A B C D E
## 2.5 6.5 10.5 14.5 18.5
## great, but putting it back in the data frame is another line:
dfr$m <- means[dfr$f]
dfr$m2 <- ave(dfr$a, dfr$f, FUN=mean) # NB argument name FUN is needed!
dfr
## a f m m2
## 1 A 2.5 2.5
## 2 A 2.5 2.5
## 3 A 2.5 2.5
## 4 A 2.5 2.5
## 5 B 6.5 6.5
## 6 B 6.5 6.5
## 7 B 6.5 6.5
## ...
В базовом пакете нет ничего, что бы работало как ave
для целых кадров данных (как by
как tapply
для фреймов данных). Но вы можете обмануть это:
dfr$foo <- ave(1:nrow(dfr), dfr$f, FUN=function(x) {
x <- dfr[x,]
sum(x$m*x$m2)
})
dfr
## a f m m2 foo
## 1 1 A 2.5 2.5 25
## 2 2 A 2.5 2.5 25
## 3 3 A 2.5 2.5 25
## ...
Я недавно обнаружил довольно полезный sweep
Функциональность и добавьте его здесь для полноты картины:
подметать
Основная идея состоит в том, чтобы пролистывать массив по строкам или столбцам и возвращать модифицированный массив. Пример прояснит это (источник: datacamp):
Допустим, у вас есть матрица и вы хотите стандартизировать ее по столбцам:
dataPoints <- matrix(4:15, nrow = 4)
# Find means per column with `apply()`
dataPoints_means <- apply(dataPoints, 2, mean)
# Find standard deviation with `apply()`
dataPoints_sdev <- apply(dataPoints, 2, sd)
# Center the points
dataPoints_Trans1 <- sweep(dataPoints, 2, dataPoints_means,"-")
print(dataPoints_Trans1)
## [,1] [,2] [,3]
## [1,] -1.5 -1.5 -1.5
## [2,] -0.5 -0.5 -0.5
## [3,] 0.5 0.5 0.5
## [4,] 1.5 1.5 1.5
# Return the result
dataPoints_Trans1
## [,1] [,2] [,3]
## [1,] -1.5 -1.5 -1.5
## [2,] -0.5 -0.5 -0.5
## [3,] 0.5 0.5 0.5
## [4,] 1.5 1.5 1.5
# Normalize
dataPoints_Trans2 <- sweep(dataPoints_Trans1, 2, dataPoints_sdev, "/")
# Return the result
dataPoints_Trans2
## [,1] [,2] [,3]
## [1,] -1.1618950 -1.1618950 -1.1618950
## [2,] -0.3872983 -0.3872983 -0.3872983
## [3,] 0.3872983 0.3872983 0.3872983
## [4,] 1.1618950 1.1618950 1.1618950
NB: для этого простого примера тот же результат, конечно, может быть достигнут легче apply(dataPoints, 2, scale)
В пакете collapse, недавно выпущенном на CRAN, я попытался сжать большую часть общих функций применения всего в 2 функции:
dapply
(Data-Apply) применяет функции к строкам или (по умолчанию) столбцам матриц и data.frames и (по умолчанию) возвращает объект того же типа и с теми же атрибутами (если результат каждого вычисления не является атомарным иdrop = TRUE
). Производительность сопоставима сlapply
для столбцов data.frame и примерно в 2 раза быстрее, чемapply
для строк или столбцов матрицы. Параллелизм доступен черезmclapply
(только для MAC).
Синтаксис:
dapply(X, FUN, ..., MARGIN = 2, parallel = FALSE, mc.cores = 1L,
return = c("same", "matrix", "data.frame"), drop = TRUE)
Примеры:
# Apply to columns:
dapply(mtcars, log)
dapply(mtcars, sum)
dapply(mtcars, quantile)
# Apply to rows:
dapply(mtcars, sum, MARGIN = 1)
dapply(mtcars, quantile, MARGIN = 1)
# Return as matrix:
dapply(mtcars, quantile, return = "matrix")
dapply(mtcars, quantile, MARGIN = 1, return = "matrix")
# Same for matrices ...
BY
является универсальным S3 для вычислений разделения-применения-комбинирования с помощью метода vector, matrix и data.frame. Это значительно быстрее, чемtapply
,by
а такжеaggregate
(также быстрее, чемplyr
, на больших данныхdplyr
хотя быстрее).
Синтаксис:
BY(X, g, FUN, ..., use.g.names = TRUE, sort = TRUE,
expand.wide = FALSE, parallel = FALSE, mc.cores = 1L,
return = c("same", "matrix", "data.frame", "list"))
Примеры:
# Vectors:
BY(iris$Sepal.Length, iris$Species, sum)
BY(iris$Sepal.Length, iris$Species, quantile)
BY(iris$Sepal.Length, iris$Species, quantile, expand.wide = TRUE) # This returns a matrix
# Data.frames
BY(iris[-5], iris$Species, sum)
BY(iris[-5], iris$Species, quantile)
BY(iris[-5], iris$Species, quantile, expand.wide = TRUE) # This returns a wider data.frame
BY(iris[-5], iris$Species, quantile, return = "matrix") # This returns a matrix
# Same for matrices ...
Списки группирующих переменных также могут быть предоставлены g
.
Говоря о производительности: главная цель коллапса - способствовать высокопроизводительному программированию на R и полностью выйти за рамки разделения-применения-объединения. Для этого в пакете есть полный набор быстрых универсальных функций на основе C++:fmean
, fmedian
, fmode
, fsum
, fprod
, fsd
, fvar
, fmin
, fmax
, ffirst
, flast
, fNobs
, fNdistinct
, fscale
, fbetween
, fwithin
, fHDbetween
, fHDwithin
, flag
, fdiff
а также fgrowth
. Они выполняют сгруппированные вычисления за один проход через данные (т. Е. Без разделения и рекомбинации).
Синтаксис:
fFUN(x, g = NULL, [w = NULL,] TRA = NULL, [na.rm = TRUE,] use.g.names = TRUE, drop = TRUE)
Примеры:
v <- iris$Sepal.Length
f <- iris$Species
# Vectors
fmean(v) # mean
fmean(v, f) # grouped mean
fsd(v, f) # grouped standard deviation
fsd(v, f, TRA = "/") # grouped scaling
fscale(v, f) # grouped standardizing (scaling and centering)
fwithin(v, f) # grouped demeaning
w <- abs(rnorm(nrow(iris)))
fmean(v, w = w) # Weighted mean
fmean(v, f, w) # Weighted grouped mean
fsd(v, f, w) # Weighted grouped standard-deviation
fsd(v, f, w, "/") # Weighted grouped scaling
fscale(v, f, w) # Weighted grouped standardizing
fwithin(v, f, w) # Weighted grouped demeaning
# Same using data.frames...
fmean(iris[-5], f) # grouped mean
fscale(iris[-5], f) # grouped standardizing
fwithin(iris[-5], f) # grouped demeaning
# Same with matrices ...
В виньетках пакета я привожу тесты. Программирование с помощью быстрых функций значительно быстрее, чем программирование с помощью dplyr или data.table, особенно для небольших данных, но также и для больших данных.
Начиная с R 4.3.0, будут поддерживаться фреймы данных и то и другое.tapply
иby
будет поддерживать группировку строк фрейма данных с помощью формулы.
> R.version.string
[1] "R version 4.3.0 beta (2023-04-07 r84200)"
> dd <- data.frame(x = 1:10, f = gl(5L, 2L), g = gl(2L, 5L))
x f g
1 1 1 1
2 2 1 1
3 3 2 1
4 4 2 1
5 5 3 1
6 6 3 2
7 7 4 2
8 8 4 2
9 9 5 2
10 10 5 2
> tapply(dd, ~f + g, nrow)
g
f 1 2
1 2 0
2 2 0
3 1 1
4 0 2
5 0 2
> by(dd, ~g, identity)
g: 1
x f g
1 1 1 1
2 2 1 1
3 3 2 1
4 4 2 1
5 5 3 1
------------------------------------------------------------
g: 2
x f g
6 6 3 2
7 7 4 2
8 8 4 2
9 9 5 2
10 10 5 2
Также есть альтернативы некоторым пакетам, которые не обсуждались выше.
The parApply()
функционировать вparallels
Пакет предоставляет альтернативу семейству функций apply для выполнения параллельных вычислений в кластере. Другие альтернативы параллельным вычислениям в R включаютforeach
пакет иdoParallel
пакет, который позволяет параллельно выполнять циклы и функции. Пакет предоставляет простой и согласованный API для использования фьючерсов, которые позволяют оценивать выражения асинхронно, параллельно или последовательно. Кроме того,purrr
пакет обеспечивает подход функционального программирования к итерации и отображению и поддерживает распараллеливание посредствомfuture
упаковка.
Вот некоторые примеры
Пример parApply():
library(parallel)
# Create a matrix
m <- matrix(1:20, nrow = 5)
# Define a function to apply to each column of the matrix
my_fun <- function(x) {
x^2
}
# Apply the function to each column of the matrix in parallel
result <- parApply(cl = makeCluster(2), X = m, MARGIN = 2, FUN = my_fun)
# View the result
result
пример для каждого:
library(foreach)
library(doParallel)
# Register a parallel backend
registerDoParallel(cores = 2)
# Create a list of numbers
my_list <- list(1, 2, 3, 4, 5)
# Define a function to apply to each element of the list
my_fun <- function(x) {
x^2
}
# Apply the function to each element of the list in parallel
result <- foreach(i = my_list) %dopar% my_fun(i)
# View the result
result
будущий пример:
library(future)
# Plan to use a parallel backend
plan(multisession, workers = 2)
# Create a list of numbers
my_list <- list(1, 2, 3, 4, 5)
# Define a function to apply to each element of the list
my_fun <- function(x) {
x^2
}
# Apply the function to each element of the list in parallel using futures
result <- future_map(my_list, my_fun)
# View the result
result
пример мурлыканья:
library(purrr)
library(future)
# Plan to use a parallel backend
plan(multisession, workers = 2)
# Create a list of numbers
my_list <- list(1, 2, 3, 4, 5)
# Define a function to apply to each element of the list
my_fun <- function(x) {
x^2
}
# Apply the function to each element of the list in parallel using purrr
result <- future_map(my_list, my_fun)
# View the result
result
РЕДАКТИРОВАТЬ 02.07.2023 ( будущим автором): заменен устаревший и больше не существующий.multiprocess
будущий бэкэнд сmultisession
.