Zpět na přehled cvičení

Rkový skript ke stažení: R

Co si zde představíme?

Třída factor

Už jsme se naučili pracovat s čísly a stringy. Ale co když máme jen pár vybraných čísel či slov, kterých mohou naše proměnné nabývat? Jak s nimi pracovat jako s kategoriemi? Na to je v Rku právě třída factor:

slova <- c("hodne", "stredne", "malo", "malo", "malo", "stredne", "hodne")
cisla <- c(2, 1, 0, 0, 0, 1, 2)
unique(slova)                        # unikátní stringy (srovnané dle prvního výskytu)
## [1] "hodne"   "stredne" "malo"
unique(cisla)                        # unikátní čísla   (srovnané dle prvního výskytu)
## [1] 2 1 0
(fslova <- factor(slova))            # jako factor o 3 kategoriích dle abecedy
## [1] hodne   stredne malo    malo    malo    stredne hodne  
## Levels: hodne malo stredne
(fcisla <- factor(cisla))            # jako factor o 3 kategoriích dle uspořádání čísel
## [1] 2 1 0 0 0 1 2
## Levels: 0 1 2
vicedruhu <- c("a", 1, "a", 2, "c")  # už tady si převede čísla na stringy (naopak nelze)
factor(vicedruhu)
## [1] a 1 a 2 c
## Levels: 1 2 a c

Čím je faktorová proměnná užitečná? Mnohé funkce v Rku reagují na takovouto proměnnou jiným způsobem, ku příkladu funkce summary:

summary(slova)          # vektor o délce 7 plný prvků třídy character
##    Length     Class      Mode 
##         7 character character
summary(fslova)         # četnosti jednotlivých kategorií
##   hodne    malo stredne 
##       2       3       2
summary(cisla)          # základní popisné charakteristiky vektoru číselných dat
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.0000  0.0000  1.0000  0.8571  1.5000  2.0000
summary(fcisla)         # četnosti jednotlivých kategorií
## 0 1 2 
## 3 2 2

Ke kategoriím se lze dostat z existujícího faktoru následovně:

levels(fslova)                        # získání vektoru kategorií
## [1] "hodne"   "malo"    "stredne"
nlevels(fslova)                       # počet kategorií factoru, alternativně: # length(levels(factor))
## [1] 3

R-ko s faktorovou proměnnou interně pracuje jako s čísly od 1 do počtu kategorií:

as.numeric(fslova)                    # kategorie jako čísla
## [1] 1 3 2 2 2 3 1
as.numeric(fcisla)                    # POZOR i čísla si to přečísluje na 1, 2, ..., nlevels!
## [1] 3 2 1 1 1 2 3
as.numeric(as.character(fcisla))      # takhle lze získat původní čísla zpět
## [1] 2 1 0 0 0 1 2

Pořadí kategorií bývá často velmi důležité, tak si ukažme, jak ho upravit pomocí levels:

factor(slova)                                            # dle abecedy
## [1] hodne   stredne malo    malo    malo    stredne hodne  
## Levels: hodne malo stredne
factor(slova, levels = unique(slova))                    # dle pořadí výskytu
## [1] hodne   stredne malo    malo    malo    stredne hodne  
## Levels: hodne stredne malo
factor(slova, levels = c("malo", "stredne", "hodne"))    # manuální řazení (je třeba napsat kategorie správně)
## [1] hodne   stredne malo    malo    malo    stredne hodne  
## Levels: malo stredne hodne
factor(slova, levels = c("m", "Málo", "str", "hodne"))   # hodnota nematchuje nic z levels --> NA
## [1] hodne <NA>  <NA>  <NA>  <NA>  <NA>  hodne
## Levels: m Málo str hodne

Existuje ještě druhý způsob pomocí funkce relevel, která posune zvolenou kategorii na první místo, což může být užitečné pro regresní modely, ale pořadí ostatních zůstane nezměněné:

relevel(fslova, "malo")
## [1] hodne   stredne malo    malo    malo    stredne hodne  
## Levels: malo hodne stredne

Chcete-li ono pořadí zafixovat a dát Rku vědět, že existuje nějaké pořadí mezi kategoriemi, tak přidejte ordered = TRUE:

ofslova <- factor(slova, levels = c("malo", "stredne", "hodne"), ordered = TRUE) 
class(fslova)
## [1] "factor"
class(ofslova)
## [1] "ordered" "factor"
ofslova
## [1] hodne   stredne malo    malo    malo    stredne hodne  
## Levels: malo < stredne < hodne
min(ofslova)                              # min, max lze provádět s ordered ale ne s běžným factor
## [1] malo
## Levels: malo < stredne < hodne
quantile(ofslova, type = 1, probs = 0.4)  # i výběrové kvantily lze provádět jen s ordered factorem
##  40% 
## malo 
## Levels: malo < stredne < hodne

Jak přejmenovávat kategorie pomocí argumentu labels:

fslova_hezky <- factor(slova, ordered = TRUE,
                       levels = c("malo", "stredne", "hodne"),   # seřazené hodnoty vyskytující se v datech
                       labels = c("Málo", "Středně", "Hodně"))   # nové popisky kategorií
summary(fslova_hezky)
##    Málo Středně   Hodně 
##       3       2       2
fcisla_hezky <- factor(cisla, ordered = TRUE,
                       levels = 0:2,                             # seřazené hodnoty vyskytující se v datech
                       labels = c("Ne", "Možná", "Ano"))         # nové popisky kategorií
summary(fcisla_hezky)
##    Ne Možná   Ano 
##     3     2     2
# Pokud už máme existující `factor`, lze to udělat také následovně:
levels(fslova)                                                   # současné kategorie factoru
## [1] "hodne"   "malo"    "stredne"
levels(fslova) <- c("Hodně", "Málo", "Středně")                  # nové kategorie TOHO SAMÉHO factoru
summary(fslova)                                                  # upravené fslova
##   Hodně    Málo Středně 
##       2       3       2

Jak sjednocovat kategorie:

fcisla_anone <- factor(cisla, ordered = TRUE,
                       levels = 0:2,                        # seřazené hodnoty vyskytující se v datech
                       labels = c("Ne", "Ano", "Ano"))      # stačí dát stejnou nálepku dvěma různým hodnotám
summary(fcisla_anone)
##  Ne Ano 
##   3   4
# Nebo opět pomocí přejmenování kategorií již existujícího factoru
levels(fcisla) <- c("Ne", "Ano", "Ano")
summary(fcisla)                                             # upravené fcisla
##  Ne Ano 
##   3   4

Jak ignorovat kategorie:

fcisla_anone <- factor(cisla, ordered = TRUE,
                       levels = c(0,2),                     # jen vybrané hodnoty
                       labels = c("Ne", "Ano"))             # nové popisky kategorií
summary(fcisla_anone)                                       # z ignorovaných kategorií se stane NA
##   Ne  Ano NA's 
##    3    2    2

Jak z numerické proměnné udělat kategoriální:

realne <- c(-1.2, 3.4, 2.0, -4.3, -5.0, 0.2, 1.7, 4.8, 5.0, 0.9, -0.7, 0.3, -0.6)
summary(realne)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    -5.0    -0.7     0.3     0.5     2.0     5.0
cisla_jako_kategorie <- factor(realne)                     # každé vyskytující se číslo má svou kategorii
summary(cisla_jako_kategorie) 
##   -5 -4.3 -1.2 -0.7 -0.6  0.2  0.3  0.9  1.7    2  3.4  4.8    5 
##    1    1    1    1    1    1    1    1    1    1    1    1    1
intervaly <- cut(realne, breaks = c(-1, 0, 1))             # funkce cut() třídí do intervalů dle breaks
summary(intervaly)                                         # nestačí dát jen oddělovače
## (-1,0]  (0,1]   NA's 
##      2      3      8
intervaly <- cut(realne, breaks = c(-5, -1, 0, 1, 5))      # 4 kategorie -> 4+1 hodnot v breaks
summary(intervaly)                                         # chybí ta nejmenší hodnota
## (-5,-1]  (-1,0]   (0,1]   (1,5]    NA's 
##       2       2       3       5       1
intervaly <- cut(realne, breaks = c(-5, -1, 0, 1, 5), 
                 include.lowest = TRUE)                    # zahrne i nejmenší hodnotu
summary(intervaly)
## [-5,-1]  (-1,0]   (0,1]   (1,5] 
##       3       2       3       5
intervaly <- cut(c(realne, -Inf),                          # pro demonstraci si do hodnot přidejme i -Inf  
                 breaks = c(-Inf, -1, 0, 1, Inf))          # takto každé číslo někam zapadne
summary(intervaly)                                         # výjimkou je právě hodnota -Inf
## (-Inf,-1]    (-1,0]     (0,1]  (1, Inf]      NA's 
##         3         2         3         5         1
intervaly <- cut(realne, breaks = c(-5, -1, 0, 1, 5), 
                 right = FALSE, include.lowest = TRUE)     # uzavřené zleva, horní také zahrnuta
summary(intervaly)
## [-5,-1)  [-1,0)   [0,1)   [1,5] 
##       3       2       3       5
intervaly <- cut(realne, breaks = c(-5, -1, 0, 1, 5),      # vzniklé kategorie si lze rovnou pojmenovat
                 labels = c("Rozhodně ne", "Spíše ne", "Spíše ano", "Rozhodně ano"),
                 include.lowest = T, ordered_result = T)   # ulož jako ordered factor      
summary(intervaly)
##  Rozhodně ne     Spíše ne    Spíše ano Rozhodně ano 
##            3            2            3            5

Jak factor funguje za přítomnosti NA?

int_s_NA <- c(NA, intervaly, NA)
fint_s_NA <- factor(int_s_NA)            # ignorování NA hodnot přes defaultní "exclude = NA"
summary(fint_s_NA)
##    1    2    3    4 NA's 
##    3    2    3    5    2
nlevels(fint_s_NA)                       # NA nemají vlastní kategorii
## [1] 4
fint_s_NA <- addNA(fint_s_NA)            # NA jako dodatečné kategorie (jen pokud víme, co to znamená!)
nlevels(fint_s_NA)
## [1] 5
nlevels(addNA(intervaly))                # přidává kategorii NA nehledě na to, zda tam jsou (ifany = FALSE)
## [1] 5
nlevels(addNA(intervaly, ifany = TRUE))  # přidává kategorii NA jen pokud, tam nějaké je
## [1] 4

Úložky na procvičení

Vyřešte si to sami, aniž byste se dívali do řešení (úplně dole vespod skriptu).

  1. Mějme vektor e-mailových adres. Vytvořte faktorovou proměnnou, která
# Výroba náhodných e-mailových adres (nepovinná část, klidně okopírujte a pokuste se pochopit)
samo <- letters[c(1,5,9,15,21,25)]
souh <- setdiff(letters, samo)
ends <- c("gmail.com", "seznam.cz", "email.cz")
n <- 100       # počet různých adres
adresy <- character(n)
for(i in 1:n){
  pocet_slabik <- sample(2:6, 1)
  adresy[i] <- ""
  for(j in 1:pocet_slabik){
    adresy[i] <- paste0(adresy[i], sample(souh, 1), sample(samo, 1))
  }
  if(sample(0:1,1)){
    adresy[i] <- paste0(adresy[i], paste0(sample(0:9, sample(1:3, 1), replace = T), collapse = ""))
  }
  adresy[i] <- paste0(adresy[i], "@", sample(ends, 1, prob = c(0.55, 0.4, 0.05)))
}
adresy
##   [1] "nyfi@seznam.cz"           "myzyboninuqu@gmail.com"   "qitydecopu@seznam.cz"    
##   [4] "zohigeva@seznam.cz"       "gujykyvideny6@seznam.cz"  "pekiky2@gmail.com"       
##   [7] "jibyqonelo@seznam.cz"     "turevu@gmail.com"         "kacofe2@seznam.cz"       
##  [10] "viquxebyva@gmail.com"     "dyca66@gmail.com"         "xowywazumu@seznam.cz"    
##  [13] "tire8@seznam.cz"          "live@gmail.com"           "bybypitogage@gmail.com"  
##  [16] "pogonyzeweki@gmail.com"   "xuly797@gmail.com"        "cicici@gmail.com"        
##  [19] "metifadorahi@seznam.cz"   "teha@gmail.com"           "pyhoze68@gmail.com"      
##  [22] "jiny709@email.cz"         "kezy87@seznam.cz"         "qalumuzi@seznam.cz"      
##  [25] "zypuziwoku@gmail.com"     "gosixico@seznam.cz"       "kanigosu198@gmail.com"   
##  [28] "sagozenifozo8@gmail.com"  "kajihirydatu20@gmail.com" "nepojujahire@gmail.com"  
##  [31] "zikenoge@gmail.com"       "lubyzulixusi57@seznam.cz" "luco3@gmail.com"         
##  [34] "cojaqevuhy@seznam.cz"     "qezulocy@seznam.cz"       "lyrymejixaha@gmail.com"  
##  [37] "buryfowaty@gmail.com"     "fumixoriny048@seznam.cz"  "loba9@gmail.com"         
##  [40] "hypeqowiqo@gmail.com"     "dubolo@seznam.cz"         "pyhapeqa93@seznam.cz"    
##  [43] "cuwovuci192@seznam.cz"    "wihyzivizugo@gmail.com"   "veduxypi@gmail.com"      
##  [46] "qaqodejaxy801@gmail.com"  "rigubagycaxy74@gmail.com" "nefu@seznam.cz"          
##  [49] "tekijorixisu@gmail.com"   "vydebo95@gmail.com"       "dabe@gmail.com"          
##  [52] "sadiqujake5@gmail.com"    "solusyrewu@gmail.com"     "luzejasy85@seznam.cz"    
##  [55] "voviqiqopesi@gmail.com"   "gureresi@gmail.com"       "qepogijyse@gmail.com"    
##  [58] "nuxi@seznam.cz"           "ditonyvi6@gmail.com"      "xizuxo@seznam.cz"        
##  [61] "hohywyceci@seznam.cz"     "tipera@gmail.com"         "solywukawipi1@gmail.com" 
##  [64] "kunyjagusygi@seznam.cz"   "rovo@gmail.com"           "porexy36@gmail.com"      
##  [67] "qamikibyjena@gmail.com"   "jypokobi@gmail.com"       "mixetokuti967@seznam.cz" 
##  [70] "nafudupe4@gmail.com"      "romi4@gmail.com"          "dymatuve@gmail.com"      
##  [73] "jedeqedytowo14@gmail.com" "fehecule2@seznam.cz"      "wojocigyrine7@gmail.com" 
##  [76] "cahijahiwy24@gmail.com"   "johowara99@seznam.cz"     "vikoguco@seznam.cz"      
##  [79] "kojamepygucu2@seznam.cz"  "xucaloli@seznam.cz"       "dukulaly@seznam.cz"      
##  [82] "hixakavaze631@seznam.cz"  "wali9@gmail.com"          "dizopanohe3@gmail.com"   
##  [85] "rala@gmail.com"           "xojejujeba69@seznam.cz"   "depufityjefa@seznam.cz"  
##  [88] "pusy325@gmail.com"        "fewudodabifo5@gmail.com"  "pixabuqufozu@gmail.com"  
##  [91] "xobywo@gmail.com"         "xybufyqeryge95@gmail.com" "jynaby156@gmail.com"     
##  [94] "jyze@seznam.cz"           "nega@seznam.cz"           "taji829@gmail.com"       
##  [97] "latutyro2@seznam.cz"      "numymahyga@seznam.cz"     "sivupe48@gmail.com"      
## [100] "pefa@gmail.com"
  1. Nagenerujte si spoustu hodnot z normálního rozdělení pomocí rnorm(). Definujte si faktorovou proměnnou o zhruba 15 ekvidistantních intervalech. Napočítejte relativní četnosti a zaneste do tabulky. Gratuluji, právě jste zkonstruovali histogram, odhad hustoty. Porovnejte se skutečnými pravděpodobnostmi (více info o pravděpodobnostních rozděleních zde).

  2. Uvažte příklad ze cvičení 3 a převeďte zjevně kategoriální proměnné na faktory. Nadefinujte si jak pracovní faktorovou proměnnou (krátké jednoduše srozumitelné kategorie), tak i faktorovou proměnnou s hezkými pojmenováními kategorií. Slučte kategorie nadváha a obezita do jedné. Využijte informace o obvodu pasu a definujte si (vlastním laickým náhledem) novou kategoriální proměnnou o třech (slušně) pojmenovaných kategoriích.