5.7 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.
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)
expr | min | mean | median | max |
---|---|---|---|---|
vektory | 0.001329 | 0.7941644 | 0.007186 | 74.59567 |
data.frame 1 | 62.955738 | 76.4914530 | 69.643374 | 124.38591 |
data.frame 2 | 43.684080 | 71.7981271 | 68.445594 | 138.33817 |
data.frame 3 | 64.195109 | 81.9319805 | 71.961323 | 174.75257 |
tibble 1 | 128.990932 | 151.7516711 | 142.678711 | 214.45897 |
tibble 2 | 63.691950 | 78.2379830 | 70.619257 | 129.24491 |
tibble 3 | 127.399926 | 157.8523898 | 143.193050 | 251.32895 |
Vysledky jsou dramaticke, jak ukazuje tabulka 5.2. 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 dozvedet vic o efektivnosti kodu v R, doporucuji zejmena Wickham (2014), kap. Performance a Profiling. Uzitecne je take Burns (2011).
References
Burns, Patrick. 2011. "The R Inferno." http://www.burns-stat.com/pages/Tutor/R_inferno.pdf.
Wickham, Hadley. 2014. Advanced R. 1st ed. Boca Raton, Florida, USA: Chapman; Hall/CRC. http://adv-r.had.co.nz/.