12 Datos ordenados

12.1 Introducción

“Todas las familias felices se parecen unas a otras, pero cada familia infeliz lo es a su manera.” –– León Tolstoy

“Todos los set de datos ordenados se parecen unos a otros, pero cada set de datos desordenado lo es a su manera” — Hadley Wickham

En este capítulo aprenderás una manera consistente para organizar tus datos en R a la que llamaremos tidy data (datos ordenados). Llevar tus datos a este formato requiere algo de trabajo previo; sin embargo, dicho trabajo tiene retorno positivo en el largo plazo. Una vez que tengas tus datos ordenados y las herramientas para ordenar datos que provee el tidyverse, vas a gastar mucho menos tiempo pasando de una forma de representar datos a otra, lo que te permitirá destinar más tiempo a las preguntas analíticas.

Este capítulo te dará una introducción práctica a los datos ordenados (o tidy data) y a las herramientas que provee el paquete tidyr. Si deseas aprender más acerca de la teoría subyacente, puede que te guste el artículo Tidy Data publicado en la revista Journal of Statistical Software, http://www.jstatsoft.org/v59/i10/paper.

12.1.1 Prerrequisitos

En este capítulo nos enfocaremos en tidyr, un paquete que provee un conjunto de herramientas que te ayudarán a ordenar datos desordenados. tidyr es parte del núcleo del tidyverse.

12.2 Datos ordenados

Puedes representar los mismos datos subyacentes de múltiples formas. El ejemplo a continuación muestra los mismos datos organizados de cuatro maneras distintas. Cada dataset muestra los mismos valores de cuatro variables —pais, anio, poblacion y casos—, pero cada uno organiza los valores de forma distinta.

Las anteriores son representaciones de los mismos datos subyacentes, pero no todas son igualmente fáciles de usar. Un tipo de conjunto de datos, el conjunto de datos ordenado, será mucho más fácil de trabajar dentro del tidyverse.

Existen tres reglas interrelacionadas que hacen que un conjunto de datos sea ordenado:

  1. Cada variable debe tener su propia columna.
  2. Cada observación debe tener su propia fila.
  3. Cada valor debe tener su propia celda.

La figura 12.1 muestra estas reglas visualmente.

Reglas que hacen que un conjunto de datos sea ordenado: las variables están en columnas, las observaciones en filas y los valores en celdas.

Figure 12.1: Reglas que hacen que un conjunto de datos sea ordenado: las variables están en columnas, las observaciones en filas y los valores en celdas.

Estas reglas están interrelacionadas ya que es imposible cumplir solo dos de las tres. Esta interrelación lleva a un conjunto práctico de instrucciones más simple aún:

  1. Coloca cada conjunto de datos en un tibble.
  2. Coloca cada variable en una columna.

En este ejemplo, solo la tabla1 está ordenada. Es la única representación en que cada columna es una variable.

¿Por qué asegurarse de que los datos estén ordenados? Existen dos ventajas principales:

  1. Existe una ventaja general al elegir una forma consistente de almacenar datos. Si tienes una estructura de datos consistente, es más fácil aprender las herramientas que funcionan con ella ya que tienen una uniformidad subyacente.
  2. Existe una ventaja específica al situar las variables en las columnas, ya que permite que la naturaleza vectorizada de R brille. Como habrás aprendido en las secciones sobre crear nuevas variables y resúmenes, muchas de las funciones que vienen con R trabajan con vectores de valores. Esto hace que transformar datos ordenados se perciba como algo casi natural.

dplyr, ggplot2 y el resto de los paquetes del tidyverse están diseñados para trabajar con datos ordenados. Aquí hay algunos ejemplos de cómo podrías trabajar con tabla1.

12.2.1 Ejercicios

  1. Usando prosa, describe cómo las variables y observaciones se organizan en las tablas de ejemplo.

  2. Calcula la tasa para las tablas tabla2 y tabla4a + tabla4b. Necesitarás las siguientes operaciones:

  3. Extraer el número de casos de tuberculosis por país y año.
  4. Extraer la población por país y año.
  5. Dividir los casos por la población y multiplicarla por 10000.
  6. Insertar los datos de vuelta en el lugar adecuado.

¿Cuál representación es más fácil de trabajar? ¿Cuál es la más difícil? ¿Por qué?

  1. Recrea el gráfico que muestra el cambio en el número de casos usando la tabla2 en lugar de la tabla1. ¿Qué necesitas hacer en primero?

12.3 Esparcir y reunir

Los principios sobre datos ordenados parecen tan obvios que te podrías preguntar si alguna vez encontrarás un dataset que no esté ordenado. Desafortunadamente, gran parte de los datos que vas a encontrar están desordenados. Existen dos principales razones para esto:

  1. La mayoría de las personas no están familiarizadas con los principios de datos ordenados y es difícil derivarlos por cuenta propia a menos que pases mucho tiempo trabajando con datos.

  2. Los datos a menudo están organizados para facilitar tareas distintas del análisis. Por ejemplo, los datos se organizan para que su registro sea lo más sencillo posible.

Esto significa que para la mayoría de los análisis necesitarás hacer algún tipo de orden. El primer paso es entender siempre cuáles son las variables y las observaciones. Esto a veces es fácil; otras veces deberás consultar con quienes crearon el dataset. El segundo paso es resolver uno de los siguientes problemas frecuentes:

  1. Una variable se extiende por varias columnas

  2. Una observación está dispersa entre múltiples filas.

Típicamente, un set de datos tiene uno de estos problemas. Si contiene ambos ¡significa que tienes muy mala suerte! Para solucionar estos problemas necesitarás las dos funciones más importantes de tidyr: gather() (reunir) y spread() (esparcir/extender).

12.3.1 Gather

Un problema común es cuando en un dataset los nombres de las columnas no representan nombres de variables, sino que representan los valores de una variable. Tomando el caso de la tabla4a: los nombres de las columnas 1999 y 2000 representan los valores de la variable año y cada fila representa dos observaciones en lugar de una.

Para ordenar un dataset como este necesitamos reunir (gather) esas columnas en un nuevo par de variables. Para describir dicha operación necesitamos tres parámetros:

  • El conjunto de columnas que representan valores y no variables. En este ejemplo son las columnas 1999 y 2000.

  • El nombre de la variable cuyos valores forman los nombres de las columnas. Llamaremos a esto key (clave) y en este caso corresponde a anio.

  • El nombre de la variable cuyos valores están repartidos por las celdas. Llamaremos a esto value (valor) y en este caso corresponde al número de casos.

Juntando estos parámetros se puede realizar una llamada a gather():

Las columnas a reunir quedan seleccionadas siguiendo el estilo de notación de dplyr::select(). En este caso hay solo dos columnas, por lo que las listamos individualmente. Nota que “1999” y “2000” son nombres no-sintáxicos (debido a que no comienzan con una letra) por lo que los rodeamos con acentos graves (o backticks, en inglés). Para refrescar tu memoria respecto de la selección de columnas, consulta la sección sobre select.

Reuniendo `tabla4` en un formato ordenado.

Figure 12.2: Reuniendo tabla4 en un formato ordenado.

En el resultado final, las columnas reunidas se eliminan y obtenemos la nuevas variables key y value. De otro modo, la relación entre las variables originales se mantiene. Visualmente, esto se observa en la Figura 12.2. Podemos usar gather() para ordenar tabla4b de modo similar. La única diferencia es la variable almacenada en los valores de las celdas:

Para combinar las versiones ordenadas de tabla4a y tabla4b en un único tibble, necesitamos usar dplyr::left_join(), función que aprenderás en el capítulo sobre datos relacionales.

12.3.2 Spread

Extender (spread) es lo opuesto de gather. Lo usas cuando una observación aparece en múltiples filas. Por ejemplo, toma la tabla tabla2: una observación es un país en un año, pero cada observación aparece en dos filas.

Para ordenar esto, primero analizamos la representación de un modo similar a cómo se haría con gather(). Esta vez, sin embargo, necesitamos únicamente dos parámetros:

  • La columna que contiene los nombres de las variables (la columna key). En este caso corresponde a tipo.

  • La columna que contiene valores de múltiples variables (la columna value). En este caso corresponde a cuenta.

Una vez resuelto esto, podemos usar spread(), como se muestra programáticamente abajo y visualmente en la Figura 12.3.

Extender la `tabla2` la vuelve ordenada

Figure 12.3: Extender la tabla2 la vuelve ordenada

Como te habrás dado cuenta a partir de los argumentos comunes key y value, las funciones gather() y spread() son complementarias. gather() genera tablas estrechas y largas, spread() genera tablas anchas y cortas.

12.3.3 Ejercicios

  1. ¿Por qué gather() y spread() no son perfectamente simétricas? Observa cuidadosamente el siguiente ejemplo:

(Sugerencia: observa los tipos de variables y piensa en los nombres de las columnas)

Tanto `spread()` como `gather()` tienen el argumento `convert` (convertir). ¿Qué   hace dicho argumento?
  1. ¿Por qué falla el siguiente código?
  1. ¿Por qué no se puede extender la siguiente tabla? ¿Cómo agregarías una nueva columna para resolver el problema?
  1. Ordena la siguiente tabla. ¿Necesitas extenderla o contraerla? ¿Cuáles son las variables?

12.4 Separar y unir

Hasta ahora has aprendido a ordenar las tablas tabla2 y tabla4, pero no la tabla3, que tiene un problema diferente: tenemos una columna (tasa) que contiene dos variables (casos y poblacion). Para solucionar este problema, necesitamos la función separate() (separar). También aprenderás acerca del complemento de separate(): unite() (unir), que se usa cuando una única variable se reparte en varias columnas.

12.4.1 Separar

separate() desarma una columna en varias columnas, dividiendo de acuerdo a la posición de un carácter separador. Tomemos la tabla3:

La columna tasa contiene tanto los casos como la poblacion, por lo que necesitamos dividirla en dos variables. La función separate() toma el nombre de la columna a separar y el nombre de las columnas a donde irá el resultado, tal como se muestra en la Figura 12.4 y el código a continuación.

Separar la `tabla3` la vuelve ordenada

Figure 12.4: Separar la tabla3 la vuelve ordenada

Por defecto, separate() dividirá una columna donde encuentre un carácter no alfanumérico (esto es, un carácter que no es un número o letra). Por ejemplo, en el siguiente código, separate() divide los valores de tasa donde aparece una barra (/). Si deseas usar un carácter específico para separar una columna, puedes especificarlo en el argumento sep de separate(). Por ejemplo, el código anterior se puede re-escribir del siguiente modo:

(Formalmente, sep es una expresión regular y aprenderás más sobre esto en el capítulo sobre cadenas de caracteres.)

Mira atentamente los tipos de columna: notarás que casos y poblacion son columnas de tipo carácter. Este es el comportamiento por defecto en separate(): preserva el tipo de columna. Aquí, sin embargo, no es muy útil, ya que se trata de números. Podemos pedir a separate() que intente convertir a un tipo más adecuado usando convert = TRUE:

También puedes pasar un vector de enteros a sep. separate() interpreta los enteros como las posiciones donde dividir. Los valores positivos comienzan en 1 al extremo izquierdo de las cadenas de texto; los valores negativos comienzan en -1 al extremo derecho. Cuando uses enteros para separar cadenas de texto, el largo de sep debe ser uno menos que el número de nombres en into.

Puedes usar este arreglo para separar los últimos dos dígitos de cada año. Esto deja los datos menos ordenados, pero es útil en otros casos, como se verá más adelante.

12.4.2 Unir

unite() es el inverso de separate(): combina múltiples columnas en una única columna. Necesitarás esta función con mucha menos frecuencia que separate(), pero aún así es una buena herramienta para tener en el bolsillo trasero.

Unir la `tabla5` la vuelve ordenada

Figure 12.5: Unir la tabla5 la vuelve ordenada

Podemos usar unite() para unir las columnas siglo y anio creadas en el ejemplo anterior. Los datos están guardados en datos::tabla5. unite() toma un data frame, el nombre de la nueva variable a crear y un conjunto de columnas a combinar, las que se especifican siguiendo el estilo de la función dplyr::select():

En este caso también necesitamos el argumento sep. Por defecto, pondrá un guión bajo (_) entre los valores de las distintas columnas. Si no queremos ningún separador usamos "":

12.4.3 Ejercicios

  1. ¿Qué hacen los argumentos extra y fill en separate()? Experimenta con las diversas opciones a partir de los siguientes datasets de ejemplo.
  1. Tanto unite() como separate() tienen un argumento remove. ¿Qué es lo que hace? ¿Por qué lo dejarías en FALSE?

  2. Compara y contrasta separate() y extract(). ¿Por qué existen tres variaciones de separación (por posición, separador y grupos), pero solo una forma de unir?

12.5 Valores faltantes

Cambiar la representación de un dataset conlleva el riesgo de generar valores faltantes. Sorprendentemente, un valor puede perderse de dos formas:

  • Explícita, esto es, aparece como NA.
  • Implícita, esto es, simplemente no aparece en los datos.

Ilustremos esta idea con un dataset muy sencillo:

Existen dos valores faltantes en este dataset:

  • El retorno del cuarto trimestre de 2015 que está explícitamente perdido, debido a que la celda donde el valor debiera estar contiene NA.

  • El retorno del primer semestre de 2016 está implícitamente perdido, debido a que simplemente no aparece en el set de datos.

Una forma de pensar respecto de esta diferencia es al estilo de un kōan Zen: Un valor faltante explícito es la presencia de una ausencia; un valor faltante implícito es la ausencia de una presencia.

La forma en que se representa un dataset puede dejar explícitos los valores implícitos. Por ejemplo, podemos volver explícitos los valores faltantes implícitos al mover los años a las columnas:

Debido a que estos valores faltantes explícitos pueden no ser tan importantes en otras representaciones de los datos, puedes especificar na.rm = TRUE en gather() para dejar explícitos los valores faltantes implícitos:

Otra herramienta importante para hacer explícitos los valores faltantes en datos ordenados es complete():

complete() toma un conjunto de columnas y encuentra todas las combinaciones únicas. Luego se asegura de que el dataset original contenga todos los valores, completando con NAs donde sea necesario.

Existe otra herramienta importante que deberías conocer al momento de trabajar con valores faltantes. En algunos casos en que la fuente de datos se ha usado principalmente para ingresar datos, los valores faltantes indican que el valor previo debe arrastrarse hacia adelante:

Puedes completar los valores faltantes usando fill(). Esta función toma un conjunto de columnas sobre las cuales los valores faltantes son reemplazados por el valor anterior más cercano que se haya reportado (también conocido como el método LOCF, del inglés last observation carried forward).

12.5.1 Ejercicios

  1. Compara y contrasta el argumento fill que se usa en spread() con complete().

  2. ¿Qué hace el argumento de dirección en fill()?

12.6 Estudio de caso

Para finalizar el capítulo, combinemos todo lo que aprendiste para enfrentar un problema real de ordenamiento de datos. El dataset datos::oms contiene datos de tuberculosis (TB) detallados por año, país, edad, sexo y método de diagnóstico. Los datos provienen del Informe de Tuberculosis de la Organización Mundial de la Salud 2014, disponible en http://www.who.int/tb/country/data/download/en/.

Existe abundante información epidemiológica en este dataset, pero es complicado trabajar con estos datos tal como son entregados:

oms
#> # A tibble: 7,240 x 60
#>   pais  iso2  iso3   anio nuevos_fpp_h014 nuevos_fpp_h1524 nuevos_fpp_h2534
#>   <chr> <chr> <chr> <int>           <int>            <int>            <int>
#> 1 Afga… AF    AFG    1980              NA               NA               NA
#> 2 Afga… AF    AFG    1981              NA               NA               NA
#> 3 Afga… AF    AFG    1982              NA               NA               NA
#> 4 Afga… AF    AFG    1983              NA               NA               NA
#> 5 Afga… AF    AFG    1984              NA               NA               NA
#> 6 Afga… AF    AFG    1985              NA               NA               NA
#> # … with 7,234 more rows, and 53 more variables: nuevos_fpp_h3534 <int>,
#> #   nuevos_fpp_h4554 <int>, nuevos_fpp_h5564 <int>, nuevos_fpp_h65 <int>,
#> #   nuevos_fpp_m014 <int>, nuevos_fpp_m1524 <int>, nuevos_fpp_m2534 <int>,
#> #   nuevos_fpp_m3534 <int>, nuevos_fpp_m4554 <int>,
#> #   nuevos_fpp_m5564 <int>, nuevos_fpp_m65 <int>, nuevos_fpn_h014 <int>,
#> #   nuevos_fpn_h1524 <int>, nuevos_fpn_h2534 <int>,
#> #   nuevos_fpn_h3534 <int>, nuevos_fpn_h4554 <int>,
#> #   nuevos_fpn_h5564 <int>, nuevos_fpn_h65 <int>, nuevos_fpn_m014 <int>,
#> #   nuevos_fpn_m1524 <int>, nuevos_fpn_m2534 <int>,
#> #   nuevos_fpn_m3534 <int>, nuevos_fpn_m4554 <int>,
#> #   nuevos_fpn_m5564 <int>, nuevos_fpn_m65 <int>, nuevos_ep_h014 <int>,
#> #   nuevos_ep_h1524 <int>, nuevos_ep_h2534 <int>, nuevos_ep_h3534 <int>,
#> #   nuevos_ep_h4554 <int>, nuevos_ep_h5564 <int>, nuevos_ep_h65 <int>,
#> #   nuevos_ep_m014 <int>, nuevos_ep_m1524 <int>, nuevos_ep_m2534 <int>,
#> #   nuevos_ep_m3534 <int>, nuevos_ep_m4554 <int>, nuevos_ep_m5564 <int>,
#> #   nuevos_ep_m65 <int>, nuevosrecaida_h014 <int>,
#> #   nuevosrecaida_h1524 <int>, nuevosrecaida_h2534 <int>,
#> #   nuevosrecaida_h3534 <int>, nuevosrecaida_h4554 <int>,
#> #   nuevosrecaida_h5564 <int>, nuevosrecaida_h65 <int>,
#> #   nuevosrecaida_m014 <int>, nuevosrecaida_m1524 <int>,
#> #   nuevosrecaida_m2534 <int>, nuevosrecaida_m3534 <int>,
#> #   nuevosrecaida_m4554 <int>, nuevosrecaida_m5564 <int>,
#> #   nuevosrecaida_m65 <int>

Este es un ejemplo muy típico de un dataset de la vida real. Contiene columnas redundantes, códigos extraños de variables y muchos valores faltantes. En breve, oms está desordenado y necesitamos varios pasos para ordenarlo. Al igual que dplyr, tidyr está diseñado de modo tal que cada función hace bien una cosa. Esto significa que en una situación real deberás encadenar múltiples verbos.

LCasi siempre, la mejor forma de comenzar es reunir las columnas que no representan variables. Miremos lo que hay:

  • Pareciera ser que pais, iso2 e iso3 son variables redundantes que se refieren al país.

  • anio es claramente una variable.

  • No sabemos aún el significado de las otras columnas, pero dada la estructura de los nombres de las variables (e.g. nuevos_fpp_h014, nuevos_ep_h014, nuevos_ep_m014) parecieran ser valores y no variables.

Necesitamos agrupar todas las columnas desde nuevos_fpp_h014 hasta recaidas_m65. No sabemos aún qué representa esto, por lo que le daremos el nombre genérico de "clave". Sabemos que las celdas representan la cuenta de casos, por lo que usaremos la variable casos.

Existen múltiples valores faltantes en la representación actual, por lo que de momento usaremos na.rm para centrarnos en los valores que están presentes.

Podemos tener una noción de la estructura de los valores en la nueva columna clave si hacemos un conteo:

Podrías resolver esto por tu cuenta pensando y experimentando un poco, pero afortunadamente tenemos el diccionario de datos a mano. Este nos dice lo siguiente:

  1. Lo que aparece antes del primer _ en las columnas indica si la columna contiene casos nuevos o antiguos de tuberculosis. En este dataset, cada columna contiene nuevos casos.

  2. Lo que aparece luego de indicar si se refiere casos nuevos o antiguos es el tipo de tuberculosis:

  • recaida se refiere a casos reincidentes
  • ep se refiere a tuberculosis extra pulmonar
  • fpn se refiere a casos de tuberculosis pulmonar que no se pueden detectar mediante examen de frotis pulmonar (frotis pulmonar negativo)
  • fpp se refiere a casos de tuberculosis pulmonar que se pueden detectar mediante examen de frotis pulmonar (frotis pulmonar positivo)
  1. La letra que aparece después del último _ se refiere al sexo de los pacientes. El conjunto de datos agrupa en hombres (h) y mujeres (m).

  2. Los números finales se refieren al grupo etareo que se ha organizado en siete categorías:

  • 014 = 0 – 14 años de edad
  • 1524 = 15 – 24 años de edad
  • 2534 = 25 – 34 años de edad
  • 3544 = 35 – 44 años de edad
  • 4554 = 45 – 54 años de edad
  • 5564 = 55 – 64 años de edad
  • 65 = 65 o más años de edad

Necesitamos hacer un pequeño cambio al formato de los nombres de las columnas: desafortunadamente lo nombres de las columnas son ligeramente inconsistentes debido a que en lugar de nuevos_recaida tenemos nuevosrecaida (es difícil darse cuenta de esto en esta parte, pero si no lo arreglas habrá errores en los pasos siguientes). Aprenderás sobre str_replace() en cadenas de caracteres, pero la idea básica es bastante simple: reemplazar los caracteres “nuevosrecaida” por “nuevos_recaida”. Esto genera nombres de variables consistentes.

Podemos separar los valores en cada código aplicando separate() dos veces. La primera aplicación dividirá los códigos en cada _.

A continuación podemos eliminar la columna nuevos, ya que es constante en este dataset. Además eliminaremos iso2 e iso3 ya que son redundantes.

Luego separamos sexo_edad en sexo y edad dividiendo luego del primer carácter:

¡Ahora el dataset oms está ordenado!

Hemos mostrado el código parte por parte, asignando los resultados intermedios a nuevas variables. Esta no es la forma típica de trabajo. En cambio, lo que se hace es formar incrementalmente un encadenamiento complejo usando pipes:

12.6.1 Ejercicios

  1. En este caso de estudio fijamos na.rm = TRUE para hacer más simple el verificar que tenemos los valores correctos. ¿Es esto razonable? Piensa en cómo los valores faltantes están representados en este dataset. ¿Existen valores faltantes implícitos? ¿Cuál es la diferencia entre NA y cero?

  2. ¿Qué ocurre si omites la aplicación de mutate()? (mutate(clave = stringr::str_replace(clave, "nuevosrecaida", "nuevos_recaida")))

  3. Afirmamos que iso2 e iso3 son redundantes respecto a pais. Confirma esta aseveración.

  4. Para cada país, año y sexo calcula el total del número de casos de tuberculosis. Crea una visualización informativa de los datos.

12.7 Datos no ordenados

Antes de pasar a otros tópicos, es conveniente referirse brevemente a datos no ordenados. Anteriormente en el capítulo, usamos el término peyorativo “desordenados” para referirnos a datos no ordenados. Esto es una sobresimplificación: existen múltiples estructuras de datos debidamente fundamentadas que no corresponden a datos ordenados. Existen dos principales razones para usar otras estructuras de datos:

  • Las representaciones alternativas pueden traer ventajas importantes en términos de desempeño o tamaño.

  • Algunas áreas especializadas han evolucionado y tienen sus propias convenciones para almacenar datos, las que pueden diferir respecto de las convenciones de datos ordenados.

Cada uno de estas razones significa que necesitarás algo distinto a un tibble (o data frame). Si tus datos naturalmente se ajustan a una estructura rectangular compuesta de observaciones y variables, pensamos que datos ordenados debería ser tu elección por defecto. Sin embargo, existen buenas razones para usar otras estructuras; los datos ordenados no son la única forma.

Si quieres aprender más acerca de datos no ordenados, recomendamos fuertemente este artículo del blog de Jeff Leek: http://simplystatistics.org/2016/02/17/non-tidy-data/