El paquete dplyr y su sintaxi

Usaremos el dataset “nycityflights13” para practicar con dplyr

library(tidyverse)
## -- Attaching packages ----------------------------------------------------------------------------------------------------------- tidyverse 1.2.1 --
## v ggplot2 3.1.0     v purrr   0.2.5
## v tibble  1.4.2     v dplyr   0.7.8
## v tidyr   0.8.2     v stringr 1.3.1
## v readr   1.1.1     v forcats 0.3.0
## -- Conflicts -------------------------------------------------------------------------------------------------------------- tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
library(nycflights13) # cargamos el dataset de los aviones

head(nycflights13::flights)

Estos datasets se sacan del BUREAU OF TRANSPORTATION STATISTICS.

Este objeto no es un dataset, es un tibble, es decir un dataframe “tuneado” para trabajar mejor con las herramientas de tidyverse. Debajo de los columnames nos aparecen el formato para cada una de las variables. La información que nos da es:

En esta sección aprenderemos a usar 5 funciones básicas que nos ayudaran a trabajar este data set:

Todas estas funciones la acompañaremos con group_by(), que opera la función a la que acompaña grupo a grupo.

Estas funciones siempre tendrán la misma estructura:

  1. data frame que usaremos

  2. operaciones que queremos hacer a las variables del data frame

  3. resultados en un nuevo data frame

El filtrado de los datos con filter()

Filter() nos ayuda a obtener un subconjunto de valores basados en una serie de valores.

Imaginemos que queremos obtener todos los vuelos del 1 de enero:

head(jan1 <- filter(flights, month == 1, day == 1)) # redondeando con paréntesis puedo ejecutar la variable a la vez que le asigno un valor

Los operadores logísticos que usaremos para filtrar son; > , < , >= , <= , == , !=

Tenemos también la función near, para poder solucionar problemas numéricos que hayan con el ordenador:

sqrt(2)^2 == 2 # debería ser TRUE porque el quadrado de la raíz quadrada se eliminan entre ellos, sin embargo, el resultado es falso. Para solcionarlo usamos near():
## [1] FALSE
near(sqrt(2)^2,2)
## [1] TRUE

El álgebra de Bool en filtrado

Imagen explicativa de cómo funcionan las condiciones booleanas en R:

imagen de las condiciones booleanas

imagen de las condiciones booleanas

EL conjunto total de operaciones booleanas que se pueden llevar a cabo son:

imagen de las operaciones booleanas

imagen de las operaciones booleanas

¿Cómo aplicarlo con filter?

head(filter(flights, month == 5 | month == 6)) # devuelveme los meses de mayo y junio

Podemos hacer un filtraje según el valor de dos meses seleccionando como vector:

head(may_june <- filter(flights, month %in% c(5,6)))

Tener en mente la ley de morgan para valores booleanas:

  • !(x&y) == (!x)|(!y)

  • !(x|y) == (!x)&(!y)

Por ejemplo, si queremos ver los vuelos que no fueran retrasados más de 60 minutos ni en salida ni en llegada, usamos el siguiente código:

head(filter(.data = flights, !(arr_delay > 60 | dep_delay > 60)))

Aún que la función previa funciona, sería más óptimo escribir:

head(filter(.data = flights, arr_delay <= 60, dep_delay <= 60))

Los NA con dplyr

¿Cómo gestionar los NAs? Primero creamos el dataset:

(df <- tibble(x = c(1,2,NA,4,5)))

Filter nos permite evadir los NA:

filter(df, x>2)

Si queréis preservar los valores de NA, se le debe pedir explícitamente en el filter:

filter(df, is.na(x) | x>2)

Ordenando filas con arrange

Opciones de visualización de un dataset de forma rápida. Ver las primeras 5 filas:

head(flights, 5)

Ver las útlimas 5 filas:

tail(flights, 5)

Arrange nos devolverá el mismo número de filas del dataset pero ordenadas de forma que hayamos seleccionado:

sorted_date <- arrange(.data = flights, year, month, day)
tail(sorted_date)

Podemos ordenar de mayor a menor gracias a desc():

head(arrange(flights, desc(arr_delay))) # ordenar los vuelos por los que tardaron más en llegar hasta el que menos

Si se usa la función de arrange para ordenar un vector que tenga NAs, estos siempre acabarán al final:

arrange(df,x)

Si queremos ver por ejemplo los vuelos que recorrieron menos distancia:

head(arrange(flights, distance))

Si queremos saber cual es el vuelo que más recorrió:

head(arrange(flights, desc(distance)))

Podemos hacer uso la función between(), para buscar valores entre determinados valores. Por ejemplo, si quisieramos saber los vuelos entre las 0:00 horas y las 6:00 horas:

head(filter(flights, between(hour, 0, 6)))

O si quisieramos saber cuales son los valores desconocidos en una variable en concreto:

head(filter(.data = flights, is.na(dep_time)))

Otras opciones con arrange:

# ¿Cuál fué el vuelo con más retraso?
arrange(flights, desc(dep_delay))[1,]
# ¿Cuál fué el vuelo con menos retraso?
arrange(flights, dep_delay)[1,]

Ejercios: Filtrando los datos con dplyr

  1. Encuentra todos los vuelos que llegaron más de una hora tarde de lo previsto.
flights %>%
  filter(arr_delay > 60)
  1. Encuentra todos los vuelos que volaron hacia San Francisco (aeropuertos SFO y OAK)
flights %>%
  filter(dest == "SFO" | dest == "OAK")
  1. Encuentra todos los vuelos operados por United American (UA) o por American Airlines (AA)
flights %>%
  filter(carrier == "UA" | carrier == "AA")
  1. Encuentra todos los vuelos que salieron los meses de primavera (Abril, Mayo y Junio)
flights %>%
  mutate(month = months(time_hour)) %>%
  filter(month == "abril" | month == "mayo" | month == "junio" )
  1. Encuentra todos los vuelos que llegaron más de una hora tarde pero salieron con menos de una hora de retraso.
flights %>%
  filter(arr_delay > 60 & dep_delay <= 60)
  1. Encuentra todos los vuelos que salieron con más de una hora de retraso pero consiguieron llegar con menos de 30 minutos de retraso (el avión aceleró en el aire)
flights %>%
  filter(dep_delay > 60  & arr_delay <= 30)
  1. Encuentra todos los vuelos que salen entre medianoche y las 7 de la mañana (vuelos nocturnos).
library(lubridate)
## 
## Attaching package: 'lubridate'
## The following object is masked from 'package:base':
## 
##     date
flights %>%
  filter(hour(time_hour) > 0 & hour(time_hour) < 7)
  1. Investiga el uso de la función between() de dplyr. ¿Qué hace? Puedes usarlo para resolver la sintaxis necesaria para responder alguna de las preguntas anteriores?
library(lubridate)
flights %>%
  filter(between(hour(time_hour), 0, 7 ))
  1. ¿Cuántos vuelos tienen un valor desconocido de dep_time?
flights %>%
  filter(is.na(dep_time)) %>%
  group_by(dep_time) %>%
  summarise(n = n())
  1. ¿Qué variables del dataset contienen valores desconocidos? ¿Qué representan esas filas donde faltan los datos?
flights[is.na(flights),]
## Warning: Length of logical index must be 1 or 336776, not 6398744
  1. Contesta que dan las siguientes condiciones booleanas

NA^0

NA|TRUE

FALSE&NA

Intenta establecer la regla general para saber cuando es o no es NA (cuidado con NA*0)

NA^0
## [1] 1
NA|TRUE
## [1] TRUE
FALSE&NA
## [1] FALSE
is.na(NA)
## [1] TRUE

Filtrar columnas con select()

Vamos a usar la función select con un determinado subgrupo, seleccionando solo las variables de dep_delay y arr_delay:

select(sorted_date[1024:1027,], dep_delay, arr_delay)

Podemos seleccionar desde una variable a otra, incluyendo todas las que están por medio:

select(sorted_date[1024:1027,], dep_time:arr_delay)

O excluir un conjunto de columnas:

select(sorted_date[1024:1027,], -(dep_delay:arr_delay))

Select tiene muchas utilidades, por ejemplo, seleccionar solo las columnas que empiecen por “dep”:

select(sorted_date[1024:1027,], starts_with("dep"))

O las que acaben con “delay”:

select(sorted_date[1024:1027,], ends_with("delay"))

O que contenga “st”:

select(sorted_date[1024:1027,], contains("st"))

Podemos buscar un rango número también. Sin embargo, en el dataset de aviones no hay ninguna variable así:

select(flights, num_range("x", 1:5)) # x1, x2, x3, x4, x5

La función one_of() de dplyr nos permite añadir variables en string dentro de un vector. Muy útil si es el resultado de un programa que ha devuelto un array de variables que queremos seleccionar automaticamente:

head(select(flights, one_of(c("year", "month", "day", "dep_delay", "arr_delay"))))

Renombrar y ordenar columnas:

Usaremos la función rename() para realizar esta acción:

head(rename(flights, deptime = dep_time, any = year, mes = month, dia = day))

Podemos ordenar las columnas con select para visualizaciones rápidas. La función everything() nos permitirá seleccionar todas las otras variables restantes:

head(select(flights, time_hour, distance, air_time, everything()))

Ordenado y selección de datos con dplyr

1.Piensa cómo podrías usar la función arrange() para colocar todos los valores NA al inicio. Pista: puedes la función is.na() en lugar de la función desc() como argumento de arrange.

x <- tibble(x = c(1,2,NA,NA,6,4,3),
            y = c(8,NA,2,NA,NA,6,3))
x %>% 
  arrange(desc(is.na(y)))
  1. Ordena los vuelos de flights para encontrar los vuelos más retrasados en la salida. ¿Qué vuelos fueron los que salieron los primeros antes de lo previsto?
names(flights)
##  [1] "year"           "month"          "day"            "dep_time"      
##  [5] "sched_dep_time" "dep_delay"      "arr_time"       "sched_arr_time"
##  [9] "arr_delay"      "carrier"        "flight"         "tailnum"       
## [13] "origin"         "dest"           "air_time"       "distance"      
## [17] "hour"           "minute"         "time_hour"
flights %>% select(flight, dep_delay) %>% arrange(desc(dep_delay))
tail(arrange(.data = flights,desc(dep_delay)))

Calcular nuevas variables a partir de las que ya tenemos con la función mutate()

Mutate() siempre añade columnas al final del dataset, así que es mejor crear un nuevo data set con el que trebajaremos, y mantener apartado el original. Creamos el nuevo dataset con el que trabajaremos:

flights_new <- select(flights,
                      year:day, 
                      ends_with("delay"),
                      distance,
                      air_time)

Ahora calcularemos nuevas variables con la función mutate():

head(mutate(flights_new,
       time_gain = arr_delay - dep_delay, # diferencia de tiempo (min)
       flight_speed = distance/(air_time/60), # velocidad = espacio * tiempo (km/h)
       air_time_hour = air_time/60,
       time_gain_per_hour = time_gain / air_time_hour
       ) -> flights_new) # las asignamos al dataset que hemos creado para tratar

Si lo que queremos es modificar las variables y eliminarlas, usaremos la función transmute().

head(transmute(flights_new,
          time_gain = arr_delay - dep_delay, # diferencia de tiempo (min)
          flight_speed = distance/(air_time/60), # velocidad = espacio * tiempo (km/h)
          air_time_hour = air_time/60,
          time_gain_per_hour = time_gain / air_time_hour
          ) -> data_from_flights_new)

Funciones útiles mara mutar datos

Mutate() es una función que solo puede operar con vectores. Es decir que se puede deber tomar un vector como parámetro para operar y obtener un vector de salida igual de largo.

Operaciones que podemos hacer dentro de mutate:

  • operaciones aritméticas: +, -, *, /, ^

  • agregados de funciones: x/suma(x) : proporción sobre el total x - mean(x) : distancia respecto de media (x - mean(x))/sd(x) : tipificación (x - min(x))/(max(x) - min(x)) : estandarizar entre [0,1]

  • aritmética modular: %/% -> cociente de la división entera %% -> resto de la división entera x == y * (x%/%y) + (x%%y)

Imaginemos que quisieramos tener las horas y minutos de un vuelo determinado:

head(transmute(flights_new, 
          air_time,
          hour_air = air_time %/% 60,
          minute_air = air_time %% 60))
  • logaritmos: log() -> logaritmo en base e log2() log10()

  • offsets: lead() -> mueve hacia la izquierda lag() -> mueve las posiciones hacia la derecha

df <- 1:12
#  derecha
lag(df)
##  [1] NA  1  2  3  4  5  6  7  8  9 10 11
#  izquierda
lead(df)
##  [1]  2  3  4  5  6  7  8  9 10 11 12 NA
  • funciones acumulativas: cumsum() cumprod() cummin() cummax() cummean()
cumsum(df)
##  [1]  1  3  6 10 15 21 28 36 45 55 66 78
cumprod(df)
##  [1]         1         2         6        24       120       720      5040
##  [8]     40320    362880   3628800  39916800 479001600
cummin(df)
##  [1] 1 1 1 1 1 1 1 1 1 1 1 1
cummax(df)
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12
cummean(df)
##  [1] 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5
  • comparaciones lógicas: >, >=, <, <=, ==, !=
head(transmute(flights,
          dep_delay,
          has_been_delayed = (dep_delay > 0)))
  • Rankings: min_rank() -> nos dice las posiciones de menor a mayor
df <- c(3,(-2),1,0,NA,(-1),2,(-3))
min_rank(df) # nos orena de menos a mas, de -3 a 3
## [1]  7  2  5  4 NA  3  6  1
df
## [1]  3 -2  1  0 NA -1  2 -3

Si queremos hacerlo a la inversa sería:

min_rank(desc(df))
## [1]  1  6  3  4 NA  5  2  7
df
## [1]  3 -2  1  0 NA -1  2 -3

Podemos usar otras funciones como dense_rank(), rwon_number() y percent_rank()

percent_rank(df) # porcentage según e mínimo de una fila vs el máximo
## [1] 1.0000000 0.1666667 0.6666667 0.5000000        NA 0.3333333 0.8333333
## [8] 0.0000000

O otra funciones como cume_dist(df), que es el porcentage rank acumulado

cume_dist(df)
## [1] 1.0000000 0.2857143 0.7142857 0.5714286        NA 0.4285714 0.8571429
## [8] 0.1428571

O si queremo tenerlo ordenado por percentiles:

ntile(df, n = 4) 
## [1]  4  1  3  2 NA  2  3  1

El resumir de las variables agrupadas con dplyr

summarise() nos permite resumir todo el data set en una variable determinada

summarise(flights, delay = mean(dep_delay, na.rm = T))

Por si solo summarise no nos es muy útil pero si la combinamos con otras funciones como group_by(), nos ayudará a ver mejores resultados:

by_month_group <- group_by(flights, year, month, day)
head(summarise(by_month_group, 
               delay = mean(dep_delay, na.rm = T), # calculo del promedio
               median = median (dep_delay, na.rm = T),
               min = min(dep_delay, na.rm = T)
               ))

¿Cómo podríamos saber cuál es la compañia que se retrasa más?

mutate(summarise(group_by(flights, carrier),
                    delay = mean(dep_delay, na.rm = T)),
          sorted = min_rank(delay)) # la compañia US tiene menos delay

Los pipes del paquete dplyr

Es un operador que nos funciona de forma ideal entre group_by() y summarise(). Empecemos con una situacion de analisis sin pipes, y luego repetiremos el proceso con pipe:

group_by_dest <- group_by(flights, dest)
delay <- summarise(group_by_dest,
                   count = n(),
                   dist = mean(distance, na.rm = T),
                   delay = mean(arr_delay, na.rm = T))
delay <- filter(delay, count>100, dest != "HNL") # filtramos quitando los aeropuertos que no queremos, ejemplo, HNL que es Honolulu queda muy lejos (outlier)
# Crearemos un plot para visualizario los datos:
ggplot(data = delay, mapping = aes(x = dist, y = delay))+
  geom_point(aes(size = count), alpha = 0.2) +
  geom_smooth(se = F) +
  geom_text(aes(label = dest), alpha = .4)
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

La sintaxis de pipes pretende juntar una serie de operaciones como las anteriores a través de “%>%”:

delays <- flights %>%
  group_by(dest) %>%
  summarise(count = n(),
            dist = mean(distance, na.rm = T),
            delay = mean(arr_delay, na.rm = T)) %>%
  filter(count > 100, dest != "HNL") %>%
  ggplot(mapping = aes(x = dist, y = delay))+
      geom_point(aes(size = count), alpha = 0.2) +
      geom_smooth(se = F) +
      geom_text(aes(label = dest), alpha = .4)
delays
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

Eliminar los NA de los datos

Como tratar los NA dentro del dataset de flights:

flights %>%
  group_by(year,month,day) %>%
  summarise(mean = mean(dep_delay, na.rm = T),
            median = median(dep_delay, na.rm = T),
            sd =  sd(dep_delay, na.rm = T),
            count = n())

Con na.rm = T eliminamos los NA del análisis, pero los NA nos pueden dar valor. En este caso, para el dataset de flights, nos dicen los vuelos cancelados, por lo tanto vamos a ver como podemos darle valor:

flights %>%
  filter(!is.na(dep_delay), !is.na(arr_delay))%>%
  group_by(year,month,day) %>%
  summarise(mean = mean(dep_delay, na.rm = T),
            median = median(dep_delay, na.rm = T),
            sd =  sd(dep_delay, na.rm = T),
            count = n())

Creación de un dataset sin NAs

not_cancelled <- flights %>%
  filter(!is.na(dep_delay), !is.na(arr_delay))

El ejemplo del beisbol

Trabajaremos con un dataset de la liga de béisbol, primero cargamos la libreria donde está:

library(Lahman)

¿Como evaluamos como de bueno es un bateador? Miramos las veces que ha bateado (H, de hit) y el numero de veces que pudo batear (AB):

Batting <- as.tibble(Lahman::Batting)

Con tibble, convertiremos la informacion a un objeto tipo tibble y así poder ejecutar mejor las funciones pipe:

batters <- Batting %>%
  group_by(playerID) %>%
  summarise(hits = sum(H, na.rm = T),
            bats = sum(AB, na.rm = T),
            bat.average = hits/bats)

Ahora ya tenemos los datos de los bateadores en la variable batters. Vamos a hacer unplot para ver sus datos

batters %>%
  filter(bats > 100) %>%
  ggplot(mapping = aes(x = bats, y = bat.average))+
    geom_point(alpha = 0.2)+
    geom_smooth(se=F)
## `geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'

El gràdico nos muestra que hay una buena relacion entre las veces que bateas y las veces que los jugadores le dan a la bola. ¿Como sabemos que no es fruto de la aleatoriedad?

head(batters) %>%
  filter(bats > 100) %>%
  arrange(desc(bat.average))

Las conclusiones es que realmente los que mas han acertado han bateado muchas veces.

Funciones estadísticas útiles que se puedan combina con summarise()

Vamos a separar las diferentes medidas entre familias:

Medidas de centralización

  • media

  • mediana

not_cancelled %>% 
  group_by(carrier) %>%
  summarise(
    mean = mean(arr_delay),
    mean2 = mean(arr_delay[arr_delay > 0]), # solo los vuelos que se retrasan para saber el promedio de su retraso
    median = median(arr_delay) # comparar con la media
                 
  )

Medidas de dispersión

Nos permite saber si hay mucha concentración de los datos o son muy variados

  • desviación estándard

  • rango intercuartílico […]

not_cancelled %>%
  group_by(carrier) %>%
  summarise(
    sd = sd(arr_delay),
    iqr = IQR(arr_delay), # rango intercuartílico
    mad = mad(arr_delay) # Median Absolute Deviation
  ) %>%
  arrange(desc(sd))# ordenar para ver los que tienen más error estandard

Medidas de orden o de ranking

  • Cuantil […]
not_cancelled %>%
  group_by(carrier) %>%
  summarise(
    first = min(arr_delay), # cual es le tiempo
    q1 = quantile(arr_delay, 0.25),
    median = quantile(arr_delay, 0.5), # median()
    q3 = quantile(arr_delay, 0.75),
    last = max(arr_delay)
  )

Medidas de posición

  • First, last, […]
not_cancelled %>%
  group_by(carrier) %>%
  summarise(
    first_dep = first(dep_time), # el primer vuelo que sale
    second_dep = nth(dep_time, 2),
    third_dep = nth(dep_time, 3),
    last_dep = last(dep_time) # elultimo vuelo que sale
  )

Creación de un ranking para ver los vuelos que salen antes por compañía:

not_cancelled %>%
  group_by(carrier) %>%
  mutate(rank = min_rank(dep_time)) %>%
  filter(rank %in% range(rank)) -> temp
head(temp)

Funciones de conteo

  • la n como conteo

  • n_distinct(), evitar los los NA

flights %>%
  group_by(dest) %>%
  summarise(
    count = n(),
    carriers = n_distinct(carrier),
    arrivals = sum(!is.na(arr_delay))
  ) %>%
  arrange(desc(carriers)) -> temp
head(temp)

Si queremos saber de los vuelos no cancelados el numero de destinos:

not_cancelled %>% count(dest) -> temp
head(temp)

sum/mean de valores lógicos

not_cancelled %>%
  group_by(year, month, day) %>%
  summarise(n_prior_5 = sum(dep_time < 500)) -> temp
head(temp)

Saber que porcentaje de vuelos tuvieron un retraso de más de 1 hora por compañía :

not_cancelled %>%
  group_by(carrier) %>%
  summarise(more_than_hour_delay = mean(arr_delay>60)) %>%
  arrange(desc(more_than_hour_delay))

Agrupaciones múltiples y desagrupaciones

¿Cómo podríamos ir acumulando grupos? Ejemplo:

daily <- group_by(flights, year, month, day)

per_day <- summarise(daily, n_fl = n())
head(per_day)
per_month <- summarise(per_day, n_fl = sum(n_fl))
head(per_month)
(per_year <- summarise(per_month, n_fl = sum(n_fl)))

También podemos usar la función ungroup() para desagrupar las variables previamente tratadas:

daily %>%
  ungroup() %>%
  summarise(n_fl = n())

Mutates y filters por segmento:

¿Como podríamos saber cuántos son destinos populares? Cogeríamos los destinos que recibieron más de un vuelo al día:

popular_dest <- flights %>%
  group_by(dest) %>%
  filter(n()>365)
head(popular_dest)