Herramientas de programación funcional

JPAG
01/03/21

Programación funcional

  • La programación funcional permite simplificar iteraciones, ya sea con vectores o listas, sin tener que lidiar con ciclos
  • Aplicar iteraciones ahorra tiempo, reduce líneas de código y previene typos
  • Las herramientas de programación en tidyverse incluyen:
library(tidyverse)
citation("purrr")
citation("tibble")
citation("reprex")
citation("magrittr")

Iteraciones

Se pueden leer archivos mediante líneas de código separadas:

Tmax<-read.csv("9014-COLONIA SANTA URSULA COAPA-DF-Tmax.csv")
Tprom<-read.csv("9014-COLONIA SANTA URSULA COAPA-DF-Tprom.csv")
Tdegree<-list.files(pattern=".csv")
Tdegree

O se puede constuir un bucle para leer de forma iterativa.

list_T<-list()
for (i in list_T){
  list_T[[i]]<-read_csv(i)
}

Iteraciones

Los bucles for son poderosos, pero a menudo aparecen typos y esto impide avanzar el flujo de trabajo. Envolver un bucle for en una sola función, map(.x, .f, …), ¨reduce la cantidad de líneas de código que necesitamos.

  • El primero es el argumento punto x, que es un objeto; puede ser una lista o un vector.

  • El segundo argumento es el argumento punto f, que es una función.

Los diferentes elementos del argumento punto x se introducirán en el argumento punto f para la iteración.

Ejemplo 1

En este ejemplo, vamos a trabajar con una lista, donde cada elemento contiene una cadena de números diferente. Cada elemento representa un parque y los recuentos son el número de aves que migran sobre un parque en un día determinado del mes de septiembre. Los diferentes parques contaban aves para diferentes números de días, por lo que tienen diferentes longitudes.

Ejemplo 1

(ex1<-list(c(3,1),c(3,8,1,2),c(8,3,9,9,5,5),c(8,9,7,9,5,4,1,5)))
[[1]]
[1] 3 1

[[2]]
[1] 3 8 1 2

[[3]]
[1] 8 3 9 9 5 5

[[4]]
[1] 8 9 7 9 5 4 1 5

Ejemplo 1

Si queremos determinar la suma de los números en cada elemento podemos hacerlo de dos formas. Primero, podemos usar cuatro líneas de código R que incluyen un bucle for para iterar sobre los elementos de ex1, colocando cada elemento en la función sum () y luego colocando el resultado en una nueva lista, llamada ex1_sum.

ex1_sum <- list()
for(i in seq_along(ex1)){
ex1_sum[[i]] <- sum(ex1[[i]])}# sumar cada elemento de ex1 en ex1_sum

O podemos usar una línea de código en purrr para calcular la suma de cada elemento de la ex1 y ponerla en la nueva lista ex1_sum.

Ejemplo 1

# Crear lista ex1_sum, iterar y sumar sobre los elementos de ex1
(ex1_sum <- map(ex1, sum))
[[1]]
[1] 4

[[2]]
[1] 14

[[3]]
[1] 39

[[4]]
[1] 48

Ejemplo 1

El resultado de ambos métodos es el mismo, sin embargo, purrr simplifica código al permitir enfocarse en las piezas con las que estamos trabajando, en lugar de en la estructura del bucle for.

Ejercicios

Listas

Las listas requieren un conjunto diferente de gramática que un data frame o los vectores. A diferencia de un data frame y los vectores, las listas pueden almacenar diferentes tipos de datos.

Ejemplo 2

En este ejemplo, la lista llamada ex2 contiene un data frame, que contiene datos sobre tres aves. El siguiente elemento de la lista podría ser cualquier cosa; no tiene por qué ser otro data frame. Por ejemplo podríamos almacenar un modelo o una gráfica.

ex2 <- list()
ex2[["data"]] <- data.frame(bird = c("robin", "sparrow", "jay"),
                            weight = c(76, 14, 100),
                            wing_length = c(100, 35, 130))
ex2[["model"]] <- lm (weight ~ wing_length, 
                      data = ex2[["data"]])
ex2[["plot"]] <- ggplot(
            data = ex2[["model"]],
            aes(x = weight, y = wing_legth)) + 
                  geom_point()  

Indexación de data frame y listas

Con un data frame, podemos indexar de dos formas. Primero, podemos usar un par de corchetes con la coma. La información sobre la fila se encuentra en el lado izquierdo de la coma. En este ejemplo, estamos indexando la primera fila. En el lado derecho de la coma está la información sobre la columna. Aquí estamos usando el nombre de una de las columnas.

mtcars[1, "wt"]
[1] 2.62

Indexación de data frame y listas

La segunda forma de indexar un data frame es con el signo de dólar y el nombre de la columna.

mtcars$wt
 [1] 2.620 2.875 2.320 3.215 3.440 3.460 3.570 3.190 3.150 3.440 3.440 4.070
[13] 3.730 3.780 5.250 5.424 5.345 2.200 1.615 1.835 2.465 3.520 3.435 3.840
[25] 3.845 1.935 2.140 1.513 3.170 2.770 3.570 2.780

Indexación de data frame y listas

La indexación de listas es un aspecto clave donde las listas difieren de undata frame y de los vectores. La indexación de listas utiliza corchetes, al igual que con data frame y con los vectores, pero de una manera diferente. Podemos usar corchetes dobles con un número para crear un subconjunto de cualquier lista. Aquí estamos tomando el segundo elemento de la lista ex2.

ex2[[2]]

Call:
lm(formula = weight ~ wing_length, data = ex2[["data"]])

Coefficients:
(Intercept)  wing_length  
   -17.3216       0.9131  

Indexación de data frame y listas

También, si se crea una lista, podemos poner el nombre del elemento entre corchetes dobles para indexar un elemento en particular. Aquí se toma el elemento “model” de la lista ex2.

ex2[["model"]]

Call:
lm(formula = weight ~ wing_length, data = ex2[["data"]])

Coefficients:
(Intercept)  wing_length  
   -17.3216       0.9131  

Ejemplo 3

Veamos otro ejemplo en el que comparamos bucles for contra la programación funcional en purrr para resolver un problema. Queremos saber cuántas filas hay en cada elemento de un data frame. En este caso, cada elemento de nuestro data frame, son los resultados de riqueza para un conjunto de variables bioclimáticas.

datos3<-read_csv("AMPHIBIANS_SET_FINAL_LGM.csv")

#Crear dataframe para poner los resultados
ex3 <- data.frame(names = names(datos3),rows = NA)
# Ciclo sobre datos3 para saber cuantas 
# columnas hay en cada elemento
for(i in 1:length(datos3)){    
  ex3[i,'rows'] <- nrow(datos3[i]) 
}

Ejemplo 3

Para verificar el número de filas en cada elemento, primero creamos un nuevo data frame para almacenar nuestros resultados. Este nuevo data frame, ex3, tiene dos columnas, una llamada names, que contiene los nombres del data frame inicial, y uno llamado rows, que actualmente está vacío. Aquí es donde pondremos la salida de nuestro bucle for. Luego escribimos un ciclo for donde cada iteración toma el elemento del data frame y lo coloca en la función nrow(). nrow() cuenta el número de filas en un data frame. El resultado de nrow() se coloca en la siguiente fila del nuevo data frame, en la columna de rows. Esto funciona bien, pero deja mucho espacio para errores tipográficos y es mucho más código del que necesitamos.

Ejemplo 3

Ahora verificamos el número de filas en cada elemento del data frame datos3 usando la función map() en purrr. La función map() toma dos argumentos. El primer argumento es el objeto list, en este caso el data frame datos3. El segundo argumento es la función por la que queremos iterar cada elemento, en este caso, la función nrow(). Ponemos la función después del símbolo de tilde (~). Luego colocamos el punto x en la función para mostrar map() donde queremos que se ingrese el elemento.

ex3 <- map(datos3,~nrow(datos3))

Ejercicios

Variantes de map()

La función map() genera una lista. A veces queremos nuestros datos en otra clase, que es donde entran los otros tipos de map(), lo que nos da muchos tipos diferentes de resultados. A veces las listas pueden ser bastante útiles pero si pensamos en el tipo de problemas que resolvemos con datos usualmente, es posible que deseemos un tipo de salida diferente, como un vector. Aquí es donde entran los otros tipos de map(). Sacar un vector de números de una lista puede ser útil para varias cosas, incluso como entrada para nuestra próxima tarea o para crear una nueva columna en un marco de datos. Empleamos map_dbl para extraer los números que representan el número de filas en cada data frame como un vector, en lugar de una lista. La función map() normal nos da una lista, mientras que map_dbl() genera un vector de números como resultado.

Variantes de map()

(map_dbl(datos3,~nrow(datos3)))
       X1         x         y  Richness    bio_01    bio_02    bio_03    bio_04 
    14114     14114     14114     14114     14114     14114     14114     14114 
   bio_05    bio_06    bio_07    bio_08    bio_09    bio_10    bio_11    bio_12 
    14114     14114     14114     14114     14114     14114     14114     14114 
   bio_13    bio_14    bio_15    bio_16    bio_17    bio_18    bio_19       aet 
    14114     14114     14114     14114     14114     14114     14114     14114 
vc_bio_01 vc_bio_12 
    14114     14114 

Variantes de map()

Un vector de valores VERDADERO y FALSO puede ser valioso para verificar que un objeto contenga lo que creemos que tiene, o como entrada para otra tarea. Aquí estamos interesados en verificar si cada elemento de nuestra lista datos3 tiene 14 filas, por lo que usamos la función map_lgl() para obtener un vector lógico como salida.

Variantes de map()

(map_lgl(datos3, ~nrow(datos3)==14114))
       X1         x         y  Richness    bio_01    bio_02    bio_03    bio_04 
     TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE 
   bio_05    bio_06    bio_07    bio_08    bio_09    bio_10    bio_11    bio_12 
     TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE 
   bio_13    bio_14    bio_15    bio_16    bio_17    bio_18    bio_19       aet 
     TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE 
vc_bio_01 vc_bio_12 
     TRUE      TRUE 

Variantes de map()

Un vector de caracteres puede ser valioso como una forma de extraer información de una lista para que pueda usarse para otras cosas, a menudo como entrada para nuestras próximas tareas. Aquí queremos adjuntar cierto texto a unas etiquetas preexistentes. Hacemos esto con map_chr().

set_names(c("foo", "bar")) %>% map_chr(paste0, ":suffix")
         foo          bar 
"foo:suffix" "bar:suffix" 

Ejemplo 4

Aquí queremos crear un vector que contenga el número de filas en cada elemento del data frame datos3 y convertirlo en una columna en un nuevo data frame. Primero, creamos nuestro nuevo data frame, lo llamaremos datos3_rows. Este nuevo data frame tiene dos columnas, una llamada names, ¨que contiene los nombres de cada elemento de datos3. La segunda columna se llama rows y está vacía por ahora. A continuación, usamos map_dbl() para crear un vector de números. Nuestro primer argumento es datos3, nuestro segundo argumento es el símbolo de tilde seguido de la función nrow(). Dentro de nrow() colocamos el punto x, para representar dónde deben colocarse los elementos de datos3. Luego asignamos map_dbl() a la columna de rows en el nuevo data frame que creamos. Ahora podemos usar names y rows para determinar si estos elementos contienen el número de filas esperado, que es una parte vital del proceso de aseguramiento y control de calidad. A diferencia del ejemplo anterior, aquí tenemos un vector que funciona fácilmente como parte de una columna de un data frame, mientras que antes la salida era una lista de valores, con la que puede ser más difícil trabajar.

Ejemplo 4

# Crear un data frame datos3_rows
datos3_rows <- data.frame(names = names(datos3),rows = NA)
# Mapaer sobre datos3 para determinar número de filas
datos3_rows$rows <- map_dbl(datos3, ~nrow(datos3))
# Imprime datos3_rows
datos3_rows

Ejercicios

Referencias