9 Algorítmico
9.1 Pruebas lógicas con if
Si queremos realizar una operación diferente según una condición, podemos configurar una prueba lógica del tipo SI esto ENTONCES esto SINO esto. Con R esto dará como resultado la función if(cond) cons.express alt.expr
como se muestra en la función help.
<- 2
myVar if(myVar < 3) print("myVar < 3")
## [1] "myVar < 3"
if(myVar < 3) print("myVar < 3") else print("myVar > 3")
## [1] "myVar < 3"
Cuando hay varias líneas de código para ejecutar basadas en la prueba lógica, o simplemente para hacer que el código sea más fácil de leer, utilizamos varias líneas con {}
y con identacion.
<- 2
myVar <- 0
myResult if(myVar < 3){
print("myVar < 3")
<- myVar + 10
myResult else {
} print("myVar > 3")
<- myVar - 10
myResult }
## [1] "myVar < 3"
print(myResult)
## [1] 12
En este ejemplo definimos una variable myVar
. Si esta variable es menor que 3, la variable myResult
se establece en myVar + 10
, y de lo contrario myResult
se establece en myVar - 10
.
Ya hemos visto el uso de la prueba lógica if
en el capítulo sobre las funciones. Habiamos probado si la variable ingresada como argumento en nuestra función era de tipo character
.
<- "qwerty"
myVar if(is.character(myVar)){
print("ok")
else {
} print("error")
}
## [1] "ok"
También podemos anidar pruebas lógicas entre sí.
<- TRUE
myVar if(is.character(myVar)){
print("myVar: character")
else {
} if(is.numeric(myVar)){
print("myVar: numeric")
else {
} if(is.logical(myVar)){
print("myVar: logical")
else {
} print("myVar: ...")
}
} }
## [1] "myVar: logical"
También es posible estipular varias condiciones, como vimos en el capítulo sobre operadores de comparación.
<- 2
myVar if(myVar > 1 & myVar < 50){
print("ok")
}
## [1] "ok"
En este ejemplo, myVar
está en formato numeric
, por lo que la primera condición (> 1
) y la segunda condición (< 50
) son verificables. Por otro lado, si asignamos una variable de tipo character
a myVar
entonces R transformará 0 y 10 en objetos de tipo character
y probará si myVar> "1"
y despues si myVar < "50"
basandose en la clasificación alfabética. En el siguiente ejemplo, "azerty"
no está ubicado segun el orden alfabético entre "1"
y "50"
, pero para "2azerty"
es el caso, lo que resulta problematico.
<- "azerty"
myVar <- 1
limInit <- 50
limEnd if(myVar > limInit & myVar < limEnd){
print(paste0(myVar, " is between ", limInit, " and ", limEnd, "."))
else {
} print(paste0(myVar, " not between ", limInit, " and ", limEnd, "."))
}
## [1] "azerty not between 1 and 50."
<- "2azerty"
myVar if(myVar > limInit & myVar < limEnd){
print(paste0(myVar, " is between ", limInit, " and ", limEnd, "."))
else {
} print(paste0(myVar, " not between ", limInit, " and ", limEnd, "."))
}
## [1] "2azerty is between 1 and 50."
Entonces, lo que nos gustaría hacer es probar si myVar
está en formato numeric
, y entonces solo si es el caso probar las siguientes condiciones.
<- "2azerty"
myVar if(is.numeric(myVar)){
if(myVar > limInit & myVar < limEnd){
print(paste0(myVar, " is between ", limInit, " and ", limEnd, "."))
else {
} print(paste0(myVar, " not between ", limInit, " and ", limEnd, "."))
}else {
} print(paste0("Object ", myVar, " is not numeric"))
}
## [1] "Object 2azerty is not numeric"
A veces es posible que necesitemos probar una primera condición y luego una segunda condición solo si la primera es verdadera en la misma prueba. Por ejemplo, para un sitio nos gustaría saber si hay una sola especie y probar si su abundancia es mayor que 10. Imagine un conjunto de datos con abundancia de vectores. Probaremos el número de especies con la función length()
.
<- c(15, 14, 20, 12)
mySpecies if(length(mySpecies) == 1 & mySpecies > 10){
print("ok!")
}## Warning message:
## In if (length(mySpecies) == 1 & mySpecies > 10) { :
## the condition has length > 1 and only the first element will be used
R devuelve un error porque no puede dentro de una prueba lógica con if()
verificar la segunda condición. De hecho, mySpecies > 10
devuelve TRUE TRUE TRUE TRUE TRUE
. Podemos separar el código en dos condiciones:
<- c(15, 14, 20, 12)
mySpecies if(length(mySpecies) == 1){
if(mySpecies > 10){
print("ok!")
} }
Una alternativa más elegante es decirle a R que verifique la segunda condición solo si la primera es verdadera. Para eso podemos usar &&
en lugar de &
.
<- c(15, 14, 20, 12)
mySpecies if(length(mySpecies) == 1 && mySpecies > 10){
print("ok!")
}<- 15
mySpecies if(length(mySpecies) == 1 && mySpecies > 10){
print("ok!")
}
## [1] "ok!"
<- 5
mySpecies if(length(mySpecies) == 1 && mySpecies > 10){
print("ok!")
}
Con &
R comprobará todas las condiciones, y con &&
R tomará cada condición una después de la otra y continuará solo si es verdadera. Esto puede parecer anecdótico, pero es bueno saber la diferencia entre &
y &&
porque a menudo los encontramos en los códigos disponibles en Internet o en los paquetes.
9.2 Pruebas lógicas con switch
La función switch()
es una variante de if()
que es útil cuando tenemos muchas opciones posibles para la misma expresión. El siguiente ejemplo muestra cómo transformar el código usando if()
a switch()
.
<- "aa"
x if(x == "a"){
<- 1
result
}if(x == "aa"){
<- 2
result
}if(x == "aaa"){
<- 3
result
}if(x == "aaaa"){
<- 4
result
}print(result)
## [1] 2
<- "aa"
x switch(x,
a = result <- 1,
aa = result <- 2,
aaa = result <- 3,
aaaa = result <- 4)
print(result)
## [1] 2
9.3 El bucle for
En programación, cuando tenemos que repetir la misma línea de código varias veces, es un signo que indica que debemos usar un bucle. Un bucle es una forma de iterar sobre un conjunto de objetos (o los elementos de un objeto) y repetir una operación. Imaginamos un data.frame
con mediciones de datos de campo en dos fechas.
<- data.frame(date01 = rnorm(n = 100, mean = 10, sd = 1),
bdd date02 = rnorm(n = 100, mean = 10, sd = 1))
print(head(bdd))
## date01 date02
## 1 11.08157 12.042192
## 2 10.96308 10.681778
## 3 10.25868 12.997187
## 4 11.05507 9.531929
## 5 11.32432 8.984654
## 6 11.50681 9.361067
Nos gustaría cuantificar la diferencia entre la primera y la segunda fecha, luego poner un indicador para saber si esta diferencia es pequeña o grande, por ejemplo, con un umbral arbitrario de 3. Entonces, para cada línea podríamos hacer:
$dif <- NA
bdd$isDifBig <- NA
bdd
$dif[1] <- sqrt((bdd$date01[1] - bdd$date02[1])^2)
bdd$dif[2] <- sqrt((bdd$date01[2] - bdd$date02[2])^2)
bdd$dif[3] <- sqrt((bdd$date01[3] - bdd$date02[3])^2)
bdd# ...
$dif[100] <- sqrt((bdd$date01[100] - bdd$date02[100])^2)
bdd
if(bdd$dif[1] > 3){
$isDifBig[1] <- "big"
bddelse{
}$isDifBig[1] <- "small"
bdd
}if(bdd$dif[2] > 3){
$isDifBig[2] <- "big"
bddelse{
}$isDifBig[2] <- "small"
bdd
}if(bdd$dif[3] > 3){
$isDifBig[3] <- "big"
bddelse{
}$isDifBig[3] <- "small"
bdd
}# ...
if(bdd$dif[100] > 3){
$isDifBig[100] <- "big"
bddelse{
}$isDifBig[100] <- "small"
bdd }
Esta forma de hacer las cosas sería extremadamente tediosa de lograr, y casi imposible de lograr si la tabla contuviera 1000 o 100000 líneas. Puede parecer lógico querer iterar sobre las líneas de nuestro data.frame
para obtener las nuevas columnas. Es lo que vamos a hacer aun que no es la solución que retendremos más adelante.
Vamos a usar un bucle for()
. El bucle for()
recorrerá los elementos de un objeto que vamos a dar como argumento. Por ejemplo, aquí hay un bucle que para todos los números del 3 al 9 calculará su valor al cuadrado. El valor actual del número está simbolizado por un objeto que puede tomar el nombre que queramos (aquí será i
).
for(i in c(3, 4, 5, 6, 7, 8, 9)){
print(i^2)
}
## [1] 9
## [1] 16
## [1] 25
## [1] 36
## [1] 49
## [1] 64
## [1] 81
Eso podemos mejorar usando la función :
.
for(i in 3:9){
print(i^2)
}
El bucle for()
puede iterar sobre todos los tipos de elementos.
<- c("a", "z", "e", "r", "t", "y")
nChar for(i in nChar){
print(i)
}
## [1] "a"
## [1] "z"
## [1] "e"
## [1] "r"
## [1] "t"
## [1] "y"
Volvamos a nuestro caso. Vamos a iterar sobre el número de líneas de nuestro data.frame
bdd
. Antes de eso crearemos las columnas dif
y isDifBig
con los valores NA
. Luego usaremos la función nrow()
para encontrar el número de líneas.
$dif <- NA
bdd$isDifBig <- NA
bddfor(i in 1:nrow(bdd)){
$dif[i] <- sqrt((bdd$date01[i] - bdd$date02[i])^2)
bddif(bdd$dif[i] > 3){
$isDifBig[i] <- "big"
bddelse{
}$isDifBig[i] <- "small"
bdd
}
}print(head(bdd, n = 20))
## date01 date02 dif isDifBig
## 1 11.081575 12.042192 0.960617588 small
## 2 10.963078 10.681778 0.281300831 small
## 3 10.258683 12.997187 2.738503709 small
## 4 11.055069 9.531929 1.523140137 small
## 5 11.324320 8.984654 2.339666153 small
## 6 11.506812 9.361067 2.145744547 small
## 7 10.668047 10.659740 0.008306904 small
## 8 12.717266 7.914531 4.802734454 big
## 9 10.057564 11.533825 1.476261463 small
## 10 10.732572 10.578534 0.154037817 small
## 11 9.017811 8.364674 0.653136933 small
## 12 9.155670 9.541653 0.385983074 small
## 13 8.607951 9.849409 1.241457364 small
## 14 8.897687 11.000430 2.102743764 small
## 15 11.247456 9.521813 1.725643223 small
## 16 9.112583 11.818153 2.705569598 small
## 17 10.283146 9.615653 0.667492151 small
## 18 10.111092 9.180646 0.930446076 small
## 19 10.434803 9.666041 0.768762246 small
## 20 9.190073 10.802210 1.612136941 small
En la práctica, esta no es la mejor manera de realizar este ejercicio porque se trata de cálculos simples en vectores contenidos en un data.frame
. R es particularmente potente para realizar operaciones en vectores. Donde sea posible, siempre tenemos que enfócarnos en operaciones vectoriales. Aquí nuestro código se convierte en:
$dif <- sqrt((bdd$date01 - bdd$date02)^2)
bdd$isDifBig <- "small"
bdd$isDifBig[bdd$dif > 3] <- "big"
bddprint(head(bdd, n = 20))
## date01 date02 dif isDifBig
## 1 11.081575 12.042192 0.960617588 small
## 2 10.963078 10.681778 0.281300831 small
## 3 10.258683 12.997187 2.738503709 small
## 4 11.055069 9.531929 1.523140137 small
## 5 11.324320 8.984654 2.339666153 small
## 6 11.506812 9.361067 2.145744547 small
## 7 10.668047 10.659740 0.008306904 small
## 8 12.717266 7.914531 4.802734454 big
## 9 10.057564 11.533825 1.476261463 small
## 10 10.732572 10.578534 0.154037817 small
## 11 9.017811 8.364674 0.653136933 small
## 12 9.155670 9.541653 0.385983074 small
## 13 8.607951 9.849409 1.241457364 small
## 14 8.897687 11.000430 2.102743764 small
## 15 11.247456 9.521813 1.725643223 small
## 16 9.112583 11.818153 2.705569598 small
## 17 10.283146 9.615653 0.667492151 small
## 18 10.111092 9.180646 0.930446076 small
## 19 10.434803 9.666041 0.768762246 small
## 20 9.190073 10.802210 1.612136941 small
La mayoría de los ejemplos que se pueden encontrar en Internet sobre el bucle for()
pueden reemplazarse por operaciones vectoriales. Aquí hay algunos ejemplos adaptados de varias fuentes:
# prueba si los números son pares
# [1] FOR
<- sample(1:100, size = 20)
x <- 0
count for (val in x) {
if(val %% 2 == 0){
<- count + 1
count
}
}print(count)
## [1] 12
# [2] VECTOR
sum(x %% 2 == 0)
## [1] 12
# calcular cuadrados
# [1] FOR
<- rep(0, 20)
x for (j in 1:20){
<- j^2
x[j]
}print(x)
## [1] 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361
## [20] 400
# [2] VECTOR
1:20)^2 (
## [1] 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361
## [20] 400
# repetir una tirada de dados y promediar
# [1] FOR
= 1000
ntrials = rep(0, ntrials)
trials for (j in 1:ntrials){
= sample(1:6, size = 1)
trials[j]
}mean(trials)
## [1] 3.57
# [2] VECTOR
mean(sample(1:6, ntrials, replace = TRUE))
## [1] 3.536
Es un buen ejercicio explorar los muchos ejemplos disponibles en Internet en el bucle for()
e intentar convertirlos en operaciones vectoriales. Esto nos permite adquirir buenos reflejos de programación con R. El bucle for()
es muy útil, por ejemplo, para leer varios archivos y tratar la información que contienen de la misma manera, hacer gráficos, o Cuando las operaciones vectoriales se vuelven tediosas. Imagina una matriz de 10 columnas y 100 líneas. Queremos la suma de cada línea (veremos cómo hacer con la función apply()
mas adelante).
<- matrix(sample(1:100, size = 1000, replace = TRUE), ncol = 10)
myMat # VECTOR
<- myMat[, 1] + myMat[, 2] + myMat[, 3] + myMat[, 4] +
sumRow 5] + myMat[, 6] + myMat[, 7] + myMat[, 8] +
myMat[, 9] + myMat[, 10]
myMat[, print(sumRow)
## [1] 566 476 292 496 480 539 573 568 552 531 346 437 454 530 389 570 610 500
## [19] 537 556 525 583 392 399 575 468 546 595 535 501 526 476 351 315 482 648
## [37] 406 505 371 456 500 355 408 517 424 526 404 365 687 470 669 477 563 520
## [55] 508 470 664 510 471 511 556 419 629 494 603 556 396 380 294 623 358 524
## [73] 617 460 468 602 455 329 510 403 363 621 419 524 495 459 558 511 395 606
## [91] 533 524 444 386 563 460 330 384 491 629
# FOR
<- rep(NA, times = nrow(myMat))
sumRow for(j in 1:nrow(myMat)){
<- sum(myMat[j, ])
sumRow[j]
}print(sumRow)
## [1] 566 476 292 496 480 539 573 568 552 531 346 437 454 530 389 570 610 500
## [19] 537 556 525 583 392 399 575 468 546 595 535 501 526 476 351 315 482 648
## [37] 406 505 371 456 500 355 408 517 424 526 404 365 687 470 669 477 563 520
## [55] 508 470 664 510 471 511 556 419 629 494 603 556 396 380 294 623 358 524
## [73] 617 460 468 602 455 329 510 403 363 621 419 524 495 459 558 511 395 606
## [91] 533 524 444 386 563 460 330 384 491 629
En conclusión, se recomienda no usar el bucle for()
con R siempre que sea posible, y en este capítulo veremos alternativas como los bucles familiares apply()
.
9.4 El bucle while
El bucle while()
, a diferencia del bucle for()
, significa MIENTRAS. Mientras no se cumpla una condición, el bucle continuará ejecutándose. Atención porque en caso de error, podemos programar fácilmente bucles que nunca terminan. Este bucle es menos común que el bucle for()
. Tomemos un ejemplo:
<- 0
i while(i < 10){
print(i)
<- i + 1
i }
## [1] 0
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9
En este ejemplo, la variable i
tiene como valor inicial 0. MIENTRAS QUE i < 10
, mostramos i
con print()
. Para que este bucle finalice, no olvidamos cambiar el valor de i
, esto se hace con la línea i <- i + 1
. Cuando la condición i < 10
ya no se cumple, el bucle se detiene.
El bucle while()
es muy útil para crear scripts que realizarán cálculos en variables cuyo valor cambia con el tiempo. Por ejemplo, imaginamos un número entre 0 y 10000 y un generador aleatorio que intentará determinar el valor de este número. Si queremos limitar los intentos de R a 2 segundos, podemos escribir el siguiente script (que debería funcionar cada vez en una computadora de escritorio típica que pueda realizar fácilmente 35000 pruebas en 2 segundos):
<- sample(x = 10000, size = 1)
myNumber <- sample(x = 10000, size = 1)
myGuess <- Sys.time()
startTime <- 0
numberGuess while(Sys.time() - startTime < 2){
if(myGuess == myNumber){
<- numberGuess + 1
numberGuess print("Number found !")
print(paste0("And I have plenty of time left: ",
round(2 - as.numeric(Sys.time() - startTime), digits = 2),
" sec"))
break
else{
}<- sample(x = 10000, size = 1)
myGuess <- numberGuess + 1
numberGuess
} }
## [1] "Number found !"
## [1] "And I have plenty of time left: 1.25 sec"
En este script generamos un número aleatorio para adivinar con la función sample()
, y cada uno de los intentos con la misma función sample()
. Luego usamos la función Sys.time()
(con una S mayúscula a Sys
), para saber la hora de inicio del bucle. Siempre que la diferencia entre cada iteración del bucle y la hora de inicio sea inferior a 2 segundos, el bucle while()
verificará si el número correcto estaba adivinando en la prueba lógica con if()
y luego si es el caso nos informa que se encontró el número, y nos indica el tiempo restante antes de los dos segundos. Luego para finalizar el bucle usamos la palabra clave “break” en la que volveremos. En resumen, break
, permite salir de un bucle. Si no se ha adivinado el número, el bucle realiza otra prueba con la función sample()
.
Más concretamente, podríamos imaginar algoritmos para explorar un espacio de soluciones a un problema con un tiempo limitado para lograrlo. El bucle while()
también puede ser útil para que un script se ejecute solo cuando un archivo de otro programa esté disponible … En la práctica, el bucle while()
se usa poco con R.
9.5 El bucle repeat
El bucle repeat()
permite repetir una operación sin condiciones para verificar. Para salir de este bucle debemos usar la palabra clave break
.
<- 1
i repeat{
print(i^2)
<- i + 1
i if(i == 5){
break
} }
## [1] 1
## [1] 4
## [1] 9
## [1] 16
Si volvemos al ejemplo anterior, podemos usar un bucle repeat()
para repetirlo cinco veces.
<- 0
numTry repeat{
<- sample(x = 10000, size = 1)
myNumber <- sample(x = 10000, size = 1)
myGuess <- Sys.time()
startTime <- 0
numberGuess while(Sys.time() - startTime < 2){
if(myGuess == myNumber){
<- numberGuess + 1
numberGuess print(round(as.numeric(Sys.time() - startTime), digits = 3))
break
else{
}<- sample(x = 10000, size = 1)
myGuess <- numberGuess + 1
numberGuess
}
}<- numTry + 1
numTry if(numTry == 5){break}
}
## [1] 0.133
## [1] 1.484
## [1] 0.294
## [1] 0.424
## [1] 1.014
Al igual que el bucle while()
, el bucle repeat()
no se usa mucho con R.
9.6 next
y break
Ya hemos visto la palabra clave break
que permite salir del bucle actual. Por ejemplo, si buscamos el primer dígito después de 111 que es divisible por 32:
<- 111:1000
myVars for(myVar in myVars){
if(myVar %% 32 == 0){
print(myVar)
break
} }
## [1] 128
Aunque hemos visto que en la práctica podemos evitar el bucle for()
con una operación vectorial:
111:1000)[111:1000 %% 32 == 0][1] (
## [1] 128
La palabra clave next
permite pasar a la siguiente iteración de un bucle si se cumple una determinada condición. Por ejemplo, si queremos imprimir las letras del alfabeto sin las vocales:
for(myLetter in letters){
if(myLetter %in% c("a", "e", "i", "o", "u", "y")){
next
}print(myLetter)
}
## [1] "b"
## [1] "c"
## [1] "d"
## [1] "f"
## [1] "g"
## [1] "h"
## [1] "j"
## [1] "k"
## [1] "l"
## [1] "m"
## [1] "n"
## [1] "p"
## [1] "q"
## [1] "r"
## [1] "s"
## [1] "t"
## [1] "v"
## [1] "w"
## [1] "x"
## [1] "z"
De nuevo podimos evitar el bucle for()
con:
! letters %in% c("a", "e", "i", "o", "u", "y")] letters[
## [1] "b" "c" "d" "f" "g" "h" "j" "k" "l" "m" "n" "p" "q" "r" "s" "t" "v" "w" "x"
## [20] "z"
En conclusión, si usamos bucles, las palabras clave next
y break
suelen ser muy útiles, pero siempre que sea posible es mejor usar operaciones vectoriales. Cuando no es posible trabajar con vectores, es mejor usar los bucles del tipo apply
que son el tema de la siguiente sección.
9.7 Los bucles de la familia apply
9.7.1 apply
La función apply()
permite aplicar una función a todos los elementos de un array
o un matrix
. Por ejemplo, si queremos saber la suma de cada fila de una matriz
de 10 columnas y 100 líneas:
<- matrix(sample(1:100, size = 1000, replace = TRUE), ncol = 10)
myMat apply(X = myMat, MARGIN = 1, FUN = sum)
## [1] 525 543 455 464 347 541 483 482 341 461 664 361 687 364 576 569 484 523
## [19] 372 426 404 529 465 571 560 643 553 604 346 534 524 485 392 424 371 569
## [37] 505 520 567 390 505 419 359 578 553 353 529 544 467 558 449 433 539 413
## [55] 436 418 530 654 398 524 443 448 382 557 525 509 470 436 415 521 444 408
## [73] 576 333 595 588 596 375 405 659 435 507 617 489 585 532 450 451 397 417
## [91] 629 587 581 591 562 375 587 455 463 571
Si queremos saber la mediana de cada columna, la expresión se convierte en:
apply(X = myMat, MARGIN = 2, FUN = median)
## [1] 55.0 45.0 41.0 42.5 58.0 45.5 51.0 47.5 52.0 51.5
El argumento X
es el objeto en el que el bucle apply
se repetirá. El argumento MARGEN
corresponde a la dimensión a tener en cuenta (1 para las filas y 2 para las columnas). El argumento FUN
es la función a aplicar. En un objeto array
, el argumento MARGIN
puede tomar tantos valores como dimensiones. En este ejemplo, MARGIN = 1
es el promedio de cada fila - dimensión 1 - (todas las dimensiones combinadas), MARGIN = 2
es el promedio de cada columna - dimensión 2 - (todas las dimensiones combinadas), y MARGEN = 3
es el promedio de cada dimensión 3. Debajo cada cálculo se realiza de dos maneras diferentes para explicar su operación.
<- array(sample(1:100, size = 1000, replace = TRUE), dim = c(10, 20, 5))
myArr apply(X = myArr, MARGIN = 1, FUN = mean)
## [1] 50.29 49.92 49.01 52.29 51.04 48.66 53.35 49.15 51.65 49.85
apply(myArr[,,1], 1, mean) + apply(myArr[,,2], 1, mean) +
(apply(myArr[,,3], 1, mean) + apply(myArr[,,4], 1, mean) +
apply(myArr[,,5], 1, mean))/5
## [1] 50.29 49.92 49.01 52.29 51.04 48.66 53.35 49.15 51.65 49.85
apply(X = myArr, MARGIN = 2, FUN = mean)
## [1] 54.42 57.04 52.74 48.84 43.46 52.88 48.68 61.30 45.30 53.44 47.56 52.64
## [13] 46.58 47.98 47.82 49.94 54.38 47.96 45.90 51.56
apply(myArr[,,1], 2, mean) + apply(myArr[,,2], 2, mean) +
(apply(myArr[,,3], 2, mean) + apply(myArr[,,4], 2, mean) +
apply(myArr[,,5], 2, mean))/5
## [1] 54.42 57.04 52.74 48.84 43.46 52.88 48.68 61.30 45.30 53.44 47.56 52.64
## [13] 46.58 47.98 47.82 49.94 54.38 47.96 45.90 51.56
apply(X = myArr, MARGIN = 3, FUN = mean)
## [1] 52.850 50.590 51.345 49.480 48.340
c(mean(myArr[,,1]), mean(myArr[,,2]), mean(myArr[,,3]),
mean(myArr[,,4]), mean(myArr[,,5]))
## [1] 52.850 50.590 51.345 49.480 48.340
También podemos calcular el promedio de cada fila y valor de columna (la función luego itera en la dimensión 3):
apply(X = myArr, MARGIN = c(1, 2), FUN = mean)
## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13]
## [1,] 62.4 52.2 44.8 47.8 27.0 56.4 35.8 64.8 60.0 49.0 42.8 72.0 32.8
## [2,] 55.0 65.6 41.8 50.0 36.0 64.6 35.2 54.2 50.6 51.0 36.6 49.0 73.4
## [3,] 53.6 47.6 52.4 22.0 44.6 43.4 50.8 64.6 43.6 62.8 63.8 41.0 36.0
## [4,] 49.0 52.6 54.6 33.4 60.2 44.2 64.4 61.8 41.2 73.2 33.4 64.2 37.6
## [5,] 53.4 28.4 66.0 64.4 39.4 71.2 60.6 44.6 44.2 56.8 55.2 62.4 31.8
## [6,] 50.8 49.2 45.2 50.4 45.2 62.2 26.8 65.8 58.6 51.0 58.2 26.8 43.2
## [7,] 43.4 77.2 65.2 60.0 56.2 47.6 56.4 58.4 43.8 55.2 46.0 57.6 56.6
## [8,] 52.2 77.2 39.0 55.0 44.4 64.2 63.8 67.4 18.8 45.0 43.8 61.0 61.6
## [9,] 57.6 64.2 64.4 49.8 48.4 35.6 46.4 58.6 56.4 62.2 41.2 42.2 51.2
## [10,] 66.8 56.2 54.0 55.6 33.2 39.4 46.6 72.8 35.8 28.2 54.6 50.2 41.6
## [,14] [,15] [,16] [,17] [,18] [,19] [,20]
## [1,] 61.6 30.0 63.4 44.8 47.2 51.6 59.4
## [2,] 45.0 39.2 40.0 66.0 44.6 53.8 46.8
## [3,] 55.0 67.6 49.4 55.8 66.6 31.4 28.2
## [4,] 54.6 50.0 49.2 55.2 49.4 56.6 61.0
## [5,] 46.0 57.0 67.2 23.4 49.4 46.4 53.0
## [6,] 41.2 37.0 39.4 59.2 59.4 55.4 48.2
## [7,] 35.4 51.8 44.6 66.6 35.2 37.8 72.0
## [8,] 33.4 48.0 31.6 43.6 43.4 20.6 69.0
## [9,] 48.2 58.2 57.6 54.0 40.8 49.2 46.8
## [10,] 59.4 39.4 57.0 75.2 43.6 56.2 31.2
9.7.2 lapply
Como se indica en la documentación, lapply()
devuelve una lista de la misma longitud que X
, y cada elemento resulta de la aplicación FUN
al elemento X
correspondiente. Si X
es una list
que contiene vector
y estamos tratando de obtener el promedio de cada elemento de list
, podemos usar la función lapply()
:
<- list(
myList a = sample(1:100, size = 10),
b = sample(1:100, size = 10),
c = sample(1:100, size = 10),
d = sample(1:100, size = 10),
e = sample(1:100, size = 10)
)print(myList)
## $a
## [1] 8 45 7 71 83 57 47 6 69 87
##
## $b
## [1] 82 19 20 94 68 88 36 66 3 43
##
## $c
## [1] 53 23 7 66 60 59 20 17 24 13
##
## $d
## [1] 89 99 30 67 61 48 81 64 59 40
##
## $e
## [1] 73 39 3 100 8 14 99 2 67 15
lapply(myList, FUN = mean)
## $a
## [1] 48
##
## $b
## [1] 51.9
##
## $c
## [1] 34.2
##
## $d
## [1] 63.8
##
## $e
## [1] 42
Al igual que con la función apply()
, podemos pasar argumentos adicionales a la función lapply()
agregándolos después de la función. Esto es útil, por ejemplo, si nuestra list
contiene estos valores faltantes NA
y queremos ignorarlos para calcular los promedios (con el argumento na.rm = TRUE
).
<- list(
myList a = sample(c(1:5, NA), size = 10, replace = TRUE),
b = sample(c(1:5, NA), size = 10, replace = TRUE),
c = sample(c(1:5, NA), size = 10, replace = TRUE),
d = sample(c(1:5, NA), size = 10, replace = TRUE),
e = sample(c(1:5, NA), size = 10, replace = TRUE)
)print(myList)
## $a
## [1] 1 5 5 5 NA 2 NA 2 5 3
##
## $b
## [1] 2 1 5 3 NA 2 4 3 1 1
##
## $c
## [1] 5 1 4 1 NA 3 1 3 3 4
##
## $d
## [1] 5 2 NA 4 NA 5 NA 1 3 5
##
## $e
## [1] 4 3 1 5 3 2 NA NA 1 4
lapply(myList, FUN = mean)
## $a
## [1] NA
##
## $b
## [1] NA
##
## $c
## [1] NA
##
## $d
## [1] NA
##
## $e
## [1] NA
lapply(myList, FUN = mean, na.rm = TRUE)
## $a
## [1] 3.5
##
## $b
## [1] 2.444444
##
## $c
## [1] 2.777778
##
## $d
## [1] 3.571429
##
## $e
## [1] 2.875
Para mayor legibilidad o si se debemos realizar varias operaciones dentro del argumento FUN
, podemos usar el siguiente script:
lapply(myList, FUN = function(i){
mean(i, na.rm = TRUE)
})
## $a
## [1] 3.5
##
## $b
## [1] 2.444444
##
## $c
## [1] 2.777778
##
## $d
## [1] 3.571429
##
## $e
## [1] 2.875
Por ejemplo, si queremos obtener i^2
si el promedio es mayor que 3, y i^3
de lo contrario:
lapply(myList, FUN = function(i){
<- mean(i, na.rm = TRUE)
m if(m > 3){
return(i^2)
else{
}return(i^3)
} })
## $a
## [1] 1 25 25 25 NA 4 NA 4 25 9
##
## $b
## [1] 8 1 125 27 NA 8 64 27 1 1
##
## $c
## [1] 125 1 64 1 NA 27 1 27 27 64
##
## $d
## [1] 25 4 NA 16 NA 25 NA 1 9 25
##
## $e
## [1] 64 27 1 125 27 8 NA NA 1 64
9.7.3 sapply
La función sapply()
es una versión modificada de la función lapply()
que realiza la misma operación pero devuelve el resultado en un formato simplificado siempre que sea posible.
lapply(myList, FUN = function(i){
mean(i, na.rm = TRUE)
})
## $a
## [1] 3.5
##
## $b
## [1] 2.444444
##
## $c
## [1] 2.777778
##
## $d
## [1] 3.571429
##
## $e
## [1] 2.875
sapply(myList, FUN = function(i){
mean(i, na.rm = TRUE)
})
## a b c d e
## 3.500000 2.444444 2.777778 3.571429 2.875000
La función sapply()
es interesante para recuperar, por ejemplo, el elemento “n” de cada elemento de una list
. La función que se llama para hacer esto es '[['
.
sapply(myList, FUN = '[[', 2)
## a b c d e
## 5 1 1 2 3
9.7.4 tapply
La función tapply()
permite aplicar una función tomando como elemento para iterar una variable existente. Imaginamos información sobre especies representadas por letras mayúsculas (por ejemplo, A, B, C) y valores de mediciones biologicas en diferentes ubicaciones.
<- sample(LETTERS[1:10], size = 1000, replace = TRUE)
species <- rnorm(n = 1000, mean = 10, sd = 0.5)
perf1 <- rlnorm(n = 1000, meanlog = 10, sdlog = 0.5)
perf2 <- rgamma(n = 1000, shape = 10, rate = 0.5)
perf3 <- data.frame(species, perf1, perf2, perf3)
dfSpecies print(head(dfSpecies, n = 10))
## species perf1 perf2 perf3
## 1 E 9.886035 25322.41 32.96742
## 2 D 9.610285 36512.66 25.90880
## 3 I 9.799424 11766.75 18.75991
## 4 F 9.891675 27291.72 18.64142
## 5 F 9.980331 17935.19 19.80527
## 6 H 9.211741 32307.61 24.09375
## 7 C 10.034522 13761.65 21.88348
## 8 E 10.705197 35397.58 24.54154
## 9 D 10.715855 23887.16 23.12923
## 10 G 9.950672 42734.65 13.00897
Podemos obtener fácilmente un resumen de las mediciones para cada especie con la función tapply()
y la función summary()
.
tapply(dfSpecies$perf1, INDEX = dfSpecies$species, FUN = summary)
## $A
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 9.137 9.699 10.030 10.052 10.363 11.091
##
## $B
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 8.857 9.683 9.948 9.951 10.229 11.076
##
## $C
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 8.977 9.608 10.010 9.985 10.329 11.289
##
## $D
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 8.882 9.775 9.989 10.038 10.338 11.224
##
## $E
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 9.008 9.672 9.898 9.974 10.275 11.310
##
## $F
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 8.799 9.593 9.980 9.946 10.214 11.639
##
## $G
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 8.887 9.536 9.848 9.881 10.196 11.292
##
## $H
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 8.877 9.659 9.957 9.931 10.202 10.941
##
## $I
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 9.06 9.83 10.02 10.05 10.34 11.16
##
## $J
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 8.747 9.688 10.030 10.012 10.403 11.203
También podemos obtener el valor promedio de cada mediciones combinando una función sapply()
con la función tapply()
y usando la función mean()
.
sapply(2:4, FUN = function(i){
tapply(dfSpecies[,i], INDEX = dfSpecies$species, FUN = mean)
})
## [,1] [,2] [,3]
## A 10.051571 24122.66 20.00674
## B 9.951009 25419.83 20.31136
## C 9.985461 25122.29 20.38010
## D 10.037936 25878.14 20.39121
## E 9.973991 23247.50 20.02727
## F 9.945782 24336.39 18.27318
## G 9.881427 24325.28 19.26992
## H 9.930504 24372.00 19.96906
## I 10.045332 26270.76 19.52240
## J 10.011758 23560.11 20.55293
9.7.5 mapply
La función mapply()
es una versión de la función sapply()
que usa múltiples argumentos. Por ejemplo, si tenemos una lista de dos elementos 1:5
y 5:1
y queremos agregar 10
al primer elemento y 100
al segundo elemento:
mapply(FUN = function(i, j){i+j}, i = list(1:5, 5:1), j = c(10, 100))
## [,1] [,2]
## [1,] 11 105
## [2,] 12 104
## [3,] 13 103
## [4,] 14 102
## [5,] 15 101
9.8 Conclusión
Felicitaciones, hemos llegado al final de este capítulo sobre algoritmos. Recordemos este mensaje clave: cuando una operación debe realizarse más de dos veces en un script y repetir el código que ya se ha escrito, es un signo que nos debe llevar a utilizar un bucle. Sin embargo, siempre que sea posible, se recomienda no usar los bucles tradicionales for()
, while()
, y repeat()
, sino preferir operaciones sobre vectores o bucles de la familia apply
. Esto puede ser difícil de integrar al principio, pero veremos que nuestros scripts serán más fáciles de mantener y leer, y mucho más eficientes si seguimos estos hábitos.