Code R idiomatique pour partitionner un vecteur par un index et effectuer une opération sur cette partition

j'essaie de trouver la façon idiomatique dans R pour diviser un vecteur numérique par un vecteur d'indice, trouver la somme de tous les nombres dans cette partition et puis diviser chaque entrée individuelle par cette somme de partition. En d'autres termes, si je commence avec ceci:

df <- data.frame(x = c(1,2,3,4,5,6), index = c('a', 'a', 'b', 'b', 'c', 'c'))

je veux que la sortie de créer un vecteur (z):

c(1/(1+2), 2/(1+2), 3/(3+4), 3/(3+4), 5/(5+6), 6/(5+6))  

Si je devais le faire, c'est SQL et pourrait utiliser les fonctions de la fenêtre, je ferais ceci:

select 
 x / sum(x) over (partition by index) as z 
from df

et si j'utilisais plyr, je ferait quelque chose comme ceci:

ddply(df, .(index), transform, z = x / sum(x))

mais je voudrais savoir comment le faire en utilisant les outils de programmation fonctionnelle standard R comme mapply / aggregate etc.

18
demandé sur Arun 2012-05-25 07:51:30

3 réponses

une autre option est ave. Pour une bonne mesure, j'ai recueilli les réponses ci-dessus, essayé de mon mieux de faire leur équivalent de sortie (un vecteur), et fourni des temps plus de 1000 passes en utilisant vos données d'exemple comme une entrée. Tout d'abord, ma réponse à l'aide de ave:ave(df$x, df$index, FUN = function(z) z/sum(z)). Je montre aussi un exemple en utilisant data.table package car il est généralement assez rapide, mais je sais que vous êtes à la recherche de solutions de base, donc vous pouvez ignorer que si vous voulez.

Et maintenant un tas de minutage:

library(data.table)
library(plyr)
dt <- data.table(df)

plyr <- function() ddply(df, .(index), transform, z = x / sum(x))
av <- function() ave(df$x, df$index, FUN = function(z) z/sum(z))
t.apply <- function() unlist(tapply(df$x, df$index, function(x) x/sum(x)))
l.apply <- function() unlist(lapply(split(df$x, df$index), function(x){x/sum(x)}))
b.y <- function() unlist(by(df$x, df$index, function(x){x/sum(x)}))
agg <- function() aggregate(df$x, list(df$index), function(x){x/sum(x)})
d.t <- function() dt[, x/sum(x), by = index]

library(rbenchmark)
benchmark(plyr(), av(), t.apply(), l.apply(), b.y(), agg(), d.t(), 
           replications = 1000, 
           columns = c("test", "elapsed", "relative"),
           order = "elapsed")
#-----

       test elapsed  relative
4 l.apply()   0.052  1.000000
2      av()   0.168  3.230769
3 t.apply()   0.257  4.942308
5     b.y()   0.694 13.346154
6     agg()   1.020 19.615385
7     d.t()   2.380 45.769231
1    plyr()   5.119 98.442308

lapply() solution semble gagner dans cette affaire, et data.table() est étonnamment lente. Voyons comment cette échelle à un plus grand problème d'agrégation:

df <- data.frame(x = sample(1:100, 1e5, TRUE), index = gl(1000, 100))
dt <- data.table(df)

#Replication code omitted for brevity, used 100 replications and dropped plyr() since I know it 
#will be slow by comparison:
       test elapsed  relative
6     d.t()   2.052  1.000000
1      av()   2.401  1.170078
3 l.apply()   4.660  2.270955
2 t.apply()   9.500  4.629630
4     b.y()  16.329  7.957602
5     agg()  20.541 10.010234

cela semble plus cohérent avec ce que je m'attends.

En résumé, vous avez beaucoup de bonnes options. Trouvez une ou deux méthodes qui fonctionnent avec votre modèle mental de la façon dont les tâches d'agrégation devraient fonctionner et maîtriser cette fonction. Plusieurs façons de dépecer un chat.

Modifier - et un exemple avec 1e7 lignes

Probablement pas assez grand pour Matt, mais aussi grande que mon ordinateur portable peut gérer sans s'écraser:

df <- data.frame(x = sample(1:100, 1e7, TRUE), index = gl(10000, 1000))
dt <- data.table(df)
#-----
       test elapsed  relative
6     d.t()    0.61  1.000000
1      av()    1.45  2.377049
3 l.apply()    4.61  7.557377
2 t.apply()    8.80 14.426230
4     b.y()    8.92 14.622951
5     agg()   18.20 29.83606
26
répondu Chase 2012-06-22 23:22:29

Si vous êtes d'exploitation uniquement sur un seul vecteur et seulement besoin d'un seul vecteur d'indexation puis tapply est assez rapide

dat <- 1:6
lev <- rep(1:3, each = 2)
tapply(dat, lev, function(x){x/sum(x)})
#$`1`
#[1] 0.3333333 0.6666667
#
#$`2`
#[1] 0.4285714 0.5714286
#
#$`3`
#[1] 0.4545455 0.5454545
#
unlist(tapply(dat, lev, function(x){x/sum(x)}))
#       11        12        21        22        31        32 
#0.3333333 0.6666667 0.4285714 0.5714286 0.4545455 0.5454545 
8
répondu Dason 2012-05-25 04:00:58

Trois autres approches ainsi:

dat <- 1:6
lev <- rep(1:3, each = 2)

lapply(split(dat, lev), function(x){x/sum(x)})
by(dat, lev, function(x){x/sum(x)})
aggregate(dat, list(lev), function(x){x/sum(x)})
8
répondu Tyler Rinker 2012-05-25 04:22:15