Definición de los bucles for

La expresión for es común a todos los lenguajes de programación, y sirve para iniciar una serie de operaciones que se repitan para una serie de elementos (listas, vectores, secuencias de números…), de manera que esta repetición, o iteración se haga de manera automática para todos estos elementos, como en un “bucle” o loop. La sintaxis es la siguiente:

Primeros ejemplos

for (elementos in secuencia){
  #operaciones que se repiten
}

Los elementos de esta sintaxis se pueden ver de la siguiente manera: forinicia el bucle. A continuación entre paréntesis definimos un elemento, un objeto temporal, que puede tener el nombre que queramos y que va a ir conviertiéndose en cada uno de los elementos de la secuencia que definamos después de la expresión in. Esta secuencia puede ser un vector de números, nombres, lo que consideremos. A continuación abriremos un corchete curvo {, debajo del cual irán todas las líneas de código que se ejecuten para cada uno de los elementos de la secuencia.

Vamos a ver un ejemplo comentado para verlo más claro:

secuencia<-1:10
for(i in secuencia){
  print(i)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9
## [1] 10

En un segundo ejemplo podemos jugar con algunos parámetros. Nuestro objeto iterativo ya no será i sino jpara resaltar que podemos utilizar el nombre que consideremos. Se irá convirtiendo sucesivamente en los números de la secuencia, esta vez algo más largo. Pero además, vamos a pedirle que nos saque no solo el valor de j en cada iteración, sino su cuadrado:

for (j in 1:10){
  print(j)
  print(j^2)
}
## [1] 1
## [1] 1
## [1] 2
## [1] 4
## [1] 3
## [1] 9
## [1] 4
## [1] 16
## [1] 5
## [1] 25
## [1] 6
## [1] 36
## [1] 7
## [1] 49
## [1] 8
## [1] 64
## [1] 9
## [1] 81
## [1] 10
## [1] 100

No tenemos que hacerlo solo con secuencias numéricas aunque serán nuestra principal herramienta porque los podremos usar como índices para movernos por los objetos. También podemos usar otro tipo de datos, como nombres. Si tenemos un vector de cadenas de nombres podemos hacer lo mismo:

contaminantes<-c("lindano","fósforo","metano")

for(c in contaminantes){
  print(c)
}
## [1] "lindano"
## [1] "fósforo"
## [1] "metano"

Decíamos que utilizar números como índices es una herramientas muy útil para diseñar nuestros bucles. Es lo que vamos a hacer a continuación. Recordamos que podemos acceder a un elemento de un vector diciéndole entre corchetes la posición numérica de los datos:

contaminantes[1]
## [1] "lindano"

Por lo tanto, podemos recorrer este vector también con números que utilizaremos como índices:

for(c in 1:3){
  print(contaminantes[c])
}
## [1] "lindano"
## [1] "fósforo"
## [1] "metano"

Ahora bien, ¿qué pasa si tengo que añadir un cuarto contaminante a nuestro vector? Que tengo que cambiar la secuencia 1:3 a 1:4. Si esto me sucede a menudo, voy a ser muy ineficiente porque tengo que cambiar muchas veces mi código. Si además tengo vectores largos, no es viable contar a mano. Es mejor usar funciones como length() para tener el dato del número de elementos de mi vector:

contaminantes<-c("lindano","fósforo","metano","plomo")
length(contaminantes)
## [1] 4

Y de esta manera, voy a tener un mejor código, más flexible, si utilizo esta función para decirle a mi bucle la longitud exacta de mi vector: desde 1 hasta tantos números tenga mi vector (o lista, o líneas en mi dataframe), tal y como me indica `length(contaminantes)’. Así me va a dar igual tener 2, 5 o 1000 elementos en ese vector. El bucle siempre es válido:

for(c in 1:length(contaminantes)){
  print(contaminantes[c])
}
## [1] "lindano"
## [1] "fósforo"
## [1] "metano"
## [1] "plomo"

Ahora bien, generalmente necesitaremos operar sobre más de un elemento: otra columna de datos en un dataframe, o como en el siguiente ejemplo, sobre otro vector que tiene información para cada uno de los cuatro contaminantes, que explica qué tipo de contaminante es: Lindano estaría asociado a contaminante de ríos, etc.

propiedades<-c("Contaminantes de ríos","Eutrofizantes","Gas invernadero","Contaminantes")

Yo lo que quiero es sacar por pantalla ambos elementos alojados en vectores distintos, pero que tienen los elementos con información asociada entre ellos. Una función útil es paste() porque nos permite introducir como elementos tantos objetos, elementos o cadenas de texto, separadas por comas, para que nos la una en una sola frase:

paste("Alejandro","González")
## [1] "Alejandro González"

En el caso de nuestros contaminantes, podemos utilizarla justo para eso. Para imprimir en pantalla la información sobre el lindano, simplemente tenemos que acudir a usar el índice para acceder al elemento en el vector contaminantes y el mismo índice en el vector propiedades:

paste(contaminantes[1],propiedades[1])
## [1] "lindano Contaminantes de ríos"

Si lo quiero hacer para todos los contaminantes sacando sus propiedades, lo podemos hacer con un bucle for:

for(c in 1:length(propiedades)){
  print(paste(contaminantes[c],propiedades[c]))
}
## [1] "lindano Contaminantes de ríos"
## [1] "fósforo Eutrofizantes"
## [1] "metano Gas invernadero"
## [1] "plomo Contaminantes"

Cuando realmente utilizamos los bucles para hacer cálculos más largos o más pesados, podremos encontrarnos sin saber en qué momento de los cálculos estamos, especialmente si son cálculos pesados, porque el bucle no saca resultados por pantalla a menos que haya una instrucción que lo haga (como básicamente hacíamos hasta ahora), como el caso de print(), u otras como cat()o message(). La función paste()por su parte nos permite unir cadenas de texto y otros objetos (que se convierten texto a su vez) de tal manera que podemos concatenar texto en un solo objeto. Introducimos todas las cadenas de texto u objetos, separados por comas.

nombre<-"Alejandro"
apellido<-"González"
paste(nombre, apellido)
## [1] "Alejandro González"

Otras exploracioes introductorias:

Vamos a utilizar paste() en un bucle combinando valores numéricos y cadenas de texto para expresar el resultado. Atención, porque para tener el resultado en la pantalla, el resultado de paste()lo tendremos que meter dentro de print(). Si no, al estar dentro de un bucle no aparecerá el resultado. En este caso no solo podemos meter un objeto numérico (j) sino operaciones sobre él (calcular su cuadrado).

for (numero in 1:4){
  print(paste("El número",numero,"tiene como cuadrado",numero^2))
}
## [1] "El número 1 tiene como cuadrado 1"
## [1] "El número 2 tiene como cuadrado 4"
## [1] "El número 3 tiene como cuadrado 9"
## [1] "El número 4 tiene como cuadrado 16"

Finalmente, vamos a realizar un último bucle algo más complicado que nos servirá para el siguiente ejercicio, que ya será más aplicado. El objetivo es aprender a usar la secuencia de números como un índice que nos sirva para movernos por las filas o columnas de nuestros datos. Para ello, vamos a utilizar un juego de datos con especies, donde cada especie está descrita en una fila. Por lo tanto, si vamos a hacer una operación para cada especie, tendremos que movernos por filas.

especies_latin<-c("Quercus ilex","Hevea brasiliensis","Betula nana") #creamos un vector de nombres en latín
especies_castellano<-c("Encina","Caucho", "Abedul") #creamos un vector con los mismos nombres de epsecies en castellano
especies<-cbind(especies_latin,especies_castellano) #pegamos con la función cbind() por columnas, obteniendo una matriz.
especies<-as.data.frame(especies,stringsAsFactors=FALSE) #sobreescribimos la matriz, creando un marco de datos con esa matriz.

Activamos el parámetro stringsAsFactors=FALSE, para evitar que R reconozca los nombres como factores y los convierta a números. Este parámetro es muy útil porque es uno de los problemas más comunes en R.

El bucle tiene las siguentes particularidades: - el objeto iterativo lo llamaremos sp (por especies) - La secuencia es numérica, pero es más flexible. Con la expresión 1:nrow(especies) le podemos decir que contenga la serie numérica desde el 1 hasta el número de filas que contiene nuestro marco de datos. En este caso tenemos tres, por lo que podríamos escribir una secuencia 1:3. Pero si el número de filas cambia, tendremos que cambiar el bucle. Si utilizamos ’ nrow() para saber el número de filas, tendremos una operación más flexible que no necesitaremos cambiar si los datos iniciales cambian. - Definimos un primer objeto teporal llamado ‘latin’ que se va a corresponder con la celda correspondiente al número de fila que toque y al número de columna donde está su nombre en latín, que es la primera. Por tanto y según la notación de corchetes será la celda [sp,1]. El número correspondiente al índice donde indicamos la fila es sp, y por lo tanto nos permitirá irnos moviendo a lo largo del bucle. - A continuación repetimos la misma operación con el nombre en castellano (objeto cast), que hemos almacenado en la segunda columna, por tanto en el índice 2 para las columnas. - Finalmente, sacamos por pantalla la unión con paste()de cadenas de texto fijas (“nombre latin” y “nombre castellano”) combinadas con el valor de los objetos latin y cast.

for (sp in 1:nrow(especies)){
  latin<-especies[sp,1]#latin será el valor de la celda fila=sp, columna 1.
  cast<-especies[sp,2]#cast será el valor de la celda fila=sp, columna 2.
  print(paste("Nombre latin:",latin,"Nombre castellano:",cast))
}
## [1] "Nombre latin: Quercus ilex Nombre castellano: Encina"
## [1] "Nombre latin: Hevea brasiliensis Nombre castellano: Caucho"
## [1] "Nombre latin: Betula nana Nombre castellano: Abedul"