3.7 Výběry částí datových struktur

Většina proměnných obsahuje více než jednu hodnotu: vektory obsahují prvky, matice a datasety obsahují řádky a sloupce atd. Někdy je potřeba z těchto hodnot vybrat jen některé. K tomu slouží subsetová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.

Hranaté závorky vrací object stejné třídy, jako je původní objekt. To se vždy nehodí, proto R nabízí i operátor dvojitých hranatých závorek ([[]]), který extrahuje prvky ze seznamů, datasetů a podobných struktur. Podobnou funkci plní i operátor dolar ($).

Subsetování lze použít nejen k získání vybraných prvků z datové struktury, ale také k jejich nahrazení nebo doplnění.

3.7.1 Atomické vektory

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.

# 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"
x[1]  # první prvek
## [1] "a"
x[3]  # třetí prvek
## [1] "c"
x[length(x)]  # poslední prvek
## [1] "l"
x[3:6]  # třetí až šestý prvek včetně
## [1] "c" "d" "e" "f"
x[c(2, 3, 7)]  # druhý, třetí a sedmý prvek
## [1] "b" "c" "g"
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):

x <- c(c = 1, b = 2, a = 3)
x
## c b a 
## 1 2 3
x["a"]  # prvek s názvem a, tj. zde poslední prvek
## a 
## 3
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!

x <- 1:12
x
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12
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:

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
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:

x <- c(1:3, NA, 5:7)
x
## [1]  1  2  3 NA  5  6  7
x[7] <- Inf  # nahrazení poslední hodnoty nekonečnem
x
## [1]   1   2   3  NA   5   6 Inf
x[is.na(x)] <- 0  # nahrazení všech hodnot NA nulou
x
## [1]   1   2   3   0   5   6 Inf
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:

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.

3.7.2 Matice

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:

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
M[2, 3]  # prvek ve druhém řádku a třetím sloupci
## [1] 8
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
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:

M[1:2, ]  # celé první dva řádky
##      [,1] [,2] [,3] [,4]
## [1,]    1    4    7   10
## [2,]    2    5    8   11
M[, c(1, 3)]  # první a třetí sloupec
##      [,1] [,2]
## [1,]    1    7
## [2,]    2    8
## [3,]    3    9
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
M[M[, 1] >= 2, M[1, ] < 6]  # submatice
##      [,1] [,2]
## [1,]    2    5
## [2,]    3    6
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:

M[4]  # vrací 1. prvek ve 2. sloupci, protože je to 4. prvek vektoru
## [1] 4
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í. 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:

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
# 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
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:

o <- sample(ncol(M))  # čísla 1:ncol(M) v náhodném pořadí
o
## [1] 1 2 4 3
M[, o]
##      [,1] [,2] [,3] [,4]
## [1,]    8    2    4    6
## [2,]    5    3    9   12
## [3,]    7   11   10    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.)

M[, 1]
## [1] 8 5 7
M[, 1, drop = FALSE]
##      [,1]
## [1,]    8
## [2,]    5
## [3,]    7

3.7.3 Seznamy

Subsetování pomocí hranatých závorek zachovává mód proměnné. V případě atomických vektorů a matic je to vhodné chování – výsledkem subsetování je atomický vektor nebo matice. V případě seznamů znamená zachování módu proměnné, že výsledkem subsetování je opět seznam, který obsahuje dané prvky, nikoli prvek jako takový:

l <- list(a = 1, b = 1:3, c = "ahoj")
l
## $a
## [1] 1
## 
## $b
## [1] 1 2 3
## 
## $c
## [1] "ahoj"
l[1]
## $a
## [1] 1
is.list(l[1])
## [1] TRUE
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:

l[[2]]
## [1] 1 2 3
is.list(l[[2]])
## [1] FALSE
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:

l[[2]][[3]]  # třetí prvek vektoru, který je druhým prvkem seznamu
## [1] 3
l[[2:3]]     # totéž
## [1] 3
# 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:

l[["b"]]  # prvek se jménem b
## [1] 1 2 3
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:

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:

# l[[4]]  # chyba: Error in l[[4]] : subscript out of bounds
l[["d"]]
## NULL
l$d
## NULL

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.

l <- list(prvni_prvek = 1, druhy_prevk = 2)
l$p
## [1] 1
l[["p"]]
## NULL
l[["p", exact = FALSE]]
## [1] 1

Doporučuji partial matching nikdy nevyužívat – může být zdrojem špatně dohledatelných chyb!

3.7.4 Datasety

Datasety 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í dataset, ve druhém může vrátit dataset (pokud se vybere více sloupců), nebo vektor (pokud se vybere jen jeden sloupec). Dolar vrací jeden sloupec, tj. vektor:

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
d$x      # vektor x
## [1] 1 2 3 4 5 6 7
d[["x"]] # totéž
## [1] 1 2 3 4 5 6 7
d[[1]]   # totéž
## [1] 1 2 3 4 5 6 7
d["x"]   # dataset s jediným sloupcem
##   x
## 1 1
## 2 2
## 3 3
## 4 4
## 5 5
## 6 6
## 7 7
d[1]     # opět dataset s jediným sloupcem
##   x
## 1 1
## 2 2
## 3 3
## 4 4
## 5 5
## 6 6
## 7 7
d[1:2, "x"]              # vektor prvních dvou hodnot z vektoru x
## [1] 1 2
d[1:2, 1]                # totéž
## [1] 1 2
d[1:2, 1, drop = FALSE]  # dataset složený z prvních dvou hodnot vektoru x
##   x
## 1 1
## 2 2
d[1:2, 1:2]              # dataset složený z prvních dvou řádků
##   x y
## 1 1 3
## 2 2 1
d[1:2, c("x", "y")]      # dataset složený z prvních dvou řádků
##   x y
## 1 1 3
## 2 2 1
d[1:2, ]                 # dataset 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:

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
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:

# 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 datasetu a podobných struktur slouží funkce complete.cases(). V případě datasetu vrací vektor logických hodnot, který je TRUE pro každý řádek datasetu, který má všechny hodnoty známé, a FALSE jinak.

complete.cases(d)
## [1]  TRUE  TRUE FALSE  TRUE  TRUE  TRUE FALSE
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 datasetů 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.

Do existujícího datasetu přidáte novou proměnnou (nový sloupec) tak, že do nové proměnné přidáte hodnoty vektoru:

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 datasetu:

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ů datasetu.