<- c(14, 17, 24, 56, 31, 20, 87, 73)
edad < 18 edad
[1] TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE
Cuadernos prácticos de Software II del Grado en Ciencia de Datos Aplicada (curso 2024-2025)
Una estructura de control se compone de una serie de comandos orientados a decidir el camino que tu código debe recorrer
Si se cumple la condición A, ¿qué sucede?
¿Y si sucede B?
¿Cómo puedo repetir una misma expresión (dependiendo de una variable)?
Si has programado antes, quizás te sea familiar las conocidas como estructuras condicionales tales como if (blabla) {...} else {...}
o bucles for/while
(a evitar siempre que podamos).
Una de las estructuras de control más famosas son las conocidas como estructuras condicionales: si pasase algo… entonces…
La más simple es la conocida como if
.
SI (IF) un conjunto de condiciones se cumple (TRUE), entonces ejecuta lo que haya dentro de las llaves
Por ejemplo, la estructura
if (x == 1) { código A }
lo que hará será ejecutar el código A entre llaves pero SOLO SI la condición entre paréntesis es cierta (solo si x
es 1). En cualquier otro caso, no hará nada.
Para ilustrarlo definamos un vector de edades de 8 personas
<- c(14, 17, 24, 56, 31, 20, 87, 73)
edad < 18 edad
[1] TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE
Nuestra estructura condicional hará lo siguiente: si existe algún menor de edad, imprimirá por pantalla un mensaje.
if (any(edad < 18)) {
print("Existe alguna persona menor de edad")
}
[1] "Existe alguna persona menor de edad"
En caso de que las condiciones no sean ciertas dentro de if()
(FALSE
), no sucede nada
if (all(edad >= 18)) {
print("Todos son mayores de edad")
}
No obtenemos ningún mensaje porque la condición all(edad >= 18)
no es TRUE
, así que no ejecuta nada.
La estructura if (condicion) { código A }
puede combinarse con un
if (condicion) { código A } else { código B }
cuando la condición se verifica se hará A, pero cuando la condición no se cumpla, se ejecutará el código alternativo B dentro de else { }
, permitiéndonos decidir que sucede cuando se cumple y cuando no.
Por ejemplo, if (x == 1) { código A } else { código B }
ejecutará A si x
es igual a 1 y B en cualquier otro caso. Piensa cuánto valdrá y
en el código inferior:
<- 3
x
if (x == 1) {
<- 2
y
else {
}
<- -1
y
}
En el ejemplo anterior y
valdrá -1 ya que la condición no se cumple y el código tomará el camino del else { ... }
. En el ejemplo anterior de las edades, vamos a imprimir por pantalla una frase si todos son mayores de edad y otra si no.
if (all(edad >= 18)) {
print("Todos son mayores de edad")
else {
}
print("Existe alguna persona menor de edad")
}
[1] "Existe alguna persona menor de edad"
Esta estructura if - else
puede ser anidada: imagina que queremos ejecutar un código si todos son menores; si no sucede, pero todos son mayores de 16, hacer otra cosa; en cualquier otra cosa, otra acción.
if (all(edad >= 18)) {
print("Todos son mayores de edad")
else if (all(edad >= 16)) {
}
print("Hay algún menor de edad pero todos con 16 años o más")
else { print("Hay alguna persona con menos de 16 años") } }
[1] "Hay alguna persona con menos de 16 años"
Puedes colapsar las estructuras haciendo click en la flecha a la izquierda que aparece en tu script.
Esta estructura condicional se puede vectorizar (en una sola línea) con if_else()
(del paquete {dplyr}
que veremos en profundidad más adelante), cuyos argumentos son
NA
Vamos a etiquetar sin son mayores/menores y un “desconocido” cuando no conocemos
library(dplyr)
Attaching package: 'dplyr'
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
<- c(NA, edad)
edad if_else(edad >= 18, "mayor", "menor", missing = "desconocido")
[1] "desconocido" "menor" "menor" "mayor" "mayor"
[6] "mayor" "mayor" "mayor" "mayor"
En R
base existe ifelse()
: no deja especificar que hacer con los ausentes pero permite especificar distintos tipos de datos en TRUE
y en FALSE
.
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 ¿Cuál es la salida del siguiente código?
if_else(sqrt(9) < 2, sqrt(9), 0)
0 ya que sqrt(9) es igual 3, y dado que no es menor que 2, devuelve el segundo argumento que es 0 La salida es
📝 ¿Cuál es la salida del siguiente código?
<- c(1, NA, -1, 9)
x if_else(sqrt(x) < 2, 0, 1)
c(0, NA, NA, 1) ya que sqrt(1) sí es menor que 2, sqrt(9) no lo es, y tanto en el caso de sqrt(NA) (raíz de ausente) como sqrt(-1) (devuelve NaN, not a number), su raíz cuadrada no puede verificarse si es menor que 2 o no, así que la salida es NA. La salida es el vector
📝 Modifica el código inferior para que, cuando no se pueda verificar si la raíz cuadrada de un número es menor que 2, devuelva -1
<- c(1, NA, -1, 9)
x if_else(sqrt(x) < 2, 0, 1)
<- c(1, NA, -1, 9)
x if_else(sqrt(x) < 2, 0, 1, missing = -1)
📝 ¿Cuál es son los valores de x
e y
del código inferior para z <- 1
, z <- -1
y z <- -5
?
<- -1
z if (z > 0) {
<- z^3
x <- -sqrt(z)
y
else if (abs(z) < 2) {
}
<- z^4
x <- sqrt(-z)
y
else {
}
<- z/2
x <- abs(z)
y
}
= 1 e y = -1. En el segundo caso x = 1 e y = 1. En el tercer caso -2.5 y 5 En primero caso x
📝 ¿Qué pasaría si ejecutamos el siguiente código? Spoiler: da error. ¿Por qué? ¿Cómo solucionarlo?
<- c(-1, 1, 5)
z if (z > 0) {
<- z^3
x <- -sqrt(z)
y
else if (abs(z) < 2) {
}
<- z^4
x <- sqrt(-z)
y
else {
}
<- z/2
x <- abs(z)
y
}
`if (condición) { } else { }` "clásicos" necesitamos que
Da error ya que en los uno (un solo valor TRU/FALSE) la condición tenga longitud
# para arreglarlo podemos hacer un if_else vectorial
<- c(-1, 1, -5)
z library(dplyr)
<- if_else(z > 0, z^3, if_else(abs(z) < 2, z^4, z/2))
x <- if_else(z > 0, -sqrt(z), if_else(abs(z) < 2, sqrt(-z), abs(z))) y
📝 ¿Qué sucederá si ejecutamos el código inferior?
<- "a"
z if (z > 0) {
<- z^3
x <- -sqrt(z)
y
else if (abs(z) < 2) {
}
<- z^4
x <- sqrt(-z)
y
else {
}
<- z/2
x <- abs(z)
y
}
# dará error ya que no es un argumento numérico
in z^3 : non-numeric argument to binary operator Error
📝 Del paquete {lubridate}
, la función hour()
nos devuelve la hora de una fecha dada, y la función now()
nos devuelve fecha y hora del momento actual. Con ambas funciones haz que se imprima por pantalla (cat()
) “buenas noches” solo a partir de las 21 horas.
# Cargamos librería
library(lubridate)
# Fecha-hora actual
<- now()
fecha_actual
# Estructura if
if (hour(fecha_actual) > 21) {
cat("Buenas noches") # print/cat dos formas de imprimir por pantalla
}
Aunque en la mayoría de ocasiones se pueden reemplazar por otras estructuras más eficientes y legibles, es importante conocer una de las expresiones de control más famosas: los bucles.
for { }
: permite repetir el mismo código en un número prefijado y conocido de veces.
while { }
: permite repetir el mismo código pero en un número indeterminado de veces (hasta que una condición deje de cumplirse).
Un bucle for es una estructura que permite repetir un conjunto de órdenes un número finito, prefijado y conocido de veces dado un conjunto de índices.
Vamos a definir un vector x <- c(0, -7, 1, 4)
y otra variable vacía y
. Tras ello definiremos un bucle for con for () { }
: dentro de los paréntesis indicaremos un índice y unos valores a recorrer, dentro de las llaves el código a ejecutar en cada iteración (en este caso, rellenar y
como x + 1
)
<- c(0, -7, 1, 4)
x <- c()
y
for (i in 1:4) {
<- x[i] + 1
y[i]
}
Fíjate que debido a que R
funciona de manera vectorial por defecto, el bucle es lo mismo que hacer x + 1
directamente.
<- c(0, -7, 1, 4)
x <- c()
y
for (i in 1:4) {
<- x[i] + 1
y[i]
} y
[1] 1 -6 2 5
<- x + 1 # hacen lo mismo
y2 y2
[1] 1 -6 2 5
Otra opción habitual es indicar los índices de manera «automática»: desde el primero 1
hasta el último (que corresponde con la longitud de x length(x)
)
<- c(0, -7, 1, 4)
x <- c()
y
for (i in 1:length(x)) {
<- x[i] + 1
y[i]
} y
[1] 1 -6 2 5
Así la estructura general de un bucle for será siempre la siguiente
for (índice in conjunto) {
código (dependiente de i) }
SIEMPRE sabemos cuántas iteraciones tenemos (tantas como elementos haya en el conjunto a indexar).
Podemos ver otro ejemplo de bucle combinando números y textos: definimos un vector de edades y de nombres, e imprimimos el nombre y edad i-ésima.
<- c("Javi", "Sandra", "Carlos", "Marcos", "Marta")
nombres <- c(33, 27, 18, 43, 29)
edades library(glue)
for (i in 1:5) {
print(glue("{nombres[i]} tiene {edades[i]} años"))
}
Javi tiene 33 años
Sandra tiene 27 años
Carlos tiene 18 años
Marcos tiene 43 años
Marta tiene 29 años
Aunque normalmente se suelen indexar con vectors numéricos, los bucles pueden ser indexados sobre cualquier estructura vectorial, da igual de que tipo sea el conjunto
library(stringr)
<- c("monday", "tuesday", "wednesday", "thursday",
week_days "friday", "saturday", "sunday")
for (days in week_days) {
print(str_to_upper(days))
}
[1] "MONDAY"
[1] "TUESDAY"
[1] "WEDNESDAY"
[1] "THURSDAY"
[1] "FRIDAY"
[1] "SATURDAY"
[1] "SUNDAY"
Vamos a combinar las estructuras condicionales y los bucles: usando el conjunto swiss
del paquete {datasets}
, vamos a asignar NA
si los valores de fertilidad son mayores de 80.
for (i in 1:nrow(swiss)) {
if (swiss$Fertility[i] > 80) {
$Fertility[i] <- NA
swiss
} }
Esto es exactamente igual a un if_else()
vectorizado
data("swiss")
$Fertility <- if_else(swiss$Fertility > 80, NA, swiss$Fertility) swiss
Como ya hemos aprendido con el paquete{microbenchmark}
podemos chequear como los bucles suelen ser muy ineficientes (de ahí que debamos evitarlos en la mayoría de ocasiones
library(microbenchmark)
<- 1:1000
x microbenchmark(y <- x^2,
for (i in 1:100) { y[i] <- x[i]^2 },
times = 500)
Warning in microbenchmark(y <- x^2, for (i in 1:100) {: less accurate
nanosecond times to avoid potential integer overflows
Unit: microseconds
expr min lq mean median
y <- x^2 1.681 1.886 2.01351 1.927
for (i in 1:100) { y[i] <- x[i]^2 } 1211.099 1223.194 1299.42095 1230.758
uq max neval
2.050 9.307 500
1244.842 5295.642 500
Otra forma de crear un bucle es con la estructura while { }
, que nos ejecutará un bucle un número desconocido de veces, hasta que una condición deje de cumplirse (de hecho puede que nunca termine). Por ejemplo, vamos a inializar una variable ciclos <- 1
, que incrementaremos en cada paso, y no saldremos del bucle hasta que ciclos > 4
.
<- 1
ciclos while(ciclos <= 4) {
print(glue("No todavía, vamos por el ciclo {ciclos}"))
<- ciclos + 1
ciclos
}
No todavía, vamos por el ciclo 1
No todavía, vamos por el ciclo 2
No todavía, vamos por el ciclo 3
No todavía, vamos por el ciclo 4
Un bucle while
será siempre como sigue
while(condición) {
TRUE
código a hacer mientras la condición sea # normalmente aquí se actualiza alguna variable
}
¿Qué sucede cuando la condición nunca es FALSE? Pruébalo tu mismo
while (1 > 0) {
print("Presiona ESC para salir del bucle")
}
Un bucle while { }
puede ser bastante «peligroso» sino controlamos bien cómo pararlo.
Debido a que puede ser problemático no saber el número predeterminado de veces que un while
va a ejecutarse, contamos con dos palabras reservadas para abortar un bucle o forzar su avance:
break
: permite abortar un bucle incluso si no se ha llegado a su finalfor(i in 1:10) {
if (i == 3) {
break # si i = 3, abortamos bucle
}print(i)
}
[1] 1
[1] 2
next
: fuerza un bucle a avanzar a la siguiente iteraciónfor(i in 1:5) {
if (i == 3) {
next # si i = 3, la obvia y continua al siguiente
}print(i)
}
[1] 1
[1] 2
[1] 4
[1] 5
Aunque no es tan usado como las opciones anteriores, también contamos con repeat { }
que ejecuta un bucle de manera infinita hasta que se indique abortar con un break
<- 0
count repeat {
<- count + 1
count if (count >= 100) { break }
} count
[1] 100
Aunque no es formalmente un bucle, otra forma de repetir código un número de veces es hacer uso de replicate()
: simplemente permite repetir lo mismo n veces
<- 1:3
x replicate(n = 3, x^2)
[,1] [,2] [,3]
[1,] 1 1 1
[2,] 4 4 4
[3,] 9 9 9
La función replicate()
se suele usar para generar distintas repeticiones de elementos aleatorios. Por ejemplo, imaginemos que queremos generar 3 muestras de distribuciones normales, en la que cada muestra tendrá 7 elementos. Para generar una se usa rnorm(n = 7)
(r de resample, norm de normal, y si no se dice nada es media 0 y desv 1).
replicate(n = 3, rnorm(n = 7))
[,1] [,2] [,3]
[1,] -0.26373776 0.1599125 -0.5156391
[2,] -2.28921509 0.9363799 -0.2805221
[3,] -0.27959798 -0.2047103 -0.1111351
[4,] -0.03947779 0.1532976 -0.1525760
[5,] 0.82417967 -0.6353651 -1.7025843
[6,] 0.33035682 1.1016968 2.1577762
[7,] 2.22307856 0.9371033 -1.2765699
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Modifica el código inferior para que se imprima un mensaje por pantalla si y solo si todos los datos de airquality
son con mes distinto a enero
library(datasets)
<- airquality$Month
months
if (months == 2) {
print("No hay datos de enero")
}
library(datasets)
<- airquality$Month
months
if (all(months != 1)) {
print("No hay datos de enero")
}
📝 Modifica el código inferior para guardar en una variable llamada temp_alta
un TRUE
si alguno de los registros tiene una temperatura superior a 90 grados Farenheit y FALSE
en cualquier otro caso
<- airquality$Temp
temp
if (temp == 100) {
print("Algunos de los registros tienen temperaturas superiores a 90 grados Farenheit")
}
# Option 1
<- airquality$Temp
temp <- FALSE
temp_alta if (any(temp > 90)) {
<- TRUE
temp_alta
}
# Option 2
<- any(airquality$Temp > 90) temp_alta
📝 Modifica el código inferior para diseñar un bucle for
de 5 iteraciones que solo recorra los primeros 5 impares (y en cada paso del bucle los imprima)
for (i in 1:5) {
print(i)
}
for (i in c(1, 3, 5, 7, 9)) {
print(i)
}
📝 Modifica el código inferior para diseñar un bucle while
que empiece con un contador count <- 1
y pare cuando llegue a 6
<- 1
count while (count == 2) {
print(count)
}
<- 1
count while (count < 6) {
print(count)
<- count + 1
count
}
Para practicar estructuras de control vamos a realizar un ejercicio de simulación
Define una variable llamada
glucosa
que empiece en 100. Diseña un bucle de 20 iteraciones donde en cada iteración, los niveles de glucosa se reduzcan a la mitad de su valor. Piensa que tipo de estructura de bucle deberías usar. El valor final deglucosa
deberia ser0.000095367
(aprox)
# Usamos un for ya que sabemos el número de iteraciones
# de manera prefijada (y no depende de nada)
# definimos inicialmente glucosa en 100
<- 100
glucosa
# para el bucle usamos por ejemplo i como índice, que va de 1 a 20
for (i in 1:20) {
# el código fíjate que es el mismo y no depende de i
<- glucosa/2
glucosa
} glucosa
Diseña una estructura de bucle de manera que encuentres la iteración en la que
glucosa
es menor que 0.001 por primera vez. Una vez encontrado guárdalo eniter
y para el bucle.
# dos formas de hacerlo: for y while
# con for
<- 100
glucosa
# ya sabemos que en 20 es menor que 0.001 así que podemos poner
# dicha cantidad como tope sabiendo que no llegará
for (i in 1:20) {
# si todavía no es menor, seguimos dividiendo
if (glucosa >= 0.001) {
<- glucosa/2
glucosa
else {
}
# si ya es menor, guardamos la iteración (piensa por qué i - 1)
<- i - 1
iter
# y paramos
break
}
}
# con while
<- 100
glucosa
<- 0 # debemos inicializar las iteraciones
iter
# no sabemos cuantas iteraciones, solo que debe parar cuando
# glucosa esté por debajo de dicha cantidad
while (glucosa >= 0.001) {
<- glucosa/2
glucosa
# estructura clásica de while: si se corre iteración
# actualizamos un valor (en este caso que cuente una iteración)
<- iter + 1
iter
}
iter
En
R
tenemos la función%%
: si ponemosa %% b
nos devuelve el resto que daría la división \(a/b\). Por ejemplo,4 %% 2
da 0 ya que 4 es un número par (es decir, su resto al dividir entre 2 es 0). Si ponemos13 %% 5
nos devuelve 3, ya que el resto de dividir 13 entre 5 es 3.
# Resto al dividir entre 2
3 %% 2
[1] 1
4 %% 2
[1] 0
5 %% 2
[1] 1
6 %% 2
[1] 0
# Resto al dividir entre 3
9 %% 3
[1] 0
10 %% 3
[1] 1
11 %% 3
[1] 2
12 %% 3
[1] 0
Empezando en un nivel de glucosa inicial
glucosa_inicial
de 100 mg/dl, diseña un bucle que te sume 3 mg/dl más la iteración por la que estés si el valor actual es par y te reste 5 mg/dl menos la iteración por la que estés si es impar, SALVO que el valor ya esté igual o por debajo de 0 (en ese caso no debe sumar ni restar). Ejemplo: si glucosa tiene 50 mg/dl y estás en la iteración 13, sumará 3 + 13 (66 en total); si glucosa tiene 51 mg/dl y estás en la iteración 13, restará 5 + 13 (33 en total); si glucosa tiene -2 mg/dl y estás en la iteración 13, sumará 3 + 13 (14 en total); si glucosa tiene -1 mg/dl y estás en la iteración 13, no hará nada. Guarda los niveles de glucosa resultantes de cada iteración (máximo de 150 iteraciones). Empieza a partir de la iteración 2
<- 100
glucosa_inicial <- c(glucosa_inicial, rep(NA, 149))
glucosa for (i in 2:150) {
if (glucosa[i - 1] %% 2 == 0) {
<- glucosa[i - 1] + 3 + i
glucosa[i]
else if (glucosa[i - 1] > 0) {
}
<- glucosa[i - 1] - (5 + i)
glucosa[i]
else {
}
<- glucosa[i - 1]
glucosa[i]
} }
¿Qué ha pasado?
En
R
la funciónsample(x = ..., size = ...)
va sernos muy útil: de una colección de elementosx
, selecciona un númerosize
al azar de ellos.
Por ejemplo, si queremos simular 3 veces el lanzamiento de un dado tenemos 6 elementos posibles (x = 1:6
) y lo seleccionamos 3 veces (size = 3
)
sample(x = 1:6, size = 3)
[1] 1 4 5
Al ser aleatorio, cada vez que lo ejecutas saldrá algo distinto
sample(x = 1:6, size = 3)
[1] 3 6 5
¿Y si queremos tirar 10 veces?
sample(x = 1:6, size = 10)
Error in sample.int(length(x), size, replace, prob): cannot take a sample larger than the population when 'replace = FALSE'
Al tener solo 6 elementos posibles y elegir 10, no puede, así que le tenemos que indicar que queremos un sample (muestreo) con reemplazamiento (como sucede en el dado, cada cara puede repetirse al volver a tirarlo)
sample(x = 1:6, size = 10, replace = TRUE)
[1] 6 4 1 6 5 6 3 3 1 4
Con lo anterior, imagina que estás en un concurso de televisión donde te dan a elegir 3 puertas: en una hay un premio millonarios y en las otras 2 una galleta oreo. Diseña el estudio de simulación con bucles for para aproximar la probabilidad de que te toque el premio (obviamente tiene que darte aprox 0.3333333). Realiza el experimento para 10, 50 intentos, 100 intentos, 500 intentos, 1000 intentos, 10 000 intentos y 25 000 intentos (pista: necesitas un bucle dentro de otro). ¿Qué observas?
# Definimos las posibilidades
<- c(1, 2, 3)
puertas
# Definimos los intentos
<- c(10, 50, 100, 500, 1000, 10000, 25000)
intentos
# Para cada escenario de intentos, definimos las veces que hemos ganado
# (al inicio empieza en 0 claro)
<- rep(0, length(intentos))
exitos
# primer bucle: cantidad de intentos permitidos
for (i in 1:length(intentos)) {
# segundo bucle: para cada intento, simulaciones una eleccion de
# puerta y un premio
for (j in 1:intentos[i]) {
# premio: de 3 puertas, solo está en una
<- sample(x = puertas, size = 1)
premio
# puerta que seleccionas como concursante: de 3 puertas, te quedas con una
<- sample(x = puertas, size = 1)
eleccion
# si la puerta seleccionada coincide con la que tiene el premio
# sumas un éxito, sino te quedas como estás
<- if_else(eleccion == premio, exitos[i] + 1, exitos[i])
exitos[i]
}# Tras jugar, lo dividimos entre el número de veces que has jugado
# para tener una proporción
<- exitos[i] / intentos[i]
exitos[i]
} exitos
¿Y si en cada ronda, te abriesen una de las puertas no premiadas que no has elegido, cambiarías de puerta o te mantendrías? Simula ambos casos y descubre cuál es la estrategia correcta (este problema se conoce como problema de Monty Hall y aparece incluso en películas como 21 Black Jack)
<- c(1, 2, 3)
puertas <- c(10, 50, 100, 500, 1000, 10000, 25000)
intentos <- exitos_cambio <- rep(0, length(intentos))
exitos_mantengo
for (i in 1:length(intentos)) {
for (j in 1:intentos[i]) {
# puerta que seleccionas como concursante: de 3 puertas, te quedas con una
<- sample(x = puertas, size = 1)
eleccion_inicial
# premio: de 3 puertas, solo está en una
<- sample(x = puertas, size = 1)
premio
# De la no elegida, el presentador te abre una no premiada
<-
puerta_abierta != eleccion_inicial & puertas != premio]
puertas[puertas
# si solo hay una opción (es decir que tu eleccion inicial
# y el premio son puertas distintas) no haces nada
# Si hubiese 2 opciones (si tu eleccion y el premio coinciden)
# te abrirá una al azar
if (length(puerta_abierta) > 1) {
<- sample(x = puerta_abierta, size = 1)
puerta_abierta
}
# si mantienes es como antes
<-
exitos_mantengo[i] if_else(eleccion == premio, exitos_mantengo[i] + 1, exitos_mantengo[i])
# si cambias es a una puerta distinta de la inicial y de la abierta, la que quede
<- puertas[puertas != eleccion_inicial & puertas != puerta_abierta]
cambio <-
exitos_cambio[i] if_else(cambio == premio, exitos_cambio[i] + 1, exitos_cambio[i])
}# Tras jugar, lo dividimos entre el número de veces que has jugado
# para tener una proporción
<- exitos_mantengo[i] / intentos[i]
exitos_mantengo[i] <- exitos_cambio[i] / intentos[i]
exitos_cambio[i]
}
exitos_mantengo exitos_cambio
¿Qué sucede?