Bucle

  • Proceso automatizado de varios pasos organizados como secuencias de acciones (.e.g. 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 cantidades de datos (y buenas prácticas de programación).

2 formas básicas:

  1. Correr hasta que se cumple una condicion (bucles while & repeat)

loop types

* Modificado de Datacamp loops tutorial  

  1. Correr un número predefinido de veces. 2 tipos:
    1. Resultados de una iteración pueden ser utilizados en la proxima iteración (for loops)
    2. Resultados de una iteración NO pueden ser utilizados en la proxima iteración (iteraciones independientes, (X)apply's)

loop types 2

 

Nota: Cuando el resultado de una iteración es completamente independiente de otras iteraciones, la tarea podría ejecutarse en paralelo  

Bucle ‘while’

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

loop types 2

 

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

# definir correlacion por defecto en 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 resultados
  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
  • Añadiendo 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

 

Noten que append puede ser muy lento (no es recomendado)

 

Con un pequeño ajuste, un bucle while también puede evaluar varias condiciones a la vez. Por ejemplo, también podemos incluir valores altos 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 mayor que 0.8 o si el bucle ha estado funcionando durante más de 10 segundos (consejo: revise la funcióndifftime y / o as.numeric)


Bucles ‘repeat’

Los bucles con repeat también deben cumplir una condición para detenerse. Así que son bastante similar a bucles while. Sin embargo, se hace para que la acción se ejecute al menos una vez, independientemente de la condición de la evaluación.

loop repeat

 

El siguiente bucle repetir hace lo mismo que el buclewhile de arriba:

# 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 bucle debe detenerse. En el bucle while la condición determina si el bucle debe continuar.

 

Ejercicio 2


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

Bucles “for”

Por mucho, el bucle más popular. El número de iteraciones puede ser fijo y conocido 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 usandobreak:

# 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 aplicamos un umbral condicional.

El constructo 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 los 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 mayor que 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 tipos de conjuntos 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 son solo necesarias cuando la ‘acción’ a realizar requiere más de una línea de código.

& nbsp;

Podemos usar el cuadro de datos recién creado para excluir “cuadros sin 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 utilizando bucles for.

Ejercicio 3


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 del ejemplo “ChickWeight” describe el “peso en función de 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 el tiempo para cada polluelo (consejos: (1) use unique (ChickWeight $ Chick) dentro de la iniciación del bucle for y (2) use el subconjunto a través de la indexación dentro del cuerpo del bucle)

 

Bucles “(X)apply”

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

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

 

(X) apply son funciones de nivel superior que toman una función como entrada y lo aplican Sin embargo, los más utilizados son apply,sapply, lapply ytapply. Todos siguen la misma lógica:

aplicar repetir

& nbsp;

lapply toma un vector (atómico o 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 lista) y aplica la función a cada elemento, sin embargo, la salida es un vector atómico (si se puede empaquetar 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étalos 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 que realicen acciones personalizadas creando nuevas funciones (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 son más limpios 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 (con los datos de ‘ChickWeight’ calcule la correlación entre el peso y el tiempo para cada pollito)


4.2 Haga un bucle 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’.


B.1 Reúna los resultados del ejercicio 3.4 en un nuevo marco de datos con columnas para ‘chick’ y ‘correlation’ usando un bucle for (consejos: userbind)


B.2 ¿Cuántos de los ejemplos de conjuntos de datos de marcos de datos contenían una columna de factores?


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


Referencias


Información de la sesión

## 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/atlas/libblas.so.3.10.3
## LAPACK: /usr/lib/x86_64-linux-gnu/atlas/liblapack.so.3.10.3
## 
## locale:
##  [1] LC_CTYPE=es_ES.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=es_CR.UTF-8        LC_COLLATE=es_ES.UTF-8    
##  [5] LC_MONETARY=es_CR.UTF-8    LC_MESSAGES=es_ES.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   knitr_1.26        
## 
## loaded via a namespace (and not attached):
##  [1] Rcpp_1.0.3        pillar_1.4.2      compiler_3.6.1    tools_3.6.1      
##  [5] zeallot_0.1.0     digest_0.6.22     evaluate_0.14     tibble_2.1.3     
##  [9] gtable_0.3.0      viridisLite_0.3.0 pkgconfig_2.0.3   rlang_0.4.1      
## [13] rstudioapi_0.10   yaml_2.2.0        xfun_0.11         withr_2.1.2      
## [17] dplyr_0.8.3       httr_1.4.1        stringr_1.4.0     xml2_1.2.2       
## [21] vctrs_0.2.0       hms_0.5.2         tidyselect_0.2.5  webshot_0.5.1    
## [25] grid_3.6.1        glue_1.3.1        R6_2.4.1          rmarkdown_1.17   
## [29] purrr_0.3.3       readr_1.3.1       magrittr_1.5      backports_1.1.5  
## [33] scales_1.0.0      htmltools_0.4.0   assertthat_0.2.1  rvest_0.3.5      
## [37] colorspace_1.4-1  labeling_0.3      stringi_1.4.3     lazyeval_0.2.2   
## [41] munsell_0.5.0     crayon_1.3.4