6.2 Datum a čas

Datum a čas lze v R ukládat do vektorů několika různých tříd. Z nich jsou nejdůležitější jsou třídy Date pro reprezentaci celých dnů bez času a POSIXct pro reprezentaci dne i hodiny. Datové typy Date a POSIXct nepatří mezi základní datové typy v R, ale jedná se o třídy objektů odvozené od tříd integer a double. První ukládá datum tak, že celé číslo reprezentuje počet dnů od 1. ledna 1970, druhá reprezentuje čas jako počet sekund, které od tohoto dne uběhl. Při výpisu však obě funkce zobrazují datum uživatelsky přátelským způsobem.

R nabízí pro práci s datumy a časem několik užitečných funkcí, které však mohou být v určitých situacích poněkud těžkopádné. Uživatelsky přívětivější funkce nabízí balík lubridate. Zde si ukážeme některé základní operace s datumy a časem jak pomocí základních funkcí, tak pomocí funkcí z balíku lubridate. Proto jej musíme nejprve načíst:

library(lubridate)

6.2.1 Zadávání datumů a času

Datum nejčastěji vytvoříme konverzí z řetězce. K tomu slouží funkce as.Date(), která předpokládá, že řetězec obsahuje datum uložené ve formátu obvyklém v anglofonním světě, kdy na prvním místě stojí rok, pak měsíc a nakonec den a jednotlivé položky jsou oddělené pomlčkou (tak R také datumy vypisuje):

as.Date("2016-11-25")  # 25. listopadu 2016
## [1] "2016-11-25"

Pokud je datum zadano v jinem formatu, musite tento format popsat v parametru format. Format se zadava pomoci formatovacich retezcu, ktere jsou popsane v dokumentaci funkce strptime(). Zakladni parametry uvadi tabulka 6.1. Řekněme, že máme data zadaná ve formátu číslo dne, zkratka měsíce a rok, kde jednotlivé položky nejsou nijak oddělené. Pak bude mít řetězec formátu tvar “%d%b%Y”: “%d” znamená, že na prvním místě je den v měsíci, “%b” říká, že na druhém místě je zkrácený název měsíce a “%Y” že na posledním místě je rok zadaný čtyřmi číslicemi.

as.Date(c("1led1960", "2led1960", "31bře1960", "30čec1960"), format = "%d%b%Y")
## [1] "1960-01-01" "1960-01-02" "1960-03-31" "1960-07-30"
Tabulka 6.1: Vybrané formátové značky pro zadávání datumu a času
kód význam
%d číslo dne v měsíci
%m číslo měsíce v roce
%b zkrácené jméno měsíce
%B plné jméno měsíce
%y rok zadaný dvěma číslicemi
%Y rok zadaný čtyřmi číslicemi
%H číslo hodiny (0–23)
%M číslo minuty (0–59)
%S číslo sekundy (0–61 pro přestupné vteřiny)

Pokud by jednotlivé položky datumu byly nějak oddělené, zadaly by se tyto oddělovače do formátu. Datum ve formátu “1. červen 1974” by mělo formátový řetězec “%d. %B %Y”:

as.Date(c("1. červen 1974", "17. listopad 1989"), format = "%d. %B %Y")
## [1] NA NA

Jména měsíců a dnů v týdnu standardně se berou z lokalizace vašeho operačního systému. Pokud vám tato lokalizace nevyhovuje (potřebujete jména v jiném jazyce) nebo musíte svůj kód přenášet mezi více počítači, můžete lokalizaci manuálně přepnout (víc detailů najdete v dokumentaci k locales: ?locales):

# nastav locales, aby jména dnů byla anglicky
# (zde se tento řádek neprovede, aby byl zbytek textu česky)
Sys.setlocale("LC_TIME", "C")

Při načítání datumů je možné nastavit i časovou zónu pomocí parametru tz. Jinak se automaticky vezme časová zóna z vašeho operačního sytému (v mém případě nyní “CEST”, tj. středoevropský letní čas).

Pokud máte dobrodružnější povahu a jednotlivé části datumu jsou zadané pomocí čísel dnů, měsíců a let, můžete použít pohodlnější funkce z balíku lubridate. Těchto funkcí je celá řada a jmenují se na první pohled krypticky ymd(), ydm(), mdy(), myd(), dmy() a dym(). Jednotlivá písmena určují, v jakém pořadí jsou uvedeny jednotlivé složky datumu: y znamená rok, m měsíc a d den, takže funkce ymd() předpokládá, že v datumu je nejdříve uveden rok, pak měsíc a nakonec den. Všechny tyto funkce jsou chytré, takže si poradí s nejrůznějšími oddělovači. Pokud funkce na některém prvku vektoru selže, vydá varování. (Všimněte si, jak funkce naloží s rokem zadaným jen dvěma číslicemi.)

dmy(c("1.6.1974", "17. 11. 1989", "1-1-2001", "1.3.99", "1.3.39", "bžů"))
## Warning: 1 failed to parse.
## [1] "1974-06-01" "1989-11-17" "2001-01-01" "1999-03-01" "2039-03-01"
## [6] NA
ymd(20140531)  # funguje i celé číslo, pokud je jednoznačné
## [1] "2014-05-31"

Datum je možné složit i z jednotlivých komponent pomocí funkce make_date():

make_date(year = 2011, month = 7, day = 5)
## [1] "2011-07-05"

K zadání dne i hodiny slouží funkce as.POSIXct(), která opět implicitně předpokládá datum a čas v anglofonním formátu. Formát je opět možné změnit zadáním parametru format.

as.POSIXct("1974-06-01 7:30")
## [1] "1974-06-01 07:30:00 CET"

Balík lubridate opět nabízí uživatelsky přívětivější (ale méně striktní, a tedy bezpečné) funkce ymd_hms(), ymd_hm(), ymd_h(), dmy_hms(), dmy_hm(), dmy_h(), mdy_hms(), mdy_hm(), mdy_h(), ydm_hms(), ydm_hm() a ydm_h(), kde písmena d, m a y mají stejný význam jako výše a h znamená hodiny, m minuty a s sekundy:

ymd_hm("1974-06-01 7:30")
## [1] "1974-06-01 07:30:00 UTC"
dmy_hm("1. 6. 1974 7.30")
## [1] "1974-06-01 07:30:00 UTC"

I datum a čas je možné složit z jednotlivých komponent pomocí funkce

make_datetime(year = 2017, month = 1:12, day = 1)
##  [1] "2017-01-01 UTC" "2017-02-01 UTC" "2017-03-01 UTC" "2017-04-01 UTC"
##  [5] "2017-05-01 UTC" "2017-06-01 UTC" "2017-07-01 UTC" "2017-08-01 UTC"
##  [9] "2017-09-01 UTC" "2017-10-01 UTC" "2017-11-01 UTC" "2017-12-01 UTC"

6.2.2 Jednotlivé komponenty datumu a času

Často je potřeba z datumu nebo času získat příslušnou hodnotu dne, měsíce nebo hodiny. K je možné použít funkce weekdays(), months(), days() a quarters() ze základního balíku. Funkce weekdays() a months() vrací jména dnů a měsíců v locace počítače; mohou vrátit i zkrácenou verzi jména, pokud je parametr abbreviate nastaven na hodnotu TRUE.

d <- as.Date("2016-11-05")
weekdays(d)  # den v týdnu
## [1] "Sobota"
months(d)    # měsíc v roce
## [1] "listopadu"
quarters(d)  # čtvrtletí
## [1] "Q4"

Balík lubridate opět definuje další funkce pro práci s jednotlivými komponentami datumu a času: year() vrací rok, month() vrací měsíc, mday() vrací číslo dne v měsíci, yday() číslo dne v roce, wday() číslo dne v týdnu, hour() hodinu, minute() minutu a second() sekundu. Funkce month() a wday() mají navíc parametry labels a abbr. Pokud je label nastaveno na hodnotu TRUE, pak funkce vrací místo čísla jméno. Pokud je navíc abbr nastaveno na TRUE, pak je jméno zkráceno. Tyto funkce však vracejí vždy anglická jména dnů a měsíců, navíc kódovaná jako faktor. Také prvním dnem týdne je neděle (s číslem 1). Funkce tz() vrací časovou zónu.

year(d)
## [1] 2016
month(d)
## [1] 11
month(d, label = TRUE)
## [1] lis
## 12 Levels: led < úno < bře < dub < kvě < čen < čec < srp < zář < ... < pro
wday(d)
## [1] 7
wday(d, label = TRUE)
## [1] So
## Levels: Ne < Po < Út < St < Čt < Pá < So
tz(d)
## [1] "UTC"

Tyto funkce z balíku lubridate je možné použít i k úpravě datumu a času:

d <- ymd("2000-01-01")
day(d) <- 31
month(d) <- 12
d
## [1] "2000-12-31"

Pokud je potřeba upravit více prvků data naráz, je možné použít funkci update():

update(d, year = 1989, month = 11, mday = 17)
## [1] "1989-11-17"

6.2.3 Operace s datem a časem

Pro třídy Date a POSIXct fungují některé aritmetické a logické operace, jako je sčítání, odečítání a diference, násobení, dělení a porovnávání:

d <- as.Date(c("2016-01-10", "2016-03-11"))
d
## [1] "2016-01-10" "2016-03-11"
d[2] - d[1]              # kolik dnů je mezi druhým a prvním dnem ve vektoru?
## Time difference of 61 days
diff(d)                  # funguje i funkce pro diferenci
## Time difference of 61 days
as.numeric(d[2] - d[1])  # převod na celé číslo
## [1] 61
d > "2016-02-05"         # které dny ve vektoru jsou po 5. únoru 2016?
## [1] FALSE  TRUE

Většina aritmetických operací však pracuje na rozdílech mezi dvěma datumy, což je proměnná třídy difftime. Práce s touto třídou není úplně intuitivní, protože datumy a čas jsou plné různých nepravidelností (počet dnů se v jednotlivých měsících liší, přestupné roky mají více dnů a některé minuty mají 61 sekund). Proto se zde blíže podíváme jen na dvě operace: zjištění časové vzdálenosti dvou bodů a tvorbu vektoru datumů s konstantním rozestupem.

Pokud nás zajímá časová vzdálenost mezi dvěma daty, obyčejná diference vypíše rozdíl v automaticky zvolených jednotkách. Užitečnější proto může být funkce difftime(), která vrací vzdálenost mezi dvěma dny v jednotkách zadaných uživatelem pomocí parametru units, který může nabývat hodnot "auto", "secs", "mins", "hours", "days" nebo "weeks":

difftime(d[2], d[1], units = "weeks") 
## Time difference of 8.714286 weeks

Balik lubridate definuje velke mnozstvi dalsich funkci datumovou aritmetiku a praci s casovymi intervaly. Zde se jim vsak nebudeme venovat. Zajemci se mohou podivat do Wickham and Grolemund (2017), kap. 13 dostupne na http://r4ds.had.co.nz/dates-and-times.html, na web baliku lubridate na http://lubridate.tidyverse.org/ nebo do puvodniho clanku o tomto baliku, Grolemund and Wickham (2011), který je dostupný ke stažení na stránkách časopisu i balíku lubridate.

6.2.4 Tvorba ekvidistantních časových vektorů

Často potřebujeme vytvořit vektor s datumy nebo časem s ekvidistantním odstupem. V některých speciálních případech to můžeme udělat pomocí funkcí pro práci s jednotlivými složkami datumu. Řekněme například, že potřebujeme vektor datumů pro začátek každého měsíce v roce. To uděláme snadno např. takto:

d <- ymd("2017-01-01")
month(d) <- 1:12
d
##  [1] "2017-01-01" "2017-02-01" "2017-03-01" "2017-04-01" "2017-05-01"
##  [6] "2017-06-01" "2017-07-01" "2017-08-01" "2017-09-01" "2017-10-01"
## [11] "2017-11-01" "2017-12-01"

Vytvořit jiné vektory je však obtížnější. Například vektor posledních dnů v měsíci takto jednoduše vytvořit nejde, protože různé měsíce mají různý počet dnů. Náš algoritmus také nevytvořil data s ekvidistantním odstupem, a to právě proto, že různé měsíce jsou různě dlouhé.

K vytvoření vektorů se skutečně stejným rozestupem mezi jednotlivými daty je možné použít funkci seq(). Stejně jako v případě číselných vektorů je možné nastavit počátek a konec období a počet hodnot, nebo počátek období a odstup mezi položkami. Ten se nastaví pomocí parametru by. Tento parametr může mít hodnotu “day”, “week”, “month”, “quarter” nebo “year” případně násobenou celým číslem; ke jménu délky periody je také možné přidat koncové “s”. Jednodenní odstup je pak “day”, dvoudenní “2days” apod.

seq(from = ymd("2015-1-1"), to = ymd("2015-12-31"), length.out = 15)
##  [1] "2015-01-01" "2015-01-27" "2015-02-22" "2015-03-20" "2015-04-15"
##  [6] "2015-05-11" "2015-06-06" "2015-07-02" "2015-07-28" "2015-08-23"
## [11] "2015-09-18" "2015-10-14" "2015-11-09" "2015-12-05" "2015-12-31"
seq(ymd("2015-1-1"), by = "2 days", length.out = 10)
##  [1] "2015-01-01" "2015-01-03" "2015-01-05" "2015-01-07" "2015-01-09"
##  [6] "2015-01-11" "2015-01-13" "2015-01-15" "2015-01-17" "2015-01-19"

Kromě toho je odstup možné nastavit i na jakoukoli hodnotu třídy difftime, kterou vrací funkce difftime(), nebo na kteroukoli jednotku, kterou funkce difftime() dokáže zpracovat. Před jednotku je možné přidat celé číslo jako násobek této jednotky:

odstup <- ymd("2017-06-05") - ymd("2017-06-01")  # odstup 4 dny
seq(ymd("2017-06-01"), by = odstup, length.out = 6)
## [1] "2017-06-01" "2017-06-05" "2017-06-09" "2017-06-13" "2017-06-17"
## [6] "2017-06-21"

6.2.5 Systémový čas

Aktuální (systémový) čas lze zjistit funkcí Sys.time(), která vrací objekt třídy POSIXct:

Sys.time()
## [1] "2020-06-03 20:34:01 CEST"

Balík lubridate navíc přidává funkce today(), která vrací dnešní datum jako objekt třídy Date, a now(), která vrací okamžité datum a čas jako objekt třídy POSIXct:

today()  # den, kdy byla tato kniha naposledy zkompilována
## [1] "2020-06-03"
now()  # a nyní včetně času
## [1] "2020-06-03 20:34:01 CEST"

References

Grolemund, Garrett, and Hadley Wickham. 2011. "Dates and Times Made Easy with Lubridate." Journal of Statistical Software 40 (3): 1-25. https://www.jstatsoft.org/article/view/v040i03.

Wickham, Hadley, and Garrett Grolemund. 2017. R forData Science. 1st ed. Sebastopol, California, USA: O'Reilly. http://r4ds.had.co.nz/.