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

Всякий раз, когда я хочу сделать что-то "сопоставить" 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): вход является матрицей. выходной вектор, где элемент i это f(строка / столбец матрицы)
  4. tapply(vector, grouping, f): output - это матрица / массив, где элемент в матрице / массиве - это значение f в группировке g вектора и g толкается к именам строк / столбцов
  5. by(dataframe, grouping, f): позволять g быть группировкой. применять f для каждого столбца группы / dataframe. довольно распечатать группировку и значение f в каждом столбце.
  6. 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:

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

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

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

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

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

мнемоника

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

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

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

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

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

Поскольку я понял, что (очень отличные) ответы на этот пост отсутствие 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 функции:

  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 для вычислений разделения-применения-комбинирования с помощью метода 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.

Другие вопросы по тегам