Trabajar con fechas en R

Introducción a R Conceptos básicos de R
Convertir y formatear fechas en R con as.Date

R guarda las fechas en una clase especial llamada Date, que permite ordenarlas, restarlas o crear secuencias de fechas. En este tutorial aprenderás a convertir cadenas de texto y números en fechas con as.Date, qué códigos de formato usar, por qué obtienes NA al convertir, cómo extraer el año o el mes con format, cómo calcular la diferencia entre dos fechas con difftime y cómo hacer lo mismo con el paquete lubridate.

La clase Date

Cuando lees un archivo, las fechas suelen llegar como cadenas de texto ("2024-03-15"). Parecen fechas, pero para R son solo texto: no puedes restarlas y se ordenarían alfabéticamente. La función as.Date las convierte a la clase Date:

x <- as.Date("2024-03-15")

x
class(x)
[1] "2024-03-15"
[1] "Date"

Internamente, una fecha de clase Date es el número de días transcurridos desde el 1 de enero de 1970. Por eso puedes sumar o restar días a una fecha y por eso al convertirla a número obtienes un entero:

as.numeric(x)
[1] 19797

Si lo que necesitas es la fecha o la hora actual, utiliza las funciones Sys.Date y Sys.time, que se explican en el tutorial sobre la fecha y hora del sistema en R. Ambas devuelven objetos de las clases que verás en este tutorial.

Convertir texto a fecha con as.Date

Por defecto, as.Date solo entiende el formato ISO "año-mes-día" ("2024-03-15") y "año/mes/día" ("2024/03/15"). Para cualquier otro formato tendrás que describir cómo están escritas tus fechas con el argumento format, usando los códigos de la siguiente sección:

as.Date("15/03/2024", format = "%d/%m/%Y")
as.Date("03-15-2024", format = "%m-%d-%Y")
as.Date("15.03.24", format = "%d.%m.%y")
as.Date("15 de marzo de 2024", format = "%d de %B de %Y")
[1] "2024-03-15"
[1] "2024-03-15"
[1] "2024-03-15"
[1] "2024-03-15"

La cadena de format tiene que reproducir el texto completo, incluidos los separadores (/, -, ., espacios o palabras como “de”). La salida siempre se muestra en formato ISO, sea cual sea el formato de entrada.

Códigos de formato de fechas

La siguiente tabla muestra los códigos de formato más utilizados y el valor que representan para la fecha 2024-03-15. Estos códigos sirven tanto para leer fechas con as.Date como para escribirlas con format. Los nombres de meses y días se han obtenido en Windows con la configuración regional en español.

Código Significado Ejemplo
%d Día del mes (01-31) 15
%m Mes en número (01-12) 03
%y Año sin siglo (00-99) 24
%Y Año con siglo 2024
%b Nombre abreviado del mes mar.
%B Nombre completo del mes marzo
%a Nombre abreviado del día de la semana vi.
%A Nombre completo del día de la semana viernes
%j Día del año (001-366) 075
%u Día de la semana en número, lunes = 1 (1-7) 5
%w Día de la semana en número, domingo = 0 (0-6) 5
%U Semana del año empezando en domingo (00-53) 10
%V Semana del año ISO 8601 (01-53) 11

Ten cuidado con %y: los años 00 a 68 se interpretan como 2000-2068 y los años 69 a 99 como 1969-1999. Una fecha de nacimiento como "01/05/68" se convierte en "2068-05-01", así que usa años de cuatro cifras siempre que puedas.

¿Por qué as.Date devuelve NA?

Es el problema más frecuente al trabajar con fechas. Si el texto no coincide con el format, as.Date devuelve NA sin ningún aviso. En el siguiente ejemplo la fecha está en formato ISO, pero el formato indica día-mes-año:

as.Date("2024-03-15", format = "%d-%m-%Y")
[1] NA

También obtendrás NA con fechas que no existen, como el 30 de febrero, y cuando el nombre del mes no coincide con el idioma de tu sistema (consulta la sección sobre la configuración regional).

Sin format, el comportamiento es diferente y aún más peligroso. Si el primer valor no se parece en nada a una fecha, obtienes un error:

as.Date("hola")
Error in charToDate(x) :
  character string is not in a standard unambiguous format

Pero si empieza por números, R puede devolver una fecha incorrecta sin avisar. Aquí "15/03/2024" se lee como el año 15, mes 3, día 20:

as.Date("15/03/2024")
[1] "0015-03-20"

Este caso es especialmente habitual en España, donde las fechas se escriben como día/mes/año.

Indica siempre el argumento format salvo que tus fechas estén en formato ISO y, después de convertir una columna, cuenta los valores faltantes con sum(is.na(x)). Si hay más NA que antes de la conversión, algunos textos no coincidían con el formato.

Columnas con varios formatos de fecha

A veces una misma columna contiene fechas escritas de formas diferentes. El argumento tryFormats no lo soluciona: elige el primer formato que funciona para el primer valor no faltante y lo aplica a todo el vector:

fechas <- c("2024-03-15", "15/03/2024", "2024-04-01")

as.Date(fechas, tryFormats = c("%Y-%m-%d", "%d/%m/%Y"))
[1] "2024-03-15" NA           "2024-04-01"

En su lugar, convierte el vector con cada formato y rellena los valores faltantes del primer intento con el segundo:

d1 <- as.Date(fechas, format = "%Y-%m-%d")
d2 <- as.Date(fechas, format = "%d/%m/%Y")

d1[is.na(d1)] <- d2[is.na(d1)]
d1
[1] "2024-03-15" "2024-03-15" "2024-04-01"

Puede que pienses en usar la función ifelse para esto, pero ifelse pierde la clase Date y devuelve el número de días:

ifelse(is.na(d1), d2, d1)
[1] 19797 19797 19814

Si usas dplyr, if_else y coalesce(d1, d2) sí conservan la clase.

Convertir números a fechas

Las fechas también pueden llegar como números, sobre todo desde Excel. Excel guarda las fechas como el número de días desde el 30 de diciembre de 1899, así que cuando una columna de fechas se importa como números (por ejemplo 45366) tendrás que indicar ese origen:

as.Date(c(45366, 45383), origin = "1899-12-30")
[1] "2024-03-15" "2024-04-01"

El origen es el 30 de diciembre y no el 1 de enero porque Excel considera 1900 un año bisiesto, cosa que no fue. Los archivos creados con el antiguo sistema de fechas 1904 de Excel para Mac necesitan origin = "1904-01-01". Los paquetes para leer archivos de Excel suelen convertir estas columnas automáticamente, así que lo necesitarás sobre todo cuando la columna contiene algún texto y se importa como carácter o como número.

Si el número es una cuenta de días desde 1970 (el origen de R), no necesitas el argumento origin en R 4.3.0 o posterior. En versiones anteriores tendrás que indicar origin = "1970-01-01":

as.Date(19797)
[1] "2024-03-15"

Un error habitual es pasar directamente números como 20240315. R los interpreta como días desde 1970 y el resultado es absurdo. Conviértelos antes a texto e indica el formato:

as.Date(20240315)
as.Date(as.character(20240315), format = "%Y%m%d")
[1] "57386-02-06"
[1] "2024-03-15"

Formatear fechas y extraer el año, el mes o el día

La función format hace lo contrario que as.Date: convierte una fecha en una cadena de texto usando los códigos de la tabla anterior. Puedes usarla para mostrar la fecha en cualquier formato:

x <- as.Date("2024-03-15")

format(x, "%d/%m/%Y")
format(x, "%d de %B de %Y")
[1] "15/03/2024"
[1] "15 de marzo de 2024"

format es también la forma de extraer el año, el mes o el día de una fecha en R base. Ten en cuenta que el resultado siempre es una cadena de texto, así que utiliza as.numeric si necesitas hacer cálculos con él:

format(x, "%Y")
as.numeric(format(x, "%Y"))
as.numeric(format(x, "%m"))
format(x, "%A")
[1] "2024"
[1] 2024
[1] 3
[1] "viernes"

Las funciones weekdays y months devuelven el nombre del día de la semana y del mes, y equivalen a format(x, "%A") y format(x, "%B").

Los nombres de meses y días dependen del idioma

Los nombres que devuelven %B, %b, %A, %a, weekdays y months dependen del idioma de tu sistema (la configuración regional o locale). Las salidas de este tutorial se han obtenido con la configuración en español: en un ordenador configurado en inglés, format(x, "%B") devuelve "March" en lugar de "marzo".

Esto también afecta a la lectura. as.Date("15-Mar-2024", format = "%d-%b-%Y") devuelve NA si el sistema no está en inglés. Y en Windows con la configuración en español tampoco funciona "15-mar-2024", porque la abreviatura es "mar." con punto. Puedes consultar la configuración actual con Sys.getlocale("LC_TIME") y cambiarla para la sesión actual:

Sys.setlocale("LC_TIME", "C")  # Nombres de meses y días en inglés

Si compartes tu código, evita depender de los nombres de los meses: los formatos numéricos como %Y-%m-%d funcionan igual con cualquier configuración regional.

Diferencia entre fechas

Como las fechas son números de días, puedes restarlas directamente. El resultado es un objeto de clase difftime:

inicio <- as.Date("2024-01-15")
fin <- as.Date("2024-03-15")

fin - inicio
Time difference of 60 days

Para elegir las unidades, utiliza la función difftime. Las unidades disponibles son "secs", "mins", "hours", "days" y "weeks":

difftime(fin, inicio, units = "weeks")
Time difference of 8.571429 weeks

Un objeto difftime muestra las unidades junto al valor, algo cómodo de leer pero que puede dar problemas en cálculos o al guardarlo en un data frame. Usa as.numeric para quedarte solo con el número:

as.numeric(fin - inicio)
as.numeric(difftime(fin, inicio, units = "weeks"))
[1] 60
[1] 8.571429

No existen las unidades "months" ni "years", porque los meses y los años no tienen una duración fija:

difftime(fin, inicio, units = "months")
Error in match.arg(units) :
  'arg' should be one of "auto", "secs", "mins", "hours", "days", "weeks"

Puedes obtener un número aproximado de años dividiendo los días entre 365.25. Por ejemplo, para calcular la edad de una persona:

nacimiento <- as.Date("1990-07-20")
hoy <- as.Date("2024-03-15")

floor(as.numeric(difftime(hoy, nacimiento, units = "days")) / 365.25)
[1] 33

Para edades exactas en años o meses, la opción de lubridate que verás al final del tutorial es más fiable.

También puedes sumar o restar días a una fecha, comparar fechas y calcular las diferencias entre fechas consecutivas con diff:

inicio + 30
inicio > fin
diff(as.Date(c("2024-01-01", "2024-01-15", "2024-03-01")))
[1] "2024-02-14"
[1] FALSE
Time differences in days
[1] 14 46

Secuencias de fechas

La función seq funciona con fechas. El argumento by admite "day", "week", "month", "quarter" y "year", opcionalmente precedidos de un número ("2 weeks"). Consulta el tutorial de seq y rep para conocer el resto de argumentos.

seq(as.Date("2024-01-01"), as.Date("2024-01-10"), by = "day")
seq(as.Date("2024-01-01"), by = "week", length.out = 5)
seq(as.Date("2024-01-01"), by = "month", length.out = 6)
 [1] "2024-01-01" "2024-01-02" "2024-01-03" "2024-01-04" "2024-01-05"
 [6] "2024-01-06" "2024-01-07" "2024-01-08" "2024-01-09" "2024-01-10"
[1] "2024-01-01" "2024-01-08" "2024-01-15" "2024-01-22" "2024-01-29"
[1] "2024-01-01" "2024-02-01" "2024-03-01" "2024-04-01" "2024-05-01"
[6] "2024-06-01"

Las secuencias mensuales que empiezan a final de mes dan resultados inesperados. El 31 de febrero no existe, así que R salta al 2 de marzo:

seq(as.Date("2024-01-31"), by = "month", length.out = 4)
[1] "2024-01-31" "2024-03-02" "2024-03-31" "2024-05-01"

Si necesitas el último día de cada mes, crea la secuencia con los primeros días del mes siguiente y resta un día: seq(as.Date("2024-02-01"), by = "month", length.out = 4) - 1.

Agrupar fechas por mes con cut

La función cut también tiene un método para fechas. Con breaks = "month" (o "week", "quarter", "year") asigna cada fecha a su periodo, algo muy útil para agregar datos por mes:

ventas <- data.frame(
  fecha = as.Date(c("2024-01-05", "2024-01-20", "2024-02-03", "2024-02-28", "2024-03-10")),
  importe = c(100, 150, 80, 120, 200)
)

ventas$mes <- cut(ventas$fecha, breaks = "month")
ventas
       fecha importe        mes
1 2024-01-05     100 2024-01-01
2 2024-01-20     150 2024-01-01
3 2024-02-03      80 2024-02-01
4 2024-02-28     120 2024-02-01
5 2024-03-10     200 2024-03-01

La nueva columna es un factor cuyos niveles son el primer día de cada mes. Ahora puedes sumar los importes por mes con aggregate:

aggregate(importe ~ mes, data = ventas, FUN = sum)
         mes importe
1 2024-01-01     250
2 2024-02-01     200
3 2024-03-01     200

Otra opción es agrupar por format(fecha, "%Y-%m"). La diferencia es que cut también crea los niveles de los meses sin datos dentro del rango, por lo que los meses vacíos aparecen con un recuento de cero en las tablas.

Fechas con hora: POSIXct

La clase Date solo guarda días. Si también necesitas la hora, utiliza la clase POSIXct, que guarda el número de segundos desde el 1970-01-01 00:00:00 UTC. Se crea con as.POSIXct, que admite los mismos códigos de formato más %H (horas), %M (minutos) y %S (segundos):

t1 <- as.POSIXct("2024-03-15 14:30:00", tz = "UTC")
t2 <- as.POSIXct("15/03/2024 18:45", format = "%d/%m/%Y %H:%M", tz = "UTC")

t1
t2 - t1
difftime(t2, t1, units = "mins")
[1] "2024-03-15 14:30:00 UTC"
Time difference of 4.25 hours
Time difference of 255 mins

Fíjate en que al restar fechas con hora R elige las unidades automáticamente (horas en este caso). Indícalas con difftime si necesitas una unidad fija.

La principal fuente de problemas con POSIXct son las zonas horarias. Si no indicas tz, R utiliza la zona horaria de tu ordenador, así que el mismo código puede dar resultados distintos en máquinas distintas. Además, convertir una fecha con hora a Date puede cambiar el día. En R 4.3.2, as.Date usa UTC por defecto, por lo que las 23:30 en Nueva York (las 03:30 del día siguiente en UTC) se convierten en el día siguiente:

t3 <- as.POSIXct("2024-03-15 23:30:00", tz = "America/New_York")

as.Date(t3)
as.Date(t3, tz = "America/New_York")
[1] "2024-03-16"
[1] "2024-03-15"

Si tus datos solo contienen días, usa la clase Date: es más sencilla y no tiene problemas de zonas horarias.

Fechas con lubridate

El paquete lubridate (parte del tidyverse) simplifica la mayoría de las tareas anteriores. En lugar de escribir códigos de formato, eliges una función cuyo nombre indica el orden de los componentes: ymd para año-mes-día, dmy para día-mes-año, mdy para mes-día-año, etc. Los separadores dan igual:

library(lubridate)

ymd("2024-03-15")
dmy(c("15/03/2024", "15-03-2024", "15.03.2024"))
dmy("15 de marzo de 2024")
[1] "2024-03-15"
[1] "2024-03-15" "2024-03-15" "2024-03-15"
[1] "2024-03-15"

A diferencia de as.Date, lubridate te avisa cuando un valor no se puede convertir:

ymd("2024-02-30")
[1] NA
Warning message:
 1 failed to parse.

Para extraer componentes, lubridate devuelve directamente números:

x <- ymd("2024-03-15")

year(x)
month(x)
day(x)
wday(x, week_start = 1)  # 1 = lunes
[1] 2024
[1] 3
[1] 15
[1] 5

floor_date redondea una fecha hacia abajo al inicio de su periodo (una alternativa a cut que devuelve un Date) y el operador %m+% suma meses sin pasarse al mes siguiente. Compáralo con sumar months(1) al 31 de enero:

floor_date(x, "month")
ymd("2024-01-31") + months(1)
ymd("2024-01-31") %m+% months(1:3)
[1] "2024-03-01"
[1] NA
[1] "2024-02-29" "2024-03-31" "2024-04-30"

La edad exacta en años cumplidos se puede calcular dividiendo un intervalo entre años:

interval(ymd("1990-07-20"), ymd("2024-03-15")) %/% years(1)
[1] 33

La siguiente tabla resume las equivalencias entre R base y lubridate:

Tarea R base lubridate
Convertir texto día/mes/año as.Date(x, format = "%d/%m/%Y") dmy(x)
Convertir texto año-mes-día as.Date(x) ymd(x)
Extraer el año as.numeric(format(x, "%Y")) year(x)
Extraer el mes as.numeric(format(x, "%m")) month(x)
Nombre del día de la semana weekdays(x) wday(x, label = TRUE)
Primer día del mes as.Date(cut(x, "month")) floor_date(x, "month")
Sumar meses seq(x, by = "month", length.out = 2)[2] x %m+% months(1)
Diferencia en días as.numeric(difftime(y, x, units = "days")) as.numeric(y - x)
Fecha y hora as.POSIXct(x, tz = "UTC") ymd_hms(x)

¿Cuál deberías usar? R base es suficiente para convertir fechas con un formato conocido, calcular diferencias en días y dar formato a la salida, y no necesita ningún paquete. Si trabajas a menudo con fechas, tienes fechas en varios formatos o necesitas sumar meses y calcular edades, lubridate te ahorra mucho código y evita los errores más comunes. Dentro de una cadena de dplyr puedes usar ambos en mutate, por ejemplo mutate(anio = year(fecha)).