Introducción:

La visualización es una herramienta importante para la generación de conocimiento; sin embargo, es raro que obtengas los datos exactamente en la forma en que los necesitas. A menudo tendrás que crear algunas variables nuevas o resúmenes, o tal vez solo quieras cambiar el nombre de las variables o reordenar las observaciones para facilitar el trabajo con los datos. En este capítulo aprenderás cómo hacer todo eso (¡y más!), incluyendo cómo transformar tus datos utilizando el paquete dplyr y el uso de un nuevo conjunto de datos sobre salida de vuelos de la ciudad de Nueva York en el año 2013.

Prerrequisitos:

En este capítulo nos enfocaremos en cómo usar el paquete dplyr, otro miembro central del tidyverse. Ilustraremos las ideas clave con el dataset vuelos que está contenido en el paquete datos. Utilizaremos ggplot2 como ayuda para comprender los datos.

install.packages("datos")
## Installing package into '/cloud/lib/x86_64-pc-linux-gnu-library/4.1'
## (as 'lib' is unspecified)
library(datos)
library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.1 ──
## ✓ ggplot2 3.3.5     ✓ purrr   0.3.4
## ✓ tibble  3.1.6     ✓ dplyr   1.0.8
## ✓ tidyr   1.2.0     ✓ stringr 1.4.0
## ✓ readr   2.1.2     ✓ forcats 0.5.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()

Toma nota acerca del mensaje de conflictos que se imprime cuando cargas el paquete tidyverse. Te indica que dplyr sobrescribe algunas funciones de R base. Si deseas usar la versión base de estas funciones después de cargar dplyr, necesitarás usar sus nombres completos: \(stats::filter() y stats::lag()\).

Para explorar los verbos básicos de manipulación de datos de dplyr, usaremos vuelos. Este conjunto de datos contiene los 336, 776 vuelos que partieron de la ciudad de Nueva York durante el 2013. Los datos provienen del Departamento de Estadísticas de Transporte de los Estados Unidos y están documentados en ?vuelos.

?vuelos
vuelos
## # A tibble: 336,776 × 19
##     anio   mes   dia horario_salida salida_programada atraso_salida
##    <int> <int> <int>          <int>             <int>         <dbl>
##  1  2013     1     1            517               515             2
##  2  2013     1     1            533               529             4
##  3  2013     1     1            542               540             2
##  4  2013     1     1            544               545            -1
##  5  2013     1     1            554               600            -6
##  6  2013     1     1            554               558            -4
##  7  2013     1     1            555               600            -5
##  8  2013     1     1            557               600            -3
##  9  2013     1     1            557               600            -3
## 10  2013     1     1            558               600            -2
## # … with 336,766 more rows, and 13 more variables: horario_llegada <int>,
## #   llegada_programada <int>, atraso_llegada <dbl>, aerolinea <chr>,
## #   vuelo <int>, codigo_cola <chr>, origen <chr>, destino <chr>,
## #   tiempo_vuelo <dbl>, distancia <dbl>, hora <dbl>, minuto <dbl>,
## #   fecha_hora <dttm>

Es posible que observes que este conjunto de datos se imprime de una forma un poco diferente a otros que podrías haber utilizado en el pasado: solo muestra las primeras filas y todas las columnas que caben en tu pantalla. Para ver todo el conjunto de datos, puedes ejecutar View(vuelos) que abrirá el conjunto de datos en el visor de RStudio. En este caso se imprime de manera diferente porque es un tibble. Los tibbles son data frames, pero ligeramente ajustados para que funcionen mejor en el tidyverse. Por ahora, no necesitas preocuparte por las diferencias; hablaremos en más detalle de los tibbles en Manejar o domar datos.

También podrás haber notado la fila de tres (o cuatro) abreviaturas de letras debajo de los nombres de las columnas. Estos describen el tipo de cada variable:

  1. int significa enteros.
  2. dbl significa dobles, o números reales.
  3. chr significa vectores de caracteres o cadenas.
  4. dttm significa fechas y horas (una fecha + una hora).

Hay otros tres tipos comunes de variables que no se usan en este conjunto de datos, pero que encontrarás más adelante:

  1. lgl significa lógico, vectores que solo contienen TRUE (verdadero) o FALSE (falso).
  2. fctr significa factores, que R usa para representar variables categóricas con valores posibles fijos.
  3. date significa fechas.

Lo básico de dplyr:

En este capítulo, aprenderás las cinco funciones clave de dplyr que te permiten resolver la gran mayoría de tus desafíos de manipulación de datos:

  1. Filtrar o elegir las observaciones por sus valores (filter() — del inglés filtrar).
  2. Reordenar las filas (arrange() — del inglés organizar).
  3. Seleccionar las variables por sus nombres (select() — del inglés seleccionar).
  4. Crear nuevas variables con transformaciones de variables existentes (mutate() — del inglés mutar o transformar).
  5. Contraer muchos valores en un solo resumen (summarise() — del inglés resumir).
  6. Todas estas funciones se pueden usar junto con group_by() (del inglés agrupar por), que cambia el alcance de cada función para que actúe ya no sobre todo el conjunto de datos sino de grupo en grupo.

Estas seis funciones proporcionan los verbos para este lenguaje de manipulación de datos.

Todos los verbos funcionan de manera similar:

  1. El primer argumento es un data frame.
  2. Los argumentos posteriores describen qué hacer con el data frame usando los nombres de las variables (sin comillas).
  3. El resultado es un nuevo data frame.
  4. En conjunto, estas propiedades hacen que sea fácil encadenar varios pasos simples para lograr un resultado complejo. Sumerjámonos y veamos cómo funcionan estos verbos.

1. Filtrar filas con la función filter():

filter() te permite filtrar un subconjunto de observaciones según sus valores. El primer argumento es el nombre del data frame. El segundo y los siguientes argumentos son las expresiones que lo filtran. Por ejemplo, podemos seleccionar todos los vuelos del 1 de enero con:

filter(vuelos, mes == 1, dia == 1)
## # A tibble: 842 × 19
##     anio   mes   dia horario_salida salida_programada atraso_salida
##    <int> <int> <int>          <int>             <int>         <dbl>
##  1  2013     1     1            517               515             2
##  2  2013     1     1            533               529             4
##  3  2013     1     1            542               540             2
##  4  2013     1     1            544               545            -1
##  5  2013     1     1            554               600            -6
##  6  2013     1     1            554               558            -4
##  7  2013     1     1            555               600            -5
##  8  2013     1     1            557               600            -3
##  9  2013     1     1            557               600            -3
## 10  2013     1     1            558               600            -2
## # … with 832 more rows, and 13 more variables: horario_llegada <int>,
## #   llegada_programada <int>, atraso_llegada <dbl>, aerolinea <chr>,
## #   vuelo <int>, codigo_cola <chr>, origen <chr>, destino <chr>,
## #   tiempo_vuelo <dbl>, distancia <dbl>, hora <dbl>, minuto <dbl>,
## #   fecha_hora <dttm>

Cuando ejecutas esa línea de código, dplyr ejecuta la operación de filtrado y devuelve un nuevo data frame. Las funciones de dplyr nunca modifican su input, por lo que si deseas guardar el resultado, necesitarás usar el operador de asignación, <-:

ene1 <- filter(vuelos, mes == 1, dia == 1)

R imprime los resultados o los guarda en una variable. Si deseas hacer ambas cosas puedes escribir toda la línea entre paréntesis:

(dic25 <- filter(vuelos, mes == 12, dia == 25))
## # A tibble: 719 × 19
##     anio   mes   dia horario_salida salida_programada atraso_salida
##    <int> <int> <int>          <int>             <int>         <dbl>
##  1  2013    12    25            456               500            -4
##  2  2013    12    25            524               515             9
##  3  2013    12    25            542               540             2
##  4  2013    12    25            546               550            -4
##  5  2013    12    25            556               600            -4
##  6  2013    12    25            557               600            -3
##  7  2013    12    25            557               600            -3
##  8  2013    12    25            559               600            -1
##  9  2013    12    25            559               600            -1
## 10  2013    12    25            600               600             0
## # … with 709 more rows, and 13 more variables: horario_llegada <int>,
## #   llegada_programada <int>, atraso_llegada <dbl>, aerolinea <chr>,
## #   vuelo <int>, codigo_cola <chr>, origen <chr>, destino <chr>,
## #   tiempo_vuelo <dbl>, distancia <dbl>, hora <dbl>, minuto <dbl>,
## #   fecha_hora <dttm>

2.1 Comparaciones:

Para usar el filtrado de manera efectiva, debes saber cómo seleccionar las observaciones que deseas utilizando los operadores de comparación. R proporciona el conjunto estándar: >, >=, <, <=, != (no igual) y == (igual).

Cuando comienzas con R, el error más fácil de cometer es usar = en lugar de == cuando se busca igualdad. Cuando esto suceda, obtendrás un error informativo:

#filter(vuelos, mes = 1)

Hay otro problema común que puedes encontrar al usar ==: los números de coma flotante. ¡Estos resultados pueden sorprenderte!

sqrt(2)^2 == 2
## [1] FALSE
1 / 49 * 49 == 1
## [1] FALSE

Las computadoras usan aritmética de precisión finita (obviamente, no pueden almacenar una cantidad infinita de dígitos), así que recuerda que cada número que ves es una aproximación. En lugar de confiar en ==, usa near() (cercano, en inglés):

near(sqrt(2)^2, 2)
## [1] TRUE
near(1 / 49 * 49, 1)
## [1] TRUE

2.2 Operadores logicos

Si tienes múltiples argumentos para filter() estos se combinan con “y”: cada expresión debe ser verdadera para que una fila se incluya en el output. Para otros tipos de combinaciones necesitarás usar operadores Booleanos: & es “y”, | es “o”, y ! es “no”. La figura siguiente muestra el conjunto completo de operaciones Booleanas.

Operaciones booleanas

El siguiente código sirve para encontrar todos los vuelos que partieron en noviembre o diciembre:

filter(vuelos, mes == 11 | mes == 12)
## # A tibble: 55,403 × 19
##     anio   mes   dia horario_salida salida_programada atraso_salida
##    <int> <int> <int>          <int>             <int>         <dbl>
##  1  2013    11     1              5              2359             6
##  2  2013    11     1             35              2250           105
##  3  2013    11     1            455               500            -5
##  4  2013    11     1            539               545            -6
##  5  2013    11     1            542               545            -3
##  6  2013    11     1            549               600           -11
##  7  2013    11     1            550               600           -10
##  8  2013    11     1            554               600            -6
##  9  2013    11     1            554               600            -6
## 10  2013    11     1            554               600            -6
## # … with 55,393 more rows, and 13 more variables: horario_llegada <int>,
## #   llegada_programada <int>, atraso_llegada <dbl>, aerolinea <chr>,
## #   vuelo <int>, codigo_cola <chr>, origen <chr>, destino <chr>,
## #   tiempo_vuelo <dbl>, distancia <dbl>, hora <dbl>, minuto <dbl>,
## #   fecha_hora <dttm>

El orden de las operaciones no funciona como en español. No puedes escribir filter(vuelos, mes == (11 | 12)), que literalmente puede traducirse como “encuentra todos los vuelos que partieron en noviembre o diciembre”. En cambio, encontrará todos los meses que son iguales a 11 | 12, una expresión que resulta en ‘TRUE’ (verdadero). En un contexto numérico (como aquí), ‘TRUE’ se convierte en uno, por lo que encuentra todos los vuelos en enero, no en noviembre o diciembre. ¡Esto es bastante confuso!

Una manera rápida y útil para resolver este problema es x %in% y (es decir, x en y). Esto seleccionará cada fila donde x es uno de los valores en y. Podríamos usarlo para reescribir el código de arriba:

nov_dic <- filter(vuelos, mes %in% c(11, 12))
nov_dic
## # A tibble: 55,403 × 19
##     anio   mes   dia horario_salida salida_programada atraso_salida
##    <int> <int> <int>          <int>             <int>         <dbl>
##  1  2013    11     1              5              2359             6
##  2  2013    11     1             35              2250           105
##  3  2013    11     1            455               500            -5
##  4  2013    11     1            539               545            -6
##  5  2013    11     1            542               545            -3
##  6  2013    11     1            549               600           -11
##  7  2013    11     1            550               600           -10
##  8  2013    11     1            554               600            -6
##  9  2013    11     1            554               600            -6
## 10  2013    11     1            554               600            -6
## # … with 55,393 more rows, and 13 more variables: horario_llegada <int>,
## #   llegada_programada <int>, atraso_llegada <dbl>, aerolinea <chr>,
## #   vuelo <int>, codigo_cola <chr>, origen <chr>, destino <chr>,
## #   tiempo_vuelo <dbl>, distancia <dbl>, hora <dbl>, minuto <dbl>,
## #   fecha_hora <dttm>

A veces puedes simplificar subconjuntos complicados al recordar la ley de De Morgan: !(x & y) es lo mismo que !x | !y, y !(x | y) es lo mismo que !x & !y. Por ejemplo, si deseas encontrar vuelos que no se retrasaron (en llegada o partida) en más de dos horas, puedes usar cualquiera de los dos filtros siguientes:

filter(vuelos, !(atraso_llegada > 120 | atraso_salida > 120))
## # A tibble: 316,050 × 19
##     anio   mes   dia horario_salida salida_programada atraso_salida
##    <int> <int> <int>          <int>             <int>         <dbl>
##  1  2013     1     1            517               515             2
##  2  2013     1     1            533               529             4
##  3  2013     1     1            542               540             2
##  4  2013     1     1            544               545            -1
##  5  2013     1     1            554               600            -6
##  6  2013     1     1            554               558            -4
##  7  2013     1     1            555               600            -5
##  8  2013     1     1            557               600            -3
##  9  2013     1     1            557               600            -3
## 10  2013     1     1            558               600            -2
## # … with 316,040 more rows, and 13 more variables: horario_llegada <int>,
## #   llegada_programada <int>, atraso_llegada <dbl>, aerolinea <chr>,
## #   vuelo <int>, codigo_cola <chr>, origen <chr>, destino <chr>,
## #   tiempo_vuelo <dbl>, distancia <dbl>, hora <dbl>, minuto <dbl>,
## #   fecha_hora <dttm>
filter(vuelos  , atraso_llegada <= 120, atraso_salida <= 120)
## # A tibble: 316,050 × 19
##     anio   mes   dia horario_salida salida_programada atraso_salida
##    <int> <int> <int>          <int>             <int>         <dbl>
##  1  2013     1     1            517               515             2
##  2  2013     1     1            533               529             4
##  3  2013     1     1            542               540             2
##  4  2013     1     1            544               545            -1
##  5  2013     1     1            554               600            -6
##  6  2013     1     1            554               558            -4
##  7  2013     1     1            555               600            -5
##  8  2013     1     1            557               600            -3
##  9  2013     1     1            557               600            -3
## 10  2013     1     1            558               600            -2
## # … with 316,040 more rows, and 13 more variables: horario_llegada <int>,
## #   llegada_programada <int>, atraso_llegada <dbl>, aerolinea <chr>,
## #   vuelo <int>, codigo_cola <chr>, origen <chr>, destino <chr>,
## #   tiempo_vuelo <dbl>, distancia <dbl>, hora <dbl>, minuto <dbl>,
## #   fecha_hora <dttm>

Siempre que empieces a usar en filter() expresiones complejas que tengan varias partes, considera convertirlas en variables explícitas. Eso hace que sea mucho más fácil verificar tu trabajo. Aprenderás cómo crear nuevas variables en breve.

2.3 Valores faltantes:

Una característica importante de R que puede hacer que la comparación sea difícil son los valores faltantes, o NAs (del inglés “no disponibles”). NA representa un valor desconocido, lo que hace que los valores perdidos sean “contagiosos”: casi cualquier operación que involucre un valor desconocido también será desconocida.

NA > 5
## [1] NA
10 == NA
## [1] NA
NA + 10
## [1] NA
NA / 2
## [1] NA

El resultado más confuso es este:

NA == NA
## [1] NA

Es más fácil entender por qué esto es cierto con un poco más de contexto:

Sea x la edad de María. No sabemos qué edad tiene.

x <- NA

Sea y la edad de Juan. No sabemos qué edad tiene.

y <- NA

¿Tienen Juan y María la misma edad?

y==x
## [1] NA

¡No sabemos!

Si deseas determinar si falta un valor, usa \(is.na():\)

is.na(x)
## [1] TRUE

\(filter()\) solo incluye filas donde la condición es TRUE; excluye tanto los valores FALSE como NA. Si deseas conservar valores perdidos, solicitalos explícitamente:

df <- tibble(x = c(1, NA, 3))
filter(df, x > 1)
## # A tibble: 1 × 1
##       x
##   <dbl>
## 1     3
filter(df, is.na(x) | x > 1)
## # A tibble: 2 × 1
##       x
##   <dbl>
## 1    NA
## 2     3

2.4 Ejercicio:

Encuentra todos los vuelos que:

  1. Tuvieron un retraso de llegada de dos o más horas

  2. Volaron a Houston (IAH oHOU)

  3. Fueron operados por United, American o Delta

  4. Partieron en invierno del hemisferio sur (julio, agosto y septiembre)

  5. Llegaron más de dos horas tarde, pero no salieron tarde

  6. Se retrasaron por lo menos una hora, pero repusieron más de 30 minutos en vuelo

  7. Partieron entre la medianoche y las 6 a.m. (incluyente)

  8. Otra función de dplyr que es útil para usar filtros es \(between()\). ¿Qué hace? ¿Puedes usarla para simplificar el código necesario para responder a los desafíos anteriores?

  9. ¿Cuántos vuelos tienen datos faltantes en horario_salida? ¿Qué otras variables tienen valores faltantes? ¿Qué representan estas filas?

  10. ¿Por qué NA ^ 0 no es faltante? ¿Por qué NA | TRUE no es faltante? ¿Por qué FALSE & NA no es faltante? ¿Puedes descubrir la regla general? (¡NA * 0 es un contraejemplo complicado!)

3. Reordenar las filas con arrange():

\(arrange()\) funciona de manera similar a filter() excepto que en lugar de seleccionar filas, cambia su orden. La función toma un data frame y un conjunto de nombres de columnas (o expresiones más complicadas) para ordenar según ellas. Si proporcionas más de un nombre de columna, cada columna adicional se utilizará para romper empates en los valores de las columnas anteriores:

arrange(vuelos, anio, mes, dia)
## # A tibble: 336,776 × 19
##     anio   mes   dia horario_salida salida_programada atraso_salida
##    <int> <int> <int>          <int>             <int>         <dbl>
##  1  2013     1     1            517               515             2
##  2  2013     1     1            533               529             4
##  3  2013     1     1            542               540             2
##  4  2013     1     1            544               545            -1
##  5  2013     1     1            554               600            -6
##  6  2013     1     1            554               558            -4
##  7  2013     1     1            555               600            -5
##  8  2013     1     1            557               600            -3
##  9  2013     1     1            557               600            -3
## 10  2013     1     1            558               600            -2
## # … with 336,766 more rows, and 13 more variables: horario_llegada <int>,
## #   llegada_programada <int>, atraso_llegada <dbl>, aerolinea <chr>,
## #   vuelo <int>, codigo_cola <chr>, origen <chr>, destino <chr>,
## #   tiempo_vuelo <dbl>, distancia <dbl>, hora <dbl>, minuto <dbl>,
## #   fecha_hora <dttm>

Usa desc() para reordenar por una columna en orden descendente:

arrange(vuelos, desc(atraso_salida))
## # A tibble: 336,776 × 19
##     anio   mes   dia horario_salida salida_programada atraso_salida
##    <int> <int> <int>          <int>             <int>         <dbl>
##  1  2013     1     9            641               900          1301
##  2  2013     6    15           1432              1935          1137
##  3  2013     1    10           1121              1635          1126
##  4  2013     9    20           1139              1845          1014
##  5  2013     7    22            845              1600          1005
##  6  2013     4    10           1100              1900           960
##  7  2013     3    17           2321               810           911
##  8  2013     6    27            959              1900           899
##  9  2013     7    22           2257               759           898
## 10  2013    12     5            756              1700           896
## # … with 336,766 more rows, and 13 more variables: horario_llegada <int>,
## #   llegada_programada <int>, atraso_llegada <dbl>, aerolinea <chr>,
## #   vuelo <int>, codigo_cola <chr>, origen <chr>, destino <chr>,
## #   tiempo_vuelo <dbl>, distancia <dbl>, hora <dbl>, minuto <dbl>,
## #   fecha_hora <dttm>

Los valores faltantes siempre se ordenan al final:

df <- tibble(x = c(5, 2, NA))
arrange(df, x)
## # A tibble: 3 × 1
##       x
##   <dbl>
## 1     2
## 2     5
## 3    NA
arrange(df, desc(x))
## # A tibble: 3 × 1
##       x
##   <dbl>
## 1     5
## 2     2
## 3    NA

3.2 Ejercicios:

  1. ¿Cómo podrías usar arrange() para ordenar todos los valores faltantes al comienzo? (Sugerencia: \(usa is.na()\)).
  2. Ordena vuelos para encontrar los vuelos más retrasados. Encuentra los vuelos que salieron más temprano.
  3. Ordena vuelos para encontrar los vuelos más rápidos (que viajaron a mayor velocidad).
  4. ¿Cuáles vuelos viajaron más lejos? ¿Cuál viajó más cerca?