--- title: "Základní datové struktury" author: "Michal Kvasnička" documentclass: article output: html_document: theme: cerulean toc: yes toc_float: yes pdf_document: default fontsize: 10pt classoption: a4paper --- # Základní datové struktury Data, která budeme zkoumat, většinou netvoří izolované hodnoty (jednotlivá izolovaná čísla), nýbrž větší množství hodnot, které mají nějaký vztah. Výsledkem ekonomického experimentu může být např.\ datový soubor, který obsahuje pro každý subjekt experimentu identifikační číslo daného subjektu, jeho treatment, identifikaci skupiny, do které patřil a seznam akcí, které zahrál, a výplat, kterých dosáhl. Takový datový soubor můžeme zorganizovat jako tabulku, ve které řádky odpovídají jednotlivých subjektům a sloupce jednotlivým proměnným. Pokud jsou všechny proměnné číselné, je tabulka matice. Alternativně můžeme uspořádat každou proměnnou zvlášť jako jednotlivé vektory. V\ každém případě však potřebujeme k\ uchování hodnot získaných z\ experimentu určitý typ datové struktury. Základní datové struktury, které nám R nabízí, lze roztřídit podle dvou charakteristik: 1)\ podle jejich dimensionality na jednorozměrné, dvourozměrné a vícerozměrné objekty a 2)\ podle homogenity použitých datových typů na homogenní a heterogenní struktury. Jednotlivé kombinace uvádí tabulka\ \@ref(tab:datastructure-overview). Homogenní struktury mají všechny položky stejného typu, např.\ celá čísla. Mezi homogenní struktury patří zejména atomické vektory a matice. Heterogenní struktury mohou mít jednotlivé položky různých typů, takže mohou najednou obsahovat např.\ reálná čísla i\ řetězce. Mezi nejdůležitější heterogenní struktury patří seznamy a různé typy tabulek, jako jsou tabulky tříd *data.frame* a *tibble*. Jednorozměrné datové struktury mají jen jeden rozměr, délku. Sem patří zejména atomické vektory a seznamy (seznamy jsou neatomické vektory). Dvourozměrné struktury mají dva rozměry, takže tvoří tabulku. Nejdůležitější dvourozměrné struktury jsou homogenní matice a nehomogenní tabulky. Table: (\#tab:datastructure-overview) Význam základních logických operací. dimenze | homogenní | heterogenní ------- | --------------- | ----------- 1 | atomický vektor | seznam 2 | matice | tabulka více | pole | $\strut$ V\ této kapitole se - seznámíte se základními datovými strukturami: atomickými vektory, atomickými maticemi, seznamy a tabulkami třídy *data.frame* a *tibble* - naučíte převádět data z\ jedné datové struktury na jinou - naučíte se získávat podmnožiny (*subsety*) těchto struktur - dozvíte něco o\ tom, jaké struktury volit ## Atomické vektory Nejzákladnější datovou strukturou je v\ R atomický vektor. R\ nemá datovou strukturu pro skalár (jedinou logickou hodnotu, jediné číslo, znak nebo řetězec)\ -- každý skalár je ve skutečnosti atomický vektor s\ jediným prvkem. Atomický vektor je vektor hodnot, jehož všechny prvky mají stejný typ (např.\ celé číslo). Atomické vektory se vytvářejí funkcí `c()` (od "concatenate"), která "slepí" jednotlivé hodnoty dohromady, přičemž provede automatickou konverzi (pokud je potřeba). ```r x <- c(1, 2, 3, 17) print(x) ``` ``` ## [1] 1 2 3 17 ``` Pomocí funkce `c()` je možné "slepit" i\ celé vektory: ```r x1 <- c(1, 2, 3) x2 <- c(4, 5, 6, NA) x <- c(x1, x2) print(x) ``` ``` ## [1] 1 2 3 4 5 6 NA ``` Všechny prvky atomického vektoru musejí mít stejný typ. Pokud tedy při tvorbě atomického vektoru smícháte proměnné různých typů, dojde k\ automatické konverzi: ```r c(TRUE, 1, "ahoj") # výsledek je vektor tří řetězců ``` ``` ## [1] "TRUE" "1" "ahoj" ``` Jednotlivé prvky atomických vektorů mohou mít jména. Jména jsou uložena v\ atributu `names`. Je možné je zadat čtyřmi různými způsoby: přímo ve funkci `c()`, pomocí funkce `attr()`, pomocí speciální funkce `names()` nebo funkce `setNames()`: ```r x <- c(a = 1, "good parameter" = 7, c = 17) x ``` ``` ## a good parameter c ## 1 7 17 ``` ```r attr(x, "names") <- c("A", "Good Parameter", "C") x ``` ``` ## A Good Parameter C ## 1 7 17 ``` ```r names(x) <- c("aa", "bb", "cc") names(x) ``` ``` ## [1] "aa" "bb" "cc" ``` ```r x ``` ``` ## aa bb cc ## 1 7 17 ``` ```r setNames(x, c("A", "B", "C")) ``` ``` ## A B C ## 1 7 17 ``` Pokud mají jednotlivé prvky vektorů přiřazená jména, vypisují se na obrazovku nad vlastní hodnoty. Délku vektoru je možné zjistit pomocí funkce `length()`: ```r length(x) ``` ``` ## [1] 3 ``` Pozor: atomický vektor může mít i\ nulovou délku, pokud neobsahuje žádné prvky. (Podobně i\ další datové struktury mohou mít nulové rozměry, např.\ nulový počet řádků apod.) Prázdný vektor vznikne často tak, že z\ existujícího vektoru vyberete hodnoty pomocí podmínky, kterou žádný prvek vektoru nesplní (jak se vybírá část datové struktury uvidíte později): ```r x <- 1:9 length(x) ``` ``` ## [1] 9 ``` ```r y <- x[x > 10] # vybereme prvky větší než 10, viz dále y ``` ``` ## integer(0) ``` ```r length(y) ``` ``` ## [1] 0 ``` Prázdný vektor je možné vytvořit pomocí konstruktorových funkcí `logical()`, `integer()`, `numeric()`, `character()` apod., které mají jediný parametr, počet prvků. Pokud je zadaný počet prvků nulový, funkce vrátí prázdný vektor. Pokud je počet prvků kladný, vznikne vektor se zadanou délkou. To je užitečné např.\ v\ situaci, kdy si chcete dopředu připravit vektor určité délky, a\ později jej naplnit hodnotami. (Funkce `numeric()` má poněkud nekonzistentní název, protože vytváří vektor typu *double*.) ```r z <- numeric(0) # parametr je délka vektoru z ``` ``` ## numeric(0) ``` ```r z <- numeric(10) # vektor 10 hodnot z ``` ``` ## [1] 0 0 0 0 0 0 0 0 0 0 ``` Některé vektory obsahují předvídatelné sekvence čísel. Pro vytváření takových vektorů existuje operátor dvojtečka\ (`:`) a speciální funkce `seq()` a `rep()` a jejich specializované varianty: ```r 1:10 # vektor celých čísel 1 až 10 ``` ``` ## [1] 1 2 3 4 5 6 7 8 9 10 ``` ```r 10:1 # vektor celých čísel sestupně 10 až 1 ``` ``` ## [1] 10 9 8 7 6 5 4 3 2 1 ``` ```r # sekvence od ... do seq(from = 1, to = 10, by = 3) # s daným krokem ``` ``` ## [1] 1 4 7 10 ``` ```r seq(from = 1, to = 10, length.out = 4) # s danou délkou výsledku ``` ``` ## [1] 1 4 7 10 ``` ```r seq_along(c(1, 3, 17, 31)) # celá čísla od 1 do délky zadaného vektoru ``` ``` ## [1] 1 2 3 4 ``` ```r seq_len(7) # celá čísla od 1 nahoru se zadanou nezápornou délkou ``` ``` ## [1] 1 2 3 4 5 6 7 ``` ```r # opakování hodnot ve vektoru rep(c(1, 3), times = 5) # celý vektor 5 krát ``` ``` ## [1] 1 3 1 3 1 3 1 3 1 3 ``` ```r rep_len(c(1, 3), length.out = 5) # celý vektor do délky 5 ``` ``` ## [1] 1 3 1 3 1 ``` ```r rep(c(1, 3), each = 3) # každý prvek 3 krát ``` ``` ## [1] 1 1 1 3 3 3 ``` ```r rep(1:6, each = 2, times = 3) ``` ``` ## [1] 1 1 2 2 3 3 4 4 5 5 6 6 1 1 2 2 3 3 4 4 5 5 6 6 1 1 2 2 3 3 4 4 5 5 6 6 ``` Složitější varianty použití funkce `rep()` viz dokumentace. Pozor! Konstrukce vektorů pomocí operátoru dvojtečka je někdy nebezpečná. Řekněme, že chcete provést nějakou operaci pro každý prvek vektoru\ `x` a že to chcete udělat pomocí cyklu `for`, viz oddíl\ \@ref(sec:for). Při psaní cyklů se často prochází hodnoty pomocného vektoru `k = 1:length(x)`. Pokud má vektor\ `x` kladnou délku, je vše v\ pořádku. Pokud však vektor\ `x` neobsahuje žádné hodnoty, pak má nulovou délku. Čekali bychom, že pomocný vektor\ `k` bude mít také nulovou délku, takže cyklus neproběhne ani jednou. To však není pravda. Vektor\ `k` je v\ tomto případě zkonstruován jako `1:0`, má tedy hodnotu `c(1, 0)` a délku\ 2! Taková věc je zdrojem špatně dohledatelných chyb. Je lepší použít `k = seq_along(x)`. Ještě lepší je cyklům se vyhýbat. R\ k\ tomu má velmi užitečné funkce typu `map()`, viz kapitola\ \@ref(kap:iterace-nad-vektory). Na obrazovku se vektory vypisují tak, že se jejich jednotlivé hodnoty skládají vedle sebe do řádku. Pokud se všechny hodnoty na řádek nevejdou, začne se vypisovat na dalším řádku. Pokud nejsou jednotlivé prvky vektoru pojmenované, pak je každý řádek uvozen číslem v\ hranatých závorkách. To je index prvního prvku vektoru na daném řádku. Protože jsou prvky vektorů číslované přirozenými čísly (tj.\ první prvek má index\ 1), bude první řádek začínat `[1]`. Pokud další řádek začíná např.\ `[31]`, znamená to, že první číslo na daném řádku je 31.\ prvek daného vektoru atd. ```r 1:100 ``` ``` ## [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ## [19] 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 ## [37] 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 ## [55] 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 ## [73] 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 ## [91] 91 92 93 94 95 96 97 98 99 100 ``` Pokud jsou prvky vektoru pojmenované, pak R nevypisuje na začátek řádku indexy prvního prvku, ale vypisuje nad jednotlivé prvky jejich jména: ```r setNames(1:26, letters) ``` ``` ## a b c d e f g h i j k l m n o p q r s t u v w x y z ## 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ``` Poznámka: I\ když se vektory vypisují na obrazovku po řádcích, neznamená to že jsou v\ R vektory řádkové\ -- nejsou ani řádkové, ani sloupcové (jako je to implicitně třeba v\ Matlabu). Hodnoty se vypisují po řádcích prostě pro úsporu místa. R je vektorizovaný jazyk. To znamená, že veškeré aritmetické a logické operace a většina funkcí, která pracuje s\ vektory, provádí danou operaci na celých vektorech po prvcích. Pokud např.\ vynásobíte dva vektory\ $x$ a $y$, výsledkem bude vektor, jehož prvky budou násobky odpovídajících prvků obou vektorů, takže $z_i = x_i \cdot y_i$: ```r x <- 1:6 y <- 2:7 x * y ``` ``` ## [1] 2 6 12 20 30 42 ``` ```r x ^ 2 ``` ``` ## [1] 1 4 9 16 25 36 ``` Pozor: pokud se délka vektorů liší, pak R automaticky "recykluje" kratší vektor, tj.\ opakuje jeho hodnoty znovu a znovu. Při tom vydá R varování jen v\ případě, že délka delšího vektoru není celočíselným násobkem délky kratšího vektoru: ```r x <- 1:2 y <- 1:6 x + y ``` ``` ## [1] 2 4 4 6 6 8 ``` ```r x * y ``` ``` ## [1] 1 4 3 8 5 12 ``` ```r y <- 1:7 x + y ``` ``` ## Warning in x + y: longer object length is not a multiple of shorter object ## length ``` ``` ## [1] 2 4 4 6 6 8 8 ``` Otestovat, zda je proměnná atomický vektor, není úplně snadné. R\ nabízí dvě potřebné funkce, z\ nichž však žádná sama o\ sobě nestačí. Funkce `is.atomic()` vrátí hodnotu `TRUE`, pokud je daná proměnná atomická (tj.\ všechny její prvky mají stejný datový typ). Tuto hodnotu však nevrací jen pro atomické vektory, ale i\ pro atomické matice, viz oddíl\ \@ref(sec:datastruc-matrices). Funkce `is.vector()` vrací logickou hodnotu `TRUE`, pokud je proměnná vektor; vektory však v\ R nemusejí být nutně atomické; funkce proto vrací `TRUE` i\ pro neatomické vektory (seznamy), viz oddíl\ \@ref(sec:datastruc-lists). Zda proměnná obsahuje atomický vektor proto musíme otestovat spojením těchto funkcí: ```r is.atomic(x) # x je atomická, tj.\ homogenní proměnná ``` ``` ## [1] TRUE ``` ```r is.vector(x) # x je vektor ``` ``` ## [1] TRUE ``` ```r is.atomic(x) & is.vector(x) # x je atomický vektor ``` ``` ## [1] TRUE ``` Někdy je potřeba získat z\ datové struktury jen vybrané prvky: z\ vektoru jednotlivé prvky, z\ matice vybrané řádky nebo sloupce apod. K\ tomu slouží subsetování. Subsetování lze použít nejen k\ získání vybraných prvků z\ datové struktury, ale také k\ jejich nahrazení nebo doplnění. K\ základnímu subsetování slouží hranaté závorky (`[]`). V\ nich se určí indexy prvků, které je třeba vybrat. Prvky mohou být vybrány třemi způsoby: pomocí svých indexů, pomocí svých jmen a nebo pomocí logických hodnot. Ukážeme si to nejprve na atomických vektorech. 1.\ Výběr pomocí číselných indexů. Prvky atomických vektorů jsou číslované přirozenými čísly $1,\ldots,N$, kde $N$ je délka vektoru (tj.\ první prvek vektoru má index\ 1, nikoli\ 0). Při výběru prvků pomocí indexů se vyberou prvky s\ danými indexy (pokud jsou indexy kladné), nebo se vynechají prvky s\ danými indexy (pokud jsou indexy záporné). Indexování pomocí kladných a záporných čísel nelze míchat. Index\ 0 se tiše ignoruje. ```r # vektor letters obsahuje 26 malých písmen anglické abecedy x <- letters[1:12] # prvních dvanáct písmen abecedy x ``` ``` ## [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" ``` ```r x[1] # první prvek ``` ``` ## [1] "a" ``` ```r x[3] # třetí prvek ``` ``` ## [1] "c" ``` ```r x[length(x)] # poslední prvek ``` ``` ## [1] "l" ``` ```r x[3:6] # třetí až šestý prvek včetně ``` ``` ## [1] "c" "d" "e" "f" ``` ```r x[c(2, 3, 7)] # druhý, třetí a sedmý prvek ``` ``` ## [1] "b" "c" "g" ``` ```r x[c(-1, -3)] # vynechají se první a třetí prvek ``` ``` ## [1] "b" "d" "e" "f" "g" "h" "i" "j" "k" "l" ``` 2.\ Výběr pomocí jmen prvků. Pokud mají prvky vektoru jména, je možné vybírat pomocí vektoru jejich jmen (zde samozřejmě nejde vynechávat pomocí znaménka minus, protože R nemá záporné řetězce): ```r x <- c(c = 1, b = 2, a = 3) x ``` ``` ## c b a ## 1 2 3 ``` ```r x["a"] # prvek s názvem a, tj. zde poslední prvek ``` ``` ## a ## 3 ``` ```r x[c("b", "c")] # prvky s názvy b a c ``` ``` ## b c ## 2 1 ``` 3.\ Výběr pomocí logických hodnot. R\ vybere prvky, které jsou indexovány logickou hodnotou `TRUE` a vynechá ostatní. Pozor: pokud je logický vektor kratší než subsetovaný vektor, pak se recykluje! ```r x <- 1:12 x ``` ``` ## [1] 1 2 3 4 5 6 7 8 9 10 11 12 ``` ```r x[c(TRUE, TRUE, FALSE)] ``` ``` ## [1] 1 2 4 5 7 8 10 11 ``` Výběr pomocí logických hodnot je užitečný zejména v\ situaci, kdy chceme vybrat prvky, které splňují nějakou podmínku: ```r x[x > 3 & x < 11] # vybere prvky, které jsou větší než tři a menší než 11 ``` ``` ## [1] 4 5 6 7 8 9 10 ``` ```r x[x < 3 | x > 11] # vybere prvky, které jsou menší než tři nebo větší než 11 ``` ``` ## [1] 1 2 12 ``` Subsetování lze využít k\ nahrazení prvků jednoduše tak, že se do výběru uloží nová hodnota, která nahradí starou: ```r x <- c(1:3, NA, 5:7) x ``` ``` ## [1] 1 2 3 NA 5 6 7 ``` ```r x[7] <- Inf # nahrazení poslední hodnoty nekonečnem x ``` ``` ## [1] 1 2 3 NA 5 6 Inf ``` ```r x[is.na(x)] <- 0 # nahrazení všech hodnot NA nulou x ``` ``` ## [1] 1 2 3 0 5 6 Inf ``` ```r x[length(x) + 1] <- 8 # přidání nové hodnoty za konec vektoru x ``` ``` ## [1] 1 2 3 0 5 6 Inf 8 ``` Pozor: postupné rozšiřování datových struktur vždy o\ několik málo prvků je výpočetně velmi neefektivní, protože R musí (téměř) pokaždé alokovat nové místo v\ paměti, do něj zkopírovat staré hodnoty a na konec přidat nový prvek. Mnohem efektivnější je naráz alokovat velký blok paměti, do něj postupně uložit hodnoty a blok na konci případně zkrátit: ```r x <- numeric(1e6) # alokace prázdného vektoru o milonu prvků x[1] <- 1 # přidání prvků (další řádky vynechány) n <- 7654 # skutečný počet vložených prvků x <- x[1:n] # zkrácení vektoru na potřebnou délku ``` Ekvivalentně je vhodné postupovat v\ případě všech homogenních datových struktur. Pozor: `numeric()` vytvoří vektor samých nul. Možná je lepší použít `rep(NA_real_, 1e6)`, které vytvoří reálný vektor hodnot `NA`. Většina lidí však používá funkce `numeric()`, `character()` apod. V\ R je možné výběry ze všech datových struktur řetězit\ -- následující výběr vybírá z\ výsledku předchozího výběru: ```r v <- 1:10 v[6:10] # 6. až 10. prvek v ``` ``` ## [1] 6 7 8 9 10 ``` ```r v[6:10][2] # druhý prvek z výběru 6. až 10. prvku v ``` ``` ## [1] 7 ``` Do zřetězeného výběru je obvykle možné dosazovat hodnoty: ```r v[6:10][2] <- NA v ``` ``` ## [1] 1 2 3 4 5 6 NA 8 9 10 ``` ## Atomické matice {#sec:datastruc-matrices} Atomická matice je matice (tj.\ dvourozměrná tabulka), jejíž všechny prvky mají stejný datový typ (např.\ celé číslo). Pro datovou analýzu nejsou matice příliš důležité, někdy se však hodí pro rychlou maticovou algebru a také některé funkce vracejí nebo očekávají jako vstup matice. Nejjednodušší způsob, jak vytvořit atomickou matici je pomocí funkce `matrix()`. Prvním parametrem je vektor, který obsahuje data. Další parametry určují počet řádků a počet sloupců matice, způsob, jak budou data do matice skládaná (zda podle řádků či sloupců; implicitně se data do matic skládají po sloupcích) a pojmenování dimenzí matice. Není potřeba zadávat všechny parametry, pokud R dokáže odhadnout hodnotu jednoho parametru z\ hodnot ostatních parametrů. R\ např.\ umí z\ délky zadaného vektoru a zadaného počtu řádků odhadnout počet sloupců. ```r matrix(1:12, nrow = 3) # matice se třemi řádky a čtyřmi sloupci, po sloupcích ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] 1 4 7 10 ## [2,] 2 5 8 11 ## [3,] 3 6 9 12 ``` ```r matrix(1:12, ncol = 4, byrow = TRUE) # stejný rozměr, data po řádcích ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] 1 2 3 4 ## [2,] 5 6 7 8 ## [3,] 9 10 11 12 ``` Při tvorbě matic R recykluje data. To znamená, že pokud je zadaných hodnot méně, než vyžadují rozměry matice, R začne číst datový vektor znovu od začátku. To může být zdrojem nepříjemných chyb. Naštěstí R vypíše varování, ovšem pouze v\ případě, že počet prvků matice není celočíselným násobkem délky zadaného vektoru. ```r matrix(1:9, nrow = 3, ncol = 4) ``` ``` ## Warning in matrix(1:9, nrow = 3, ncol = 4): data length [9] is not a sub- ## multiple or multiple of the number of columns [4] ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] 1 4 7 1 ## [2,] 2 5 8 2 ## [3,] 3 6 9 3 ``` Otestovat, zda je objekt matice, je možné pomocí funkce `is.matrix()`; převést data na matici je možné pomocí konverzní funkce `as.matrix()`. Zjistit rozměry matice je možné pomocí následujících funkcí: `nrow()` vrátí počet řádků matice, `ncol()` vrátí počet sloupců matice, `dim()` vrací vektor s\ počtem řádků a sloupců matice a `length()` vrací počet prvků matice. (Pro vektory vrací funkce `nrow()`, `ncol()` a `dim()` hodnotu `NULL`.) ```r m <- matrix(1:12, nrow = 3) m ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] 1 4 7 10 ## [2,] 2 5 8 11 ## [3,] 3 6 9 12 ``` ```r nrow(m) ``` ``` ## [1] 3 ``` ```r ncol(m) ``` ``` ## [1] 4 ``` ```r dim(m) ``` ``` ## [1] 3 4 ``` ```r length(m) ``` ``` ## [1] 12 ``` Matice a podobné objekty je možné skládat pomocí funkcí `rbind()` a `cbind()`. První (`rbind()` od "row bind") spojuje matice po řádcích (tj.\ skládá je pod sebe), druhá (`cbind()` od "column bind") po sloupcích (tj.\ skládá je vedle sebe): ```r A <- matrix(1:12, nrow = 3) B <- matrix(101:112, nrow = 3) rbind(A, B) ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] 1 4 7 10 ## [2,] 2 5 8 11 ## [3,] 3 6 9 12 ## [4,] 101 104 107 110 ## [5,] 102 105 108 111 ## [6,] 103 106 109 112 ``` ```r cbind(A, B) ``` ``` ## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] ## [1,] 1 4 7 10 101 104 107 110 ## [2,] 2 5 8 11 102 105 108 111 ## [3,] 3 6 9 12 103 106 109 112 ``` Matice mohou mít následující atributy: `dim` je celočíselný vektor rozměrů (viz výše), jména řádků (čte i\ nastavuje se funkcí `rownames()`), jména sloupců (čte i\ nastavuje se funkcí `colnames()`) a jména dimenzí včetně jmen řádků a sloupců (čte i\ nastavuje se funkcí `dimnames()`): ```r rownames(A) <- c("a", "b", "c") colnames(A) <- c("alpha", "beta", "gamma", "delta") A ``` ``` ## alpha beta gamma delta ## a 1 4 7 10 ## b 2 5 8 11 ## c 3 6 9 12 ``` ```r dimnames(A) <- list(id = c("A", "B", "C"), variables = c("Alpha", "Beta", "Gamma", "Delta")) A ``` ``` ## variables ## id Alpha Beta Gamma Delta ## A 1 4 7 10 ## B 2 5 8 11 ## C 3 6 9 12 ``` ```r attributes(A) ``` ``` ## $dim ## [1] 3 4 ## ## $dimnames ## $dimnames$id ## [1] "A" "B" "C" ## ## $dimnames$variables ## [1] "Alpha" "Beta" "Gamma" "Delta" ``` Atomická matice je implementována jako atomický vektor, který má přiřazený atribut `dim`, který je celočíselný vektor délky dva: ```r M <- 1:12 M ``` ``` ## [1] 1 2 3 4 5 6 7 8 9 10 11 12 ``` ```r dim(M) <- c(3, 4) M ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] 1 4 7 10 ## [2,] 2 5 8 11 ## [3,] 3 6 9 12 ``` ```r is.matrix(M) ``` ``` ## [1] TRUE ``` Podobně lze zrušením atributu `dim` převést matici zpět na vektor (matice se vektorizuje po sloupcích). Stejného výsledku jde dosáhnout pomocí funkce `as.vector()`: ```r as.vector(M) ``` ``` ## [1] 1 2 3 4 5 6 7 8 9 10 11 12 ``` ```r dim(M) <- NULL M ``` ``` ## [1] 1 2 3 4 5 6 7 8 9 10 11 12 ``` Protože matice je atomický vektor, který má přiřazený atribut `dim`, funkce `is.atomic()` vrací pro matici hodnotu `TRUE`; funkce `is.vector()` však samozřejmě vrací `FALSE`, protože testuje, zda je v\ proměnné uložen vektor. ```r M <- matrix(1:12, nrow = 3) is.atomic(M) ``` ``` ## [1] TRUE ``` ```r is.vector(M) ``` ``` ## [1] FALSE ``` ### Maticová aritmetika Obyčejné symboly násobení (`*`), dělení (`/`) a umocňování (`^`) pracují "po prvcích". Při násobení se např.\ vynásobí odpovídající prvky matice. Matice tedy musejí mít stejné rozměry (stejný počet řádků a sloupců). ```r A * B ``` ``` ## variables ## id Alpha Beta Gamma Delta ## A 101 416 749 1100 ## B 204 525 864 1221 ## C 309 636 981 1344 ``` ```r A ^ 2 ``` ``` ## variables ## id Alpha Beta Gamma Delta ## A 1 16 49 100 ## B 4 25 64 121 ## C 9 36 81 144 ``` Pro skutečné maticové násobení se používá operátor `%*%`. Inverzní matici vrací funkce `solve()` (obecně tato funkce řeší soustavy lineárních rovnic). K\ transponování matice slouží funkce `t()`. Hlavní diagonálu matice vrací funkce `diag()`. Speciální matice: ```r diag(1, nrow = 3, ncol = 3) # jednotková matice ``` ``` ## [,1] [,2] [,3] ## [1,] 1 0 0 ## [2,] 0 1 0 ## [3,] 0 0 1 ``` ```r matrix(0, nrow = 3, ncol = 3) # nulová matice (využívá recyklace) ``` ``` ## [,1] [,2] [,3] ## [1,] 0 0 0 ## [2,] 0 0 0 ## [3,] 0 0 0 ``` Příklad inverze: ```r M <- matrix(c(1:8, 0), nrow = 3) invM <- solve(M) E <- diag(1, nrow = nrow(M), ncol = ncol(M)) all.equal(M %*% invM, E) ``` ``` ## [1] TRUE ``` ```r all.equal(invM %*% M, E) ``` ``` ## [1] TRUE ``` ### Subsetování matic Subsetování matic je podobné jako u\ atomických vektorů s\ jedním rozdílem: protože má matice řádky a sloupce, je třeba subsetovat pomocí dvou indexů. První index vybírá řádky, druhý sloupce: ```r M <- matrix(1:12, nrow = 3) M ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] 1 4 7 10 ## [2,] 2 5 8 11 ## [3,] 3 6 9 12 ``` ```r M[2, 3] # prvek ve druhém řádku a třetím sloupci ``` ``` ## [1] 8 ``` ```r M[1:2, c(1,4)] # prvky na prvních dvou řádcích a v prvním a čtvrtém sloupci ``` ``` ## [,1] [,2] ## [1,] 1 10 ## [2,] 2 11 ``` ```r M[-1, -1] # matice bez prvního řádku a sloupce ``` ``` ## [,1] [,2] [,3] ## [1,] 5 8 11 ## [2,] 6 9 12 ``` Pokud je jeden z\ indexů prázdný, vybírá celý řádek nebo sloupec: ```r M[1:2, ] # celé první dva řádky ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] 1 4 7 10 ## [2,] 2 5 8 11 ``` ```r M[, c(1, 3)] # první a třetí sloupec ``` ``` ## [,1] [,2] ## [1,] 1 7 ## [2,] 2 8 ## [3,] 3 9 ``` ```r M[M[, 1] >= 2, ] # všechny řádky, ve kterých je prvek v prvním sloupci >= 2 ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] 2 5 8 11 ## [2,] 3 6 9 12 ``` ```r M[M[, 1] >= 2, M[1, ] < 6] # submatice ``` ``` ## [,1] [,2] ## [1,] 2 5 ## [2,] 3 6 ``` ```r M[ , ] # celá matice M ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] 1 4 7 10 ## [2,] 2 5 8 11 ## [3,] 3 6 9 12 ``` Pokud se matice indexuje jen jedním indexem, R\ ji tiše převede na jeden vektor (spojí sloupce matice za sebe) a vybere prvky z\ takto vzniklého vektoru: ```r M[4] # vrací 1. prvek ve 2. sloupci, protože je to 4. prvek vektoru ``` ``` ## [1] 4 ``` ```r M[c(1, 4:7)] ``` ``` ## [1] 1 4 5 6 7 ``` Subsetování může nejen vybírat hodnoty, ale také měnit jejich pořadí. Zadáním indexů můžeme např.\ otočit pořadí sloupců matice: ```r M <- matrix(c(8, 5, 7, 2, 3, 11, 6, 12, 1, 4, 9, 10), nrow = 3) M ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] 8 2 6 4 ## [2,] 5 3 12 9 ## [3,] 7 11 1 10 ``` ```r M[, 4:1] ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] 4 6 2 8 ## [2,] 9 12 3 5 ## [3,] 10 1 11 7 ``` Stejným způsobem můžeme dosáhnout i\ zajímavějších efektů: např.\ seřadit řádky nebo sloupce podle hodnot vybraného vektoru nebo je náhodně permutovat. Funkce `order()` vrací indexy uspořádané podle velikosti původního vektoru. Funkce např.\ umožňuje setřídit hodnoty všech sloupců matice podle jednoho sloupce: ```r # indexy prvků 1. sloupce matice M seřazené podle velikosti prvků, # tj. na 1. místě je 2. prvek původního vektoru (5), pak 3. prvek (7) atd. order(M[, 1]) ``` ``` ## [1] 2 3 1 ``` ```r M[order(M[, 1]), ] # řádky matice seřazené podle prvního sloupce ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] 5 3 12 9 ## [2,] 7 11 1 10 ## [3,] 8 2 6 4 ``` Funkce `sample()` náhodně permutuje zadaná čísla. Lze jí tak mimo jiné využít k\ náhodné permutaci sloupců matice: ```r o <- sample(ncol(M)) # čísla 1:ncol(M) v náhodném pořadí o ``` ``` ## [1] 4 1 2 3 ``` ```r M[, o] ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] 4 8 2 6 ## [2,] 9 5 3 12 ## [3,] 10 7 11 1 ``` Subsetování se vždy snaží snížit rozměry matice\ -- pokud počet řádků nebo sloupců klesne na\ 1, matice se změní ve vektor. Pokud tomu chceme zabránit, je třeba přidat parametr `drop = FALSE`. (Nemělo by vám být divné, že je možné hranatým závorkám přidávat parametry\ -- jako vše v\ R je i\ použití hranatých závorek volání funkce\ -- a\ funkce mohou mít parametry.) ```r M[, 1] ``` ``` ## [1] 8 5 7 ``` ```r M[, 1, drop = FALSE] ``` ``` ## [,1] ## [1,] 8 ## [2,] 5 ## [3,] 7 ``` ## Neatomické vektory (seznamy) {#sec:datastruc-lists} Neatomické vektory (častěji nazývané seznamy) jsou vektory, jejichž jednotlivé prvky mohou mít různé datové typy, třeba i\ jiné seznamy. Seznamy jsou někdy užitečné, protože umožňují v\ jedné proměnné skladovat různé typy dat. Jejich hlavní význam však spočívá v\ tom, že se používají jako základ pro tvorbu většiny objektů v\ systému `S3`, viz\ kapitola\ \@ref(kap:objekty). Seznamy se vytvářejí pomocí funkce `list()`: ```r l <- list(1L, 11, 1:3, "ahoj", list(1, 1:3, "ahoj")) l ``` ``` ## [[1]] ## [1] 1 ## ## [[2]] ## [1] 11 ## ## [[3]] ## [1] 1 2 3 ## ## [[4]] ## [1] "ahoj" ## ## [[5]] ## [[5]][[1]] ## [1] 1 ## ## [[5]][[2]] ## [1] 1 2 3 ## ## [[5]][[3]] ## [1] "ahoj" ``` Stejně jako atomické vektory, i\ seznamy mohou mít atribut `names`, tj.\ jednotlivé prvky seznamu mohou mít svá jména. Ta se přiřazují stejně jako v\ případě atomických vektorů: ```r l <- list(a = 1, b = "ahoj", c = 1:3, d = list(1:3, "ahoj")) names(l) ``` ``` ## [1] "a" "b" "c" "d" ``` ```r l ``` ``` ## $a ## [1] 1 ## ## $b ## [1] "ahoj" ## ## $c ## [1] 1 2 3 ## ## $d ## $d[[1]] ## [1] 1 2 3 ## ## $d[[2]] ## [1] "ahoj" ``` Délku seznamu zjistíme pomocí funkce `length()`: ```r length(l) ``` ``` ## [1] 4 ``` K\ otestování, zda je proměnná seznam, slouží funkce `is.list()`. Funkce `is.vector()` vrací hodnotu `TRUE` jak pro atomické vektory, tak i\ pro seznamy. ```r is.list(l) ``` ``` ## [1] TRUE ``` ```r is.vector(l) ``` ``` ## [1] TRUE ``` Strukturu seznamu je možné přehledně zobrazit pomocí funkce `str()`. Podobný výsledek dostanete v\ RStudiu tak, že v\ záložce `Environment` kliknete na trojúhelníček v\ kolečku vedle jména seznamu. Musíte však být v\ módu List. ```r str(l) ``` ``` ## List of 4 ## $ a: num 1 ## $ b: chr "ahoj" ## $ c: int [1:3] 1 2 3 ## $ d:List of 2 ## ..$ : int [1:3] 1 2 3 ## ..$ : chr "ahoj" ``` Subsetování seznamů je poněkud složitější, než je tomu u\ atomických proměnných. Subsetování pomocí hranatých závorek zachovává mód proměnné. To znamená, že použití hranatých závorek na seznam vrací opět seznam. Pokud chceme získat přímo prvek uložený v seznamu, musíme použít dvojité hranaté závorky (`[[]]`). Podobnou funkci plní i\ operátor dolar (`$`). Subsetování seznamu pomocí hranatých závorek vrací prvky opět zabalené do seznamu: ```r l <- list(a = 1, b = 1:3, c = "ahoj") l ``` ``` ## $a ## [1] 1 ## ## $b ## [1] 1 2 3 ## ## $c ## [1] "ahoj" ``` ```r l[1] ``` ``` ## $a ## [1] 1 ``` ```r is.list(l[1]) ``` ``` ## [1] TRUE ``` ```r l[1:2] ``` ``` ## $a ## [1] 1 ## ## $b ## [1] 1 2 3 ``` Pokud chceme získat vlastní prvek seznamu, musíme použít dvojité hranaté závorky. Dvojité hranaté závorky "vybalí" daný prvek ze seznamu ven: ```r l[[2]] ``` ``` ## [1] 1 2 3 ``` ```r is.list(l[[2]]) ``` ``` ## [1] FALSE ``` ```r is.numeric(l[[2]]) ``` ``` ## [1] TRUE ``` Syntaxe dvojitých hranatých závorek je poněkud nečekaná. Pokud je argumentem vektor, nevrací dvojité hranaté závorky vektor hodnot (to ani nejde, protože výsledkem by musel být opět seznam), nýbrž se vektor přeloží na rekurentní volání dvojitých hranatých závorek: ```r l[[2]][[3]] # třetí prvek vektoru, který je druhým prvkem seznamu ``` ``` ## [1] 3 ``` ```r l[[2:3]] # totéž ``` ``` ## [1] 3 ``` ```r # protože druhým prvkem seznamu je zde atomický vektor, mohou být druhé závorky jednoduché: l[[2]][3] ``` ``` ## [1] 3 ``` Pokud jsou prvky seznamu pojmenované, nabízí R zkratku ke dvojitým hranatým závorkám: operátor dolar (`$`): `l[["b"]]` je totéž jako `l$b`: ```r l[["b"]] # prvek se jménem b ``` ``` ## [1] 1 2 3 ``` ```r l$b # totéž (uvozovky se zde neuvádějí) ``` ``` ## [1] 1 2 3 ``` Použití dolaru od dvojitých závorek v\ jednom ohledu liší: pokud máme jméno prvku, který chceme získat uloženo v\ proměnné, je třeba použít hranaté závorky\ -- operátor dolar zde nelze použít: ```r element <- "c" # element není v uvozovkách, protože vybíráme hodnotu, která je v něm uložená: l[[element]] ``` ``` ## [1] "ahoj" ``` Pokud indexujeme jménem prvek seznamu, který v\ seznamu chybí, dostaneme hodnotu `NULL`. Pokud jej však indexujeme číselným indexem, dostaneme chybu: ```r l[["d"]] ``` ``` ## NULL ``` ```r l$d ``` ``` ## NULL ``` ```r l[[4]] # chybný řádek ``` ``` ## Error in l[[4]]: subscript out of bounds ``` Seznamy umožňují používat i\ jen části jmen prvků, pokud jsou určeny jednoznačně (tomu se říká "partial matching"). S\ dolarem partial matching zapnutý vždy; s\ dvojitými hranatými závorkami jen v\ případě, že o\ to požádáte parametrem `exact = FALSE`. ```r l <- list(prvni_prvek = 1, druhy_prevk = 2) l$p ``` ``` ## [1] 1 ``` ```r l[["p"]] ``` ``` ## NULL ``` ```r l[["p", exact = FALSE]] ``` ``` ## [1] 1 ``` Doporučuji partial matching nikdy nevyužívat\ -- může být zdrojem špatně dohledatelných chyb! ## Tabulky třídy *data.frame* Pro analýzu dat jsou nejdůležitější datovou strukturou tabulky. Každý sloupec tabulky může obsahovat proměnné jiného typu, v\ rámci sloupce však musí být typ stejný. Tím se tabulky liší od atomických matic, které musí mít všechny prvky stejného typu. Obvyklé použití tabulek je takové, že řádky tabulky představují jednotlivá pozorování a sloupce jednotlivé proměnné. R\ má několik implementací tabulek. Základní třída tabulek, které se budeme věnovat v\ tomto oddíle, se nazývá *data.frame*. Technicky je implementovaná jako seznamy atomických vektorů o\ stejné délce, které jsou spojené vedle sebe. V\ příštím oddíle se podíváme na poněkud příjemnější variantu tabulek třídy *tibble*. Tabulky třídy *data.frame* se tvoří pomocí funkce `data.frame()`. Řekněme, že chceme zaznamenat údaje o\ subjektech, které se zúčastnily nějakého experimentu. Pro každý subjekt pozorujeme jeho identifikační číslo id, výšku a váhu. Pokud máme čtyři subjekty, můžeme vytvořit tabulku např.\ takto: ```r experiment <- data.frame(id = c(1, 2, 3, 41), vyska = c(158, 174, 167, 203), vaha = c(51, 110, 68, 97)) experiment ``` ``` ## id vyska vaha ## 1 1 158 51 ## 2 2 174 110 ## 3 3 167 68 ## 4 41 203 97 ``` Při zadávání vektorů do tabulek můžeme zadat jejich jména, která pak R vypíše. R\ samo přidá jména řádků (automaticky jim dá přirozená čísla od 1 do počtu proměnných). I\ při konstrukci tabulek R recykluje proměnné, pokud není zadáno dost hodnot. Pokud jsou všechny naše subjekty muži, stačí zadat tuto hodnotu jen jednou\ -- R\ ji zrecykluje. ```r experiment <- data.frame(id = c(1, 2, 3, 41), gender = "muž", vyska = c(158, 174, 167, 203), vaha = c(51, 110, 68, 97), zdravy = c(TRUE, TRUE, FALSE, TRUE)) experiment ``` ``` ## id gender vyska vaha zdravy ## 1 1 muž 158 51 TRUE ## 2 2 muž 174 110 TRUE ## 3 3 muž 167 68 FALSE ## 4 41 muž 203 97 TRUE ``` Poznámka: Starší verze\ R při zadání dat do tabulek pomocí funkce `data.frame()` automaticky převedly všechny řetězce na faktory, viz\ oddíl\ \@ref(sec:factors). Této konverzi šlo zabránit nastavením parametru `stringsAsFactors = FALSE`. Od verze R\ 4.0 má parametr `stringsAsFactors` hodnotu `FALSE` implicitně. Pokud tedy chceme v\ novějším\ R převést řetězce na faktory, musíme nastavit parametr `stringsAsFactors` na hodnotu `TRUE` nebo je převést pomocí funkce `factor()`, což je zřejmě rozumnější, viz\ oddíl\ \@ref(sec:factors). Někdy se hodí vytvořit tabulku, která obsahuje všechny možné kombinace hodnot nějakého vektoru. K\ tomu slouží funkce `expand.grid()`: ```r expand.grid(x = 1:3, y = factor(c("male", "female")), z = c(TRUE, FALSE)) ``` ``` ## x y z ## 1 1 male TRUE ## 2 2 male TRUE ## 3 3 male TRUE ## 4 1 female TRUE ## 5 2 female TRUE ## 6 3 female TRUE ## 7 1 male FALSE ## 8 2 male FALSE ## 9 3 male FALSE ## 10 1 female FALSE ## 11 2 female FALSE ## 12 3 female FALSE ``` Počet řádků tabulky zjistíme pomocí funkce `nrow()`, počet sloupců funkcí `ncol()` nebo `length()`; funguje i\ funkce `dim()`: ```r nrow(experiment) # počet řádků ``` ``` ## [1] 4 ``` ```r ncol(experiment) # počet sloupců ``` ``` ## [1] 5 ``` ```r length(experiment) # počet sloupců ``` ``` ## [1] 5 ``` ```r dim(experiment) # vektor počtu řádků a sloupců ``` ``` ## [1] 4 5 ``` Tabulky mají standardně tři atributy: `class` (jméno třídy\ -- data set je totiž objekt), `names` obsahuje jména sloupců (tj.\ jednotlivých proměnných) a `row.names` obsahuje jména jednotlivých řádků (tj.\ pozorování, implicitně mají hodnoty 1, 2 atd.). ```r attributes(experiment) ``` ``` ## $names ## [1] "id" "gender" "vyska" "vaha" "zdravy" ## ## $class ## [1] "data.frame" ## ## $row.names ## [1] 1 2 3 4 ``` Jména řádků můžete zjistit i změnit pomocí funkcí `rownames()` a `row.names()`, jména sloupců pomocí funkcí `colnames()` nebo `names()`: ```r colnames(experiment) <- c("id", "sex", "height", "weight", "healthy") experiment ``` ``` ## id sex height weight healthy ## 1 1 muž 158 51 TRUE ## 2 2 muž 174 110 TRUE ## 3 3 muž 167 68 FALSE ## 4 41 muž 203 97 TRUE ``` Jména řádků vypadají na první pohled jako dobrý způsob, jak uložit nějakou identifikaci pozorování, např.\ id subjektu v\ experimentu. Nedělejte to! Veškeré informace o\ pozorování ukládejte přímo do tabulky. Je to filosoficky správnější a i\ praktičtější: některé funkce, které se používají ke zpracování tabulek, jména řádků odstraní. Stejně tak některé formáty, do kterých se data ukládají, jména řádků nepodporují, takže byste přišli o\ důležité informace. Naproti tomu jména sloupců (tj.\ proměnných) jsou bezpečná. Pozor: R vám dovolí změnit i\ třídu objektu tím, že přepíšete atribut `class` (buď pomocí funkce `attr()` nebo funkce `class()`). Pak se však budou pro daný objekt volat jiné funkce (metody) a výsledek může být podivný. Nedělejte to, pokud nevíte, co děláte. Někdy je užitečné moci převést tabulku na matici a matici na tabulku. K\ převodu tabulky na matici slouží funkce `as.matrix()` a `data.matrix()`. První převede všechny sloupce tabulky automatickou konverzí na stejný typ, a\ pak na matici. Druhá převede logické hodnoty a faktory na celá čísla, řetězce na faktory a ty na celá čísla. Pokud jsou po této konverzi všechny sloupce typu *integer*, má výsledná matice tento typ, jinak má typ *double*. Při automatické konverzi můžeme skončit s\ řetězci, což nemusí být žádoucí; s\ explicitní konverzí na reálná čísla můžeme řetězce ztratit a faktory mohou být zavádějící (faktory se převedou na čísla jako při konverzi na celé číslo). ```r as.matrix(experiment) # použije automatickou konverzi na stejný typ ``` ``` ## id sex height weight healthy ## [1,] " 1" "muž" "158" " 51" "TRUE" ## [2,] " 2" "muž" "174" "110" "TRUE" ## [3,] " 3" "muž" "167" " 68" "FALSE" ## [4,] "41" "muž" "203" " 97" "TRUE" ``` ```r data.matrix(experiment) # použije explicitní konverzi na reálná čísla ``` ``` ## id sex height weight healthy ## [1,] 1 1 158 51 1 ## [2,] 2 1 174 110 1 ## [3,] 3 1 167 68 0 ## [4,] 41 1 203 97 1 ``` Matici lze převést na tabulku pomocí funkcí `as.data.frame()` i `data.frame()`. Pokud má matice pojmenované sloupce, jejich jména jsou v\ tabulce zachována; v\ opačném případě je R samo pojmenuje `V1`, `V2` atd nebo `X1`, `X2` atd. ```r M <- matrix(1:12, nrow = 3) as.data.frame(M) ``` ``` ## V1 V2 V3 V4 ## 1 1 4 7 10 ## 2 2 5 8 11 ## 3 3 6 9 12 ``` ```r data.frame(M) ``` ``` ## X1 X2 X3 X4 ## 1 1 4 7 10 ## 2 2 5 8 11 ## 3 3 6 9 12 ``` ```r colnames(M) <- c("a", "b", "c", "d") as.data.frame(M) ``` ``` ## a b c d ## 1 1 4 7 10 ## 2 2 5 8 11 ## 3 3 6 9 12 ``` ```r data.frame(M) ``` ``` ## a b c d ## 1 1 4 7 10 ## 2 2 5 8 11 ## 3 3 6 9 12 ``` ### Subsetování tabulek třídy *data.frame* Tabulky jsou "kříženec" mezi seznamy a maticemi, takže je na ně možné je subsetovat jako matice i\ jako seznamy. Pokud použijete jeden index, pak je indexujete jako seznamy, pokud dva indexy, pak je indexujete jako matice. V\ prvním případě tedy `[` vrátí tabulku, ve druhém může vrátit tabulku (pokud se vybere více sloupců), nebo vektor (pokud se vybere jen jeden sloupec). Dolar i\ dvojité hranaté závorky vrací jeden sloupec, tj.\ vektor: ```r d <- data.frame(x = 1:7, y = c(3, 1, NA, 7, 5, 12, NA)) d ``` ``` ## x y ## 1 1 3 ## 2 2 1 ## 3 3 NA ## 4 4 7 ## 5 5 5 ## 6 6 12 ## 7 7 NA ``` ```r d$x # vektor x ``` ``` ## [1] 1 2 3 4 5 6 7 ``` ```r d[["x"]] # totéž ``` ``` ## [1] 1 2 3 4 5 6 7 ``` ```r d[[1]] # totéž ``` ``` ## [1] 1 2 3 4 5 6 7 ``` ```r d["x"] # tabulka s jediným sloupcem ``` ``` ## x ## 1 1 ## 2 2 ## 3 3 ## 4 4 ## 5 5 ## 6 6 ## 7 7 ``` ```r d[1] # opět tabulka s jediným sloupcem ``` ``` ## x ## 1 1 ## 2 2 ## 3 3 ## 4 4 ## 5 5 ## 6 6 ## 7 7 ``` ```r d[1:2, "x"] # vektor prvních dvou hodnot z vektoru x ``` ``` ## [1] 1 2 ``` ```r d[1:2, 1] # totéž ``` ``` ## [1] 1 2 ``` ```r d[1:2, 1, drop = FALSE] # tabulka složená z prvních dvou hodnot vektoru x ``` ``` ## x ## 1 1 ## 2 2 ``` ```r d[1:2, 1:2] # tabulka složená z prvních dvou řádků ``` ``` ## x y ## 1 1 3 ## 2 2 1 ``` ```r d[1:2, c("x", "y")] # tabulka složená z prvních dvou řádků ``` ``` ## x y ## 1 1 3 ## 2 2 1 ``` ```r d[1:2, ] # tabulka složená z prvních dvou řádků ``` ``` ## x y ## 1 1 3 ## 2 2 1 ``` Samozřejmě je možné použít i\ indexování pomocí logických hodnot: ```r d[d[, "y"] < 7, ] # výběr řádků, kde je hodnota y menší než 7 ``` ``` ## x y ## 1 1 3 ## 2 2 1 ## NA NA NA ## 5 5 5 ## NA.1 NA NA ``` ```r d[d$y < 7 , ] # totéž ``` ``` ## x y ## 1 1 3 ## 2 2 1 ## NA NA NA ## 5 5 5 ## NA.1 NA NA ``` Výběr zachová i řádky, kde je hodnota $y$ `NA`. To jde vyřešit např.\ takto: ```r # vybíráme pouze prvky, kde y zároveň není NA a zároveň je menší než 7 nebo d[!is.na(d$y) & d$y < 7, ] ``` ``` ## x y ## 1 1 3 ## 2 2 1 ## 5 5 5 ``` K\ vyřazení neúplných hodnot z\ tabulky a podobných struktur slouží funkce `complete.cases()`. V\ případě tabulky vrací vektor logických hodnot, který je `TRUE` pro každý řádek tabulky, který má všechny hodnoty známé, a\ `FALSE` jinak. ```r complete.cases(d) ``` ``` ## [1] TRUE TRUE FALSE TRUE TRUE TRUE FALSE ``` ```r d[complete.cases(d), ] ``` ``` ## x y ## 1 1 3 ## 2 2 1 ## 4 4 7 ## 5 5 5 ## 6 6 12 ``` Pro složitější výběry z\ tabulek existuje funkce `subset()`. Té však nebudeme věnovat pozornost, protože se později naučíte mnohem příjemnější a rychlejší funkce implementované v\ balíku **dplyr**, viz\ kapitola\ \@ref(kap:dplyr). Do existující tabulky přidáte novou proměnnou (nový sloupec) tak, že do nové proměnné přidáte hodnoty vektoru: ```r d$z <- letters[1:nrow(d)] d ``` ``` ## x y z ## 1 1 3 a ## 2 2 1 b ## 3 3 NA c ## 4 4 7 d ## 5 5 5 e ## 6 6 12 f ## 7 7 NA g ``` Nová proměnná se přidá jako poslední sloupec. Pokud do existující proměnné přiřadíte hodnotu `NULL`, vyřadíte tím proměnnou z\ tabulky: ```r d$z <- NULL d ``` ``` ## x y ## 1 1 3 ## 2 2 1 ## 3 3 NA ## 4 4 7 ## 5 5 5 ## 6 6 12 ## 7 7 NA ``` Jiná možnost, jak vynechat proměnnou nebo změnit jejich pořadí, je využít subsetování sloupců tabulky. Subsetování jde použít i\ ke změně pořadí řádků nebo sloupců. Řekněme, že chceme řádky tabulky\ `d` seřadit podle proměnné\ "y" a zároveň vyměnit pořadí sloupců. To můžeme udělat např.\ takto: ```r d[order(d$y), 2:1] ``` ``` ## y x ## 2 1 2 ## 1 3 1 ## 5 5 5 ## 4 7 4 ## 6 12 6 ## 3 NA 3 ## 7 NA 7 ``` Hodnoty `NA` skončí implicitně na konci (jde změnit ve funkci `order()`). ## Tabulky třídy *tibble* Kromě tabulek třídy *data.frame* existuje v\ R ještě několik dalších typů tabulek. Nejpohodlnější z\ nich je třída *tibble*, která je součástí **tidyverse**, skupiny balíků určených pro datovou analýzu, kterými se budeme zabývat v\ pozdějších částech tohoto textu. Tato třída tabulek má některé velmi příjemné vlastnosti, pro které je programování s\ tabulkami třídy *tibble* pohodlnější než programování s\ tabulkami třídy *data.frame*. Tabulku třídy *tibble* vytvoříte pomocí funkce `tibble()`: ```r library(tibble) ds <- tibble(x = 1:1e6, y = 2 * x, zed = x / 3 + 1.5 * y - 7) ds ``` ``` ## # A tibble: 1,000,000 × 3 ## x y zed ## ## 1 1 2 -3.67 ## 2 2 4 -0.333 ## 3 3 6 3 ## 4 4 8 6.33 ## 5 5 10 9.67 ## 6 6 12 13 ## 7 7 14 16.3 ## 8 8 16 19.7 ## 9 9 18 23 ## 10 10 20 26.3 ## # … with 999,990 more rows ``` Pokud potřebujete vytvořit tabulku třídy *tibble* ručně, existuje i\ příjemná funkce `tribble()`, která umožňuje zadávat data po řádcích: ```r tribble( ~name, ~weight, ~height, "Adam", 68, 193, "Bětka", 55, 163, "Cyril", 103, 159 ) ``` ``` ## # A tibble: 3 × 3 ## name weight height ## ## 1 Adam 68 193 ## 2 Bětka 55 163 ## 3 Cyril 103 159 ``` Ke konverzi jiných tříd na *tibble* slouží funkce `as_tibble()`. Vytvoření *tibble* se od vytvoření *data.frame* v\ několika ohledech liší: *tibble* 1. nemění "nepovolená" jména sloupců na povolená nahrazením divných znaků tečkami, 1. vyhodnocuje své argumenty postupně, takže můžete později zadaný argument použít při tvorbě dříve zadaného argumentu (jako v\ příkladu výše), 1. podporuje jen omezenou recyklaci: všechny zadané vektory musejí mít buď stejnou délku, nebo délku\ 1, 1. nepoužívá jména řádků (která jsou ostatně nebezpečná k\ uchovávání dat) a 1. převod na *tibble* pomocí `as_tibble()` je rychlejší než převod na *data.frame* pomocí `as.data.frame()`. Liší se také to, jak *tibble* vypisuje svou hodnotu do konzole. Oproti *data.frame* zobrazí *tibble* navíc rozměr tabulky a typ jednotlivých proměnných. Naopak vypíše jen prvních deset řádků a jen takový počet sloupců, které se vejdou na obrazovku. Počet vypsaných řádků je možné ovlivnit ve funkci `print()` pomocí parametru\ `n`; počet sloupců pomocí parametru\ `width`, kde `width` je maximální počet znaků, které může *tibble* při tisku použít: ```r print(ds, n = 5) ``` ``` ## # A tibble: 1,000,000 × 3 ## x y zed ## ## 1 1 2 -3.67 ## 2 2 4 -0.333 ## 3 3 6 3 ## 4 4 8 6.33 ## 5 5 10 9.67 ## # … with 999,995 more rows ``` Hlavní rozdíl mezi *tibble* a *data.frame* se však týká subsetování: *tibble* má konzistentnější chování a (na rozdíl od *data.frame*) vrací vždy stejnou datovou strukturu, což je výhodné zejména při programování. *tibble* nikdy nezahazuje zbytečné rozměry a ani je nepřidává. To znamená, že `[]` vždy vrací *tibble*, zatímco `[[]]` a `$` vždy vrací vektor. Navíc *tibble* nikdy nepodporuje partial matching: ```r ds <- ds[1:6, ] # omezíme ds na prvních 6 řádků ds[, 1] ``` ``` ## # A tibble: 6 × 1 ## x ## ## 1 1 ## 2 2 ## 3 3 ## 4 4 ## 5 5 ## 6 6 ``` ```r ds[[1]] ``` ``` ## [1] 1 2 3 4 5 6 ``` ```r ds$z ``` ``` ## Warning: Unknown or uninitialised column: `z`. ``` ``` ## NULL ``` Tato příjemná konzistence má však i\ svá nebezpečí. Technicky je *tibble* (stejně jako *data.frame*) seznamem vektorů. Přestože jsme zatím vždy uvažovali atomické vektory, ve skutečnosti mohou být sloupci *tibble* i\ seznamy. To znamená, že následují kód ošklivě selže, protože do proměnné vloží celou tabulku: ```r ds <- tibble(a = 1:3, b = 11:13) ds$c <- ds[, "a"] # vybere se tibble s jedním sloupcem a vloží do sloupce b ds ``` ``` ## # A tibble: 3 × 3 ## a b c$a ## ## 1 1 11 1 ## 2 2 12 2 ## 3 3 13 3 ``` ```r ds$c ``` ``` ## # A tibble: 3 × 1 ## a ## ## 1 1 ## 2 2 ## 3 3 ``` ```r class(ds$c) ``` ``` ## [1] "tbl_df" "tbl" "data.frame" ``` Správný postup je opět použít operátor, který z\ dat vybere jeden sloupec: ```r ds$d <- ds$a ds$e <- ds[["a"]] ds ``` ``` ## # A tibble: 3 × 5 ## a b c$a d e ## ## 1 1 11 1 1 1 ## 2 2 12 2 2 2 ## 3 3 13 3 3 3 ``` ```r class(ds$d) ``` ``` ## [1] "integer" ``` ```r class(ds$e) ``` ``` ## [1] "integer" ``` Možnost využívající dvojité hranaté závorky se hodí zejména v\ případě, kdy máte jméno sloupce uložené v\ nějaké proměnné, takže je nemůžete zapsat přímo: ```r name <- "a" ds$f <- ds[[name]] ds ``` ``` ## # A tibble: 3 × 6 ## a b c$a d e f ## ## 1 1 11 1 1 1 1 ## 2 2 12 2 2 2 2 ## 3 3 13 3 3 3 3 ``` Někdy se nehodí pracovat s\ *tibble* (např.\ proto, že některé funkce očekávají jiný výsledek subsetování). V\ takovém případě můžete *tibble* převést na *data.frame* pomocí konverzní funkce `as.data.frame()`: ```r as.data.frame(ds) ``` ``` ## Warning in format.data.frame(if (omit) x[seq_len(n0), , drop = FALSE] else x, : ## corrupt data frame: columns will be truncated or padded with NAs ``` ``` ## a b c d e f ## 1 1 11 # A tibble: 3 × 1 1 1 1 ## 2 2 12 a 2 2 2 ## 3 3 13 3 3 3 ``` ## Operátor trubka (`|>` a `%>%`) {#sec:pipes} Při datové analýze se často hodí používat operátor "trubka". R\ má dnes dvě základní verze tohoto operátoru: operátor\ `|>`, který je součástí základního\ R (od verze\ 4.1) a operátor\ `%>%` definovaný v\ balíku **magrittr** (načtou jej však i\ některé další balíky, jako např.\ **dplyr**). RStudio zavádí pro "trubku" speciální klávesovou zkratku `Ctrl-Shift-M`. V\ menu `Tools`$\rightarrow$`Global Options...`$\rightarrow$`Code`$\rightarrow$`Editing` si můžete vybrat, zda se použije nativní verze, nebo verze z\ balíku **magrittr**. Trubka umožňuje zapsat výraz se složitě zanořenými funkcemi poněkud čitelnějším způsobem. Řekněme, že chceme na proměnnou `x` aplikovat funkci\ `f()` a na výsledek funkci\ `g()`. Standardním způsobem bychom to zapsali takto: ```r g(f(x)) ``` Operátor trubka umožní výraz přepsat do čitelnější podoby: ```r library(magrittr) x |> f() |> g() # nebo x %>% f() %>% g() ``` Tato syntaxe lépe vystihuje to, co chceme provést. Doslova říká: vezmi proměnnou `x` a vlož ji jako první argument do funkce\ `f()`. Výsledek vlož jako první argument do funkce\ `g()`. Výrazy `x |> f()` a `x %>% f()` jsou tedy ekvivalentní výrazu `f(x)`. Funkce `f()` a `g()` samozřejmě mohou obsahovat i\ další parametry. Ty se zařadí za proměnnou\ `x`, takže `x |> mean(na.rm = TRUE)` je ekvivalentní výrazu `mean(x, na.rm = TRUE)`; stejně tak výraz s\ `%>%`. Mnoho funkcí, se kterými budeme pracovat, je přizpůsobeno používání trubek, takže data do nich vstupují jako první argument. Některé funkce však berou data na jiné pozici. V\ takovém případě není možné použít základní operátor\ `|>`, který dosadí výraz na levé straně vždy na první pozici funkce na pravé straně. Verze\ `%>%` z\ balíku **magrittr** umožňuje toto omezení obejít pomocí speciální proměnné tečka (`.`). Do té trubka vloží vstupní data. Díky tomu je možné zapsat výraz `f(y, x)` jako `x %>% f(y, .)` a `f(y, z = x)` jako `x %>% f(y, z = .)`. Pozor: Pokud je speciální proměnná tečka použita ve funkci přímo, pak trubka nevloží proměnnou\ `x` na první místo ve funkci (jak jsme to viděli v\ předchozích případech). Pokud je však tečka použita jako součást nějakého výrazu, pak trubka proměnnou\ `x` na první místo ve funkci vloží! To znamená, že `x %>% f(y = nrow(.), z = ncol(.))` je ekvivalentní s\ `f(x, y = nrow(x), z = ncol(x))`. Pokud tomu chcete zabránit, musíte funkci\ `f()` uzavřít do bloku. Takže `f(y = nrow(x), z = ncol(x))` musíme zapsat pomocí trubky jako `x %>% {f(y = nrow(.), z = ncol(.))}`.^[Nativní trubka\ `|>` je méně flexibilní, je však rychlejší. Ve většině případů na tom nezáleží, protože volání trubek trvá v\ řádu nano až mikrosekund. Pokud však opakovaně simulujete velké množství vzorků dat, může být i\ takový drobný rozdíl užitečný.] Podívejme se na typický příklad použití trubek. Řekněme, že máme tabulku\ `df` (zde si ji nasimulujeme) a chceme pro každou unikátní hodnotu identifikátoru\ `x` spočítat průměrné hodnoty proměnných\ `y` a `z`. To můžeme udělat např.\ takto: ```r library(tibble) library(dplyr) df <- tribble( ~x, ~y, ~z, 1, 1, 1, 1, 2, 3, 2, 3, 5, 2, 4, 6, 3, 5, 7 ) df |> group_by(x) |> summarize(my = mean(y), mz = mean(z)) ``` ``` ## # A tibble: 3 × 3 ## x my mz ## ## 1 1 1.5 2 ## 2 2 3.5 5.5 ## 3 3 5 7 ``` Výraz s\ použitím trubky\ `%>%` dá stejný výsledek: ```r df %>% group_by(x) %>% summarize(my = mean(y), mz = mean(z)) ``` Detaily fungování tohoto kódu si vysvětlíme v\ kapitole \@ref(kap:dplyr). Nyní nás zajímá jen fungování trubek: proměnná\ `df` se nejdříve vloží do funkce `group_by()` a vyhodnotí se jako `group_by(df, x)`. Výsledek této operace se pak vloží na první místo do funkce\ `summarize()` a vyhodnotí se jako `summarize(., my = mean(y), mz = mean(z))`, kde tečka označuje výsledek předchozího výpočtu, tj.\ funkce\ `group_by()`. Balík **magrittr** definuje i\ některé další operátory. Seznámit se s\ nimi můžete na webu balíku http://magrittr.tidyverse.org/ a v\ kapitole "Pipes" ve\ @WickhamRfDS dostupné na http://r4ds.had.co.nz/pipes.html. Doporučuji však omezit se více méně jen na základní operátory\ `|>` a `%>%`. ## Poznámka k\ doplňování hodnot do tabulek Stejně jako v\ případě ostatních základních datových struktur v\ R, je i\ v\ případě tabulek možné přiřadit novou hodnotu do výběru. To se hodí např.\ v\ situaci, kdy potřebujete nějakou hodnotu opravit nebo aktualizovat. Často se také hodí ukládat vypočtené nebo stahované hodnoty do tabulky. V\ tomto posledním případě je však potřeba jisté opatrnosti, protože ukládání do výběru tabulky je výpočetně extrémně nákladná operace (kvůli nákladům na vyhledání dané hodnoty). V\ případě malých tabulek to není problém, v\ případě tabulek o\ milionech řádků to už však může být problematické. Ukažme si to na příkladu (nenechte se vyvést z\ míry tím, že nebudete rozumět všem detailům; většině porozumíte později v\ tomto textu). Nejdříve vytvoříme dva vektory o\ délce 10\ milionů hodnot, první celočíselný a druhý reálný. Vektory naplníme chybějícími hodnotami\ `NA`. Vytvoříme i\ dvě tabulky, které naplníme těmito vektory. První tabulka bude třídy *data.frame*, druhá třídy *tibble*. Náš test spočívá v\ tom, že do každého vektoru budeme chtít vložit na stejnou pozici jedničku. Funkce `microbenchmark()` ze stejnojmenného balíku nám změří, jak dlouho bude tato operace trvat. ```r library(microbenchmark) library(dplyr) library(tibble) x <- rep(NA_integer_, 1e7) y <- rep(NA_real_, 1e7) df <- data.frame(x = x, y = y) dt <- tibble(x = x, y = y) performance <- microbenchmark( "vektory" = {x[1000] <- 1L; y[1000] <- 1}, "data.frame 1" = {df[1000, "x"] <- 1L; df[1000, "y"] <- 1}, "data.frame 2" = {df$x[1000] <- 1L; df$y[1000] <- 1}, "data.frame 3" = df[1000, ] <- data.frame(x = 1L, y = 1), "tibble 1" = {dt[1000, "x"] <- 1L; dt[1000, "y"] <- 1}, "tibble 2" = {dt$x[1000] <- 1L; dt$y[1000] <- 1}, "tibble 3" = dt[1000, ] <- tibble(x = 1L, y = 1), unit = "ms" ) %>% summary() %>% select(expr, min, mean, median, max) ``` Table: (\#tab:performance-test-inkluze)Čas potřebný na vložení dvou hodnot do vektorů a tabulek v milisekundách. |expr | min| mean| median| max| |:------------|---------:|---------:|----------:|---------:| |vektory | 0.001045| 0.562759| 0.0077225| 55.48476| |data.frame 1 | 50.166440| 74.245804| 60.0957815| 151.36829| |data.frame 2 | 33.378585| 63.037425| 51.8705345| 178.47397| |data.frame 3 | 50.575516| 74.089759| 58.5210485| 139.47674| |tibble 1 | 50.762607| 72.825996| 59.3345290| 157.76705| |tibble 2 | 50.211753| 69.835946| 57.5166385| 141.41219| |tibble 3 | 51.302001| 71.805813| 59.4620765| 140.16878| Výsledky jsou dramaticky odlišné, jak ukazuje tabulka\ \@ref(tab:performance-test-inkluze). Vložení dvou hodnot do tabulek trvá (na mém poměrně výkonném počítači) v\ průměru několik desítek milisekund, zatímco vložení do vektoru trvá na stejném počítači tisíciny milisekund (pokud se díváme na mediány). Praktický výsledek je zřejmý: pokud vkládáte do tabulky jednotlivé hodnoty, nevadí to, pokud je tabulka malá nebo vkládáte jen velmi málo hodnot. V\ opačném případě se výrazně vyplatí pracovat s\ vektory. Jednou jsem potřeboval zjistit vzdálenosti vzdušnou čarou mezi asi 5\ 000 čerpacími stanicemi. Bylo tedy potřeba spočítat a uložit asi 12\ milionů vzdáleností (vzdálenost vzdušnou čarou je symetrická). Vlastní výpočet vzdálenosti je extrémně rychlý. Přesto však celý výpočet neskončil ani za čtyři dny\ -- spočítalo se asi jen 1.5\ milionu hodnot. Na vině bylo to, že jsem doplňoval hodnoty přímo do tabulky. Když jsem kód přepsal tak, aby pracoval s\ vektory, které jsem spojil do tabulky až nakonec, výpočet proběhl zhruba za půl hodiny. Všimněte si také toho, že jsem vektory a tabulky celé předalokoval. Kdybychom chtěli datové struktury zvětšovat postupně přidáváním dalších a dalších hodnot nebo řádků, byl by výpočet ještě pomalejší, protože by R muselo neustále alokovat nové bloky paměti a do nich nejdříve překopírovat stará data, a\ teprve potom přidat nové hodnoty. To by se opakovalo při každém zvětšení vektoru nebo tabulky. Pokud se chcete dozvědět víc o\ efektivnosti kódu v\ R, doporučuji zejména @WickhamAdvR, kap.\ Performance a Profiling. Užitečné je také @RInferno. ## Volba datové struktury Vstupní data pro jakoukoli analýzu budou mít ve většině případů formát tabulky. Naproti tomu si strukturu dat, která vzniknou transformacemi původních dat, můžete zvolit sami. Tuto strukturu byste si měli dopředu pořádně rozmyslet. Vhodně zvolená struktura vám umožní s\ daty pracovat jednoduše; špatně zvolená struktura může v\ následné práci dělat problémy. Ve většině případů doporučuji používat pro uschování jakýchkoli dat tabulky (nejlépe třídy *tibble*). Oproti jiným strukturám mají několik výhod: 1)\ snadno se ukládají, čtou a převádí do jiného software, 2)\ snadno se z\ nich dělají výběry, 3)\ snadno se transformují a 4)\ snadno se vizualizují, ať už jako tabulky nebo v\ grafech. R\ nabízí mnoho balíků pro transformace tabulek (zejména **tidyr** a **dplyr**) a pro jejich vizualizaci (zejména **ggplot2**); o\ těchto balících bude řeč později. Práce s\ ostatními datovými strukturami je mnohem méně standardizovaná, takže si víc kódu budete muset napsat sami. Použití jiné datové struktury k\ úschově dat má smysl pouze ve speciálních situacích: 1)\ pokud jinou strukturu (typicky vektory nebo matice) vyžadují použité funkce jako své vstupy, 2)\ když potřebujete rychlou maticovou aritmetiku a 3)\ když jsou složitější objekty výsledkem výpočtů (např.\ modelové objekty v\ ekonometrii). Pro dočasnou úschovu nehomogenních dat se hodí i\ seznamy, které typicky vznikají iteracemi nad vektory. Přesto vám výrazně doporučuji, abyste si pokaždé, když budete chtít zvolit jinou datovou strukturu než tabulku, tuto volbu raději několikrát promysleli.