class: center, middle .linea-superior[] .linea-inferior[] <img src="imagenes/logo_portada2.png" width="200" /> ## Capacitación en R y herramientas de productividad - nivel intermedio ## Proyecto Ciencia de Datos ## Funcionales --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Estructura de la clase **Contenidos de la clase** - Programación con `dplyr` (evaluaciones no estándar) - Funcionales --- class: inverse, center, middle # I. Programación con dplyr --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Introducción a la programación con dplyr ### ¿Han intentado programar una función usando dplyr? -- ### Queremos una función que calcule mínimo, máximo, media y mediana en un `dataframe` ``` r library(gapminder) library(tidyverse) calcular_cosas <- function(data, var) { data %>% summarise(min = min(var), max = max(var), mean = mean(var), median = median(var) ) } calcular_cosas(gapminder, pop) ``` ``` ## Error in `summarise()`: ## ℹ In argument: `min = min(var)`. ## Caused by error: ## ! objeto 'pop' no encontrado ``` .center[ <img src="https://media.giphy.com/media/xUPGcz2H1TXdCz4suY/giphy.gif" width="150" /> ] --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Introducción a la programación con dplyr ### Cambiemos un par de cosas ``` r library(rlang) calcular_cosas <- function(data, var) { * enquo_var <- enexpr(var) data %>% * summarise(min = min(!!enquo_var), max = max(!!enquo_var), mean = mean(!!enquo_var), median = median(!!enquo_var) ) } calcular_cosas(gapminder, pop) ``` ``` ## # A tibble: 1 × 4 ## min max mean median ## <int> <int> <dbl> <dbl> ## 1 60011 1318683096 29601212. 7023596. ``` -- ### Durante este taller revisaremos `!!` y `enexpr` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Evaluaciones no estándar ### En `R`, los argumentos pueden ser *evaluados* o *quoted* (¿citados?) - evaluados: situación más usual en R - quoted: los argumentos son procesados de una manera "atípica" (evaluación no estándar) -- ### Para saber si un argumento es quoted o evaluado, simplemente ejecutamos el argumento fuera de la función ``` r pop ``` ``` ## Error: objeto 'pop' no encontrado ``` -- ### Si la ejecución no funciona, el argumento es quoted --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Ejemplos de argumentos quoted y evaluados ### Argumentos evaluados ``` r sum(mtcars$am) class(mtcars) ``` -- ### Argumentos quoted ``` r library(dplyr) by_cyl <- mtcars %>% summarise(mean = mean(mpg)) *calcular_cosas(gapminder, pop) ``` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Expresiones ### Evaluar de manera no estándar consiste en atrapar expresiones y evaluarlas en otro momento ``` r expr(x + y) ``` ``` ## x + y ``` -- ### `expr` ha "encapsulado" la expresión `x + y` -- ``` r devolver_expresion <- function(x) { expr(x)} devolver_expresion(a + b) ``` ``` ## x ``` ### La función `expr` es demasiado literal. Podemos usar enexpr (enrich) ``` r devolver_expresion <- function(x) { enexpr(x) } devolver_expresion(a + b) ``` ``` ## a + b ``` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Expresiones ### Volviendo a nuestra función `calcular_cosas`... ``` r calcular_cosas <- function(data, var) { * enquo_var <- enexpr(var) data %>% summarise(min = min(!!enquo_var), max = max(!!enquo_var), mean = mean(!!enquo_var), median = median(!!enquo_var) ) } ``` -- ### Ya sabemos qué significa la primera línea -- ``` r calcular_cosas <- function(data, var) { enquo_var <- enexpr(var) return(enquo_var) } calcular_cosas(var = pop) ``` ``` ## pop ``` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Expresiones .pull-left[ ### ¿Qué significa esto `min(!!enquo_var)`? ] .pull-right[ <img src="https://media.giphy.com/media/xUPGcz2H1TXdCz4suY/giphy.gif" width="150" /> ] -- ### Una vez que la expresión se "congela", es necesario evaluarla ### El operador bang-bang (!!) hace *unquoting* -- ``` r calcular_cosas <- function(data, var) { enquo_var <- enexpr(var) data %>% * summarise(min = min(!!enquo_var)) } calcular_cosas(gapminder, pop) ``` ``` ## # A tibble: 1 × 1 ## min ## <int> ## 1 60011 ``` ### Estamos evaluando la variable enquo_var --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Usar el código del usuario ### ¿Qué pasa si queremos una función que use el nombre de un parámetro, para nombrar una variable? ### Idea número 1 ``` r calcular_cosas <- function(data, var) { enquo_var <- enexpr(var) data %>% * summarise(var = min(!!enquo_var)) } calcular_cosas(gapminder, pop) ``` ``` ## # A tibble: 1 × 1 ## var ## <int> ## 1 60011 ``` -- ### Funciona, pero `R` fue demasiado literal. Usó el nombre "var" --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Usar el código del usuario ### Idea número 2: evaluar *enquo_var* ``` r calcular_cosas <- function(data, var) { enquo_var <- enexpr(var) data %>% * summarise(!!enquo_var = min(!!enquo_var)) } calcular_cosas(gapminder, pop) ``` ``` ## Error in parse(text = input): <text>:5:27: inesperado '=' ## 4: data %>% ## 5: summarise(!!enquo_var = ## ^ ``` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Operador := ### Debemos resolver el problema con un nuevo operador ``` r calcular_cosas <- function(data, var) { enquo_var <- enexpr(var) data %>% * summarise(!!enquo_var := min(!!enquo_var))} calcular_cosas(gapminder, pop) ``` ``` ## # A tibble: 1 × 1 ## pop ## <int> ## 1 60011 ``` -- Si queremos entregar un string como input podemos usar `sym` `sym` convierte un string en un símbolo. Esta operación se denomina *parsing* ``` r calcular_cosas <- function(data, var) { enquo_var <- sym(var) data %>% * summarise(!!enquo_var := min(!!enquo_var))} calcular_cosas(gapminder, "pop") ``` ``` ## # A tibble: 1 × 1 ## pop ## <int> ## 1 60011 ``` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Parsing ### Otra opción es usar la función `parse_expr` de `rlang` ``` r calcular_cosas <- function(data, var) { * enquo_var <- parse_expr(var) data %>% summarise(!!enquo_var := min(!!enquo_var)) } calcular_cosas(gapminder, "pop") ``` ``` ## # A tibble: 1 × 1 ## pop ## <int> ## 1 60011 ``` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Nueva sintaxis ### Hace un tiempo existe el operador embrace `{{}}` ``` r calcular_cosas <- function(data, var) { data %>% * summarise({{var}} := min({{var}})) } calcular_cosas(gapminder, pop) ``` ``` ## # A tibble: 1 × 1 ## pop ## <int> ## 1 60011 ``` -- ### Este operador nos hubiera evitado la vuelta que dimos, pero no habríamos aprendido sobre evaluaciones no estándar .center[ # 🤓 ] --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Ejercicio dplyr Trabajaremos con los datos de Casen. Escriba una función llamada `sum_something` que agrupe por una variable y sume otra. Por ejemplo, `sum_something(casen, region, ytotcor)` debería devolver lo siguiente: ``` ## # A tibble: 3 × 2 ## region n ## <dbl> <dbl> ## 1 1 2526228202 ## 2 2 2577850836 ## 3 3 2157490923 ``` -- ``` r sum_something <- function(data, group_var, var) { data %>% group_by(!!enexpr(group_var)) %>% summarise(n = sum(!!enexpr(var))) } ``` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Ejercicio dplyr (continuación) Crea una función `plot_table` que acompañe a `sum_something`. `plot_table` debe recibir la tabla creada por `sum_something` y devolver un gráfico de barras. Es importante que la función reciba el nombre de la variable x y la variable y. Además, debe existir un parámetro para agregar un título al gráfico. El llamado a la función `plot_table(table, region, n, "Total del ingreso por región" )` debería tener este resultado .pull-left[ <!-- --> ] -- .pull-right[ ``` r tabla <- sum_something(casen, region, ytotcor) plot_table <- function(table, x_var, y_var, input_title ) { ggplot(table, aes(x = !!enexpr(x_var), y = !!enexpr(y_var) )) + geom_bar(stat = "identity") + labs(title = input_title) } plot_table(tabla, region, n, "Total del ingreso por región" ) ``` ] --- class: inverse, center, middle # II. functionals --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Introducción a funcionales (functionals) *"To become significantly more reliable, code must become more transparent. In particular, nested conditions and loops must be viewed with great suspicion. Complicated control flows confuse programmers. Messy code often hides bugs."* - Bjarne Stroustrup -- Una "funcional" (*functional*) es una función que recibe como input otra función ``` r randomise <- function(f) f(runif(10)) ``` -- `randomise` recibe una función y hace algo con un vector de 10 números aleatorios ``` r randomise(mean) ``` ``` ## [1] 0.4787039 ``` ``` r randomise(sum) ``` ``` ## [1] 5.226148 ``` ``` r randomise(median) ``` ``` ## [1] 0.7362392 ``` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Introducción a funcionales (functionals) Es muy probable que ustedes hayan usado funcionales con `R` base - `apply`, `lapply`, `sapply` -- Los *loops* tienen mala fama en `R` -- Típicamente, las funcionales se usan como alternativa a los *loops* -- Nosotros trabajaremos con el paquete `purrr` ``` r library(purrr) ``` .center[ <img src="imagenes/purrr.png" width="200" /> ] --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Mi primera funcional Función que calcula el cuadrado del input ``` r cuadrado <- function(x) x ** 2 ``` -- `map` lleva el vector `1:3` hacia la función cuadrado ``` r map(1:3, cuadrado) ``` ``` ## [[1]] ## [1] 1 ## ## [[2]] ## [1] 4 ## ## [[3]] ## [1] 9 ``` .center[ <img src="https://d33wubrfki0l68.cloudfront.net/f0494d020aa517ae7b1011cea4c4a9f21702df8b/2577b/diagrams/functionals/map.png" width="200" /> ] --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # ¿Qué tienen que ver los mapas? .center[ <img src="imagenes/mapa.png" width="400" /> ] -- .center[ <img src="imagenes/mapear_funcion.png" width="400" /> ] --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Ventajas de purrr Permite abstraernos de lo que hace la función -- Tenemos claridad de lo que entra y sale de la función - `map` devuelve una lista - `map_int` devuelve vector de enteros - `map_dbl` devuelve vector de reales - `map_chr` devuelve vector de characters - `map_df` devuelve un dataframe ``` r triple_chr <- function(x) as.character(x * 3) map_int(1:3, triple_chr) ``` ``` ## Error in `map_int()`: ## ℹ In index: 1. ## Caused by error: ## ! Can't coerce from a string to an integer. ``` ``` r map_chr(1:3, triple_chr) ``` ``` ## [1] "3" "6" "9" ``` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Trabajando con listas anidadas ### La función `mean` se evalúa en cada uno de los vectores de la lista ``` r lista_vectores <- list(1:3, 1:10, 2:9, 1) map_dbl(lista_vectores, mean) ``` ``` ## [1] 2.0 5.5 5.5 1.0 ``` -- ### ¿Cómo uso esto en un dataframe? .center[ <img src="https://media.giphy.com/media/xUPGcz2H1TXdCz4suY/giphy.gif" width="150" /> ] --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Trabajando con dataframes ### En `R`, los `dataframes` son listas con vectores de la misma longitud ``` r typeof(mtcars) ``` ``` ## [1] "list" ``` -- ``` r 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 ``` .center[ <img src="https://media.giphy.com/media/l3V0dy1zzyjbYTQQM/giphy.gif" width="250" /> ] --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Trabajando con más argumentos ### ¿Qué pasa si queremos usar más argumentos? ``` r lista_vectores <- list(1:3, c(1:10, NA)) map_dbl(lista_vectores, ~mean(.x, na.rm = TRUE)) ``` ``` ## [1] 2.0 5.5 ``` `.x` toma el valor de los elementos que están dentro de `lista_vectores` El parámetro que varía va a la izquierda y el parámetro fijo a la derecha -- .center[ <img src="https://d33wubrfki0l68.cloudfront.net/e1b3536a7556aef348f546a79277125c419a5fdc/0c0a1/diagrams/functionals/map-arg.png" width="400" /> ] --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Usando 2 inputs ### La función `media_sumar` calcula la media y suma un valor ``` r media_sumar <- function(vector, valor) { mean(vector) + valor } ``` ### Queremos que la función itere sobre `lista_vectores` y sobre `valores` ``` r lista_vectores <- list(1:3, 1:5, 2:5) valores <- c(2, 3, 8) ``` -- ``` r map2_dbl(lista_vectores, valores, media_sumar) ``` ``` ## [1] 4.0 6.0 11.5 ``` .center[ <img src="https://d33wubrfki0l68.cloudfront.net/f5cddf51ec9c243a7c13732b0ce46b0868bf8a31/501a8/diagrams/functionals/map2.png" width="300" /> ] --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Ejercicio map2 Tenemos una lista muy larga de elementos con nombres inscritos en el registro civil ``` r lista_anios <- split(guaguas::guaguas, ~anio ) names(lista_anios)[1:5] ``` ``` ## [1] "1920" "1921" "1922" "1923" "1924" ``` Vamos a imaginar que cada elemento ocupa mucha memoria, por lo que deben ser procesados en secuencia -- La idea es calcular la suma de nombres (variable n) para cada año, filtrando por una variable que puede ser hombre o mujer ``` r filtro <- map_chr(1:length(lista_anios), ~sample(x = c("F", "M"), size = 1)) filtro[1:5] ``` ``` ## [1] "M" "M" "F" "F" "F" ``` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Resultado ejercicio map2 ``` r totales <- map2(lista_anios, filtro, ~.x %>% dplyr::filter(sexo == .y) %>% dplyr::summarise(total = sum(n) ) ) ``` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Usando *n* cantidad de inputs ### Necesitamos iterar sobre 3 vectores ``` r media_sumar_dividir <- function(vector, valor_suma, valor_division) { (mean(vector) + valor_suma) / valor_division } lista_vectores <- list(1:3, 1:5, 2:5) valores_suma <- c(2, 3, 8) valores_division <- c(2, 1, 9) ``` -- ``` r pmap_dbl(list(lista_vectores, valores_suma, valores_division), media_sumar_dividir) ``` ``` ## [1] 2.000000 6.000000 1.277778 ``` .center[ <img src="https://d33wubrfki0l68.cloudfront.net/2eb2eefe34ad6d114da2a22df42deac8511b4788/5a538/diagrams/functionals/pmap-arg.png" width="300" /> ] --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Trabajar con *side-effects* ### A veces, nos interesa una función por sus *side-effects* ``` r animales <- c("perro", "gato", "elefante") map(animales, print) ``` ``` ## [1] "perro" ## [1] "gato" ## [1] "elefante" ``` ``` ## [[1]] ## [1] "perro" ## ## [[2]] ## [1] "gato" ## ## [[3]] ## [1] "elefante" ``` -- ### `map` está creando una lista y, además, está imprimiendo los valores --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Usando walk .center[ <img src="imagenes/walk.jpg" width="350" /> ] -- ``` r walk(animales, print) ``` ``` ## [1] "perro" ## [1] "gato" ## [1] "elefante" ``` ### Ahora solo estamos imprimiendo los strings --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Usando `walk` ### Necesito escribir archivos ``` r continentes <- split(gapminder, gapminder$continent) ``` ``` r library(feather) files <- paste0("data/", names(continentes), ".feather") walk2(continentes, files, write_feather) ``` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # imap A veces, es útil iterar sobre los nombres de una lista -- Extraer nombres de archivos y construir etiquetas ``` r files <- list.files("data/datos_ene/", full.names = T) trimestres <- list.files("data/datos_ene/") %>% str_extract(pattern = "-[[:digit:]]{2}-") %>% str_remove_all("-") ``` -- Leer archivos y etiquetar ``` r varios_ene <- map(files, read_csv2 ) names(varios_ene) <- paste0("trimestre_", trimestres) nombres_lista <- imap(varios_ene, ~.y) nombres_lista ``` ``` ## $trimestre_01 ## [1] "trimestre_01" ## ## $trimestre_02 ## [1] "trimestre_02" ## ## $trimestre_03 ## [1] "trimestre_03" ## ## $trimestre_04 ## [1] "trimestre_04" ## ## $trimestre_05 ## [1] "trimestre_05" ## ## $trimestre_06 ## [1] "trimestre_06" ``` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # imap Creamos una variable con el nombre de cada elemento ``` r ocupados <- imap(varios_ene, .f = ~mutate(., .y = if_else(activ == 1, 1, 0) )) ocupados[[1]] %>% count(trimestre_01) ``` ``` ## Error in `count()`: ## ! Must group by variables found in `.data`. ## ✖ Column `trimestre_01` is not found. ``` ### ¿Qué podemos hacer para arreglar el problema? .center[ <img src="https://media.giphy.com/media/xUPGcz2H1TXdCz4suY/giphy.gif" width="150" /> ] -- ``` r ocupados <- imap(varios_ene, .f = ~mutate(., !!rlang::parse_expr(.y) := if_else(activ == 1, 1, 0) )) ``` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Ejercicio: apliquemos todo Tenemos un listado de `dataframes` separados por año ``` r gapminder_list <- split(gapminder, gapminder$year) ``` Retomemos nuestras funciones `sum_something` y `plot_table`. La idea es usar `purrr` con el listado que tenemos y generar un gráfico para cada año en el que se muestre la población de cada continente. -- ``` r plots_by_year <- gapminder_list %>% map(~sum_something(.x, continent, pop)) %>% map(~plot_table(.x, continent, n, "Población mundial, según continente" )) ``` -- ``` r plots_by_year <- gapminder_list %>% map(sum_something, continent, pop) %>% map(plot_table, continent, n, "Población mundial, según continente" ) ``` --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Tarea para la 🏠 **Nota: Es deseable que las tareas solo contengan funciones** **Ejercicio 1** El siguiente código genera un resultado muy similar al del último ejercicio revisado en la clase. La diferencia es que la implementación es mediante un ciclo *for*. Adicionalmente, se agrega una funcionalidad que agrega al título el año correspondiente. --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Tarea para la 🏠 (continuación ejercicio 1) ``` r gapminder_list <- split(gapminder, gapminder$year) plot_with_for <- function(tablas){ plots <- list(vector(length = length(tablas) )) i <- 1 for (plot in tablas) { table <- sum_something(plot, continent, pop) plots[[i]] <- plot_table(table, continent, n, paste("Población mundial, según continente. Año", plot$year[1] ) ) i <- i + 1 } return(plots) } ``` ``` r plots <- plot_with_for(gapminder_list) ``` La tarea consiste en llegar al mismo resultado, pero utilizando únicamente las herramientas de `purrr`. **Crea una función llamada plot_with_purrr que reciba una lista de tablas y devuelva una lista de gráficos** **Pista**: La función `imap` puede ser de gran ayuda --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Tarea para la 🏠 **Ejercicio 2** Modifica la función `plot_table` para que el año del gráfico aparezca en el subtítulo y no en el título. La función modificada debería recibir un parámetro extra llamado `subtitulo`, que permita agregar el año al subtítulo del gráfico. Una vez que hayas modificado tu función, utilízala dentro de `plot_with_purrr`. Cada gráfico debería tener el año correspondiente en el subtítulo. --- background-image: url("imagenes/fondo2.PNG") background-size: contain; background-position: 100% 0% # Tarea para la 🏠 **Ejercicio 3** El siguiente *for* anidado genera pares de *x* e *y*. El ejercicio consiste en escribir una función llamada **nested_map** que utilice una sintaxis de `purrr`. La función debe recibir dos vectores numéricos (de igual o distinto largo) e imprimir pares de número. Es posible que la sintaxis llegue a ser un poco confusa. Reflexiona sobre la pertinencia de `purrr` para tareas de este tipo. ``` r nested_for <- function(v1, v2) { for (x in v1) { for (y in v2){ print(paste(x, y)) } } } nested_for(1:3, 5:8) ``` --- class: center, middle .linea-superior[] .linea-inferior[] <img src="imagenes/logo_portada2.png" width="200" /> ## Capacitación en R y herramientas de productividad - nivel intermedio ## Proyecto Ciencia de Datos ## Funcionales