web-dev-qa-db-fra.com

Fonctions de regroupement (tapply, par, agrégées) et la famille * apply

Chaque fois que je veux faire quelque chose "map" py dans R, j'essaie généralement d'utiliser une fonction de la famille apply.

Cependant, je n'ai jamais bien compris les différences qui les séparent - comment {sapply, lapply, etc.} appliquent la fonction à l'entrée/à l'entrée groupée, à quoi ressemblera la sortie, ou même ce que l’entrée peut être - alors souvent, je les passe en revue jusqu’à ce que j’obtienne ce que je veux.

Quelqu'un peut-il expliquer comment utiliser lequel quand?

Ma compréhension actuelle (probablement incorrecte/incomplète) est ...

  1. sapply(vec, f): input est un vecteur. la sortie est un vecteur/matrice, où l'élément i est f(vec[i]), ce qui vous donne une matrice si f a une sortie multi-éléments

  2. lapply(vec, f): identique à sapply, mais la sortie est une liste?

  3. apply(matrix, 1/2, f): input est une matrice. la sortie est un vecteur, où l'élément i est f (ligne/colonne i de la matrice)
  4. tapply(vector, grouping, f): output est une matrice/un tableau, où un élément de la matrice/du tableau a la valeur de f dans un groupe g du vecteur et où g est poussé vers la ligne/noms de col
  5. by(dataframe, grouping, f): Soit g un groupe. applique f à chaque colonne du groupe/cadre de données. joli imprimer le groupement et la valeur de f à chaque colonne.
  6. aggregate(matrix, grouping, f): semblable à by, mais au lieu d’imprimer assez la sortie, l’agrégat colle tout dans une trame de données.

Question secondaire: je n’ai toujours pas appris le plyr ou le remodelage - est-ce que plyr ou reshape remplacerait tout cela?

992
grautur

R possède de nombreuses fonctions * apply qui sont décrites de manière pertinente dans les fichiers d’aide (par exemple, ?apply). Cependant, ils sont assez nombreux pour que les utilisateurs débutants aient du mal à décider laquelle convient le mieux à leur situation ou même à se souvenir de toutes. Ils peuvent avoir le sentiment général que "je devrais utiliser une fonction * apply ici", mais il peut être difficile de les garder tous droits au début.

Malgré le fait (noté dans d'autres réponses) qu'une grande partie des fonctionnalités de la famille * apply sont couvertes par le package extrêmement populaire plyr, les fonctions de base restent utiles et valent la peine d'être connues.

Cette réponse est censée agir comme une sorte de indicateur pour les nouveaux utilisateurs afin de les orienter vers la fonction * appropriée appropriée pour leur problème particulier. Notez que ceci n'est pas destiné à simplement régurgiter ou remplacer la documentation R! L'espoir est que cette réponse vous aide à décider quelle fonction * apply convient à votre situation et qu'il vous appartient ensuite de la rechercher plus en profondeur. À une exception près, les différences de performances ne seront pas traitées.

  • apply - Lorsque vous souhaitez appliquer une fonction aux lignes ou aux colonnes d'une matrice (et d'analogues de dimension supérieure); généralement déconseillé pour les trames de données, car il forcera d’abord à former une matrice.

    # 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
    

    Si vous voulez des moyennes ligne/colonne ou des sommes pour une matrice 2D, assurez-vous d’examiner les options colMeans, rowMeans, colSums, rowSums hautement optimisées.

  • lapply - Lorsque vous souhaitez appliquer une fonction à chaque élément d'une liste, vous obtenez une liste.

    C’est le cheval de bataille de beaucoup des autres fonctions * apply. Détachez leur code et vous trouverez souvent lapply en dessous.

    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 - Lorsque vous souhaitez appliquer une fonction à chaque élément d'une liste, mais que vous souhaitez un vector retour, plutôt qu'une liste.

    Si vous vous trouvez en train de taper unlist(lapply(...)), arrêtez-vous et considérez 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 
    

    Dans les utilisations plus avancées de sapply, il tentera de contraindre le résultat à un tableau multidimensionnel, le cas échéant. Par exemple, si notre fonction retourne des vecteurs de même longueur, sapply les utilisera comme colonnes d'une matrice:

    sapply(1:5,function(x) rnorm(3,x))
    

    Si notre fonction retourne une matrice à 2 dimensions, sapply fera essentiellement la même chose, en traitant chaque matrice retournée comme un seul vecteur long:

    sapply(1:5,function(x) matrix(x,2,2))
    

    Sauf si nous spécifions simplify = "array", dans ce cas, il utilisera les matrices individuelles pour créer un tableau multidimensionnel:

    sapply(1:5,function(x) matrix(x,2,2), simplify = "array")
    

    Chacun de ces comportements dépend évidemment de notre fonction qui renvoie des vecteurs ou des matrices de même longueur ou de même dimension.

  • vapply - Lorsque vous voulez utiliser sapply mais que vous avez peut-être besoin de tirer un peu plus de vitesse de votre code.

    Pour vapply, vous donnez à R un exemple de ce que votre fonction retournera, ce qui peut permettre de gagner du temps en forçant les valeurs renvoyées à tenir dans un seul vecteur atomique.

    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 - Lorsque vous avez plusieurs structures de données (par exemple, des vecteurs, des listes) et que vous souhaitez appliquer une fonction aux premiers éléments de chaque , puis les deuxièmes éléments de chacun, etc., contraignant le résultat à un vecteur/tableau comme dans sapply.

    Ceci est multivarié dans le sens où votre fonction doit accepter plusieurs arguments.

    #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
    
  • Carte - n wrapper to mapply avec SIMPLIFY = FALSE, il est donc garanti de renvoyer une liste. =

    Map(sum, 1:5, 1:5, 1:5)
    [[1]]
    [1] 3
    
    [[2]]
    [1] 6
    
    [[3]]
    [1] 9
    
    [[4]]
    [1] 12
    
    [[5]]
    [1] 15
    
  • rapply - Lorsque vous souhaitez appliquer une fonction à chaque élément d'une liste imbriquée structure, récursivement.

    Pour vous donner une idée de la rareté de rapply, je l'ai oublié lorsque j'ai posté cette réponse pour la première fois! Évidemment, je suis sûr que beaucoup de gens l'utilisent, mais le YMMV. rapply est mieux illustré par une fonction définie par l'utilisateur à appliquer:

    # 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 - Pour quand appliquer une fonction à des sous-ensembles d'un vecteur et les sous-ensembles sont définis par un autre vecteur, généralement un facteur.

    Le mouton noir de la * appliquer famille, en quelque sorte. L'utilisation par le fichier d'aide de l'expression "ragged array" peut être un peu déroutant , mais c'est en fait assez simple.

    Un vecteur:

    x <- 1:20
    

    Un facteur (de même longueur!) Définissant les groupes:

    y <- factor(rep(letters[1:5], each = 4))
    

    Additionnez les valeurs dans x dans chaque sous-groupe défini par y:

    tapply(x, y, sum)  
     a  b  c  d  e  
    10 26 42 58 74 
    

    Des exemples plus complexes peuvent être traités lorsque les sous-groupes sont définis par les combinaisons uniques d’une liste de plusieurs facteurs. tapply est similaire dans l'esprit aux fonctions split-apply-combine communes à R (aggregate, by, ave, ddply, etc.) D'où son statut de mouton noir.

1279
joran

Sur la note latérale, voici comment les diverses fonctions plyr correspondent aux fonctions de base *apply (de l’introduction au document plyr de la page Web 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 

L'un des objectifs de plyr est de fournir des conventions de dénomination cohérentes pour chacune des fonctions, en codant les types de données d'entrée et de sortie dans le nom de la fonction. Il fournit également une cohérence dans la sortie, en ce sens que la sortie de dlply() est facilement transmissible à ldply() pour produire une sortie utile, etc.

Conceptuellement, apprendre plyr n'est pas plus difficile que de comprendre les fonctions de base *apply.

Les fonctions plyr et reshape ont remplacé la quasi-totalité de ces fonctions dans mon utilisation quotidienne. Mais aussi du document Intro to Plyr:

Les fonctions associées tapply et sweep n'ont pas de fonction correspondante dans plyr et restent utiles. merge est utile pour combiner des résumés avec les données d'origine.

182
JoFrhwld

De la diapositive 21 de http://www.slideshare.net/hadley/plyr-one-data-analytic-strategy :

apply, sapply, lapply, by, aggregate

(Espérons qu'il soit clair que apply correspond à @ [Hadry's aaply et aggregate correspond à @ Hadley's ddply etc. La diapositive 20 du même diaporama vous expliquera si vous n'obtenez pas à partir de cette image.)

(à gauche est entrée, en haut est sortie)

129
isomorphismes

Commençons par excellente réponse de Joran - rien ne permet de douter de l'amélioration.

Ensuite, les mnémoniques suivants peuvent aider à rappeler les distinctions entre chacun. Alors que certains sont évidents, d'autres le sont peut-être moins - vous trouverez une justification dans les discussions de Joran.

Mnémoniques

  • lapply est une liste qui agit sur une liste ou un vecteur et renvoie une liste.
  • sapply est un simple lapply (la fonction retourne par défaut un vecteur ou une matrice)
  • vapply est un vérifié applique (permet de spécifier le type d'objet renvoyé)
  • rapply est un récursif s'applique aux listes imbriquées, c'est-à-dire aux listes au sein de listes
  • tapply est un étiqueté s'applique là où les étiquettes identifient les sous-ensembles
  • apply est générique : applique une fonction aux lignes ou aux colonnes d'une matrice (ou plus généralement aux dimensions d'un tableau).

Construire le bon fond

Si vous utilisez toujours la famille apply pour vous sentir un peu étranger, il se peut que vous manquiez d'un point de vue essentiel.

Ces deux articles peuvent aider. Elles fournissent les bases nécessaires pour motiver les techniques de programmation fonctionnelle fournies par la famille apply.

Les utilisateurs de LISP reconnaîtront immédiatement le paradigme. Si vous n'êtes pas familier avec LISP, une fois que vous aurez compris l'esprit de FP, vous aurez acquis un puissant point de vue pour une utilisation dans R - et apply aura plus de sens.

96
Assad Ebrahim

Depuis que j’ai réalisé que (les très excellentes) réponses de cet article manquaient de by et aggregate explications. Voici ma contribution.

PAR

La fonction by, comme indiqué dans la documentation, peut toutefois être utilisée comme un "wrapper" pour tapply. La puissance de by apparaît lorsque nous voulons calculer une tâche que tapply ne peut pas gérer. Un exemple est ce code:

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 

Si nous imprimons ces deux objets, ct et cb, nous avons "essentiellement" les mêmes résultats et les seules différences sont dans la façon dont ils sont affichés et les différents attributs class, respectivement by pour cb et array pour ct.

Comme je l'ai dit, le pouvoir de by apparaît lorsque nous ne pouvons pas utiliser tapply; le code suivant est un exemple:

 tapply(iris, iris$Species, summary )
Error in tapply(iris, iris$Species, summary) : 
  arguments must have same length

R dit que les arguments doivent avoir les mêmes longueurs, disons "nous voulons calculer la summary de toutes les variables dans iris le long du facteur Species": mais R ne peut tout simplement pas le faire car il ne sait pas comment gérer.

Avec la fonction by R, envoyez une méthode spécifique pour la classe data frame puis laissez la fonction summary fonctionner même si la longueur du premier argument (et du type) est différente.

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     

cela fonctionne vraiment et le résultat est très surprenant. C'est un objet de la classe by que le long de Species (par exemple, pour chacun d'eux) calcule le summary de chaque variable.

Notez que si le premier argument est un data frame, la fonction distribuée doit avoir une méthode pour cette classe d'objets. Par exemple si nous utilisons ce code avec la fonction mean, nous aurons ce code qui n’a aucun sens:

 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

AGRÉGAT

aggregate peut être vu comme un autre mode d'utilisation tapply si nous l'utilisons de cette manière.

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

Les deux différences immédiates sont que le deuxième argument de aggregatedoit est une liste alors que tapplypeut (non obligatoire) est une liste et que le la sortie de aggregate est une trame de données alors que celle de tapply est une array.

La puissance de aggregate réside dans le fait qu’il peut gérer facilement des sous-ensembles de données avec l’argument subset et qu’il dispose de méthodes pour les objets ts et formula.

Ces éléments rendent aggregate plus facile à utiliser avec tapply dans certaines situations. Voici quelques exemples (disponibles dans la documentation):

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

Nous pouvons réaliser la même chose avec tapply mais la syntaxe est légèrement plus dure et la sortie (dans certaines circonstances) moins lisible:

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

Il y a d'autres fois où nous ne pouvons pas utiliser by ou tapply et nous devons utiliser 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

Nous ne pouvons pas obtenir le résultat précédent avec tapply dans un appel, mais nous devons calculer la moyenne le long de Month pour chaque élément, puis les combiner (notez également que nous devons appeler le na.rm = TRUE, parce que les méthodes formula de la fonction aggregate ont par défaut le 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

alors que avec by nous ne pouvons tout simplement pas y parvenir. En fait, l'appel de fonction suivant renvoie une erreur (mais il est très probablement lié à la fonction fournie, mean):

by(airquality[c("Ozone", "Temp")], airquality$Month, mean, na.rm = TRUE)

D'autres fois, les résultats sont les mêmes et les différences ne sont que dans la classe (et ensuite, comment cela est montré/imprimé et pas seulement - par exemple, comment le sous-créer):

byagg <- by(airquality[c("Ozone", "Temp")], airquality$Month, summary)
aggagg <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, summary)

Le code précédent atteignait le même objectif et les mêmes résultats, à certains moments, quel outil utiliser était simplement une question de goûts et de besoins personnels; les deux objets précédents ont des besoins très différents en termes de sous-ensemble.

46
SabDeM

Il y a beaucoup de bonnes réponses qui traitent des différences dans les cas d'utilisation pour chaque fonction. Aucune des réponses ne discute des différences de performance. C’est raisonnable, car diverses fonctions s’attendent à diverses entrées et produisent diverses sorties, mais la plupart d’entre elles ont un objectif général commun d’évaluer par séries/groupes. Ma réponse va se concentrer sur la performance. En raison de ce qui précède, la création d’entrée à partir des vecteurs est incluse dans la synchronisation, de même que la fonction apply n’est pas mesurée.

J'ai testé deux fonctions différentes sum et length à la fois. Le volume testé est 50M en entrée et 50K en sortie. J'ai également inclus deux paquets actuellement populaires qui n'étaient pas largement utilisés au moment où la question a été posée, data.table et dplyr. Les deux valent vraiment la peine d'être regardés si vous visez de bonnes performances.

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
31
jangorecki

Malgré toutes les bonnes réponses données ici, il reste 2 fonctions de base qui méritent d’être mentionnées, la fonction utile outer et la fonction obscure eapply.

externe

outer est une fonction très utile cachée de manière plus banale. Si vous lisez l'aide pour outer sa description indique:

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], ...).

ce qui donne l’impression que cela n’est utile que pour les choses de type algèbre linéaire. Cependant, il peut être utilisé comme mapply pour appliquer une fonction à deux vecteurs d'entrées. La différence est que mapply appliquera la fonction aux deux premiers éléments, puis aux deux autres, etc., tandis que outer appliquera la fonction à chaque combinaison d'un élément du premier vecteur et d'un du second. . Par exemple:

 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

J'ai personnellement utilisé cela lorsque j'ai un vecteur de valeurs et un vecteur de conditions et que je souhaite voir quelles valeurs correspondent à quelles conditions.

en avance

eapply ressemble à lapply sauf que plutôt que d'appliquer une fonction à chaque élément d'une liste, il applique une fonction à chaque élément d'un environnement. Par exemple, si vous souhaitez rechercher une liste de fonctions définies par l'utilisateur dans l'environnement global:

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 

Franchement, je ne l'utilise pas beaucoup, mais si vous construisez beaucoup de paquets ou créez beaucoup d'environnements, cela peut être utile.

23
John Paul

Il est peut-être intéressant de mentionner ave. ave est le cousin amical de tapply. Il renvoie les résultats dans un formulaire que vous pouvez réintégrer directement dans votre cadre de données.

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

Il n'y a rien dans le package de base qui fonctionne comme ave pour des trames de données entières (car by est semblable à tapply pour des trames de données). Mais vous pouvez le tromper:

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

J'ai récemment découvert la fonction plutôt utile sweep et l'ajoute ici par souci d'exhaustivité:

balayage

L'idée de base est de balayer dans un tableau, ligne par colonne ou par colonne, et de renvoyer un tableau modifié. Un exemple le clarifiera (source: datacamp ):

Disons que vous avez une matrice et que vous voulez standardiser la colonne par colonne:

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: pour cet exemple simple, le même résultat peut bien sûr être obtenu plus facilement en
apply(dataPoints, 2, scale)

9
vonjd