Control de flujo
Hoja de ruta
Leccion: 45 min
Prácticas: 20 minPreguntas
¿Cómo puedo hacer elecciones dependiendo de mis datos en R?
¿Cómo puedo repetir operaciones en R?
Objetivos
Escribir declaraciones condicionales utilizando
if()
yelse()
.Escribir y entender los bucles con
for()
.
A menudo, cuando estamos programando deseamos controlar el flujo de nuestras acciones. Esto se puede realizar estableciendo acciones para que ocurran solo si se cumple una condición o un conjunto de condiciones. Alternativamente, también podemos establecer que una acción ocurra un número determinado de veces.
Hay varias maneras de controlar el flujo en R. Para declaraciones condicionales, los enfoques más comúnmente utilizados son los constructs:
# if
if (la condición es verdad) {
realizar una acción
}
# if ... else
if (la condición es verdad) {
realizar una acción
} else { # es decir, si la condición es falsa,
realizar una acción alternativa
}
Digamos, por ejemplo, que queremos que R imprima un mensaje si una variable x
tiene un valor en particular:
# Muestrea un numero al azar de una distribución de Poisson
# con una media (lambda) de 8
x <- rpois(1, lambda=8)
if (x >= 10) {
print("x es mayor o igual que 10")
}
## [1] "x es mayor o igual que 10"
x
## [1] 10
Ten en cuenta que puedes no obtener el mismo resultado que tu vecino porque se generaron diferentes números aleatorios de una misma distribución.
Vamos a establecer una semilla (seed) para que todos generemos el mismo número ‘pseudo-aleatorio’, y luego obtener más información:
set.seed(10)
x <- rpois(1, lambda=8)
if (x >= 10) {
print("x es mayor o igual a 10")
} else if (x > 5) {
print("x es mayor a 5")
} else {
print("x es menor a 5")
}
## [1] "x es mayor a 5"
Tip: números pseudo-aleatorios
En el caso anterior, la función
rpois()
genera un número aleaotorio de una distribución de Poisson con una media (ej. lambda) de 8. La funciónset.seed()
garantiza que todas las computadoras generarán el mismo número ‘pseudo-aleatorio’ (más información sobre los números pseudo-aleatorios). Entonces si nosotros escribimosset.seed(10)
, vemos quex
toma el valor de 8. Deberias obtener el mismo número exacto.
Importante: cuando R evalua las condiciones dentro de if()
, esta buscando
elementos logicos, ej., TRUE
o FALSE
. Esto puede ser un dolor de cabeza para
los principiantes. Por ejemplo:
x <- 4 == 3
if (x) {
"4 igual a 3"
}
Como podemos ver, el mensaje no se mostro porque el resultado del vector x es FALSE
x <- 4 == 3
x
## [1] FALSE
Desafío 1
Usa una declaración
if()
para mostrar un mensaje adecuado reportando si hay algún registro de 2002 en el dataset degapminder
. Ahora haz lo mismo para el año 2012.Solución al Desafío 1
Primero veremos una solución al Desafío 1 que no usa la funcion
any()
. Primero obtenemos un vector lógico que desscribe que el elementogapminder$year
es igual a2002
:gapminder[(gapminder$year == 2002),]
Luego, contamos el número de filas del data.frame
gapminder
que corresponde al año 2002:rows2002_number <- nrow(gapminder[(gapminder$year == 2002),])
La presencia de cualquier registro para el año 2002 es equivalente a la petición de que
rows2002_number
sea uno o más:rows2002_number >= 1
Poniendo esto junto, nosotros obtenemos:
if(nrow(gapminder[(gapminder$year == 2002),]) >= 1){ print("Se encontraron registro(s) para el año 2002.") }
Todo esto se puede hacer más rápido con
any()
. La condición lógica se puede expresar como:if(any(gapminder$year == 2002)){ print("Se encontraron registro(s) para el año 2002.") }
¿Alguien recibió un mensaje de advertencia como este?
## Error in eval(expr, envir, enclos): object 'gapminder' not found
Si tu condición se evalúa como un vector con más de un elemento lógico,
la función if()
todavía se ejecutará, pero solo evaluará la condición en el primer
elemento. Aquí debes asegurarte de que tu condición sea de longitud 1.
Tip:
any()
yall()
La función
any()
devolverá TRUE si al menos un valor TRUE se encuentra dentro del vector, en caso contrario devolveráFALSE
. Esto se puede usar de manera similar al operador%in%
. La funciónall()
, como el nombre sugiere, devolveraTRUE
si todos los valores en el vector sonTRUE
.
Operaciones repetidas
Si quieres iterar
sobre un conjunto de valores, cuando el orden de iteración es importante, y realizar
la misma operación en cada uno, un bucle for()
hará el trabajo.
Vimos los bucles for()
en las lecciones anteriores de terminal. Esta es la
operación de bucle más flexible, pero también la más dificil de usar
correctamente. Evita usar bucles for()
a menos que el orden de la iteración sea importante:
i.e. Cuando el cálculo de cada interación dependa del resultado de la interación previa.
La estructura básica de un bucle for()
es:
for(iterador en conjunto de valores){
haz alguna acción
}
Por ejemplo:
for(i in 1:10){
print(i)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9
## [1] 10
El rango 1:10
crea un vector sobre la marcha; puedes iterar
sobre cualquier otro vector también.
Podemos usar un bucle for()
anidado con otro bucle for()
para iterar sobre dos cosas
a la vez.
for(i in 1:5){
for(j in c('a', 'b', 'c', 'd', 'e')){
print(paste(i,j))
}
}
## [1] "1 a"
## [1] "1 b"
## [1] "1 c"
## [1] "1 d"
## [1] "1 e"
## [1] "2 a"
## [1] "2 b"
## [1] "2 c"
## [1] "2 d"
## [1] "2 e"
## [1] "3 a"
## [1] "3 b"
## [1] "3 c"
## [1] "3 d"
## [1] "3 e"
## [1] "4 a"
## [1] "4 b"
## [1] "4 c"
## [1] "4 d"
## [1] "4 e"
## [1] "5 a"
## [1] "5 b"
## [1] "5 c"
## [1] "5 d"
## [1] "5 e"
En lugar de mostrar los resultados, podriamos escribir lor resultados en un nuevo objeto.
output_vector <- c()
for(i in 1:5){
for(j in c('a', 'b', 'c', 'd', 'e')){
temp_output <- paste(i, j)
output_vector <- c(output_vector, temp_output)
}
}
output_vector
## [1] "1 a" "1 b" "1 c" "1 d" "1 e" "2 a" "2 b" "2 c" "2 d" "2 e" "3 a"
## [12] "3 b" "3 c" "3 d" "3 e" "4 a" "4 b" "4 c" "4 d" "4 e" "5 a" "5 b"
## [23] "5 c" "5 d" "5 e"
Este enfoque puede ser útil, pero “aumentará o crecerá tus resultados” (construir el objeto resultante de forma incremental) es computacionalmente ineficiente, así que evítalo cuando estés iterando a través de muchos valores.
Tip: no crezcas tus resultados
Una de las cosas más importantes que hace tropezar tanto a los principiantes como a los usuarios experimentados de R es la construcción de un objeto de resultados (vector, lista, matriz, data frame) a medida que progresas en el bucle for. Las computadoras son muy malas para manejar esto, por lo que tus cálculos pueden alentarse muy rápidamente. Es mucho mejor definir un objeto de resultados vacío de primera mano con las dimensiones apropiadas. Entonces, si sabe que el resultado final se almacenará en una matriz como la anterior, cree una matriz vacía con 5 filas y 5 columnas, luego en cada iteración almacene los resultados en la ubicación adecuada.
Una mejor manera es definir el objeto de salida (vacio) antes de completar los valores. Para este ejemplo, parece más complicado, pero es aún más eficiente.
output_matrix <- matrix(nrow=5, ncol=5)
j_vector <- c('a', 'b', 'c', 'd', 'e')
for(i in 1:5){
for(j in 1:5){
temp_j_value <- j_vector[j]
temp_output <- paste(i, temp_j_value)
output_matrix[i, j] <- temp_output
}
}
output_vector2 <- as.vector(output_matrix)
output_vector2
## [1] "1 a" "2 a" "3 a" "4 a" "5 a" "1 b" "2 b" "3 b" "4 b" "5 b" "1 c"
## [12] "2 c" "3 c" "4 c" "5 c" "1 d" "2 d" "3 d" "4 d" "5 d" "1 e" "2 e"
## [23] "3 e" "4 e" "5 e"
Tip: Bucles While
Algunas veces tendras la necesidad de repetir una operación hasta que cierta condición se cumpla. Puedes hacer esto con un bucle
while()
.while(mientras esta condición es verdad){ haz algo }
Como ejemplo, aquí hay un bucle while que genera números aleatorios a partir de una distribución uniforme (la función
runif()
) entre 0 y 1 hasta que obtiene uno que es menor a 0.1.z <- 1 while(z > 0.1){ z <- runif(1) print(z) }
Los bucle
while()
no siempre seran apropiados. Tienes que ser particularmente cuidadoso de no terminar en un bucle infinito porque tu condición nunca se cumple.
Desafío 2
Compara los objetos output_vector y output_vector2. ¿Son lo mismo? Si no, ¿por qué no? ¿Cómo cambiarías el último bloque de código para hacer output_vector2 igual a output_vector?
Solución al Desafío 2
Podemos verificar si dos vectores son idénticos usando la función
all()
:all(output_vector == output_vector2)
Sin embargo, todos los elementos de
output_vector
se pueden encontrar enoutput_vector2
:all(output_vector %in% output_vector2)
y vice versa:
all(output_vector2 %in% output_vector)
Por lo tanto, el elemento en
output_vector
youtput_vector2
estan en orden distinto. Esto es porqueas.vector ()
genera los elementos de una matriz de entrada que pasa por su columna. Echando un vistazo aoutput_matrix
, podemos notar que queremos sus elementos por filas. La solución es transponer laoutput_matrix
. Podemos hacerlo llamando a la función de transposiciónt ()
o ingresando los elementos en el orden correcto. La primera solución requiere cambiar el originaloutput_vector2 <- as.vector(output_matrix)
into
output_vector2 <- as.vector(t(output_matrix))
La segunda solución requiere cambiar
output_matrix[i, j] <- temp_output
into
output_matrix[j, i] <- temp_output
Desafío 3
Escribe un script que a través de bucles recorra los datos
gapminder
por continente e imprima si la esperanza de vida media es menor o mayor que 50 años.Solución al Desafío 3
Paso 1: Queremos asegurarnos de que podamos extraer todos los valores únicos del vector continente
gapminder <- read.csv("data/gapminder-FiveYearData.csv") unique(gapminder$continent)
Paso 2: También tenemos que recorrer cada uno de estos continentes y calcular la esperanza de vida promedio para cada “subconjunto” de datos. Podemos hacer eso de la siguiente manera:
- Pasa por encima de cada uno de los valores únicos de ‘continente’
- Para cada valor de continente, crea una variable temporal que almacene la vida útil para ese subconjunto,
- Regresar la expectativa de vida calculada al usuario imprimiendo el resultado:
for( iContinent in unique(gapminder$continent) ){ tmp <- mean(subset(gapminder, continent==iContinent)$lifeExp) cat("Average Life Expectancy in", iContinent, "is", tmp, "\n") rm(tmp) }
Paso 3: El ejercicio solo requiere que se imprima el resultado si la expectativa de vida promedio es menor a 50 o superior a 50. Por lo tanto, debemos agregar una condición ‘if’ antes de imprimir, lo cual evalúa si la expectativa de vida promedio calculada es superior o inferior a un umbral, e imprime una salida condicional en el resultado. Necesitamos corregir (3) desde arriba:
3a. Si la esperanza de vida calculada es menor que algún umbral (50 años), devuelva el continente y una declaración de que la esperanza de vida es menor que el umbral, de lo contrario devuelva el continente y una declaración de que la esperanza de vida es mayor que el umbral ,:
thresholdValue <- 50 for( iContinent in unique(gapminder$continent) ){ tmp <- mean(subset(gapminder, continent==iContinent)$lifeExp) if(tmp < thresholdValue){ cat("Average Life Expectancy in", iContinent, "is less than", thresholdValue, "\n") } else{ cat("Average Life Expectancy in", iContinent, "is greater than", thresholdValue, "\n") } # end if else condition rm(tmp) } # end for loop
Desafío 4
Modifica el script del Desafío 4 por un bucle sobre cada uno de los paises. Qué esta vez, imprima si la esperanza de vida es menor que 50, entre 50 y 70, o mayor que 70.
Solución al Desafío 4
Modificamos nuestra solución al Reto 3 agregando ahora dos umbrales,
lowerThreshold
yupperThreshold
y extendiendo nuestras declaraciones if-else:lowerThreshold <- 50 upperThreshold <- 70 for( iCountry in unique(gapminder$country) ){ tmp <- mean(subset(gapminder, country==iCountry)$lifeExp) if(tmp < lowerThreshold){ cat("Average Life Expectancy in", iCountry, "is less than", lowerThreshold, "\n") } else if(tmp > lowerThreshold && tmp < upperThreshold){ cat("Average Life Expectancy in", iCountry, "is between", lowerThreshold, "and", upperThreshold, "\n") } else{ cat("Average Life Expectancy in", iCountry, "is greater than", upperThreshold, "\n") } rm(tmp) }
Desafío 5 - Avanzado
Escribir un script que con un bucle recorra cada país en el dataset
gapminder
, prueba si el país comienza con una ‘B’ y grafica la esperanza de vida contra el tiempo como un gráfico de líneas si la esperanza de vida media es menor de 50 años.Solución para el Desafío 5
Usaremos el comando
grep
que se introdujo en la lección Shell de Unix para encontrar países que comiencen con” B “.” Vamos a entender cómo hacer esto primero. Siguiendo la sección de shell de Unix, podemos tener la tentación de probar lo siguientegrep("^B", unique(gapminder$country))
Pero cuando evaluamos este comando, regresa los índices de la variable del factor
country
que comienza con” B “. Para obtener los valores, debemos agregar la opciónvalue = TRUE
al comandogrep
:grep("^B", unique(gapminder$country), value=TRUE)
Ahora almacenaremos estos países en una variable llamada candidateCountries, y luego con un bucle recorremos cada entrada en la variable. Dentro del bucle, evaluamos la expectativa de vida promedio para cada país, y si la expectativa de vida promedio es menor a 50, usamos un gráfico base para trazar la evolución de la expectativa de vida promedio:
thresholdValue <- 50 candidateCountries <- grep("^B", unique(gapminder$country), value=TRUE) for( iCountry in candidateCountries){ tmp <- mean(subset(gapminder, country==iCountry)$lifeExp) if(tmp < thresholdValue){ cat("Average Life Expectancy in", iCountry, "is less than", thresholdValue, "plotting life expectancy graph... \n") with(subset(gapminder, country==iCountry), plot(year,lifeExp, type="o", main = paste("Life Expectancy in", iCountry, "over time"), ylab = "Life Expectancy", xlab = "Year" ) # end plot ) # end with } # end for loop rm(tmp) }
Puntos Clave
Usar
if
yelse
para realizar elecciones.Usar
for
para operaciones repetidas.