7. cvičení – Funkce
Funkce
Cíle 7. cvičení: V rámci 10. kapitoly bude probrána základní deklarace funkce, definice jejich argumentů, návratové hodnoty, lokální a globální proměnné.
Pokud využíváte nějaké skripty pravidelně, rutinně bude výhodné napsat si tyto skripty ve formě funkcí, ke kterým se můžete opakovaně vracet. Funkce tedy slouží jako nadstavba nad jednotlivé skripty, které jsou následně ve funkci sdruženy. Funkce by tedy měla obsahovat zobecněný skript, který vrací jeden výstup. Samotné prostředí R nabízí řadu vestavěných funkcí, z nichž některé (často využívané) byly pro snazší pochopení přeloženy do češtiny.
Deklarace a argumenty funkce
Pro definici funkce budeme využívat příkaz function. Každá funkce napsaná v programu R musí být definována ve tvaru:
nazev_funkce <- function(argumenty_funkce){skript}
Funkci lze tedy chápat jako skript, který vykoná jiný skritp na základě jednoho a více vstupních argumentů a vrátí do konzole výsledek. Funkci budeme vždy volat jako nazev_funkce()
, kde do kulatých závorek vložíme vstupní argumenty, které budou zpracovány skriptem, jenž byl definován ve složených závorkách = těle funkce. Složené závorky lze vynechat, pokud chceme v rámci těla funkce zapsat pouze jediný příkaz. Tento postup však nedoporučujeme - psaní se závorkami dodává funkcina přehlednosti.
Jako jednoduchý příklad uveďme funci, která spočítá průměr z námi zadaných tří čísel:
prumer <- function(a, b, c)
{
(a + b + c) / 3
}
Pokud nyní funkci zavoláme se vstupem a = 1, b = 2, c = 3:
prumer(1, 2, 3)
bude konzole na výstupu vracet hodnotu 3. Správnost výsledku snadno ověříme vestavěnou funkcí R pro výpočet průměru:
mean(c(1, 2, 3))
Pokud neuvádíme při volání funkce názvy argumentů, vyhodnotí je funkce automaticky dle pořadí, v jakém jsme je zadali. Voláním pomocí názvů argumentů se rozumí tato deklarace:
prumer(a = 1, b = 2, c = 3)
Chceme-li nějaké argumenty definovat implicitně a tím zajistit, že funkce může být vypočtena i bez jejich konkrétního zadání, musíme tak učinit již v rámci těla funkce:
prumer <- function(a = 1, b = 2, c)
{
(a + b + c) / 3
}
Naše funkce prumer
se nyní bude dát vyhodnotit i př zadání pouze jednoho argumentu:
prumer(c = 3)
# funkce automaticky za a dosadila hodnotu 1 a za b hodnotu 2
prumer(c = 3, b = 5)
# funkce automaticky za a dosadila hodnotu 1, ostatní argumenty bere dle zadání v závorce, výsledek je (1 + 5 + 3)/3 = 3
Pokud budeme volat definovanou funkci, není nezbytně nutné vypsat celý název argumentů, stačí pouze jejich zkratky. Pro ilustraci si nadefinujeme výpočet BMI:
BMI <- function(hmotnost, vyska_m)
{
print(hmotnost / (vyska_m ^ 2))
}
V následujícím příkladu si ukážeme výsledky, které vrací konzola:
BMI(75, 1.75) # výsledek dle očekávání 24.4898
BMI(1.75, 75) # chybný výsledek - R vyhodnotí argumenty dle pořadí a zamění výšku a váhu
BMI(vys = 1.75, hm = 75) # pokud argumenty zadáme včetně jejich názvu (popřípadně zkrácenin názvu), R vyhodnotí funkci korektně
BMI(vys = 1.75, nost = 75) # chyba - zkrácení názvu musí vycházet ze začátku slova
BMI(75, 1.75, 25) # chyba - nadbytečný počet argumentů
BMI(75, "dva") # chyba - R očekává na vstupu numerický operátor (POZOR - i v případě funkce s implicitními hodnotami, není hodnota jiného datového typu nahrazena)
Funkce lze deklarovat přímo v příkazové řádce, pro jejich tvorbu však doporučujeme využívat skripty, ke kterým se bude moci uživatel vracet opakovaně, případně je předávat jiným uživatelům. Skript lze otevřít jak v konzoli, tak v RStudiu pomocí příkazu File -> New Skript
, případně lze k tvorbě skriptů využít jakýkoliv textový editor - soubory ukládáme s příponou ".R". Načtení skriptového souboru pak provádíme pomocí příkazu source(skript.R)
(pokud nemáme skript umístěn v pracovním adresáři je potřeba zadat do argumentu funkce source celou cestu k souboru). Jakmile máme funkci pomocí source()
načtenu, spustíme ji klasicky zavoláním nazev_funkce(argumenty)
.
Návratové hodnoty funkce
Pokud chceme vypsat výstupní hodnoty funkce, je vhodné využt příkaz return()
, případně print(), které nám vypíší hodnoty do konzole. V následujícím příkladu si definujeme jednoduchou funkci, která nám vrátí součet, rozdíl, podíl a umocnění dvou zadaných čísel, výsledky zobrazíme formou seznamu.
vypocty <- function(x, y)
{
soucet <- x + y
rozdil <- x - y
podil <- x / y
mocnina <- (x ^ y)
return(list(soucet=soucet, rozdil=rozdil, podil=podil, mocnina=mocnina))
}
Pokud si výsledek funkce uložíme do konkrétního objektu, můžeme se na jednotlivé prvky seznamu odkazovat a využít je pro další výpočty:
vysledek <- vypocty (x = 1, y = 2)
vysledek$soucet
8 - vysledek$soucet # vrátí hodnoty 8 - (1 + 2) = 5
Lokální a globální proměnné
Objekty, které definujeme uvnitř funkce, nejsou k dispozici mimo funkci - takovýmto objektům říkáme lokální proměnné. Naopak proměnné definované v pracovním prostředí (workspace, definovány pomocí příkazové řádky) jsou označovány jako globální. Programu R není nutné předem definovat, které proměnné jsou lokální a které globální - při vyhodnocení nejprve projde definici uvnitř funkce (tedy proměnné lokální), pokud proměnnou neobjeví ve funkci, hledá ji v globálním prostředí. Pokud není objekt nalezen ani zde, vrátí konzole chybové hlášení o chybějícím argumentu.
Vrátíme-li se tedy k příkladu ze začátku cvičení, mohli bychom funkci průměr tří čísel definovat také takto:
prumer <- function()
{
(a + b + c) / 3
}
Pokud budeme chtít funkci vyhodnotit, musíme zadat prostor pro argumenty prázdný (vychází z naší definice funkce):
prumer()
Funkce nemá zadány žádné argumenty, R tedy nejprve hledá objekty a, b, c v pracovním prostředí, pokud je nalezne, funkce je vyhodnocena a její hodnota vrácena konzolí. Pokud by objekty nebyly v globálním porstředí nalezeny, vrátila by konzole chybovou hlášku. Pozor - toto tvrzení neplatí pro argumenty funkce - pokud bychom nechali definici funkce v původním rozsahu včetně argumentů, bude je funkce požadovat na vstupu - k hledání v globálním prostředí nedojde. Tuto skutečnost ověřme na příkladu funkce, kteoru jsme si definovali pro výpočet BMI:
hmotnost <- 75
vyska_m <- 1.75
BMI() # chyba - hned první argument, který se snaží funkce hodnotit, není deklarován
BMI(vyska_m, hmotnost) # výsledek dle očekávání 24.4898; R jako vyska_m priradila prvni argument (tj. objekt vyska_m), argument hmotnost byl pak reprezentován objektem hmotnost
BMI(hmotnost, vyska_m) # chybný výsledek - R vyhodnotí argumenty dle pořadí a zamění výšku a váhu bez ohledu na název proměnné v globálním prostředí
Pokud definujeme objekty jen uvnitř funkce (= jako lokální proměnné), nejsou k dispozici mimo funkci. Podíváme-li se tedy na objekty naší funkce vypocty()
, nebudou v pracovním prostředí existovat:
vypocty(2, 3) # funkce vrací objekt typu seznam
soucet # chyba - objekt v pracovním (= globálním) prostředí nenalezen
V rámci funkce je však možné namísto lokálních definovat globální proměnné - pro jejich definici využijeme "<<-":
vypocty <- function(x, y)
{
soucet <<- x + y
rozdil <- x - y
podil <<- x / y
mocnina <- (x ^ y)
return(list(soucet=soucet, rozdil=rozdil, podil=podil, mocnina=mocnina))
}
vypocty(2, 3) # funkce opět vrací objekt typu seznam
soucet # objekt deklarován i mimo funkci, konzole vrací hodnotu 5
rozdil # chyba - objekt v pracovním (= globálním) prostředí nenalezen
Cvičení
Pro cvičení na téma tvorby funkcí se stáhněte soubor kuřáci.csv, ve kterém jsou shrnuty údaje o 75 pacientech s nemalobuněčným karcinomem plic. Představte si situaci, kdy zpracováváte datové soubory pro každé centrum zapojené ve vašem výzkumu zvlášť – víte tedy, že každá vaše datová tabulka bude obsahovat stejné proměnné a bude chtít vždy na datech provést stejné úkony. Pro tento případ se hodí zpracování pomocí skriptů, případně funkcí, které budete moci spouštět opakovaně případně předávat dalším osobám.
1. Napište funkci jmeno_prijmeni
, která zřetězí do jednoho sloupečku údaje ve sloupcích jmeno
a prijmeni
, kde máme vyplněn identifikátor pacienta.
2. Napište funkci vstup
, rozhodne, zda pacienti ve vaší datové tabulce splňují vstupní kritéria pro vaši analýzu (1 přiřaďte pacientům, co kritéria splňují, 0 těm, co do analýzy nebudou zařazeni). Tyto podmínky jsou definovány jako věk 40–69 let při diagnóze (využijte sloupce datum_narozeni
a datum_dg
) a zároveň pacient vykouřil již více než 10 tzv. balíčkoroků (ty jsou definovány jako pocet_cigaret
/20 * roky_koureni
).
3. Vytvořte funkci hemoglobin_kat
, kde shrňte, zda uvedené hodnoty hemoglobinu (sloupec hemoglobin
) jsou v normě / pod normou / nad normou. Norma: muži 132–173 g/l, ženy 117–155 g/l.
4. Navrhněte funkci, která zhodnotí, kolik daný člověk utratil za cigarety. Cenu spočtěte jako součin baličkoroků (viz úkol 2) a ceny za krabičku (cena
). Funkci napište tak, aby (cena
nebyla argumentem funkce, ale R ji hledalo jako globální proměnnou v pracovním prostředí.
5. Pro účely zhodnocení přežití pacientů se často využívá proměnná follow-up, na základě které se hodnotí délka sledování pacientů. Přidejte tuto hodnotu v letech do dat – vypočtete ji jako rozdíl mezi poslední_kontrola
a datum_dg
, pokud nemá pacient vyplněn sloupec poslední_kontrola
, nastavte funkci tak, aby v takovýchto případech jako datum poslední kontroly brala 31. 10. 2018.
6. Uložte si do pracovní složky funkci rozrazeni.txt
, funkc otevřte v programu R a uložte s příponou .R. Funkce pacienty z našeho příkladu rozřadí do dvou ramen studie (léčba vs. kontrola). Funkce má pouze jeden vstupní argument – vaši tabulku s daty. Načtěte funkci v konzole, spusťte ji na vašich datech a výsledek si uložte do proměnné data_new
. V rámci této proměnné sumarizujte počet osob zařazených do jednotlivých ramen studie. Přepište funkci tak, aby nerozřazovala léčbu vs. kontrolu 1:1, ale v jiném poměru, např. 2:1.