En este nuevo post sobre la familia de funciones apply, es momento de mostrar algunos ejemplos de uso sobre tres nuevas funciones: lapply()
, sapply()
y vapply()
. Las tres funciones están pensadas para trabajar con objetos de clase list
, sin embargo, otras variantes pueden trabajarse con vectores.
Supongamos que tenemos varias bases de datos sobre las cuales necesitamos obtener la suma de las columnas. Vamos primero a generar una sencilla función que nos devuelva una base de datos de números aleatorios del intervalo \([1,100]\) y que además contenga valores faltantes, con un determinado número de filas y columnas.
datos <- function(filas, columnas){
valores <- sample(x = c(NA,1:100),
size = filas*columnas,
replace = TRUE)
as.data.frame(matrix(data = valores,
nrow = filas,
ncol = columnas))
}
Así, por ejemplo, podemos generar una base de datos con 50 filas y 5 columnas ejecutando el siguiente comando:
set.seed(20191208)
df1 <- datos(50, 5)
Generemos otras dos bases de datos de dimensiones \(100\times5\) y \(150\times5\).
set.seed(20191208)
df2 <- datos(100, 5)
df3 <- datos(150, 5)
Como vimos en este post, podríamos utilizar la función apply()
para obtener la suma de las columnas.
apply(df1,2,sum)
## V1 V2 V3 V4 V5
## 2546 2602 2699 1993 2482
apply(df2,2,sum)
## V1 V2 V3 V4 V5
## 5148 4692 NA 5024 5177
apply(df3,2,sum)
## V1 V2 V3 V4 V5
## NA 7554 NA NA NA
O bien, algo más sencillo sería utilizar la función colSums()
.
colSums(df1)
## V1 V2 V3 V4 V5
## 2546 2602 2699 1993 2482
colSums(df2)
## V1 V2 V3 V4 V5
## 5148 4692 NA 5024 5177
colSums(df3)
## V1 V2 V3 V4 V5
## NA 7554 NA NA NA
Sin embargo, podríamos tener una gran cantidad de bases datos, con lo cual los procedimientos anteriores no son viables, pues escribir tantas líneas de código es bastante aburrido…
Es ahí donde entran en juego las listas. Las tres bases de datos que generamos podemos almacenarlas en una sola lista de la siguiente manera.
lista <- list(df1, df2, df3)
Al tener una lista, cada base de datos se convierte en un elemento de esa lista. Por ejemplo, el elemento 1 de esa lista es el conjunto df1, mientras que los conjuntos df2 y df3 son los elementos 2 y tres respectivamente. Una forma de llamar a los elementos dentro de una lista es mediante [[]]
, de la siguiente manera llamamos al conjunto de datos df2:
lista[[2]]
## V1 V2 V3 V4 V5
## 1 35 90 35 84 79
## 2 51 36 4 100 5
## 3 76 47 65 90 36
## 4 17 22 97 72 1
## 5 95 16 8 86 65
## 6 73 31 47 52 87
## 7 37 23 64 85 13
## 8 81 62 33 82 27
## 9 59 63 79 40 45
## 10 38 25 34 9 24
## 11 92 82 53 75 80
## 12 94 51 50 35 87
## 13 44 54 10 49 71
## 14 96 23 54 62 99
## 15 73 87 8 64 16
## 16 58 94 67 41 100
## 17 92 93 78 34 59
## 18 30 64 53 49 80
## 19 14 21 94 59 86
## 20 14 9 51 42 19
## 21 55 99 12 70 54
## 22 30 34 93 76 68
## 23 19 40 85 29 51
## 24 46 83 98 41 88
## 25 48 34 100 49 37
## 26 29 34 72 11 62
## 27 39 78 68 41 20
## 28 68 62 51 68 76
## 29 92 69 37 15 95
## 30 15 25 63 72 24
## 31 72 90 4 63 35
## 32 7 34 91 95 5
## 33 62 43 1 66 57
## 34 32 69 29 87 30
## 35 96 67 21 32 13
## 36 13 71 26 96 91
## 37 58 72 30 66 5
## 38 80 45 84 45 86
## 39 84 89 83 86 2
## 40 44 78 13 6 23
## 41 24 70 63 93 1
## 42 22 65 32 85 40
## 43 32 54 79 34 88
## 44 10 12 37 31 21
## 45 48 28 9 61 75
## 46 19 95 22 71 41
## 47 60 48 4 26 30
## 48 79 34 59 80 65
## 49 25 77 61 5 95
## 50 69 7 71 75 60
## 51 33 53 100 43 19
## 52 16 82 9 98 52
## 53 53 60 35 3 77
## 54 9 31 33 61 72
## 55 42 17 64 40 80
## 56 71 87 95 31 72
## 57 79 20 11 6 67
## 58 67 55 57 55 31
## 59 82 82 5 87 93
## 60 84 34 20 22 9
## 61 38 22 92 45 24
## 62 48 75 60 5 74
## 63 44 78 78 29 66
## 64 10 70 14 37 16
## 65 12 12 93 66 19
## 66 88 29 63 94 30
## 67 77 44 34 59 96
## 68 5 46 81 11 72
## 69 79 40 73 26 99
## 70 96 24 46 94 7
## 71 46 64 12 2 30
## 72 3 38 50 89 85
## 73 86 17 14 21 37
## 74 57 37 NA 15 36
## 75 23 12 48 20 46
## 76 41 7 82 69 46
## 77 59 5 4 93 98
## 78 47 52 23 59 94
## 79 19 47 58 12 47
## 80 44 34 15 15 47
## 81 15 38 57 84 67
## 82 98 8 83 10 64
## 83 50 38 48 95 40
## 84 17 18 24 1 59
## 85 73 12 2 30 48
## 86 41 79 38 48 68
## 87 12 84 77 25 79
## 88 66 52 5 85 41
## 89 80 39 60 29 29
## 90 100 43 15 16 3
## 91 9 37 75 52 94
## 92 94 22 19 48 52
## 93 53 9 100 83 52
## 94 37 39 78 31 65
## 95 96 26 66 87 22
## 96 64 25 29 7 55
## 97 62 50 91 21 45
## 98 18 44 94 15 18
## 99 61 11 48 48 50
## 100 98 45 27 17 68
Ahora, si quisiéramos aplicar la función colSums()
a cada conjunto de datos, podemos utilizar la función lapply()
:
lapply(lista, colSums)
## [[1]]
## V1 V2 V3 V4 V5
## 2546 2602 2699 1993 2482
##
## [[2]]
## V1 V2 V3 V4 V5
## 5148 4692 NA 5024 5177
##
## [[3]]
## V1 V2 V3 V4 V5
## NA 7554 NA NA NA
El resultado es una lista con la suma de las columnas de cada base de datos. Si quisiéramos realizar las sumas pero sin contar los valores faltantes, solo hay que incorporar el argumento respectivo de la función colSums()
.
lapply(lista, colSums, na.rm=TRUE)
## [[1]]
## V1 V2 V3 V4 V5
## 2546 2602 2699 1993 2482
##
## [[2]]
## V1 V2 V3 V4 V5
## 5148 4692 4887 5024 5177
##
## [[3]]
## V1 V2 V3 V4 V5
## 7780 7554 7470 7826 7033
Como puede notarse, la función lapply()
trabaja básicamente con tres argumentos: una lista (en este caso el objeto lista), una función que deseamos aplicarle a cada elemento de esa lista (en este caso colSums()
), y de ser necesario, los argumentos que solicitados por la función in dicada (en este caso na.rm=TRUE
de la función colSums()
).
El resultado anterior devuelve los cálculos en un objeto de clase list
, sin embargo, en muchas ocasiones es deseable obtener un formato algo más ordenado. la función sapply()
trabaja de forma idéntica a lapply()
, con la salvedad de que si el resultado de cada elemento de la lista posee la misma longitud, la función sapply()
agrupa el resultado. Al utilizar la sunción lapply()
obtenemos una lista de tres elementos, donde cada elemento es un vector de longitud cinco, es decir, todos tienen la misma longitud, por lo que la función sapply()
devolvería lo siguiente:
sapply(lista, colSums)
## [,1] [,2] [,3]
## V1 2546 5148 NA
## V2 2602 4692 7554
## V3 2699 NA NA
## V4 1993 5024 NA
## V5 2482 5177 NA
sapply(lista, colSums, na.rm=TRUE)
## [,1] [,2] [,3]
## V1 2546 5148 7780
## V2 2602 4692 7554
## V3 2699 4887 7470
## V4 1993 5024 7826
## V5 2482 5177 7033
Aunque la función sapply()
parezca más útil que lapply()
, tiene un pequeño inconveniente, y es que siempre va a funcionar… ¿Cómo puede esto ser un inconveniente? Incorporemos un nuevo conjunto de datos, pero esta vez con seis columnas en lugar de cinco como los anteriores.
df4 <- datos(150, 6)
lista2 <- list(df1, df2, df3, df4)
Si utilizamos nuevamente la función lapply()
, obtendríamos de nuevo las sumas por columnas en forma de una lista:
lapply(lista2, colSums, na.rm=TRUE)
## [[1]]
## V1 V2 V3 V4 V5
## 2546 2602 2699 1993 2482
##
## [[2]]
## V1 V2 V3 V4 V5
## 5148 4692 4887 5024 5177
##
## [[3]]
## V1 V2 V3 V4 V5
## 7780 7554 7470 7826 7033
##
## [[4]]
## V1 V2 V3 V4 V5 V6
## 8205 6712 6794 8049 7562 7335
Mientras que si aplicamos la función sapply()
:
sapply(lista2, colSums, na.rm=TRUE)
## [[1]]
## V1 V2 V3 V4 V5
## 2546 2602 2699 1993 2482
##
## [[2]]
## V1 V2 V3 V4 V5
## 5148 4692 4887 5024 5177
##
## [[3]]
## V1 V2 V3 V4 V5
## 7780 7554 7470 7826 7033
##
## [[4]]
## V1 V2 V3 V4 V5 V6
## 8205 6712 6794 8049 7562 7335
Ahora obtenemos una lista, es decir, el mismo resultado que con sapply()
. Esto sucede porque ahora no todos los elementos tienen la misma longitud, antes se tenían tres vectores de longitud cinco, mientras que ahora también se tiene un vector de longitud seis. Esto parece ser algo sin importancia, pues igual se está obteniendo un resultado, sin embargo dependiendo del contexto ese resultado no siempre será válido.
Supongamos que basados en el análisis que estamos realizando, sabemos que la suma de las columnas solamente puede devolver un vector de longitud cinco, y que si el resultado es otra cosa puede deberse a un error en alguna de las bases de datos, como columnas adicionales. La función vapply()
nos permite, al igual que sapply()
, aplicar una función a los elementos de una lista, pero especificando que el resultado esperado, en este caso, es un vector numérico de longitud cinco. Hagamos primero una comparación entre sapply()
y vapply()
con el objeto lista
, que tiene tres bases de datos con cinco columnas:
sapply(lista, colSums, na.rm=TRUE)
## [,1] [,2] [,3]
## V1 2546 5148 7780
## V2 2602 4692 7554
## V3 2699 4887 7470
## V4 1993 5024 7826
## V5 2482 5177 7033
vapply(lista, colSums, numeric(5), na.rm=TRUE)
## [,1] [,2] [,3]
## V1 2546 5148 7780
## V2 2602 4692 7554
## V3 2699 4887 7470
## V4 1993 5024 7826
## V5 2482 5177 7033
Los resultados son idénticos. Pero ahora repitamos el ejemplo anterior pero para el objeto lista2
, que contiene un conjunto de datos con seis columnas.
sapply(lista2, colSums, na.rm=TRUE)
## [[1]]
## V1 V2 V3 V4 V5
## 2546 2602 2699 1993 2482
##
## [[2]]
## V1 V2 V3 V4 V5
## 5148 4692 4887 5024 5177
##
## [[3]]
## V1 V2 V3 V4 V5
## 7780 7554 7470 7826 7033
##
## [[4]]
## V1 V2 V3 V4 V5 V6
## 8205 6712 6794 8049 7562 7335
La función sapply()
realiza el cálculo, pero bajo el supuesto de que el resultado esperado son vectores de longitud cinco, este resultado es incorrecto. La función vapply()
nos ayuda a controlar esto:
vapply(lista2, colSums, numeric(5), na.rm=TRUE)
## Error in vapply(lista2, colSums, numeric(5), na.rm = TRUE): Los valores deben ser de longitud 5,
## pero el resultado FUN(X [[4]]) es la longitud 6
Al intentar ejecutar el código obtenemos un error, pues evaluar la función en el elemento número cuatro de la lista hace que se obtenga un vector de longitud seis, y no de cinco como esperábamos. Utilizar vapply()
es, generalmente, más recomendable que sapply()
pues permite tener un mayor control sobre los resultados esperados.