Resumen

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.

Qué es, de dónde y cómo instalo R?

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

Figura 1. Número de paquetes en CRAN

Figura 2. Serie de tiempo del número de paquetes en CRAN

Obtención e instalación de R

R se puede descargar de la siguiente página web http://www.r-project.org/. Figura_3

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).

Figura_4

Figura_4

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)

Tutorial: Introducción a R

Una vez que han instalado R en sus computadoras ahora estamos listos para comenzar a aprender programación en este ambiente maravilloso.

Sintaxis

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"

R como calculadora

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

Variables (Global environment)

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

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

Los vectores y matrices en R

Comencemos con algunas definiciones necesarias:

  • Escalar: Un escalar es sólo una cantidad numérica i.e. un entero o un número real.
  • Vector: Se llama vector de dimensión \(n\) a una tupla de \(n\) números reales o componentes del vector (un vector está constituido por 1 o varios escalares).

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.

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í.

Indexación de vectores en R

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.

Generación de secuencias (vectores) regulares en R

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

El comando rep

La función rep permite generar secuencias repetidas de vectores.

vec <- c("A","B")
rep(vec, 2)
## [1] "A" "B" "A" "B"

El comando sample

Un comando frecuentemente utilizado para extraer una muestra aleatoria de un vector el comando sample. Algunos de sus argumentos son:

  • El vector de donde se va a sacar la secuencia aleatoria (\(x\)).
  • El número de elementos (size) que se van a extraer del vector \(x\).
  • La muestra será obtenida con remplazo (replace)

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

Midiendo la longitud de un vector

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

Las matrices en R

En R las matrices pueden ser de 2 tipos:

  1. Numérica
  2. Cadena

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)

Creación de matrices en R

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"

Indexación de matrices en R

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…

Llamando sólo una fila de una 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
Llamando sólo una columna de una matriz

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
Combinando formas de indexar

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:

  1. las primeras 3 filas de las columnas 1 y 2
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
  1. Las filas de 1 a la 5 de las columas 1 y 5
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í.

Los data.frame

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).

Construcción de 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

Indexación de 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

Las listas

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

Extracción de los elementos de la lista

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
Extrayendo elementos contenidos en una lista de listas

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
Agregando nuevos elementos a una lista

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

Leyendo datos en R

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.

  • Datos separados por comas
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
  • Datos separados por tabulaciones
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
  • Usando read.csv para leer datos separados por tabulaciones
df_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
Un ejemplo de como leer archivos de Excel

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.

  • Con 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

Estructuras de Control y Loops

Programar implica escribir instrucciones relativamente complejas y repetir de manera iterada algunas de ellas. Hay dos grandes tipos de programación:

  1. La programación imperativa en donde se le indica a la computadora de manera consecutiva un conjunto de operaciones.

  2. 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.

La sentencia 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 loop

Supongamos 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")
}
Ejemplos un poco más laboriosos

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 NAs (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 NAs.

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 loop

Habrá 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

Funciones definidas por el usuario.

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.

Función es_par

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

La operaciones vectorizadas

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:

  • Ese número 1.0 es de tipo flotante
  • La variable a es de tipo numérica
  • Poner un lugar en la memoria donde almacenar el 1.0
  • Registar “a” como un puntero a ese lugar de memoria.

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.

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

Algoritmo de búsqueda no vectorizado

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

Algoritmo de búsqueda vectorizado

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

La familia 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

lapply

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