Los campos de fechas suelen ser formatos difíciles de trabajar y procesar. ¿Cómo conocer la fecha mínima de un dataset? ¿Cómo calcular la diferencia entre dos fechas? Si bien existen funciones en Rbase para el manejo de fechas, el paquete lubridate cuenta con muchas funcionalidades para el manejo de fechas. Con este paquete, además de poder operar sobre las fechas, podemos también conocer los días de las semanas y los meses.

En primer lugar vamos a cargar las librerías. Recuerden instalarlas previamente con install.packages():

# install.packages("tidyverse")
library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.3.5     ✓ purrr   0.3.4
## ✓ tibble  3.1.3     ✓ dplyr   1.0.7
## ✓ tidyr   1.1.3     ✓ stringr 1.4.0
## ✓ readr   1.3.1     ✓ forcats 0.5.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
# install.packages("lubridate")
library(lubridate)
## 
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
## 
##     date, intersect, setdiff, union

Sobre el dataset

Vamos a trabajar con datos de uso de bicicletas públicas. Los datos originales fueron obtenidos de BADATA. Este dataset contiene más de 2.600.000 de registros representando cada viaje realizado durante 2021 mediante el sistema público de bicicletas de la Ciudad Autónoma de Buenos Aires.

A partir de la información geoespacial de los barrios de la ciudad, se le asignó a cada viaje el barrio de origen y el barrio de destino. Luego se agrupó el dataset por par origen-destino, realizando un conteo de cantidad de recorridos. Por último, se seleccionaron los pares origen-destino con más de 20 viajes diarios realizados entre el 1 de julio 2021 y el 30 de noviembre 2021 (Diciembre no estaba completo en la base).

# carga de dataset
viajes_od <- read.csv( "https://raw.githubusercontent.com/paulavidela/utdt_cienciadedatos/main/data/viajes_od_bicis_2021.csv")

En primer lugar vamos a inspeccionar nuestra base de datos:

head(viajes_od)
##   barrio_origen fecha_hora_origen barrio_destino fecha_hora_destino cantidad
## 1       ALMAGRO     2021-07-01 00        ALMAGRO      2021-07-01 00        1
## 2       ALMAGRO     2021-07-01 00       RECOLETA      2021-07-01 00        1
## 3       ALMAGRO     2021-07-01 05        PALERMO      2021-07-01 05        1
## 4       ALMAGRO     2021-07-01 06        ALMAGRO      2021-07-01 06        1
## 5       ALMAGRO     2021-07-01 06      BALVANERA      2021-07-01 06        1
## 6       ALMAGRO     2021-07-01 06      CABALLITO      2021-07-01 06        2

El dataset contiene:
- barrio de inicio del recorrido: barrio_origen
- fecha y hora de inicio del recorrido: fecha_hora_origen
- barrio de finalización del recorrido: barrio_destino
- fecha y hora de finalización del recorrido: fecha_hora_destino
- cantidad de viajes realizados para ese par origen-destino en ese horario: cantidad

str(viajes_od)
## 'data.frame':    236351 obs. of  5 variables:
##  $ barrio_origen     : chr  "ALMAGRO" "ALMAGRO" "ALMAGRO" "ALMAGRO" ...
##  $ fecha_hora_origen : chr  "2021-07-01 00" "2021-07-01 00" "2021-07-01 05" "2021-07-01 06" ...
##  $ barrio_destino    : chr  "ALMAGRO" "RECOLETA" "PALERMO" "ALMAGRO" ...
##  $ fecha_hora_destino: chr  "2021-07-01 00" "2021-07-01 00" "2021-07-01 05" "2021-07-01 06" ...
##  $ cantidad          : int  1 1 1 1 1 2 1 1 2 3 ...

Observamos que hay dos variables relacionadas a la fecha:
- fecha_hora_origen: correspondiente a la fecha y hora en la que se inició el recorrido en bicicleta
- fecha_hora_destino: correspondiente a la fecha y hora en la que se finalizó el recorrido en bicicleta

Como podemos ver en el resultado de la función str(), las dos variables son ‘chr’ es decir, son de tipo caracter. Podemos confirmar la clase con la función class()

class(viajes_od$fecha_hora_destino)
## [1] "character"

R reconoció esa variable como de tipo caracter. Podemos transformar estas variables con funciones de Lubridate.

Primeros pasos en lubridate

Lubridate tiene funciones que permiten calcular la fecha y hora actual:
- today(): nos indica la fecha de hoy
- now(): nos indica la fecha y hora de ahora

today()
## [1] "2022-05-29"

¿Cuál es la clase de today()?

class(today())
## [1] "Date"
now()
## [1] "2022-05-29 17:58:13 -03"

¿Cuál es la clase de now()?

class(now())
## [1] "POSIXct" "POSIXt"

Existen tres tipos de formato de fechas:
- fecha (tipo ‘Date’)
- hora (tipo ‘Period’)
- fecha-hora (tipo ‘POSIXt’)

¿Cómo se transforman las variables?

Para realizar los procesamientos de forma más rápida, vamos a seleccionar un dataset reducido con el OD más frecuente. Para eso, vamos a calcular la cantidad de viajes totales para cada par origen-destino . ¿Cuál creen que puede ser el OD más frecuente?

matriz_od <- viajes_od %>%
  group_by(barrio_origen, barrio_destino) %>%
  summarise(cantidad = sum(cantidad)) %>%
  arrange(desc(cantidad))
## `summarise()` has grouped output by 'barrio_origen'. You can override using the `.groups` argument.
head(matriz_od, 10)
## # A tibble: 10 × 3
## # Groups:   barrio_origen [5]
##    barrio_origen barrio_destino cantidad
##    <chr>         <chr>             <int>
##  1 PALERMO       PALERMO          105186
##  2 PALERMO       RECOLETA          36491
##  3 RECOLETA      PALERMO           35536
##  4 RECOLETA      RECOLETA          34788
##  5 BELGRANO      BELGRANO          18010
##  6 CABALLITO     CABALLITO         17266
##  7 BALVANERA     BALVANERA         17223
##  8 BELGRANO      PALERMO           13522
##  9 PALERMO       BELGRANO          13132
## 10 BALVANERA     RECOLETA          12468

El OD Palermo-Palermo es el OD más frecuente. Filtremos solo estos viajes

od_palermo <- viajes_od %>%
  filter(barrio_origen == "PALERMO" & barrio_destino == "PALERMO")

Veamos el dataset resultante

head(od_palermo)
##   barrio_origen fecha_hora_origen barrio_destino fecha_hora_destino cantidad
## 1       PALERMO     2021-07-01 00        PALERMO      2021-07-01 00        7
## 2       PALERMO     2021-07-01 00        PALERMO      2021-07-01 01        2
## 3       PALERMO     2021-07-01 01        PALERMO      2021-07-01 01        1
## 4       PALERMO     2021-07-01 05        PALERMO      2021-07-01 05        2
## 5       PALERMO     2021-07-01 06        PALERMO      2021-07-01 06        6
## 6       PALERMO     2021-07-01 06        PALERMO      2021-07-01 07        1

Ahora si, vamos a transformar la variable fecha_hora_origen a formato fecha. Para esto pueden utilizar multiples funciones de lubridate dependiendo de como estén escritos los campos. Vamos a mostrar algunas de ellas. Pueden ver otras maneras aqui.

Nuestro campo tiene fecha y hora. La fecha se encuentra en formato: año(year), mes(month), día(day) y hora(hour). Se utiliza entonces la función ymd_h().

od_palermo <- od_palermo %>% 
  mutate(fh_inicio = ymd_h(fecha_hora_origen), 
         fh_final = ymd_h(fecha_hora_destino))

Veamos los resultados

head(od_palermo)
##   barrio_origen fecha_hora_origen barrio_destino fecha_hora_destino cantidad
## 1       PALERMO     2021-07-01 00        PALERMO      2021-07-01 00        7
## 2       PALERMO     2021-07-01 00        PALERMO      2021-07-01 01        2
## 3       PALERMO     2021-07-01 01        PALERMO      2021-07-01 01        1
## 4       PALERMO     2021-07-01 05        PALERMO      2021-07-01 05        2
## 5       PALERMO     2021-07-01 06        PALERMO      2021-07-01 06        6
## 6       PALERMO     2021-07-01 06        PALERMO      2021-07-01 07        1
##             fh_inicio            fh_final
## 1 2021-07-01 00:00:00 2021-07-01 00:00:00
## 2 2021-07-01 00:00:00 2021-07-01 01:00:00
## 3 2021-07-01 01:00:00 2021-07-01 01:00:00
## 4 2021-07-01 05:00:00 2021-07-01 05:00:00
## 5 2021-07-01 06:00:00 2021-07-01 06:00:00
## 6 2021-07-01 06:00:00 2021-07-01 07:00:00

Observamos que R ajustó el formato de nuestra variable.

Ahora la función summary nos va a brindar más información.

summary(od_palermo$fh_inicio)
##                  Min.               1st Qu.                Median 
## "2021-07-01 00:00:00" "2021-08-11 15:15:00" "2021-09-19 16:00:00" 
##                  Mean               3rd Qu.                  Max. 
## "2021-09-18 02:47:34" "2021-10-26 10:00:00" "2021-11-30 23:00:00"

Veamos la clase de la nueva variable

class(od_palermo$fh_inicio)
## [1] "POSIXct" "POSIXt"

Podemos ver que la clase de la variable fh_inicio es de POSIXct (fecha - hora) . Al tratarse de fechas, podríamos realizar operaciones. Por ejemplo, podríamos calcular la diferencia entre la fecha-hora de inicio, y la fecha-hora de finalización.

od_palermo <- od_palermo %>%
  mutate(duracion = fh_final - fh_inicio)

Veamos un resumen

head(od_palermo)
##   barrio_origen fecha_hora_origen barrio_destino fecha_hora_destino cantidad
## 1       PALERMO     2021-07-01 00        PALERMO      2021-07-01 00        7
## 2       PALERMO     2021-07-01 00        PALERMO      2021-07-01 01        2
## 3       PALERMO     2021-07-01 01        PALERMO      2021-07-01 01        1
## 4       PALERMO     2021-07-01 05        PALERMO      2021-07-01 05        2
## 5       PALERMO     2021-07-01 06        PALERMO      2021-07-01 06        6
## 6       PALERMO     2021-07-01 06        PALERMO      2021-07-01 07        1
##             fh_inicio            fh_final  duracion
## 1 2021-07-01 00:00:00 2021-07-01 00:00:00    0 secs
## 2 2021-07-01 00:00:00 2021-07-01 01:00:00 3600 secs
## 3 2021-07-01 01:00:00 2021-07-01 01:00:00    0 secs
## 4 2021-07-01 05:00:00 2021-07-01 05:00:00    0 secs
## 5 2021-07-01 06:00:00 2021-07-01 06:00:00    0 secs
## 6 2021-07-01 06:00:00 2021-07-01 07:00:00 3600 secs

La variable duracion es de tipo time. Podríamos transformarla a numérica para ver un resumen.

summary(as.numeric(od_palermo$duracion))
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##       0       0    3600    2626    3600  104400

Nuestro dataset tiene unicamente fecha y hora, por eso la diferencia no tiene valores menores a 3600 segundos (1hora). Hay muchos recorridos que se realizaron dentro de la misma hora.

Lubridate nos permite también calcular valores específicos en relación a la fecha. Como nuestra variable fh_inicio tiene una clase adecuada, podemos ingresar a los componetes ‘año’, ‘mes’ y ‘día’ con las funciones year(), month(), day()

od_palermo<- od_palermo %>% 
  mutate(anio = year(fh_inicio), 
         mes = month(fh_inicio), 
         dia = day(fh_inicio), 
         hora = hour(fh_inicio))

Veamos el resultado

head(od_palermo)
##   barrio_origen fecha_hora_origen barrio_destino fecha_hora_destino cantidad
## 1       PALERMO     2021-07-01 00        PALERMO      2021-07-01 00        7
## 2       PALERMO     2021-07-01 00        PALERMO      2021-07-01 01        2
## 3       PALERMO     2021-07-01 01        PALERMO      2021-07-01 01        1
## 4       PALERMO     2021-07-01 05        PALERMO      2021-07-01 05        2
## 5       PALERMO     2021-07-01 06        PALERMO      2021-07-01 06        6
## 6       PALERMO     2021-07-01 06        PALERMO      2021-07-01 07        1
##             fh_inicio            fh_final  duracion anio mes dia hora
## 1 2021-07-01 00:00:00 2021-07-01 00:00:00    0 secs 2021   7   1    0
## 2 2021-07-01 00:00:00 2021-07-01 01:00:00 3600 secs 2021   7   1    0
## 3 2021-07-01 01:00:00 2021-07-01 01:00:00    0 secs 2021   7   1    1
## 4 2021-07-01 05:00:00 2021-07-01 05:00:00    0 secs 2021   7   1    5
## 5 2021-07-01 06:00:00 2021-07-01 06:00:00    0 secs 2021   7   1    6
## 6 2021-07-01 06:00:00 2021-07-01 07:00:00 3600 secs 2021   7   1    6

Podemos incluso ajustar parámetros para las funciones. Por ejemplo, nos interesa el nombre del mes y no el número. Por eso vamos a activar la etiqueta de forma completa (no abreviada). También es posible configurar el idioma con el parámetro locale. Por ejemplo: locale = "Spanish" para Windows o locale = "es_ES.UTF-8" para IOS.

od_palermo <- od_palermo %>%
  mutate(mes = month(fh_inicio, label = TRUE, abbr = FALSE, locale =  "es_ES.UTF-8"))

Veamos ahora el resultado

head(od_palermo)
##   barrio_origen fecha_hora_origen barrio_destino fecha_hora_destino cantidad
## 1       PALERMO     2021-07-01 00        PALERMO      2021-07-01 00        7
## 2       PALERMO     2021-07-01 00        PALERMO      2021-07-01 01        2
## 3       PALERMO     2021-07-01 01        PALERMO      2021-07-01 01        1
## 4       PALERMO     2021-07-01 05        PALERMO      2021-07-01 05        2
## 5       PALERMO     2021-07-01 06        PALERMO      2021-07-01 06        6
## 6       PALERMO     2021-07-01 06        PALERMO      2021-07-01 07        1
##             fh_inicio            fh_final  duracion anio   mes dia hora
## 1 2021-07-01 00:00:00 2021-07-01 00:00:00    0 secs 2021 julio   1    0
## 2 2021-07-01 00:00:00 2021-07-01 01:00:00 3600 secs 2021 julio   1    0
## 3 2021-07-01 01:00:00 2021-07-01 01:00:00    0 secs 2021 julio   1    1
## 4 2021-07-01 05:00:00 2021-07-01 05:00:00    0 secs 2021 julio   1    5
## 5 2021-07-01 06:00:00 2021-07-01 06:00:00    0 secs 2021 julio   1    6
## 6 2021-07-01 06:00:00 2021-07-01 07:00:00 3600 secs 2021 julio   1    6

Vamos a realizar un gráfico de cantidad de viajes por mes

ggplot(od_palermo) +
  geom_bar(aes(x = mes, weight = cantidad, fill = mes)) + 
  labs(title = "Cantidad de Viajes PALERMO-PALERMO",
       subtitle = "Desde julio 2021 a noviembre 2021") + 
  theme_minimal() + 
  scale_fill_brewer(palette = 8, type = "qual") + 
  theme(legend.position = "none", 
        axis.title = element_blank())

Nos interesa conocer la evolución de la cantidad de viajes por día y hora para una semana en particular. Vamos a tomar la semana 27 (aprox principios de julio) Vamos a utilizar la función week() para calcular la semana

od_palermo <- od_palermo %>%
  mutate(semana = week(fh_inicio))
ggplot(od_palermo %>%
         filter(semana == 27)) +
  geom_line(aes(x=fh_inicio, y = cantidad), color =  "darkslateblue")+ 
  labs(title = "Cantidad de Viajes PALERMO-PALERMO",
       subtitle = "Año 2021 - Semana 27") + 
  theme_minimal() + 
  theme(legend.position = "none", 
        axis.title = element_blank())

Habiendo explorado algunas funciones iniciales en lubridate, ahora vamos a analizar los patrones temporales en nuestro dataset.

Patrones en movilidad en bicicleta en Buenos Aires

Para preprocesar nuestro dataset, vamos a transformas la variable fecha_hora_origen a formato fecha-hora. Además vamos a calcular solo el día (para luego agrupar por registros diarios), el día de la semana y la hora.
La función round_date() nos permite redondear el día, y la función wday() nos permite calcular el día de la semana.

viajes_od_final <- viajes_od %>%
  mutate(fecha_hora_o = ymd_h(fecha_hora_origen), 
         fecha_o = round_date(fecha_hora_o, "day"), 
         hora_o = hour(fecha_hora_o)) 
head(viajes_od_final, 10)
##    barrio_origen fecha_hora_origen barrio_destino fecha_hora_destino cantidad
## 1        ALMAGRO     2021-07-01 00        ALMAGRO      2021-07-01 00        1
## 2        ALMAGRO     2021-07-01 00       RECOLETA      2021-07-01 00        1
## 3        ALMAGRO     2021-07-01 05        PALERMO      2021-07-01 05        1
## 4        ALMAGRO     2021-07-01 06        ALMAGRO      2021-07-01 06        1
## 5        ALMAGRO     2021-07-01 06      BALVANERA      2021-07-01 06        1
## 6        ALMAGRO     2021-07-01 06      CABALLITO      2021-07-01 06        2
## 7        ALMAGRO     2021-07-01 06        PALERMO      2021-07-01 06        1
## 8        ALMAGRO     2021-07-01 06        PALERMO      2021-07-01 07        1
## 9        ALMAGRO     2021-07-01 07        ALMAGRO      2021-07-01 08        2
## 10       ALMAGRO     2021-07-01 07      BALVANERA      2021-07-01 07        3
##           fecha_hora_o    fecha_o hora_o
## 1  2021-07-01 00:00:00 2021-07-01      0
## 2  2021-07-01 00:00:00 2021-07-01      0
## 3  2021-07-01 05:00:00 2021-07-01      5
## 4  2021-07-01 06:00:00 2021-07-01      6
## 5  2021-07-01 06:00:00 2021-07-01      6
## 6  2021-07-01 06:00:00 2021-07-01      6
## 7  2021-07-01 06:00:00 2021-07-01      6
## 8  2021-07-01 06:00:00 2021-07-01      6
## 9  2021-07-01 07:00:00 2021-07-01      7
## 10 2021-07-01 07:00:00 2021-07-01      7

Registros diarios

Vamos a calcular la cantidad de registros por día para cada par origen-destino.

viajes_diarios <- viajes_od_final %>%
  group_by(fecha_o, barrio_origen, barrio_destino) %>%
  summarise(cantidad = sum(cantidad))
## `summarise()` has grouped output by 'fecha_o', 'barrio_origen'. You can override using the `.groups` argument.

De acuerdo a nuestro dataset de pares OD, los 3 más frecuentes son:
- Palermo - Palermo
- Palermo - Recoleta
- Recoleta - Palermo

Veamos como varía la cantidad de viajes diarios para esos destinos

viajes_diarios_od_freq <- viajes_diarios %>%
  filter(
    (barrio_origen == "PALERMO" & barrio_destino == "PALERMO") | 
    (barrio_origen == "PALERMO" & barrio_destino == "RECOLETA") |
    (barrio_origen == "RECOLETA" & barrio_destino == "PALERMO")) %>%
  mutate(etiqueta = paste0(barrio_origen, " a ", barrio_destino))

Veamos la variación en un gráfico

viajes_diarios_od_freq %>% 
  ggplot() + 
  geom_line(aes(x=fecha_o, y = cantidad, color = etiqueta)) + 
  labs(title = "Viajes por día", 
       subtitle = "Para OD más frecuentes entre Julio y Noviembre 2021", 
       caption = "Fuente: BADATA", 
       x = "", 
       y = "", 
       color = "") + 
  scale_color_brewer(type = "qual", palette = 2) +
  scale_y_continuous(breaks = seq(0, 10000, 2000)) + 
  theme_minimal() + 
  theme(legend.position = "bottom")

El comportamiento diario de los viajes Palermo-Recoleta y Recoleta-Palermo son muy similares. ¿Se tratará de viajes de ida y de vuelta? En este caso deberíamos detectar un comportamiento distinto a nivel horario.

Registros por hora

viajes_hora_od_freq <- viajes_od_final %>%
  group_by(hora_o, barrio_origen, barrio_destino) %>%
  summarise(cantidad = sum(cantidad)) %>% 
  filter(
    (barrio_origen == "PALERMO" & barrio_destino == "PALERMO") | 
    (barrio_origen == "PALERMO" & barrio_destino == "RECOLETA") |
    (barrio_origen == "RECOLETA" & barrio_destino == "PALERMO")) %>%
  mutate(etiqueta = paste0(barrio_origen, " a ", barrio_destino))
## `summarise()` has grouped output by 'hora_o', 'barrio_origen'. You can override using the `.groups` argument.

Veamos el comportamiento

viajes_hora_od_freq %>% 
  ggplot() + 
  geom_line(aes(x=hora_o, y = cantidad, color = etiqueta)) + 
  labs(title = "Viajes por hora", 
       subtitle = "Para OD más frecuentes entre Julio y Noviembre 2021", 
       caption = "Fuente: BADATA", 
       x = "", 
       y = "", 
       color = "") + 
  scale_color_brewer(type = "qual", palette = 2) +
  scale_y_continuous(breaks = seq(0, 10000, 2000)) + 
  theme_minimal() + 
  theme(legend.position = "bottom")

El comportamiento es nuevamente similar. Para los tres pares OD, los viajes a la tarde son más frecuentes.

Registros por día de la semana

En el gráfico de viajes por día, detectamos un patrón en el comportamiento: se observan picos de descenso en el uso de bicicletas de manera frecuente. ¿Está relacionado con el día de la semana?
La función wday() del paquete de lubridate permite calcular el día de la semana.

wday(today())
## [1] 1

El día de la semana de formato numérico es poco claro. Podemos ajustar los parámetros label (para mostrar el día en texto) y abbr (texto completo o abreviado)

wday(today(), label = TRUE, abbr = FALSE)
## [1] Sunday
## 7 Levels: Sunday < Monday < Tuesday < Wednesday < Thursday < ... < Saturday

Por defecto, los parámetros se encuentran de la siguiente manera: label = FALSE y abbr =TRUE. También se puede configurar el idioma. Por último, podemos ajustar el parámetro week_start para determinar el inicio de la semana. Por defecto, es week_start = 0 , es decir primero domingo.

viajes_dia_semana <- viajes_diarios %>%
  group_by(fecha_o) %>%
  summarise(cantidad = sum(cantidad)) %>%
  mutate(dia_semana_o = wday(fecha_o, 
                             label = TRUE, 
                             abbr = FALSE, 
                             locale = "es_ES.UTF-8", 
                             week_start = 1))

Veamos nuestro nuevo dataset

head(viajes_dia_semana)
## # A tibble: 6 × 3
##   fecha_o             cantidad dia_semana_o
##   <dttm>                 <int> <ord>       
## 1 2021-07-01 00:00:00     1108 jueves      
## 2 2021-07-02 00:00:00     4286 viernes     
## 3 2021-07-03 00:00:00     3729 sábado      
## 4 2021-07-04 00:00:00     1814 domingo     
## 5 2021-07-05 00:00:00     2782 lunes       
## 6 2021-07-06 00:00:00     4823 martes

Veamos como varía la cantidad de viajes según día de la semana.

ggplot(viajes_dia_semana) + 
  geom_boxplot(aes(x = dia_semana_o, 
                   y=cantidad, 
                   fill = as.character(dia_semana_o))) + 
  labs(title = "Cantidad de viajes", 
       y = "", 
       x = "Día de la semana de inicio") + 
  theme_minimal() + 
  scale_y_continuous(breaks = seq(0,10000,1000)) + 
  scale_fill_brewer(palette = 8, type = "qual") + 
  theme(legend.position = "none")

Podemos observar que el valor medio de los viajes es muy similar desde martes a viernes, disminuyendo considerablemente los domingos.

¡Con el paquete ‘lubridate’ podemos trabajar sencillamente con las variables de tipo fecha!