Estructuras de control de flujo

Condicionales y Bucles

Como hemos visto hasta ahora, cuando escribimos código y lo ejecutamos el mismo será interpretando línea a línea, hasta que se ejecuta la última línea y el programa termina. Las estructuras de control de flujo permiten alterar este comportamiento para adecuarlo a nuestras necesidades.

En general podemos definir dos grandes tipos de estructuras para el control de flujo. Por un lado, aquellas estructuras que evalúan condiciones y ejecutan un código subsiguiente en función de si el resultado de esa evaluación es verdadero o falso, a estas estructuras las llamamos condicionales. Por otro lado, tenemos estructuras que repiten iterativamente una porción de código mientras se cumpla alguna condición dada, a estas estructuras las denominamos bucles.

Nota: Antes de continuar es recomendable repasar los operadores aprendidos en el primer día de clases.

If … else

El constructo if ... else es una estructura de tipo condicional. En pocas palabras, if evaluará una condición dada y si la misma se cumple (i.e. el resultado es TRUE), se ejecutará el código correspondiente. Para definir un bloque de código a ejecutarse si no se cumple la condición previamente establecida, utilizaremos else. Si además quisiéramos definir múltiples condiciones, en caso de que la primera no se cumpla y que deriven en diferentes ejecuciones, deberemos utilizar else if.

if (TRUE) {
  print("Se ejecutará el código bajo 'if' ya que la condición resulta TRUE")
}
[1] "Se ejecutará el código bajo 'if' ya que la condición resulta TRUE"
# Invertimos el TRUE usando el operador ! para obtener FALSE
if (!TRUE) {
  print("Se ejecutará el código bajo 'if' ya que la condición resulta TRUE")
} else {
  print("Se ejecutará el código bajo 'else' dado que la condición resulta FALSE")
}
[1] "Se ejecutará el código bajo 'else' dado que la condición resulta FALSE"

Múltiples condiciones

En ocasiones necesitamos establecer múltiples condiciones. Ejecute el código siguiente varias veces, pero cambiando cada vez el valor de a.

a <- 25

if (a >= 40) {
  print("a es mayor o igual que 40")
} else if (a >= 30) {
  print("a es mayor o igual que 30")
} else if (a >= 10) {
  print("a es mayor o igual que 10")
} else {
  print("a es menor a 10")
}
[1] "a es mayor o igual que 10"

Para pensar: ¿Qué pasa cuando, por ejemplo, a = 35? ¿Cuántas evaluaciones independientes serían TRUE? ¿Qué porción de código se ejecuta y por qué?

And / or

Es posible agrupar diferentes evaluaciones utilizando los operadores Y (&) y O (|). Ejecute el siguiente código varias veces cambiando los valores de a, b y c.

a <- 35
b <- 12
c <- 65

if (a < 40 & b > 10 | c > 50) {
  print("'a' es menor a 40 y 'b' mayor a 10 ó 'c' es mayor a 50")
} else {
  print("No se cumple ninguna condición")
}
[1] "'a' es menor a 40 y 'b' mayor a 10 ó 'c' es mayor a 50"

Ejercicio 1

Compare el código de la celda precedente con el de aquí abajo. ¿Qué diferencias nota? Ejecútelo y explique el resultado con el obtenido usando el código previo con los mismos valores para a, b y c.

a <- 52
b <- 8
c <- 65

if (a < 40 & (b > 10 | c > 50)) {
  print("'a' es menor a 40 y 'b' mayor a 10 ó 'c' es mayor a 50")
} else {
  print("No se cumple ninguna condición")
}

If anidados

Recién vimos cómo combinar varias evaluaciones en una misma expresión, pero frecuentemente lo que necesitaremos es realizar diferentes evaluaciones de forma secuencial y ejecutar código consecuentemente a los resultados obtenidos. Para ello podemos incorporar evaluaciones dentro de una evaluación previa, es decir, anidar las evaluaciones.

a <- 25

if (a > 10) {
  print("'a' es mayor que 10")
  if (a > 20) {
    print("y también es mayor que 20")
    if (a > 30) {
      print("y también es mayor que 30")
    } else {
      print("pero no mayor que 30")
    }
  }
}
[1] "'a' es mayor que 10"
[1] "y también es mayor que 20"
[1] "pero no mayor que 30"

Para pensar: ¿Qué sucede si a <= 10? ¿Qué podemos aprender de esta situación? ¿Cómo podríamos salvar el inconveniente?

Ejercicio 2

Andrea y Ariel fueron a hacer compras, construya con código que indique quién gastó más dinero y de cuanto es la diferencia. El mismo código debe servir para evaluar los tres escenarios siguientes:

  • Escenario Nº 1: compra_andrea = 690 / compra_ariel = 730

  • Escenario Nº 2: compra_andrea = 745 / compra_ariel = 745

  • Escenario Nº 3: compra_andrea = 890 / compra_ariel = 730

Ayudita: La salida esperada combina texto y variables, por ejemplo “Ariel gasto x pesos más que Andrea”. Para generar una salida de este tipo, podemos utilizar la función paste() de la siguiente manera:

variable <- 25
print(paste("La variable vale", variable))
[1] "La variable vale 25"

Si no usáramos paste() obtendríamos un error:

print("La variable vale", variable)
Error in print.default("La variable vale", variable): invalid printing digits 25

While …

El constructo while es una estructura de tipo bucle. En pocas palabras, evaluará una condición y ejecutará repetitivamente una porción de código siempre y cuando la misma se cumpla (i.e. el resultado sea TRUE).

i <- 1
while (i < 4) {
  print(paste("Repetición nº:", i))
  i <- i + 1
}
[1] "Repetición nº: 1"
[1] "Repetición nº: 2"
[1] "Repetición nº: 3"

Atención!!! Si en el bucle precedente omitimos incrementar el contador i, nuestro bucle se ejecutará indefinidamente.

While en vectores u otros iterables

Teniendo en cuenta lo que aprendimos sobre el acceso a elementos de un vector, podemos utilizar un bucle while para acceder a los mismos. No obstante, existen formas más eficientes de hacer esto y lo veremos posteriormente.

carreras <- c('Biología', 'Bioquímica', 'Medicina', 'Nutrición')
i <- 1
while (i <= length(carreras)) {
  print(carreras[i])
  i <- i + 1
}
[1] "Biología"
[1] "Bioquímica"
[1] "Medicina"
[1] "Nutrición"

Rompiendo un bucle

Si fuera necesario, podemos utilizar la declaración break para interrumpir la ejecución de un bucle, dada una condición establecida.

i <- 1
while (i < 12) {
  print(paste("Repetición nº:", i))
  if (i == 5) {
    break
  }
  i <- i + 1
}
[1] "Repetición nº: 1"
[1] "Repetición nº: 2"
[1] "Repetición nº: 3"
[1] "Repetición nº: 4"
[1] "Repetición nº: 5"

Saltando dentro de un bucle

De forma similar podemos, dada una condición, saltearnos ciertas ejecuciones dentro de un bucle. Para ello, utilizaremos la declaración next.

i <- 0
while (i < 9) {
  i <- i + 1
  if (i > 2 & i < 7) {
    next
  }
  else {
    print(paste("Repetición nº:", i))
  }
}
[1] "Repetición nº: 1"
[1] "Repetición nº: 2"
[1] "Repetición nº: 7"
[1] "Repetición nº: 8"
[1] "Repetición nº: 9"

Ejercicio 3

Analice el siguiente código SIN EJECUTARLO e interprete cuál es la salida esperada explicando que sucede en cada iteración. Luego escriba a mano la salida exacta que dará este código.

compras <- c("pan", "queso", "jamón", "tomate", "lechuga")

i <- 1
while (i <= length(compras)) {
  if (i == 1) {
    print(paste("En mis lista de compras hay", compras[i]))
  }
  else if (i == 2) {
    print(paste("y también hay", compras[i]))
  }
  else {
    print(paste("y", compras[i]))
  }
  i <- i + 1
}

For … in …

El constructo for es otra estructura de tipo bucle que permite iterar sobre una colección dada de elementos. En pocas palabras, repetirá la ejecución de un mismo bloque de código tantas veces como elementos existan en dicha secuencia. Por ello, es importante recordar que su sintaxis básica es:

for (item in colección) {código a ejecutar}

asistentes <- c("Maria Guadalupe", "Clarisa", "Pablo Alejandro", "Andrea", "Raúl Andres", "Evangelina",
                "Juan Manuel", "María Azul", "Gala", "Lucille", "Lucas", "Ludmila", "Adolfo Emiliano",
                "Ana", "Julieta Lourdes", "Joaquín", "Agostina", "Victoria Lucia")
for (nombre in asistentes) {
  print(nombre)
}
[1] "Maria Guadalupe"
[1] "Clarisa"
[1] "Pablo Alejandro"
[1] "Andrea"
[1] "Raúl Andres"
[1] "Evangelina"
[1] "Juan Manuel"
[1] "María Azul"
[1] "Gala"
[1] "Lucille"
[1] "Lucas"
[1] "Ludmila"
[1] "Adolfo Emiliano"
[1] "Ana"
[1] "Julieta Lourdes"
[1] "Joaquín"
[1] "Agostina"
[1] "Victoria Lucia"

Para reflexionar: Compare el código de la celda precedente con el que usamos para listar las carreras usando un bucle while. ¿Qué diferencias nota? ¿Cuándo usaría uno u otro constructo?

for en conjunto con data.frame

El bucle for es ampliamente utilizado para trabajar sobre filas y columnas de un data.frame. Por ejemplo, podemos tomar la tabla iris (cargada por defecto en R) y generar una nueva columna llamada Rel.SepPet.L:

iris$Rel.SepPet.L <- NA
head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Rel.SepPet.L
1          5.1         3.5          1.4         0.2  setosa           NA
2          4.9         3.0          1.4         0.2  setosa           NA
3          4.7         3.2          1.3         0.2  setosa           NA
4          4.6         3.1          1.5         0.2  setosa           NA
5          5.0         3.6          1.4         0.2  setosa           NA
6          5.4         3.9          1.7         0.4  setosa           NA

Gracias a un bucle for, es posible trabajar sobre cada fila para rellenar la nueva columna creada con los valores resultantes de la relación (el ratio) entre el largo de los sépalos y pétalos:

for (i in 1:nrow(iris)) {
  iris$Rel.SepPet.L[i] <- iris$Sepal.Length[i] / iris$Petal.Length[i]
}
head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Rel.SepPet.L
1          5.1         3.5          1.4         0.2  setosa     3.642857
2          4.9         3.0          1.4         0.2  setosa     3.500000
3          4.7         3.2          1.3         0.2  setosa     3.615385
4          4.6         3.1          1.5         0.2  setosa     3.066667
5          5.0         3.6          1.4         0.2  setosa     3.571429
6          5.4         3.9          1.7         0.4  setosa     3.176471

Queda claro que, como hemos visto anteriormente en el práctico de tablas, esta no es la forma más eficiente de generar la nueva columna (¿cuál sería la forma más eficiente?). No obstante, este ejemplo sirve para entender cómo funciona un bucle for con un data.frame.

La función seq() en un bucle for

Cuando trabajamos con bucles, es muy frecuente establecer un rango para definir la iteración. Allí entra en juego esta útil función que, como vimos en la clase de Vectores devuelve una secuencia numérica y cuya sintaxis es: seq(inicio,fin,paso), recordando que por defecto (es decir, si no explicitamos el valor del argumento), el inicio es “1” y el paso “1”.

# Secuencia de 1 a 3
for (n in seq(3)){
  print(n)
}
[1] 1
[1] 2
[1] 3
# Secuencia de 2 a 8, cada 2
for (n in seq(2,8,2)){
  print(n)
}
[1] 2
[1] 4
[1] 6
[1] 8

Break, next y condicionales

Al igual que vimos con while, podemos controlar el comportamiento de un bucle for de acuerdo a diferentes condiciones.

fruta <- c("banana", "manzana", "pera")

for (f in fruta){
  print(paste("Cortamos el bucle en su primer iteración, con la fruta", f))
  if (f == "banana"){
    break
  }
}
[1] "Cortamos el bucle en su primer iteración, con la fruta banana"
for (i in seq(1, 10)) {
  if (i %% 2 != 0) {
    next
  } else {
    print(paste("Me salteo todos los impares:", i))
  }
}
[1] "Me salteo todos los impares: 2"
[1] "Me salteo todos los impares: 4"
[1] "Me salteo todos los impares: 6"
[1] "Me salteo todos los impares: 8"
[1] "Me salteo todos los impares: 10"

Bucles anidados

En muchas ocasiones, nos encontraremos con la necesidad de operar iterativamente sobre más de un conjunto de elementos, en esos casos podemos anidar los bucles.

letras <- c("a", "b", "c")
numeros <- c(1, 2, 3)

for (i in letras) {
  for (j in numeros) {
    print(paste(i, j))
  }
}
[1] "a 1"
[1] "a 2"
[1] "a 3"
[1] "b 1"
[1] "b 2"
[1] "b 3"
[1] "c 1"
[1] "c 2"
[1] "c 3"

for anidados y matrices

El uso de for anidados es muy útil para trabajar con matrices:

mat <- matrix(sample(1:100, size = 9), ncol = 3)
mat
     [,1] [,2] [,3]
[1,]   15    5   19
[2,]   59   12   82
[3,]   40   46   56
for (i in 1:nrow(mat)) {
  for( j in 1:ncol(mat)) {
    print(paste("El elemento de la fila", i, "y columna", j, "es igual a", mat[i, j]))
  }
}
[1] "El elemento de la fila 1 y columna 1 es igual a 15"
[1] "El elemento de la fila 1 y columna 2 es igual a 5"
[1] "El elemento de la fila 1 y columna 3 es igual a 19"
[1] "El elemento de la fila 2 y columna 1 es igual a 59"
[1] "El elemento de la fila 2 y columna 2 es igual a 12"
[1] "El elemento de la fila 2 y columna 3 es igual a 82"
[1] "El elemento de la fila 3 y columna 1 es igual a 40"
[1] "El elemento de la fila 3 y columna 2 es igual a 46"
[1] "El elemento de la fila 3 y columna 3 es igual a 56"

¿Cómo funciona este código? La mejor forma de entender un bucle individual, o bucles anidados, es evaluar el primer caso de la serie de repeticiones que el bucle está efectuando y evaluar el código para el caso. Aquí, el primer caso sería cuando i = 1 y j = 1.

Ejercicio 4

all_num <- c(17, 6, -12, 38, -88, 147, 1, -140, -19, 24, 74, -36)

Escriba un código que separe los números positivos y los negativos del vector proporcionado en diferentes vectores. Imprima dichos vectores identificando su condición (positivo o negativo) y ordenados de menor a mayor.

Ayuda:

Un vector numérico vacío puede generarse de la siguiente manera:

vec <- vector(mode = "numeric")

O de la siguiente forma:

vec <- numeric()

Luego, recordemos que, como vimos en la clase de vectores, un vector vacío puede rellenarse (y actualizar sus valores) con el operador c(). Por ejemplo:

vec <- c(vec, 42)

Ejecute el siguiente código previamente y vea que aprende con ello.

ej1 <- c(3,1,2)
print(paste("Ordenados ej1:", sort(ej1)))
print(ej1)

ej2 <- c(4,8,6)
cat("Ordenados ej2:", sort(ej2))

Algunos consejos…

Programar desde cero puede ser muy frustrante! Esto es, obviamente, la situación más común si nuestra experiencia en lenguajes de programación es nula. A continuación, presentamos algunos consejos que pueden ayudarnos a programar nuestras primeras rutinas:

  • Paso a paso. No trates de programar la tarea propuesta entera de una sola vez, mejor descomponer la tarea en tareas más pequeñas concatenadas entre sí.

  • Pseudocódigo. Es útil pensar, o incluso escribir, los pasos a seguir mediante pseudocódigo. Es decir, representar con oraciones simples cada tarea a realizar.

  • Empezar por el final. Muchas veces es útil pensar cuál es el resultado final que necesitamos y, luego, pensar desde allí, de atrás hacia adelante, qué tareas debemos realizar para lograr ese resultado.

  • Ejemplo de juguete. Ante una tarea compleja, lo mejor es intentar generar la rutina deseada con un ejemplo de juguete o una pequeña porción de lo que nos proponemos hacer. Luego, si salió todo bien, extender la solución al problema en su totalidad.

  • Primero lo primero. Al programar un ciclo (for o while), puede ser difícil desarrollar de un tirón todo el código. Lo más indicado es programar las operaciones a realizar sobre el primer caso y, luego de haber obtenido el resultado exitosamente, generalizar el código para todos los casos.

  • Pensar en modo tabla. Si lo que quiero es obtener resultados en forma de tabla, una buena estrategia es generar la tabla con la o las columnas vacías. Luego, utilizamos un ciclo for para ir rellenando cada fila de las columnas creadas. Si lo pensamos así, nuestros ciclos for tendrán la siguiente forma general: for (i in 1:nrow(mi_tabla)) {mi código a ejecutar}.

  • El error como parte del proceso. Es normal que aparezcan errores todo el tiempo, pero es clave aprender a leer y comprender los mensajes de error. Estos mensajes suelen indicar exactamente qué está fallando y dónde.

Ejercicio Final

Tomando de base el conjunto de datos iris, construya el siguiente data.frame:

model_outputs <- data.frame(Species = unique(iris$Species),
                            Estimate = NA,
                            P.value = NA)
model_outputs
     Species Estimate P.value
1     setosa       NA      NA
2 versicolor       NA      NA
3  virginica       NA      NA

Luego, evalúe modelos lineales que relacionen a la variable Petal.Length con la variable Sepal.Length en ese orden, para cada especie. Rellene la tabla generada anteriormente con los valores de los coeficientes (llamado Estimate o coeficiente de la variable predictora) y su valor-P (P.value) en cada caso. Para hacer esto, utilice alguna estructura de control de flujo aprendida!

Ayuda: utilice la función lm() para construir los modelos lineales. La salida de la función summary(), que actúa sobre el modelo (salida de lm()), contiene la información sobre los coeficientes que necesitamos.


Ejercicio Extra

Cuando vimos los condicionales, armamos un código para evaluar en 3 escenarios diferentes los gastos que realizaron Andrea y Ariel. Ahora que sabe usar bucles, escriba un código que evalúe en una sola ejecución todos los escenarios planteados en ese ejercicio y que utilice todos los constructos aprendidos, es decir: if, while y for.

Ayuda: recuerde que los bucles while pueden ejecutarse indefinidamente si no imponemos condiciones para que finalicen.

Volver arriba