Zpět na přehled cvičení

Rkový skript ke stažení: R

Co si zde představíme?

Logické operátory

Už jsme se dříve seznámili s logical (boolean) třídou, kterou tvoří dva prvky TRUE a FALSE, zkráceně T a F. Podívejme se tedy detailněji na to, jak v Rku skládat dohromady logické výroky.

Zde jsou základní operátory pro práci s logical:

a = 1; b = 2; c = 1
a == b                                     # rovnost dvou prvků
## [1] FALSE
a != b                                     # nerovnost dvou prvků
## [1] TRUE
!(a == c)                                  # negace výroku pomocí vykřičníku před výrazem
## [1] FALSE
c(a < b, a <= b, a > b, a >= b)            # operátory pro lineární uspořádání
## [1]  TRUE  TRUE FALSE FALSE
c(T&T, T&F, F&T, F&F)                      # operátor & je "a zároveň"="AND"
## [1]  TRUE FALSE FALSE FALSE
c(T|T, T|F, F|T, F|F)                      # operátor | je "nebo"="OR" (nevylučovací)
## [1]  TRUE  TRUE  TRUE FALSE
c(xor(T,T), xor(T,F), xor(F,T), xor(F,F))  # vylučovací ",nebo"="XOR"
## [1] FALSE  TRUE  TRUE FALSE
c(isTRUE(T), isTRUE(1))                    # indikátor booleanu TRUE
## [1]  TRUE FALSE

Výše jsme pracovali jen s čísly, ale to samé lze provádět i se stringy. A jak to dopadne, když se tyto operátory potkají s NA?

x <- c(NA, FALSE, TRUE)
names(x) <- as.character(x) # aby tabulky vzniklé níže měly pojmenované řádky a sloupce
!x
##  <NA> FALSE  TRUE 
##    NA  TRUE FALSE
outer(x, x, `&`)            # AND  tabulka
##        <NA> FALSE  TRUE
## <NA>     NA FALSE    NA
## FALSE FALSE FALSE FALSE
## TRUE     NA FALSE  TRUE
outer(x, x, `|`)            # OR   tabulka
##       <NA> FALSE TRUE
## <NA>    NA    NA TRUE
## FALSE   NA FALSE TRUE
## TRUE  TRUE  TRUE TRUE
outer(x, x, `xor`)          # XOR  tabulka
##       <NA> FALSE  TRUE
## <NA>    NA    NA    NA
## FALSE   NA FALSE  TRUE
## TRUE    NA  TRUE FALSE

Jakými funkcemi se dá vícero logických hodnot upravit na jedinou hodnotu?

x <- rep(T, 10)
all(x)                   # Jsou všechny hodnoty TRUE?
## [1] TRUE
x[10] <- FALSE
all(x)                   # Jediné přítomné FALSE z toho udělá FALSE
## [1] FALSE
x[9] <- NA
all(x)                   # Je-li přítomno jediné FALSE, tak to vrátí FALSE i přes přítomnost NA
## [1] FALSE
x[10] <- NA
all(x)                   # Je-li přítomno NA a jen TRUE, tak je to NA
## [1] NA
all(x, na.rm = T)        # NA hodnoty lze ignorovat, aby nedošlo k neočekávanému NA
## [1] TRUE
x <- rep(F, 10)
any(x)                   # Je aspoň nějaká hodnota TRUE?
## [1] FALSE
x[10] <- T
any(x)                   # Stačí jediné TRUE na TRUE
## [1] TRUE
x[9] <- NA
any(x)                   # i za přítomnosti NA stačí jediné TRUE
## [1] TRUE
x[10] <- NA
any(x)                   # při samém FALSE či NA vrátí NA
## [1] NA
any(x, na.rm = T)        # lze opět ignorovat hodnoty NA
## [1] FALSE

Jak porovnat dva (číselné) objekty stejných rozměrů mezi sebou?

x <- c(1, 3e-5, pi)
y <- c(1, 3e-5, pi)
identical(x, y)          # Jsou objekty x a y totožné? - obecnější funkce
## [1] TRUE
all.equal(x, y)          # Jsou všechny položky ve vektoru shodné?
## [1] TRUE
y[3] <- 22/7
all.equal(x, y)          # Už nejsou stejné, napozorovalo to celkovou absolutní odchylku větší než je tolerance
## [1] "Mean relative difference: 0.0004024994"
all.equal(x, y, tolerance = 1e-3) # při přesnosti na tisíciny jsou již vektory shodné
## [1] TRUE
y[3] <- pi + 1e-7      
isTRUE(all.equal(x, y))  # ještě jsou rozdílné
## [1] FALSE
y[3] <- pi + 1e-8
isTRUE(all.equal(x, y))  # už jsou dle defaultního nastavení shodné
## [1] TRUE
(tolerance = sqrt(.Machine$double.eps)) # defaultní hodnota (viz help(all.equal))
## [1] 1.490116e-08

Vzhledem k tomu, že all.equal vrací buď TRUE nebo string, tak do if se pak dává isTRUE(all.equal(x,y)).

S logickými operátory lze pracovat i po složkách:

x <- c(T, T, F, F)
y <- c(T, F, T, F)
x & y
## [1]  TRUE FALSE FALSE FALSE
x | y
## [1]  TRUE  TRUE  TRUE FALSE
xor(x, y)
## [1] FALSE  TRUE  TRUE FALSE

Pokud spojujeme vícero logických hodnot za sebe, tak se doporučuje používat zdvojený symbol && či ||. Třeba funkce isTRUE(x) není nic jiného než výsledek následujícího výrazu:

is.logical(x) && length(x) == 1 && !is.na(x) && x
## [1] FALSE

Přičemž pořadí členů hraje důležitou roli. Jednotlivé výroky se vyhodnocují zleva doprava a jakmile nějaká podmínka selže, okamžitě vrací FALSE, aniž by kontroloval podmínky zbylé. Takto se dá předejít případných chybovým hláškám, které by se spustily při naprosto nevhodném vstupu.

Ještě tu vzpomeňme funkce typu is.element, is.logical, is.na, is.numeric, is.integer, is.list apod., které nám slouží k identifikaci třídy konkrétního objektu a vrací čistě logickou informaci.

Dále je dobré mít na paměti, že T a F hodnoty lze použít v numerických výpočtech, kde T=1 a F=0. Jsme tak třeba schopni jednoduše napočítávat třeba četnosti výskytu:

x <- c(1, 1, 2, 3, 1, 6, 5, 4)
sum(x == 1)                      # počet jedniček ve vektoru x
## [1] 3
mean(x == 1)                     # relativní četnost
## [1] 0.375

Větvení příkazů pomocí if a else

Pokud, chceme provést nějaký příkaz jen, pokud nastane nějaký jev, tak použijeme if:

hod_kostkou <- sample(1:6, 1)               # podrobněji o generování čísel v jiném cvičení
if(hod_kostkou == 6) print("Padla šestka!")

Pokud je třeba provést vícero příkazů, tak je třeba je obklopit složenými závorkami {}. Pro přehlednost je zvykem tak činit i pro jediný příkaz. Nefunguje zde odsazování pomocí tabulátoru jako v Pythonu. Má-li se v případě nesplnění provést jiný příkaz, tak hned za if přijde sekce else.

hod_kostkou <- sample(1:6, 1)
if(hod_kostkou == 6){
  dalsi_hod <- sample(1:6, 1)
  soucet <- hod_kostkou + dalsi_hod
}else{                                # pro přehlednost nejlépe takto na nové řádky (leda by to bylo krátké)
  soucet <- hod_kostkou
}

Existuje i zkratková funkce, která jen přiřadí konkrétní hodnotu:

soucet <- ifelse(hod_kostkou == 6,             # test
                 hod_kostkou + sample(1:6, 1), # hodnota, pokud projde testem
                 hod_kostkou)                  # hodnota, pokud neprojde testem
hody <- 1:6
ifelse(hody == 6, hody + sample(1:6,1), hody)  # funguje i vektorově po složkách
## [1] 1 2 3 4 5 9

Bohužel neexistuje elif jako v Pythonu, pokud chceme rozdělovat na více větví. Ale máme k dispozici aspoň switch:

hod_kostkou <- sample(1:6, 1)
switch(hod_kostkou,                         # zde očekává character, takže si převede pomocí as.character()
       "1" = "To je pech",                  # proto zde čísla musí být obklopena "" 
       "2" = "Mohlo být hůř",
       "3" = "Nic moc",
       "4" = "Ujde",
       "5" = "Skvělé",
       "6" = "Štěstí přeje připraveným",
       "Podvodník!")                        # poslední argument pro jakoukoliv jinou hodnotu
## [1] "Ujde"
# Nelze jednoduše sdružovat, v případě shodného výsledku je třeba vypsat pro každou možnost zvlášť
switch(hod_kostkou, 
       "1" = "To je málo", "2" = "To je málo", 
       "3" = "Ujde", "4" = "Ujde", 
       "5" = "Super", "6" = "Super",
       "Podvodník!")
## [1] "Ujde"
# Leda by se nejprve transformoval hod_kostkou na něco o 3 hodnotách

Cykly - for, while, repeat

Pro detailní vysvětlení se lze podívat na help(for). Zde si ukážeme typické použití cyklů. Začněme s tím nejčastěji používaným v Rku, což je for. Ten iteruje přes přesně vymezené hodnoty po přesně dané množství iterací. Iterační množinou mohou být čísla, ale klidně i stringy. Zápis je následující:

for(i in 1:5) print(1:i)          # pokud jeden příkaz tak lze takto do řádku
## [1] 1
## [1] 1 2
## [1] 1 2 3
## [1] 1 2 3 4
## [1] 1 2 3 4 5
for(i in c(10, 20, 50, 100)){     # ale raději doporučuji používat tento styl s {} a obsahem na nových řádcích
  hod_i_krat_kostkou <- sample(1:6, size = i, replace = TRUE)
  print(paste("Při", i, "hodech padlo celkem", sum(hod_i_krat_kostkou == 6), "šestek"))
}
## [1] "Při 10 hodech padlo celkem 2 šestek"
## [1] "Při 20 hodech padlo celkem 3 šestek"
## [1] "Při 50 hodech padlo celkem 13 šestek"
## [1] "Při 100 hodech padlo celkem 14 šestek"

Iterační proměnná se sice může během jedné iterace změnit, ale následná iterace se stále bere z dané množiny:

for(i in 1:3){
  print(i)
  i = i+10      # změna iterační proměnné
  print(i)
}
## [1] 1
## [1] 11
## [1] 2
## [1] 12
## [1] 3
## [1] 13

Na druhé straně while cyklus je založen na pravidelné úpravě dané podmínky. Jeho typické použití je v iteračních algoritmech, kde je třeba docílit konkrétní přesnosti.

soucet <- hod <- sample(1:6, 1)
while(hod == 6){                 # podmínka, jejíž proměnná se může měnit
  hod <- sample(1:6)
  soucet <- soucet + hod
}
print(soucet)
## [1] 5

Dejte pozor, aby jste neskončili v nekonečné smyčce. V takovém případě použijte červené tlačítko stop v panelu Console, které je přítomno při dlouhých výpočtech:

soucet <- hod <- sample(1:6, 1)
while(hod <= 6){                 # vždy splnitelná podmínka
  hod <- sample(1:6, 1)
  soucet <- soucet + hod
}

Cyklus repeat nemá jasně daná ukončovací pravidla a je čistě na uživateli, aby tam nějaká definoval. To lze korigovat pomocí příkazu break, který okamžitě ukončuje nejniternější cyklus.

soucet <- hod <- sample(1:6, 1)
repeat{                 
  hod <- sample(1:6, 1)
  soucet <- soucet + hod
  if(hod < 6){
    break
  }
}
soucet
## [1] 2

Další cyklus řídící příkaz je next, který přestane ve výpočtu současné iterace, navýší iterační proměnnou a pokračuje dál další iterací v nejniternějším cyklu.

for(i in 1:6){
  if(i %% 2) next                          # zbytek po dělení 2
  print(paste("Index i =",i,"byl sudý."))
}
## [1] "Index i = 2 byl sudý."
## [1] "Index i = 4 byl sudý."
## [1] "Index i = 6 byl sudý."

Alternativy k for cyklu - apply

Při psaní for cyklu se nám může stát, že jej zapíšeme neefektivně pro výpočty. To se projeví delším výpočetním časem. Při psaní kódu je tedy dobré myslet na efektivnější způsoby zápisu for cyklu, které takovýmto chybám mohou předcházet Tou je použití funkce apply, případně jejích dalších verzí sapply, lapply či tapply.

Funkce apply je určena pro matrix či array objekt. Ukážeme si to na příkladu výpočtu průměru v jednotlivých sloupcích.

X <- matrix(1:1e8, nrow=1e5, ncol = 1e3)
cm_apply <- apply(X, 2, mean)    # 2 značí, která dimenze má být zachována

V tomto případě je rychlejší výpočet pomocí optimalizované funkce colMeans:

cm_colMeans <- colMeans(X)

Ale apply má tu výhodu, že místo funkce mean můžeme použít libovolnou funkci, klidně námi utvořenou.

V případě, že máme vícerozměrný array, tak si můžeme určit, které dimenze zachovat a přes které počítat:

Y <- array(1:2e7, dim = c(2, 1e3, 1e4))
means13 <- apply(Y, c(1,3), mean)
dim(means13)
## [1]     2 10000
means1 <- apply(Y, 1, mean)
length(means1)
## [1] 2

Další výhodou může být jednoduchost zápisu. Dejme tomu, že máme list několika matic a chceme u každého spočítat průměr pro každý sloupec.

matice <- list()
matice[["a"]] <- matrix(1:1000, 10, 100)
matice[["b"]] <- matrix(1:500, 5, 100)
matice[["c"]] <- matrix(1:200, 2, 100)

Pomocí for cyklu bychom to napsali takto:

prumery_for <- list()
for(m in names(matice)){
  prumery_for[[m]] <- numeric(dim(matice[[m]])[2]) # nadefinování vektoru, kam budeme ukládat
  for(i in 1:dim(matice[[m]])[2]){
    prumery_for[[m]][i] <- mean(matice[[m]][,i])
  }
}

Ale pomocí apply a podobných funkcí to lze jednoduše zapsat takto:

prumery_apply <- lapply(matice, function(X){apply(X, 2, mean)}) 
# či dokonce takto
prumery_apply <- lapply(matice, colMeans)

Museli jsme si jen nadefinovat novou funkci (více v pozdějším cvičení), která při dané matici X počítá opět pomocí apply průměr v jednotlivých sloupcích.

Seznámili jsme se zde s verzí lapply, která nepracuje s matrix nebo array, ale pracuje s listem. Tedy na každou položku listu aplikuje danou funkci. Zde je seznam základních verzí funkce apply:

sapply(seq(0, 1, length.out=101), round, digits=1) # další argumenty používané funkce lze doplnit
##   [1] 0.0 0.0 0.0 0.0 0.0 0.0 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2 0.2
##  [27] 0.3 0.3 0.3 0.3 0.3 0.3 0.3 0.3 0.3 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.5 0.5 0.5 0.5 0.5 0.5
##  [53] 0.5 0.5 0.5 0.6 0.6 0.6 0.6 0.6 0.6 0.6 0.6 0.6 0.6 0.7 0.7 0.7 0.7 0.7 0.7 0.7 0.7 0.7 0.7 0.8 0.8 0.8
##  [79] 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.9 0.9 0.9 0.9 0.9 0.9 0.9 0.9 0.9 1.0 1.0 1.0 1.0 1.0 1.0
mapply(rep, 1:4, 4:1)
## [[1]]
## [1] 1 1 1 1
## 
## [[2]]
## [1] 2 2 2
## 
## [[3]]
## [1] 3 3
## 
## [[4]]
## [1] 4
mapply(rep, times = 1:4, x = 4:1)
## [[1]]
## [1] 4
## 
## [[2]]
## [1] 3 3
## 
## [[3]]
## [1] 2 2 2
## 
## [[4]]
## [1] 1 1 1 1
x <- 1:10                    # hodnoty k průměrování
g <- c(1,1,2,2,2,2,2,3,1,3)  # kategorie, klidně zpřeházené
tapply(x, g, mean)           # průměry v jednotlivých kategoriích
## 1 2 3 
## 4 5 9

Úložky na procvičení

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

  1. Naprogramujte si řešení kvadratické rovnice s koeficienty ve vektoru abc, nezapomeňte ošetřit všechny případy.
  2. Naprogramujte si Newton-Raphsonovu metodu pro hledání kořenů polynomiální rovnice dané vektorem a, tedy např. : a[1] + x * a[2] + x^2 * a[3] + x^3 * a[4] = 0. Hint: \(x_{k+1} = x_k - f(x_k) / f'(x_k)\).
  3. Přepište následující for cykly pomocí apply a podobných funkcí:
X <- matrix(1:100, 10, 10)
Y <- matrix(0, 10, 10)
for(i in 1:10){
  for(j in 1:10){
    Y[i,j] <- sum(X[1:i,j])
  }
}
  1. Přepište následující apply formulaci pomocí for cyklů:
X <- list(Alois = rnorm(3), Bozka = rnorm(4), Cyril = rnorm(10)) # náhodné vektory v listu
vysledek <- lapply(X, function(x){sapply(1:length(x), function(y){max(x[1:y])})})