10.5 Zabezpečení iterací proti chybám

Pokud jedna z iterací ve funkci map() a spol. skončí chybou, skončí chybou celé volání této funkce. Pak může být obtížné zjistit, který prvek vektoru chybu způsobil. Jedním z přínosů balíku purrr je to, že zavádí několik speciálních funkcí, které umožňují tento problém vyřešit. Všechny tyto funkce fungují tak, že transformují funkci .f ještě před tím, než vstoupí do map() – na vstupu vezmou funkci a vrací její zabezpečenou verzi odolnou vůči selhání.

První z těchto funkcí je safely(.f, otherwise = NULL, quiet = TRUE). Tato funkce bere na vstupu iterovanou funkci a vrací její modifikovanou verzi, která nikdy neskončí chybou a která vrací seznam dvou prvků: výsledku a chybového objektu. Pokud původní funkce proběhla, má chybová hlášení hodnotu NULL, pokud skončila chybou, má hodnotu NULL výsledek. Protože funkce vrací seznam, je možné ji použít pouze ve funkci map(), ne v jejích zjednodušujících variantách jako je map_lgl() apod.

Ukážeme si použití této funkce na příkladu. Máme seznam v, který obsahuj většinou čísla, mezi která je však přimíchaný jeden řetězec. Pro každý prvek v chceme spočítat vektor jeho logaritmů (pomocí funkce map() přesto, že funkce log() je vektorizovaná). Přímé volání funkce log() skončí chybou:

v <- list(1, 2, "a", 5)
map(v, log)
## Error in .Primitive("log")(x, base): non-numeric argument to mathematical function

Pokud funkci log() “obalíme” funkcí safely(), výpočet proběhne až do konce a výsledkem bude struktura popsaná výše. Všimněte si, že v 1., 2. a 4. prvku struktury má chybová složka hodnotu NULL. Ve 3. prvku, který zhavaroval, obsahuje chybová složka seznam, který obsahuje objekt třídy error. Ruční prohlídka našeho výsledku by nám umožnila zjistit, že je to 3. prvek vektoru v, který způsobuje chyby:

result <- map(v, safely(log))
str(result)  # struktura výsledku
## List of 4
##  $ :List of 2
##   ..$ result: num 0
##   ..$ error : NULL
##  $ :List of 2
##   ..$ result: num 0.693
##   ..$ error : NULL
##  $ :List of 2
##   ..$ result: NULL
##   ..$ error :List of 2
##   .. ..$ message: chr "non-numeric argument to mathematical function"
##   .. ..$ call   : language .Primitive("log")(x, base)
##   .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
##  $ :List of 2
##   ..$ result: num 1.61
##   ..$ error : NULL

Ruční hledání chyb je však možné jen v případě, že zpracovávaná data jsou velmi malá. Balík purrr naštěstí umožňuje proces hledání chyby zautomatizovat. Stačí na výsledek předchozího výpočtu použít funkci transpose(), která změní seznam párů na pár seznamů. Výraz transpose(result) tedy vrátí seznam dvou prvků: první obsahuje všechny výsledky a druhý všechna chybová hlášení (oboje uložené jako seznamy):

transpose(result)
## $result
## $result[[1]]
## [1] 0
## 
## $result[[2]]
## [1] 0.6931472
## 
## $result[[3]]
## NULL
## 
## $result[[4]]
## [1] 1.609438
## 
## 
## $error
## $error[[1]]
## NULL
## 
## $error[[2]]
## NULL
## 
## $error[[3]]
## <simpleError in .Primitive("log")(x, base): non-numeric argument to mathematical function>
## 
## $error[[4]]
## NULL

To nám umožní najít problematické prvky vektoru v např. tak, že vybereme pouze část chybových hlášení a z ní sestavíme logický vektor, který bude mít hodnotu TRUE tam, kde hodnota chyby není NULL, tj. volání funkce selhalo. Logický vektor pak můžeme použít k nalezení indexů prvků vektoru, kde k chybě došlo (pomocí funkce which()), nebo k vypsání hodnot, které chybu způsobily:

bugs <- transpose(result)$error  # transpozice a výběr chybové složky
bugs <- !map_lgl(bugs, is.null)  # TRUE, kde error není NULL, tj. kde je chyba
which(bugs)  # index prvků v, kde nastala chyba (jako vektor)
## [1] 3
x[bugs]  # hodnoty prvků v, kde nastala chyba (jako seznam)
## [[1]]
## NULL

Pokud nás nezajímají chyby, ale pouze ty výsledky, které se skutečně spočítaly, můžeme použít funkci possibly(.f, otherwise, quiet = TRUE). Tato funkce zabezpečí funkci .f tak, že nikdy nezhavaruje. Pokud není schopná spočítat výsledek, vrátí místo něj hodnotu zadanou v parametru otherwise (pokud není zadána, funkce zhavaruje). Díky tomu funkce possibly() vrací jen vlastní výsledky, takže může být využitá i ve zjednodušujících variantách map():

map_dbl(v, possibly(log, otherwise = NA_real_))  # chybná hodnota je nahrazena NA
## [1] 0.0000000 0.6931472        NA 1.6094379

Pokud byste místo zachytávání chyb potřebovali zachytit zprávy a varování, která vrací iterovaná funkce, můžete použít funkci quietly(.f).

Poslední funkce, kterou balík purrr nabízí k ovlivnění výpočtu, je funkce auto_browse(.f), která transformuje funkci .f tak, že v případě chyby, automaticky spustí ladící mechanismus:

map(v, auto_browse(log))