3.5 Speciální datové typy

Kromě základních datových typů existují v R i další datové typy, které jsou implementované jako třídy objektů. Z nich nejdůležitější jsou faktory a třídy pro uchovávání datumů a času.

3.5.1 Faktory

Faktory slouží k uchovávání kategoriálních proměnných, ať už ordinálních (záleží na pořadí), nebo neordinálních. Příkladem ordinální kategoriální proměnné je kvalita služby: může např. nabývat hodnot “velmi špatná”, “špatná”, “průměrná”, “dobrá” a “výborná”. Příkladem neordinální kategoriální proměnné je pohlaví, které může nabývat hodnot “žena” nebo “muž”. Na rozdíl od předchozího případu zde není jasné pořadí, ve kterém by měly být hodnoty uspořádány.

Kategoriální proměnné je možné kódovat např. jako celá čísla: např. muž bude 0 a žena 1, nejhorší kvalita služby bude 0, druhá nejhorší 1 atd. To však není hned z několika důvodů dobrý nápad: 1) je obtížné pamatovat si, co která hodnota znamená, 2) R nebude vědět, jak s takovými proměnnými zacházet a bude je považovat za kardinální veličiny (tj. bude např. kvalitu služby “průměrnou” kódovanou jako 2 považovat za dvakrát lepší než kvalitu “špatnou” kódovanou jako 1, přestože jediné, co víme, je, že “průměrná” kvalita je lepší než “špatná”, ale už ne o kolik nebo kolikrát) a 3) R nebude schopné hlídat, zda není zadána nesmyslná úroveň proměnné (např. kvalita služby 7).

Faktory řeší všechny tyto problémy: jednotlivým hodnotám dávají “nálepky”, které ukazují na jejich význam, a zároveň říkají R, že se jedná o kategoriální proměnnou. R také zná platné úrovně faktorů, tj. může hlídat, zda je zadaná úroveň platná.

Faktory se tvoří pomocí funkce factor(). Ta vyžaduje nutně pouze vektor řetězců, který obsahuje hodnoty, které se mají na faktor převést:

factor(c("žena", "muž", "muž", "žena"))
## [1] žena muž  muž  žena
## Levels: muž žena

V tomto případě R odhaduje úrovně faktoru z dat, což není příliš bezpečné, protože v konkrétním datovém vzorku může některá platná úroveň faktoru chybět. Obecně je bezpečnější říct funkci factor() i to, jakých hodnot může faktor nabývat (pomocí parametru levels). To navíc umožní zadat i hodnoty, které nyní faktor neobsahuje, ale obsahovat by je mohl, a také určit pořadí faktorů (jinak je R řadí podle abecedy); pořadí je důležité pro ordinální faktory a také při některých statistických metodách určuje, která hodnota bude brána jako kontrast. Parametr labels navíc umožňuje úrovně faktorů překódovat:

factor(c("male", "female", "female", "male", "female"),  # hodnoty vektoru
       levels = c("female", "male", "asexual"),          # možné úrovně
       labels = c("žena", "muž", "asexulní"))            # co se bude vypisovat
## [1] muž  žena žena muž  žena
## Levels: žena muž asexulní

Všimněte si, že při vypsání faktor vypisuje nejen hodnoty vektoru, nýbrž i seznam hodnot, kterých mohou nabývat.

Pokud se pokusíte uložit do faktoru hodnotu, která neodpovídá zadaným úrovním, R danou hodnotu nahradí hodnotou NA:

factor(c("male", "female", "female", "male", "beaver"),  # hodnoty vektoru
       levels = c("female", "male", "asexual"),          # možné úrovně
       labels = c("žena", "muž", "asexulní"))            # co se bude vypisovat
## [1] muž  žena žena muž  <NA>
## Levels: žena muž asexulní

To je velmi šikovné, protože to umožňuje hlídat, zda jsou všechny hodnoty zadané správně. Řekněme, že svá data stahujete z nějakého serveru, který s vámi nespolupracuje a může kdykoli bez varování změnit kódování některých kategoriálních hodnot. Jedna možnost, jak to zjistit, je převádět je z řetězců na faktory s pevně zadanými hodnotami úrovní. Pokud se mezi hodnotami faktoru objeví NA, znamená to, že server změnil kódování dané proměnné.

Implicitně jsou všechny faktory ne-ordinální. Pokud chcete R říci, že faktor je ordinální, přidáte parametr ordered = TRUE. Pak na faktory funguje porovnání větší a menší:

quality <- factor(c("poor", "satisfactory", "excelent"),
                  levels = c("poor", "satisfactory", "excelent"),
                  ordered = TRUE)
quality
## [1] poor         satisfactory excelent    
## Levels: poor < satisfactory < excelent
quality[1] < quality[3]  # quality[i] je i-tý prvek vektoru
## [1] TRUE

Funkce is.ordered() vrací logickou hodnotu TRUE, pokud je faktor ordinální.

Poznámka: pokud opravdu dobře nevíte, co děláte, používejte raději ne-ordinální faktory. Ordinální faktory se ve formulích (tj. např. v ekonometrické analýze, viz kapitola ??) chovají jinak, než byste možná čekali, viz https://goo.gl/HY3uNf a https://goo.gl/F9Shll, oddíl 11.1.1.

Hodnoty úrovní můžete získat pomocí funkce levels(); jejich počet pomocí funkce nlevels(). Funkce levels() umožňuje i měnit hodnoty úrovní faktoru:

f <- factor(c("female", "male", "female"))
f
## [1] female male   female
## Levels: female male
levels(f) <- c("a", "b", "c")
f
## [1] a b a
## Levels: a b c

Někdy je užitečné rozdělit spojitou škálu do diskrétních hodnot. Např. můžeme chtít rozdělit studenty do tří kategorií podle jejich studijního průměru: na excelentní žáky (do průměru 1.2 včetně), běžné žáky (od průměru 1.2 do 2.5 včetně) a ostatní. K tomu slouží funkce cut():

grades <- c(1.05, 3.31, 2.57, 1.75, 2.15)  # studijní průměry
students <- cut(grades, breaks = c(0, 1.2, 2.5, Inf), right = TRUE)
students
## [1] (0,1.2]   (2.5,Inf] (2.5,Inf] (1.2,2.5] (1.2,2.5]
## Levels: (0,1.2] (1.2,2.5] (2.5,Inf]
levels(students) <- c("excelent", "normal", "rest")
students
## [1] excelent rest     rest     normal   normal  
## Levels: excelent normal rest

Změnu názvů úrovní je možné nastavit přímo ve funkci cut() parametrem labels. Detaily funkce viz dokumentace.

Jako mnoho jiných funkcí ve standardní výbavě R má i funkce cut() mnoho (na první pohled) složitých parametrů. Balík ggplot2 nabízí tři specializovanějí funkce, které udělají stejnou práci jako cut(), ale jejich použití je jednodušší: cut_width() nařeže původní proměnnou na úrovně s danou šířkou, cut_interval() na daný počet úrovní se stejnou šířkou a cut_number() na daný počet úrovní se stejným počtem pozorování.

Faktory jsou užitečné, ale i zrádné. Technicky jsou implementované jako vektor celých čísel, který má navíc pojmenované úrovně:

unclass(factor(c("žena", "muž", "muž", "žena")))
## [1] 2 1 1 2
## attr(,"levels")
## [1] "muž"  "žena"

Při automatické konverzi se tedy může stát, že se faktor převede na celá čísla – svých úrovní. Většinou to nevadí, existuje však jedna zrádná výjimka:

f <- factor(c("747", "737", "777", "747"))  # vektor typů vašich letadel Boeing
f
## [1] 747 737 777 747
## Levels: 737 747 777
as.integer(f)  # chcete dostat zpět typy letadel, ale ouha! dostanete čísla úrovní!
## [1] 2 1 3 2
as.integer(as.character(f))  # je potřebná dvojí konverze!
## [1] 747 737 777 747

Historicky se faktory používaly velmi často, protože šetřily paměť počítače. Místo vektoru řetězců, kde se hodnoty často opakovaly, zbyl krátký vektor unikátních hodnot řetězců (úrovně levels) a paměťově úspornější vektor celých čísel, který říkal, která úroveň se právě používá. Dnes to však už není pravda: R skladuje řetězce v jednom velkém balíků, každý z nich pouze jednou a vektory řetězců jsou implementovány jako odkazy do tohoto skladu. Prakticky to znamená, že převedením řetězců na faktor se žádná paměť neušetří.

Z těchto historických důvodů se R snaží řetězce převádět na faktory např. při použití funkce data.frame() i při načítání tabulek ze souboru (viz dále). Pokud tomu chcete zabránit, musíte o to R explicitně požádat.

3.5.2 Datum a čas

Datum a čas lze v R ukládat do mnoha různých tříd objektů. Z nich jsou nejzákladnější tři: třída Date pro reprezentaci datumů (celých dnů bez času) a třídy POSIXct a POSIXlt pro reprezentaci času. První dvě třídy ukládají datum a čas jako počet dnů nebo sekund od 1. ledna 1970.

3.5.2.1 Datum

Třídu Date dostaneme explicitní konverzí řetězce, který obsahuje datum:

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

Pokud je datum zadáno v jiném formátu, musíte tento formát popsat parametrem format:

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"

Formát se zadává pomocí formátovacích řetězců, které jsou popsané v dokumentaci funkce strptime(). Jména měsíců a dnů v týdnu 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:

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

Víc detailů je v dokumentaci k locales (?locales).

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).

3.5.2.2 Čas

K uložení času slouží třídy POSIXct a POSIXlt. Třída POSIXct ukládá počet sekund od referenčního data, jedná se tedy o celé číslo. Třída POSIXlt je seznam o mnoha položkách. Pro ukládání času do datasetu se tedy více hodí třída POSIXct, protože je paměťově úspornější.

K převodu na třídy času slouží funkce as.POSIXct() a as.POSIXlt(). Užitečné jsou i funkce strftime() a strptime(), které slouží k převodu řetězce na třídu POSIXlt a opačným směrem. Detaily jsou v dokumentaci.

Pokud např. chceme zjistit jaký den odpovídá nějakému datu, můžeme použít funkci strftime():

d <- as.Date("2016-11-05")
strftime(d, format = "%A")
## [1] "Sobota"

3.5.2.3 Operace s datem a časem

Pro třídy Date, POSIXct a POSIXlt fungují některé aritmetický a logické operace:

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 diferencí
## 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

K získání základních informací o datech slouží funkce weekdays(), months(), days() a quarters():

weekdays(d)  # den v týdnu
## [1] "Neděle" "Pátek"
months(d)    # měsíc v roce
## [1] "leden"  "březen"
quarters(d)  # čtvrtletí
## [1] "Q1" "Q1"

Funkce difftime() vrací vzdálenost mezi dvěma dny v zadaných jednotkách (parametr units), které mohou mít jednotky "auto", "secs", "mins", "hours", "days" nebo "weeks":

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

Funkce seq() může vytvářet časové vektory. Parametr by se nastaví buď na 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:

seq(as.Date("2015-1-1"), by = "2 days", length = 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"

3.5.2.4 Systémový čas

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

Sys.time()
## [1] "2017-06-12 13:55:22 CEST"

3.5.2.5 Další užitečné balíky pro práci s datem a časem

Balík lubridate definuje mnoho užitečných funkcí, které zjednodušují práci s datem a časem, viz Wickham and Grolemund (2017), kap. 16 nebo https://goo.gl/OKgNtq.

Balíky chron a zoo implementují pokročilejší třídy pro reprezentaci data a času v R.

References

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