Empezando en R (V). Rutinas y bucles

Es hora de seguir con nuestra rutina para aprender R.

Como ya tenemos las bases para operar, estamos en condiciones de encadenar estas operaciones para realizar una tarea concreta. Hablamos de rutinas, como cuando te levantas por la mañana y haces siempre las mismas cosas para llegar a un resultado final. Te levantas, vas a la ducha, despiertas a los peques (si los tienes), un café rápido o un desayuno completo… Todo para llegar al trabajo. Claro que en R los problemas a solucionar son diferentes así que hablemos de rutinas en programación.

¿Me dejas que te cuente?

Rutinas en R

Así como levantarte de la cama, tomarte un café o despertar a los más pequeños de la casa son acciones que acaban llevándote a un fin: empezar la mañana. Las rutinas, subrutinas, programas o subprogramas, son un conjunto de acciones/operaciones que permiten llevar a cabo una tarea en R.

Una sucesión de expresiones

La unidad más básica de una rutina son las expresiones. Una expresión es cualquier linea escrita en R que complete una acción. Por ejemplo:

> 3+4
## [1] 7

sería una expresión que completa una suma, pero también es una expresión la asignación de ese valor a un objeto:

> y <- 3+4

una expresión cuyo resultado es la creación de ese objeto.

R nos permite, además, crear objetos de tipo expresión mediante la función expression, que guardan estas acciones para evaluarlas cuando sea el momento adecuado usando la fución eval. Por ejemplo:

> #Generamos varias expresiones
> exp1<-expression(3+4) 
> exp2<-expression(sum(1:10)) 
> exp3<-expression(b<-1:10) 
> #Las evaluamos
> eval(exp1); eval(exp2); eval(exp3)
## [1] 7
## [1] 55
#Nota: el ; permite poner varias expresiones en una misma linea.

Una rutina puede entonces estar formada por una o más expresiones que agruparemos entre llaves {expre.1; expre.2; …; expre.m}. Tal y como os indicaba en la nota del código anterior, las expresiones se pueden encadenar saltando de lÍnea o añadiendo un ; entre una y otra.

Vamos a ver algunos ejemplos, pero quiero que os fijéis especialmente en que pasa cuando encadenamos las expresiones y las convertimos en rutina poniéndolas entre llaves.

> {y <- 2+5; y+3; y*4}
## [1] 28
> # ¿Qué valor devuelve? 
> 
> # ¿y si quitamos las llaves?
> y <- 2+5; y+3; y*4
## [1] 10
## [1] 28

Probemos ahora con esta otra rutina:

> {y <- rnorm(100,mean = 3, sd = 1)
+   mean(y)
+   sd(y)
+   }
## [1] 1.010716

Vemos que sólo devuelve el último valor, en este caso, la desviación estándar de los valores que hemos simulado. Puedes probar a quitar las llaves, pero si quisiéramos forzar a que la rutina devuelva algo más que la última linea, lo podemos hacer utilizando la función print :

> {y <- rnorm(100,mean = 3, sd = 1)
+   print(mean(y))
+   sd(y)
+   }
## [1] 2.994188
## [1] 0.8868828

Aunque este no es realmente el objetivo de una rutina, como os comentaba antes, el propósito es llegar a un resultado final que se consigue tras una serie de operaciones. Digamos que entonces una rutina con algo más de sentido podría ser:

> {y <- rnorm(100,mean = 3, sd = 1) #simulamos datos
+  x <- log(y) #los transformamos
+   c(mean(x),sd(x)) #devolvemos la media y la desviación estándar
+   }
## [1] 1.0639161 0.3621465

Utilidad

  • Vale Ana, entonces… ¿para qué complicarnos con rutinas? ¿Por qué no hacemos simplemente una serie de operaciones y punto?

Vale, vamos a probar una cosa. No sé cual es tu técnica para ejecutar los comandos de R. Es posible que estés copiando y pegando, o puede que, desde el script (el de este documento lo puedes encontrar aquí) estés usando el botón Run o que estés usando el atajo ctrl + enter. Centrémonos en las dos últimas opciones y vamos a repetir la rutina.

Sitúate en la primera linea y ejecútala, ¿qué sucede?

> {y <- rnorm(100,mean = 3, sd = 1) #simulamos datos
+  x <- log(y) #los transformamos
+   c(mean(x),sd(x)) #devolvemos la media y la desviación estándar
+   }
## [1] 1.0513239 0.3782866

Hazlo ahora sin usar las llaves. Vuelve a la primera línea y ejecuta.

> y <- rnorm(100,mean = 3, sd = 1) #simulamos datos
> x <- log(y) #los transformamos
> c(mean(x),sd(x)) #devolvemos la media y la desviación estándar

¿Ves que ha pasado en la consola? Efectivamente, mientras que si ponemos las llaves se ejecuta todo de una, al tener las lineas por separado sólo se ha ejecutado la primera línea.

Esto está bien porque hace que una rutina tenga sentido como un todo que se ejecuta de una sola vez. Este uso es especialmente útil cuando tenemos que repetir el procedimiento muchas veces dentro de lo que llamamos bucles, o cuando queremos tenerlo programado en forma de función para ejecutarlo siempre que lo necesitemos.

Centrémonos hoy en la primera utilidad, hablemos de bucles.

Bucles

Espera, espera… Para explicarte lo que son los bucles en R necesito que hagas un ejercicio mental primero.

Vamos a pensar en un proceso para el que haya que seguir ciertos pasos… no sé… vamos a hacer merengue.

En primer lugar vamos a preparar los ingredientes.

  • 4 huevos
  • 200 gr de azúcar

Ahora cogemos un recipiente hondo y comprobamos si está limpio y seco. Si no lo estuviese, lo limpiamos y secamos y volvemos a comprobar.

Una vez lo tenemos listo toca separar, para cada uno de los huevos, la clara de la yema cuidando que no se mezclen. Un poco de yema puede hacer que se nos arruine el merengue. Para evitar problemas lo podemos hacer en un cuenco a parte, un huevo cada vez, y si se rompe la yema desechamos ese huevo y cogemos otro.

Es momento ahora de batir las claras hasta que estén empiecen a estar firmes. Si lo hacemos con varillas eléctricas podemos ir parando cada minuto o cada dos minutos para comprobar si ya están listas. Cuando estén firmes empezaremos a añadir el azúcar, poco a poco, hasta añadirlo por completo y seguiremos batiendo hasta que esté a punto de nieve, es decir, hasta que lo pongas boca abajo y no se caiga. En este ultimo paso también podemos ir parando cada cierto tiempo para comprobar si ya está.

  • ¡Madre mía Ana, que te esta pasando! ¿Por qué nos cuentas una receta de cocina?
  • Espera, espera, que ahora viene lo mejor.

Esta receta la podemos resumir mediante el siguiente dibujo, que llamamos diagrama de flujo:

Diagrama de flujo: Cómo hacer merengue.

Observa que hay operaciones en cuadrados grises y preguntas en rombos naranjas. Algunas operaciones hay que repetirlas varias veces, otras hay que repetirlas hasta que la respuesta sea  y después, en algunos casos, si pasa algo, hay que añadir un paso (como limpiar el recipiente si no está limpio y seco).

Bueno, llegados a este punto quiero contarte que acabas de preparar un algoritmo, sí, parecidos a esos que les pasamos a una máquina para que resuelva algo. De hecho, con este algoritmo podrías enseñar a un robot a hacer merengue, perooooo para eso ahora tenemos que pasar a programarlo así que, ahora sí, vamos al lío.

Traduzcamos nuestros rombos naranjas a bucles.

¿Qué es un bucle?

En cualquier lenguaje de programación, incluido R, un bucle es un operador que nos permite repetir o ejecutar una rutina bajo ciertas condiciones o durante un tiempo determinado.

Si nos fijamos en los rombos tenemos de tres tipos:

  • Los que nos hacen repetir algo varias veces: repetir la separación de clara y huevo para 4 huevos.
  • Los que nos hacen repetir algo hasta que se cumpla una condición: sigue batiendo hasta que las claras esten firmes
  • Los que nos llevan a otra acción si se cumple una condición: si el recipiente no está limpio y seco, límpialo y sécalo.

Si esto lo traducimos al lenguaje R tenemos:

  • for: Para repetir una expresión una cantidad marcada de veces.
  • while: Para repetir una expresión mientras pase algo.
  • if: Para ejecutar una expresión de forma condicional.

Existe algún tipo de bucle más como el repeat, que repite una operación indefinidamente o hasta que la fuerces a parar con un if o algo similar, pero esos los vamos a dejar estar.

Vamos a ver como usar los tres más importantes.

for

Como ya hemos comentado, el bucle for sirve para repetir una operación varias veces. El código que usaremos será el siguiente:

> for(nombre in valores) rutina

Ese nombre in valores hace que el bucle for sea muy flexible y significa que nombres va a ir tomando todos los valores que aparezcan en el vector valores, y para cada uno de ellos va a repetir la rutina que especifiquemos.

Esa rutina será como las que hemos explicado al principio, varias expresiones encerradas entre llaves para las que lo ejecutará todo y, si es el caso, devolverá el resultado de la ultima operación que le marquemos (siempre y cuando no la hayamos asignado a un objeto).

Lo importante del bucle for es que en la rutina es posible usar el valor que tiene nombre en ese momento

Mira uno muy simple:

> for (i in 1:5){
+   cat("caso ",i,"\n")
+ }
## caso  1 
## caso  2 
## caso  3 
## caso  4 
## caso  5

Fíjate que en este caso en lugar de nombres hemos utilizado i, puedes usar el nombre que más te guste. Esa i va a variar en el vector 1:5 que ya sabemos que son los valores, c(1,2,3,4,5). Dentro de la rutina hemos usado la función cat, que nos escribe por pantalla lo que le indicamos. En este caso escribirá “caso” seguido del valor que tiene i en cada pasada (iteración). Primero 1, luego 2, luego 3, etc. el “\n” es sólo para que cada vez que escribe baje a la siguiente línea. Veámoslo:

> for (i in 1:5){
+   cat("caso ",i,"\n")
+ }
## caso  1 
## caso  2 
## caso  3 
## caso  4 
## caso  5

Ten en cuenta que el vector no tiene porque ser de números consecutivos y ni siquiera tiene que ser de números. Mira:

> for (i in c("Alba","Marcos","Adriana","Jorge")){
+   cat(i,", es tú turno, \n")
+ }
## Alba , es tú turno, 
## Marcos , es tú turno, 
## Adriana , es tú turno, 
## Jorge , es tú turno,

En el caso de la receta, simplemente haríamos algo como (for i in 1:4) y, dentro de la rutina, romperíamos el huevo, separaríamos yema y clara, y pasaríamos al siguiente, con la particularidad de que antes hay que comprobar que el huevo esté bien separado de su clara. Pero para eso necesitamos más bucles, así que sigamos.

while

El siguiente bucle es while. Este bucle se repite hasta que se cumple una condición. Lo de cumplirse una condición en R es equivalente a decir que hacemos una comprobación lógica y nos da TRUE. Su código, por tanto, sería el siguiente:

> while(condicion) rutina

En este caso, la rutina es evaluada mientras la condicion sea cierta (TRUE). Ahora bien, para avanzar en este bucle tenemos que tener algo que nos marque que damos un paso más, porque no es algo que se haga de forma continua. Mira el siguiente ejemplo:

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

En él hemos empezado con una variable i que tiene valor 5. Mientras i sea mayor que 0 vamos a pintar por pantalla el número de caso y, al terminar, vamos a restar uno a i para volver a empezar.

La primera vez i vale 5, que da TRUE al hacer i>0, la segunda vale 4, la tercera 3… y así hasta que llegamos a 0, en ese caso la condición ya no se cumple (obtenemos FALSE), y el bucle no continúa.

Veamos otro ejemplo:

> dia <- c("lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo")
> i <- 1
> while (dia[i]!="sábado") {
+   cat(paste("Hoy es", dia[i], "toca trabajar \n"))
+   i <- i+1
+ }
## Hoy es lunes toca trabajar 
## Hoy es martes toca trabajar 
## Hoy es miércoles toca trabajar 
## Hoy es jueves toca trabajar 
## Hoy es viernes toca trabajar

Aquí la condición no tiene que ver directamente con i, aunque i sí que nos ayuda a recorrer todas las posiciones de un vector hasta que uno de sus valores cumple la condición de ser sábado.

En nuestro merengue, la idea sería que cada 5 minutos pararíamos y miraríamos si están firmes las claras y, si no lo están, continuaríamos batiendo.

Tambien podemos añadir un while al proceso de separación de claras. Así, mientras no tengamos una nueva clara sin yema no dejaremos avanzar al for anterior.

Es importante mencionar que el bucle while es un poco peligroso, es necesario que en algún momento llegue a cumplirse el criterio de parada. Si no, si eres un poco torpe, tú puedes seguir cascando huevos hasta el infinito o el ordenador seguir ejecutando el bucle para siempre (bueno, o hasta que tú te quedes sin huevos o fuerces el ordenador a parar… jeje).

Y hablando de no repetir infinitamente, vamos ahora con un bucle que no implica repetir, si no tomar decisiones.

El bucle condicional: If

El lenguaje R tiene la posibilidad de ejecutar expresiones condicionalmente mediante el siguiente código:

> if(condicion) rutina1 else rutina2

Así, si la condicion es TRUE se ejecutará la rutina1 pero si es FALSE se ejecutará la rutina2.

Mira este ejemplo:

> b <- sample(1:10, 1) #Elegimos un número al azar del 1 al 10
> 
> if (b>3){
+   cat("SI b>3 \n")
+   }else{
+     cat("NO b>3 \n")
+   }
## SI b>3

En la condición podemos incluir varias comprobaciones utilizando los operadores lógicos que ya vimos.

Por ejemplo:

> x <- 4
> #Prueba a cambiar este valor por un 0
> if (is.numeric(x) & min(x) > 0){
+   rax <- sqrt(x)
+   log(rax)
+   }else{
+     stop("x debe ser numérico y positivo \n")}
## [1] 0.6931472

Fíjate que este bucle nos sirve para comprobar si se cumple una condición y parar en caso contrario. Por tanto, lo podríamos usar dentro de un bucle for, por ejemplo, para parar si algo no va bien.

En el ejemplo del merengue, cuando comprobamos si el recipiente está limpio y seco, basicamente estamos haciendo un if que nos lleva a limpiarlo en el caso de que no lo esté o a continuar con el trabajo si lo está. El bucle if tiene una versión en forma de función, veámosla.

La función ifelse

Cuando ponemos una condición en el bucle if, esta tiene que ser un único valor, TRUE o FALSE. Sin embargo, a veces podemos tener que comprobar una condición para cada uno de los valores de un vector y ahí es donde entra en juego la función ifelse().

Esta función parte de un vector de condiciones según las cuales aplica una expresión u otra al correspondiente elemento. Pruébalo:

> y <- -5:5 
> # Un vector de valores entre - 5 y 5
> 
> y.y <- ifelse(y>1,y^2,0) 
> # hago el cuadrado de los valores que son mayores que 1
> y
##  [1] -5 -4 -3 -2 -1  0  1  2  3  4  5
> y.y
##  [1]  0  0  0  0  0  0  0  4  9 16 25

Notas finales

Hasta aquí hemos visto los principales bucles que podemos utilizar en R pero tengo que hacer un par de aclaraciones.

La primera es sobre el uso de rutinas dentro de un bucle y, en particular, de las llaves que las abren y cierran. Todo el tiempo he puesto llaves porque se debían ejecutar todas esas ordenes, sin embargo, si sólo tenemos una expresión que ejecutar, podríamos ahorrarnos esas llaves. Eso sí, ten en cuenta que cuando hay una rutina, como explicabamos al principio, el bucle sólo devolverá lo último que hayamos puesto. Si quieres forzar a que salga algo más tendrás que guardarlo en un objeto o usar funciones como print o return.

La segunda es sobre la eficiencia de los bucles. Los bucles pueden ser muy intuitivos y fáciles de implementar, sin embargo, no siempre son lo más eficiente. Si es posible traducir la acción para realizarla usando una función tipo apply el tiempo de computación se reducirá notablemente. Valora siempre el tiempo que te puede llevar pasar por el apply frente al tiempo total que va a tardar en ejecutarse tu programa.

Y eso es todo por hoy. Espero que te haya gustado y ¡seguimos aprendiendo!

Indice del curso 

  5 Replies to “Empezando en R (V). Rutinas y bucles”

  1. Miguel
    22/03/2023 at 18:59

    ¡Hola, Ana!
    Espero que estés bien, ante todo.
    Quería preguntarte si tienes pensado seguir con estos tutoriales. Resultaban muy ilustrativos y útiles.

    Un saludo,

    • 13/04/2023 at 07:39

      Hola Miguel,
      Sí, sin duda… he tenido una racha de mucho trabajo pero prometo retomarlo pronto.
      Gracias por estar ahí.
      Un saludo
      Anabel

  2. Luismi
    18/03/2023 at 10:02

    Gracias Ana! Llevo muchos años con R y nunca había visto eso de las rutinas. Desde que descubres el Tidyverse se le olvida a uno que, de base, R tiene cosillas muy interesantes como esta. Practicaré estas rutinas.
    ¿Tienes pensado tratar el tema de POO en R?
    Un saludo.-

    • 13/04/2023 at 07:42

      Hola Luismi,
      Pues la verdad es que no lo había pensado. Mi idea es continuar con funciones y luego pasar a utilidades para análisis de datos, incluyendo algún tutorial sobre el uso de Tidyverse. Si hay alguna cosilla en particular que te gustaría que tratase, no dudes en decirlo.

      Gracias por seguir por aquí.
      Un saludo
      Anabel

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

A %d blogueros les gusta esto: