Группировка функций (tapply, в совокупности) и *применение семейного


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

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

9 934

9 ответов:

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

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

этот ответ призван действовать как своего рода указатель для новых пользователей, чтобы помочь направить их к правильной * применить функцию для их конкретной проблемы. Обратите внимание, это не предназначен для простого отрыгивания или замены документации 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 -если вы хотите применить функцию к каждому элементу a список в свою очередь и получить список обратно.

    это рабочая лошадка многих других *применять функции. Корка верните их код и вы часто будете находить 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
    
  • поставка -если вы хотите применить функцию к каждому элементу список, в свою очередь, но вы хотите вектор назад, а не список.

    если вы обнаружите, что печатаете 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 -когда у вас есть несколько структуры данных (например, векторы, списки) и вы хотите применить функцию к 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, поэтому гарантируется возврат a список.

    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 -когда вы хотите применить функцию к подмножеств в вектор и подмножества определяются некоторым другим вектором, обычно a факторный.

    паршивая овца * применить семьи, своего рода. Использование файла справки фраза "рваный массив" может быть немного сбивает с толку, но это на самом деле совсем простой.

    A вектор:

    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 is аналогично по духу функции split-apply-combine, которые являются общие в R (aggregate,by,ave,ddply и т. д.) Следовательно свой статус белой вороны.

на стороне обратите внимание, вот как различные plyr функции соответствуют основанию *apply функции (из введения в документ plyr с веб-страницы plyrhttp://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() произвести полезный выход, etc.

концептуально, обучение plyr не сложнее, чем понять базу *apply функции.

plyr и reshape функции заменили почти все эти функции в моем ежедневном использовании. Но, также из вступления к документу Plyr:

соответствующие функции tapply и sweep не имеют соответствующей функции в plyr, и остаются полезными. merge полезно для объединения резюме с исходными данными.

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

apply, sapply, lapply, by, aggregate

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

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

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

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

Мнемоника

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

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

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

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

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

так как я понял, что (очень отличные) ответы на этот пост не хватает by и aggregate объяснения. Вот мой вклад.

BY

The 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 это то, что он может легко обрабатывать подмножества данных с

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

возможно, стоит упомянуть 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
## ...

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

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

недавно я обнаружил довольно полезный