O pacote lubridate

04/04/2024

Linguagem R

Lidar com datas no R pode ser um desafio! Isso ocorre devido à própria natureza desse tipo de dado. A depender de onde estamos, o fuso-horário pode variar e em certas análises pode ser necessário levar isso em consideração. Além disso, as datas podem estar representadas em diferentes formatos. Por exemplo, o dia  09/02/2024 pode estar escrito da forma 2024-02-09 ou até mesmo por extenso, podendo ainda estar acompanhado pelas horas. Outro fator importante a se considerar é o chamado Daylight Saving Time (Conhecido no Brasil como horário de verão), que apesar de não ser mais usado aqui, é amplamente utilizado por outros países.

No R, através do pacote {base} temos algumas classes para trabalhar com esse tipo de dado, como Date, diffitime, POSIXcT e POSIXlt e algumas funções relativas a tais classes. Date representa a data, POSIXct e POSIXlt representam a data e as horas conjuntamente e a classe difftime representa as diferenças entre as datas (funciona tanto para Date quanto para POSIXct e POSIXlt).

A exemplo do que já se consegue fazer usando o pacote {base}, considere agora os dados dessa base. Tal base de dados foi utilizada nas aplicações do livro Ciência de Dados Aplicada à Saúde Materno-Infantil, do Observatório Obstétrico Brasileiro. Inicialmente, vamos verificar as variáveis de interesse no banco de dados:

> dados = readRDS("baselimpa_covid19.rds")
> dados = dados[,1:4]  
> str(dados)
Classes ‘data.table’ and 'data.frame': 11523 obs. of 4 variables:
$ dt_notific: Date, format: "2020-05-15" "2020-05-18" ...
$ dt_sin_pri: Date, format: "2020-05-06" "2020-05-10" ...
$ dt_nasc : Date, format: "2003-06-03" "1996-07-07" ...
$ dt_interna: Date, format: "2020-05-15" "2020-05-15" ...
> attach(dados) #separa as variáveis em vetores diferentes 
com seus respectivos nomes

Suponha que estamos interessados na diferença de tempo entre a data de internação (dt_interna) e a data do primeiro sintoma de COVID-19 (dt_sin_pri).

> dif = na.omit(dados[,4]-dados[,2])
> str(dif)
'difftime' num [1:11523] 9 5 4 5 ... #mostra as diferenças entre as datas
- attr(*, "units")= chr "days" #mostra que o tempo esta medido em dias

Repare que a diferença de tempo apresenta dias como unidade de medida. Suponha que seja necessário mudar a unidade de medida de tempo dessas diferenças. Usando a função difftime() do pacote {base} podemos fazer o seguinte:

> tempo = difftime(dados[,4],dados[,2],units="weeks")
> tempo[1]
Time difference of 1.285714 weeks #em semanas
> tempo = difftime(dados[,4],dados[,2],units="secs")
> tempo[1]
Time difference of 777600 secs #em segundos
> help(difftime) #Para mais informações

Considerando agora a variável dt_interna, imagine que queremos mudar a ordem na qual dia, mês e anos estão sendo apresentados. Com a função format() basta escolher a ordem. Repare que no segundo argumento da função, %d representa dia, %m representa mês e %Y representa o ano. A ordem que eles estiverem escritos será a ordem mostrada. Note também que o formato mudou de Date para chr.

> str(dt_interna)
Date[1:11523], format: "2020-05-15" "2020-05-15" "2020-04-24" "2020-05-09"  "2020-06-30" ...
> dt_interna2=format(dt_interna,"%d/%m/%Y")
> str(dt_interna2)
chr [1:11523] "15/05/2020" "15/05/2020" "24/04/2020" "09/05/2020" ...

Mesmo com tais funções, podemos encontrar diversos problemas pelo caminho. Desta forma, o pacote {lubridate} vem como um complemento bem útil, facilitando bastante o trabalho com as datas e aumentando as possibilidades para trabalhar com elas.

O que se pode fazer com o pacote?

Suponha que queremos criar a data 15/04/2017.

library(lubridate) #carregando o pacote
> ymd(20170415) #considera a ordem ano/mês/dia  
[1] "2017-04-15"
> dmy(15042017) #considera a ordem dia/mês/ano
[1] "2017-04-15"
> mdy(04152017) #considera a ordem mês/dia/ano
[1] "2017-04-15"

Se por acaso houvesse datas por extenso não teríamos problemas. Podemos transformar diferentes formatos em datas.

> x="23 de setembro de 1926"
> dmy(x)
[1] "1926-09-23"
> x="23 de setembro, 1926"
> dmy(x)
[1] "1926-09-23"
> x="September 23, 1926"
> mdy(x)
[1] "1926-09-23"
> x="1926 September 23"
> ymd(x)
[1] "1926-09-23"

Note que todos geram a mesma saída. Agora, sem o {lubridate}:

> as.Date("2017-04-15")
[1] "2017-04-15"
as.Date("15-04-2017) #caso não se especifique a ordem
[1] "15-04-20"
as.Date("15-04-2017",format="%d-%m-%Y")
[1] "2017-04-15"

Voltando ao {lubridate}, considere que fosse desejado criar data e as horas conjuntamente.

> dmy_hms(29022024235959) 
 [1] "2024-02-29 23:59:59 UTC"
> dmy_hms("29/02/2024 23:59:59")
[1] "2024-02-29 23:59:59 UTC"
> dmy_hms("29-02-2024 23:59:59")
[1] "2024-02-29 23:59:59 UTC"

Já sem o {lubridate}:

> as.POSIXct("2024/02/29 23:59:59",tz="UTC")
[1] "2024-02-29 23:59:59 UTC"

Perceba que quando se usou o lubridate, foi utilizado o UTC (tempo universal coordenado) como o fuso-horário. Quando resolvemos não utilizá-lo tivemos que especificar por meio do argumento tz. Caso se deseje outro fuso-horário basta  especificar da mesma forma nas funções do pacote.

> dmy_hms("29/02/2024 23:59:59",tz="GMT")
[1] "2024-02-29 23:59:59 GMT"

Considere que agora o interesse é calcular a diferença entre duas datas, porém uma está na classe Date e a outra está na classe POSIXct ou POSIXlt (classe para data e hora).

> x = dmy_hms("6 de março de 1942 13:00:00"); x 
 [1] "1942-03-06 13:00:00 UTC"
> class(x)
[1] "POSIXct" "POSIXt" 
> y = dmy("5 de agosto de 1941"); y 
[1] "1941-08-05"
class(y)
> [1] "Date"
> x-y
[1] "1942-03-06 15:52:56 UTC"
Warning message:
Incompatible methods ("-.POSIXt", "-.Date") for "-"

Veja que não é possível calcular a diferença entre elas dessa forma. Sendo assim, é preciso converter as informações para o mesmo formato.

> z = as_date(x) #converte para o formato Date
> z
[1] "1942-03-06"
> class(z)
[1] "Date"
> z-y
Time difference of 213 days

Imagine que agora estamos estamos trabalhando com um conjunto de dados onde as informações de data e hora (ano, mês, dia, hora, minuto, segundo) estão separadas, como mostra o exemplo abaixo.

> x #dataframe com as informações da data separadas por colunas
    dia mes  ano hora min
 1   20   5 1978    8  12
 2   17   1 2008   15  18

Para juntar tudo podemos utilizar a função make_datetime().

> attach(x)
> make_datetime(ano,mes,dia,hora,min)
[1] "1978-05-20 08:12:00 UTC" "2008-01-17 15:18:00 UTC"

Se não utilizarmos o lubridate, podemos utilizar a função ISOdatetime() do pacote {base}.

Pode acontecer também o contrário. Por exemplo, dada uma data, queremos extrair um de seus componentes (ano, mês, hora e etc) , para isso podemos usar as funções year(), month() e day() para extrair ano, mês e dia respectivamente.

> x = dmy_hms("29/02/2024 23:59:59")
> x
[1] "2024-02-29 23:59:59 UTC"
> year(x) #somente o ano
[1] 2024
> month(x) #somente o mês
[1] 2
> mday(x) #dia do mês
[1] 29
> yday(x) #dia do ano
[1] 60
> wday(x) #dia da semana
[1] 5
> hour(x) #somente a hora
[1] 23
> minute(x) #somente o minuto
[1] 59
> second(x) #somente o segundo
[1] 59

Caso se deseje que seja mostrado o texto por extenso, basta especificar nos argumentos da função. Caso abbr esteja como TRUE, as escritas aparecerão abreviadas.

> wday(x,label = TRUE, abbr = FALSE) #dia da semana por extenso
[1] quinta
month(x,label = TRUE, abbr = FALSE) #mês por extenso
[1] fevereiro 

Imagine que agora queremos mudar alguma informação contida na data.

> x = ymd_hms("2012-12-12 12:12:12")
> x
[1] "2012-12-12 12:12:12 UTC"
> year(x)=1912 #muda apenas o ano
> x
[1] "1912-12-12 12:12:12 UTC"
> year(x)=1999
> x = ymd_hms("2012-12-12 12:12:12")
> month(x)=01 #muda apenas o mês
> x
[1] "2012-01-12 12:12:12 UTC"
> day(x)=01 #muda apenas o dia
> x
[1] "2012-01-01 12:12:12 UTC"

Se for necessário, é possível até mudar um conjunto de informações da data, como mostra o exemplo abaixo.

> update(x,year=2000,hour=00,min=00,sec=01)
[1] "2000-01-01 00:00:01 UTC

Suponha que agora estamos com duas datas de eventos ocorridos nos Estados unidos  (lá se adota o Dayligth Saving Time, equivalente ao nosso antigo horário de verão, como dito anteriormente).

> x = ymd_hms("2023-03-10 09:25:07",tz = "America/New_York")
> y = ymd_hms("2023-03-09 09:25:07",tz = "America/New_York")
> x-y
Time difference of 23 hours

Ou seja, no dia 11 de março de 2023  quem mora nos Estados Unidos teve que adiantar seu relógio uma hora às 11 da noite. E se tais eventos tivessem acontecido no Brasil?  Basta mudar o fuso-horário ( em inglês time zone), utilizando a função force_tz().

> Sys.timezone() #função do pacote {base} que mostra o fuso-horário
[1] "America/Sao_Paulo"
> x = force_tz(x,tz="America/Sao_Paulo")
> x
[1] "2024-03-10 09:25:07 -03"
> y = force_tz(y,tz="America/Sao_Paulo")
> y
[1] "2024-03-09 09:25:07 -03"

Agora o fuso-horário mudou. Sendo assim:

> x-y
Time difference of 1 days 

Dessa forma, perceba como as funções do {lubridate} facilitam bastante a forma de trabalhar com as datas no R, se tornando uma ferramenta valiosa para qualquer estatístico e cientista de dados.

Referências

WICKHAM, Hadley; GROLEMUND, Garrett. R for data science.  O’Reilly