<- read.table("data/pob_aragon_2014.csv",
pob_aragon_2014 header = T, sep = "\t")
pob_aragon_2014
Provincia Hombres Mujeres
1 Huesca 113840 111069
2 Teruel 71449 68916
3 Zaragoza 471675 488436
En contra de la opinión generalizada, una parte fundamental del trabajo con R consiste en manipular y transformar datos para darles la forma necesaria para su uso en un análisis posterior. En las secciones anteriores hemos explorado ya algunas técnicas básicas que pueden usarse para este fin. En esta y alguna de las siguientes vamos a presentar ciertas metatransformaciones de datos, i.e., operaciones genéricas que subyacen a muchas transformaciones específicas.
En concreto, en esta sección exploraremos el paquete reshape2
que contiene, esencialmente, dos funciones: melt
y dcast
1. La primera, a partir de una tabla, crea una versión en formato largo, arreglado2 o tidy. La función dcast
es una inversa —y algo más— de melt
. Entre otras cosas, dcast
incorpora a R operaciones análogas a las pivot tables de Excel.
Una tabla está en formato largo cuando cada fila contiene un dato y el resto de las columnas son etiquetas que le dan contexto. Además de ser más claras conceptualmente, suele ser más sencillo procesar tablas en formato largo. A los interesados en saber más sobre este tipo de datos se les recomienda leer el artículo Tidy Data3.
Vamos a comenzar leyendo un fichero de datos que contiene la población en 2014 de las provincias aragonesas por sexo. Se trata de un conjunto de datos simple y minúsculo pero que, gracias a eso, permite visualizar las transformaciones que vamos a realizar.
<- read.table("data/pob_aragon_2014.csv",
pob_aragon_2014 header = T, sep = "\t")
pob_aragon_2014
Provincia Hombres Mujeres
1 Huesca 113840 111069
2 Teruel 71449 68916
3 Zaragoza 471675 488436
Es habitual encontrar conjuntos de datos (aunque típicamente más grandes) en formatos similares. Estos datos no están en formato largo o arreglado. En tablas arregladas, las columnas representan necesariamente variables. Sin embargo, en pob_aragon_2014
existe una variable implícita, sexo
de la que Hombres
y Mujeres
son niveles (o valores).
Podemos usar la función melt
para pivotar la tabla y arreglar los datos, i.e., disponerlos en formato largo:
library(reshape2)
melt(pob_aragon_2014)
Provincia variable value
1 Huesca Hombres 113840
2 Teruel Hombres 71449
3 Zaragoza Hombres 471675
4 Huesca Mujeres 111069
5 Teruel Mujeres 68916
6 Zaragoza Mujeres 488436
Es evidente que la información contenida en ambos conjuntos de datos es la misma. Pero en la nueva tabla cada fila tiene un único valor. El resto de las columnas, Provincia
y variable
—como R desconoce que el nombre ideal de la nueva variable sería sexo
, por defecto, la denomina variable
— contextualizan dicha cifra.
Un ejemplo un poco más intersante es este:
<- read.table("data/pob_aragon.csv",
pob_aragon header = T, sep = "\t")
pob_aragon
Provincia Periodo Hombres Mujeres
1 Huesca 2014 113840 111069
2 Huesca 2004 107961 104940
3 Teruel 2014 71449 68916
4 Teruel 2004 71073 68260
5 Zaragoza 2014 471675 488436
6 Zaragoza 2004 441840 455510
El nuevo conjunto de datos tiene una variable adicional, Periodo
. Si se le aplica melt
directamente, el resultado dista del esperado:
melt(pob_aragon)
Provincia variable value
1 Huesca Periodo 2014
2 Huesca Periodo 2004
3 Teruel Periodo 2014
4 Teruel Periodo 2004
5 Zaragoza Periodo 2014
6 Zaragoza Periodo 2004
7 Huesca Hombres 113840
8 Huesca Hombres 107961
9 Teruel Hombres 71449
10 Teruel Hombres 71073
11 Zaragoza Hombres 471675
12 Zaragoza Hombres 441840
13 Huesca Mujeres 111069
14 Huesca Mujeres 104940
15 Teruel Mujeres 68916
16 Teruel Mujeres 68260
17 Zaragoza Mujeres 488436
18 Zaragoza Mujeres 455510
Efectivamente, R confunde Periodo
con un nivel más de la nueva variable, junto a Hombres
y Mujeres
. Pero, en realidad, tanto la provincia como el periodo forman la clave de la tabla. Esto se le puede indicar a melt
así:
melt(pob_aragon, id.vars = c("Provincia", "Periodo"))
Provincia Periodo variable value
1 Huesca 2014 Hombres 113840
2 Huesca 2004 Hombres 107961
3 Teruel 2014 Hombres 71449
4 Teruel 2004 Hombres 71073
5 Zaragoza 2014 Hombres 471675
6 Zaragoza 2004 Hombres 441840
7 Huesca 2014 Mujeres 111069
8 Huesca 2004 Mujeres 104940
9 Teruel 2014 Mujeres 68916
10 Teruel 2004 Mujeres 68260
11 Zaragoza 2014 Mujeres 488436
12 Zaragoza 2004 Mujeres 455510
Los datos en formato largo están muy relacionados con los arrays (conocidos en algunos contextos empresariales como cubos multidimensionales). Un ejemplo de ellos es la tabla Titanic
de R, que se refiere a los viajeros del Titanic, y que tiene cuatro dimensiones: la clase, el sexo, el grupo de edad y si sobrevivieron o no al hundimiento:
dimnames(Titanic)
$Class
[1] "1st" "2nd" "3rd" "Crew"
$Sex
[1] "Male" "Female"
$Age
[1] "Child" "Adult"
$Survived
[1] "No" "Yes"
La función as.data.frame
aplicada a un array multidimensional produce una tabla en formato largo:
<- as.data.frame(Titanic)
tmp rbind(head(tmp), tail(tmp))
Class Sex Age Survived Freq
1 1st Male Child No 0
2 2nd Male Child No 0
3 3rd Male Child No 35
4 Crew Male Child No 0
5 1st Female Child No 0
6 2nd Female Child No 0
27 3rd Male Adult Yes 75
28 Crew Male Adult Yes 192
29 1st Female Adult Yes 140
30 2nd Female Adult Yes 80
31 3rd Female Adult Yes 76
32 Crew Female Adult Yes 20
Ejercicio 7.1 La función melt
también funciona con matrices. Una de ellas es VADeaths
. Usa melt
para disponer esa matriz en formato largo. Verás que aparece una columna con valores del tipo Rural Male
: construye a partir de ella dos columnas (sex
y residency
). Nota: existe una función auxiliar en reshape2
, colsplit
que realiza precisamente esa operación.
Ejercicio 7.2 Toma el conjunto de datos airquality
y disponlo en formato largo. Ten cuidado con la definición de la clave de la tabla.
Ejercicio 7.3 Cambia el nombre a la variable variable
creada automáticamente por melt
en el ejercicio anterior.
Ejercicio 7.4 Calcula el valor mediano (median
) de las variables de airquality
(después de haberlo dispuesto en formato largo). Usa la función tapply
.
En general, disponer datos en formato largo facilita muchas operaciones de manipulación de datos. En el ejercicio anterior, en lugar de calcular la mediana variable a variable, ha sido posible obtenerla en dos líneas de código. Que son independientes, además, del número de indicadores de la tabla.
A partir del formato largo se puede pasar a distintos tipos de formatos no largos (o anchos) usando la función dcast
. Por ejemplo, a partir de los datos
<- melt(pob_aragon_2014)
pob_aragon_2014_largo pob_aragon_2014_largo
Provincia variable value
1 Huesca Hombres 113840
2 Teruel Hombres 71449
3 Zaragoza Hombres 471675
4 Huesca Mujeres 111069
5 Teruel Mujeres 68916
6 Zaragoza Mujeres 488436
se pueden poner las pronvicias en filas
dcast(pob_aragon_2014_largo, Provincia ~ variable)
Provincia Hombres Mujeres
1 Huesca 113840 111069
2 Teruel 71449 68916
3 Zaragoza 471675 488436
o en columnas:
dcast(pob_aragon_2014_largo, variable ~ Provincia)
variable Huesca Teruel Zaragoza
1 Hombres 113840 71449 471675
2 Mujeres 111069 68916 488436
En su versión más simple, dcast
necesita:
+
, delante de ~
)~
)La función dcast
utiliza una serie de reglas simples para determinar cuál es la columna de la que extraer los datos que incluir en la tabla resultante. Pero, a veces, como en el ejemplo que sigue, se equivoca. Por ejemplo, vamos a intentar medir el incremento en las horas de sueño (extra
) producidas por el tratamiento (o group
) 2 sobre el 1 en el conjunto de datos sleep
. Este conjunto de datos se refiere a individuos (ID
) que fueron sometidos a dos tratamientos distintos. Podemos hacer
dcast(sleep, ID ~ group)
Using ID as value column: use value.var to override.
ID 1 2
1 1 1 1
2 2 2 2
3 3 3 3
4 4 4 4
5 5 5 5
6 6 6 6
7 7 7 7
8 8 8 8
9 9 9 9
10 10 10 10
para intentar colocar los dos valores correspondientes a cada individuo en la misma fila para poder restarlos después. Sin embargo, el efecto no es el deseado y el warning de R es muy informativo sobre el problema: que dcast
ha elegido como variable de valor ID
en lugar de extra
. Además, indica cómo forzar una alternativa a la que usa por defecto, i.e., usando value.var
:
dcast(sleep, ID ~ group, value.var = "extra")
ID 1 2
1 1 0.7 1.9
2 2 -1.6 0.8
3 3 -0.2 1.1
4 4 -1.2 0.1
5 5 -0.1 -0.1
6 6 3.4 4.4
7 7 3.7 5.5
8 8 0.8 1.6
9 9 0.0 4.6
10 10 2.0 3.4
Ahora es posible añadir la columna adicional de interés haciendo
<- dcast(sleep, ID ~ group, value.var = "extra")
tmp $diff <- tmp[, "2"] - tmp[, "1"]
tmphead(tmp, 3)
ID 1 2 diff
1 1 0.7 1.9 1.2
2 2 -1.6 0.8 2.4
3 3 -0.2 1.1 1.3
Ejercicio 7.5 as.data.frame(Titanic)
es una tabla en formato largo que contiene información sobre los viajeros que sobrevivieron o perecieron el naufragio del Titanic. Calcula la proporción de viajeros que sobrevivieron por clase, sexo y grupo de edad. Nota: recuerda que para indicar varias filas, ya sea en filas o columnas (aunque lo segundo no se recomienda), los nombres de columna tienen que indicarse separados del signo +
.
No obstante, al usar dcast
pueden ocurrir situaciones como que ilustra el ejemplo siguiente. En primer lugar construimos una versión larga de iris
:
<- melt(iris)
iris_long head(iris_long, 4)
Species variable value
1 setosa Sepal.Length 5.1
2 setosa Sepal.Length 4.9
3 setosa Sepal.Length 4.7
4 setosa Sepal.Length 4.6
Hemos hecho la operación melt(iris)
para crear un conjunto de datos de práctica. Sin embargo, en esta operación se pierde información: ya no sabemos a qué fila de la tabla original corresponden las de la versiòn arreglada. Por lo tanto, la reconstrucción de los datos originales es imposible.
Ahora, al hacer
dcast(iris_long, Species ~ variable)
Aggregation function missing: defaulting to length
Species Sepal.Length Sepal.Width Petal.Length Petal.Width
1 setosa 50 50 50 50
2 versicolor 50 50 50 50
3 virginica 50 50 50 50
obtenemos un resultado extraño (además de un warning). Se debe a que en cada posición de la tabla resultante hay más de un valor candidato; de hecho, 50 de ellos. La función dcast
, por defecto, combina esos valores usando la función length
. Sin embargo, es posible especificar otras:
dcast(iris_long, Species ~ variable, fun.aggregate = mean)
Species Sepal.Length Sepal.Width Petal.Length Petal.Width
1 setosa 5.006 3.428 1.462 0.246
2 versicolor 5.936 2.770 4.260 1.326
3 virginica 6.588 2.974 5.552 2.026
La función dcast
admite otras invocaciones equivalentes. Por ejemplo, como hemos visto más arriba, se puede especificar explícitamente la variable sobre la que opera la nueva función:
dcast(iris_long, Species ~ variable, fun.aggregate = mean, value.var = "value")
No obstante, la opción más sucinta es
dcast(iris_long, Species ~ variable, mean)
Ejercicio 7.6 Calcula la media de cada variable meteorológica de airquality
por mes.
Para los ejercicios siguientes vamos a utilizar una tabla descargada del INE que contiene información sobre la situación laboral de los ciudadanos de todas las provincias de España por periodo, provincia y sexo.
<- read.table("data/paro.csv", header = T, sep = "\t") paro
Los valores que aparecen en la tabla son valores absolutos: es el número de personas en miles.
Los datos están en formato largo, pero se pueden pivotar, p.e., así:
<- dcast(paro, Gender + Provinces + Periodo ~ Situation) paro_alt
Ejercicio 7.7 Añade a paro_alt
una columna adicional con la tasa de paro (desempleados entre población activa).
El ejercicio anterior demuestra cómo en ocasiones es conveniente crear un determinado tipo de formato ancho para operar sobre los datos más fácilmente. En concreto, para colocar en la misma fila valores sobre los que realizar operaciones; en nuestro caso, un cociente.
Ejercicio 7.8 Identifica las provincias y periodos en los que la tasa de paro masculina supera a la femenina.
Si se utilizan todas las variables que forman parte de la clave de una tabla en la fórmula de dcast
, en cada casilla de la tabla resultante aparece una única observación. Si se omite una o más, comienzan a aparecer valores repetidos y eso permite utilizar funciones de agregación para realizar operaciones similares a un group by
de SQL.
Ejercicio 7.9 Obtén totales para toda España (i.e., ignorando la provincia y el sexo) usando dcast
.
No obstante, en una sección posterior veremos una manera alternativa, la recomendada, para realizar este tipo de agregaciones.
En esta sección hemos aprendido a distinguir entre datos en formato largo (o arreglados) y ancho. Los dos tipos tienen sus ventajas e inconvenientes. Los datos arreglados son conceptualmente más simples de describir y comprender. Además, facilitan ciertas transformaciones, como las operaciones por bloques o el filtrado (selección de un determinado subconjunto de las filas).
Muchas transformaciones de datos implican operar sobre valores que, en una tabla arreglada, estarían en filas distintas. Para ello es necesario yuxtaponerlos, i.e., colocarlos en una misma fila. Para ello es necesario pivotar la tabla. Suele ser habitual volver a arreglar los datos después de realizadas estas transformaciones.
De hecho, existen flujos de trabajo de manipulación de datos que consisten en una secuencia de pivotaciones entre el formato largo y distintas versiones de formatos anchos en los que se realizan determinadas operaciones.
Estas operaciones de pivotación no son sencillas con otras herramientas habituales, como SQL o Spark. En R existe una función de serie, reshape
, que permite realizar estas transformaciones. Sin embargo, no se recomienda su uso: es muy mejorable, particularmente, en lo concerniente a su usabilidad.
Debido a esas limitaciones, se introdujo en R el paquete reshape2
(que es, a su vez, una evolución de un paquete anterior, reshape
). Este paquete contiene esencialmente dos funciones fundamentales, melt
y dcast
, que son las que hemos tratado aquí.
El autor de este paquete teorizó más tarde sobre los datos en formatos anchos y largos en el artículo, muy recomendable, Tidy Data. Además, a raíz de ese artículo, desarrolló un paquete adicional, tidyr
. El paquete tidyr
tiene una función similar a melt
, gather
y otra similar a dcast
, spread
. En http://www.milanor.net/blog/reshape-data-r-tidyr-vs-reshape2/ se comparan ambos paquetes.
Ejercicio 7.10 Identifica las provincias en las que más crece la tasa de paro en 2014.
Ejercicio 7.11 Lee este fichero (puedes hacerlo pasándole directamente la URL a read.table
) y dispónlo en formato largo.
Ejercicio 7.12 Lee este fichero que contiene datos de casos de tuberculosis por país y año partido por sexos (m/f) y grupos de edad (u
es desconocido). Ponlo en formato largo descomponiendo las variables que combinan edad y sexo convenientemente. Después, calcula el número de casos:
Ejercicio 7.13 Usa el conjunto de datos de los casos de tuberculosis y repite el ejercicio de la agregación pero solo para algún país concreto.
La traducción de estos dos verbos es, respectivamente, fundir y moldear; es conviente recordarlo.↩︎
De acuerdo con el DRAE, arreglado significa reducido o sujeto a regla; efectivamente, veremos cómo los datos en formato largo siguen una serie de reglas que se echan mucho de menos cuando no se cumplen.↩︎
Cuyo autor lo es también del paquete reshape2
, el tema de esta sección↩︎