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

 

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:

# determinar valor por defecto 0
corr_coef <- 0

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

  # generar variable 2
  v2 <- rnorm(n = 20, mean = 100, sd = 20)
  
  # correr correlacion
  corr_coef <- cor(v1, v2)

  # imprimir resultado
  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:

# determinar valor por defecto 0
corr_coef <- 0

# crear vector vacio 
cc_vector <- NULL

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

  # generar variable 2
  v2 <- rnorm(n = 20, mean = 100, sd = 20)
  
  # correr correlacion
  corr_coef <- cor(v1, v2)

  # guardar resultado
  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:

# determinar valor por defecto 0
corr_coef <- 0

# crear vector vacio 
cc_vector <- NULL

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

  # generar variable 2
  v2 <- rnorm(n = 20, mean = 100, sd = 20)
  
  # correr correlacion
  corr_coef <- cor(v1, v2)

  # guardar resultado
  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:

# determinar valor por defecto 0
corr_coef <- 0

# crear vector vacio 
cc_vector <- NULL

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

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

  # guardar resultado
  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:

# crear vector vacio 
cc_vector <- NULL

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

  # generar variable 2
  v2 <- rnorm(n = 20, mean = 100, sd = 20)
  
  # correr correlacion
  corr_coef <- cor(v1, v2)

  # guardar resultado con 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:

# crear vector vacio 
cc_vector <- NULL

# definir numero de iteraciones
reps <- 30

# iniciar bucle
for(i in 1:reps)
{
  # generar variable 1
  v1 <- rnorm(n = 20, mean = 100, sd = 20)

  # generar variable 2
  v2 <- rnorm(n = 20, mean = 100, sd = 20)
  
  # correr correlacion
  corr_coef <- cor(v1, v2)

  # guardar resultado
  cc_vector[length(cc_vector) + 1] <- corr_coef
 
  }

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

# crear vector vacio
cc_vector <- NULL

# definir iteraciones
reps <- 100

# iniciar bucle
for(i in 1:reps)
{
  # generar variable 1
  v1 <- rnorm(n = 20, mean = 100, sd = 20)

  # generar variable 2
  v2 <- rnorm(n = 20, mean = 100, sd = 20)
  
  # correr correlacion
  corr_coef <- cor(v1, v2)

  # guardar resultado
  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:

# crear vector vacio 
cc_vector <- NULL

# definir iteraciones
reps <- 100

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

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

  # guardar resultado
  cc_vector[length(cc_vector) + 1] <- corr_coef

  # calcular diferencia absoluta solo luego de la primer iteracion
  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
  
}

Ejercicio 3


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

# load data
data("ChickWeight") 

# Convertir  a un data frame regular   
ChickWeight <- data.frame(ChickWeight, stringsAsFactors = FALSE)

# revisar las primeras filas 
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
# ver estructura
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 ...


3.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:

lista_datos <- list(iris, PlantGrowth, quakes)

df_nrow <- lapply(X = lista_datos, FUN = nrow)

df_nrow
## [[1]]
## [1] 150
## 
## [[2]]
## [1] 30
## 
## [[3]]
## [1] 1000
class(df_nrow)
## [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 = lista_datos, FUN = nrow)

df_nrow
## [1]  150   30 1000
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):

# funcion fuera del bucle
dims <- function(x) c(nrow(x), ncol(x))

# correr bucle
df_dims <- lapply(X = lista_datos, FUN = dims)

# ver resultados
head(df_dims, 3)
## [[1]]
## [1] 150   5
## 
## [[2]]
## [1] 30  2
## 
## [[3]]
## [1] 1000    5
# funcion dentro del loop
df_dims <- lapply(X = lista_datos, FUN = function(x) c(nrow(x), ncol(x)))

# ver resultados
head(df_dims, 3)
## [[1]]
## [1] 150   5
## 
## [[2]]
## [1] 30  2
## 
## [[3]]
## [1] 1000    5

 

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’.

 


Referencias


Información de la sesión

## R version 4.0.2 (2020-06-22)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 20.04 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
## 
## locale:
##  [1] LC_CTYPE=pt_BR.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=es_CR.UTF-8        LC_COLLATE=pt_BR.UTF-8    
##  [5] LC_MONETARY=es_CR.UTF-8    LC_MESSAGES=pt_BR.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.3.2      RColorBrewer_1.1-2 kableExtra_1.1.0   knitr_1.29        
## 
## loaded via a namespace (and not attached):
##  [1] Rcpp_1.0.5        pillar_1.4.6      compiler_4.0.2    tools_4.0.2      
##  [5] digest_0.6.25     evaluate_0.14     lifecycle_0.2.0   tibble_3.0.3     
##  [9] gtable_0.3.0      viridisLite_0.3.0 pkgconfig_2.0.3   rlang_0.4.7      
## [13] rstudioapi_0.11   yaml_2.2.1        xfun_0.16         withr_2.2.0      
## [17] httr_1.4.2        stringr_1.4.0     dplyr_1.0.2       xml2_1.3.2       
## [21] generics_0.0.2    vctrs_0.3.2       hms_0.5.3         tidyselect_1.1.0 
## [25] webshot_0.5.2     grid_4.0.2        glue_1.4.1        R6_2.4.1         
## [29] rmarkdown_2.3     farver_2.0.3      purrr_0.3.4       readr_1.3.1      
## [33] magrittr_1.5      scales_1.1.1      ellipsis_0.3.1    htmltools_0.5.0  
## [37] rvest_0.3.6       colorspace_1.4-1  labeling_0.3      stringi_1.4.6    
## [41] munsell_0.5.0     crayon_1.3.4