Capítulo25 Fechas, horas y minutas

## [1] "2026-09-09"

El tema proviene de los siguientes sitios.

English: https://r4ds.had.co.nz/dates-and-times.html

Español: https://es.r4ds.hadley.nz/16-datetimes.html


25.1 Temas:

  • Por qué las fechas son más difíciles de lo que parecen

  • Los tres tipos: <date>, <time> y <dttm>

  • Convertir texto a fecha con ymd() y compañía

  • Extraer partes: year(), month(), wday(), hour()

  • Redondear: floor_date(), ceiling_date(), round_date()

  • Duraciones, períodos e intervalos: los tres no son lo mismo

  • Zonas horarias

  • library(lubridate)

  • library(datos)

  • library(tidyverse)


25.2 Por qué las fechas son difíciles

Contar tiempo parece trivial y no lo es, porque el calendario que usamos está lleno de excepciones que el código tiene que respetar:

  • los meses no duran lo mismo, así que “un mes después del 31 de enero” no tiene una respuesta obvia;
  • hay años bisiestos, así que un año no siempre son 365 días;
  • hay cambios de horario de verano, así que un día no siempre son 24 horas;
  • hay zonas horarias, así que dos personas pueden estar en fechas distintas en el mismo instante;
  • y hasta hay segundos intercalares, que se añaden de vez en cuando para cuadrar el reloj con la rotación de la Tierra.

De ahí sale la idea central del capítulo, que conviene tener clara desde el principio: hay dos maneras distintas de medir tiempo, y hacen falta las dos. Una cuenta segundos exactos, sin excepciones, y la otra cuenta en unidades humanas respetando el calendario. Casi todos los errores con fechas vienen de usar la que no era.

Puerto Rico no cambia de horario de verano y usa siempre la zona "America/Puerto_Rico" (AST, cuatro horas menos que UTC). Eso nos ahorra un problema que sufren los que trabajan con datos de Estados Unidos continental, pero aparece en cuanto se comparan datos de aquí con datos de allá.


25.3 Creando fechas/horas

En R y en los sistemas tipo Unix el tiempo se cuenta como el número de segundos (o días) transcurridos desde la medianoche del 1 de enero de 1970, conocida como la época Unix. Por eso as.numeric() de una fecha devuelve los días desde ese punto.

R distingue tres cosas, y conviene usar la más sencilla que sirva:

  • date,
  • time,
  • date-time,
  • <date>: solo el día, sin hora. Por dentro es un número: los días transcurridos desde el 1 de enero de 1970.
  • <time>: solo la hora del día, sin fecha. Por dentro, los segundos desde la medianoche.
  • <dttm>: fecha y hora juntas. Por dentro, los segundos desde la época Unix, y además arrastra una zona horaria.

Usa <dttm> solo cuando de verdad necesites la hora: las fechas-hora arrastran zona horaria y complican todo lo demás.

https://github.com/edgararuiz/guias-rapidas/blob/master/fechas.pdf

library(tidyverse)
library(lubridate) # paquete para convertir fechas

tidyverse_packages() # lista completa de los paquetes activado con **tidyverse**
##  [1] "broom"         "conflicted"    "cli"           "dbplyr"       
##  [5] "dplyr"         "dtplyr"        "forcats"       "ggplot2"      
##  [9] "googledrive"   "googlesheets4" "haven"         "hms"          
## [13] "httr"          "jsonlite"      "lubridate"     "magrittr"     
## [17] "modelr"        "pillar"        "purrr"         "ragg"         
## [21] "readr"         "readxl"        "reprex"        "rlang"        
## [25] "rstudioapi"    "rvest"         "stringr"       "tibble"       
## [29] "tidyr"         "xml2"          "tidyverse"

25.4 El paquete lubridate

ymd(), mdy(), dmy() (lubridate): convertir texto a fechas.

  • Qué hacen: interpretan una cadena según el orden de sus partes: ymd("2024-03-01"), mdy("3/1/2024"), dmy("1/3/2024").
  • Con hora: añade _hms, _hm o _h (p. ej. dmy_hms()).
  • Extraer partes: year(), month(), day(), wday() (día de la semana).

El truco de lubridate es que el nombre de la función dice el orden de las partes, no el formato exacto. dmy() significa “día, mes, año”, y con eso le basta: acepta "1/3/2024", "01-03-2024", "1 marzo 2024" y "1.3.24". No hay que escribir formatos como %d/%m/%Y, que es lo que hacía falta en base R y lo que casi nadie recuerda.

"01/03/2024" es el 1 de marzo o el 3 de enero según de dónde venga el archivo, y ninguna función puede adivinarlo. dmy() y mdy() las dos van a darte una fecha válida, y una de las dos estará mal. Antes de convertir, averigua de dónde salieron los datos. Si hay valores con día mayor que 12 en la columna, salen NA con una de las dos funciones, y esa es tu pista.

La función today() regresa la fecha de hoy, similar a Sys.date()

today()
## [1] "2026-09-09"

Use la función de tribble(), del paquete tibble, para crear una tabla manualmente

fechas <- tribble(
  ~ codigo, ~ fecha,
    "001",    "01/01/2019 00:00:00",
    "002",    "31/03/2019 01:05:00",
    "003",    "14/06/2019 20:00:00",
    "004",    "01/12/1859 11:32:13",
    "005",    "01/01/0501 05:04:33"
)
fechas
## # A tibble: 5 × 2
##   codigo fecha              
##   <chr>  <chr>              
## 1 001    01/01/2019 00:00:00
## 2 002    31/03/2019 01:05:00
## 3 003    14/06/2019 20:00:00
## 4 004    01/12/1859 11:32:13
## 5 005    01/01/0501 05:04:33

Hay varias opciones para convertir texto a fechas, el más usado es mdy_hms(), pero en este caso el formato utilizado no funciona bien.

Un error muy común es usar mdy() cuando las fechas están en formato día/mes/año. Como no existe un mes 31, obtendrás NA o fechas equivocadas; elige la función (dmy(), mdy(), ymd()) según el orden real de la fecha.

25.4.1 Un error tipico

fechas %>%
  mutate(fecha = mdy_hms(fecha))
## # A tibble: 5 × 2
##   codigo fecha              
##   <chr>  <dttm>             
## 1 001    2019-01-01 00:00:00
## 2 002    NA                 
## 3 003    NA                 
## 4 004    1859-01-12 11:32:13
## 5 005    0501-01-01 05:04:33

Ya que el día es el primero, y no el mes, usamos dmy_hms()

nueva_fechas <- fechas %>%
  mutate(fecha = dmy_hms(fecha))

nueva_fechas
## # A tibble: 5 × 2
##   codigo fecha              
##   <chr>  <dttm>             
## 1 001    2019-01-01 00:00:00
## 2 002    2019-03-31 01:05:00
## 3 003    2019-06-14 20:00:00
## 4 004    1859-12-01 11:32:13
## 5 005    0501-01-01 05:04:33

lubridate tiene varias funciones para extraer partes de la fecha, por ejemplo: año, mes, día, hora, minuto, y cuatrimestre.

nueva_fechas %>%
  mutate(a = year(fecha),
         m = month(fecha),
         d = day(fecha),
         h = hour(fecha),
         mn = minute(fecha),
         q = quarter(fecha)
         )
## # A tibble: 5 × 8
##   codigo fecha                   a     m     d     h    mn     q
##   <chr>  <dttm>              <int> <int> <int> <int> <int> <int>
## 1 001    2019-01-01 00:00:00  2019     1     1     0     0     1
## 2 002    2019-03-31 01:05:00  2019     3    31     1     5     1
## 3 003    2019-06-14 20:00:00  2019     6    14    20     0     2
## 4 004    1859-12-01 11:32:13  1859    12     1    11    32     4
## 5 005    0501-01-01 05:04:33   501     1     1     5     4     1

Las funciones round_date(), ceiling_date() y floor_date() permiten redondear la fecha al número más cercano de la unidad especificada

nueva_fechas %>%
  mutate(
    redondear = round_date(fecha, unit = " month"), # round_date es redondea al mes más cercano
    techo = ceiling_date(fecha, unit = "day"), # ceiling_date redondea al día más cercano
    suelo = floor_date(fecha, unit = "month"), # floor_date redondea al mes más cercano
    suelo_hour= floor_date(fecha, unit = "hour") # redondea a la hora más cercana
  )
## # A tibble: 5 × 6
##   codigo fecha               redondear           techo              
##   <chr>  <dttm>              <dttm>              <dttm>             
## 1 001    2019-01-01 00:00:00 2019-01-01 00:00:00 2019-01-01 00:00:00
## 2 002    2019-03-31 01:05:00 2019-04-01 00:00:00 2019-04-01 00:00:00
## 3 003    2019-06-14 20:00:00 2019-06-01 00:00:00 2019-06-15 00:00:00
## 4 004    1859-12-01 11:32:13 1859-12-01 00:00:00 1859-12-02 00:00:00
## 5 005    0501-01-01 05:04:33 0501-01-01 00:00:00 0501-01-02 00:00:00
## # ℹ 2 more variables: suelo <dttm>, suelo_hour <dttm>

25.5 Intervalos y duraciones

La función interval() crea un objeto R de intervalo de tiempo. En este caso, el intervalo entre la fecha en la tabla, y el día de hoy. Nota que esto puede ser muy útil si quiere calcular la cantidad de tiempo entre dos fechas.

nueva_fechas %>%
  mutate(intervalo = interval(fecha, today())) # intervalo entre la fecha y hoy
## # A tibble: 5 × 3
##   codigo fecha               intervalo                              
##   <chr>  <dttm>              <Interval>                             
## 1 001    2019-01-01 00:00:00 2019-01-01 00:00:00 UTC--2026-09-09 UTC
## 2 002    2019-03-31 01:05:00 2019-03-31 01:05:00 UTC--2026-09-09 UTC
## 3 003    2019-06-14 20:00:00 2019-06-14 20:00:00 UTC--2026-09-09 UTC
## 4 004    1859-12-01 11:32:13 1859-12-01 11:32:13 UTC--2026-09-09 UTC
## 5 005    0501-01-01 05:04:33 0501-01-01 05:04:33 UTC--2026-09-09 UTC

int_length() regresa el número de segundos dentro del intervalo

nueva_fechas %>%
  mutate(
    intervalo = interval(fecha, today()), #
    segundos = int_length(intervalo) # número de segundos en el intervalo
    )
## # A tibble: 5 × 4
##   codigo fecha               intervalo                                  segundos
##   <chr>  <dttm>              <Interval>                                    <dbl>
## 1 001    2019-01-01 00:00:00 2019-01-01 00:00:00 UTC--2026-09-09 UTC   242611200
## 2 002    2019-03-31 01:05:00 2019-03-31 01:05:00 UTC--2026-09-09 UTC   234917700
## 3 003    2019-06-14 20:00:00 2019-06-14 20:00:00 UTC--2026-09-09 UTC   228369600
## 4 004    1859-12-01 11:32:13 1859-12-01 11:32:13 UTC--2026-09-09 UTC  5262841667
## 5 005    0501-01-01 05:04:33 0501-01-01 05:04:33 UTC--2026-09-09 UTC 48146036127

25.6 El operador %--% simplifica el cálculo del intervalo

Compara con el script anterior

nueva_fechas %>%
  mutate(intervalo = fecha %--% today())
## # A tibble: 5 × 3
##   codigo fecha               intervalo                              
##   <chr>  <dttm>              <Interval>                             
## 1 001    2019-01-01 00:00:00 2019-01-01 00:00:00 UTC--2026-09-09 UTC
## 2 002    2019-03-31 01:05:00 2019-03-31 01:05:00 UTC--2026-09-09 UTC
## 3 003    2019-06-14 20:00:00 2019-06-14 20:00:00 UTC--2026-09-09 UTC
## 4 004    1859-12-01 11:32:13 1859-12-01 11:32:13 UTC--2026-09-09 UTC
## 5 005    0501-01-01 05:04:33 0501-01-01 05:04:33 UTC--2026-09-09 UTC

Para saber el número de días en el intervalo, divida el intervalo por la función que corresponde a días, days()

nueva_fechas %>%
  mutate(dias = fecha %--% today() / days(),
         anio= fecha %--% today() / years()) # número de días en el intervalo
## # A tibble: 5 × 4
##   codigo fecha                  dias    anio
##   <chr>  <dttm>                <dbl>   <dbl>
## 1 001    2019-01-01 00:00:00   2808     7.69
## 2 002    2019-03-31 01:05:00   2719.    7.44
## 3 003    2019-06-14 20:00:00   2643.    7.24
## 4 004    1859-12-01 11:32:13  60913.  167.  
## 5 005    0501-01-01 05:04:33 557246. 1526.
#Cuantos años de diferencia en el intervalo?

Intervalo entre una lista de fecha y otra fecha y hora específica

nueva_fechas %>%
  mutate(dias = fecha %--% "2020-01-01 00:00:00" / days())
## # A tibble: 5 × 3
##   codigo fecha                  dias
##   <chr>  <dttm>                <dbl>
## 1 001    2019-01-01 00:00:00    365 
## 2 002    2019-03-31 01:05:00    276.
## 3 003    2019-06-14 20:00:00    200.
## 4 004    1859-12-01 11:32:13  58470.
## 5 005    0501-01-01 05:04:33 554803.

25.7 Ejercicio

Usa esta función y calcula el número de día que ha transcurrido entre el día de su nacimiento y el día de hoy

segundos = "2018-08-14 00:00:00" %--% today()/seconds()
segundos
## [1] 254707200
dias = "2018-08-14 00:00:00" %--% today() /  days()
dias
## [1] 2948
mes= "2018-08-14 00:00:00" %--% today() /  months(1)
mes
## [1] 96.83871
anio = "2018-08-14 00:00:00" %--% today()/ years()
anio
## [1] 8.071233
millisecond = "2018-08-14 00:00:00" %--% today() /  milliseconds()

millisecond
## [1] 254707200000

Los resultados se pueden visualizar usando ggplot2

nueva_fechas %>%
  mutate(dias = fecha %--% today() / years()) %>%
  ggplot() +
  geom_col(aes(codigo, dias)) +
  coord_flip()

25.8 Serie de funciones para calcular la duración de un intervalo de tiempo.

  • dyears() # años
  • dmonths() # meses
  • dweeks() # semanas
  • ddays() # días
  • dhours() # horas
  • dminutes() # minutos
  • dseconds() # segundos
  • dmilliseconds() # milisegundos
  • dmicroseconds() # microsegundos
  • dnanoseconds() # nanosegundos
  • dpicoseconds() # picosegundos

25.9 as.duration

as.duration() crea un objecto en R que contiene la duración del intervalo de tiempo.

nueva_fechas %>%
  mutate(desde_hoy = as.duration(fecha %--% today())) # duración desde la fecha hasta hoy
## # A tibble: 5 × 3
##   codigo fecha               desde_hoy                    
##   <chr>  <dttm>              <Duration>                   
## 1 001    2019-01-01 00:00:00 242611200s (~7.69 years)     
## 2 002    2019-03-31 01:05:00 234917700s (~7.44 years)     
## 3 003    2019-06-14 20:00:00 228369600s (~7.24 years)     
## 4 004    1859-12-01 11:32:13 5262841667s (~166.77 years)  
## 5 005    0501-01-01 05:04:33 48146036127s (~1525.66 years)

El objeto de duración de tiempo se puede filtrar fácilmente basado en una variedad de tipo de tiempos. En este caso, semanas dweeks() crea un objeto de duración de la largura especificada

nueva_fechas %>%
  mutate(desde_hoy = as.duration(fecha %--% today())) %>%
  filter(desde_hoy > dyears(7))
## # A tibble: 5 × 3
##   codigo fecha               desde_hoy                    
##   <chr>  <dttm>              <Duration>                   
## 1 001    2019-01-01 00:00:00 242611200s (~7.69 years)     
## 2 002    2019-03-31 01:05:00 234917700s (~7.44 years)     
## 3 003    2019-06-14 20:00:00 228369600s (~7.24 years)     
## 4 004    1859-12-01 11:32:13 5262841667s (~166.77 years)  
## 5 005    0501-01-01 05:04:33 48146036127s (~1525.66 years)

Otra opción es ddays().

nueva_fechas %>%
  mutate(desde_hoy = as.duration(fecha %--% today())) %>%
  filter(desde_hoy < ddays(2500))  # Nota que se puede filtrar por el número de días que han acumulado
## # A tibble: 0 × 3
## # ℹ 3 variables: codigo <chr>, fecha <dttm>, desde_hoy <Duration>

25.9.1 Funciones

  • today()
  • now()
today()
## [1] "2026-09-09"
now()
## [1] "2026-09-09 14:06:18 AST"

25.9.2 Fechas con años, meses y días

  • ymd()
  • ydm()
  • mdy()
  • myd()
  • dmy()
  • dym()

25.9.3 Fechas con horas, minutos y segundos

  • ymd_hms()

  • ymd_hm()

  • ymd_h()

  • ydm_hms()

  • ydm_hm()

  • etc

25.9.4 Fechas

  • yq() Year quarter (quarters are 1, 2, 3, 4, Jan-March, April-Jun, Jul-Sept, Oct-Dec)
x =c("2012.1", "1970.4")
yq(x)
## [1] "2012-01-01" "1970-10-01"
yq("2012.1")
## [1] "2012-01-01"
  • Cuidado con el paquete hms que tiene funciones igual como lubridate (hms, hm, y ms) , pero no necesariamente hace lo mismo.

25.10 Crear una fecha desde columnas individuales

  • make_date()
  • make_datetime()

Unir el año, mes, día, hora y minutos que estén en diferentes columnas en uno

  • sumamente practico cuando se tiene una base de datos con las fechas en diferentes columnas
library(datos)
head(vuelos)
## # A tibble: 6 × 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
## # ℹ 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>
vuelos %>%
  dplyr::select(anio, mes, dia, hora, minuto) %>%
  mutate(salida_dia=make_date(anio, mes, dia),
         salida_dia_minutos = make_datetime(anio, mes, dia, hora, minuto))
## # A tibble: 336,776 × 7
##     anio   mes   dia  hora minuto salida_dia salida_dia_minutos 
##    <int> <int> <int> <dbl>  <dbl> <date>     <dttm>             
##  1  2013     1     1     5     15 2013-01-01 2013-01-01 05:15:00
##  2  2013     1     1     5     29 2013-01-01 2013-01-01 05:29:00
##  3  2013     1     1     5     40 2013-01-01 2013-01-01 05:40:00
##  4  2013     1     1     5     45 2013-01-01 2013-01-01 05:45:00
##  5  2013     1     1     6      0 2013-01-01 2013-01-01 06:00:00
##  6  2013     1     1     5     58 2013-01-01 2013-01-01 05:58:00
##  7  2013     1     1     6      0 2013-01-01 2013-01-01 06:00:00
##  8  2013     1     1     6      0 2013-01-01 2013-01-01 06:00:00
##  9  2013     1     1     6      0 2013-01-01 2013-01-01 06:00:00
## 10  2013     1     1     6      0 2013-01-01 2013-01-01 06:00:00
## # ℹ 336,766 more rows

Unir el año, mes, día, hora que estén en diferentes columnas en uno

vuelos %>%
  dplyr::select(anio, mes, dia, hora, minuto) %>%
  mutate(salida2 = make_date(anio, mes, dia))
## # A tibble: 336,776 × 6
##     anio   mes   dia  hora minuto salida2   
##    <int> <int> <int> <dbl>  <dbl> <date>    
##  1  2013     1     1     5     15 2013-01-01
##  2  2013     1     1     5     29 2013-01-01
##  3  2013     1     1     5     40 2013-01-01
##  4  2013     1     1     5     45 2013-01-01
##  5  2013     1     1     6      0 2013-01-01
##  6  2013     1     1     5     58 2013-01-01
##  7  2013     1     1     6      0 2013-01-01
##  8  2013     1     1     6      0 2013-01-01
##  9  2013     1     1     6      0 2013-01-01
## 10  2013     1     1     6      0 2013-01-01
## # ℹ 336,766 more rows

## Conversión de fechas que no son estándar

Datos_raros=tribble(~Ind, ~Persona, ~fecha_de_nacimiento,
                    1, "Abuela", "1/1/30",
                    2, "Abuelo","1/30/01",
                    3, "Tio", "1/30/09")

Datos_raros 
## # A tibble: 3 × 3
##     Ind Persona fecha_de_nacimiento
##   <dbl> <chr>   <chr>              
## 1     1 Abuela  1/1/30             
## 2     2 Abuelo  1/30/01            
## 3     3 Tio     1/30/09
Datos_raros %>%
  mutate(nueva_fecha = mdy(fecha_de_nacimiento))
## # A tibble: 3 × 4
##     Ind Persona fecha_de_nacimiento nueva_fecha
##   <dbl> <chr>   <chr>               <date>     
## 1     1 Abuela  1/1/30              2030-01-01 
## 2     2 Abuelo  1/30/01             2001-01-30 
## 3     3 Tio     1/30/09             2009-01-30
as.Date(0, origin = "1970-01-01")
## [1] "1970-01-01"
# Returns "1970-01-01"
df <- tibble(
  Persona = c("Abuela", "Abuelo", "Tio"),
  fecha_de_nacimiento = c("1/1/1930", "1/30/1901", "1/30/1909")
)


df <- df %>%
  mutate(
    nueva_fecha = mdy(fecha_de_nacimiento),
    nueva_fecha = if_else(year(nueva_fecha) > year(today()), 
                          nueva_fecha - years(100), 
                          nueva_fecha)
  )
df
## # A tibble: 3 × 3
##   Persona fecha_de_nacimiento nueva_fecha
##   <chr>   <chr>               <date>     
## 1 Abuela  1/1/1930            1930-01-01 
## 2 Abuelo  1/30/1901           1901-01-30 
## 3 Tio     1/30/1909           1909-01-30
#Datos_raros$fecha2=as.Date(as.Date(format(as.Date(Datos_raros$fecha_de_nacimiento, format="%m/%d/%Y"), "19%y%m%d"), "%Y%m%d"))

#Datos_raros

25.11 %/% y %%: división entera y residuo

Estos dos operadores no son de fechas, pero aparecen constantemente al trabajar con ellas, porque muchos archivos guardan la hora como un número: las 5:30 de la tarde vienen como 1730.

  • %/% es la división entera: se queda con la parte de arriba y tira el resto. 1730 %/% 100 da 17, la hora.
  • %% es el residuo: se queda con lo que sobra. 1730 %% 100 da 30, los minutos.

Con los dos juntos se parte un 1730 en hora y minutos sin tocar texto.

vuelos |> dplyr::select(tiempo_vuelo) |> 
  mutate(t=tiempo_vuelo %/% 100) |>  # Division
  mutate(m= tiempo_vuelo %% 100)     # modulus... Cambia el 100 para 1, 10, 1000... y mira cual es el resultado
## # A tibble: 336,776 × 3
##    tiempo_vuelo     t     m
##           <dbl> <dbl> <dbl>
##  1          227     2    27
##  2          227     2    27
##  3          160     1    60
##  4          183     1    83
##  5          116     1    16
##  6          150     1    50
##  7          158     1    58
##  8           53     0    53
##  9          140     1    40
## 10          138     1    38
## # ℹ 336,766 more rows

25.11.1 El operador %% devuelve el residuo de una división

  • Por ejemplo, 5 %% 2 devuelve 1, porque el residuo de dividir 5 entre 2 es 1.
  • Y 5 %/% 2 devuelve 2, la parte entera de la división.

%% sirve también para preguntar “¿es múltiplo de?”: x %% 2 == 0 es la manera de saber si un número es par, y anio %% 4 == 0 es el primer paso de la regla de los años bisiestos.

names(vuelos)
##  [1] "anio"               "mes"                "dia"               
##  [4] "horario_salida"     "salida_programada"  "atraso_salida"     
##  [7] "horario_llegada"    "llegada_programada" "atraso_llegada"    
## [10] "aerolinea"          "vuelo"              "codigo_cola"       
## [13] "origen"             "destino"            "tiempo_vuelo"      
## [16] "distancia"          "hora"               "minuto"            
## [19] "fecha_hora"
hacer_fechahora_100 <- function(anio, mes, dia, tiempo) {
  make_datetime(anio, mes, dia, tiempo %/% 100, tiempo %% 100)
}

vuelos_dt <- vuelos %>%
  filter(!is.na(horario_salida), !is.na(horario_llegada)) %>%
  mutate(
    horario_salida = hacer_fechahora_100(anio, mes, dia, horario_salida),
    horario_llegada = hacer_fechahora_100(anio, mes, dia, horario_llegada),
    salida_programada = hacer_fechahora_100(anio, mes, dia, salida_programada),
    llegada_programada = hacer_fechahora_100(anio, mes, dia, llegada_programada)
  ) %>%
  dplyr::select(origen, destino, starts_with("atraso"), starts_with("horario"), ends_with("programada"), tiempo_vuelo)

head(vuelos_dt)
## # A tibble: 6 × 9
##   origen destino atraso_salida atraso_llegada horario_salida     
##   <chr>  <chr>           <dbl>          <dbl> <dttm>             
## 1 EWR    IAH                 2             11 2013-01-01 05:17:00
## 2 LGA    IAH                 4             20 2013-01-01 05:33:00
## 3 JFK    MIA                 2             33 2013-01-01 05:42:00
## 4 JFK    BQN                -1            -18 2013-01-01 05:44:00
## 5 LGA    ATL                -6            -25 2013-01-01 05:54:00
## 6 EWR    ORD                -4             12 2013-01-01 05:54:00
## # ℹ 4 more variables: horario_llegada <dttm>, salida_programada <dttm>,
## #   llegada_programada <dttm>, tiempo_vuelo <dbl>

25.11.1.1 Cuantos segundos hay en un dia?

vuelos_dt %>%
  ggplot(aes(horario_salida)) +
  geom_freqpoly(binwidth = 86400) # 86400 segundos = 1 día

vuelos_dt %>%
  filter(horario_salida < ymd(20130102)) %>%
  ggplot(aes(horario_salida)) +
  geom_freqpoly(binwidth = 600) # 600 segundos = 10 minutos

Nota variables que no funciona

ymd(c("2010-10-10", "bananas"))
## [1] "2010-10-10" NA
d1 <- "Jan 1, 2010"
d1
## [1] "Jan 1, 2010"
mdy(d1)
## [1] "2010-01-01"
d2 <- "2015-Mar-07"
d2
## [1] "2015-Mar-07"
ymd(d2)
## [1] "2015-03-07"
d3 <- "06-Jun-2017"
d3
## [1] "06-Jun-2017"
dmy(d3)
## [1] "2017-06-06"
d4 <- c("Aug 19 (2015)", "Jul 1 (2015)")
d4
## [1] "Aug 19 (2015)" "Jul 1 (2015)"
mdy(d4)
## [1] "2015-08-19" "2015-07-01"
d5 <- "12/30/14" # Diciembre 30, 2014
d5
## [1] "12/30/14"
mdy(d5)
## [1] "2014-12-30"
d6 <- "ene 1, 2010"
d6
## [1] "ene 1, 2010"
mdy(d6)
## [1] NA
d7 <- c("Agosto 19 (2015)", "Julio 1 (2015)")
d7
## [1] "Agosto 19 (2015)" "Julio 1 (2015)"
mdy(d7)
## [1] NA NA
mdy(d1)
## [1] "2010-01-01"
ymd(d2)
## [1] "2015-03-07"
dmy(d3)
## [1] "2017-06-06"
mdy(d4)
## [1] "2015-08-19" "2015-07-01"
mdy(d5)
## [1] "2014-12-30"
mdy(d6)
## [1] NA
mdy(d7)
## [1] NA NA
  1. Ejercicios:

Hacer los ejercicios en la sección 16.2.4 del libro en español


25.12 Extrayendo parte de las fecha-hora

  • year()
  • month()
  • mday()
  • yday()
  • wday()

Extraer y redondear (lubridate): sacar una parte de una fecha, o llevarla a un borde.

  • year(), month(), mday(), yday(), wday(): el año, el mes, el día del mes, el día del año (1 a 366) y el día de la semana.

  • month(x, label = TRUE) y wday(x, label = TRUE): devuelven el nombre en vez del número, como factor ordenado. Es lo que hace falta para graficar.

  • hour(), minute(), second(): las partes de la hora.

  • ceiling_date()

  • floor_date()

  • round_date()

  • floor_date(x, "month"): lleva la fecha hacia atrás al principio del mes. Es la manera de agrupar por mes sin perder el año.

  • ceiling_date(): hacia adelante; round_date(): al borde más cercano.

Para contar por mes, floor_date(fecha, "month") es mejor que month(fecha). month() devuelve un número del 1 al 12 y mezcla los años: el enero de 2016 y el de 2017 caen en el mismo grupo. floor_date() conserva el año, así que la serie sale en orden y sin mezclas.

  1. Ejercicios:

Hacer estos ejercicios en la sección 16.3.4 del libro en español. Entregar por MSTeams.

¿Cómo cambia la demora promedio durante el curso de un día? ¿Deberías usar horario_salida o salida_programada? ¿Por qué?

¿En qué día de la semana deberías salir si quieres minimizar las posibilidades de una demora?

Confirma nuestra hipótesis de que las salidas programadas en los minutos 20-30 y 50-60 están causadas por los vuelos programados que salen más temprano. Pista: crea una variable binaria que te diga si un vuelo tuvo o no demora.


25.13 Lapso de tiempo

Aquí está la idea más importante del capítulo. lubridate ofrece tres maneras de hablar de un lapso de tiempo, y no son intercambiables:

Duraciones, períodos e intervalos: tres maneras de medir un lapso.

  • Duración (dseconds(), ddays(), dyears()): un número exacto de segundos. Un dyears(1) son siempre 31 536 000 segundos, haya bisiesto o no. Es tiempo de físico.
  • Período (days(), months(), years()): unidades humanas, que se estiran según el calendario. years(1) sumado al 1 de marzo da el 1 de marzo siguiente, tenga ese año 365 o 366 días.
  • Intervalo (inicio %--% fin): un lapso con puntos de inicio y fin concretos. Solo con un intervalo se puede saber cuántos días tuvo de verdad, porque se sabe entre qué fechas está.

La regla práctica: períodos para hablar con humanos, duraciones para hacer física, intervalos cuando importa exactamente cuándo empezó.

El ejemplo que lo deja claro: si sumas dyears(1) (una duración) a una fecha justo antes de un 29 de febrero, caes en un día distinto del que esperabas, porque un año bisiesto tiene un día más y la duración no lo sabe. Con years(1) (un período) caes donde cualquier persona diría. Los dos son correctos; contestan preguntas distintas.

25.14 duraciones

# ¿Qué edad tiene Charles Darwin?  fecha de naciemiento February 12, 1809
edad_h <- today() - ymd("1809-02-12")
edad_h <- today() - ymd(18090212)
edad_h
## Time difference of 79467 days
as.duration(edad_h)
## [1] "6865948800s (~217.57 years)"
x=as.duration(edad_h)

Otras funciones de duración

dseconds(15)
## [1] "15s"
dminutes(10)
## [1] "600s (~10 minutes)"
dhours(c(12, 24))
## [1] "43200s (~12 hours)" "86400s (~1 days)"
ddays(0:5)
## [1] "0s"                "86400s (~1 days)"  "172800s (~2 days)"
## [4] "259200s (~3 days)" "345600s (~4 days)" "432000s (~5 days)"
dweeks(3)
## [1] "1814400s (~3 weeks)"
dyears(1)
## [1] "31557600s (~1 years)"

25.15 Puede agregar periodos de tiempo

2 * dyears(1)
## [1] "63115200s (~2 years)"
dyears(1) + dweeks(12) + dhours(15)
## [1] "38869200s (~1.23 years)"
ayer <- today() - ddays(1)
anio_pasado <- today() - dyears(1)

ayer
## [1] "2026-09-08"
anio_pasado
## [1] "2025-09-08 18:00:00 UTC"

25.16 períodos

una_pm <- ymd_hms("2016-03-12 13:00:00", tz = "America/New_York")

una_pm
## [1] "2016-03-12 13:00:00 EST"
#> [1] "2016-03-12 13:00:00 EST"
una_pm + ddays(1)
## [1] "2016-03-13 14:00:00 EDT"
#> [1] "2016-03-13 14:00:00 EDT"
seconds(15)
## [1] "15S"
#> [1] "15S"
minutes(10)
## [1] "10M 0S"
#> [1] "10M 0S"
hours(c(12, 24))
## [1] "12H 0M 0S" "24H 0M 0S"
#> [1] "12H 0M 0S" "24H 0M 0S"
days(7)
## [1] "7d 0H 0M 0S"
#> [1] "7d 0H 0M 0S"
months(1:6)
## [1] "1m 0d 0H 0M 0S" "2m 0d 0H 0M 0S" "3m 0d 0H 0M 0S" "4m 0d 0H 0M 0S"
## [5] "5m 0d 0H 0M 0S" "6m 0d 0H 0M 0S"
#> [1] "1m 0d 0H 0M 0S" "2m 0d 0H 0M 0S" "3m 0d 0H 0M 0S" "4m 0d 0H 0M 0S"
#> [5] "5m 0d 0H 0M 0S" "6m 0d 0H 0M 0S"
weeks(3)
## [1] "21d 0H 0M 0S"
#> [1] "21d 0H 0M 0S"
years(1)
## [1] "1y 0m 0d 0H 0M 0S"
#> [1] "1y 0m 0d 0H 0M 0S"
10 * (months(6) + days(1))
## [1] "60m 10d 0H 0M 0S"
#> [1] "60m 10d 0H 0M 0S"
days(50) + hours(25) + minutes(2)
## [1] "50d 25H 2M 0S"
#> [1] "50d 25H 2M 0S"
# Un año bisiesto
ymd("2016-01-01") + dyears(1)
## [1] "2016-12-31 06:00:00 UTC"
#> [1] "2016-12-31 06:00:00 UTC"
ymd("2016-01-01") + years(1)
## [1] "2017-01-01"
#> [1] "2017-01-01"

# Horarios de verano
una_pm + ddays(1)
## [1] "2016-03-13 14:00:00 EDT"
#> [1] "2016-03-13 14:00:00 EDT"
una_pm + days(1)
## [1] "2016-03-13 13:00:00 EDT"
#> [1] "2016-03-13 13:00:00 EDT"
10 * (months(6) + days(1))
## [1] "60m 10d 0H 0M 0S"
#> [1] "60m 10d 0H 0M 0S"
days(50) + hours(25) + minutes(2)
## [1] "50d 25H 2M 0S"
#> [1] "50d 25H 2M 0S"
  • intervalos

25.16.1 Multiplicaciones y sumas de fechas

25.17 Períodos

  • seconds()
  • minutes()
  • hours()
  • days()
  • months()
  • weeks()
  • years()

25.17.1 Multiplicaciones y sumas de periodos

25.18 Intervalos

  • %–% start end

Un intervalo es lo único que sabe entre qué dos fechas está, y por eso es lo único que puede convertirse a cualquier unidad sin ambigüedad. La receta es siempre la misma: se construye el intervalo con %--% y se divide entre la unidad que se quiera.

25.19 Resumen

Para escoger entre los tres, la pregunta es qué quieres que pase con las excepciones del calendario:

Quieres Usa Ejemplo
Un número exacto de segundos, sin excepciones duración tiempo de reacción, decaimiento
Lo que diría una persona con un calendario período “la cita es dentro de un mes”
Cuánto tiempo hay entre dos fechas concretas intervalo edad, duración de un experimento
  1. Ejercicios:

Hacer los ejercicios en la sección 16.4.5 del libro en español


25.20 Zonas horarias

  • Sys.timezone()
  • head(OlsonNames())
  • tz= “”

Una fecha-hora sin zona horaria está incompleta: las 3 de la tarde en San Juan y las 3 de la tarde en Madrid son dos instantes distintos. R guarda la zona como un atributo de la fecha-hora, y por defecto usa la de tu computadora.

Zonas horarias (base R y lubridate).

  • Sys.timezone(): en cuál zona está tu computadora ahora mismo.
  • OlsonNames(): la lista completa de nombres válidos, unos 600. Los nombres van en la forma "Continente/Ciudad", como "America/Puerto_Rico", y no con siglas como "AST", que son ambiguas.
  • with_tz(x, "zona"): el mismo instante, mostrado en otra zona. Cambia lo que se lee, no el momento.
  • force_tz(x, "zona"): la misma hora del reloj, declarada en otra zona. Cambia el momento.

with_tz() y force_tz() se confunden y hacen cosas opuestas. with_tz() es “esta reunión, ¿a qué hora es en Madrid?”. force_tz() es “esta hora que anoté sin decir dónde, en realidad era hora de Madrid”. Si te equivocas de función, los datos se mueven varias horas y nada avisa.

  1. Ejercicios:

Seleccionar 3 archivos de los vuelos que salen o llegan a PR, (el código del aeropuerto es “SJU”) de la base de datos de https://www.transtats.bts.gov/DL_SelectFields.asp?Table_ID=236 Pueden ser el mismo mes en 3 diferentes años o 3 diferentes meses en el mismo año.

  • Repite la mayoría de los análisis enseñado arriba (como práctica).

  • Evaluar el tiempo de retraso de los vuelos que salen de SJU en cada periodo seleccionado, y haz una gráfica para visualizar el patrón

  • Cuál es el día preferible para no tener retraso

  • Cuál es la mejor hora de salida para no tener retraso

  • Compara por lo menos 3 diferentes líneas saliendo de SJU y el periodo de retraso.



25.21 Ejercicios del capítulo

Sobre los ejercicios de este capítulo. Cada ejercicio se entrega con tres cosas: el script, el resultado (que el bloque corra) y una explicación en palabras. Los ejercicios usan Datos/crimen_pr.csv, que está en el repositorio del curso y trae fecha y hora de verdad, no vuelos ni la fecha de nacimiento de Darwin, que son los del capítulo. La hoja completa para entregar está en Ejercicios/Ejercicios_Capitulo_25_fechas.Rmd.

25.21.1 Ejercicio 1. La misma fecha, escrita de cinco maneras

Convierte a fecha estos cinco textos, que son todos el mismo día, cada uno con la función de lubridate que le corresponda: "2016-08-01", "01/08/2016", "August 1, 2016", "20160801" y "01.08.2016".

Verificación: los cinco tienen que dar exactamente la misma fecha. Compruébalo con n_distinct(), que debe dar 1.

En palabras: prueba ahora mdy("01/08/2016") y mira qué sale. ¿Dio error? Explica por qué este es el error más peligroso de todo el capítulo y qué comprobación harías en una columna de 5000 fechas para detectarlo.

# Escribe tu código aquí

25.21.2 Ejercicio 2. Extraer las partes

Lee Datos/crimen_pr.csv. Saca el día de la semana y la hora de cada incidente, y cuenta cuántos hay de cada uno. Usa label = TRUE para que los días salgan con su nombre.

Verificación: tienen que salir 7 días de la semana y 24 horas del día, todas con algún caso.

En palabras: ¿cuál día y cuál hora concentran más incidentes? Haz una gráfica de barras de cada uno. ¿Qué habría pasado si hubieras usado wday() sin label = TRUE para graficar?

# Escribe tu código aquí

25.21.3 Ejercicio 3. %/% y %%

Muchos archivos guardan la hora como un número de la forma HHMM. Toma este vector: horas <- c(1730, 905, 2359, 15, 1200).

Sepáralo en hora y minutos con %/% y %%, y calcula los minutos transcurridos desde la medianoche de cada uno. Después convierte los mismos valores a hora de verdad con hm() de lubridate y comprueba que coinciden.

Verificación: los minutos desde la medianoche tienen que ser 1050, 545, 1439, 15, 720. La suma de los cinco es 3769.

En palabras: el valor 15 significa las 00:15, no las 15:00. Explica por qué %/% y %% lo resuelven bien sin ningún caso especial. ¿Qué habría pasado si hubieras intentado partir el número con str_sub(), cortando por posición?

# Escribe tu código aquí

25.21.4 Ejercicio 4. Duración contra período

Toma el 1 de marzo de 2024 (un año bisiesto) y réstale un año de las dos maneras: con years(1) y con dyears(1). Haz lo mismo sumando un mes al 31 de enero, con months(1).

Verificación: las dos restas dan días distintos, y la suma al 31 de enero da NA.

En palabras: ninguno de los tres resultados es un error del programa. Explica qué contesta cada uno, y por qué el NA es la respuesta más honesta que puede dar R a “¿qué día es un mes después del 31 de enero?”.

# Escribe tu código aquí

25.21.5 Ejercicio 5. Intervalos

Con crimen_pr.csv, construye el intervalo que va de la primera a la última fecha del archivo con %--%, y exprésalo en días, en semanas y en meses.

Verificación: el intervalo tiene que ser de unos dos meses, y en días tiene que dar un número entre 60 y 65.

En palabras: divide también entre dmonths(1) en vez de months(1) y compara. ¿Por qué no dan lo mismo? ¿Cuál de los dos números pondrías en la sección de métodos de un informe?

# Escribe tu código aquí

25.21.6 Ejercicio 6. Redondear para agrupar

Con el mismo archivo, cuenta los incidentes por semana usando floor_date(Fecha, "week"), y haz una gráfica de líneas de la serie.

Verificación: deben salir unas nueve o diez semanas, y la primera y la última tienen que tener bastantes menos casos que las del medio.

En palabras: ¿por qué los extremos son más bajos? Ya viste este mismo problema en el capítulo de importar datos. ¿Qué harías con esas dos semanas antes de enseñarle la gráfica a alguien?

# Escribe tu código aquí

25.21.7 Ejercicio 7 (BONO). Zonas horarias

Averigua en cuál zona horaria está tu computadora con Sys.timezone(). Después toma el instante "2016-08-01 15:30:00" en "America/Puerto_Rico" y míralo en "Europe/Madrid" y en "America/Los_Angeles" con with_tz(). Prueba también force_tz() con el mismo instante.

Verificación: con with_tz() el instante es el mismo y solo cambia la hora que se lee; con force_tz() cambia el instante.

En palabras: explica la diferencia con tus palabras. Puerto Rico no cambia de horario de verano y el este de Estados Unidos sí: ¿qué le pasa a la diferencia de horas entre San Juan y Nueva York a lo largo del año, y qué problema causaría eso al comparar datos de vuelos de los dos sitios?

# Escribe tu código aquí