11.3 Bucles y vectorización

11.3.1 Bucles

R permite crear bucles repetitivos (loops) y la ejecución condicional de sentencias. R admite bucles for, repeat and while.

11.3.1.1 El bucle for

La sintaxis de un bucle for es la que sigue:

for (i in lista_de_valores)  { expresión }

Por ejemplo, dado un vector \(x\) se puede calcular \(y=x^2\) con el código:

x <- seq(-2, 2, 0.5)
n <- length(x)
y <- numeric(n) # Es necesario crear el objeto para acceder a los componentes...
for (i in 1:n) { y[i] <- x[i] ^ 2 }
x
## [1] -2.0 -1.5 -1.0 -0.5  0.0  0.5  1.0  1.5  2.0
y
## [1] 4.00 2.25 1.00 0.25 0.00 0.25 1.00 2.25 4.00
x^2
## [1] 4.00 2.25 1.00 0.25 0.00 0.25 1.00 2.25 4.00

Otro ejemplo:

for(i in 1:5) print(i)
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5

El siguiente código simula gráficamente el segundero de un reloj:

angulo <- seq(0, 360, by = 6)
radianes <- angulo * pi / 180
x <- sin(radianes)
y <- cos(radianes)

sec <- seq(6, 61, by = 5)
for (i in 1:61) {
  plot(x, y, axes = FALSE, xlab = "", ylab = "", type = 'l', col = 'grey')
  points(x[i], y[i])
  # Añadir "decoración"
  text(x[sec]*0.9, y[sec]*0.9, labels = sec - 1)
  arrows(0, 0, x[i]*0.85, y[i]*0.85, col = 'blue')
  # Esperar un segundo
  Sys.sleep(1)
}

11.3.1.2 El bucle while

La sintaxis del bucle while es la que sigue:

while (condición lógica)  { expresión }

Por ejemplo, si queremos calcular el primer número entero positivo cuyo cuadrado no excede de 5000, podemos hacer:

cuadrado <- 0
n <- 0
while (cuadrado <= 5000) {
  n <- n + 1
  cuadrado <- n^2
}
cuadrado
## [1] 5041
n
## [1] 71
n^2
## [1] 5041

Nota: Dentro de un bucle se puede emplear el comando break para terminarlo y el comando next para saltar a la siguiente iteración.

11.3.2 Vectorización

Como hemos visto en R se pueden hacer bucles. Sin embargo, es preferible evitar este tipo de estructuras y tratar de utilizar operaciones vectorizadas que son mucho más eficientes desde el punto de vista computacional.

Por ejemplo para sumar dos vectores se puede hacer con un for:

x <- c(1, 2, 3, 4)
y <- c(0, 0, 5, 1)
n <- length(x)
z <- numeric(n)
for (i in 1:n) {
  z[i] <- x[i] + y[i]
}
z
## [1] 1 2 8 5

Sin embargo, la operación anterior se podría hacer de modo más eficiente en modo vectorial:

z <- x + y
z
## [1] 1 2 8 5

11.3.3 Funciones apply

11.3.3.1 La función apply

Una forma de evitar la utilización de bucles es utilizando la función apply() que permite evaluar una misma función en todas las filas, columnas, …. de un array de forma simultánea.

La sintaxis de esta función es:

apply(X, MARGIN, FUN, ...)
  • X: matriz (o array)
  • MARGIN: Un vector indicando las dimensiones donde se aplicará la función. 1 indica filas, 2 indica columnas, y c(1,2) indica filas y columnas.
  • FUN: función que será aplicada.
  • ...: argumentos opcionales que serán usados por FUN.

Veamos la utilización de la función apply con un ejemplo:

x <- matrix(1:9, nrow = 3)
x
##      [,1] [,2] [,3]
## [1,]    1    4    7
## [2,]    2    5    8
## [3,]    3    6    9
apply(x, 1, sum)    # Suma por filas
## [1] 12 15 18
apply(x, 2, sum)    # Suma por columnas
## [1]  6 15 24
apply(x, 2, min)    # Mínimo de las columnas
## [1] 1 4 7
apply(x, 2, range)  # Rango (mínimo y máximo) de las columnas
##      [,1] [,2] [,3]
## [1,]    1    4    7
## [2,]    3    6    9

11.3.3.2 La función tapply

La function tapply es similar a la función apply y permite aplicar una función a los datos desagregados, utilizando como criterio los distintos niveles de una variable factor. La sintaxis de esta función es como sigue:

    tapply(X, INDEX, FUN, ...,)
  • X: matriz (o array).
  • INDEX: factor indicando los grupos (niveles).
  • FUN: función que será aplicada.
  • ...: argumentos opcionales .

Consideremos, por ejemplo, el data.frame ChickWeight con datos de un experimento relacionado con la repercusión de varias dietas en el peso de pollos.

data(ChickWeight)
head(ChickWeight)
##   weight Time Chick Diet
## 1     42    0     1    1
## 2     51    2     1    1
## 3     59    4     1    1
## 4     64    6     1    1
## 5     76    8     1    1
## 6     93   10     1    1
peso <- ChickWeight$weight
dieta <- ChickWeight$Diet
levels(dieta) <- c("Dieta 1", "Dieta 2", "Dieta 3", "Dieta 4")
tapply(peso, dieta, mean)  # Peso medio por dieta
##  Dieta 1  Dieta 2  Dieta 3  Dieta 4 
## 102.6455 122.6167 142.9500 135.2627
tapply(peso, dieta, summary)
## $`Dieta 1`
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   35.00   57.75   88.00  102.65  136.50  305.00 
## 
## $`Dieta 2`
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    39.0    65.5   104.5   122.6   163.0   331.0 
## 
## $`Dieta 3`
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    39.0    67.5   125.5   142.9   198.8   373.0 
## 
## $`Dieta 4`
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   39.00   71.25  129.50  135.26  184.75  322.00

Otro ejemplo:

provincia <- as.factor(c(1, 3, 4, 2, 4, 3, 2, 1, 4, 3, 2))
levels(provincia) = c("A Coruña", "Lugo", "Orense", "Pontevedra")
hijos <- c(1, 2, 0, 3, 4, 1, 0, 0, 2, 3, 1)
data.frame(provincia, hijos)
##     provincia hijos
## 1    A Coruña     1
## 2      Orense     2
## 3  Pontevedra     0
## 4        Lugo     3
## 5  Pontevedra     4
## 6      Orense     1
## 7        Lugo     0
## 8    A Coruña     0
## 9  Pontevedra     2
## 10     Orense     3
## 11       Lugo     1
tapply(hijos, provincia, mean) # Número medio de hijos por provincia
##   A Coruña       Lugo     Orense Pontevedra 
##   0.500000   1.333333   2.000000   2.000000