Familia de funciones apply - Parte 3

Las funciones lapply(), sapply() y vapply()

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.

Relacionado

comments powered by Disqus
ORCID iD iconhttps://orcid.org/0000-0001-6733-4759