Buscar..


Observaciones

Los bucles son un método de control de flujo para repetir una tarea o un conjunto de tareas en un dominio. La estructura central de un bucle for es

for ( [index] in [domain]){
  [body]
}

Dónde

  1. [index] es un nombre que toma exactamente un valor de [domain] sobre cada iteración del bucle.
  2. [domain] es un vector de valores sobre los cuales iterar.
  3. [body] es el conjunto de instrucciones para aplicar en cada iteración.

Como ejemplo trivial, considere el uso de un bucle for para obtener la suma acumulativa de un vector de valores.

x <- 1:4
cumulative_sum <- 0
for (i in x){
  cumulative_sum <- cumulative_sum + x[i]
}
cumulative_sum

Optimizando la estructura de los bucles for

Para los bucles puede ser útil para conceptualizar y ejecutar tareas para repetir. Si no es cuidadosamente construido, sin embargo, pueden ser muy lento para ejecutar en comparación con el preferido usado del apply familia de funciones. No obstante, hay un puñado de elementos que puede incluir en su construcción de bucle for para optimizar el bucle. En muchos casos, una buena construcción del bucle for dará una eficiencia computacional muy cercana a la de una función de aplicación.

Un 'construido adecuadamente' para un bucle se basa en la estructura central e incluye una declaración que declara el objeto que capturará cada iteración del bucle. Este objeto debe tener una clase y una longitud declarada.

[output] <- [vector_of_length]
for ([index] in [length_safe_domain]){
  [output][index] <- [body]
}

Para ilustrar, escribamos un bucle para cuadrar cada valor en un vector numérico (este es un ejemplo trivial solo para ilustración. La forma 'correcta' de completar esta tarea sería x_squared <- x^2 ).

x <- 1:100
x_squared <- vector("numeric", length = length(x))
for (i in seq_along(x)){
  x_squared[i] <- x[i]^2
}

Nuevamente, note que primero x_squared un receptáculo para la salida x_squared , y le dimos la clase "numérica" ​​con la misma longitud que x . Además, seq_along un "dominio seguro de longitud" utilizando la función seq_along . seq_along genera un vector de índices para un objeto que es adecuado para usar en bucles. Si bien parece intuitivo de usar for (i in 1:length(x)) , si x tiene una longitud de 0, el bucle intentará iterar sobre el dominio de 1:0 , lo que generará un error (el índice 0 no está definido en R ).

Objetos receptáculo y dominios seguros de longitud se manejan internamente por el apply familia de funciones y los usuarios se les anima a adoptar el apply el enfoque en el lugar de los bucles tanto como sea posible. Sin embargo, si se construye correctamente, un bucle for ocasionalmente puede proporcionar una mayor claridad de código con una pérdida mínima de eficiencia.

Vectorización para bucles

A menudo, los bucles pueden ser una herramienta útil para conceptualizar las tareas que deben completarse dentro de cada iteración. Cuando el bucle está completamente desarrollado y conceptualizado, puede haber ventajas en convertir el bucle en una función.

En este ejemplo, desarrollaremos un bucle for para calcular la media de cada columna en el conjunto de datos mtcars (de nuevo, un ejemplo trivial como podría lograrse a través de la función colMeans ).

column_mean_loop <- vector("numeric", length(mtcars))
for (k in seq_along(mtcars)){
  column_mean_loop[k] <- mean(mtcars[[k]])
}

El bucle for se puede convertir en una función de aplicación reescribiendo el cuerpo del bucle como una función.

col_mean_fn <- function(x) mean(x)
column_mean_apply <- vapply(mtcars, col_mean_fn, numeric(1))

Y para comparar los resultados:

identical(column_mean_loop, 
          unname(column_mean_apply)) #* vapply added names to the elements
                                     #* remove them for comparison

Las ventajas de la forma vectorizada es que pudimos eliminar algunas líneas de código. La función de aplicación se encarga de la mecánica de determinar la longitud y el tipo del objeto de salida y la iteración de un dominio seguro de longitud. Además, la función de aplicación es un poco más rápida que el bucle. La diferencia de velocidad es a menudo despreciable en términos humanos según el número de iteraciones y la complejidad del cuerpo.

Básico para construcción de bucle

En este ejemplo, calcularemos la desviación al cuadrado para cada columna en un marco de datos, en este caso los mtcars .

Opción A: índice entero

squared_deviance <- vector("list", length(mtcars))
for (i in seq_along(mtcars)){
  squared_deviance[[i]] <- (mtcars[[i]] - mean(mtcars[[i]]))^2
}

squared_deviance es una lista de 11 elementos, como se esperaba.

class(squared_deviance)
length(squared_deviance)

Opción B: índice de caracteres

squared_deviance <- vector("list", length(mtcars))
Squared_deviance <- setNames(squared_deviance, names(mtcars))
for (k in names(mtcars)){
  squared_deviance[[k]] <- (mtcars[[k]] - mean(mtcars[[k]]))^2
}

¿Qué pasa si queremos un data.frame como resultado? Bueno, hay muchas opciones para transformar una lista en otros objetos. Sin embargo, y tal vez el más simple en este caso, será la de almacenar la for los resultados en un data.frame .

squared_deviance <- mtcars #copy the original
squared_deviance[TRUE]<-NA  #replace with NA or do squared_deviance[,]<-NA
for (i in seq_along(mtcars)){
  squared_deviance[[i]] <- (mtcars[[i]] - mean(mtcars[[i]]))^2
}
dim(squared_deviance)
[1] 32 11

El resultado será el mismo evento aunque usemos la opción de carácter (B).

Construcción óptima de un bucle for

Para ilustrar el efecto de bueno para la construcción de bucles, calcularemos la media de cada columna de cuatro maneras diferentes:

  1. Usando un bucle mal optimizado
  2. Usando un bucle bien optimizado para for
  3. Usando una *apply familia de funciones
  4. Usando la función colMeans

Cada una de estas opciones se mostrará en código; se mostrará una comparación del tiempo computacional para ejecutar cada opción; y finalmente se dará una discusión de las diferencias.

Mal optimizado para bucle

column_mean_poor <- NULL
for (i in 1:length(mtcars)){
  column_mean_poor[i] <- mean(mtcars[[i]])
}

Bien optimizado para loop

column_mean_optimal <- vector("numeric", length(mtcars))
for (i in seq_along(mtcars)){
  column_mean_optimal <- mean(mtcars[[i]])
}

Función vapply

column_mean_vapply <- vapply(mtcars, mean, numeric(1))

Función colMeans

column_mean_colMeans <- colMeans(mtcars)

Comparacion de eficiencia

Los resultados de la evaluación comparativa de estos cuatro enfoques se muestran a continuación (código no mostrado)

Unit: microseconds
     expr     min       lq     mean   median       uq     max neval  cld
     poor 240.986 262.0820 287.1125 275.8160 307.2485 442.609   100    d
  optimal 220.313 237.4455 258.8426 247.0735 280.9130 362.469   100   c 
   vapply 107.042 109.7320 124.4715 113.4130 132.6695 202.473   100 a   
 colMeans 155.183 161.6955 180.2067 175.0045 194.2605 259.958   100  b

Observe que el bucle optimizado for superado el bucle mal construido para. El mal construido para el bucle aumenta constantemente la longitud del objeto de salida, y en cada cambio de la longitud, R está reevaluando la clase del objeto.

Parte de esta carga general se elimina mediante el bucle optimizado al declarar el tipo de objeto de salida y su longitud antes de iniciar el bucle.

En este ejemplo, sin embargo, el uso de una función vapply duplica la eficiencia computacional, en gran parte porque le dijimos a R que el resultado tenía que ser numérico (si alguno de los resultados no fuera numérico, se devolvería un error).

El uso de la función colMeans es un toque más lento que la función vapply . Esta diferencia es atribuible a algunas verificaciones de errores realizadas en colMeans y principalmente a la conversión as.matrix (porque mtcars es un data.frame ) que no se realizaron en la función vapply .

Las otras construcciones en bucle: mientras que y repita

R proporciona dos construcciones de bucle adicionales, while repeat , que normalmente se utilizan en situaciones en las que el número de iteraciones requeridas es indeterminado.


El while de bucle

La forma general de un while de bucle es como sigue,

while (condition) {
    ## do something
    ## in loop body
}

donde se evalúa la condition antes de ingresar al cuerpo del bucle. Si la condition evalúa como TRUE , el código dentro del cuerpo del bucle se ejecuta, y este proceso se repite hasta que la condition evalúa en FALSE (o se alcanza una declaración de break ; consulte más abajo). A diferencia de la for bucle, si un while de bucle utiliza una variable para realizar iteraciones incrementales, la variable debe ser declarado e inicializado antes de tiempo, y debe actualizarse dentro del cuerpo del bucle. Por ejemplo, los siguientes bucles realizan la misma tarea:

for (i in 0:4) {
    cat(i, "\n")
}
# 0 
# 1 
# 2 
# 3 
# 4 

i <- 0
while (i < 5) {
    cat(i, "\n")
    i <- i + 1
}
# 0 
# 1 
# 2 
# 3 
# 4 

En el while bucle anterior, la línea i <- i + 1 es necesario para evitar un bucle infinito.


Además, es posible poner fin a un while de bucle con una llamada a break desde el interior del cuerpo del ciclo:

iter <- 0
while (TRUE) {
    if (runif(1) < 0.25) {
        break
    } else {
        iter <- iter + 1
    }
}
iter
#[1] 4

En este ejemplo, la condition siempre es TRUE , por lo que la única forma de terminar el bucle es con una llamada a break dentro del cuerpo. Tenga en cuenta que el valor final de iter dependerá del estado de su PRNG cuando se ejecute este ejemplo, y debe producir resultados diferentes (esencialmente) cada vez que se ejecuta el código.


El bucle de repeat

La construcción repeat es esencialmente la misma que while (TRUE) { ## something } , y tiene la siguiente forma:

repeat ({
    ## do something
    ## in loop body
})

Los {} extra no son necesarios, pero los () son. Reescribiendo el ejemplo anterior usando repeat ,

iter <- 0
repeat ({
    if (runif(1) < 0.25) {
        break
    } else {
        iter <- iter + 1
    }
})
iter
#[1] 2 

Más sobre break

Es importante tener en cuenta que la break solo terminará el bucle de cierre inmediato . Es decir, lo siguiente es un bucle infinito:

while (TRUE) {
    while (TRUE) {
        cat("inner loop\n")
        break
    }
    cat("outer loop\n")
}

Sin embargo, con un poco de creatividad, es posible romper por completo dentro de un bucle anidado. Como ejemplo, considere la siguiente expresión, que, en su estado actual, tendrá un bucle infinito:

while (TRUE) {
    cat("outer loop body\n")
    while (TRUE) {
        cat("inner loop body\n")
        x <- runif(1)
        if (x < .3) {
            break
        } else {
            cat(sprintf("x is %.5f\n", x))
        }
    }
}

Una posibilidad es reconocer que, a diferencia de break , el return expresión no tiene la capacidad de devolver el control a través de múltiples niveles de bucles que encierran. Sin embargo, dado que el return solo es válido cuando se usa dentro de una función, no podemos simplemente reemplazar break con return() arriba, sino que también necesitamos envolver la expresión completa como una función anónima:

(function() {
    while (TRUE) {
        cat("outer loop body\n")
        while (TRUE) {
            cat("inner loop body\n")
            x <- runif(1)
            if (x < .3) {
                return()
            } else {
                cat(sprintf("x is %.5f\n", x))
            }
        }
    }
})()

Alternativamente, podemos crear una variable ficticia ( exit ) antes de la expresión, y activarla mediante <<- desde el bucle interno cuando estemos listos para terminar:

exit <- FALSE
while (TRUE) {
    cat("outer loop body\n")
    while (TRUE) {
        cat("inner loop body\n")
        x <- runif(1)
        if (x < .3) {
            exit <<- TRUE
            break
        } else {
            cat(sprintf("x is %.5f\n", x))
        }
    }
    if (exit) break
}


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow