Rkový skript ke stažení: R
Co si zde představíme?
if
else
,for
, while
cykly,apply
.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
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
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ý."
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
list
em. Tedy na každou položku list
u aplikuje
danou funkci. Zde je seznam základních verzí funkce
apply
:
lapply
- aplikuje funkci na prvky zadaného listu, vrací
jako list,sapply
- aplikuje funkci na jednotlivé prvky
vektoru,vapply
- podobné sapply
, ale vrací předem
určený typ,mapply
- vícerozměrná verze sapply
, která
přijímá více argumentů,tapply
- počítá funkci po stejných hodnotách
kategoriální proměnné.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
Vyřešte si to sami, aniž byste se dívali do řešení (úplně dole vespod skriptu).
abc
, nezapomeňte ošetřit všechny případy.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)\).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])
}
}
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])})})