Itzulpena: gather() funtzioa erabiltzeko kasuak

Casos de uso para la función gather()

26 de noviembre de 2018

Herramientas: tidyr, ggplot2, dplyr

Hace unos días @inesgn comentaba esto en twitter.

Aunque puse algunos ejemplos en el mismo hilo, creo que puede ser útil documentar un poco mejor los tipos de casos de datasets con los que he solido usar las función gather().

Los dos primeros ejemplos son muy habituales cuando utilizamos datos de terceros (portales opendata…); el último ejemplo es algo distinto, ya que partimos de un dataset en formato tidy que tenemos que reestructurar por las características del sistema gráfico y cómo mapeamos las variables del dataset a variables gráficas.

Finalmente, traigo un ejemplo en el que gather() se usa junto con otras funciones para agregar datos.

Tablas cruzadas

Cuando trabajamos con datos de fuentes externas, es muy habitual que no tratemos con las fuentes originales, sino con algún tipo resumen a modo de datos agregados; además, es muy habitual que, para facilitar la lectura de los datos a los seres humanos, hayan creado alguna tabla cruzada, en la que los valores de una variable pasan a ser las columnas de la nueva tabla, como por ejemplo esta tabla de “ÍNDICES ANUALES DE PRECIOS PERCIBIDOS EN LOS SECTORES AGROFORESTAL Y PESQUERO”.

Los nombre de las columnas son números, por lo que para seleccionarlas sin problemas usamos su índice.

library(tidyverse)
library(readxl)

# antes de cargar el archivo Excel en R hemos descombinado todas las celdas

tabla <- read_excel("Precio_indagroforestpesca_9903.xls", range = cell_rows(8:30))

tabla_reestructurada <- tabla %>%
  gather(key=año, value=indice, 2:7)
head(tabla_reestructurada)

## # A tibble: 6 x 4
##   `Azpisectoreak / Grupo de Productos`        `Peso Relativo` año   indice
##   <chr>                                                 <dbl> <chr>  <dbl>
## 1 Zerealak / Cereales (*)                                 0.2 1995     100
## 2 Lekadunak / leguminosas(*)                              0   1995     100
## 3 Tuberkuluak / Tuberculos (*)                            0.1 1995     100
## 4 Industri laboreak / Cultivos industriales ~             0.1 1995     100
## 5 Barazkiak / Hortalizas (*)                              0.2 1995     100
## 6 Fruituak / Frutas (*)                                   0   1995     100

En casos más complejos, las tablas cruzadas se han elaborado a partir de más de dos variables, como éstas del Índice de incidencia (por mil) de accidentes de trabajo con baja en jornada laboral por mes y acumulado en el periodo por sexo, territorio y sector en el 2017 disponibles en el portal Open Data Euskadi: provincia, sector, sexo, año e tipo de índice.

Itinerarios de A a B

Otra estructura de datos habitual y para la que podemos necesitar una reestructuración es aquella en la que tenemos información de itinerarios con información sobre los puntos de inicio y final. Este es un dataset que he utilizado en varias clases sobre visualización de datos: Metro Share Bike (Los Ángeles).

library(tidyverse)

download.file("https://bikeshare.metro.net/wp-content/uploads/2018/10/metro-bikshare-trips-2018-q3.csv.zip", "recorridos.zip")
recorridos <- read_csv(unz("recorridos.zip","metro-bike-share-trips-2018-q3.csv"))

## Parsed with column specification:
## cols(
##   trip_id = col_integer(),
##   duration = col_integer(),
##   start_time = col_datetime(format = ""),
##   end_time = col_datetime(format = ""),
##   start_station = col_integer(),
##   start_lat = col_double(),
##   start_lon = col_double(),
##   end_station = col_integer(),
##   end_lat = col_double(),
##   end_lon = col_double(),
##   bike_id = col_character(),
##   plan_duration = col_integer(),
##   trip_route_category = col_character(),
##   passholder_type = col_character()
## )

as.tibble(recorridos)

## # A tibble: 95,283 x 14
##     trip_id duration start_time          end_time            start_station
##       <int>    <int> <dttm>              <dttm>                      <int>
##  1 94851140        8 2018-07-01 00:04:00 2018-07-01 00:12:00          3058
##  2 94851141        8 2018-07-01 00:04:00 2018-07-01 00:12:00          3058
##  3 94851138       15 2018-07-01 00:09:00 2018-07-01 00:24:00          4147
##  4 94851137        7 2018-07-01 00:22:00 2018-07-01 00:29:00          4157
##  5 94851136       35 2018-07-01 00:23:00 2018-07-01 00:58:00          3013
##  6 94851135        6 2018-07-01 00:38:00 2018-07-01 00:44:00          3029
##  7 94851134        5 2018-07-01 00:39:00 2018-07-01 00:44:00          3029
##  8 94857739       33 2018-07-01 00:40:00 2018-07-01 01:13:00          3069
##  9 94851133        6 2018-07-01 00:44:00 2018-07-01 00:50:00          3067
## 10 94851132        3 2018-07-01 00:46:00 2018-07-01 00:49:00          3034
## # ... with 95,273 more rows, and 9 more variables: start_lat <dbl>,
## #   start_lon <dbl>, end_station <int>, end_lat <dbl>, end_lon <dbl>,
## #   bike_id <chr>, plan_duration <int>, trip_route_category <chr>,
## #   passholder_type <chr>

En ggplot2 podemos usar geom_segment() o geom_curve() para crear directamente una línea (curva) para cada recorrido, mapeando las coordenadas de inicio y de final a las variables x, y, xend y yend:

ggplot(recorridos) +
    geom_segment(aes(x=start_lon, y=start_lat, xend=end_lon, yend=end_lat))
## Warning: Removed 2084 rows containing missing values (geom_segment).

Sin embargo, para poder usar geom_path (o geom_polygon) tenemos que reestructurar el conjunto de datos para que cada línea contenga un único punto; además, necesitaremos otra variable que permita agrupar los puntos por líneas (y en el caso de que cada línea pueda pasar por más de dos puntos, una cuarta variable que permita ordenar los puntos).

Junto con gather() vamos aplicar las siguientes operaciones para quedarmos con un dataset mínimo:

  • Concatenar latitud y longitud de inicio, y latitud y longitud de final.
  • Seleccionar las tres variables que necesitamos para usar geom_path().
  • Pivotar (gather()) las coordenadas de inicio y final.
  • Una vez pivotados los datos, volver a separar latitud y longitud.
  • Convertir latitud y longitud a numérico.
puntos_de_recorridos <- recorridos %>%
  unite(coordenada_inicio, start_lat,start_lon,sep=";") %>%
  unite(coordenada_final, end_lat,end_lon,sep=";") %>%
  select(c("trip_id", "coordenada_inicio","coordenada_final")) %>%
  gather(key="punto", value = coordenadas, coordenada_inicio,coordenada_final) %>%
  separate(coordenadas, c("latitud", "longitud"), sep=";") %>%
  mutate(latitud = as.numeric(latitud)) %>%
  mutate(longitud = as.numeric(longitud)) %>%
  arrange(trip_id)

## Warning in evalq(as.numeric(latitud), <environment>): NAs introducidos por
## coerción

## Warning in evalq(as.numeric(longitud), <environment>): NAs introducidos por
## coerción

as.tibble(puntos_de_recorridos)

## # A tibble: 190,566 x 4
##     trip_id punto             latitud longitud
##       <int> <chr>               <dbl>    <dbl>
##  1 94851130 coordenada_inicio    34.0    -118.
##  2 94851130 coordenada_final     34.1    -118.
##  3 94851131 coordenada_inicio    34.0    -118.
##  4 94851131 coordenada_final     34.1    -118.
##  5 94851132 coordenada_inicio    34.0    -118.
##  6 94851132 coordenada_final     34.0    -118.
##  7 94851133 coordenada_inicio    34.0    -118.
##  8 94851133 coordenada_final     34.0    -118.
##  9 94851134 coordenada_inicio    34.0    -118.
## 10 94851134 coordenada_final     34.0    -118.
## # ... with 190,556 more rows

ggplot(puntos_de_recorridos) +
    geom_path(aes(x=longitud, y=latitud, group = trip_id))

## Warning: Removed 1771 rows containing missing values (geom_path).

Aunque en este ejemplo concreto no sería necesario realizar la reestructuración de los datos, lo más habitual será disponer de más de dos puntos por cada recorrido, en cuyo caso será necesario tener este tipo de estructura de datos.

Variables como elementos de una “metavariable” gráfica

Finalmente, otro caso se da cuando queremos ver el comportamiento de distintas variables en una misma gráfica.

Si echamos un vistazo a la página de referencia de la función gather() vemos el siguiente ejemplo (en nuestro ejemplo, hemos almacenado el resultado de la función en mini_iris_gathered para utilizarlo luego con ggplot()):

library(tidyverse)
# get first observation for each Species in iris data -- base R
mini_iris <- iris[c(1, 51, 101), ]
# gather Sepal.Length, Sepal.Width, Petal.Length, Petal.Width
mini_iris_gathered <- gather(mini_iris, key = flower_att, value = measurement,
       Sepal.Length, Sepal.Width, Petal.Length, Petal.Width)

Una vez modificada la estructura de los datos, podemos crear gráficas en las que las distintas variables tienen que aparecer como niveles de X o como un gráfico de coordinadas paralelas (nota: este ejemplo es relativamente sencillo, ya que las variables usadas comparten escala y el rango es similar):

ggplot(mini_iris_gathered, aes(x=flower_att, y=measurement)) +
  geom_line(aes(group=Species, color=Species)) +
  geom_point(size=2, colour="grey50") +
  theme_classic() +
  theme(panel.grid.major.x = element_line(color = "black"))

Las gráficas de tipo heatmap también pueden requerir este tipo de transformación. En este ejemplo utilizan la función melt() del paquete reshape, una versión anterior de la función gather(): Heatmaps con ggplot2.

En cualquier caso, las personas a las que no les guste tener que reestructurar los datos pueden usar paquetes específicos para crear este tipo de gráficas, como ComplexHEatmap o ggparallel.

spread()

spread() sirve para pasar una estructura estrecha a ancha. Nunca me he encontrado con un dataset de terceros con el que haya tenido la necesidad de usar esta función, pero resulta myu útil como complemento de gather(). Como podemos ver en este ejemplo (último ejemplo) spread() se puede usar junto con gather(), group_by() y summarise() para agregar datos.


Más posts