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: verifique la funcióndifftime y / o as.numeric)


Bucles ‘repeat’

Los bucles repeate 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 repeate 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 repeate el buclewhile 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 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 marco de datos recién creado para excluir “marcos que no son de datos”:

# 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 marco de datos podemos explorar más a fondo la estructura de los conjuntos de datos de ejemplo usando bucles for.

Ejercicio 2


3.1 Haga un bucle for que devuelva el número de columnas para cada conjunto de datos de ejemplo


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


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


3.4 El conjunto 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)

 

(X) aplicar bucles

(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 conjunto 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 buclefor en el ejercicio 3.4 (* utilizando los datos ‘ChickWeight’ calcule la correlación entre peso y tiempo para cada Chick *)


4.2 Haga un ciclo de ‘sapply’ para calcular la correlación promedio para cada uno de los tipos de Dieta.


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

Ejercicios extra


E.1 Reúna los resultados del ejercicio 3.4 en un nuevo marco de datos con columnas para ‘pollito’ y ‘correlación’ usando un bucle for (consejo: use rbind)


E.2 ¿Cuántos de los ejemplos de conjuntos de datos del marco de datos contenían una columna de factor?


E.3 Calcule el coeficiente de variación de cada variable numérica por especie en el conjunto 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 19.04
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.8.0
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.8.0
## 
## 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.1.1      RColorBrewer_1.1-2 kableExtra_1.1.0  
## [4] knitr_1.23        
## 
## loaded via a namespace (and not attached):
##  [1] Rcpp_1.0.1        pillar_1.4.0      compiler_3.6.1   
##  [4] plyr_1.8.4        tools_3.6.1       digest_0.6.19    
##  [7] evaluate_0.14     tibble_2.1.1      gtable_0.3.0     
## [10] viridisLite_0.3.0 pkgconfig_2.0.2   rlang_0.3.4      
## [13] rstudioapi_0.10   yaml_2.2.0        xfun_0.7         
## [16] withr_2.1.2       httr_1.4.0        stringr_1.4.0    
## [19] dplyr_0.8.0.1     xml2_1.2.0        hms_0.4.2        
## [22] tidyselect_0.2.5  webshot_0.5.1     grid_3.6.1       
## [25] glue_1.3.1        R6_2.4.0          rmarkdown_1.13   
## [28] purrr_0.3.2       readr_1.3.1       magrittr_1.5     
## [31] scales_1.0.0      htmltools_0.3.6   assertthat_0.2.1 
## [34] rvest_0.3.3       colorspace_1.4-1  labeling_0.3     
## [37] stringi_1.4.3     lazyeval_0.2.2    munsell_0.5.0    
## [40] crayon_1.3.4