5. cvičení

Programování v R, základy dobrého kódování

Cíle 5. cvičení:

Naučit se psát programový kód analýz obsahující výběry prvků v množinách, podmínky, větvení a cykly.


Před samotným výkladem o toku programového kódu se na chvíli pozastavme nad termíny programování a kódování. Přestože postrádáme exaktní definici obou pojmů, obvykle bývá jako programování označována činnost zahrnující širší paletu úkonů - od samotné formulace cílů při vytváření programu, přes utváření jeho logické struktury, její optimalizaci až po samotné kódování - tedy zapsání programového kódu v daném programovacím jazyce - a následné testování programu. Kódováním se pak rozumí vlastně pouze překlad již formulovaných požadavků na program do konkrétního jazyka či jednoduše kódu.

Přestože jazyk R byl od svého počátku vyvíjen zejména jako statistický nástroj, bývá často považován také za plnohodnotný programovací jazyk, který umožňuje konstrukci a spouštění složitých programů (skriptů) sloužících nejen ke statistickému hodnocení vstupních dat. Při vhodném použití lze pomocí jazyka R řešit matematické a simulační úlohy, vytvářet grafy, obrázky, animace nebo třeba vytvářet formuláře či webové stránky.

Pro efektivní využití programovacího jazyka a počítače, na kterém budeme program spouštět, je výhodné si osvojit metody a pravidla správného kódování - v našem případě pak půjde zejména o efektivní práci s datovými vektory, výběry prvků splňujících určitá kritéria, větvení kódu a jeho spouštění v cyklech, které nám umožní zpracovávat data automatizovaně.


Větvení programového kódu

Základní funkcí sdílenou prakticky všemi programovacími jazyky, je funkce if (česky tedy „když“), která umožňuje na základě (ne)splnění zadané podmínky rozvětvení programového kódu na dvě větve. V R je funkce if implementována s jediným argumentem, kterým je podmínka pro větvení programu. Funkce může být buď jednoduchá (pokud je podmínka splněna, provede se příkaz zapsaný za funkcí) nebo s variantou obsahující výraz else, po kterém následuje příkaz prováděný v případě nesplnění podmínky. Dodejme, že podmínka je buď výraz (tzv. logický výraz zapsaný ve formě nerovnice či rovnice s dvojitou značkou ==), který lze vyhodnotit jako logickou hodnotu nebo přímo ona logická hodnota (tedy TRUE nebo FALSE):

if(5>2) print("Pět je větší než dva.")
if(2>5) print("Dva je větší než pět.")
class(2>5)
if(TRUE) print("Pravda.")
if(FALSE) print("Nepravda.")
vyraz<-TRUE
if(vyraz) alarm() if(5-3==2) print("Rovná se.")
if(18==20-2) print("Osmnáct je bez dvou za dvacet.") else ("Osmnáct není bez dvou za dvacet")

Povšimněme si, že všechny uvedené příklady se vměstnají vždy na jeden řádek. V některých případech však za podmínkou může následovat programový blok dlouhý až stovky řádků. V případě, že je zapotřebí po funkci if (a stejně tak po výrazu else) zadat kód na více než jeden řádek, je nutné kód „zabalit“ do složených závorek:

if(pi>0) {
  print("Ludolfovo číslo je kladné.")
  odmocnina.z.pi<-sqrt(pi)
  print(paste("Odmocnina z Ludolfova čísla je",odmocnina.z.pi))
} else {
  print("Ludolfovo číslo je záporné.")
  print("Odmocninu z Ludolfova čísla nelze spočítat.")
}

Naopak v případě, kdy je zapotřebí (z prostorových důvodů) směstnat celý blok příkazů do jediného řádku, lze efektivně využít znak středník:

if(pi>0) print("Ludolfovo číslo je kladné.");odmocnina.z.pi<-sqrt(pi);print(paste("Odmocnina z Ludolfova čísla je",odmocnina.z.pi))

Syntakticky podobné funkci if je použití cyklu s předem daným počtem opakování, implemetovaného pomocí funkce for. Tzv. for cyklus umožňuje provádět opakovaně zadaný blok příkazů např. pro jednotlivé prvky vektoru nebo pro měnící se hodnotu indexové proměnné:

soucet<-0
for(i in 1:10) {
  soucet<-soucet+i
}
print(paste0("Součet čísel od jedné do deseti je ",soucet,"."))

Použití funkce for má širokou škálu uplatnění, jak uvidíme níže. Dříve, než se pustíme do popisu práce s cykly, podívejme se ještě na podobnou funkci while, která umožňuje rovněž provádět opakovaně zadaný blok příkazů, nicméně dopředu nemusí být znám počet opakování. Před každým opakováním se testuje splnění zadané podmínky (obecně proměnné třídy logical) a pokud je její hodnota TRUE, cyklus se provede. Při použití while cyklu je třeba dbát zvýšené opatrnosti - zadání vždy splnitelné podmínky totiž může vést k vytvoření tzv. nekonečného cyklu, který na delší dobu zahltí výpočetní kapacitu počítače (než dojde k přetečení paměti):

soucet<-0
i<-1
while(i<=10) {
  soucet<-soucet+i
  i<-i+1
}
print(paste0("Součet čísel od jedné do deseti je ",soucet,"."))

Využití cyklů je výhodné zejména pro provádění opakovaných výpočtů a generování výstupů. Každé opakování cyklu tak může odpovídat jednomu prvku vektoru nebo jednomu sloupci datové tabulky:

mesta<-c("Jenestří","Doubín","Hřešť","Rozalovice")
for(i in 1:length(mesta)) {
  delka.slova<-nchar(mesta[i])
  print(paste0("Délka slova ",mesta[i]," je ",delka.slova," znaků."))
}

Pomocí for cyklu tak lze jednoduše vygenerovat třeba celou sadu grafů:

mesta<-c("Jenestří","Doubín","Hřešť","Rozalovice")
obyvatelstvo<-data.frame("jenestri"=c(8422,8425,8471,8456),
                         "doubin"=c(332,341,336,341),
                         "hrest"=c(988,1023,1001,982),
                         "rozalovice"=c(516,516,508,521))
rownames(obyvatelstvo)<-paste0("rok",2015:2018)
for(i in 1:length(mesta)) {
  plot(2015:2018,obyvatelstvo[,i],type="l",lwd="3",col="darkred",main=paste("Počet obyvatel v obci ",mesta[i]))
}

V mnoha případech je nicméně využití cyklů zbytečně neelegantní. Jako programovací jazyk zaměřený na statistické zpracování velkých objemů dat totiž R obsahuje řadu možností, jak zpracovávat data ve formě celých vektorů nebo datových tabulek najednou. Ty si nyní krátce představíme.


Práce s prvky ve vektorech

Pokud bude v průběhu programu zapotřebí zjistit, které prvky vektoru splňují určitou podmínku, standardní přístup povede k cyklu, který postupně projde všechny prvky vektoru a pro každý prvek zjistí, zda je podmíénka splněna nebo ne. Protože jde o velmi často řešený problém, je v R pro tyto účely implementována užitečná funkce which, která pomocí jednoduše zadané podmínky vyhodnotí najednou platnost výrazu pro celý vektor.

Uveďme si oba přístupy na jednoudchém příkladu, ze kterého bude zřejmé, o kolik je efektivnější využití funkce which ve srovnání s celým for cyklem. Předpokládejme, že máme vektor tělesných teplot dvanácti pacientů:

teploty<-c(36.2,36.4,37.1,41.1,40.8,36.7,36.8,38.2,36.9,36.5,39.7,36.0)

Standardní přístup, jak získat pořadová čísla pacientů s teplotou vyšší než 37 °C, je dotázat se na každý prvek zvlášť pomocí for cyklu a funkce if:

zvysene<-integer()
for(i in 1:length(teploty)) {
  if(teploty[i]>37.0) {
    zvysene<-c(zvysene,i)
  }
}

Správného výsledku 3, 4 ,5, 8 a 11 se však lze dobrat podstatně jednodušším způsobem:

zvysene<-which(teploty>37.0)

Pokud není předmětem našeho zájmu pouze množina prvků splňujících zadanou podmínku, ale naopak logický vektor ukazující pro každý prvek původního vektoru, zda tuto podmínku splňuje, můžeme zapsat potřebný kód dokonce ještě jednodušším způsobem:

jsou.zvysene<-teploty>37.0

Rozšířením funkce which() je pak funkce match(), která umožňuje zjistit jakou pozici zaujímají prvky prvního vektoru ve druhém vektoru (tj. lze je testovat oproti více hodnotám, než byla jediná u funkce which()). Pokud se daný prvek z prvního vektoru ve druhém vůbec nevyskytuje, bude na jeho pozici hodnota NA. Pokud se daný prvek z prvního vektoru ve druhém vyskytuje opakovaně, vrátí funkce polohu jeho prvního výskytu (zleva).

mesta<-c("Jenestří","Doubín","Hřešť","Rozalovice")
adresy<-c("Adamov","Brno","Cetenov","Doubín","Eš","Frahelž","Golčův Jeníkov","Hřešť","Ivančice","Jenestří")
match(mesta,adresy)
match(adresy,mesta)

Obdobně jako bylo jednoduše možné získat logický vektor s hodnotami TRUE na pozicích odpovídajících hodnotám získaným pomocí funkce which(), lze i funkci match() opsat tímto způsobem. Slouží k tomu funkce is.element(), která vrací logický vektor, délkou odpovídající prvnímu vektoru (argumentu), který pro každý jeho prvek uvádí, zda se tento prvek nachází (na libovolné pozici) ve druhém vektoru (argumentu):

mesta<-c("Jenestří","Doubín","Hřešť","Rozalovice")
adresy<-c("Adamov","Brno","Cetenov","Doubín","Eš","Frahelž","Golčův Jeníkov","Hřešť","Ivančice","Jenestří")
is.element(mesta,adresy)

Také zde existuje zjednodušení využívající tzv. uživatelsky definovaný operátor, „obalený“ značkami procent (tak se vyznačuje množina uživatelsky definovaných operátorů, tj. funkcí o právě dvou argumentech, které si může dodefinovat sám uživatel). V našem případě jde o předdefinovaný operátor %in%, který přesně odpovídá funkci is.element().

mesta<-c("Jenestří","Doubín","Hřešť","Rozalovice")
adresy<-c("Adamov","Brno","Cetenov","Doubín","Eš","Frahelž","Golčův Jeníkov","Hřešť","Ivančice","Jenestří")
mesta %in% adresy

Poslední užitečnou funkcí související se zpracováním celých vektorů a datových tabulek je funkce apply(), která umožňuje aplikovat jinou zvolenou funkci najednou na více řádků nebo sloupců datové tabulky, bez nutnosti využívat for cyklus. Funkce má volitelný počet argumentů, přičemž podstatné jsou první tři: datová tabulka (nebo jiná vícerozměrná struktura), dimenze, podle které se má zadaná funkce aplikovat (1 = řádky, 2 = sloupce) a samotná aplikovaná funkce:

obyvatelstvo<-data.frame("jenestri"=c(8422,8425,8471,8456),
                         "doubin"=c(332,341,336,341),
                         "hrest"=c(988,1023,1001,982),
                         "rozalovice"=c(516,516,508,521))
apply(obyvatelstvo,2,mean)


Cvičení

  1. Načtěte do proměnné pacienti soubor infarkt_myokardu.xlsx, obsahující na svém prvním listu informace o pacientech hospitalizovaných s podezřením na infarkt (fiktivní). Zkontrolujte, že se data správně načetla do datové tabulky.
  2. Vložte pomocí for cyklu do datové tabulky pacienti nový sloupec vekova.kategorie, který bude obsahovat věkové dekády (tj. "0–9" pro věk do 10 let, "10–19" pro věk od 10 do 20 let, "20–29" pro věk od 20 do 30 let atd.
  3. Využijte funkci which() a logické operátory & (logické a/a současně) a | (logické nebo) pro vytvoření nové datové tabulky nadvyber, která bude obsahovat pouze ženy starší 45 let.
  4. Vytvořte datovou tabulku vyber, která bude obsahovat sloupce totožné s datovou tabulkou nadvyber, ale bude mít 0 řádků.
  5. Pomocí while cyklu přidávejte postupně odshora po jednom řádky z tabulky vyber do tabulky vyber tak dlouho, dokud nebude v datové tabulce vyber alespoň 100 pacientek s diabetem, 100 pacientek se zvýšeným krevním tlakem a 100 pacientek se zvýšenou hladinou cholesterolu v krvi.
  6. Nadefinujte proměnnou leciva obsahující vzorce antikoagulantů používaných pro omezení srážlivosti krve pomocí následujícího kódu:

    leciva<-data.frame("lecivo"=c("Warfarin","Heparin","Dabigatran","Aspirin","Clopidogrel","Pradaxa"),
                       "vzorec"=c("C19H16O4","C12H19NO20S3","C34H41N7O5","C9H8O4","C16H16ClNO2S","C34H41N7O5"))

  7. Vytvořte v datové tabulce vyber nový sloupec vzorec a pomocí funkce match() do něj doplňte chcemické vzorce odpovídající užívaným léčivům ve sloupci koagulant. Pokud žádné léčivo užíváno nebylo, ponechte příslušnou buňku prázdnou.
  8. Vytvořte v datové tabulce vyber nový sloupec antikoagulant.2 a pomocí funkce match() do něj doplňte zpětně názvy léčiv odpovídající sumárním vzorcům ve sloupci vzorec.
  9. Zjistěte kolik různých hodnot (mimo prázdného pole) se vyskytuje ve sloupci antikoagulant.2 a diskutujte.
  10. Transformujte v původní datové tabulce pacientisloupec pobyt na číselnou proměnnou. Pacienty zemřelé před operací zanedbejte.
  11. Pomocí funkcí which() a is.na() (resp. její obrácené formy !is.na()) odstraňte z tabulky pacienti pacienty, kteří nemají ve sloupci pobyt číselnou hodnotu.
  12. Spočtěte mediánovou délku hospitalizace přeživších pacientů - nekuřáků.
  13. Pomocí funkcí apply(), unique() a length() zjistěte počty různých hodnot ve všech sloupcích upravené datové tabulky pacienti.
 
Článek ze dne 8. 1. 2019 byl naposledy upraven dne 22. 1. 2019 a zobrazen celkem 427×, naposledy dne 24. 6. 2019 v 23:13.
 
 

Komentáře:

Jméno autora:
Email (nebude zveřejněn):
Komentář:
Sem napiš slovo Adamov:




Stránka:
 
Citace: Kalina, J., Sloupová, K., Vérteši, M., Správným směrem [online]. Jiří Kalina, 2014 [cit. 2019-06-25]
Dostupné z: http://spravnym.smerem.cz/Tema/5.%20cvi%C4%8Den%C3%AD.
 
Desktopová verze | Mobilní verze