En el presente documento se da una breve introducción al lenguaje R. Exploraremos desde las funciones más básicas del lenguaje (como leer tablas de datos, hacer aritmética, algunas operaciones algebraicas) hasta cómo utilizar funciones de paquetes especializados para el análisis espacial (i.e. raster
, rgdal
, dismo
). También se hará énfansis en cómo programar funciones propias o funciones definidas por el usuario (helper functions) las cuales tienen el objetivo de facilitarnos tareas complejas. Es importante notar que operaciones básicas como la indexación suelen ser vitales para poder realizar operaciones mucho más complicadas y complejas dentro de los loops y estructuras de control.
R es un lenguaje de programación de código abierto el cual está basado en el lenguaje S. En sus inicios el lenguaje realizaba solo operaciones estadísticas y gráficas, sin embargo a través de los años se ha convertido en un lenguaje polifacético en donde podemos encontrar desde paquetes para el análisis de textos (text mining), web scraping, análisis espaciales (i.e. spatstat
) hasta para la programación de aplicaciones web (i.e shiny
). Lo anterior se debe a que varios de sus usuarios se han convertido en desarrolladores activos de la comunidad. Acutualmente existen casi 8000 paquetes (Fig. 1), el crecimiento de estos ha sido exponencial (Fig. 2).
Figura 1. Número de paquetes en CRAN
Figura 2. Serie de tiempo del número de paquetes en CRAN
R se puede descargar de la siguiente página web http://www.r-project.org/.
En el panel izquierdo encontrarás la opción de download donde elegirás un mirror para descargarlo, los links para México son los siguientes:
Elige el link de descarga que te parezca más adecuado en función del área en que te encuentres. Una vez elegido el mirror, selecciona la versión de R de acuerdo a tu sistema operativo (Linux, Mac o Windows).
La forma más fácil de instalar R es por medio de los binarios (o ejecutables), sin embargo si deseas instalarlo desde teminal de Linux o Mac aquí puedes encontrar las instrucciones:
Antes de empezar con el tutorial voy a fijar una semilla aleatoria para que los ejemplos de este documento sean reproducibles (i.e., para que todos en el grupo lleguen a los mismos resultados).
set.seed(111)
Una vez que han instalado R en sus computadoras ahora estamos listos para comenzar a aprender programación en este ambiente maravilloso.
La primera cosa que hay que explorar es la sintaxis del lenguaje; en esta sección daremos un panorama general de ella. La primera cosa que hay que tener en cuenta sobre la sintaxis de R es que a diferencia de otros lenguajes como SQL
, BASIC
, Pascal
, R
es sensible a mayúsculas y minúsculas por lo que un objeto llamado libro es diferente a Libro. Veamos el ejemplo:
libro <- "En las montañas de la locura"
# Existe en la memoria de R el objeto libro?
exists("libro")
## [1] TRUE
# y que hay sobre el objeto Libro?
exists("Libro")
## [1] FALSE
A pesar de que no se comentó anteriormente (se dio por hecho), en R la forma de declarar variables es mediante el comando de asignación (<-). Aunque también es posible declarar las variables con el signo (=) se recomienda usar <-. Veremos más sobre variables en las siguientes secciones
# Asigna a uno el valor numérico 1 usando el comando "<-"
uno <- 1
# Alternativamente
1 -> uno
# Asigna a dos el valor numérico 2 usando el comando "="
dos = 2
# Asignar el valor 3 a la variable 3 usando el comando assing
assign("tres", 3)
print(uno)
## [1] 1
print(dos)
## [1] 2
print(tres)
## [1] 3
Otra característica de la sintaxis es que en R solo se permite nombrar a las variables comenzando con letras (mayúsculas o minúsculas) ó “.”* y nunca con números.
Olor <- "Dulce"
olor <- "Agradable"
# Esto produce un error
# 0lor <- "Fétido" # la variable comienza con el digito cero
print(Olor)
## [1] "Dulce"
print(olor)
## [1] "Agradable"
Las variables pueden contener números en cualquier otra posición (nunca al comienzo).
ol0r <- "fresco"
o1or <- "fresco"
print(ol0r)
## [1] "fresco"
print(o1or)
## [1] "fresco"
Ahora probemos con el “.”*
.ayuda <- "SOS"
print(.ayuda)
## [1] "SOS"
La consola de R puede ser usada como calculadora aritmética, vemos algunas operaciones básicas:
Una suma
5 + 89
## [1] 94
Una resta
18 - 167
## [1] -149
Una multiplicación
2 * 98
## [1] 196
División
6/5
## [1] 1.2
Potencia
9 ^ 9
## [1] 387420489
El residuo de la división
9 %% 7
## [1] 2
La parte entera
9 %/% 7
## [1] 1
Podemos verificar que 9 es la suma de su residuo más 7 veces la parte entera de la fracción
# Residuo + 7 * la parte entera de la fracción
(9 %% 7) + 7 * (9 %/% 7)
## [1] 9
Los paréntesis puede ser usados para especificar el orden de las operaciones
(1 + 1/100)^100
## [1] 2.704814
R contiene algunas funciones matemáticas ya incluidas como \(\sin(x)\), \(\cos(x)\), \(\tan(x)\), (en radianes), \(\exp(x)\), \(\log(x)\) y \(\sqrt{x}\).
exp(1)
## [1] 2.718282
sin(1)
## [1] 0.841471
cos(1)
## [1] 0.5403023
log(1)
## [1] 0
sqrt(1)
## [1] 1
También tiene en memoria algunas constantes como \(\pi\)
pi
## [1] 3.141593
Podemos especificar la precisión de las salidas con el comando options
options(digits = 16)
pi
## [1] 3.141592653589793
Redondear cantidades
# Redondea dos digitos de precisión
round(10/3.4, 2)
## [1] 2.94
# Entero mayor más cercano
ceiling(1/3)
## [1] 1
R tiene un ambiente de trabajo conocido como ambiente global (Global environment) en donde se guardan los resultados de los cálculos. Los resultados se guardan en forma de objetos o variables; una variable es como un folder etiquetado donde guardamos documentos, éstos puede ser de diversa naturaleza y siempre tenemos la opción de cambiar el contenido de ellos, sin embargo, el folder se llamará de la misma manera.
Las variables globales se encuentran en el Global environment y pueden ser llamadas en cualquier momento durante la sesión; así, si quisiéramos almacenar una cantidad, digamos la tasa de interés anual a la que pagaremos nuestro coche, podemos guardarla en la variable i.e. tasa_anual y utilizarla cuando la necesitemos.
Imaginemos que vamos a comprar un Audi RS5 4.2 FSI 450 hp y que queremos pagarlo a 48 meses (4 años). La tasa anual que maneja audi es aproximadamente de 12.9%; olvidándonos de los pagos de comisión por apertura etc. hagamos el cálculo de cuánto pagaremos por nuestro Audi (Fig. 5).
Figura 5. Audi RS5 4.2 FSI 450 hp
# Auto Audi RS5 4.2 FSI 450 hp precio de lista
audi_rs5 <- 1484900.00
# Tasa anual de 12.9%
tasa_anual <- 0.129
# Cuanto pagaremos de interes en un año
(interes_anual <- audi_rs5*tasa_anual)
## [1] 191552.1
# Y en los cuatro años
(interes_4 <- audi_rs5*(tasa_anual*4))
## [1] 766208.4
# Total a pagar
(total_audi_rs5 <- audi_rs5 + interes_4)
## [1] 2251108.4
Comencemos con algunas definiciones necesarias:
El conjunto de todos los vectores de dimensión \(n\), se representa como \(\mathbb{R}^n\). Así, un vector \({\textbf v}\) perteneciente a un espacio \(\mathbb{R}^n\) se representa como:
\({\textbf v} = (a_1, a_2, a_3, \dots, a_n)\) donde \({\textbf v} \in \mathbb{R}^n\)
En matemáticas existen dos tipos de vectores, el vector renglón (el de la ecuación de arriba) y el vector columna.
\[\textbf v= \left( \begin{array}{c} a_1\\ a_2\\ \vdots\\ a_n\\ \end{array} \right)\]
Para saber más sobre las operaciones matemáticas de vectores y matrices en R da clic aquí.
Nótese que se puede localizar a cada componente del vector por su posición (ver ejemplo).
\[\textbf f = (5,2,4,3)\]
De modo que la componente 3 del vector \(\textbf f\) es el número \(4\). En R, a lo anterior se le denomina indexación y es una de las herramientas más poderosas que podemos aprender; si logramos dominar este arte, la programación y automatización de tareas muy complejas será mucho más sencilla.
El desconocimiento de la forma en cómo se indexan los vectores o matrices hace que el código fuente de los programas o scripts sea obscuro. En los siguientes ejemplos veremos que esta arte es mucho más clara de lo que parece.
Primero debemos notar que en R la forma más básica de construir vectores es utilizando la función concatenar “c”. Este comando concatena a cada uno de los elementos que formarán a nuestro vector.
# Generemos el vector f (el del ejemplo anterior)
f <- c(5,2,4,3)
Comprobemos si efectivamente el elemento 3 del vector \({\textbf f}\) es el número \(4\)
f[3]
## [1] 4
Efectivamente es el número 4 y que hay del primer elemento?
f[1]
## [1] 5
Si quisiéramos extraer más de un elemento (un subconjunto \(\textbf f_1\) de dimensión \(m\) con \(m \le n\)) del vector \({\textbf f}\)?
Ahora se muestra como formar un subconjunto \(\textbf f_1\) constituido por los elementos 1, 3 y 4 del vector \({\textbf f}\)
(f1 <- f[c(1,3,4)])
## [1] 5 4 3
Nota: En algunos lenguajes como python la indexación comienza con el 0.
R tiene varias formas de generar secuencias, por ejemplo, el comando 1:n genera la secuencia \(1,2,3,\dots,n\). La secuencia anterior es una de las más utilizadas para generar subconjuntos de vectores de dimensión \(n\) y cuyos elementos son números enteros. Otro comando que encontraremos con frecuencia es el comando “seq”, este comando genera secuencias de números en el intervalo \([a,b]\) con particiones de tamaño \(\delta\).
Generemos una secuencia de números del 0 al 10 de 2 en 2, es decir \(\delta=2\)
seq(from =0,to = 10,by = 2)
## [1] 0 2 4 6 8 10
Ahora una secuencia de 0 a 1 con \(\delta=0.01\)
seq(0,1,by=0.01)
## [1] 0.00000000000000000 0.01000000000000000 0.02000000000000000
## [4] 0.03000000000000000 0.04000000000000000 0.05000000000000000
## [7] 0.06000000000000000 0.07000000000000001 0.08000000000000000
## [10] 0.09000000000000000 0.10000000000000001 0.11000000000000000
## [13] 0.12000000000000000 0.13000000000000000 0.14000000000000001
## [16] 0.14999999999999999 0.16000000000000000 0.17000000000000001
## [19] 0.17999999999999999 0.19000000000000000 0.20000000000000001
## [22] 0.20999999999999999 0.22000000000000000 0.23000000000000001
## [25] 0.23999999999999999 0.25000000000000000 0.26000000000000001
## [28] 0.27000000000000002 0.28000000000000003 0.28999999999999998
## [31] 0.29999999999999999 0.31000000000000000 0.32000000000000001
## [34] 0.33000000000000002 0.34000000000000002 0.35000000000000003
## [37] 0.35999999999999999 0.37000000000000000 0.38000000000000000
## [40] 0.39000000000000001 0.40000000000000002 0.41000000000000003
## [43] 0.41999999999999998 0.42999999999999999 0.44000000000000000
## [46] 0.45000000000000001 0.46000000000000002 0.47000000000000003
## [49] 0.47999999999999998 0.48999999999999999 0.50000000000000000
## [52] 0.51000000000000001 0.52000000000000002 0.53000000000000003
## [55] 0.54000000000000004 0.55000000000000004 0.56000000000000005
## [58] 0.57000000000000006 0.57999999999999996 0.58999999999999997
## [61] 0.59999999999999998 0.60999999999999999 0.62000000000000000
## [64] 0.63000000000000000 0.64000000000000001 0.65000000000000002
## [67] 0.66000000000000003 0.67000000000000004 0.68000000000000005
## [70] 0.69000000000000006 0.70000000000000007 0.70999999999999996
## [73] 0.71999999999999997 0.72999999999999998 0.73999999999999999
## [76] 0.75000000000000000 0.76000000000000001 0.77000000000000002
## [79] 0.78000000000000003 0.79000000000000004 0.80000000000000004
## [82] 0.81000000000000005 0.82000000000000006 0.83000000000000007
## [85] 0.83999999999999997 0.84999999999999998 0.85999999999999999
## [88] 0.87000000000000000 0.88000000000000000 0.89000000000000001
## [91] 0.90000000000000002 0.91000000000000003 0.92000000000000004
## [94] 0.93000000000000005 0.94000000000000006 0.95000000000000007
## [97] 0.95999999999999996 0.96999999999999997 0.97999999999999998
## [100] 0.98999999999999999 1.00000000000000000
Imaginemos que se quiere una secuencia de números definidos en el intervalo \((13,25)\) pero cuya longitud sea de 40 números. Uno de los argumentos del comando seq es length.out y con este se puede especificar la longitud de la secuencia de valores que queremos generar.
# longitud de la secuencia que queremos generar
tamano <- 40
seq(13,25,length.out=tamano)
## [1] 13.00000000000000 13.30769230769231 13.61538461538461
## [4] 13.92307692307692 14.23076923076923 14.53846153846154
## [7] 14.84615384615385 15.15384615384615 15.46153846153846
## [10] 15.76923076923077 16.07692307692308 16.38461538461539
## [13] 16.69230769230769 17.00000000000000 17.30769230769231
## [16] 17.61538461538462 17.92307692307692 18.23076923076923
## [19] 18.53846153846154 18.84615384615385 19.15384615384615
## [22] 19.46153846153846 19.76923076923077 20.07692307692308
## [25] 20.38461538461539 20.69230769230769 21.00000000000000
## [28] 21.30769230769231 21.61538461538462 21.92307692307692
## [31] 22.23076923076923 22.53846153846154 22.84615384615385
## [34] 23.15384615384615 23.46153846153846 23.76923076923077
## [37] 24.07692307692308 24.38461538461539 24.69230769230769
## [40] 25.00000000000000
La función rep
permite generar secuencias repetidas de vectores.
vec <- c("A","B")
rep(vec, 2)
## [1] "A" "B" "A" "B"
Un comando frecuentemente utilizado para extraer una muestra aleatoria de un vector el comando sample. Algunos de sus argumentos son:
En el siguiente ejemplo crearemos un vector de 1000 elementos y tomaremos una muestra aleatoria de 100. Veamos cómo hacerlo en R!
vec <- 1:1000
sample(x = vec,size = 100,replace = FALSE)
## [1] 593 726 370 514 377 417 11 529 429 93 551 584 67 47 155 440 169
## [18] 951 306 603 423 280 335 378 945 314 637 276 766 579 57 495 451 454
## [35] 348 689 113 755 618 774 616 316 609 889 550 351 995 816 598 963 965
## [52] 556 32 959 943 519 544 431 91 759 1 439 163 243 861 217 50 284
## [69] 994 979 817 619 422 934 585 410 248 909 88 724 111 810 121 368 80
## [86] 343 626 671 704 993 466 776 572 526 913 955 899 360 880 743
Supongamos que estamos creando un algoritmo donde en uno de los pasos se genera un vector \(\textbf l\) cuyo tamaño puede variar en función de que se cumpla una condición \(c\); imaginemos que estamos interesados en saber cuál es el número promedio de iteraciones que necesita nuestro algoritmo para cumplir la condición \(c\). Una forma de averiguar lo anterior es guardar en un vector n_iter el número de iteraciones que el algoritmo necesitó en cada una de las simulaciones que relizamos. La pregunta es cómo saber la longitud del vector \(\textbf l\): en R es muy sencillo, el comando para realizarlo es length
# logitudes posibles de nuestro vector l
tamanos <- 1:30
# tamaño de l
tamano_l <- sample(tamanos,size = 1)
l <- seq(1,5,length.out = tamano_l)
# La longitud de l
length(l)
## [1] 18
En R las matrices pueden ser de 2 tipos:
Lo anterior quiere decir que no podemos combinar números con caracteres. Si fuera este el caso, R internamente realiza una operación conocida como casteo cast y convierte a todos los elementos a caracteres (strings)
Las matrices en R se crean con el comando matrix y sólo necesitamos indicar los elementos que la conformarán (data), el número de filas (nrow) y columnas (ncol) que contendrá.
elementos <- seq(1:16)
mat <- matrix(elementos,nrow = 4,ncol = 4)
print(mat)
## [,1] [,2] [,3] [,4]
## [1,] 1 5 9 13
## [2,] 2 6 10 14
## [3,] 3 7 11 15
## [4,] 4 8 12 16
Como se había mencionado las matrices en R sólo pueden ser de dos tipos numéricas o de cadena de caracteres:
# Matriz tipo cadena
letras <- LETTERS[1:9]
mat_carac <- matrix(letras,ncol=3,nrow = 3)
Tratemos de combinar números con letras
letras_b <- LETTERS[1:8]
numero_b <- 1:8
letras_numeros <- c(letras_b,numero_b)
mat_num_car <- matrix(letras_numeros,ncol=4,nrow = 4)
print(mat_num_car)
## [,1] [,2] [,3] [,4]
## [1,] "A" "E" "1" "5"
## [2,] "B" "F" "2" "6"
## [3,] "C" "G" "3" "7"
## [4,] "D" "H" "4" "8"
Sea \(\textbf M\) una matriz de \(6\times 8\) dimensiones. Supongamos que se nos pide extraer el elemento que corresponde a la fila 3 y al renglón 6. La forma de indexar matrices en R sigue la misma lógica que en matemáticas en donde \(\mathbf{M_{fila,columna}}\) en R se indexa como \(\mathbf{M[fila,columna]}\). Veamos el código!
dats <- seq(2,4,length.out = 48)
M <- matrix(dats,nrow = 6,ncol = 8,byrow = TRUE)
# Extraigo el elemento a_{3,6}
print(M[3,6])
## [1] 2.893617021276595
También podemos usar la indexación para mostrar una sola fila de la matriz, una columna completa, los primeros \(m\) elementos de una fila o una columna, las primeras \(k\) columnas y las primeras \(r\) filas de la matriz…
A continuación se muestra como enlistar la segunda fila de la matriz \(\mathbf M\)
print(M[2,])
## [1] 2.340425531914894 2.382978723404255 2.425531914893617 2.468085106382979
## [5] 2.510638297872340 2.553191489361702 2.595744680851064 2.638297872340425
A continuación se muestra como enlistar la tercera columna de la matriz \(\mathbf M\)
print(M[,3])
## [1] 2.085106382978724 2.425531914893617 2.765957446808510 3.106382978723405
## [5] 3.446808510638298 3.787234042553191
Con las operaciones vistas hasta el momento podemos realizar varias combinaciones para extraer elementos de una matriz
. Veamos algunos ejemplos de lo anterior.
Dada la matriz \(\mathbf {M_{5,5}}\) extraer:
d_mat <- 1:25
M <- matrix(d_mat,ncol = 5,nrow = 5, byrow = TRUE)
print(M[1:3,c(1,2)])
## [,1] [,2]
## [1,] 1 2
## [2,] 6 7
## [3,] 11 12
print(M[c(1,2,3,4,5),c(1,5)])
## [,1] [,2]
## [1,] 1 5
## [2,] 6 10
## [3,] 11 15
## [4,] 16 20
## [5,] 21 25
Para saber más sobre matrices y sus operaciones en R da clic aquí.
Al igual que las matrices, un data.frame
es un arreglo bidimensional de reglones y columnas. A diferencia de las matrices este permite tener columnas de tipo numérico y de tipo caracter en él. Cada columna representa una variable (en el sentido estadístico).
data.frames
La función data.frame
permite contruir un data.frame
a partir de vectores; cada vector representa una variable o columna en el data.frame
. A continuación veremos cómo construir un data.frame
cuyas variables son de tipo numérico y de tipo caracter.
Supón que tienes una granja de pollitos y que tu veterinario te ofrece probar 5 diferentes suplementos para engordar a los pequeños :). Debido a que el veterinario es buenísima onda te surte de manera gratuita suplemento suficiente para alimentar a 75 durante 3 meses. Dada tu formación cuantitativa se te ocurre diseñar un experimento donde defines como variable de respuesta al peso y como variable explicativa al suplemento. Como hay un total de 75 pollitos te das cuenta que puedes realizar un experimento balanceado en donde alimentas con el sumplento 1 a 5 pollitos, con el suplemento 2 a otros 5 y así sucesivamente para los 5 tipos de alimento (1 dieta cada 5 pollitos - \(5\times5=25\) -) y por tanto puedes tener un total 3 repeticiones.
pollito_id <- 1:75
pollito_id <- sample(pollito_id,size = 75)
dieta <- paste0("D",rep(1:5,each=15))
length(dieta)
## [1] 75
peso_d1 <- rnorm(mean = 160, n = 15,sd = 1)
peso_d2 <- rnorm(mean = 180, n = 15,sd = 1.4)
peso_d3 <- rnorm(mean = 130, n = 15,sd = 3)
peso_d4 <- rnorm(mean = 150, n = 15,sd = .6)
peso_d5 <- rnorm(mean = 155, n = 15,sd = 1.7)
pesos_all <- c(peso_d1,peso_d2,peso_d3,peso_d4,peso_d5)
pollitos <- data.frame(pollito_id, dieta,pesos_all)
pollitos <- pollitos[sample(pollito_id,size = 75),]
# Imprimamos una parte del data.frame
print(pollitos[c(1:3,21:23,31:33,51:53,71:73),])
pollito_id | dieta | pesos_all | |
---|---|---|---|
4 | 60 | D1 | 160.4908081788184 |
29 | 8 | D2 | 178.1562044213714 |
22 | 51 | D2 | 179.5102092571074 |
47 | 32 | D4 | 150.2164605542841 |
28 | 74 | D2 | 179.8214203218564 |
45 | 63 | D3 | 125.2092943230147 |
49 | 3 | D4 | 148.0059990212176 |
71 | 23 | D5 | 152.9807265268597 |
57 | 5 | D4 | 150.5264060235786 |
11 | 46 | D1 | 159.2706134863485 |
10 | 14 | D1 | 159.8725224816243 |
73 | 20 | D5 | 155.6147992224247 |
42 | 56 | D3 | 130.7598846648075 |
37 | 21 | D3 | 134.6959782373489 |
8 | 39 | D1 | 158.5989584026338 |
data.frames
La indexación de los data.frames
es idéntica al de las matrices; sin embargo, tiene una ventaja adicional, esta ventaja radica en que podemos llamar una columana completa indicando el nombre de esta en vez del número. De este modo en el ejemplo de pollitos, no importa si no sabemos la posición exacta de la columna dieta R la encontrará por nosotros.
Veamos como hacerlo, imprimamos los primeros 10 elementos de la columana dieta
pollitos[1:10,c("dieta")]
## [1] D1 D2 D2 D4 D3 D1 D1 D4 D1 D2
## Levels: D1 D2 D3 D4 D5
Otra forma de realizar la operación anterior es utilizando el símbolo de \(\$\)
pollitos$dieta[1:10]
## [1] D1 D2 D2 D4 D3 D1 D1 D4 D1 D2
## Levels: D1 D2 D3 D4 D5
Es momento de introducir a uno de los objetos estrella de R, este es el objeto lista. Al igual que los vectores, las listas son arreglos unidimensionales con la peculiaridad de que los elementos que las integran pueden ser de varias clases, por ejemplo, data.frames
, matrices
, vectores
, rasters
, etc. A continuación veamos cómo crear una lista que contiene como elementos un data.frame
, un vector
y una lista
, esta se crea con el comando list()
# un vector de numeros aleatorio del 0 a la 1
vector <- runif(10)
# Un subconjunto del DF pollitos
data_frame_pollito <- pollitos[1:10,]
# uan lista
sublista <- list(a=1:10)
lista <- list(vector=vector, pollitos_df = data_frame_pollito, sublista=sublista)
print(lista)
## $vector
## [1] 0.18183119688183069 0.27641138387843966 0.14377396157942712
## [4] 0.79579753126017749 0.09936926979571581 0.01533198193646967
## [7] 0.44952995958738029 0.17851411667652428 0.75940234912559390
## [10] 0.69747883360832930
##
## $pollitos_df
## pollito_id dieta pesos_all
## 4 60 D1 160.4908081788184
## 29 8 D2 178.1562044213714
## 22 51 D2 179.5102092571074
## 50 22 D4 149.7194907281375
## 32 26 D3 131.1819747251847
## 7 19 D1 160.0138229114622
## 3 13 D1 162.0507495261136
## 52 40 D4 149.6376063276962
## 15 41 D1 160.4390934344356
## 27 48 D2 176.9266181934850
##
## $sublista
## $sublista$a
## [1] 1 2 3 4 5 6 7 8 9 10
Como habíamos visto en la sección anterior, las listas se comportan como vectores y por tanto la forma de extraer un elemento de esta simplemente es ocupando la sintaxis lista[[element_index]]
.
# El segundo elemento de la lista es el DF pollitos
lista[[2]]
## pollito_id dieta pesos_all
## 4 60 D1 160.4908081788184
## 29 8 D2 178.1562044213714
## 22 51 D2 179.5102092571074
## 50 22 D4 149.7194907281375
## 32 26 D3 131.1819747251847
## 7 19 D1 160.0138229114622
## 3 13 D1 162.0507495261136
## 52 40 D4 149.6376063276962
## 15 41 D1 160.4390934344356
## 27 48 D2 176.9266181934850
Otra forma de extraer un elemento de la lista es con la sintaxis lista$nombre_elemento
lista$pollitos_df
## pollito_id dieta pesos_all
## 4 60 D1 160.4908081788184
## 29 8 D2 178.1562044213714
## 22 51 D2 179.5102092571074
## 50 22 D4 149.7194907281375
## 32 26 D3 131.1819747251847
## 7 19 D1 160.0138229114622
## 3 13 D1 162.0507495261136
## 52 40 D4 149.6376063276962
## 15 41 D1 160.4390934344356
## 27 48 D2 176.9266181934850
o bien lista[["nombre_elemento"]]
lista[["pollitos_df"]]
## pollito_id dieta pesos_all
## 4 60 D1 160.4908081788184
## 29 8 D2 178.1562044213714
## 22 51 D2 179.5102092571074
## 50 22 D4 149.7194907281375
## 32 26 D3 131.1819747251847
## 7 19 D1 160.0138229114622
## 3 13 D1 162.0507495261136
## 52 40 D4 149.6376063276962
## 15 41 D1 160.4390934344356
## 27 48 D2 176.9266181934850
Estamos interesados en extaer el elemento 10 del vector a contenido en la lista nombrada sublista que se encuentra dentro de nuestra lista principal. La sintaxis para extraer un elemento de una lista contenida dentro de otra lista es lista[[indice_nivel_1]][[indice_nivel_2]][[indice_nivel3]]
# El elemento de lista que a la sublista que contiene al vector objetivo es el 3
# Esta sublista esta contituida por un solo elemento el cual es el vector a
# El elemento del vector a que nos interesa es el 10
indice_nivel_1 <- 3
indice_nivel_2 <- 1
indice_nivel_3 <- 10
lista[[indice_nivel_1]][[indice_nivel_2]][[indice_nivel_3]]
## [1] 10
o bien
lista[["sublista"]][["a"]][[indice_nivel_3]]
## [1] 10
Agreguemos un nuevo elemento (un vector de números aleatorios) a lista
lista[["nuevo_elem"]] <- rnorm(30)
lista
## $vector
## [1] 0.18183119688183069 0.27641138387843966 0.14377396157942712
## [4] 0.79579753126017749 0.09936926979571581 0.01533198193646967
## [7] 0.44952995958738029 0.17851411667652428 0.75940234912559390
## [10] 0.69747883360832930
##
## $pollitos_df
## pollito_id dieta pesos_all
## 4 60 D1 160.4908081788184
## 29 8 D2 178.1562044213714
## 22 51 D2 179.5102092571074
## 50 22 D4 149.7194907281375
## 32 26 D3 131.1819747251847
## 7 19 D1 160.0138229114622
## 3 13 D1 162.0507495261136
## 52 40 D4 149.6376063276962
## 15 41 D1 160.4390934344356
## 27 48 D2 176.9266181934850
##
## $sublista
## $sublista$a
## [1] 1 2 3 4 5 6 7 8 9 10
##
##
## $nuevo_elem
## [1] 0.24808156566424361 0.11958214399610551 -2.44876246558915600
## [4] -2.62255666029343892 1.46267878174225907 -1.59707891388979162
## [7] 0.30665061441904906 1.09136924610944641 0.41848451288896160
## [10] -0.11797700533048078 -0.01597864742971554 -0.12726940167021722
## [13] 0.04654090599314615 0.67176451449107977 -0.61625328544361713
## [16] 1.04921716457545200 0.64160001287298063 -0.86959257954203950
## [19] 0.70340362628330910 0.58819571687630801 -0.91701701282439629
## [22] -0.94703304323816306 0.17992584942361345 -0.15931828698762843
## [25] -0.08601028026378041 0.48798127685024495 1.58825416578003176
## [28] 1.32141840030061419 0.07066348624595932 0.39434015048128590
o bien
lista$nuevo_elem_2 <- rbinom(30,size = 1,prob = 0.5)
lista
## $vector
## [1] 0.18183119688183069 0.27641138387843966 0.14377396157942712
## [4] 0.79579753126017749 0.09936926979571581 0.01533198193646967
## [7] 0.44952995958738029 0.17851411667652428 0.75940234912559390
## [10] 0.69747883360832930
##
## $pollitos_df
## pollito_id dieta pesos_all
## 4 60 D1 160.4908081788184
## 29 8 D2 178.1562044213714
## 22 51 D2 179.5102092571074
## 50 22 D4 149.7194907281375
## 32 26 D3 131.1819747251847
## 7 19 D1 160.0138229114622
## 3 13 D1 162.0507495261136
## 52 40 D4 149.6376063276962
## 15 41 D1 160.4390934344356
## 27 48 D2 176.9266181934850
##
## $sublista
## $sublista$a
## [1] 1 2 3 4 5 6 7 8 9 10
##
##
## $nuevo_elem
## [1] 0.24808156566424361 0.11958214399610551 -2.44876246558915600
## [4] -2.62255666029343892 1.46267878174225907 -1.59707891388979162
## [7] 0.30665061441904906 1.09136924610944641 0.41848451288896160
## [10] -0.11797700533048078 -0.01597864742971554 -0.12726940167021722
## [13] 0.04654090599314615 0.67176451449107977 -0.61625328544361713
## [16] 1.04921716457545200 0.64160001287298063 -0.86959257954203950
## [19] 0.70340362628330910 0.58819571687630801 -0.91701701282439629
## [22] -0.94703304323816306 0.17992584942361345 -0.15931828698762843
## [25] -0.08601028026378041 0.48798127685024495 1.58825416578003176
## [28] 1.32141840030061419 0.07066348624595932 0.39434015048128590
##
## $nuevo_elem_2
## [1] 0 1 0 1 0 1 0 1 0 1 0 1 1 1 1 1 0 1 1 0 1 1 1 0 1 0 1 1 0 0
Se pueden importar datos tabulados provenientes de varios formatos. Los más comunes son .csv
,.txt
y .xls
. Los comandos para leer estos formatos son read.csv
, read.table
, read.xls
(en el paquete gdata
) y loadWorkbook
(en el paquete XLConnect
).
En este documento veremos cómo cargar datos con los comandos read.csv
y read.table
. El primero sirve para leer datos separados por comas y el segundo por tabulaciones.
Algunas bases de datos contienen otro tipo de separadores, tales como “;” “:” o cualquier otro caracter. Tanto read.csv
como read.table
tienen un argumento que perimite indicar el caracter sepador, este argumento es sep = "caracter"
. Así, si nuestra base de datos estuviera separada por “;”, sólo tendríamos que indicarlo de la siguiente manera: sep=";"
.
Veamos algunos ejemplos concretos.
df_csv <- read.csv("Arichivos_01Intro/data_Clean.csv")
head(df_csv)
## species long lat
## 1 bradypus_variegatus -65.40000000000001 -10.3833
## 2 bradypus_variegatus -65.38330000000001 -10.3833
## 3 bradypus_variegatus -65.13330000000001 -16.8000
## 4 bradypus_variegatus -63.66670000000000 -17.4500
## 5 bradypus_variegatus -63.85000000000000 -17.4000
## 6 bradypus_variegatus -64.41670000000001 -16.0000
df_tab <- read.table("Arichivos_01Intro/ambys_data_dynamic_map.txt",header = T)
head(df_tab[,1:4])
## name longitude latitude issues
## 1 Ambystoma tigrinum -92.48258000000000 34.46026 cdround,gass84
## 2 Ambystoma tigrinum -95.88531999999999 31.93524 cdround,gass84
## 3 Ambystoma tigrinum -93.27306000000000 45.21076 cdround,gass84
## 4 Ambystoma tigrinum -96.32323000000000 44.00937 cdround,gass84
## 5 Ambystoma tigrinum -95.60391000000000 41.46921 cdround,gass84
## 6 Ambystoma tigrinum -97.75873000000000 35.26342 cdround,gass84
read.csv
para leer datos separados por tabulacionesdf_tab_to_csv <- read.csv("Arichivos_01Intro/ambys_data_dynamic_map.txt",header = T,sep=" ")
head(df_tab_to_csv[,1:4])
## name longitude latitude issues
## 1 Ambystoma tigrinum -92.48258 34.46026 cdround,gass84
## 2 Ambystoma tigrinum -95.88532 31.93524 cdround,gass84
## 3 Ambystoma tigrinum -93.27306 45.21076 cdround,gass84
## 4 Ambystoma tigrinum -96.32323 44.00937 cdround,gass84
## 5 Ambystoma tigrinum -95.60391 41.46921 cdround,gass84
## 6 Ambystoma tigrinum -97.75873 35.26342 cdround,gass84
Nota: una cuestión que debemos de tomar en cuenta cuando leemos datos en R es la codificación de nuestro sistema, ya que esto puede causar que no podamos leer un archivo. Algunas de las codificaciones más frecuentes son: UTF-8
(default en MAC), ASCII
, WINDOWS-1252
y las ISO-xxxx-x
. En R podemos especificar la codificación con el argumento fileEncoding
de las funciones read.csv
y read.table
d <- read.csv("Arichivos_01Intro/ambys_data_dynamic_map.txt",header = T,sep=" ",fileEncoding = "UTF-8")
head(d[,5:7])
## prov key datasetKey
## 1 gbif 1265535984 50c9509d-22c7-4a22-a47d-8c48425ef4a7
## 2 gbif 1088947578 50c9509d-22c7-4a22-a47d-8c48425ef4a7
## 3 gbif 1088946504 50c9509d-22c7-4a22-a47d-8c48425ef4a7
## 4 gbif 1143544830 50c9509d-22c7-4a22-a47d-8c48425ef4a7
## 5 gbif 1227733876 50c9509d-22c7-4a22-a47d-8c48425ef4a7
## 6 gbif 1252601766 2e64dedd-0996-4cd6-b6cd-4f055a46c38c
Como se mencionó anteriormente, en R es posible leer archivos de Excel utilizando las funciones read.xls
y loadWorkbook
de los paquetes gdata
y XLConnect
respectivamente. Veamos cómo instalar estos paquetes para utilizar las funciones señaladas.
read.xls
sólo tenemos que señalar la ruta donde se encuentra nuestro xls y la hoja del libro.# install.packages("gdata")
library(gdata)
## gdata: read.xls support for 'XLS' (Excel 97-2004) files ENABLED.
##
## gdata: read.xls support for 'XLSX' (Excel 2007+) files ENABLED.
##
## Attaching package: 'gdata'
## The following object is masked from 'package:stats':
##
## nobs
## The following object is masked from 'package:utils':
##
## object.size
d_xls <- read.xls("Arichivos_01Intro/data_Clean.xlsx",sheet = 1)
head(d_xls)
## IterationNo AUC_at_Value_0.95 AUC_at_0.5 AUC_ratio
## 1 1 0.6904844465485830 0.455461834131189 1.51600945415264
## 2 2 0.6502447312374160 0.440165890604005 1.47727196749648
## 3 3 0.7395332992408410 0.466813435453584 1.58421596953881
## 4 4 0.6904844465485830 0.455461834131189 1.51600945415264
## 5 5 0.7908706395783081 0.479626956033633 1.64892867181312
## 6 6 0.6259150694167050 0.433288054415460 1.44457033384203
Veamos cómo hacer lo anterior con el paquete XLConnect
library(XLConnect)
## Loading required package: XLConnectJars
## XLConnect 0.2-11 by Mirai Solutions GmbH [aut],
## Martin Studer [cre],
## The Apache Software Foundation [ctb, cph] (Apache POI, Apache Commons
## Codec),
## Stephen Colebourne [ctb, cph] (Joda-Time Java library)
## http://www.mirai-solutions.com ,
## http://miraisolutions.wordpress.com
lib_xls <- loadWorkbook("Arichivos_01Intro/data_Clean.xlsx")
# Mostrar las hojas dispobles
getSheets(lib_xls)
## [1] "ROC" "OCCS"
# Cargar una hoja
d_xlsC <- readWorksheet(object = lib_xls,sheet = "ROC")
head(d_xlsC)
## IterationNo AUC_at_Value_0.95 AUC_at_0.5 AUC_ratio
## 1 1 0.6904844465485830 0.455461834131189 1.51600945415264
## 2 2 0.6502447312374160 0.440165890604005 1.47727196749648
## 3 3 0.7395332992408410 0.466813435453584 1.58421596953881
## 4 4 0.6904844465485830 0.455461834131189 1.51600945415264
## 5 5 0.7908706395783081 0.479626956033633 1.64892867181312
## 6 6 0.6259150694167050 0.433288054415460 1.44457033384203
Programar implica escribir instrucciones relativamente complejas y repetir de manera iterada algunas de ellas. Hay dos grandes tipos de programación:
La programación imperativa en donde se le indica a la computadora de manera consecutiva un conjunto de operaciones.
La programación declarativa en donde se da una descripción del resultado final (i.e HTML, \(\LaTeX\)) sin especificar cómo se obtiene tal resultado.
Dentro de cada uno de estos estilos generales hay subdivisiones y algunos programas pueden contener varios aspectos de ellos. De manera específica en R, podemos encontrar a la programación modular (paquetes), la orientada a objetos y funcional.
A continuación veremos unas estructuras conocidas como estructuras de control. Estas controlan cuántas veces una operación debe de repetirse.
if
if
es una sentencia cuya entrada es una operación de tipo lógica y nos permite probar si alguna condición se cumple o no. El diagrama de flujo para la sentencia if
es el siguiente
La sintaxis if
en R:
if (condicion) {
comandos o operaciones cuando TRUE
}
if (condicion) {
comandos o operaciones cuando TRUE
} else {
comandos o operaciones cuando FALSE
}
Imaginemos que nos interesa hacer un conjunto de operaciones sólo sobre los números divisibles entre 2 (pares) y descartar aquellos números que no son pares, a continuación veremos cómo utilizar if para lograr nuestro cometido.
n1 <- 1
n2 <- 2
n3 <- 3
n4 <- 4
if(n1 %% 2 == 0){
cat(n1,"Es par")
} else {
cat(n1, "No es par")
}
## 1 No es par
if(n2 %% 2 == 0){
cat(n2,"Es par")
} else {
cat(n2, "No es par")
}
## 2 Es par
if(n3 %% 2 == 0){
cat(n3,"Es par")
} else {
cat(n3, "No es par")
}
## 3 No es par
if(n4 %% 2 == 0){
cat(n4,"Es par")
} else {
cat(n4, "No es par")
}
## 4 Es par
for
loopSupongamos que saber cuáles son los números que son pares en un vector de 100 elementos; esto implicaría repetir 100 veces el código de la sección anterior. La sentencia for
permite especificar con tan sólo una pocas líneas de código operaciones que se repiten n
veces.
La sintaxis del for loop en R es
for (iterador in vector) {comandos o operaciones}
# Genero una secuencia de números enteros (del 150 al 550) aleatoria
vec_num <- sample(150:550,size = 100,replace = F)
# Nuestro vector es
print(vec_num)
## [1] 472 477 264 363 484 267 526 168 198 260 223 451 461 447 252 233 547
## [18] 174 512 448 232 229 322 221 321 326 265 481 389 333 266 210 392 402
## [35] 150 386 298 311 377 475 427 508 187 510 438 456 345 284 200 500 226
## [52] 417 467 213 411 531 179 370 156 373 464 182 214 511 376 242 239 454
## [69] 161 519 307 346 469 491 256 291 498 285 234 529 331 339 192 299 309
## [86] 222 165 414 155 235 524 471 283 243 504 280 473 303 250 318
for(n in vec_num){
if(n %% 2 == 0)
cat(n,"Es par\n")
else
cat(n, "No es par\n")
}
## 472 Es par
## 477 No es par
## 264 Es par
## 363 No es par
## 484 Es par
## 267 No es par
## 526 Es par
## 168 Es par
## 198 Es par
## 260 Es par
## 223 No es par
## 451 No es par
## 461 No es par
## 447 No es par
## 252 Es par
## 233 No es par
## 547 No es par
## 174 Es par
## 512 Es par
## 448 Es par
## 232 Es par
## 229 No es par
## 322 Es par
## 221 No es par
## 321 No es par
## 326 Es par
## 265 No es par
## 481 No es par
## 389 No es par
## 333 No es par
## 266 Es par
## 210 Es par
## 392 Es par
## 402 Es par
## 150 Es par
## 386 Es par
## 298 Es par
## 311 No es par
## 377 No es par
## 475 No es par
## 427 No es par
## 508 Es par
## 187 No es par
## 510 Es par
## 438 Es par
## 456 Es par
## 345 No es par
## 284 Es par
## 200 Es par
## 500 Es par
## 226 Es par
## 417 No es par
## 467 No es par
## 213 No es par
## 411 No es par
## 531 No es par
## 179 No es par
## 370 Es par
## 156 Es par
## 373 No es par
## 464 Es par
## 182 Es par
## 214 Es par
## 511 No es par
## 376 Es par
## 242 Es par
## 239 No es par
## 454 Es par
## 161 No es par
## 519 No es par
## 307 No es par
## 346 Es par
## 469 No es par
## 491 No es par
## 256 Es par
## 291 No es par
## 498 Es par
## 285 No es par
## 234 Es par
## 529 No es par
## 331 No es par
## 339 No es par
## 192 Es par
## 299 No es par
## 309 No es par
## 222 Es par
## 165 No es par
## 414 Es par
## 155 No es par
## 235 No es par
## 524 Es par
## 471 No es par
## 283 No es par
## 243 No es par
## 504 Es par
## 280 Es par
## 473 No es par
## 303 No es par
## 250 Es par
## 318 Es par
Alternativamente
# Iteramos sobre los elementos de vec_num
vec_size <- length(vec_num)
for(iterador in 1:vec_size){
n <- vec_num[iterador]
if(n %% 2 == 0)
cat(n,"Es par\n")
else
cat(n, "No es par\n")
}
Supongan que ahora se les pide guardar en un vector los números que son pares y los que no lo son en otro (para números enteros son los impares).La idea para solucionar nuestro problema es simple, apliquemos el algoritmo par o impar y vayamos guardando (“rellenando”) los resultados en un vector nulo (vacío).
vec_pares <- NULL
vec_impares <- NULL
# Iteramos sobre los elementos de vec_num
vec_size <- length(vec_num)
for(iterador in 1:vec_size){
n <- vec_num[iterador]
if(n %% 2 == 0){
vec_pares[iterador] <- n
}
else{
vec_impares[iterador] <- n
}
}
En el vector vec_pares
se generaron de manera automática NA
s (Not Aviable) cuando la condición es par no se cumplió (R lo hace de manera automática). Lo mismo ocurrió para el vector vec_impares
cuando la otra condición no se cumplió (condición es impar).
Veamos cómo quitar los NA
s.
vec_pares_limpio <- as.vector(na.omit(vec_pares))
print(vec_pares_limpio)
## [1] 472 264 484 526 168 198 260 252 174 512 448 232 322 326 266 210 392
## [18] 402 150 386 298 508 510 438 456 284 200 500 226 370 156 464 182 214
## [35] 376 242 454 346 256 498 234 192 222 414 524 504 280 250 318
vec_impares_limpio <-as.vector( na.omit(vec_impares))
print(vec_impares_limpio)
## [1] 477 363 267 223 451 461 447 233 547 229 221 321 265 481 389 333 311
## [18] 377 475 427 187 345 417 467 213 411 531 179 373 511 239 161 519 307
## [35] 469 491 291 285 529 331 339 299 309 165 155 235 471 283 243 473 303
De forma similar podemos filtrar los datos con el comando is.na
. Debido a que estamos interesados en los datos que no son NA
usamos el comando de negación !
para preguntar cuáles no son NA
. Finalmente, con el comando which
podemos saber las posiciones del vector vec_pares
que no son NA
.
vec_pares_limpio <- vec_pares[which(!is.na(vec_pares))]
vec_impares_limpio <- vec_impares[which(!is.na(vec_impares))]
Si nos preguntaramos de nuestro vector original (vec_num
) cuantos son pares y cuantos son impares…
cat("El numero de enteros pares en vec_num fue de",length(vec_pares_limpio),"\n")
## El numero de enteros pares en vec_num fue de 49
cat("El numero de enteros impares en vec_num fue de",length(vec_impares_limpio),"\n")
## El numero de enteros impares en vec_num fue de 51
while
loopHabrá situaciones donde no sabemos cuántas iteraciones exactas necesitamos para llegar a cierto resultado. Será necesario hacer los cálculos y evaluar el resultado en cada iteración hasta que la condición que buscamos se cumpla. En esta situaciones podemos ocupar el ciclo while
.
Supongamos que se nos pide calcular cuál es el número de la sucesión de Fibonacci más cercano al 400. El algoritmo para encontrar el \(n-esimo\) término de la sucesión de Fibonacci es el siguiente
\[ f_{n} = f_{n-1} + f_{n-2}\]
con \(f_{1}=0\), \(f_{2}=1\) y \(f_{3}=1\). Ahora vemos cómo generar la secuencia de números de Fibonacci menores a 400 usando while
.
fib_1 <- 0
fib_2 <- 1
fib_3 <- 1
fibonacci <- c(fib_1,fib_2,fib_3)
while(fib_3 < 400){
fibonacci <- c(fibonacci,fib_3)
fib_n <- fib_2 + fib_3
fib_2 <- fib_3
fib_3 <- fib_n
}
print(fibonacci)
## [1] 0 1 1 1 2 3 5 8 13 21 34 55 89 144 233 377
¿Cuál es la posición del elemento de la sucesión más cercano a 400?
print(length(fibonacci))
## [1] 16
Es común que hagamos ciertas operaciones de manera repetida y como consecuencia de ello tengamos que duplicar código que ya hemos ocupado con anterioridad. Lo anterior hace nuestros programas o scripts sean enormes y con mucha información redundante. Las funciones definidas por el usuarios precisamente están pensadas para que cuando tengamos que hacer una operación de manera repetida podamos llamar código con una sola instrucción sin tener que escribir todo nuevamente.
En R podemos definir nuestras propias funciones de manera fácil. Veremos cómo definir una función para pregruntar si un número entero es par o impar. Asimismo, definieremos una función para generar los primeros \(n\) números de la secuencia de Fibonacci.
es_par <- function(x){
# Cheamos que x es entero
if(x%%1==0){
# Checamos si es par
if(x%%2==0){
return(TRUE)
}
else
return(FALSE)
}
# Mensaje notificando que x no es entero
warning("El numero tiene que ser un numero entero")
}
Probemos nuestra función
# Primero con un numero que no es entero
es_par(2.2)
## Warning in es_par(2.2): El numero tiene que ser un numero entero
# Ahora con un impar
es_par(3)
## [1] FALSE
# Finalmente con un par
es_par(18)
## [1] TRUE
Dentro de las críticas más fuertes que se le hacen a R es que es un lenguaje “lento”. Una de las razones de ello, es que al ser un leguaje interpretado, R pregunta el tipo de variable (integer, floating, char, etc) en el tiempo de ejecución, sus instrucciones se traducen o interpretan una a una, cada vez que se ejecuta el programa. Cuando asignamos un valor a una variable, digamos a <- 1.0
lo que hace el interprete de R
es preguntar:
1.0
es de tipo flotantea
es de tipo numérica1.0
Para una explicación maravillosa sobre este tópico ver el artículo de Noam Ross
En los leguajes compliados (C
, C++
, fortran
) declaramos los tipos de las variables desde un principio, sus instrucciones se traducen a código máquina de manera directa y optimizada mediante un compilador, lo que los hace más efecientes.
En R una forma de abordar el problema de eficiencia es utilizando operaciones vectorizadas siempre que sea posible. Las operaciones vectorizadas permiten a R llamar funciones de alto nivel que ejecutan código de C
, lo que resulta en programas mucho más rápidos.
Comparemos el rendimiento de operaciones no vectorizadas vs. vectorizadas.
Sea df_occs una base de datos con puntos de presencia de varias especies de reptiles y anfibios; suponga que se le pide filtar los datos de presencia por especie y contruir una lista donde cada elemento de ella es un data.frame
que contiene los datos de cada especie. Veremos cómo hacer este filtrado utilizando operaciones no vectorizadas y vectorizadas. Haremos uso de la función system.time
para medir el tiempo que tarde en correr los algoritmos…
Primero leemos la base de datos de los puntos de presencia y exploramos la base de datos (BD).
occs_bd <- read.csv("./Arichivos_01Intro/occs_rep_amf.csv")
# Dimensiones del DF.
dim(occs_bd )
## [1] 4000 6
# Una explo de la BD (mostrando solo 6 registros)
head(occs_bd,6)
## name longitude latitude prov date
## 1 Sceloporus occidentalis -120.65935 37.8203800000000 gbif 2016-03-16
## 2 Pseudacris regilla -121.98533 40.4388299990685 gbif 2003-06-25
## 3 Gehyra variegata 137.40600 -20.9770000000000 gbif 2009-04-12
## 4 Lampropholis delicata 150.59459 -34.5286200331773 gbif 2012-04-01
## 5 Zootoca vivipara 10.36416 59.9032500000000 gbif 2015-07-31
## 6 Chamaeleo dilepis 29.45000 -23.8166700000000 gbif 1916-11-19
## key
## 1 1262381503
## 2 543583266
## 3 1085956160
## 4 1074452855
## 5 1238773985
## 6 287041669
# Los nombres de las variables de la BD.
names(occs_bd)
## [1] "name" "longitude" "latitude" "prov" "date" "key"
# Los nombres de las especies
print(unique(occs_bd$name))
## [1] Sceloporus occidentalis Pseudacris regilla
## [3] Gehyra variegata Lampropholis delicata
## [5] Zootoca vivipara Chamaeleo dilepis
## [7] Rana pretiosa Ctenotus taeniolatus
## [9] Egernia cunninghami Anolis lemurinus
## [11] Psammodromus algirus Craugastor fleischmanni
## [13] Anaxyrus debilis Eremiascincus richardsonii
## [15] Tiliqua rugosa Eurycea bislineata
## [17] Dipsosaurus dorsalis Liolaemus bibronii
## [19] Desmognathus fuscus Notechis scutatus
## [21] Austrelaps superbus Egernia saxatilis
## [23] Anolis carolinensis Sphaerodactylus nicholsi
## [25] Rana cascadae Egernia striolata
## [27] Sceloporus undulatus Sceloporus variabilis
## [29] Stellagama stellio Lepidophyma flavimaculatum
## [31] Lacerta agilis Bassiana duperreyi
## [33] Varanus varius Pseudacris cadaverina
## [35] Ctenotus regius Lithobates pipiens
## [37] Sceloporus graciosus Amphibolurus muricatus
## [39] Xantusia vigilis Incilius alvarius
## [41] Heteronotia binoei Anaxyrus cognatus
## [43] Eleutherodactylus coqui Takydromus sexlineatus
## [45] Uta stansburiana Psammodromus hispanicus
## [47] Rhinella marina Incilius marmoreus
## [49] Incilius mazatlanensis Hemiergis decresiensis
## [51] Moloch horridus Pseudonaja textilis
## [53] Plestiodon gilberti Ctenotus uber
## [55] Liolaemus darwinii Sphaerodactylus macrolepis
## [57] Lithobates warszewitschii Platysaurus intermedius
## [59] Liolaemus koslowskyi Tiliqua nigrolutea
## [61] Anolis humilis Sceloporus malachiticus
## [63] Eremiascincus fasciolatus Liolaemus kingii
## [65] Hemiergis peronii Liolaemus quilmes
## [67] Pachymedusa dacnicolor Lithobates palmipes
## [69] Sceloporus merriami Dendrobates auratus
## [71] Anolis intermedius Liopholis inornata
## [73] Parasuta flagellum Liolaemus boulengeri
## [75] Anolis gundlachi Podarcis muralis
## [77] Liolaemus robustus Smilisca fodiens
## [79] Liolaemus tenuis Liolaemus rothi
## [81] Liolaemus kriegi Pseudemoia entrecasteauxii
## [83] Pseudemoia spenceri Protobothrops mucrosquamatus
## [85] Liolaemus fitzingerii Lobulia elegans
## [87] Liolaemus pictus Eulamprus kosciuskoi
## [89] Anolis cupreus Liolaemus elongatus
## [91] Anolis cristatellus Anaxyrus canorus
## [93] Tarentola boettgeri Anolis lionotus
## [95] Takydromus septentrionalis Hyla walkeri
## [97] Anolis tropidolepis Liolaemus petrophilus
## [99] Anolis cooki Sphaerodactylus klauberi
## [101] Liolaemus chacoensis Liolaemus multimaculatus
## [103] Phymaturus patagonicus Liolaemus scapularis
## [105] Liolaemus melanops Liolaemus olongasta
## [107] Liolaemus tari Liolaemus walkeri
## [109] Rankinia diemensis
## 109 Levels: Amphibolurus muricatus Anaxyrus canorus ... Zootoca vivipara
Primero definimos la lista en donde guardaremos los data.frames
de cada especie. Nótese que la lista tendrá tantos elementos como especies en la BD.
# Creamos una lista vacia
occs_df_list <- list()
# Guardamos los nombres de las especies en un vector
sp_names <- unique(occs_bd$name)
Ahora implementamos el algorimo de búsqueda. La idea básica es ir recorriendo toda la columana name
de la base de datos occs_bd
e ir preguntando cuál registro cumple la condición occs_bd$name == sp_name_i
; posteriormente almacenar estos índices en un vector llamado sp_index
con los índices que cumplen la condición. Finalemente hacer un subset de occs_bd
con sp_index
.
# Vector donde guardare los idices (vacio)
sp_index <- c()
# Iteraremos
tiempo_novec <- system.time({
# Recorremos cada especie en sp_names
for(spi in 1:length(sp_names)){
# Especie i en la iteracion i
sp_name_i <- sp_names[spi]
# Recorremos cada registro en BD
for(registro in 1:dim(occs_bd)[1]){
# Preguntamos si coincide el nombre sp_name_i con el registro occs_bd$name (condicion 1)
if(sp_name_i == occs_bd$name[registro]){
sp_index <- c(sp_index,registro)
}
}
# Los registros que cumplieron la condicion 1
# en sp_index son ocupados para hacer el subset
# Guardamos estos en la lista
occs_df_list[[spi]] <- occs_bd[sp_index,]
# reinializamos el vector sp_index
sp_index <- c()
}
# Le ponemos nombre de la sp a cada elemento de la lista
names(occs_df_list) <- sp_names
})
Cuánto tardó en correr?
print(tiempo_novec)
## user system elapsed
## 36.71800000000000 0.41100000000000 36.97600000000001
Utilizaremos un comando de mi lista de estrellas, este es which
# Creamos una lista vacia
occs_df_list_vec <- list()
# nuevamente definamos una variable con los nombres de sps
sp_names <- unique(occs_bd$name)
Combinamos la operación vectorizada which
con el ciclo for
tiempo_vec <- system.time({
for(spi in 1:length(sp_names)){
# Especie i en la iteracion i
sp_name_i <- sp_names[spi]
sp_index_vec <- which(sp_name_i == occs_bd$name)
occs_df_list_vec[[spi]] <- occs_bd[sp_index_vec,]
}
})
names(occs_df_list_vec) <- sp_names
Cuánto tardo con el vectorizado??
print(tiempo_vec)
## user system elapsed
## 0.04200000000000159 0.00000000000000000 0.04200000000000159
Cuántas veces corrió más rápido el código vectorizado?
tiempo_ratio <- tiempo_novec[[3]]/tiempo_vec[[3]]
print(tiempo_ratio)
## [1] 880.3809523809192
apply
La familia de funciones apply tienen como objetivo operar sobre estructuras de datos como matrix
, data.frames
, arrays
, y lists
de forma repetitiva. Estas funciones aplican una función sobre un conjunto de datos y devulven un arreglo el cual puede ser una matriz, una tabla de datos o una lista. Una característica de ellas, es que permiten evitar los loops, por lo que serán necesarias menos lineas de código para llegar a un mismo resultado (el código suele ser más elegante).
Entre los R-programadores se suele decir que es más R-tónico (en alusión a pythonico) utilizar las funciones de la familia apply
sobre los loops.
Veamos cuales funciones conforman esta familia y sus ouputs (es información la tomé de esta página):
Función | |
---|---|
base::apply | Apply Functions Over Array Margins |
base::by | Apply a Function to a Data Frame Split by Factors |
base::eapply | Apply a Function Over Values in an Environment |
base::lapply | Apply a Function over a List or Vector |
base::mapply | Apply a Function to Multiple List or Vector Arguments |
base::rapply | Recursively Apply a Function to a List |
base::tapply | Apply a Function Over a Ragged Array |
La función lappy regresa una lista cuyos elementos pueden ser cualquier tipo de estructura de datos (matrix
, data.frames
, arrays
, lists
, etc.).
Veremos cómo implementar nuestro algortimo de búsqueda y filtrado de datos por especie utilizando las apply
functions.
Como se había mencionado anteriormente las funciones de esta gran familia aplican una función (generalmente definida por el usuario) sobre una estructura de datos y por lo tanto, para implementar nuestro algoritmo de búsqueda será necesario definir nuestra función.
# Funcion de busqueda
# sp_name: Es el nombre de la especie
# bd_datos: data.frame con los datos de presencia
# col_name: Columna en el data.frame sobre la que se hara la busqueda
find_sp <- function(sp_name, bd_datos,col_name){
# Preguntamos si coincide el nombre sp_name con el registro occs_bd[,col_name] (condicion 1)
sp_index <- which(bd_datos[,col_name] == sp_name)
# Subset con los datos que cumplen la condicion 1
sp_df <- bd_datos[sp_index,]
return(sp_df)
}
Apliquemos nuestra función usando la sintaxis lapply
. En esta sólo hay que especificar un vector de índices o listas (X) sobre el cual la función que definimos va a operar (find_sp
).
sp_names <- unique(occs_bd$name)
tiempo_lapply <- system.time({
occs_df_list_apply <- lapply(sp_names,
function(sp)
find_sp(sp,bd_datos = occs_bd,col_name = "name") )
})
Comparemos los rendimientos de nuestras implementaciones:
cat("Tiempo de ejecucion:", tiempo_novec[[3]],"algoritmo no vectorizado\n")
## Tiempo de ejecucion: 36.97600000000001 algoritmo no vectorizado
cat("Tiempo de ejecucion:", tiempo_vec[[3]],"algoritmo vectorizado\n")
## Tiempo de ejecucion: 0.04200000000000159 algoritmo vectorizado
cat("Tiempo de ejecucion:", tiempo_lapply[[3]],"algoritmo vectorizado y lappy\n")
## Tiempo de ejecucion: 0.04100000000000392 algoritmo vectorizado y lappy