Question Fonctions de regroupement (tapply, by, aggregate) 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 dans le apply famille.

Cependant, je n'ai jamais vraiment compris les différences entre eux - comment {sapply, lapply, etc.} appliquez la fonction à l'entrée d'entrée / groupée, à quoi ressemblera la sortie, ou même ce que l'entrée peut être - alors je passe souvent par tous les éléments jusqu'à obtenir ce que je veux.

Quelqu'un peut-il expliquer comment utiliser quand?

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

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

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

  3. apply(matrix, 1/2, f): l'entrée est une matrice. la sortie est un vecteur, où l'élément i est f (ligne / col i de la matrice)
  4. tapply(vector, grouping, f): la sortie est une matrice / tableau, où un élément dans la matrice / tableau est la valeur de f à un groupement g du vecteur, et g est poussé vers les noms de rang / col
  5. by(dataframe, grouping, f): laisser g être un groupement. appliquer f à chaque colonne du groupe / dataframe. joli imprimer le groupement et la valeur de f à chaque colonne.
  6. aggregate(matrix, grouping, f): semblable à by, mais au lieu de joliment imprimer la sortie, l'agrégat colle tout dans une base de données.

Question subsidiaire: je n'ai pas encore appris plyr ou remodeler - serait plyr ou reshape remplacer tout cela entièrement?


915
2017-08-17 18:31


origine


Réponses:


R a de nombreuses fonctions d'application qui sont décrites avec précision dans les fichiers d'aide (par ex. ?apply). Il y en a cependant assez, que les débutants peuvent avoir de la difficulté à décider lequel est approprié à leur situation ou même se souvenir de tous. 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 * s'appliquent est couverte par le très populaire plyr paquet, les fonctions de base restent utiles et méritent d'être connues.

Cette réponse est destinée à agir comme une sorte de poteau indicateur pour les nouveaux utilisateurs, aidez-les à appliquer la bonne fonction * apply pour leur problème particulier. Notez, ceci est ne pas destiné à simplement régurgiter ou remplacer la documentation R! L'espoir est que cette réponse vous aide à décider quelle fonction d'application convient à votre situation et ensuite c'est à vous de la rechercher plus avant. À une exception près, les différences de performance ne seront pas prises en compte.

  • appliquer - Lorsque vous souhaitez appliquer une fonction aux lignes ou colonnes d'une matrice (et d'analogues de plus grande dimension); pas généralement conseillé pour les trames de données car il contraindra d'abord à 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 de ligne / colonne ou des sommes pour une matrice 2D, assurez-vous de étudier la très optimisée, rapide comme l'éclair colMeans, rowMeans, colSums, rowSums.

  • lapply - Lorsque vous souhaitez appliquer une fonction à chaque élément d'un liste à tour de rôle et récupère une liste.

    C'est le cheval de bataille de beaucoup d'autres fonctions d'application. Peler retour leur code et vous trouverez souvent lapply sous.

    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'un liste à tour de rôle, mais vous voulez un vecteur retour, plutôt qu'une liste.

    Si vous vous trouvez en train de taper unlist(lapply(...)), arrêtez 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 va essayer de forcer le résultat à un tableau multidimensionnel, le cas échéant. Par exemple, si notre fonction renvoie 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 renvoie une matrice bidimensionnelle, sapply fera essentiellement la même chose en traitant chaque matrice renvoyée comme un seul long vecteur:

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

    Sauf si nous spécifions simplify = "array", auquel cas il utilisera les matrices individuelles pour construire un tableau multidimensionnel:

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

    Chacun de ces comportements dépend bien sûr de notre fonction de retour de vecteurs ou de matrices de même longueur ou dimension.

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

    Pour vapply, vous donnez essentiellement R un exemple de ce genre de chose votre fonction reviendra, ce qui peut vous faire gagner un peu de temps valeurs pour 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 - Pour quand vous avez plusieurs structures de données (par ex. vecteurs, listes) et vous souhaitez appliquer une fonction aux 1er éléments de chaque, puis les 2èmes éléments de chacun, etc., contraindre 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 - Une enveloppe à mapply avec SIMPLIFY = FALSE, donc il est garanti de retourner 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
    
  • rapper - Pour quand vous voulez appliquer une fonction à chaque élément d'un liste imbriquée structure, récursivement.

    Pour vous donner une idée de la rareté rapply est, je l'ai oublié lors de la première publication de cette réponse! Évidemment, je suis sûr que beaucoup de gens l'utilisent, mais YMMV. rapply est mieux illustré avec 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 vous voulez appliquer une fonction à sous-ensembles d'un vecteur et les sous-ensembles sont définis par un autre vecteur, généralement un facteur.

    Les moutons noirs de la famille * s'appliquent, en quelque sorte. L'utilisation du fichier d'aide de l'expression "tableau ragged" peut être un peu déroutantmais c'est en fait assez facile.

    Un vecteur:

    x <- 1:20
    

    Un facteur (de même longueur!) Définissant des 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-combiner qui sont commun en R (aggregate, by, ave, ddply, etc.) D'où son statut de mouton noir.


1204
2017-08-21 22:50



Sur la note de côté, voici comment les différents plyr les fonctions correspondent à la base *apply fonctions (de l'intro au document plyr de la page Web 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 nommage 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 la cohérence dans la production, dans cette sortie de dlply() est facilement passable à ldply() pour produire une sortie utile, etc.

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

plyr et reshape fonctions ont remplacé presque toutes ces fonctions dans mon utilisation quotidienne. Mais, aussi à partir du document Intro to Plyr:

Fonctions connexes tapply et sweep n'ont pas de fonction correspondante dans plyret reste utile. merge est utile pour combiner des résumés avec les données d'origine.


171
2017-08-17 19:20



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

apply, sapply, lapply, by, aggregate

(Espérons qu'il est clair que apply correspond à @ Hadley aaply et aggregate correspond à @ Hadley ddply etc. La diapositive 20 du même slideshare clarifiera si vous ne l'obtenez pas à partir de cette image.)

(sur la gauche est l'entrée, sur le haut est la sortie)


118
2017-10-09 05:29



Premier départ avec L'excellente réponse de Joran - doute que tout puisse mieux.

Ensuite, les mnémoniques suivants peuvent aider à se souvenir des distinctions entre chacun. Alors que certains sont évidents, d'autres peuvent l'être moins --- pour ceux-ci vous trouverez une justification dans les discussions de Joran.

Mnémotechnique

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

Construire le bon fond

Si vous utilisez le apply la famille se sent encore un peu étrangère à vous, alors il se peut que vous manquiez un point de vue clé.

Ces deux articles peuvent aider. Ils fournissent le contexte nécessaire pour motiver le techniques de programmation fonctionnelle qui sont fournis par le apply famille de fonctions.

Les utilisateurs de Lisp reconnaîtront le paradigme immédiatement. Si vous n'êtes pas familier avec Lisp, une fois que vous maîtrisez FP, vous aurez acquis un point de vue puissant pour une utilisation dans R - et apply va faire beaucoup plus de sens.


88
2018-04-25 00:20



Depuis que je me suis rendu compte que (très excellent) réponses de ce poste manque de by et aggregate explications. Voici ma contribution.

PAR

le by fonction, comme indiqué dans la documentation peut être cependant, comme un "wrapper" pour tapply. Le pouvoir de by se pose lorsque nous voulons calculer une tâche qui tapplyne 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 "essentiellement" ont les mêmes résultats et les seules différences sont dans la façon dont ils sont montrés et les différents class attributs, respectivement by pour cb et array pour ct.

Comme je l'ai dit, le pouvoir de by se pose 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, disent "nous voulons calculer le summary de toute variable 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 le by La fonction R envoie une méthode spécifique pour data frame classe, puis laissez le summary La fonction fonctionne même si la longueur du premier argument (et le type aussi) sont différentes.

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 en effet et le résultat est très surprenant. C'est un objet de classe by ça le long Species (disons, pour chacun d'eux) calcule la summary de chaque variable.

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

 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 une autre façon d'utiliser 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 aggregate  doit être une liste tout en tapply  pouvez (non obligatoire) être une liste et que la sortie de aggregate est un cadre de données alors que celui de tapply est un array.

Le pouvoir de aggregate est qu'il peut gérer facilement des sous-ensembles de données avec subset argument et qu'il a des méthodes pour ts objets et formula ainsi que.

Ces éléments font aggregate plus facile de travailler avec ça 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 difficile 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 moments 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 Month pour chaque élément et ensuite les combiner (notez également que nous devons appeler le na.rm = TRUE, parce que le formula méthodes de aggregate fonction a 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

avec by nous ne pouvons simplement pas réaliser cela en fait l'appel de fonction suivant renvoie une erreur (mais très probablement il est 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 sont juste dans la classe (et ensuite comment il est montré / imprimé et pas seulement - exemple, comment le sous-ensemble) objet:

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

Le code précédent atteint le même objectif et les mêmes résultats, à certains moments, quel outil utiliser est juste 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-ensembles.


39
2017-08-28 02:28



Il y a beaucoup de bonnes réponses qui discutent des différences dans les cas d'utilisation pour chaque fonction. Aucune réponse ne traite des différences de performance. Cela est raisonnable parce que diverses fonctions attendent diverses entrées et produisent diverses sorties, mais la plupart d'entre elles ont un objectif commun général à évaluer par séries / groupes. Ma réponse va se concentrer sur la performance. En raison de ci-dessus la création d'entrée à partir des vecteurs est inclus dans le calendrier, aussi le applyla fonction n'est pas mesurée.

J'ai testé deux fonctions différentes sum et length immediatement. Le volume testé est de 50M en entrée et de 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 de regarder 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

30
2017-12-08 22:42



Il vaut peut-être la peine de mentionner ave. ave est tapplyLe cousin sympathique. Il renvoie les résultats dans un formulaire que vous pouvez brancher directement dans votre trame 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 paquet de base qui fonctionne comme ave pour des trames de données entières (comme by est comme tapply pour les trames de données). Mais vous pouvez le trafiquer:

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

20
2017-11-06 00:00



En dépit de toutes les bonnes réponses ici, il y a 2 autres fonctions de base qui méritent d'être mentionnées, l'utile outer fonction et l'obscur eapply fonction

extérieur

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

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 appliquer une fonction à deux vecteurs d'entrées. La différence est que mapply appliquera la fonction aux deux premiers éléments, puis les deux autres etc, tandis que outer appliquera la fonction à chaque combinaison d'un élément du premier vecteur et de l'autre 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é ceci quand j'ai un vecteur de valeurs et un vecteur de conditions et que je souhaite voir quelles valeurs répondent aux conditions.

eapply

eapply est comme lapply sauf qu'au lieu d'appliquer une fonction à chaque élément d'une liste, elle applique une fonction à chaque élément d'un environnement. Par exemple si vous voulez trouver 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 vous être utile.


20
2018-05-16 03:59



J'ai récemment découvert le plutôt utile sweep Fonctionnez et ajoutez-le ici par souci d'exhaustivité:

balayage

L'idée de base est de balayage à travers un tableau rangée ou colonne et renvoyer un tableau modifié. Un exemple rendra cela clair (source: datacamp):

Disons que vous avez une matrice et que vous voulez standardiser il en 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 entendu être obtenu plus facilement
  apply(dataPoints, 2, scale)


5
2018-06-16 16:03