Функции группировки (tapply, by, aggregate) и семейство * apply


1041

Всякий раз, когда я хочу сделать что-то «сопоставить» py в R, я обычно пытаюсь использовать функцию в applyсемье.

Однако я никогда не понимал различий между ними - как { sapply, lapplyи т. Д.} Применять функцию к входу / групповому вводу, как будет выглядеть вывод, или даже каким может быть ввод - поэтому я часто просто пройди их все, пока я не получу то, что хочу.

Может кто-нибудь объяснить, как использовать какой, когда?

Мое текущее (вероятно, неправильное / неполное) понимание ...

  1. sapply(vec, f): вход является вектором. Выходной вектор / матрица, где элемент iявляется f(vec[i]), что дает вам матрицу , если fимеет выход многоэлементного

  2. lapply(vec, f): тоже самое sapply, но вывод есть список?

  3. apply(matrix, 1/2, f): вход является матрицей. output - вектор, где element i- это f (строка / столбец матрицы i)
  4. tapply(vector, grouping, f): Выход матрица / массив, в котором элемент в матрице / массиве значения fпри группировке gвектора, и gполучает толкнул к / именам Col строка
  5. by(dataframe, grouping, f): пусть gбудет группировка. применить fк каждому столбцу группы / dataframe. Довольно распечатать группировку и значение fв каждом столбце.
  6. aggregate(matrix, grouping, f): похоже на by, но вместо того, чтобы красиво печатать вывод, агрегат вставляет все в информационный фрейм.

Дополнительный вопрос: я до сих пор не изучил plyr или не изменил форму - полностью plyrили reshapeполностью я бы их заменил?


33
на вашу сторону вопрос: для многих вещей Plyr является прямой заменой *apply()и by. Plyr (по крайней мере, мне) кажется гораздо более последовательным в том, что я всегда точно знаю, какой формат данных он ожидает, и что именно он будет выплевывать. Это избавляет меня от многих хлопот.
JD Long

12
Кроме того, я бы рекомендовал добавить: doByи возможности выбора и применения data.table.
Итератор

7
sapplyэто просто lapplyс добавлением simplify2arrayна выходе. applyприводит к атомарному вектору, но вывод может быть вектором или списком. byразбивает фреймы данных на суб-фреймы, но не использует fотдельно столбцы. Только если есть метод для класса «data.frame», можно fприменять по столбцам by. aggregateявляется универсальным, поэтому существуют разные методы для разных классов первого аргумента.
IRTFM

8
Мнемоника: l для «списка», s для «упрощения», t для «по типу» (каждый уровень группировки является типом)
Lutz Prechelt

В пакете Rfast также есть некоторые функции, такие как: eachcol.apply, apply.condition и другие, которые работают быстрее, чем эквиваленты R
Стефанос

Ответы:


1330

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
    

    Если вы хотите средства строк / столбцов или суммы для 2D - матрицы, убедитесь , что для расследования оптимизированный, молниеносный 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
    
  • Map - Обертка для mapplywith 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аналогичен по духу сплит-применять функции-объединить , которые являются общими в R ( aggregate, by, ave, ddplyи т.д.) Следовательно , его статус черные овцы.


32
Поверьте, вы обнаружите, что byэто чисто раздвоение и aggregateлежит tapplyв их основе. Я думаю, что черная овца делает отличную ткань.
IRTFM

21
Фантастический ответ! Это должно быть частью официальной документации R :). Одно крошечное предложение: возможно , добавить несколько пуль по использованию aggregateи byтак же? (Я, наконец, понимаю их после вашего описания!, Но они довольно распространены, поэтому может быть полезно выделить и привести несколько конкретных примеров для этих двух функций.)
grautur

3
@grautur Я активно сокращал этот ответ, чтобы он не был (а) слишком длинным и (б) не переписывался с документацией. Я решил , что время aggregate, byи т.д., на основе * применяются функции, как вы к ним , используя разные достаточно с точки зрения пользователей , что они должны быть представлены в виде отдельного ответа. Я могу попытаться сделать это, если у меня будет время, или, может быть, кто-то другой побьет меня и заработает мой голос.
Джоран

4
Кроме того, ?Mapкак родственникmapply
крестить

3
@jsanders - я бы с этим не согласился. data.frames являются абсолютно центральной частью R и как listобъект, которым часто манипулируют, в lapplyчастности. Они также действуют как контейнеры для группировки векторов / факторов многих типов в традиционный прямоугольный набор данных. Хотя data.tableи plyrмогут добавлять определенный тип синтаксиса, который некоторым может показаться более удобным, они расширяются и действуют data.frameсоответственно на s.
thelatemail

191

Кроме того, здесь показано, как различные 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полезно для объединения резюме с исходными данными.


13
Когда я начал изучать R с нуля, я обнаружил, что plyr НАМНОГО легче освоить, чем *apply()семейство функций. Для меня это ddply()было очень интуитивно понятно, так как я был знаком с функциями агрегации SQL. ddply()стал моим молотом для решения многих проблем, некоторые из которых могли бы быть лучше решены с помощью других команд.
JD Long

1
Думаю, я понял, что концепция, лежащая в основе plyrфункций, аналогична *applyфункциям, поэтому, если вы можете сделать одно, вы можете сделать другое, но plyrфункции легче запомнить. Но я полностью согласен с ddply()молотком!
JoFrhwld

1
Пакет plyr имеет join()функцию, которая выполняет задачи, аналогичные слиянию. Возможно, более важно упомянуть об этом в контексте plyr.
Марбель

Давайте не будем забывать бедных, забытыхeapply
JDL

Отличный ответ в целом, но я думаю, что это преуменьшает полезность vapplyи недостатки sapply. Основным преимуществом vapplyявляется то, что он обеспечивает тип и длину вывода, поэтому вы получите либо точный ожидаемый результат, либо информативную ошибку. С другой стороны, sapplyмы попытаемся упростить вывод, следуя правилам, которые не всегда очевидны, и в противном случае вернемся к списку. Например, попытаться предсказать тип выхода этого будет производить: sapply(list(1:5, 6:10, matrix(1:4, 2)), function(x) head(x, 1)). Как насчет sapply(list(matrix(1:4, 2), matrix(1:4, 2)), ...)?
Алексей Шикломанов

133

Из слайда 21 http://www.slideshare.net/hadley/plyr-one-data-analytic-strategy :

применять, sapply, lapply, путем, совокупность

(Надеюсь, это ясно, что applyсоответствует @ Hadley's aaplyи aggregateсоответствует @ Hadley's ddplyи т. Д. Слайд 20 того же слайдшера прояснит, если вы не получите его из этого изображения.)

(слева ввод, сверху вывод)


4
на слайде есть опечатка? Верхняя левая ячейка должна быть в состоянии aaply
JHowIX

100

Сначала начните с отличного ответа Джорана - сомнительно, что все может быть лучше.

Тогда следующая мнемоника может помочь вспомнить различия между каждым. В то время как некоторые из них очевидны, другие могут быть не так - для них вы найдете оправдание в обсуждениях Джорана.

мнемоника

  • lapplyявляется применением списка, которое действует на список или вектор и возвращает список.
  • sapplyявляется простым lapply (функция по умолчанию возвращает вектор или матрицу, когда это возможно)
  • vapplyявляется проверенным применением (позволяет предварительно указать тип возвращаемого объекта)
  • rapplyявляется рекурсивным применением для вложенных списков, то есть списков в списках
  • tapplyявляется тегом применить, где теги идентифицируют подмножества
  • apply является универсальным : применяет функцию к строкам или столбцам матрицы (или, в более общем случае, к измерениям массива)

Создание правильного фона

Если использование applyсемьи все еще кажется вам чуждым, возможно, вам не хватает ключевой точки зрения.

Эти две статьи могут помочь. Они обеспечивают необходимую основу для мотивации методов функционального программирования , предоставляемых applyсемейством функций.

Пользователи Lisp сразу узнают эту парадигму. Если вы не знакомы с Lisp, то, как только вы разберетесь с FP, вы получите мощную точку зрения для использования в R - и у вас applyбудет гораздо больше смысла.


51

Так как я понял, что (очень отличные) ответы на этот пост отсутствуют 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переменную all по всей длине 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)

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


Как я уже сказал, сила by возникает, когда мы не можем использовать tapply; следующий код является одним из примеров: ЭТО СЛОВА, КОТОРЫЕ ВЫ ИСПОЛЬЗУЛИ. И вы привели пример вычисления резюме. Хорошо, скажем, что итоговая статистика может быть вычислена только потому, что она нуждается в очистке: например, data.frame(tapply(unlist(iris[,-5]),list(rep(iris[,5],ncol(iris[-5])),col(iris[-5])),summary))это использование tapply . With the right splitting there is nothing you cant do with tapply . The only thing is it returns a matrix. Please be careful by saying we cant use tapply`
Onyambu

35

Есть много отличных ответов, которые обсуждают различия в вариантах использования для каждой функции. Ни один из ответов не обсуждает различия в производительности. Это разумно, потому что различные функции ожидают различного ввода и производят различный вывод, но большинство из них имеют общую общую цель - оценивать по сериям / группам. Мой ответ будет сосредоточен на производительности. Из-за вышеизложенного создание ввода из векторов включено в синхронизацию, также 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

Это нормально, что dplyr ниже, чем функции applt?
Мостафа

1
@DimitriPetrenko Я так не думаю, не уверен, почему это здесь. Лучше всего проверить свои данные, так как в игру вступает множество факторов.
Джангорецкий

28

Несмотря на все замечательные ответы здесь, есть еще 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 

Честно говоря, я не очень этим пользуюсь, но если вы создаете много пакетов или создаете много сред, это может пригодиться.


25

Возможно, стоит упомянуть ave. aveэто tapply«S дружелюбный кузен. Он возвращает результаты в форме, которую вы можете подключить обратно в свой фрейм данных.

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
## ...

12

Недавно я обнаружил довольно полезную sweepфункцию и добавил ее здесь для полноты картины:

развертка

Основная идея заключается в том, чтобы подметать через row- массива или столбцы и возвращает модифицированный массив. Пример прояснит это (источник: 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)


1
Это связано с группировкой?
Фрэнк

2
@Frank: Если честно, заголовок этого поста вводит в заблуждение: когда вы читаете сам вопрос, речь идет о «подходящей семье». sweepэто функция высшего порядка , как и все другие упомянутые здесь, например apply, sapply, lapplyтак и тот же вопрос может быть задан вопрос о общепринятом ответ с более чем 1000 upvotes и примеры приведены в ней. Просто посмотрите на пример, приведенный applyтам.
vonjd

2
Sweep имеет вводящее в заблуждение имя, вводящее в заблуждение значение по умолчанию и вводящее в заблуждение имя параметра :). Мне легче понять это следующим образом: 1) STATS - это векторное или единичное значение, которое будет повторяться для формирования матрицы того же размера, что и первый вход, 2) FUN будет применен к 1-му входу и этой новой матрице. Может быть , лучше проиллюстрировать: sweep(matrix(1:6,nrow=2),2,7:9,list). Это обычно более эффективно, чем applyпотому, что там apply, где есть циклы, sweepможно использовать векторизованные функции.
Moody_Mudskipper

2

В схлопывания пакете недавно выпущенный на CRAN, я попытался сжать большую часть общего применять функции в только 2 функции:

  1. 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 ...
  1. BYявляется универсальным S3 для вычисления split-apply-Combine с вектором, матрицей и методом 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 , особенно для небольших данных, но также и для больших данных.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.