<- c("Fred", "Daphne", "Velma", "Shaggy", "Scooby") name
6 Funkce
Jedním z centrálních typů objektů, o kterém jsme zatím nemluvili, jsou funkce. Funkce jsou objekty které nám umožňují manipulovat jinými objekty. Poznat funkci je jednoduché, protože jméno každé z nich je následované závorky ()
. V předchozích kapitolách jsme již funkce dokonce používali, ať už se jednalo o c()
, data.frame()
nebo install.packages()
. Teď se na ně konečně podíváme pořádně.
6.1 Používání funkcí
Používat funkce je jednoduché. Každá funkce obsahuje argumenty, pomocí kterých funkci upřesňujete, co přesně má vykonat. Tyto argumenty si píší právě do závorek za jménem funkce. Funkce, kterou jsme viděli opakovaně, je například naše staré dobré c()
:
V tomto případě jsme použili funkci c()
pro vytvoření nového vektoru. Této funkci jsme zadali pět argumentů, definující z jakých elementů se má nový vektor skládat. Obdobně bychom na náš nový objekt name
mohli použít funkci print()
:
print(name)
[1] "Fred" "Daphne" "Velma" "Shaggy" "Scooby"
V tomto případě jsme funkci print()
specifikovali pouze jeden argument, a to který objekt má vytisknout do konzole.
6.2 Argumenty funkcí
Jak jsme zmínili, každá funkce má argumenty, které ovlivňují její fungování. V předchozích příkladech sloužili argumenty primárně pro určení toho, se kterými daty má funkce pracovat. Většina argumentů ale upravuje primárně to, co má funkce s daty dělat. Pro ukázku si vytvořme nový vektor:
<- c(16, 16, 17, 15, NA) age
Jedná se o numerický vektor, jehož poslední hodnota je neznámá (NA
). Co kdybychom chtěli spočítat průměr těchto hodnot? K tomu poslouží funkce mean()
, pokud bychom ji ale aplikovali na náš vektor, narazili bychom na problém:
mean(age)
[1] NA
R nám říká, že průměr těchto čísel je NA
, tedy neznámý. Proč? Narážíme tu na jistou pedantnost typickou pro R. R nám svým způsobem říká “Alespoň jedno z čísel v tomto vektoru je neznámé a může teoreticky nabývat jakékoliv hodnoty. Proto i výsledný průměr může nabývat jakékoliv hodnoty, a je tedy sám neznámý”. V tom má R jistě pravdu. Co kdybychom se ale spokojili s tím, že budeme neznámé hodnoty ignorovat a spočítat průměr jen pro známá čísla? Přesně k tomu má funkce mean()
argument na.rm
(remove NAs). Tento argument může nabývat dvou hodnot TRUE
a FALSE
. Ve výchozím nastavení je tento argument nastaven na FALSE
, což mi ale můžeme jednoduše změnit:
mean(age, na.rm = TRUE)
[1] 16
A je to! Pomocí argumentu na.rm
jsme změnili fungování funkce mean()
tak, aby ignorovalo neznámé hodnoty.
6.3 Funkce a vnořené objekty
V předchozí kapitole jsme si řekli, že většina dat je uchovávaných v dataframech. Jeden takový dataframe můžeme vytvořit pomocí:
<- data.frame(name = c("Fred", "Daphne", "Velma", "Shaggy", "Scooby"),
gang age = c(16, 16, 15, 17, 3),
is_dog = c(FALSE, FALSE, FALSE, FALSE, TRUE))
Co kdybychom chtěli spočítat počet členů členů Scoobyho gangu? Jako první se nabízí možnost:
length(name) # Error: object 'name' not found
To ovšem nebude fungovat, protože R nemůže najít žádný objekt jménem name
. Tento objekt je totiž vnořený (nested) uvnitř jiného objektu, gang
a R nebude prohledávat všechny existující objekty pokaždé, když mu řekneme, aby aplikovalo některou funkci. Je tedy na nás, abychom R navedli, kde má proměnnou name
hledat.
Toho lze docílit několik způsoby. Tím prvním je pomocí operátoru $
. Tímto operátorem můžeme navigovat vnořenými objekty, například jím můžeme vybrat proměnnou name
v dataframu gang
:
length(gang$name)
[1] 5
R teď ví, že objekt name
by mělo hledat unvitř objektu gang
.
Specifikování vnořených objektu pomocí $
je asi nejpoužívanější způsob pokud pracujeme s dataframy, není ale jediný. Alternativní způsob představuje indexovaní pomocí hranatých závorek []
. Ty lze aplikovat několika způsoby. Prvním z nich je skrze jméno vnořeného objektu:
"name"] gang[
name
1 Fred
2 Daphne
3 Velma
4 Shaggy
5 Scooby
Alternativně můžeme využít pořadí řádků a sloupců v objektu. name
je první proměnnou v dataframu gang
a můžeme ji tedy vybrat následovně:
1] gang[,
[1] "Fred" "Daphne" "Velma" "Shaggy" "Scooby"
Všimněte si, že závorky v tomto případě obsahují čárku ([, 1]
). To proto, že pomocí hranatých závorek můžeme vybírat jak sloupce, tak řádky. Pořadí řádku se z konvence píše na první pozici, sloupce na druhé. Kdybychom se chtěli dozvědět více o Fredovi, mohli bychom použít:
1, ] gang[
name age is_dog
1 Fred 16 FALSE
Oboje možnosti je samozřejmě možné kombinovat. Hodnotu prvního řádku a prvního sloupce bychom získali následovně:
1,1] gang[
[1] "Fred"
Pokud tedy pracujeme s vnořenými objekty, a to budeme téměř neustále, nesmíme R nikdy zapomenout říct, kde má hledat.
6.4 Řetězení funkcí
Všechny příklady, které jsme zatím viděli, aplikovali vždy pouze jednu funkci. Asi ovšem tušíte, že v reálné analýze budeme muset na naše data aplikovat mnohem více funkcí, než se dostaneme ke kýženým výsledkům. To s sebou přináší praktický problém. Jak na sebe efektivně řetězit větší počet funkcí tak, aby byl náš kód stále čitelný? V principu existují tři varianty.
První možnost je aplikovat funkci jednu po druhé a ukládat mezivýsledky do nových objektů:
<- wake_up(me)
me_awake <- wash(me_awake)
me_clean <- eat_breakfest(me_clean)
me_fed <- go_to_work(me_fed) me_working
Tento postup je analogický tomu, co jsme dělali dosud. Aplikujeme funkci a její výsledek uložíme do nového objektu. Jedná se o vcelku přehledný postup, nevýhodou ovšem je, že vytváříme velké množství objektu, které zabírají místo a znepřehledňují naše prostředí.
Alternativně je možné na sebe funkce “nabalovat”:
<- go_to_work(eat_breakfest(wash(wake_up(me)))) me_working
Tímto se vyhneme vytváření nových objektů, výsledný kód ovšem není příliš čitelný. Hlavním problém je, že pokud chceme vědět, co tento kód dělá, je nutné ho číst od středu. Jako první je aplikovaná funkce v “jádru”, tedy wake_up()
, a poté všechny ostatní směrem k okraji. Funkce go_to_work()
je aplikovaná jako poslední a to i přesto, že je na řádku jako první.
Poslední, námi preferovanou, metodu je využívání takzvaných pipes. Používat budeme pipes z balíčku magrittr, který je součástí Tidyverse. Ty vypadají takto: %>%
a aplikuje se následovně:
<- me %>%
me_working wake_up() %>%
wash() %>%
eat_breakfest() %>%
go_to_work()
Pipes (%>%
) vezmou objekt nalevo od nich a vloží ho do funkce napravo. První pipe tedy vezme objekt me
a vloží ho do funkce wake_up()
. Druhá pipe vezme výsledek funkce wake_up()
a vloží ho do funkce wash()
. Takto celý řetězec pokračuje dále až po funkci go_to_work()
. Výsledek celého řetězce je uložen do objektu me_working
tak, jak jsme zvyklý. Protože psát jednotlivé pipes ručně by bylo otravné, existuje pro ně v Rstudiu klávesová zkrátka Shift
+ Ctrl
+ M
(respektive Shift
+ Command
+ M
na MacOS).
Pipes jsou preferovaný způsob řetězení funkcí v Tidyverse a budou využívány ve zbytku této knihy. Jejich hlavní výhodou je, že výsledný kód je dobře čitelný, protože je možné ho číst zleva doprava, tak jak jsme zvyklí u normálního textu. Na druhou stranu, někteří lidé argumentují že takto psaný kód zabírá příliš mnoho místa.
6.5 Dokumentace funkcí
Po všem tom povídání si teď možná říkate, jak si má člověk zapamatovat, co která funkce dělá, nemluvě o tom, jaké má argumenty. Naštěstí pro nás si toho moc nazpaměť pamatovat nemusíme, protože každá funkce má svou vlastní dokumentaci. Ta obsahuje popis funkce, výčet všech jejich argumentů, detaily o jejím fungování a příklady použití. Dokumentaci pro vybranou funkci můžeme zobrazit pomocí funkce help()
, případně ?
:
help(mean)
?mean
Obě výše zmíněné funkce mají stejný výsledek, a to otevření dokumentace pro funkci mean()
. Dokumentace všech funkcí má stejnou strukturu, složenou z následujících součástí.
První sekcí je Description, která obsahuje krátký popis funkce, v tomto případě vysvětlení, že funkce mean()
počítá aritmetický průměr.
V sekci Usage je k vidění výchozí nastavení funkce, vidíme například, že argument trim
má výchozí hodnotu 0 a argument na.rm
je nastavený na FALSE.
Sekce Arguments nepřekvapivě popisuje jednotlivé argumenty, k čemu slouží a jakých hodnot mohou nabývat.
Následuje sekce Value, která popisuje výsledek dané funkce, tedy co dostaname, pokud funkci aplikujeme.
Občas přítomná je také sekce Details, která poskytuje další detaily o fungování funkce. Tato sekce se objevuje hlavně u funkcí pro výpočet statistických modelů a podobně komplikovanějších funkcí.
References je klasickým seznamem literatury. Najdeme zde všechny texty citované v dokumentaci a odkazy na další užitečné práce.
See Also je seznamem příbuzných funkcí, které by uživatele mohli zajímat. Vidíme například, že je nám doporučena funkce weighted.mean()
pro výpočet váženého průměru.
Examples je poslední sekcí dokumentace, která obsahuje ukázky použití funkce v praxi.
6.6 Vytváření vlastních funkcí
Jedním z největších předností R je, že se při naší práci nemusíme spoléhat pouze na funkce, které pro nás připravili jiné lidé, ale můžeme si vytvořit funkce na míru naším potřebám. Vytvoření nové funkce je velmi jednoduché, pomocí funkce function
.
Funkci, kterou základní instalace R překvapivě postrádá, je výpočet počtu chybějících hodnot v proměnné. Ne, že by se jednalo o obtížný úkol. Lze k tomu využít kombinaci dvou funkcí, is.na()
a sum()
.
Funkce is.na()
zkontroluje, jestli každý element vektoru chybějící hodnota a vrátí nám nový logický vektor, který bude mít hodnotu TRUE
v případě chybějících hodnot a hodnotu FALSE
v případě těch platných. Například:
<- c(NA, 16, 17, NA, 3)
age is.na(age)
[1] TRUE FALSE FALSE TRUE FALSE
Jak vidíme, na první a čtvrtý element jsou chybějící hodnoty. Nyní můžeme použít funkci sum()
, který při aplikaci na logický vektor vrátí počet TRUE
hodnot:
sum(is.na(age))
[1] 2
A opravdu, dozvěděli jsme se, že v našem vektoru jsou dvě chybějící hodnoty. Nabízí se ale otázka, jestli by se kombinace funkcí sum(is.na())
nedala nějak zjednodušit. Přeci jen, počítat chybějící hodnoty budeme relativně často, a čím méně závorek v našem kódu, tím menší šance, že některou z nich zapomeneme uzavřít.
Vytvoříme si proto vlastní funkci, která bude počítat chybějící hodnoty za nás. Taková funkce by mohla vypadat třeba takto:
<- function(var) {
count_missings sum(is.na(var))
}
Jako první musíme naší funkci vymyslet jméno. V tomto případě použijeme popisné count_missings
. Novou funkci vytvoříme pomocí funkce function()
. Do kulatých závorek vypíšeme, jaké argumenty by naše nová funkce měla mít. V našem případě bude stačit pouze jediný argument, a to var
. Následují spojené závorky a uvnitř to hlavní, tedy popis toho, co má naše nová funkce dělat. V tomto případě spočítá počet chybějících hodnot. Všimněte si, že se zde znovu objevu argument var
, který jsme definovali v předchozím kroku.
A to je vše. Teď už můžeme používat naší novou funkci a ušetřit si trochu psaní:
count_missings(age)
[1] 2