Fechas con lubridate: parsing, locale, zonas horarias
Por qué las fechas en R son un mundo aparte
Las fechas son la fuente número uno de sorpresas silenciosas en cualquier análisis real. Tres razones:
- Formatos inconsistentes. El mismo dataset puede tener
"2024-03-01","01/03/2024","1-Mar-24"y"March 1, 2024". Cada uno requiere un parser distinto. - Zonas horarias. Una venta a las 23:00 en Madrid es a las 17:00 en Nueva York. ¿La fecha de venta es la de ayer o la de hoy? Depende.
- Aritmética no trivial. “Un mes después del 31 de enero”, ¿28 de febrero? ¿2 de marzo? Depende de qué entiendas por “un mes”.
lubridate resuelve los tres con una API mucho más clara que as.Date() + strptime() de R base.
Parsing: la familia ymd/dmy/mdy
El truco mental: el nombre de la función te dice el orden en que aparecen los componentes en el string.
library(lubridate)
ymd("2024-03-15") # Y-M-D (ISO 8601, el formato sano)
dmy("15/03/2024") # D-M-Y (Europa)
mdy("03/15/2024") # M-D-Y (EE. UU.)
# Con hora
ymd_hms("2024-03-15 14:30:00")
dmy_hms("15/03/2024 14:30:00")Estas funciones son tolerantes con el separador: aceptan -, /, ., espacios, lo que sea. Lo único que les importa es el orden de los componentes.
dmy("15/03/2024") # OK
dmy("15-03-2024") # OK
dmy("15.3.2024") # OK
dmy("15 marzo 2024") # OK con locale apropiadoCuando el formato es ambiguo o exótico, hay parsers explícitos:
parse_date_time(
c("3/15/24", "March 15, 2024"),
orders = c("mdy", "B d Y")
)orders acepta una lista de formatos. lubridate prueba cada uno y se queda con el que funciona para cada string.
Extraer componentes
Una vez parseadas, las fechas se interrogan con funciones legibles:
fecha <- ymd("2024-03-15")
year(fecha) # 2024
month(fecha) # 3
month(fecha, label = TRUE) # "Mar" (factor ordenado)
day(fecha) # 15
wday(fecha) # día de la semana, 1 = Domingo
wday(fecha, label = TRUE, week_start = 1) # "vie" (semana lunes-primero)
yday(fecha) # día del año (1..366)
quarter(fecha) # 1
isoweek(fecha) # semana ISO (1..53)Para usar en mutate():
ventas |>
mutate(
año = year(fecha),
mes = month(fecha, label = TRUE),
dia_sem = wday(fecha, label = TRUE, week_start = 1)
)Casi siempre vas a querer extraer componentes para agrupar (group_by(mes)) o para visualizar (ggplot(aes(x = mes))).
Aritmética temporal: tres conceptos distintos
Esta es la sección que importa. Hay tres formas de medir “un mes”, y entender la diferencia te evita bugs sutiles.
1. duration: tiempo absoluto en segundos
dweeks(2) # exactamente 2 × 7 × 24 × 60 × 60 segundos
ddays(1) # exactamente 86 400 segundos
dyears(1) # exactamente 365.25 × 86 400 segundosÚtiles para mediciones precisas. No tienen en cuenta cambios de hora ni meses irregulares.
2. period: tiempo humano (calendar-aware)
weeks(2) # "dos semanas" — siempre cae el mismo día de la semana
months(1) # "un mes" — del 15 de marzo al 15 de abril
years(1) # "un año" — mismo día y mes del año siguienteÚtiles para la mayoría de operaciones humanas. Toman en cuenta meses irregulares y horarios de verano/invierno.
ymd("2024-01-31") + months(1) # NA — el 31 de febrero no existe
ymd("2024-01-31") + dmonths(1) # 2024-03-02 — 30.4 días despuésDiferencia crítica: months(1) rechaza fechas inválidas devolviendo NA. dmonths(1) da un resultado calendario válido pero raro (un mes promediado). La primera opción suele ser la correcta.
3. interval: tramo entre dos momentos concretos
inicio <- ymd("2024-01-15")
fin <- ymd("2024-04-22")
intervalo <- interval(inicio, fin)
intervalo / months(1) # 3.23 meses
intervalo / days(1) # 98 díasÚtiles para calcular duración exacta entre dos eventos concretos: edad de un cliente, antigüedad de una cuenta, lag entre pedido y entrega.
Regla práctica: duration para “tiempo de máquina”, period para “tiempo humano”, interval para “el tramo entre estas dos fechas concretas”.
Zonas horarias: donde casi todo el mundo se cae
Por defecto, una fecha parseada con ymd_hms() se almacena en UTC. Pero los datos del mundo real vienen en otras zonas. Si las mezclas sin pensar, sumas o restas horas sin querer.
# Sin zona — se guarda como UTC silenciosamente
ymd_hms("2024-03-15 14:30:00")
# Con zona explícita
ymd_hms("2024-03-15 14:30:00", tz = "Europe/Madrid")
# Cambiar zona conservando el instante absoluto
fecha_madrid <- ymd_hms("2024-03-15 14:30:00", tz = "Europe/Madrid")
with_tz(fecha_madrid, "America/New_York") # 9:30 AM EDT, mismo instante
# Cambiar zona reinterpretando la "hora local"
force_tz(fecha_madrid, "America/New_York") # 14:30 EDT — ¡otro instante!La distinción with_tz() vs force_tz() es la trampa más sutil:
with_tzcambia la representación sin cambiar el instante. “Esta venta ocurrió a las 14:30 de Madrid, dime cómo se ve eso en NY”.force_tzcambia el instante manteniendo la hora del reloj. “Olvida la zona que le puse al parsear. Trátalo como si fuera hora local de NY desde el principio”.
force_tz es lo que necesitas cuando un dataset vino con horas mal etiquetadas (etiquetadas como UTC cuando en realidad eran hora local del país).
Trampas habituales
- Parsear con la función equivocada de la familia.
dmy("03/15/2024")devuelveNAcon warning porque “15” no es mes. Si tienes datos mezclados, usaparse_date_time()con variosorders. - Sumar y restar
days(...)vsddays(...). En zonas con horario de verano, restarddays(1)puede saltarte por encima del cambio de hora.days(1)(period) respeta el calendario. - Comparar fechas con cadenas.
ymd("2024-03-15") < "2024-04-01"puede darTRUEpor casualidad. Compara siempre fecha contra fecha. Parsea ambos lados antes. - Excel y los números serie de fecha. Excel guarda fechas como número de días desde 1899-12-30 (Windows) o desde 1904-01-01 (Mac antiguo). Si recibes un CSV con números de cinco dígitos donde esperas fechas, eso es. Convierte con
as.Date(numero, origin = "1899-12-30").
En la siguiente entrega
Has cerrado el bloque 3. Ahora tienes todo lo necesario para un análisis completo: cargar, filtrar, transformar, agregar, combinar tablas y reorganizar formatos. El último tutorial de la ruta es exactamente eso: un caso real completo que junta los 11 tutoriales anteriores en un análisis publicado en Quarto. Es el cierre.