R Language
Estructuras de flujo de control
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
-
[index]
es un nombre que toma exactamente un valor de[domain]
sobre cada iteración del bucle. -
[domain]
es un vector de valores sobre los cuales iterar. -
[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:
- Usando un bucle mal optimizado
- Usando un bucle bien optimizado para for
- Usando una
*apply
familia de funciones - 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
}