Bucles (loops)

  • Proceso automatizado de varios pasos organizado como secuencias de acciones (p. Ej., procesos ‘por lotes’)
  • Se usa para acelerar los procedimientos en los que se aplica la misma acción a muchos objetos (similares)
  • Crítico para la gestión de grandes bases de datos (‘big data’, y buenas prácticas de programación)

2 tipos básicos:

  1. Ejecutar hasta que se cumpla una condición predefinida (bucles while yrepeat)

tipos de bucle

* Modificado de Tutorial de bucles Datacamp  

  1. Ejecutar para un número predefinido de iteraciones (es decir, tiempos). 2 tipos:
    1. Los resultados pueden ingresarse nuevamente en la siguiente iteración (bucles for)
    2. Los resultados de una interacción no pueden afectar a otras iteraciones ((X) aplica)

loop types 2

 

Nota: cuando el resultado de una iteración es completamente independiente de otras iteraciones, la tarea podría ejecutarse en paralelo. Hablaremos sobre computación paralela durante la clase de “codigo eficiente”

 

Bucles ‘While’

Los bucles while aplican una acción (1 o más funciones) en una secuencia de elementos hasta que se cumpla una condición. La condición puede evaluar un resultado del propio ciclo o una entrada externa:

tipos de bucle 2

 

El siguiente ciclo while se ejecuta hasta que la correlación de las variables continuas generadas al azar es mayor que un umbral:

# set default value as 0
corr_coef <- 0

# initiate loop
while(corr_coef < 0.5) {
  
  # generate variable 1
  v1 <- rnorm(n = 20, mean = 100, sd = 20)

  # generate variable 2
  v2 <- rnorm(n = 20, mean = 100, sd = 20)
  
  # run correlation
  corr_coef <- cor(v1, v2)

  # print result
  print(corr_coef)
  }

corr_coef
##  [1]  0.0872575  0.3865472  0.1199017 -0.2902941  0.2413406 -0.1667497
##  [7]  0.1272541  0.0011283 -0.1344395 -0.0184824  0.5966901

 

Para guardar cada uno de los resultados hay 2 opciones:

  • Usando la función append
  • Agregar nuevos elementos a un vector usando indexación

Usando append:

# set default value as 0
corr_coef <- 0

# create empty vector 
cc_vector <- NULL

while(corr_coef < 0.5) {
  
  # generate variable 1
  v1 <- rnorm(n = 20, mean = 100, sd = 20)

  # generate variable 2
  v2 <- rnorm(n = 20, mean = 100, sd = 20)
  
  # run correlation
  corr_coef <- cor(v1, v2)

  # store results using append
  cc_vector <- append(cc_vector, corr_coef)
  
  }

head(cc_vector)
## [1] -0.047693  0.232015  0.133767 -0.089900  0.364152  0.203306

 

Usando indexación:

# set default value as 0
corr_coef <- 0

# create empty vector 
cc_vector <- NULL

while(corr_coef < 0.5) {
  
  # generate variable 1
  v1 <- rnorm(n = 20, mean = 100, sd = 20)

  # generate variable 2
  v2 <- rnorm(n = 20, mean = 100, sd = 20)
  
  # run correlation
  corr_coef <- cor(v1, v2)

  # store results using append
  cc_vector[length(cc_vector) + 1] <- corr_coef
  
  }

head(cc_vector)
## [1]  0.239060  0.212589  0.392069  0.132913 -0.093418 -0.320691

 

Pero tenga en cuenta que append puede ser muy lento (no recomendado)

 

Con un pequeño ajuste, un bucle while también puede evaluar varias condiciones a la vez. Por ejemplo, también podemos incluir altos valores de correlación negativa:

# set default value as 0
corr_coef <- 0

# create empty vector 
cc_vector <- NULL

while(corr_coef < 0.5 & corr_coef > -0.5) {
  
  # generate variable 1
  v1 <- rnorm(n = 20, mean = 100, sd = 20)

  # generate variable 2
  v2 <- rnorm(n = 20, mean = 100, sd = 20)
  
  # run correlation
  corr_coef <- cor(v1, v2)

  # store results using append
  cc_vector[length(cc_vector) + 1] <- corr_coef
  
  }

head(cc_vector)
## [1]  0.323266 -0.153637 -0.049445  0.154694  0.191022 -0.168300

 


Ejercicio 1


1.1 Haga un bucle while que se detenga solo si la correlación es mayor que 0.5 pero menor que 0.55


1.2 Haga un bucle while que se detenga si la correlación es superior a 0.8 o si el bucle ha estado ejecutándose durante más de 10 segundos (consejo: use la funcióndifftime y / o as.numeric)


Bucles ‘repeat’

Los bucles repeat también deben cumplir una condición para detenerse. Muy parecido a los bucles while. Sin embargo, se realiza para que la acción se ejecute al menos una vez, independientemente de la evaluación de la condición

loop repeat

 

El siguiente bucle repeat hace lo mismo que el buclewhile anterior:

# create empty vector 
cc_vector <- NULL

repeat
{
  # generate variable 1
  v1 <- rnorm(n = 20, mean = 100, sd = 20)

  # generate variable 2
  v2 <- rnorm(n = 20, mean = 100, sd = 20)
  
  # run correlation
  corr_coef <- cor(v1, v2)

  # store results using append
  cc_vector[length(cc_vector) + 1] <- corr_coef
 
  if (corr_coef > 0.5)   break
   
  }

head(cc_vector)
## [1]  0.2327630  0.2225129  0.3036371 -0.0098055  0.0495164 -0.1844224

  Tenga en cuenta que en este caso la condición determina si el ciclo debe detenerse. En el ciclo while la condición determina si el ciclo debe continuar.

 

Ejercicio 2


2.1 Convierta en un bucle repeat el bucle while del ejercicio 1.2

Bucles ‘for’

Por mucho, for es el bucle más popular. El número de iteraciones se puede arreglar y conocer de antemano:

for loop

 

Nuevamente, creamos un bucle que calcula las correlaciones entre variables aleatorias, en este caso usando un bucle for:

# create empty vector 
cc_vector <- NULL

# set number of iterations
reps <- 30

# initiate loop
for(i in 1:reps)
{
  # generate variable 1
  v1 <- rnorm(n = 20, mean = 100, sd = 20)

  # generate variable 2
  v2 <- rnorm(n = 20, mean = 100, sd = 20)
  
  # run correlation
  corr_coef <- cor(v1, v2)

  # store results using append
  cc_vector[length(cc_vector) + 1] <- corr_coef
 
  }

los bucles for se pueden convertir en bucles condicionales usando break:

# create empty vector 
cc_vector <- NULL

# set number of iterations
reps <- 100

# initiate loop
for(i in 1:reps)
{
  # generate variable 1
  v1 <- rnorm(n = 20, mean = 100, sd = 20)

  # generate variable 2
  v2 <- rnorm(n = 20, mean = 100, sd = 20)
  
  # run correlation
  corr_coef <- cor(v1, v2)

  # store results using append
  cc_vector[length(cc_vector) + 1] <- corr_coef
 
  # set conditional stop
  if(corr_coef > 0.5 | corr_coef < -0.5) break
  
}

De esta manera podemos controlar el número máximo de iteraciones mientras seguimos aplicando un umbral condicional.

El operador de control next también se puede usar para omitir una iteración basada en una condición.

Una característica importante de los bucles while,repeat y for es que pueden tomar resultados de iteraciones anteriores como entrada en iteraciones posteriores. Esto se debe a que los objetos creados dentro de la función se guardan en el entorno actual (a diferencia de los bucles Xapply). Por ejemplo, el siguiente bucle for también se detiene si la diferencia (absoluta) entre la correlación actual y la de la iteración anterior es superior a 0.6:

# create empty vector 
cc_vector <- NULL

# set number of iterations
reps <- 100

# initiate loop
for(i in 1:reps)
{
  set.seed(i)
  # generate variable 1
  v1 <- rnorm(n = 20, mean = 100, sd = 20)

  set.seed(i + 10)
  # generate variable 2
  v2 <- rnorm(n = 20, mean = 100, sd = 20)
  
  # run correlation
  corr_coef <- cor(v1, v2)

  # store results using append
  cc_vector[length(cc_vector) + 1] <- corr_coef

  # calculate absolute difference only after first iteration
  if (i == 1) abs_diff <- 0 else
  abs_diff <-  abs(cc_vector[length(cc_vector) - 1] - corr_coef) 
  
  # set conditional stop
  if (corr_coef > 0.5 | corr_coef < -0.5 | abs_diff > 0.6) break
  
}

La utilidad de los bucles for para la gestión de datos se entiende mejor cuando se aplica a conjuntos de datos. En este ejemplo examinamos los juegos de datos de ejemplo en R:

# read example data set names
dt_sets <- data()$results[,3]

# remove the ones with spaces in their names
dt_sets <- grep(" ", dt_sets, value = TRUE, invert = TRUE)

# create empty vector 
is_df <- NULL

# run for loop over each element
for(i in 1:length(dt_sets))  
  is_df[i] <- is.data.frame(get(dt_sets[i]))

# put results in a data frame
df <- data.frame(dt_sets, is_df, stringsAsFactors = FALSE)

# check firs 13 rows
head(df, 13)
dt_sets is_df
diamonds TRUE
economics TRUE
economics_long TRUE
faithfuld TRUE
luv_colours TRUE
midwest TRUE
mpg TRUE
msleep TRUE
presidential TRUE
seals TRUE
txhousing TRUE
AirPassengers FALSE
BJsales FALSE

 

Note el uso de la funcion get, que permite leer el nombre del juego de datos como si fuera un objeto en R:

que estos 3 bucles son equivalentes:

# 1
for(i in 1:length(dt_sets))  
  is_df[i] <- is.data.frame(get(dt_sets[i]))

# 2
for(i in 1:length(dt_sets))  {
  is_df[i] <- is.data.frame(get(dt_sets[i]))
  }

# 3
for(i in 1:length(dt_sets))  {
  x <- get(dt_sets[i])
  is_df[i] <- is.data.frame(x)
  }

Las llaves solo se necesitan cuando la ‘acción’ a realizar requiere más de una línea de código.

 

Podemos usar el juego de datos recién creado para excluir objetos que no son ‘data.frames’:

# remove non-data frames
df <- df[df$is_df, ]

# check firs 13 rows
head(df, 13)
dt_sets is_df
diamonds TRUE
economics TRUE
economics_long TRUE
faithfuld TRUE
luv_colours TRUE
midwest TRUE
mpg TRUE
msleep TRUE
presidential TRUE
seals TRUE
txhousing TRUE
BOD TRUE
CO2 TRUE

 

Con este nuevo juego de datos podemos explorar más a fondo la estructura de los conjuntos de datos de ejemplo usando bucles for.

Ejercicio 3


3.1 Haga un bucle for que devuelva el número de columnas para cada juego de datos de ejemplo (recuerde usar la funcion get)


3.2 Haga un bucle for que devuelva el número de filas para cada juego de datos de ejemplo


3.3 Haga un bucle for que devuelva el número de filas y columnas para cada juego de datos de ejemplo


3.4 El juego de datos de ejemplo “ChickWeight” describe el “peso versus la edad de los pollitos en diferentes dietas”:

# load data
data("ChickWeight") 

# Convert to a regular data frame  
ChickWeight <- data.frame(ChickWeight, stringsAsFactors = FALSE)

# check first rows
head(ChickWeight) 
weight Time Chick Diet
42 0 1 1
51 2 1 1
59 4 1 1
64 6 1 1
76 8 1 1
93 10 1 1
# see structure
str(ChickWeight)
## 'data.frame':    578 obs. of  4 variables:
##  $ weight: num  42 51 59 64 76 93 106 125 149 171 ...
##  $ Time  : num  0 2 4 6 8 10 12 14 16 18 ...
##  $ Chick : Ord.factor w/ 50 levels "18"<"16"<"15"<..: 15 15 15 15 15 15 15 15 15 15 ...
##  $ Diet  : Factor w/ 4 levels "1","2","3","4": 1 1 1 1 1 1 1 1 1 1 ...


Usando los datos de ChickWeight, calcule la correlación entre el peso y la edad de cada pollito (consejo: (1) use unique (ChickWeight $ Chick) dentro del inicio del bucle for y (2) cree subconjuntos usando indexación dentro del cuerpo del bucle)

 

Bucles (X)apply

(X) apply son funciones de nivel superior que toman una función como entrada y la aplican a una secuencia de objetos (vectores sensu lato). Bucles creados con (X)apply. Hay varias funciones (X)apply en R:

apropos("apply$")
##  [1] "apply"      "dendrapply" "eapply"     "kernapply"  "lapply"    
##  [6] "mapply"     "rapply"     "sapply"     "tapply"     "vapply"

 

Sin embargo, los más utilizados son apply,sapply, lapply ytapply. Todos siguen la misma lógica:

apply repeat

 

lapply toma un vector (atómico o de lista), aplica una función a cada elemento y devuelve una lista:

df_list <- lapply(X = df$dt_sets, FUN = get)

class(df_list)
## [1] "list"

 

sapply también toma un vector (atómico o de lista) y aplica la función a cada elemento, sin embargo, el resultado es un vector atómico (si puede empaquetarse como un vector):

df_nrow <- sapply(X = df_list, FUN = nrow)

class(df_nrow)
## [1] "integer"

 

apply aplica una función a cada una de las filas o columnas de un objeto bidimensional:

head(iris, 4)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
5.1 3.5 1.4 0.2 setosa
4.9 3.0 1.4 0.2 setosa
4.7 3.2 1.3 0.2 setosa
4.6 3.1 1.5 0.2 setosa
apply(X = iris[1:10, -5],MARGIN =  1, FUN =  sum)
##    1    2    3    4    5    6    7    8    9   10 
## 10.2  9.5  9.4  9.4 10.2 11.4  9.7 10.1  8.9  9.6
apply(X = iris[1:10, -5], MARGIN =  1, FUN = mean)
##     1     2     3     4     5     6     7     8     9    10 
## 2.550 2.375 2.350 2.350 2.550 2.850 2.425 2.525 2.225 2.400
apply(X = iris, MARGIN = 2, FUN = class)
## Sepal.Length  Sepal.Width Petal.Length  Petal.Width      Species 
##  "character"  "character"  "character"  "character"  "character"

 

tapply es más específico ya que aplica una función a un subconjunto de datos definido por un vector categórico adicional. Por ejemplo, podemos calcular la longitud promedio de pétalo para cada especie en el juego de datos ‘iris’ de la siguiente manera:

tapply(X = iris$Petal.Length, INDEX = iris$Species, FUN = mean)
##     setosa versicolor  virginica 
##      1.462      4.260      5.552

 

Los bucles (X) apply pueden modificarse para realizar “acciones” personalizadas creando nuevas funciones (ya sea dentro o fuera del bucle):

# function outside loop
dims <- function(x) c(nrow(x), ncol(x))

#run loop
df_dims <- lapply(X = df_list, FUN = dims)

#check results
head(df_dims, 3)
## [[1]]
## [1] 53940    10
## 
## [[2]]
## [1] 574   6
## 
## [[3]]
## [1] 2870    4
# function inside loop and run loop
df_dims <- lapply(X = df_list, FUN = function(x) c(nrow(x), ncol(x)))

#check results
head(df_dims, 3)
## [[1]]
## [1] 53940    10
## 
## [[2]]
## [1] 574   6
## 
## [[3]]
## [1] 2870    4

 

Tenga en cuenta que:

  1. en este tipo de bucles no hay retroalimentación de las iteraciones anteriores (es decir, los resultados de una iteración no se pueden ingresar en las iteraciones posteriores)

  2. (X)apply es más limpio que otros bucles porque los objetos creados dentro de ellos no están disponibles en el entorno de trabajo actual.

Ejercicio 4


4.1 Haga un bucle lapply equivalente al bucle for en el ejercicio 3.4 (utilizando los datos ‘ChickWeight’ calcule la correlación entre peso y tiempo para cada Chick)


4.2 Haga un bucle sapply para calcular el mayor peso registrado para cada tipo de dieta (pista: unique(ChickWeight$Diet), deberia devolver un valor por tipo de dieta). Nombre el vector resultante para que contenga el identificador de cada dieta.


4.3 Haga un bucle apply para calcular la media de cada variable numérica en el juego de datos ‘iris’.

Ejercicios extra


E.1 Reúna los resultados del ejercicio 3.4 en un nuevo juego de datos con columnas para ‘chick’ y ‘correlation’ usando un bucle for (consejo: use rbind)


E.2 ¿Cuántos de los juego de datos de ejemplo contenían una columna que era un factor?


E.3 Calcule el coeficiente de variación de cada variable numérica por especie en el juego de datos ‘iris’ usando ‘tapply’.


Referencias


Session information

## R version 3.6.1 (2019-07-05)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 18.04.3 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=es_CR.UTF-8        LC_COLLATE=en_US.UTF-8    
##  [5] LC_MONETARY=es_CR.UTF-8    LC_MESSAGES=en_US.UTF-8   
##  [7] LC_PAPER=es_CR.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=es_CR.UTF-8 LC_IDENTIFICATION=C       
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] ggplot2_3.2.1      RColorBrewer_1.1-2 kableExtra_1.1.0  
## [4] knitr_1.24        
## 
## loaded via a namespace (and not attached):
##  [1] Rcpp_1.0.2        pillar_1.4.2      compiler_3.6.1   
##  [4] tools_3.6.1       zeallot_0.1.0     digest_0.6.20    
##  [7] evaluate_0.14     tibble_2.1.3      gtable_0.3.0     
## [10] viridisLite_0.3.0 pkgconfig_2.0.2   rlang_0.4.0      
## [13] rstudioapi_0.10   yaml_2.2.0        xfun_0.9         
## [16] withr_2.1.2       dplyr_0.8.3       stringr_1.4.0    
## [19] httr_1.4.1        xml2_1.2.2        vctrs_0.2.0      
## [22] hms_0.5.1         tidyselect_0.2.5  webshot_0.5.1    
## [25] grid_3.6.1        glue_1.3.1        R6_2.4.0         
## [28] rmarkdown_1.15    purrr_0.3.2       readr_1.3.1      
## [31] magrittr_1.5      backports_1.1.4   scales_1.0.0     
## [34] htmltools_0.3.6   assertthat_0.2.1  rvest_0.3.4      
## [37] colorspace_1.4-1  labeling_0.3      stringi_1.4.3    
## [40] lazyeval_0.2.2    munsell_0.5.0     crayon_1.3.4