Los fundamentos de programación son esenciales a la hora de ahorrar un tiempo muy valioso en nuestros análisis. Como ya avisábamos en un principio, si nuestro código es muy repetitivo la programación y la automatización que hemos visto es un elemento vital para no extenuarse con esta herramienta.
Sin embargo, lo visto hasta ahora no es la única manera. Como avisábamos, la programación funcional, la que utiliza funciones que sustituyen a los bucles puede ser sumamente efectiva por dos razones:
La primera es el ahorro de código. Si en vez de tener que escribir en un bucle todas las operaciones necesarias, una función realiza todo eso, en una sola línea habremos resuelto la operación.
La segunda es la conocida como vectorización. R es un lenguaje que maneja los vectores de manera más eficiente que cualquier otro formato, y eso no solo se aplica a los datos, sino también a la sintaxis. Si vectorizamos nuestras operaciones los tiempos de computación se reducirán, lo cual es importante si nos enfrentamos a procesos exigentes a nivel de procesamiento. Vectorizar es simplemente expresar nuestras operaciones en forma de vector o similar a un vector, como por ejemplo la lista de parámetros, de manera que con una expresión simplificada podemos ahorrar tiempo. Muchas veces además podremos acudir a funciones que están construidas con código que interactúa directamente con la memoria del ordenador como C++ o Fortran, y son computacionalmente algo más eficientes que si R tiene que interpretar las instrucciones de una manera más indirecta.
No siempre será posible utilizar funciones en lugar de bucles, dependerá de cada tarea que necesitemos. Pero si utilizamos nuestras propias funciones, las podemos diseñar para que que se ejecuten de manera iterativa mediante programación funcional en vez de en un bucle.
La clave de la programación funcional es la familia de funciones apply. Se trata de un conjunto de funciones básicas que ejecutarán una función determinada sobre una secuencia (como en for), una matriz, una lista, etc. con los parámetros que le digamos. La función principal es apply, pero vamos a ver también sapply y lapply. Otras como tapply, mapply y vapply.
La estructura de funcionamiento es similar pero varía ligeramente porque las funciones no se usan para los mismos tipos de datos. COmo función principal, apply se usa para matrices o dataframes, y lapply/sapply para vectores así que hay ciertas diferencias. El patrón en cualquier caso es: funcion_apply(vector_o_matriz,funcion_a_aplicar_iterativamente,parametros_de_la_funcion_a_aplicar)
.
La función apply()
tiene tres parámetors como veíamos arriba: el objeto sobre el que queremos aplicar una función (para apply, matriz o dataframe), el parámetro MARGIN, y el parámetro FUN. FUN especifica, sin comillas, el nombre de la función que queremos aplicar. Y MARGIN especifica si queremos aplicarlo por filas (=1) o columnas (=2).
apply(mimatriz,MARGIN=2,FUN)
Vamos a verlo con una matriz con números de 1 a 100 creada con la función matrix()
, con 20 filas y 5 columnas:
matriz<-matrix(1:100,nrow=20,ncol=5)
matriz
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 21 41 61 81
## [2,] 2 22 42 62 82
## [3,] 3 23 43 63 83
## [4,] 4 24 44 64 84
## [5,] 5 25 45 65 85
## [6,] 6 26 46 66 86
## [7,] 7 27 47 67 87
## [8,] 8 28 48 68 88
## [9,] 9 29 49 69 89
## [10,] 10 30 50 70 90
## [11,] 11 31 51 71 91
## [12,] 12 32 52 72 92
## [13,] 13 33 53 73 93
## [14,] 14 34 54 74 94
## [15,] 15 35 55 75 95
## [16,] 16 36 56 76 96
## [17,] 17 37 57 77 97
## [18,] 18 38 58 78 98
## [19,] 19 39 59 79 99
## [20,] 20 40 60 80 100
Ahora le vamos a aplicar apply por columnas con la raíz cuadrada:
apply(matriz,MARGIN=1,FUN=sqrt)
## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
## [1,] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427
## [2,] 4.582576 4.690416 4.795832 4.898979 5.000000 5.099020 5.196152 5.291503
## [3,] 6.403124 6.480741 6.557439 6.633250 6.708204 6.782330 6.855655 6.928203
## [4,] 7.810250 7.874008 7.937254 8.000000 8.062258 8.124038 8.185353 8.246211
## [5,] 9.000000 9.055385 9.110434 9.165151 9.219544 9.273618 9.327379 9.380832
## [,9] [,10] [,11] [,12] [,13] [,14] [,15] [,16]
## [1,] 3.000000 3.162278 3.316625 3.464102 3.605551 3.741657 3.872983 4.000000
## [2,] 5.385165 5.477226 5.567764 5.656854 5.744563 5.830952 5.916080 6.000000
## [3,] 7.000000 7.071068 7.141428 7.211103 7.280110 7.348469 7.416198 7.483315
## [4,] 8.306624 8.366600 8.426150 8.485281 8.544004 8.602325 8.660254 8.717798
## [5,] 9.433981 9.486833 9.539392 9.591663 9.643651 9.695360 9.746794 9.797959
## [,17] [,18] [,19] [,20]
## [1,] 4.123106 4.242641 4.358899 4.472136
## [2,] 6.082763 6.164414 6.244998 6.324555
## [3,] 7.549834 7.615773 7.681146 7.745967
## [4,] 8.774964 8.831761 8.888194 8.944272
## [5,] 9.848858 9.899495 9.949874 10.000000
Las funciones sapply()
y lapply()
funcionan con vectores especificando los tres argumentos de arriba (el vector de donde tomar los elementos, la función a aplicarles, y los argumentos extra. La función que especifico cogerá como su primer argumento cada uno de los elementos del vector, aplicándoles la función y los argumentos extra, uno a uno.
La diferencia entre ellas cómo almacenan y muestran los resultados
El ejemplo que utilizaremos es un truco para cargar muchos paquetes a la vez sin recurrir individualmente a library()
y cambiándolo por la función require()
.
Defino una vector con los nombres (por tanto entre comillas de los paquetes que quiero cargar. Añadiré como argumento extra, el argumento de la función require()
, character.only=TRUE
.
paquetes<-c("ggplot","raster","sp", "sudoku")
lapply(paquetes,require,character.only=TRUE)
## Loading required package: ggplot
## Warning in library(package, lib.loc = lib.loc, character.only = TRUE,
## logical.return = TRUE, : there is no package called 'ggplot'
## Loading required package: raster
## Loading required package: sp
## Loading required package: sudoku
## Warning in library(package, lib.loc = lib.loc, character.only = TRUE,
## logical.return = TRUE, : there is no package called 'sudoku'
## [[1]]
## [1] FALSE
##
## [[2]]
## [1] TRUE
##
## [[3]]
## [1] TRUE
##
## [[4]]
## [1] FALSE
sapply(paquetes,require,character.only=TRUE)
## Loading required package: ggplot
## Warning in library(package, lib.loc = lib.loc, character.only = TRUE,
## logical.return = TRUE, : there is no package called 'ggplot'
## Loading required package: sudoku
## Warning in library(package, lib.loc = lib.loc, character.only = TRUE,
## logical.return = TRUE, : there is no package called 'sudoku'
## ggplot raster sp sudoku
## FALSE TRUE TRUE FALSE
El resultado que obtengo es el siguiente: lapply me devuelve en una lista qué paquetes ha podido cargar y cuáles no. sapply igual, pero de una manera un poco más limpia, lo mismo. El paquete ggplot no existe, pues se llama en realidad ggplot2. El paquete sudoku no lo tengo instalado.
Finalmente, vamos a utilizar sapply con nuesta función creada en el tema anterior ’mauna_analysis()`:
años<-c(1987,2001,1981,2014)
sapply(años,mauna_analysis)