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)
<- rep(NA_integer_, 1e7)
x <- rep(NA_real_, 1e7)
y <- data.frame(x = x, y = y)
df <- tibble(x = x, y = y)
dt
<- microbenchmark(
performance "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.000858 | 0.3730667 | 0.0069685 | 36.68847 |
data.frame 1 | 35.121408 | 49.3930537 | 39.3517615 | 97.75422 |
data.frame 2 | 23.603386 | 42.3836958 | 37.2730475 | 112.43231 |
data.frame 3 | 35.897626 | 47.7953549 | 39.8374445 | 104.72969 |
tibble 1 | 35.678292 | 46.7628822 | 39.4573860 | 101.55800 |
tibble 2 | 35.268639 | 45.0353950 | 38.3740045 | 92.29636 |
tibble 3 | 36.240846 | 43.5741577 | 39.4811190 | 91.97515 |
Vysledky jsou dramaticky odlisne, 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).