Totales agregados por bloques en tablas

En ocasiones uno quiere añadir un total calculado en ciertos bloques a una tabla. Por ejemplo, en la tabla

set.seed(1234)
ventas.orig <- data.frame(cliente = rep(1:10, each = 5), 
                       producto = rep(letters[1:5], times = 10), 
                       importe = rlnorm(50))

tenemos clientes, productos e importes. Y nos preguntamos por el porcentaje en términos de importe que cada producto supone para cada cliente.

Una manera natural pero torpe de realizar este cálculo consiste en usar un objeto intermedio y merge:

library(plyr)
tmp <- ddply(ventas.orig, .(cliente), summarize, total = sum(importe))
ventas <- merge(ventas.orig, tmp)
ventas$pct.producto <- 100 * ventas$importe / ventas$total

No os asustéis, se puede hacer aún peor (p.e., usando sqldf). Pero existen dos maneras, cuando menos, de hacerlo mejor. La primera es usando data.table.

library(data.table)
 
ventas <- data.table(ventas.orig)
ventas[, total.cliente := sum(importe), by = cliente]
ventas$pct.producto <- 100 * ventas$importe / ventas$total.cliente

El operador := es el que hace la magia en la segunda línea. Una ventaja de data.table es que vuela literalmente con conjuntos de datos semigrandes.

También es posible hacerlo todavía más sucintamente con plyr:

library(plyr)
ventas <- ddply(ventas.orig, .(cliente), transform, pct.producto = 100 * importe / sum(importe))

Una única línea. El problema de plyr, sin embargo, es que es ineficiente con conjuntos de datos grandecitos.

Tengo pendiente hacerme con dplyr. Dicen que combina lo mejor de ambos mundos (plyr y data.table). Espero que pronto podamos saberlo todos.

4 comentarios sobre “Totales agregados por bloques en tablas

  1. alberto 25 marzo, 2014 10:24

    Hola Carlos,
    Una duda: En la primera versión (plyr/merge) la linea que dice:
    ventas <- merge(ventas, tmp)
    no debería ser:
    ventas <- merge(ventas.orig, tmp)
    El objeto "ventas" no ha sido aun declarado, por eso al menos a mi me da error.
    En cualquier caso, muchas gracias, gran post!!!

  2. José Luis 26 marzo, 2014 22:26

    Buena entrada. Con dplyr creo que sería algo así
    library(dplyr)

    vent.grp <- group_by(ventas.orig,cliente)

    ventas <- mutate(vent.grp,pct =100*importe/sum(importe))

    El paso de group_by aunque parezca que no haga nada es necesario

  3. alberto 27 marzo, 2014 10:33

    Muchas gracias Carlos, me ha gustado mucho el post!!

Los comentarios están desabilitados.