for() loops.

Los for() loops son una herramienta muy útil para realizar tareas repetitivas (iterar) sistematicamente. Estas tareas pueden ser realizadas a lo largo de los elementos de una lista, de un data frame (filas o columnas), una matriz (filas o columnas), de una lista de data frames o matrices, o simplemente de un número de repeticiones dado.
Cuando se crea un loop, R va a ejecutar las instrucciones que le digamos un número de veces especificado, o hasta que se cumpla cietra condición. En esta oportunidad vamos a ver solamente los loops donde se especifíca el número de repeticiones o ciclos.

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

En estos dos ejemplos no hay un objeto externo, pero muchas veces necesitamos que, lo que en nuestro ejemplo fueron simplemente números enteros consecutivos (1,2,3,4,5), sean en verdad objetos externos. Veamos un ejemplo simple con los datos que se encuentran en el archivo “datos_loops.txt”. Imaginemos que cada columna (A a H) es una localidad diferente y que para cada localidad tenemos una muestra de la talla de 100 individuos.

Podemos calcular sistematicamente la media de cada localidad (aunque no es tan práctico, es sólo para ejemplificar…).

Primero cargamos los datos y exploramos su estructura…

datos<-read.table("datos_loops.txt", header=TRUE)
head(datos)
##      A    B    C    D    E    F    G    H
## 1 11.3  9.6  9.8 19.4  6.2 15.4 21.9  7.4
## 2 11.7 10.1 11.0 12.7 12.0 10.4  7.9  6.8
## 3 11.0 12.3  7.8 13.6  7.8 20.1 23.1 13.7
## 4 10.6 10.6 11.4 12.3  9.7 16.1 29.9 12.4
## 5  9.9 20.3  8.7  3.3 10.8 21.5  6.5 11.3
## 6  9.0  9.3 11.3  1.0 11.1 20.3 30.8  5.8

Son 8 columnas (localidades), tenemos que hacer un loop que tenga 8 ciclos y en cada ciclo calcule (por ejemplo) la media de una de las columnas:

for(i in 1:8){
  print(mean(datos[,i]))
}
## [1] 9.82
## [1] 10.847
## [1] 10.231
## [1] 10.015
## [1] 9.701
## [1] 20.665
## [1] 20.296
## [1] 9.334

Como vemos, está haciendo la tarea pero solamente nos muestra (imprime) los resultados. Tal vez nos interece guardarlos. Hay diferentes maneras de conseguir el mismo resultado. Una es crear un objeto y llenarlo en cada vuelta.

medias<-data.frame(localidad=c("A", "B", "C", "D", "E", "F", "G", "H"), media=rep(NA, 8))
medias
##   localidad media
## 1         A    NA
## 2         B    NA
## 3         C    NA
## 4         D    NA
## 5         E    NA
## 6         F    NA
## 7         G    NA
## 8         H    NA

La columna que tiene que tener las medias está temporalmente vacía. Tenemos que lograr que se llene con los valores calculados en cada ciclo.

for(i in 1:8){
  medias$media[i]<-mean(datos[,i])
}
medias
##   localidad  media
## 1         A  9.820
## 2         B 10.847
## 3         C 10.231
## 4         D 10.015
## 5         E  9.701
## 6         F 20.665
## 7         G 20.296
## 8         H  9.334

Ahora medias tiene los valores que queríamos.

Podemos complejizar esto un poco…

estadisticos<-data.frame(localidad=c("A", "B", "C", "D", "E", "F", "G", "H"), 
                         media=rep(NA, 8), 
                         mediana=rep(NA, 8), 
                         Q25=rep(NA, 8), 
                         Q75=rep(NA, 8))

for(i in 1:8){
  estadisticos$media[i]<-mean(datos[,i])
  estadisticos$mediana[i]<-median(datos[,i])
  estadisticos$Q25[i]<-quantile(datos[,i], 0.25)
  estadisticos$Q75[i]<-quantile(datos[,i], 0.75)
}

estadisticos
##   localidad  media mediana    Q25    Q75
## 1         A  9.820   10.30  9.075 11.100
## 2         B 10.847   10.10  9.000 11.850
## 3         C 10.231   10.25  9.000 11.225
## 4         D 10.015   10.20  5.550 13.725
## 5         E  9.701    9.75  7.350 12.050
## 6         F 20.665   20.90 17.700 23.800
## 7         G 20.296   19.15 15.200 25.525
## 8         H  9.334    8.50  6.300 12.450

Podríamos usar loops para graficar

par(mfrow=c(2,4))
for(i in 1:8){
hist(datos[,i])  
}

Loops anidados: Muchas veces es necesario hacer una especie de mamushka de loops para realizar la tarea que necesitamos. Veamos la lógica de los loops anidados:

for (i in 1:3)
{
    for (j in 1:3)
    {
        print(i * j)
    }
}
## [1] 1
## [1] 2
## [1] 3
## [1] 2
## [1] 4
## [1] 6
## [1] 3
## [1] 6
## [1] 9

Veamos una posible aplicación usando los datos de las tallas y localidades. Supongamos que queremos realizar un test de t para cada par de localidades. Empecemos de a poco… Para realizar un test de t comparando dos localidades podemos usar la función t.test().

t.test(datos$A, datos$B, var.equal = F)
## 
##  Welch Two Sample t-test
## 
## data:  datos$A and datos$B
## t = -2.9444, df = 165.83, p-value = 0.003701
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
##  -1.7156556 -0.3383444
## sample estimates:
## mean of x mean of y 
##     9.820    10.847

Si (y ya van a ver porqué) de esta salida queremos quedarnos con la información que nos interesa, podemos hacerlo.

resultado<-t.test(datos$A, datos$B, var.equal = F)
str(resultado)
## List of 10
##  $ statistic  : Named num -2.94
##   ..- attr(*, "names")= chr "t"
##  $ parameter  : Named num 166
##   ..- attr(*, "names")= chr "df"
##  $ p.value    : num 0.0037
##  $ conf.int   : num [1:2] -1.716 -0.338
##   ..- attr(*, "conf.level")= num 0.95
##  $ estimate   : Named num [1:2] 9.82 10.85
##   ..- attr(*, "names")= chr [1:2] "mean of x" "mean of y"
##  $ null.value : Named num 0
##   ..- attr(*, "names")= chr "difference in means"
##  $ stderr     : num 0.349
##  $ alternative: chr "two.sided"
##  $ method     : chr "Welch Two Sample t-test"
##  $ data.name  : chr "datos$A and datos$B"
##  - attr(*, "class")= chr "htest"
resultado$statistic
##         t 
## -2.944404
resultado$p.value
## [1] 0.003700625

Listo, con lo que aprendimos antes podemos armar nuestro loop para realizar las comparaciones de a pares y guardar todo en una tabla

salida=NULL
for (i in 1:7)
{
  for (j in (i+1):8)
  {
    resultados<-t.test(datos[,i], datos[,j])
    comparacion<-paste(colnames(datos)[i], colnames(datos)[j])
    t<-resultados$statistic
    p<-resultados$p.value
    salida_ij<-data.frame(comparacion, t, p)
    salida<-rbind(salida, salida_ij)
  }
}

salida
##     comparacion           t            p
## t           A B  -2.9444041 3.700625e-03
## t1          A C  -1.6232963 1.061232e-01
## t2          A D  -0.3352866 7.379901e-01
## t3          A E   0.2850648 7.760030e-01
## t4          A F -23.0722658 1.704977e-48
## t5          A G -13.1220565 2.965652e-24
## t6          A H   1.3431479 1.811153e-01
## t7          B C   1.7956271 7.444353e-02
## t8          B D   1.3291481 1.857952e-01
## t9          B E   2.4008419 1.733246e-02
## t10         B F -18.7383846 1.065266e-43
## t11         B G -11.3674677 4.287396e-21
## t12         B H   3.5223363 5.312983e-04
## t13         C D   0.3735946 7.093742e-01
## t14         C E   1.2843389 2.011509e-01
## t15         C F -22.4001615 1.799799e-46
## t16         C G -12.6467166 4.234068e-23
## t17         C H   2.5175050 1.283444e-02
## t18         D E   0.4710044 6.382266e-01
## t19         D F -15.1970105 1.649135e-34
## t20         D G -10.7920374 3.181419e-21
## t21         D H   1.0752805 2.839067e-01
## t22         E F -19.1694426 1.191982e-46
## t23         E G -12.2869976 4.016009e-24
## t24         E H   0.7536831 4.519647e-01
## t25         F G   0.4150973 6.786448e-01
## t26         F H  21.2700668 5.515831e-51
## t27         G H  13.1001087 1.608314e-25

Los ciclos no necesariamente tienen que estar definidos con números consecutivos de 1 a n (en verdad va a terminar siendo lo mismo, pero podemos hacer referencia directa a objetos sin tener que pasar por números consecutivos).

for (i in c(-8, 9, 11, 45))
{
    print(i)
}
## [1] -8
## [1] 9
## [1] 11
## [1] 45
x<- c(-8, 9, 11, 45)
for (i in x)
{
    print(i+2)
}
## [1] -6
## [1] 11
## [1] 13
## [1] 47

Usando un ejemplo con una base de datos que se encuentra precargada en R:

data(iris)
head(iris)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
## 4          4.6         3.1          1.5         0.2  setosa
## 5          5.0         3.6          1.4         0.2  setosa
## 6          5.4         3.9          1.7         0.4  setosa

En este caso, si nos interesa analizar por separado cada especie, la lógica es diferente al ejemplo de las tallas ya que no tenemos cada especie en una columna diferente sino que tenemos una columna que nos dice a qué especie pertenece cada dato. Podemos ver esta columna y crear un vector con sus “niveles” o posibles valores.

iris$Species
##   [1] setosa     setosa     setosa     setosa     setosa     setosa    
##   [7] setosa     setosa     setosa     setosa     setosa     setosa    
##  [13] setosa     setosa     setosa     setosa     setosa     setosa    
##  [19] setosa     setosa     setosa     setosa     setosa     setosa    
##  [25] setosa     setosa     setosa     setosa     setosa     setosa    
##  [31] setosa     setosa     setosa     setosa     setosa     setosa    
##  [37] setosa     setosa     setosa     setosa     setosa     setosa    
##  [43] setosa     setosa     setosa     setosa     setosa     setosa    
##  [49] setosa     setosa     versicolor versicolor versicolor versicolor
##  [55] versicolor versicolor versicolor versicolor versicolor versicolor
##  [61] versicolor versicolor versicolor versicolor versicolor versicolor
##  [67] versicolor versicolor versicolor versicolor versicolor versicolor
##  [73] versicolor versicolor versicolor versicolor versicolor versicolor
##  [79] versicolor versicolor versicolor versicolor versicolor versicolor
##  [85] versicolor versicolor versicolor versicolor versicolor versicolor
##  [91] versicolor versicolor versicolor versicolor versicolor versicolor
##  [97] versicolor versicolor versicolor versicolor virginica  virginica 
## [103] virginica  virginica  virginica  virginica  virginica  virginica 
## [109] virginica  virginica  virginica  virginica  virginica  virginica 
## [115] virginica  virginica  virginica  virginica  virginica  virginica 
## [121] virginica  virginica  virginica  virginica  virginica  virginica 
## [127] virginica  virginica  virginica  virginica  virginica  virginica 
## [133] virginica  virginica  virginica  virginica  virginica  virginica 
## [139] virginica  virginica  virginica  virginica  virginica  virginica 
## [145] virginica  virginica  virginica  virginica  virginica  virginica 
## Levels: setosa versicolor virginica
spp<-unique(iris$Species)

Supongamos que queremos graficar la relación entre dos variables morfológicas para cada especie

par(mfrow=c(1,3))
for(i in spp){
  datosXsp<-subset(iris, iris$Species==i)
  plot(datosXsp$Sepal.Length,datosXsp$Petal.Length)
}