16 Fechas y horas

16.1 Introducción

Este capítulo te mostrará cómo trabajar con fechas y horas en R. A primera vista, esto parece sencillo. Las usas en todo momento en tu vida regular y no parecen causar demasiada confusión. Sin embargo, cuanto más aprendes de fechas y horas, más complicadas se vuelven. Para prepararnos, intenta estas preguntas sencillas:

  • ¿Todos los años tienen 365 días?
  • ¿Todos los días tienen 24 horas?
  • ¿Cada minuto tiene 60 segundos?

Estamos seguros que sabes que no todos los años tienen 365 días, ¿pero acaso conoces la regla entera para determinar si un año es bisiesto? (Tiene tres partes, de hecho). Puedes recordar que muchas partes del mundo usan horarios de verano, así que algunos días tienen 23 horas y otros tienen 25. Puede ser que no supieras que algunos minutos tienen 61 segundos, porque de vez en cuando se agregan segundos adicionales ya que la rotación de la tierra se hace cada vez más lenta.

Las fechas y las horas son complicadas porque tienen que reconciliar dos fenómenos físicos (la rotación de la Tierra y su órbita alrededor del sol), con todo un conjunto de fenómenos geopolíticos que incluyen a los meses, los husos horarios y los horarios de verano. Este capítulo no te enseñará cada detalle sobre fechas y horas, pero te dará un sólido fundamento de habilidades prácticas que te ayudarán con los desafíos más comunes de análisis de datos.

16.1.1 Requisitos previos

Este capítulo se centra en el paquete lubridate, que simplifica el trabajo con fechas y horas en R. lubridate no es parte de los paquetes centrales de tidyverse porque solo se necesita al trabajar con fechas/horas. A su vez, necesitaremos los datos sobre vuelos contenidos en el paquete datos.

16.2 Creando fechas/horas

Hay tres tipos de datos de fechas/horas que se refieren a un instante en el tiempo:

  • Una fecha o date. Un tibble lo imprime como <date>.

  • Una hora o time dentro de un día. Los tibbles lo imprimen como <time>.

  • Una fecha-hora o date-time es una fecha con una hora adjunta: identifica de forma única como un instante en el tiempo (típicamente al segundo más cercano). Los tibbles imprimen esto como <dttm>. En otras partes de R, estos se llaman POSIXct, pero no creemos que sea un nombre muy útil.

En este capítulo solo nos concentraremos en fechas (dates) y fechas con horas (date-times) ya que R no tiene una clase nativa para almacenar horas. Si necesitas una, puedes usar el paquete hms.

Siempre deberías usar el tipo de datos más sencillo que se ajusta a tus necesidades. Esto significa que si puedes usar date en lugar de date-time, deberías hacerlo. Las fechas-horas son sustancialmente más complicadas porque necesitas gestionar los husos horarios, a los que volveremos al final del capítulo.

Para obtener la fecha o fecha-hora actual, puedes usar today() (hoy) o now() (ahora):

Hay tres modos en los que puedes crear una fecha/hora:

  • Desde una cadena de caracteres (o string, en inglés).
  • Desde componentes de fecha-hora individuales.
  • Desde un objeto fecha-hora existente.

Estos funcionan de la siguiente manera.

16.2.1 Desde cadenas de caracteres

Los datos de fecha/hora a menudo vienen como cadenas de caracteres. Ya has visto una forma de segmentarlas como date-times en el capítulo sobre importación de datos. Otra forma es usar las ayudas provistas por lubridate. Estas trabajan automáticamente el formato una vez que especificas el orden de los componentes. Para usarlas, identifica el orden en el que el año, mes y día aparecen en tus fechas, y luego ordena “y” (del inglés year), “m” (mes) y “d” (día) en el mismo orden. Esto te dará el nombre de la función lubridate que segmetará tu fecha. Por ejemplo:

Estas funciones también reciben números sin comillas. Esta es la forma más concisa de crear un solo objeto fecha-hora, ya que puedes necesitarla cuando filtres datos temporales. ymd() (del inglés año-mes-día) es corto y no ambigüo:

ymd() y sus funciones amigas crean fechas (date). Para generar una fecha-hora, agrega un guión bajo y uno o más de “h”, “m” y “s” al nombre de la función de segmentación:

También puedes forzar la creación de una fecha hora desde una fecha, al proveer un huso horario:

16.2.2 Desde componentes individuales

En lugar de una cadena de caracteres simple, a veces tienes los componentes individuales de una fecha-hora repartidos en múltiples columnas. Esto es lo que tenemos en los datos de vuelos:

Para crear una fecha-hora desde este tipo de input, usa make_date() (crear fecha) para las fechas, o make_datetime() (crear fecha-hora) para las fechas-horas:

Hagamos esto mismo para cada una de las cuatro columnas de tiempo en vuelos. Las horas están representadas en un formato ligeramente más extraño, así que usamos el módulo aritmético para extraer los componentes de horas y minutos. Una vez que hayamos creado las variables fecha-hora, nos centraremos en las variables que usaremos por el resto del capítulo.

Con estos datos, podemos visualizar la distribución de las horas de salida a lo largo del año:

O para un solo día:

Mira con detenimiento, ya que cuando usas fechas-hora en un contexto numérico (como en un histograma), 1 significa un segundo, entonces un binwidth (ancho del contenedor) de 86400 significa un día. Para las fechas, 1 significa un día.

16.2.3 Desde otros tipos

Puedes querer cambiar entre una fecha-hora y una fecha. Ese es el trabajo de as_datetime() (como fecha-hora) y as_date() (como fecha):

A veces tendrás fechas/horas como desfasajes numéricos de la “Época Unix” similares a 1970-01-01. Si el desfasaje es un segundos, usa as_datetime(); si es en días, usa as_date().

16.2.4 Ejercicios

  1. ¿Qué sucede si analizas una cadena de caracteres que contiene fechas inválidas?

  2. ¿Qué hace el argumento tzone (time zone = huso horario) para today()? ¿Por qué es importante?

  3. Utiliza la función de lubridate apropiada para analizar las siguientes fechas:

16.3 Componentes de fecha-hora

Ahora que ya conoces cómo tener datos de fechas y horas en las estructuras de datos de R, vamos a explorar qué puedes hacer con ellos. Esta sección se concentrará en las funciones de acceso (accessor functions) que te permiten obtener y configurar componentes individuales. La siguiente sección mirará el trabajo aritmético con fechas-horas.

16.3.1 Obteniendo los componentes

Puedes obtener las partes individuales de una fecha con las funciones de acceso year() (año), month() (mes), mday() (día del mes), yday() (día del año), wday() (día de la semana), hour() (hora), minute() (minuto), y second() (segundo).

Para month() y wday() puedes configurar label = TRUE para retornar el nombre abreviado del mes o del día de la semana. Usa abbr = FALSE para retornar el nombre completo.

Podemos usar wday() para ver que más vuelos salen durante la semana que durante el fin de semana:

Hay un patrón interesante si miramos la demora promedio por minuto dentro de la hora. ¡Parece que los vuelos que salen en los minutos 20-30 y 50-60 tienen mucho menos demora que en el resto de la hora!

Es interesante que si miramos el horario programado de salida, no vemos un patrón tan prominente:

Entonces, ¿por qué vemos ese patrón con los horarios reales de salida? Bueno, como muchos datos recolectados por los humanos, hay un sesgo importante hacia los vuelos que salen en horas “agradables”. ¡Mantente siempre alerta respecto a este tipo de patrón cada vez que trabajes con datos que involucran al juicio humano!

16.3.2 Redondeo

Un método alternativo para graficar los componentes individuales es redondear la fecha a una unidad de tiempo cercana, con floor_date() (fecha hacia abajo), round_date() (redondear fecha), y ceiling_date() (fecha hacia arriba). Cada función toma un vector de fechas a ajustar y luego el nombre de la unidad redondeada hacia abajo (con floor), hacia arriba (con ceiling) o al maar el número de vuelos por semana:

Calcular la diferencia entre una fecha redondeada y una sin redondear puede ser particularmente útil.

16.3.3 Configurando componentes

También puedes usar las funciones de acceso para darle un valor a los componentes de las fechas/horas:

Alternativamente, en lugar de modificar en un solo lugar, puedes crear una nueva fecha-hora con update() (actualizar). Esto también te permite configurar múltiples valores al mismo tiempo.

Si los valores son demasiado grandes, darán la vuelta:

Puedes utilizar update() para mostrar la distribución de los vuelos a lo largo del día para cada día del año:

Fijar los componentes más grandes de una fecha con una constante es una técnica que te permite explorar patrones en los componentes más pequeños.

16.3.4 Ejercicios

  1. ¿Cómo cambia la distribución de las horas de los vuelos dentro de un día a lo largo del año?

  2. Compara horario_salida, salida_programada and atraso_salida. ¿Son consistentes? Explica tus hallazgos.

  3. Compara tiempo_vuelo con la duración entre la salida y la llegada. Explica tus hallazgos. (Pista: considera la ubicación del aeropuerto).

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

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

  6. ¿Qué hace que la distribución de diamantes$carat y vuelos$salida_programada sea similar?

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

16.4 Lapsos de tiempo

Ahora, aprenderás cómo trabaja la aritmética con fechas, incluyendo la sustracción, adición y división. En el camino, aprenderás sobre tres importantes clases que representan períodos de tiempo:

  • durations (del inglés, duraciones), que representa un número exacto de segundos.
  • periods (períodos), que representan unidades humanas como semanas o meses.
  • intervals (intervalos), que representan un punto de inicio y uno de finalización.

16.4.1 Duraciones

En R, cuando restas dos fechas obtienes un objeto de diferencia temporal (en inglés, difftimes):

Un objeto de clase difftime registra un lapso de tiempo de segundos, minutos, horas, días o semanas. Esta ambiguedad hace que los difftimes sean un poco complicados de trabajar, por lo que lubridate provee una alternativa que siempre usa segundos: la duración.

Las duraciones traen un conveniente grupo de constructores:

Las duraciones siempre registran el lapso de tiempo en segundos. Las unidades más grandes se crean al convertir minutos, horas, días, semanas y años a segundos, mediante una conversión estándar (60 segundos en un minuto, 60 minutos en una hora, 24 horas en un día, 7 días en una semana, 365 días en un año).

Puedes agregar y multiplicar duraciones:

Puedes sumar y restar duraciones a días:

Sin embargo, como las duraciones representan un número exacto de segundos, a veces puedes obtener un resultado inesperado:

¡¿Por qué un día después de la 1 pm del 12 de marzo son las 2 pm del 13 de marzo!? Si miras con cuidado a la fecha, te darás cuenta de que los husos horarios han cambiado. Debido al horario de verano (EDT es el horario de verano de la Costa Este), el 12 de marzo solo tiene 23 horas, por lo que si agregamos un día entero de segundos terminamos con una hora diferente.

16.4.2 Períodos

Para resolver este problema, lubridate provee periodos. Estos son plazos de tiempo que no tienen un largo fijo en segundos, sino que funcionan con tiempos “humanos”, como días o meses. Esto les permite trabajar en una forma más intuitiva:

Como las duraciones, los períodos pueden ser creados mediante un número de funciones constructoras amigables.

Puedes sumar y multiplicar períodos:

Y, por supuesto, puedes sumarlos a las fechas. Comparados a las duraciones, los períodos son más propensos a hacer lo que esperas que hagan:

Usemos los períodos para arreglar una rareza relacionada a nuestras fechas de vuelos. Algunos aviones parecen arribar a su destino antes de salir de la ciudad de Nueva York.

Estos son vuelos nocturnos. Usamos la misma información de fecha para los horarios de salida y llegada, pero estos vuelos llegaron al día siguiente. Podemos arreglarlo al sumar days(1) a la fecha de llegada de cada vuelo nocturno.

Ahora todos los vuelos obedecen a las leyes de la física.

16.4.3 Intervalos

Resulta obvio lo que dyears(1) / ddays(365) debería retornar: uno, porque las duraciones siempre se representan por un número de segundos, y la duración de un año se define como 365 días convertidos a segundos.

¿Qué debería devolver years(1) / days(1)? Bueno, si el año fuera 2015 debería retornar 365, ¡pero si fuera 2016 debería retornar 366! No hay suficiente información para que lubridate nos de una sola respuesta sencilla. Entonces, lo que hace es darnos una estimación con una advertencia:

Si quieres una medida más precisa, tendrás que usar un intervalo. Un intervalo es una duración con un punto de partida: eso lo hace preciso, por lo que puedes determinar exactamente cuán largo es:

Para encontrar cuántos períodos caen dentro de un intervalo, tienes que usar la división entera:

16.4.4 Resumen

¿Cómo eliges entre duraciones, períodos e intervalos? Como siempre, selecciona la estructura de datos más sencilla que resuelva tu problema. Si solo te interesa el tiempo físico, usa una duración; si necesitas agregar tiempos humanos, usa un período; si tienes que deducir cuán largo es un lapso de tiempo en unidades humanas, usa un intervalo.

La figura 16.1 resume las operaciones artiméticas permitidas entre los tipos de datos.

Las operaciones artiméticas permitidas entre pares de clases fecha/hora.

Figure 16.1: Las operaciones artiméticas permitidas entre pares de clases fecha/hora.

16.4.5 Ejercicios

  1. ¿Por qué hay months() pero no dmonths() (días del mes)?

  2. Explica days(nocturno * 1) a alguien que apenas comienza a aprender R. ¿Cómo funciona?

  3. Crea un vector de fechas dando el primer día de cada mes en 2015. Crea un vector de fechas dando el primer día de cada mes del año actual.

  4. Crea una función en la cual, dado tu cumpleaños (como una fecha), retorne qué edad tienes en años.

  5. ¿Por qué no funciona (today() %--% (today() + years(1)) / months(1) ?

16.5 Husos horarios

Los husos horarios son un tema enormemente complicado debido a su interacción con entidades geopolíticas. Afortunadamente, no necesitamos escarbar en todos los detalles, ya que no todos son necesarios para el análisis de datos. Sin embargo, hay algunos desafíos que tendremos que enfrentar.

El primer desafío es que los nombres comunes de los husos horarios tienden a ser ambiguos. Por ejemplo, si eres estadounidense, probablemente te sea familiar la sigla EST, (del inglés de Tiempo Este Estándar). Sin embargo, ¡Canadá y Australia también tienen EST! Para evitar la confusión, R usa el estándar internacional IANA para husos horarios. Estos tienen un esquema de nombres que sigue el formato “<área>/”, típicamente escrito como “<continente>/<ciudad>”, en idioma inglés (hay algunas pocas excepciones porque no todos los países están ubicados en un continente). Algunos ejemplos: “America/New_York”, “Europe/Paris”, “Pacific/Auckland”, “America/Bogota”.

Puedes que te preguntes por qué un huso horario usa una ciudad, cuando típicamente piensas en ellos como asociados a un país o a una región dentro de un país. Esto se debe a que la base de datos de IANA tiene que registrar décadas de reglamentos sobre husos horarios. En el curso de las décadas, los países cambian nombres (o desaparecen) de forma bastante frecuente, pero los nombres de las ciudades tienden a mantenerse igual. Otro problema es que los nombres tienen que reflejar no solo el comportamiento actual, sino también la historia completa. Por ejemplo, hay husos horarios tanto para “America/New_York” como para “America/Detroit”. Actualmente, ambas ciudades usan el EST, pero entre 1969 y 1972, Michigan (el estado en el que está ubicado Detroit), no empleaba el horario de verano, así que necesita un nombre diferente. ¡Vale la pena leer la base de datos sobre husos horarios (disponible en http://www.iana.org/time-zones) solo para enterarse de algunas de estas historias!

Puedes encontrar cuál es tu huso horario actual para R, usando Sys.timezone():

(Si R no lo sabe, obtendrás un NA.)

Y puedes ver la lista completa de todos los husos horarios con OlsonNames():

En R, el huso horario es un atributo de la fecha-hora (date-time) que solo controla la impresión. Por ejemplo, estos tres objetos representan el mismo instante en el tiempo:

Puedes verificar que son lo mismo al usar una resta:

Excepto que se especifique otra cosa, lubridate siempre usa UTC. UTC (Tiempo Universal Coordinado) es el huso horario estándar empleado por la comunidad científica y es aproximadamente equivalente a su predecesor GMT (siglas en inglés de Tiempo del Meridiano de Greenwich). UTC no tiene horario de verano, por lo que resulta una representación conveniente para la computación. Las operaciones que combinan fechas y horas, como c(), a menudo descartan el huso horario. En ese caso, las fechas y horas se muestran en tu huso local:

Puedes cambiar el huso horario de dos formas: