Title
El paquete
reshape2, creado por Hadley Wickham permite transformar y agregar datos con dos funciones: melt y cast. En esta entrada nos vamos a centrar en la agregación de datos para obtener resultados similares a los obtenidos con las tablas dinámicas en Excel. El objetivo de la agregación es ordenar, reducir y mostrar la información resumida de manera que nos facilite responder a preguntas planteadas sobre nuestros datos.
Partimos de la base de datos Northwind. Exportamos la consulta Análisis de ventas a Excel y creamos una tabla dinámica con la que iremos comparando los resultados que deseamos obtener en R. Desde Excel guardamos la hoja de datos como fichero CSV que importaremos en R.
Puedes descargar el fichero Análisis de ventas.csv y copiarlo en tu directorio de trabajo.
Importación y formato
1. Importamos el fichero CSV. Es importante observar el formato de la columna con valores. El símbolo de moneda si lo hubiera, los separadores decimal y de miles. Previamente instalamos los paquetes necesarios.
require(plyr) # Para usar la función . de plyr
require(reshape2)
data <- read.csv("Análisis de ventas.csv", sep = ";")
2. Formateamos los datos importados.
names(data) <- tolower(names(data)) # Nombres de columnas en minúsculas
data$ventas <- sub("([.])", "", data$ventas) # Separador de miles la coma
data$ventas <- sub(",", ".", data$ventas) # Separador decimal el punto
data$ventas <- as.numeric(data$ventas) # Ventas tipo de variable numérica
data$fecha.de.pedido <- as.Date(data$fecha.de.pedido, "%d/%m/%Y") # Fechas
reshape2
3. Definimos identifiers (id.vars) y measured variables (measure.vars) con melt. Identificadores y valores, el equivalente en las tablas dinámicas de Excel a etiquetas o rótulos de filas y valores.
data.m <- melt(data, id.vars = c(1:19), measure.vars = 6)
data.m # Resultado:
cliente mesdetrimestre variable value
1 Compañía AA 1 ventas 1400
2 Compañía AA 1 ventas 105
3 Compañía D 1 ventas 300
4 Compañía D 1 ventas 530
5 Compañía D 1 ventas 35
6 Compañía L 1 ventas 270
A la derecha de la última id.vars (mesdetrimestre en este caso) crea dos columnas, con los nombres variable y value. Bajo variable sólo incluirá la variable ventas pues es la única definida como
measured variable.
4. Comenzamos a agregar datos usando dcast.
a. Agrupando por empleado. Queremos el siguiente resultado.
data.c <- dcast(data.m, empleado ~ variable, sum, margins = "empleado")
data.c # Resultado:
empleado ventas
1 Francisco Chaves 19974.25
2 Humberto Acevedo 3786.50
3 Jesús Escolar 2617.50
4 Juan Carlos Rivas 6378.00
5 Luis Bonifaz 680.00
6 María González 6561.00
7 María Jesús Cuesta 6278.00
8 Pilar Pinilla Gallego 5787.50
9 (all) 52062.75
Argumentos de dcast:
data: molten data frame. El data frame obtenido con melt anteriormente.
formula: especificamos las variables que deseamos incluir, en este caso empleado. Hay un par de caracteres especiales: "..." representa todas las otras variables no usadas en la fórmula y "." representa a ninguna variable.
fun.aggregate: la función de agregación de la función, en ese caso sum.
margins: para añadir los subtotales, margins = TRUE devolverá todos los subtotales disponibles.
fill: valor que sustituirá a aquellos que faltan cuando fun.aggregate se aplica sobre un vector de longitud 0.
Más ejemplos:
# Total de ventas
data.c <- dcast(data.m, . ~ variable, sum)
# Agrupar por otra variable, nombre de cliente
data.c <- dcast(data.m, nombre.del.cliente ~ variable, sum, margins = "nombre.del.cliente")
Función . de plyr para filtrar
5. Agrupamos por nombre de cliente y filtramos por el empleado Francisco Chaves. Queremos el siguiente resultado.
data.c <- dcast(data.m, nombre.del.cliente ~ variable, sum, margins = "nombre.del.cliente", subset = .(empleado == "Francisco Chaves"))
data.c
nombre.del.cliente ventas
1 Compañía AA 1505.00
2 Compañía BB 13800.00
3 Compañía D 184.00
4 Compañía Y 860.00
5 Compañía Z 3625.25
6 (all) 19974.25
Más consultas
Jugamos con los elementos anteriores, tomados de reshape2, plyr, y los operadores lógicos. Practicamos la sintaxis e interrogamos nuestros datos hasta que confiesen.1
6. Queremos saber las ventas de los empleados Francisco Chaves y Humberto Acevedo, agrupadas por categoría con su correspondiente subtotal por empleado. El resultado sería:
Añadimos dos identifiers (rótulos de fila), agrupamos por el total de categoría de cada empleado y filtramos por dos empleados, Francisco Chaves o Humberto Acevedo.
data.c <- dcast(data.m, empleado + categoría ~ variable, sum, margins = "categoría", subset = .(empleado == "Francisco Chaves" | empleado == "Humberto Acevedo"))
empleado categoría ventas
1 Francisco Chaves Aceite 533.75
2 Francisco Chaves Bebidas 15200.00
3 Francisco Chaves Carne enlatada 552.00
4 Francisco Chaves Condimentos 660.00
5 Francisco Chaves Frutos secos 105.00
6 Francisco Chaves Mermeladas y confituras 2250.00
7 Francisco Chaves Productos horneados 384.00
8 Francisco Chaves Sopas 289.50
9 Francisco Chaves (all) 19974.25
10 Humberto Acevedo Pasta 1950.00
11 Humberto Acevedo Productos lácteos 1740.00
12 Humberto Acevedo Sopas 96.50
13 Humberto Acevedo (all) 3786.50
Más ejemplos:
# Por empleado y categoría, excluyendo la Compañía H de los clientes
data.c <- dcast(data.m, empleado + categoría ~ variable, sum, margins = c("categoría", "empleado"), subset = .(nombre.del.cliente != "Compañía H"))
# Otras funciones de agregación: mínimo, media y máximo
# Ventas por nombre del cliente
data.c <- dcast(data.m, nombre.del.cliente ~ variable, min, fill = 0)
data.c <- dcast(data.m, nombre.del.cliente ~ variable, mean, fill = 0)
data.c <- dcast(data.m, nombre.del.cliente ~ variable, max, fill = 0)
Introducimos el argumento fill para evitar el mensaje de advertencia.
Fechas
7. La venta del empleado María Jesús Cuesta por producto entre dos fechas.
Opción 1
# Genera un error
data.c <- dcast(data.m, fecha.de.pedido + producto ~ variable, margins = c("fecha.de.pedido", "producto"), sum, subset = .(fecha.de.pedido > "2006-03-01" & fecha.de.pedido < "2006-05-01" & empleado == "María Jesús Cuesta"))
str(data.c) # Estructura de un objeto
La inclusión de la fecha para los subtotales (
margins) y del filtro por fecha genera un error. Y transforma fecha.de.pedido de
Date a
Factor. Se comprueba con la función
str que muestra la estructura interna del objeto.
Warning messages:
1: In Ops.factor(fecha.de.pedido, "2006-03-01") :
> not meaningful for factors
2: In Ops.factor(fecha.de.pedido, "2006-05-01") :
< not meaningful for factors
> str(data.c)
'data.frame': 1 obs. of 3 variables:
$ fecha.de.pedido: Factor w/ 24 levels "2006-01-15","2006-01-20",..: NA
$ producto : Factor w/ 24 levels "Aceite de oliva Northwind Traders",..: NA
$ NA : num NA
Opción 2
# Subtotales incompletos
data.c <- dcast(data.m, as.Date(fecha.de.pedido) + producto ~ variable, sum, margins =c("fecha.de.pedido", "producto"), subset = .(fecha.de.pedido > "2006-03-01" & fecha.de.pedido < "2006-05-01" & empleado == "María Jesús Cuesta"))
data.c
No genera un error pero presenta uno subtotales incompletos, solamente por producto.
as.Date(fecha.de.pedido) producto ventas
1 2006-03-24 Té verde Northwind Traders 598
2 2006-03-24 (all) 598
3 2006-04-05 Galletas de chocolate surtidas Northwind Traders 230
4 2006-04-05 Salsa curry Northwind Traders 1000
5 2006-04-05 (all) 1230
6 2006-04-25 Almíbar Northwind Traders 500
7 2006-04-25 Salsa curry Northwind Traders 120
8 2006-04-25 (all) 620
Opción 3
# Resultado correcto
fecha <- data.m$fecha.de.pedido
data.d <- data.m[fecha > "2006-03-01" & fecha < "2006-05-01", ]
data.c <- dcast(data.d, fecha.de.pedido + producto ~ variable, sum, margins = c("fecha.de.pedido", "producto"), subset = .(empleado == "María Jesús Cuesta"))
data.c
Filtramos previamente el
molten data frame creado anteriormente con melt (punto 4).
fecha.de.pedido producto ventas
1 2006-03-24 Té verde Northwind Traders 598
2 2006-03-24 (all) 598
3 2006-04-05 Galletas de chocolate surtidas Northwind Traders 230
4 2006-04-05 Salsa curry Northwind Traders 1000
5 2006-04-05 (all) 1230
6 2006-04-25 Almíbar Northwind Traders 500
7 2006-04-25 Salsa curry Northwind Traders 120
8 2006-04-25 (all) 620
9 (all) (all) 2448
Referencias:
Introducción y
manual
1 Ronald Coase: "If you torture the data enough, nature will always confess." ↩