Функції групування (tapply, by, agregate) та сім'я * застосовувати


1040

Кожного разу, коли мені хочеться зробити щось "карта" 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): вхід - матриця. вихід - вектор, де елемент if (рядок / col i матриці)
  4. tapply(vector, grouping, f): вихід - матриця / масив, де елемент в матриці / масиві - це значення групи fпри групуванні gвектора, і він gпереходить до імен рядків / стовпців
  5. by(dataframe, grouping, f): нехай gбуде групуванням. застосувати fдо кожного стовпця групи / фрейму даних. досить роздрукувати групування та значення fкожного стовпця.
  6. aggregate(matrix, grouping, f): аналогічно by, але замість того, щоб сильно друкувати вихід, агрегат вкладає все у кадр даних.

Побічне запитання: я досі не вивчив plyr чи переформувати - plyrчи reshapeзамінювати б ці всі ці повністю?


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

12
Також я рекомендую додати: doByта можливості вибору та застосування data.table.
Ітератор

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

8
Мнемонічне: l - для "списку", s - для "спрощення", t - для "для кожного типу" (кожен рівень угрупування - тип)
Lutz Prechelt

У пакеті Rfast також існують деякі функції, такі як: коженcol.apply, apply.condition та багато іншого, які швидше, ніж еквіваленти R
Стефанос

Відповіді:


1330

R має багато * застосувати функції, які вміло описані у довідкових файлах (наприклад, ?apply). Їх достатньо, проте, що початкові користувачіR можуть мати труднощі вирішити, який з них підходить для їхньої ситуації або навіть запам'ятати їх усіх. Вони можуть мати загальне відчуття, що "я повинен тут використовувати функцію * застосовувати", але спочатку їх може бути важко тримати все прямо.

Незважаючи на те, що (зазначається в інших відповідях), що значна частина функціональних можливостей сімейства застосованих plyrпакетів * охоплюється надзвичайно популярним пакетом, базові функції залишаються корисними та їх варто знати.

Ця відповідь призначена для того, щоб діяти як своєрідний покажчик для нового використанняR, щоб допомогти спрямувати їх на правильну функцію * застосувати для їх конкретної проблеми. Зауважте, це не призначене для простої регенерації або заміни документації R! Сподіваємось, що ця відповідь допоможе вам вирішити, яка функція * застосовуватиметься відповідно до вашої ситуації, і тоді вам належить дослідити її далі. За одним винятком, відмінності в продуктивності не будуть усунені.

  • застосовувати - коли потрібно застосувати функцію до рядків або стовпців матриці (та аналогів більш великих розмірів); як правило, не рекомендується для кадрів даних, оскільки це буде примусовим до матриці спочатку.

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

    Якщо наша функція повертає двовимірну матрицю, 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 - якщо у вас є кілька структур даних (наприклад, вектори, списки), і ви хочете застосувати функцію до 1-го елемента кожного, а потім до 2-го елемента кожного і т.д., примушуючи результат до вектора / масиву, як у 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аналогічний за духом спліт-застосовувати функції-об'єднати , які є загальними в R ( aggregate, by, ave, ddplyі т.д.) Отже , його статус чорні вівці.


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

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

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

4
також, ?Mapяк родичmapply
баптист

3
@jsanders - Я б зовсім не погодився з цим. data.frames є абсолютно центральною частиною R і як listоб'єкт часто маніпулюють, lapplyзокрема. Вони також виступають контейнерами для групування векторів / факторів багатьох типів разом у традиційному прямокутному наборі даних. Хоча data.tableі plyrможе додати певний тип синтаксису, який деяким може бути зручнішим, вони розширюються і діють на data.frames відповідно.
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функції замінили майже всі ці функції в моєму щоденному використанні. Але також з документа Intro to 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 :

застосовувати, сапполювати, скупо, по сукупності

(Сподіваємось, це зрозуміло, що applyвідповідає @ Хадлі aaplyі aggregateвідповідає @ Хадлі і ddplyт. Д. Слайд 20 того ж слайда буде роз'яснено, якщо ви не отримаєте це з цього зображення.)

(зліва вводиться, вгорі виводиться)


4
чи є помилка на слайді? Лівий верхній осередок повинен бути вразливим
JHowIX

100

Спочатку почніть з чудової відповіді Джоран - сумнівне все може покращити це.

Тоді наступні мнемоніки можуть допомогти запам'ятати відмінності між ними. Хоча деякі очевидні, інші можуть бути меншими, тому --- ви знайдете виправдання в дискусіях Джорана.

Мнемоніка

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

Побудова правильного фону

Якщо використання applyродини все ще відчуває вас трохи чуже, то, можливо, вам не вистачає ключової точки зору.

Ці дві статті можуть допомогти. Вони забезпечують необхідну основу для мотивації функціональних прийомів програмування , які надаються applyсімейством функцій.

Користувачі Lisp негайно розпізнають парадигму. Якщо ви не знайомі з Lisp, як тільки ви обернетеся навколо FP, ви отримаєте потужну точку зору для використання в R - і applyбудете мати набагато більше сенсу.


51

Оскільки я зрозумів, що (дуже відмінні) відповіді на цей пост відсутні byі aggregateпояснення. Ось мій внесок.

BY

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кожну змінну.

Зауважте, що якщо перший аргумент є a 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- an 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)

Попередній код досягає тієї ж мети та результатів, у деяких моментах, який інструмент використовувати - це лише питання особистих смаків та потреб; попередні два об'єкти мають дуже різні потреби щодо підмножини.


Як я вже говорив, потужність компанії виникає тоді, коли ми не можемо використовувати tapply; наступний код - один із прикладів: ЦЕ СЛОВИ, ЩО ВИ ВИКОРИСТОВУвали. І ви навели приклад обчислення підсумків. Ну давайте скажемо, що підсумкова статистика може бути обчислена лише тим, що їй знадобиться очищення: наприклад, data.frame(tapply(unlist(iris[,-5]),list(rep(iris[,5],ncol(iris[-5])),col(iris[-5])),summary))це використання тачлового . With the right splitting there is nothing you cant do with дотику . The only thing is it returns a matrix. Please be careful by saying we cant use tapply tap`
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 Я так не думаю, не впевнений, чому він тут. Найкраще перевірити власні дані, оскільки існує багато факторів.
jangorecki

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це як 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доброзичливим двоюрідним братом. Він повертає результати у формі, яку ви зможете підключити прямо у рамку даних.

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 функцію і додав її сюди заради повноти:

підмітати

Основна ідея полягає в тому, щоб прогортати масив у рядку чи стовпці та повертати модифікований масив. Приклад зробить це зрозумілим (джерело: 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

Зверніть увагу: для цього простого прикладу той самий результат, звичайно, можна досягти легше
apply(dataPoints, 2, scale)


1
Це пов’язано з групуванням?
Френк

2
@Frank: Ну, якщо чесно з вами, заголовок цієї публікації є досить оманливим: коли ви читаєте саме питання, мова йде про "сімейство застосувати". sweepце функція вищого порядку , як і всі інші згадані тут, наприклад apply, sapply, lapplyтак і те ж питання може бути поставлено питання про загальноприйнятому відповідь з більш ніж 1000 upvotes і приклади наведені в ній. Просто погляньте на приклад, поданий applyтам.
фондж

2
sweep має оманливе ім'я, вводить в оману за замовчуванням і оманливе ім'я параметра :). Мені простіше зрозуміти це так: 1) STATS - це векторне або єдине значення, яке буде повторене, щоб утворити матрицю такого ж розміру, що і перший вхід; 2) FUN буде застосовано на першому вході та цій новій матриці. Може бути , краще проілюструвати: 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 для обчислювальних технологій роздільного застосування та комбінування з методом вектора, матриці та даних.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.