Funcionales

La clase de hoy ha sido tomado del Capítulo 2, tema 9 del libro Advanced R programming, de Hadley Wickham https://adv-r.hadley.nz/fp.html.

R, es un lenguaje funcional. Esto significa que tiene un estilo de resolución de problemas centrado en funciones.

Las técnicas funcionales han experimentado un gran interés porque pueden producir soluciones eficientes y elegantes a muchos problemas modernos.

A continuación analizaremos las tres técnicas funcionales clave para descomponer los problemas en partes más pequeñas:

Un funcional es una función que toma una función como entrada y devuelve un vector como salida.

randomise <- function(f) f(runif(1e3)) #  la función input genera 1000 números uniformes aleatoriamente

randomise(mean) 
## [1] 0.5064725
randomise(mean) # cada vez el promedio resultado es distinto 
## [1] 0.5062585
randomise(sum)
## [1] 499.9515
randomise(sum) # cada vez la suma resultado es distinta
## [1] 498.1816

Es posible que ya hayan utilizado reemplazos del bucle for como lapply(), apply() y tapply() de la base R; o map().

Un uso común de los funcionales es como alternativa a los bucles for. Los bucles for tienen una mala reputación en R porque muchas personas creen que son lentos, pero la verdadera desventaja de los bucles for es que son muy flexibles: un bucle que calcula lo que se está iterando, pero no lo que se debe hacer con los resultados. Así como es mejor usar while que repetir, y es mejor usar for que while, es mejor usar un funcional que for.

Cada funcioanl está diseñada para una tarea específica, por lo que cuando reconoce el funcional, inmediatamente sabe por qué se está utilizando.

Prerrequisitos

La primera técnica se centrará en las funciones proporcionadas por el paquete purrr (Henry y Wickham 2018a).

Mi primer funcional: map()

El funcional más fundamental es purrr::map(). Toma un vector y una función, llama a la función una vez para cada elemento del vector y devuelve los resultados en una lista. En otras palabras, map(1:3,f) es equivalente a list(f(1), f(2), f(3)).

library(purrr)
triple <- function(x) x*3
map(1: 3, triple)
## [[1]]
## [1] 3
## 
## [[2]]
## [1] 6
## 
## [[3]]
## [1] 9

O, gráficamente:

El equivalente base de map() es lapply(). La única diferencia es que lapply() no admite los ayudantes sobre los que aprenderá a continuación.

Produciendo vectores atómicos

map() devuelve una lista, lo que la convierte en la más general de la familia de maps porque puede poner cualquier cosa en una lista. Pero es inconveniente devolver una lista cuando una estructura de datos más simple sería suficiente, por lo que hay cuatro variantes más específicas: map_lgl(), map_int(), map_dbl() y map_chr(). Cada uno devuelve un vector atómico del tipo especificado:

# map_chr () siempre devuelve un vector de caracteres
map_chr(mtcars, typeof)
##      mpg      cyl     disp       hp     drat       wt     qsec       vs 
## "double" "double" "double" "double" "double" "double" "double" "double" 
##       am     gear     carb 
## "double" "double" "double"
# map_lgl() siempre devuelve un vector lógico
map_lgl(mtcars, is.double)
##  mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
## TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
# map_int () siempre devuelve un vector entero
n_unique <- function(x) length(unique(x))
map_int(mtcars, n_unique)
##  mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
##   25    3   27   22   22   29   30    2    2    3    6
# map_dbl() siempre devuelve un vector doble
map_dbl(mtcars, mean)
##        mpg        cyl       disp         hp       drat         wt       qsec 
##  20.090625   6.187500 230.721875 146.687500   3.596563   3.217250  17.848750 
##         vs         am       gear       carb 
##   0.437500   0.406250   3.687500   2.812500

purrr usa la convención de que los sufijos, *_dbl()*, se refieren a la salida. Todas las funciones map _() pueden tomar cualquier tipo de vector como entrada. Estos ejemplos se basan en dos hechos: mtcars es un Data.frame* y los data.frame son listas que contienen vectores de la misma longitud. Esto es más obvio si dibujamos un data.frame con la misma orientación que el vector:

Todas las funciones de map siempre devuelven un vector de salida de la misma longitud que la entrada, lo que implica que cada llamada a .f debe devolver un solo valor. Si no es así, obtendrá un error:

pair <- function(x) c(x, x)
#map_dbl(1:2, pair)

Esto es similar al error que obtendrá si .f devuelve el tipo de resultado incorrecto:

#map_dbl(1:2, as.character)

En cualquier caso, suele ser útil volver a map(), porque map() puede aceptar cualquier tipo de salida. Eso le permite ver el resultado problemático y averiguar qué hacer con él.

map(1:2, pair)
## [[1]]
## [1] 1 1
## 
## [[2]]
## [1] 2 2
map(1:2, as.character)
## [[1]]
## [1] "1"
## 
## [[2]]
## [1] "2"