class: center, middle, inverse, title-slide # Programación avanzada ## Programación para el análisis de datos ### Departamento de Ciencias Sociales, UCU - Martín Opertti --- class: inverse, center, middle # Loops --- ## Loops Los loops son una forma de repetir una secuencia de operaciones a distintos objetos. En R existen loops de tipo `for`, `while` y `repeat`, veremos solamente los `for` loops que son los más sencillos. Un `for` loop repite líneas de código muchas veces, una para cada elemento de un imput. De forma general: ```r for (cada_valor in lista_de_valores) { realiza_esta_operacion } ``` --- ## Loops Supongamos que queremos imprimir en la consola los primeros 5 números primos seguidos de la frase "es un número primo". Podríamos hacer esto: .codefontchico[ ```r print("2 es un numero primo") ``` ``` ## [1] "2 es un numero primo" ``` ```r print("3 es un numero primo") ``` ``` ## [1] "3 es un numero primo" ``` ```r print("5 es un numero primo") ``` ``` ## [1] "5 es un numero primo" ``` ```r print("7 es un numero primo") ``` ``` ## [1] "7 es un numero primo" ``` ```r print("11 es un numero primo") ``` ``` ## [1] "11 es un numero primo" ``` ] --- ## Loops O con un loop: .codefont[ ```r numeros_primos <- c(2, 3, 5, 7, 11) for (i in seq_along(numeros_primos)){ print(paste(numeros_primos[i], "es un numero primo")) } ``` ``` ## [1] "2 es un numero primo" ## [1] "3 es un numero primo" ## [1] "5 es un numero primo" ## [1] "7 es un numero primo" ## [1] "11 es un numero primo" ``` ] --- ## Loops Para guardar el output de un loop debemos crear un objeto vacío para almacenarlo antes, con su dimensión esperada. Esto lo hacemos con la función `vector()`, `list()` o `data.frame()` .codefont[ ```r numeros_primos <- c(2, 3, 5, 7, 11) objeto <- vector("character", # definimos el tipo de los datos length(numeros_primos)) # definimos la extensión for (i in seq_along(numeros_primos)){ objeto[i] <- paste(numeros_primos[i], "es un numero primo") } objeto ``` ``` ## [1] "2 es un numero primo" "3 es un numero primo" "5 es un numero primo" ## [4] "7 es un numero primo" "11 es un numero primo" ``` ] --- ## Loops Para crear objetos de forma separada dentro de un loop usamos `assign()`. .codefont[ ```r rm(list=ls()) # Para asignar un nombre a cada elemento numeros_primos <- c(2, 3, 5, 7, 11) # Creo un vector con el nombre de cada objeto nom_np <- paste(as.character(numeros_primos), "obj", sep = "_") for (i in seq_along(numeros_primos)){ assign(nom_np[i], paste(numeros_primos[i], "es un numero primo") ) } ls()[1:8] # Objetos en el ambiente ``` ``` ## [1] "11_obj" "2_obj" "3_obj" "5_obj" ## [5] "7_obj" "i" "nom_np" "numeros_primos" ``` ] --- class: inverse, center, middle # Listas, dataframes y loops --- ## Listas Las listas son una clase de objeto en R que permite almacenar objetos de distintos tipos y tamaños (vectores, dataframes, incluso otras listas). En escencia las listas consisten en englobar en un solo objeto elementos heterogéneos. Se crean utilizando la función `list()`. Por ejemplo: .codefont[ ```r d_gap <- gapminder::gapminder # Crear lista ejemplo_lista <- list( c("Uruguay", "Paraguay"), # Vector lenght = 2 d_gap, # Dataframe de gapminder 2 # Valor suelto ) class(ejemplo_lista) ``` ``` ## [1] "list" ``` ```r # Indexar listas ejemplo_lista[[1]] # Primer elemento de lista ``` ``` ## [1] "Uruguay" "Paraguay" ``` ```r ejemplo_lista[[1]][[2]] # Segundo valor del rpimer elemento ``` ``` ## [1] "Paraguay" ``` ] --- ## Listas de dataframes Los elementos dentro de una lista pueden ser exclusivamente dataframes. Para ello podemos o crear una lista desde cero asignando los dataframes que la van a componer o tomar un dataframe y dividirlo según una o más variables y que cada una de esas divisiones sea un dataframe que a su vez sea un elemento dentro de una lista. Esto lo podemos hacer con la función `group_split()` de dplyr: .codefontchico[ ```r # Divido el dataframe de gapminder según continente lista_df <- d_gap %>% group_split(continent, named = TRUE) %>% setNames(sort(unique(d_gap$continent))) lista_df ``` ``` ## <list_of< ## tbl_df< ## country : factor<39935> ## continent: factor<be586> ## year : integer ## lifeExp : double ## pop : integer ## gdpPercap: double ## named : logical ## > ## >[5]> ## $Africa ## # A tibble: 624 x 7 ## country continent year lifeExp pop gdpPercap named ## <fct> <fct> <int> <dbl> <int> <dbl> <lgl> ## 1 Algeria Africa 1952 43.1 9279525 2449. TRUE ## 2 Algeria Africa 1957 45.7 10270856 3014. TRUE ## 3 Algeria Africa 1962 48.3 11000948 2551. TRUE ## 4 Algeria Africa 1967 51.4 12760499 3247. TRUE ## 5 Algeria Africa 1972 54.5 14760787 4183. TRUE ## 6 Algeria Africa 1977 58.0 17152804 4910. TRUE ## 7 Algeria Africa 1982 61.4 20033753 5745. TRUE ## 8 Algeria Africa 1987 65.8 23254956 5681. TRUE ## 9 Algeria Africa 1992 67.7 26298373 5023. TRUE ## 10 Algeria Africa 1997 69.2 29072015 4797. TRUE ## # ... with 614 more rows ## ## $Americas ## # A tibble: 300 x 7 ## country continent year lifeExp pop gdpPercap named ## <fct> <fct> <int> <dbl> <int> <dbl> <lgl> ## 1 Argentina Americas 1952 62.5 17876956 5911. TRUE ## 2 Argentina Americas 1957 64.4 19610538 6857. TRUE ## 3 Argentina Americas 1962 65.1 21283783 7133. TRUE ## 4 Argentina Americas 1967 65.6 22934225 8053. TRUE ## 5 Argentina Americas 1972 67.1 24779799 9443. TRUE ## 6 Argentina Americas 1977 68.5 26983828 10079. TRUE ## 7 Argentina Americas 1982 69.9 29341374 8998. TRUE ## 8 Argentina Americas 1987 70.8 31620918 9140. TRUE ## 9 Argentina Americas 1992 71.9 33958947 9308. TRUE ## 10 Argentina Americas 1997 73.3 36203463 10967. TRUE ## # ... with 290 more rows ## ## $Asia ## # A tibble: 396 x 7 ## country continent year lifeExp pop gdpPercap named ## <fct> <fct> <int> <dbl> <int> <dbl> <lgl> ## 1 Afghanistan Asia 1952 28.8 8425333 779. TRUE ## 2 Afghanistan Asia 1957 30.3 9240934 821. TRUE ## 3 Afghanistan Asia 1962 32.0 10267083 853. TRUE ## 4 Afghanistan Asia 1967 34.0 11537966 836. TRUE ## 5 Afghanistan Asia 1972 36.1 13079460 740. TRUE ## 6 Afghanistan Asia 1977 38.4 14880372 786. TRUE ## 7 Afghanistan Asia 1982 39.9 12881816 978. TRUE ## 8 Afghanistan Asia 1987 40.8 13867957 852. TRUE ## 9 Afghanistan Asia 1992 41.7 16317921 649. TRUE ## 10 Afghanistan Asia 1997 41.8 22227415 635. TRUE ## # ... with 386 more rows ## ## $Europe ## # A tibble: 360 x 7 ## country continent year lifeExp pop gdpPercap named ## <fct> <fct> <int> <dbl> <int> <dbl> <lgl> ## 1 Albania Europe 1952 55.2 1282697 1601. TRUE ## 2 Albania Europe 1957 59.3 1476505 1942. TRUE ## 3 Albania Europe 1962 64.8 1728137 2313. TRUE ## 4 Albania Europe 1967 66.2 1984060 2760. TRUE ## 5 Albania Europe 1972 67.7 2263554 3313. TRUE ## 6 Albania Europe 1977 68.9 2509048 3533. TRUE ## 7 Albania Europe 1982 70.4 2780097 3631. TRUE ## 8 Albania Europe 1987 72 3075321 3739. TRUE ## 9 Albania Europe 1992 71.6 3326498 2497. TRUE ## 10 Albania Europe 1997 73.0 3428038 3193. TRUE ## # ... with 350 more rows ## ## $Oceania ## # A tibble: 24 x 7 ## country continent year lifeExp pop gdpPercap named ## <fct> <fct> <int> <dbl> <int> <dbl> <lgl> ## 1 Australia Oceania 1952 69.1 8691212 10040. TRUE ## 2 Australia Oceania 1957 70.3 9712569 10950. TRUE ## 3 Australia Oceania 1962 70.9 10794968 12217. TRUE ## 4 Australia Oceania 1967 71.1 11872264 14526. TRUE ## 5 Australia Oceania 1972 71.9 13177000 16789. TRUE ## 6 Australia Oceania 1977 73.5 14074100 18334. TRUE ## 7 Australia Oceania 1982 74.7 15184200 19477. TRUE ## 8 Australia Oceania 1987 76.3 16257249 21889. TRUE ## 9 Australia Oceania 1992 77.6 17481977 23425. TRUE ## 10 Australia Oceania 1997 78.8 18565243 26998. TRUE ## # ... with 14 more rows ``` ] --- ## Loops sobre lista con dataframes Crear listas con dataframes o dividir dataframes en elementos de una lista puede tener muchas ventajas. Una de ellas es crear loops que iteren para cada dataframe dentro de una lista. Por ejemplo, supongamos que estmaos trabjaando con muchos dataframes y queremos guardar todos en una carpeta de forma separada. En vés de utilizar `write.csv()` repeditamente, podemos usar el siguiente loop: ```r # De esta forma, puedo hacer un loop para guardar cada uno de ellos for (i in seq_along(lista_df)) { filename <- paste0("resultados/loops/", names(lista_df)[i], ".csv") write.csv(lista_df[[i]], filename) } # Quedan 5 dataframes guardados en la carpeta resultados/loops ``` --- ## Loops sobre lista con dataframes Otro uso que nos puede ahorrar mucho tiempo es leer todos los dataframes de una carpeta mediante un loop. Con `list.files()` obtenemos el nombre de todos los archivos en una carpeta, con `substr()` recortamos el nombre completo para nombrar a los objetos y luego con `assign()` podemos asignar el nombre a cada .csv que leemos con `read_csv()` .codefont[ ```r rm(list = ls()) # Lista con dataframes en la carpeta filenames <- list.files("resultados/loops", full.names=TRUE) # Creo la lista con los nombres que va a tener cada base (quitar .csv) namesfiles <- substr(filenames, 18, nchar(filenames)-4) # Ahora hago un loop para leer cada base for (i in seq_along(filenames)) { assign(namesfiles[i], read_csv(filenames[i]) ) } ls() ``` ``` ## [1] "Africa" "Americas" "Asia" "Europe" "filenames" ## [6] "i" "namesfiles" "Oceania" ``` ] --- class: inverse, center, middle # Funcionales --- ## Funcionales A pesar de su utilidad, los loops en R son útiles son un poco lentos comparado con otros procedimientos. Por ejemplo, cuando usamos listas de dataframes podemos utilizar otras funciones que corren más rápido y más sencillo como `lapply()` o `map()` que aplica una función a cada elemento de una lista y devuelva la lista modificada. .codefontchico[ ```r # Creamos una lista de dataframes con los 5 dataframes lista_df_new <- list(Africa, Americas, Asia, Europe, Oceania) # Incluir los nombres de los objetos de una lista names(lista_df_new) <- c("Africa", "Americas", "Asia", "Europe", "Oceania") # Usamos map para crear una variable nueva y eliminar una existente # dentro de cada dataframe list_df_final <- map(lista_df_new, function(df) { df <- df %>% mutate(var_nueva = "Valor nuevo") %>% select(-lifeExp) }) # Chequeamos Africa <- list_df_final[[1]] # Extraigo el primer df head(Africa, 2) ``` ``` ## # A tibble: 2 x 8 ## ...1 country continent year pop gdpPercap named var_nueva ## <dbl> <chr> <chr> <dbl> <dbl> <dbl> <lgl> <chr> ## 1 1 Algeria Africa 1952 9279525 2449. TRUE Valor nuevo ## 2 2 Algeria Africa 1957 10270856 3014. TRUE Valor nuevo ``` ] --- ## Loop con map() o lapply() Es más, con `map()` `lapply()` podríamos haber leído las bases de forma más sencilla .codefont[ ```r # Podríamos haber hecho todo con lapply() filenames <- list.files("resultados/loops", full.names=TRUE) lista_df_2 <- map(filenames, read.csv) # El output es una lista de dataframes, a la cuál le agrego nombres names(lista_df_2) <- c("Africa", "Americas", "Asia", "Europe", "Oceania") ``` ] --- ## De listas de dataframes a dataframes Cuando utilizamos una lista de dataframes podemos tanto unir todos los dataframes ```r # Unir dataframes desde una lista df_gapminder <- bind_rows(lista_df_2) as_tibble(df_gapminder) ``` ``` ## # A tibble: 1,704 x 8 ## X country continent year lifeExp pop gdpPercap named ## <int> <chr> <chr> <int> <dbl> <int> <dbl> <lgl> ## 1 1 Algeria Africa 1952 43.1 9279525 2449. TRUE ## 2 2 Algeria Africa 1957 45.7 10270856 3014. TRUE ## 3 3 Algeria Africa 1962 48.3 11000948 2551. TRUE ## 4 4 Algeria Africa 1967 51.4 12760499 3247. TRUE ## 5 5 Algeria Africa 1972 54.5 14760787 4183. TRUE ## 6 6 Algeria Africa 1977 58.0 17152804 4910. TRUE ## 7 7 Algeria Africa 1982 61.4 20033753 5745. TRUE ## 8 8 Algeria Africa 1987 65.8 23254956 5681. TRUE ## 9 9 Algeria Africa 1992 67.7 26298373 5023. TRUE ## 10 10 Algeria Africa 1997 69.2 29072015 4797. TRUE ## # ... with 1,694 more rows ``` --- class: inverse, center, middle # Condicionales --- ## Condicionales En R podemos utilizar los operadores relacionales y lógicos que ya conocemos para modificar el comportamiento de nuestros scripts. Supongamos que tenemos un script donde cargamos un vector numérico distinto cada vez que lo utilizamos. Queremos una medida de resumen para el vector cada vez que corremos el script. Normalmente usamos la media, pero queremos decirle a R que cuando haya algún valor mayor o igual a 10, calcule la mediana, no la media. ```r if(condicion){ expresion } else { otra expresion } ``` --- ## Condicionales ```r vector_numerico <- c(1, 4, 5, 6, 7, 8, 9, 2) if(max(vector_numerico) < 10){ mean(vector_numerico) } else { median(vector_numerico) } ``` ``` ## [1] 5.25 ```