1. Directorios de trabajo

Primero vamos a establecer un directorio de trabajo. Establecer una ruta de trabajo es un paso práctico más no necesario, que nos va a permitir importar archivos utilizando solo su nombre y extensión (ruta relativa) en lugar de la ruta completa (ruta absoluta). No obstante, siempre podemos usar las rutas completas si tenemos archivos en otras carpetas y simplemente no queremos crear copias que ocupen espacio.

setwd("D:/Daniel/Documents/Introducción a R") # Si usamos el forward slash (/) va a funcionar
setwd("D:\\Daniel\\Documents\\Introducción a R") # Si usamos el backslash (\) con secuencia de escape va a funcionar
setwd("D:\Daniel\Documents\Introducción a R") # Si usamos el backslash (\) sin secuencia de escape no va a funcionar

2. Variables y tipos de variable

Una dimensión y un elemento

Para asignarle valor a una variable solo tenemos que utilizar el símbolo de «asignación» (Var <- Valor). También podríamos hacerlo con el símbolo de igual (Var = Valor), pero por convención no se usa porque el igual está reservado para otro tipo de operaciones. Una variable puede contener un único valor, una serie de valores en una dimensión (vector), una serie de valores en dos dimensiones (matrices o marcos de datos), o un objeto en general (listas, gráficas, rasters, árboles filogenéticos, entre otros).

# El nombre de una variable debe ser una única palabra, es decir, no puede contener espacios (se pueden separar palabras con guion bajo o puntos). Tampoco puede empezar con un número.

a <- 100
b <- "Hola mundo" # Todo lo que esté dentro de comillas dobles va a ser interpretado como texto
c <- '100' # También lo que esté dentro de comillas simples
d <- TRUE # A este tipo de variables se les llama lógicas o booleanas (TRUE/FALSE)
a
## [1] 100
b
## [1] "Hola mundo"
c
## [1] "100"
d
## [1] TRUE

# R siempre nos va a mostrar por defecto la posición de los elementos de una variable dentro de llaves, lo que nos es muy útil para ubicarnos e indexar (lo veremos más adelante), pero no hace parte del contenido de la misma.

Una dimensión y varios elementos

Todas las anteriores son variables unidimensionales de un único elemento, ahora vamos a crear vectores, que no son más que variables de una sola dimensión pero que contienen múltiples elementos de un mismo tipo. Para crear un vector usaremos la función c(), separando cada elemento con una coma.

e <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
f <- c("México", "lindo", "y", "querido")
e
##  [1]  1  2  3  4  5  6  7  8  9 10
f
## [1] "México"  "lindo"   "y"       "querido"

En caso de que queramos una secuencia de números podemos hacer «trampa» para no tener que escribirlos uno por uno.

g <- 1:100 # El operador : nos permite crear secuencias numéricas consecutivas
h <- seq(0, 100, by = 5) # La función seq nos permite crear secuencias y establecer el intervalo
g
##   [1]   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
##  [19]  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36
##  [37]  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54
##  [55]  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72
##  [73]  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90
##  [91]  91  92  93  94  95  96  97  98  99 100
h
##  [1]   0   5  10  15  20  25  30  35  40  45  50  55  60  65  70  75  80  85  90
## [20]  95 100

Dos dimensiones

Ahora vamos a movernos a una segunda dimensión, en R existen dos tipos de objetos bidimensionales, las matrices y los marcos de datos (data frames, en inglés), ambos de apariencia similar pero con diferentes propiedades. En este caso crearemos una matriz, ya que más adelante vamos a cargar y a crear marcos de datos.

i <- matrix(data = 1:25, nrow = 5, ncol = 5, byrow = FALSE) # El valor por defecto siempre es FALSE
j <- matrix(data = 1:25, nrow = 5, ncol = 5, byrow = TRUE)
i # Cuando el argumento byrow = FALSE la matriz se llena de manera vertical
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    6   11   16   21
## [2,]    2    7   12   17   22
## [3,]    3    8   13   18   23
## [4,]    4    9   14   19   24
## [5,]    5   10   15   20   25
j # Cuando el argumento byrow = TRUE la matriz se llena de manera horizontal
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    2    3    4    5
## [2,]    6    7    8    9   10
## [3,]   11   12   13   14   15
## [4,]   16   17   18   19   20
## [5,]   21   22   23   24   25

# La diferencia fundamental entre una matriz y un marco de datos es que las matrices solo pueden contener un único tipo de datos (número, texto o lógicos), mientras que cada columna de un marco de datos puede contener objetos de diferentes tipos.

«Tres» dimensiones

Finalmente, tenemos objetos en «tres» dimensiones a los que llamamos listas, las cuales en realidad no tienen propiamente tres dimensiones, pero como veremos más adelante en la sección de indexado, requeriremos de una coordenada adicional para poder acceder a sus elementos. Una lista es un objeto más complejo que los anteriores, que nos permite almacenar otros objetos independientemente de si estos son o no del mismo tipo.

k <- list(a, b, c, d, e, f, g, h, i, j)
k
## [[1]]
## [1] 100
## 
## [[2]]
## [1] "Hola mundo"
## 
## [[3]]
## [1] "100"
## 
## [[4]]
## [1] TRUE
## 
## [[5]]
##  [1]  1  2  3  4  5  6  7  8  9 10
## 
## [[6]]
## [1] "México"  "lindo"   "y"       "querido"
## 
## [[7]]
##   [1]   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
##  [19]  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36
##  [37]  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54
##  [55]  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72
##  [73]  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90
##  [91]  91  92  93  94  95  96  97  98  99 100
## 
## [[8]]
##  [1]   0   5  10  15  20  25  30  35  40  45  50  55  60  65  70  75  80  85  90
## [20]  95 100
## 
## [[9]]
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    6   11   16   21
## [2,]    2    7   12   17   22
## [3,]    3    8   13   18   23
## [4,]    4    9   14   19   24
## [5,]    5   10   15   20   25
## 
## [[10]]
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    2    3    4    5
## [2,]    6    7    8    9   10
## [3,]   11   12   13   14   15
## [4,]   16   17   18   19   20
## [5,]   21   22   23   24   25

¿De qué tipo es mi variable?

De manera intuitiva, cuando creamos una variable y le asignamos un valor o valores tenemos una idea de qué tipo de datos son. Sin embargo, existe una función que nos permite validar fácilmente el tipo de nuestras variables, puesto que ciertas funciones solo reciben datos de entrada con tipos o formatos específicos.

class(a)
## [1] "numeric"

# R tiene dos formas de almacenar números, los enteros (integer) y los dobles (double = numeric). Los enteros son números sin parte decimal y requieren de menos espacio para ser almacenados, mientras que los dobles pueden ser tanto números enteros como números decimales. Por defecto, todo número y todo cálculo se almacena y procesa como doble.

Particularmente, cuando tenemos variables numéricas que queremos operar, tenemos que considerar las limitaciones informáticas de la maquina donde estemos trabajando y las limitaciones de R. Por ejemplo, por defecto, R solo muestra en pantalla 7 digitos.

pi # El comando pi nos permite usar el número π
## [1] 3.141593

Pero eso no significa que π tenga solo 6 decimales ni que los cálculos se hagan con el número redondeado. Para R, los cálculos se hacen con hasta 15 decimales de precisión (si es que el número los tiene). Por otro lado, si quisiéramos saber cuáles son los máximos números que nuestra máquina puede almacenar, podemos utilizar la siguiente función.

data.frame("Max Integer" = .Machine$integer.max,
           "Min Double" = .Machine$double.xmin,
           "Max Double" = .Machine$double.xmax)
##   Max.Integer    Min.Double    Max.Double
## 1  2147483647 2.225074e-308 1.797693e+308

Volviendo al tema, la validación del tipo podemos hacerla para cada una de las variables, pero como somos listos y un poco flojos, vamos a anidar funciones para que R nos arroje un único resultado en forma de tabla.

data.frame("Var" = ls(), # La función ls nos da un listado de todas las variables en el ambiente de trabajo
           "Class" = rbind(class(a), # La función rbind combina los elementos a manera de filas (su análoga es cbind)
                           class(b),
                           class(c),
                           class(d),
                           class(e),
                           class(f),
                           class(g),
                           class(h),
                           class(i),
                           class(j),
                           class(k)))
##    Var   Class.1   Class.2
## 1    a   numeric   numeric
## 2    b character character
## 3    c character character
## 4    d   logical   logical
## 5    e   numeric   numeric
## 6    f character character
## 7    g   integer   integer
## 8    h   numeric   numeric
## 9    i    matrix     array
## 10   j    matrix     array
## 11   k      list      list

# Un objeto puede tener más de una clase, por ejemplo, las matrices suelen ser de tipo matrix y array.

3. Cargando datos externos

En R es posible cargar conjuntos de datos desde fuentes externas y en diferentes formatos, pero la manera más fácil y universal de hacerlo es mediante texto delimitado. El texto delimitado es un archivo de texto plano sin formato, donde existe un caracter que nos delimita entre columnas (comas, puntos y comas, espacios, tabulaciones, entre otros).

Para este ejemplo comenzaremos por cargar un conjunto de datos que contiene errores intencionados, pero que reflejan los errores más comunes que hacen que nuestros datos queden mal cargados y empecemos a tener problemas a la hora de procesarlos.

data <- read.csv("D:/Daniel/Documents/Introducción a R/Datos (malos).csv")
dim(data) # La función dim nos sirve para validar las dimensiones de nuestro marco de datos (filas x columnas)
## [1] 32  1

¿Cuántas filas y columnas esperamos encontrar? ¿Tiene sentido con lo que R nos está diciendo?

Si revisamos nuestros datos en un editor de texto, podemos notar que las columnas se encuentran separadas por punto y coma (;) haciendo que R no pueda entender cómo están estructurados. Por defecto, la función read.csv() asume el formado inglés (coma como separador de columnas y punto para la posición decimal de los números), pero estos datos fueron exportados desde Excel en español (punto y coma como separador de columnas y coma para la posición decimal de los números). Por lo tanto, tenemos que especificar dentro de la función cuál es nuestro separador de columnas y cual nuestro separador decimal.

data <- read.csv(file = "D:/Daniel/Documents/Introducción a R/Datos (malos).csv",
                 header = TRUE, # Este argumento nos sirve para indicar si los datos tienen encabezado
                 sep = ";", # Aquí indicamos cuál es el separador de columnas
                 dec = ",") # Aquí indicamos cuál es el separador decimal
dim(data)
## [1] 32  5

Si abrimos nuestros datos ya sea mediante la consola o directamente en R Studio, podremos ver que ahora sí están estructurados a manera de tabla y que se ven aparentemente normales. Pero estos datos aún tienen algunos errores. ¿Pueden encontrarlos a simple vista?

# Pista: Hay tres tipos de errores en los datos.

head(data) # La función head nos muestra solo las primeras 6 filas de la tabla
##     País               Estado   Area Superficie Población
## 1 México            Chihuahua 247455       12.6   3741869
## 2 México               Sonora 179355        9.2   2944840
## 3 México Coahuila de Zaragoza 151562        7.7   3146771
## 4 México              Durango 123317        6,3   1832650
## 5 México               Oaxaca  93757        4,8   4132148
## 6 México           Tamaulipas  80249        4,1   3527735
tail(data) # La función tail nos muestra solo las últimas 6 filas de la tabla
##      País           Estado  Area Superficie Población
## 27 Mexico        Querétaro 11699        0,6   2368467
## 28 México           Colima  5627        0,3    731391
## 29 México   Aguascalientes  5616        0,3   1425607
## 30 México          Morelos  4879        0,2   1971520
## 31 México         Tlaxcala  4016        0,2   1342977
## 32 México Ciudad de México  1495        0,1   9209944

Cuando tenemos conjuntos de datos grandes, se hace extremadamente difícil encontrar los errores a simple vista, y si nuestros datos tienen errores, R nos va a comenzar a mostrar mensajes de error tras error, o en casos particulares, cálculos, pero malos. El problema es que, si no estamos familiarizados con los mensajes de error, o no somos muy diestros buscando en foros de internet, va a ser un verdadero dolor de cabeza. Los errores se pueden encontrar y corregir de manera programática (usando código), pero eso implica más tiempo y un conocimiento más profundo de R. Así que lo ideal es que nuestros datos estén curados usando buenas prácticas antes de importarlos.

Vamos a ver cómo podríamos identificar los errores que aún quedan, lo que va a ser muy sencillo porque yo los incluí intencionalmente así que sé cuáles son y cómo ubicarlos. ¿Pero y si no lo supiera?

unique(data$País) # La función unique nos permite ver los valores únicos de un elemento
## [1] "México"  "México " "Mexico"

Como vemos, existen tres versiones de México cuando solo debería haber una, lo que va a derivar en duplicados cuando tengamos que agrupar. Este error es bastante común y se produce porque nos quedan espacios al final de las palabras o espacios dobles entre ellas, y al ser «invisibles» son muy difíciles de detectar, o porque algunas palabras quedan con o sin acentos.

Supongamos que ahora quisieramos hacer una operación muy sencilla con los datos, para lo que vamos a sumar el área, la proporción de superficie y la población para obtener el total de cada una.

sum(data$Area) # La función sum nos permite sumar todos los valores de un objeto (en este caso un vector)
## [1] 1961485
sum(data$Superficie)
## Error in sum(data$Superficie): 'type' (character) de argumento no válido
sum(data$Población)
## [1] 126014024

Como vemos, hemos obtenido un error a la hora de sumar la superficie. Este error nos dice algo así como que no se pueden sumar caracteres. ¿Pero no se supone que la superficie era un número?

A continuación, aplicaremos una función que ya habíamos usado, con el objetivo de validar de qué tipo son las columnas de nuestro marco de datos.

data.frame("Col" = colnames(data), # La función colnames nos da un listado con el nombre de las columnas del df
           "Class" = c(class(data$País),
                       class(data$Estado),
                       class(data$Area),
                       class(data$Superficie),
                       class(data$Población)))
##          Col     Class
## 1       País character
## 2     Estado character
## 3       Area   integer
## 4 Superficie character
## 5  Población   integer

Lo que R nos está diciendo es que, en efecto, la columna de «Superficie» no es un número y por ende no se puede operar. ¿Pero por qué no es un número? Si revisamos de nuevo los datos con un poco más de cuidado podremos ver que existen números decimales separados con punto y otros con coma.

head(data)
##     País               Estado   Area Superficie Población
## 1 México            Chihuahua 247455       12.6   3741869
## 2 México               Sonora 179355        9.2   2944840
## 3 México Coahuila de Zaragoza 151562        7.7   3146771
## 4 México              Durango 123317        6,3   1832650
## 5 México               Oaxaca  93757        4,8   4132148
## 6 México           Tamaulipas  80249        4,1   3527735

Este error es muy común, particularmente si copiamos y pegamos datos de diferentes fuentes que tienen diferentes formatos, o si al cargar los datos el separador decimal no coincide con el que le indicamos a la función.

Ya que sabemos dónde y cómo no meter la pata, vamos a cargar los datos corregidos y a validar de nuevo para verificar que en efecto ya no tienen errores.

data <- read.csv(file = "D:/Daniel/Documents/Introducción a R/Datos (buenos).csv",
                 header = TRUE,
                 sep = ",",
                 dec = ".")
dim(data)
## [1] 32  5
unique(data$País)
## [1] "México"
sum(data$Superficie)
## [1] 99.9
data.frame("Col" = colnames(data),
           "Class" = c(class(data$País),
                       class(data$Estado),
                       class(data$Area),
                       class(data$Superficie),
                       class(data$Población)))
##          Col     Class
## 1       País character
## 2     Estado character
## 3       Area   integer
## 4 Superficie   numeric
## 5  Población   integer

4. Indexado y operadores lógicos

El indexado significa en palabras simples referenciar (o extraer) una parte de un conjunto de datos mediante la posición de los elementos que nos interesan, es decir, usando coordenadas o en algunos casos, nombres. Según el tipo de objeto, la indexación se puede hacer de una o de varias formas, y para este ejercicio, vamos a reutilizar algunas de las variables que ya hemos creado.

Cuando indexamos usando coordenadas, siempre vamos a utilizar las llaves de la siguiente forma: OBJETO[POSICIÓN], no importa si es un vector, una matriz, un marco de datos, o una lista, lo que cambia es lo que ponemos dentro o el número de llaves.

Vamos a comenzar por los vectores, que como ya sabemos tienen una sola dimensión. Para ello vamos a invocar de nuevo a la variable f, donde almacenamos las palabras que componen la frase «México lindo y querido», para luego extraer solo las palabras «México» y «querido».

f
## [1] "México"  "lindo"   "y"       "querido"

En este caso, como es un vector muy corto es posible identificar a simple vista que las palabras «México» y «querido» se encuentran en la posición 1 y 4, respectivamente. Al ser un vector con una sola dimensión, debemos ingresar la posición del elemento o elementos de nuestro interés como VECTOR[X], donde X puede ser un valor único o un vector de valores.

f[1] # Aquí indexamos solo el elemento en la posición 1
## [1] "México"
f[4] # Aquí indexamos solo el elemento en la posición 4
## [1] "querido"
f[c(1, 4)] # Aquí indexamos ambos elementos en las posiciones 1 y 4
## [1] "México"  "querido"

Sin embargo, existe una función que nos ayuda a encontrar elementos específicos y que nos será de mucha utilidad cuando tenemos grandes conjuntos de datos. En este punto podemos detenernos también para introducir algunos operadores lógicos que nos permiten hacer filtros o establecer condiciones.

Por ejemplo, cuando queremos determinar si algo coincide exactamente con un criterio de nuestro interés, vamos a utilizar el símbolo de igual dos veces (A == B).

which(f == "México") # La función which nos muestra la posición de un elemento que coincida con un criterio
## [1] 1
which(f == "querido")
## [1] 4

Por el contrario, si queremos identificar algo que sea diferente, podemos utilizar la negación del igual (A != B).

which(f != "México")
## [1] 2 3 4

Si queremos validar que algo coincida con dos o más condiciones podemos emplear el AND si nuestro criterio es excluyente, o el OR si no lo es.

which(f == "México" & f == "querido") # En R el AND se escribe &
## integer(0)
which(f == "México" | f == "querido") # Y el OR se escribe |
## [1] 1 4

Cuando tenemos números, los operadores mayor que (A > B), mayor o igual que (A >= B), menor que (A < B), o menor o igual que (A <= B), nos serán de mucha ayuda. Por ejemplo, vamos a indexar sobre la variable h donde tenemos una secuencia de números de 5 en 5 hasta 100, buscando números menores o iguales a 50.

which(h <= 50) # which nos da una posición, no el valor, para obtener los valores debemos indexar las posiciones
##  [1]  1  2  3  4  5  6  7  8  9 10 11
pos <- which(h <= 50) # Asignamos las posiciones a una variable
h[pos] # Indexamos las posiciones mediante la variable
##  [1]  0  5 10 15 20 25 30 35 40 45 50
h[which(h <= 50)] # También podemos indexar directamente y ahorrarnos una variable
##  [1]  0  5 10 15 20 25 30 35 40 45 50

# Los operadores lógicos no están restringidos a la función which(), pueden utilizarse dentro de cualquier otra siempre y cuando tenga sentido usarlos allí.

Cuando indexamos también podemos omitir posiciones indicándolas con un signo menos antes del número o el vector de números. Por ejemplo, si por el contrario quiero los números mayores a 50 y conozco la posición de los menores o iguales, puedo decirle a R que omita estas últimas.

h[-pos] # Indexamos las posiciones mediante la variable pero con el signo - indicamos que las queremos omitir
##  [1]  55  60  65  70  75  80  85  90  95 100

Ahora vamos a indexar elementos bidimensionales, como matrices o marcos de datos. Para ello tendremos que utilizar de nuevo las llaves, pero esta vez referenciando la posición de la o las filas y columnas que nos interesan de la forma OBJETO[X, Y], donde X es corresponde a las filas y Y a las columnas. Para el ejemplo, utilizaremos la matriz i que contiene los números del 1 al 25 distribuidos en 5 filas y 5 columnas.

i
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    6   11   16   21
## [2,]    2    7   12   17   22
## [3,]    3    8   13   18   23
## [4,]    4    9   14   19   24
## [5,]    5   10   15   20   25

Al igual que con los objetos de una dimensión, aquí podemos indexar un valor único o una serie de valores mediante un vector, pero vamos a comenzar por lo simple. Supongamos que nos interesa únicamente el valor del centro, que en este caso, al tratarse de una matriz de 5 x 5 correspondería al elemento de la tercera fila y la tercera columna.

i[3, 3]
## [1] 13

¿Y que tal si lo que nos interesa es una serie de valores?

i[3, ] # Una opción es dejar vacía la dimensión que nos interesa (= todos los elementos)
## [1]  3  8 13 18 23
i[3, 1:5] # También podemos indicar un rango de valores
## [1]  3  8 13 18 23
i[3, c(1, 2, 3, 4, 5)] # O introducir todo el vector manualmente
## [1]  3  8 13 18 23

Al igual que las matrices, los marcos de datos también se pueden indexar utilizando las coordenadas X e Y del elemento o los elementos que nos interesen. Por ejemplo, si quisiéramos calcular la densidad poblacional del estado de Sonora tendríamos que dividir el valor de población de dicho estado sobre su área.

head(data)
##     País               Estado   Area Superficie Población
## 1 México            Chihuahua 247455       12.6   3741869
## 2 México               Sonora 179355        9.2   2944840
## 3 México Coahuila de Zaragoza 151562        7.7   3146771
## 4 México              Durango 123317        6.3   1832650
## 5 México               Oaxaca  93757        4.8   4132148
## 6 México           Tamaulipas  80249        4.1   3527735
data[2, 5] / data [2, 3] # Indexamos el valor de población para Sonora y lo dividimos por su área
## [1] 16.41906

# Cuando hacemos operaciones matemáticas, R utiliza las reglas de prioridad aritmética, de modo que, 5 + 5 * 5 = 30, mientras que, (5 + 5) * 5 = 50.

Otra diferencia particular entre los marcos de datos y las matrices es que, los primeros también permiten indexar columnas enteras usando el nombre de estas, en cuyo caso el resultado sería un vector. Para ello lo único que tenemos que hacer es utilizar el símbolo de pesos luego del nombre de nuestra variable de tipo data.frame seguido del nombre de la columna que nos interesa (DATA.FRAME$NOMBRE_COLUMNA).

data$Población / data$Area # Aquí obtendríamos la densidad poblacional para todos los estados
##  [1]   15.12141   16.41906   20.76227   14.86129   44.07295   43.95986
##  [7]  106.22679   21.54692   10.80311   75.62068  112.25154   52.75045
## [13]   90.16214   55.67465   46.16280   81.03971   52.00933   16.14348
## [19]   41.56101   58.72123  191.89873  201.48112   44.34993   97.14925
## [25]  760.25314  148.12093  202.45038  129.97885  253.84740  404.08280
## [31]  334.40662 6160.49766

También podríamos crear una nueva columna para almacenar los datos, de modo que podamos utilizarlos luego.

data$Densidad <- round(data$Población / data$Area, 2) # La función round nos permite redondear a N cantidad de decimales
head(data)
##     País               Estado   Area Superficie Población Densidad
## 1 México            Chihuahua 247455       12.6   3741869    15.12
## 2 México               Sonora 179355        9.2   2944840    16.42
## 3 México Coahuila de Zaragoza 151562        7.7   3146771    20.76
## 4 México              Durango 123317        6.3   1832650    14.86
## 5 México               Oaxaca  93757        4.8   4132148    44.07
## 6 México           Tamaulipas  80249        4.1   3527735    43.96

Pero el indexado se puede hacer aún más «divertido», así que vamos a complejizar la forma como indexamos utilizando lo que hemos aprendido hasta ahora. Digamos que queremos saber cuáles son los estados que tienen más de 5 millones de habitantes.

data[which(data$Población >= 5000000), ] # El which nos dará la posición de las filas que cumplen la condición
##      País           Estado  Area Superficie Población Densidad
## 7  México          Jalisco 78588        4.0   8348151   106.23
## 10 México          Chiapas 73311        3.7   5543828    75.62
## 11 México         Veracruz 71826        3.7   8062579   112.25
## 13 México       Nuevo León 64156        3.3   5784442    90.16
## 21 México           Puebla 34306        1.7   6583278   191.90
## 22 México       Guanajuato 30608        1.6   6166934   201.48
## 25 México Estado de México 22351        1.1  16992418   760.25
## 32 México Ciudad de México  1495        0.1   9209944  6160.50

Por último, nos queda aprender a indexar los elementos de una lista. Como mencionamos anteriormente, las listas son algo así como objetos con «tres» dimensiones, para lo que necesitaremos una coordenada adicional de la forma LISTA[[Z]][X, Y], donde Z es equivalente a la posición del elemento dentro de la lista y se debe escribir con doble llave, mientras que, X e Y son las posiciones relativas que nos interesan de un elemento listado según sus dimensiones (si el elemento es unidimensional solo usamos X, si es bidimensional usamos X e Y).

Pero llevémoslo a la práctica para que quede más claro. Vamos a reutilizar la lista k que habíamos creado para listar todas las variables que fuimos generando al comienzo.

k
## [[1]]
## [1] 100
## 
## [[2]]
## [1] "Hola mundo"
## 
## [[3]]
## [1] "100"
## 
## [[4]]
## [1] TRUE
## 
## [[5]]
##  [1]  1  2  3  4  5  6  7  8  9 10
## 
## [[6]]
## [1] "México"  "lindo"   "y"       "querido"
## 
## [[7]]
##   [1]   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
##  [19]  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36
##  [37]  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54
##  [55]  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72
##  [73]  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90
##  [91]  91  92  93  94  95  96  97  98  99 100
## 
## [[8]]
##  [1]   0   5  10  15  20  25  30  35  40  45  50  55  60  65  70  75  80  85  90
## [20]  95 100
## 
## [[9]]
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    6   11   16   21
## [2,]    2    7   12   17   22
## [3,]    3    8   13   18   23
## [4,]    4    9   14   19   24
## [5,]    5   10   15   20   25
## 
## [[10]]
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    2    3    4    5
## [2,]    6    7    8    9   10
## [3,]   11   12   13   14   15
## [4,]   16   17   18   19   20
## [5,]   21   22   23   24   25

Como vemos, esta lista almacena números, cadenas de texto, vectores y matrices, y es lo que mencionamos anteriormente, una lista no está limitada a un mismo tipo de datos. De esta lista queremos extraer por ejemplo el texto «Hola mundo» del elemento en la posición 2, la palabra «lindo» del vector en la posición 6, y la última fila de la matriz en la posición 9.

k[[2]] # Indexamos todo el elemento de la posición 2 con doble llave
## [1] "Hola mundo"
k[[6]][2] # Indexamos el elemento en la posición 6 y luego el valor en la posición 2
## [1] "lindo"
k[[9]][5,] # Indexamos el elemento en la posición 9 y luego todos los valores de la fila 5
## [1]  5 10 15 20 25

5. Extrayendo datos sin indexar

Otra forma como podemos filtrar y extraer datos es mediante la función subset(), la cual nos permite establecer una condición directamente sin tener que indexar. Vamos de nuevo a extraer los estados de México que cumplan con tener más de 5 millones de habitantes.

subset(data, data$Población >= 5000000)
##      País           Estado  Area Superficie Población Densidad
## 7  México          Jalisco 78588        4.0   8348151   106.23
## 10 México          Chiapas 73311        3.7   5543828    75.62
## 11 México         Veracruz 71826        3.7   8062579   112.25
## 13 México       Nuevo León 64156        3.3   5784442    90.16
## 21 México           Puebla 34306        1.7   6583278   191.90
## 22 México       Guanajuato 30608        1.6   6166934   201.48
## 25 México Estado de México 22351        1.1  16992418   760.25
## 32 México Ciudad de México  1495        0.1   9209944  6160.50

6. Ciclos y condicionales

En R y al igual que en muchos otros lenguajes de programación, es posible crear ciclos de iteraciones pare repetir un procedimiento, o condicionales para la toma de decisiones según una serie de criterios.

Vamos a comenzar por los ciclos de iteraciones, los cuales se hacen mediante la función for(){}. Esta función requiere que especifiquemos una variable iterante y un rango de valores sobre los cuales iterar, de modo que al terminar cada ciclo la variable aumente en una unidad y se repita el procedimiento que hemos definido. Por ejemplo, vamos a transformar el área de los estados de México de km2 a campos de fútbol (un campo de fútbol = 0.00714 km2) y a llenar un nuevo vector con esta información.

areas.futbol <- rep(NA, # La función rep nos permite repetir algo N cantidad de veces (en este caso, NA = vacío)
                nrow(data)) # La función nrow nos da el número de filas de un objeto bidimensional (su análoga es ncol)
names(areas.futbol) <- data$Estado # La función names nos permite asignarle nombres a los elementos del vector
areas.futbol
##            Chihuahua               Sonora Coahuila de Zaragoza 
##                   NA                   NA                   NA 
##              Durango               Oaxaca           Tamaulipas 
##                   NA                   NA                   NA 
##              Jalisco            Zacatecas  Baja California Sur 
##                   NA                   NA                   NA 
##              Chiapas             Veracruz      Baja California 
##                   NA                   NA                   NA 
##           Nuevo León             Guerrero      San Luis Potosí 
##                   NA                   NA                   NA 
##            Michoacán              Sinaloa             Campeche 
##                   NA                   NA                   NA 
##         Quintana Roo              Yucatán               Puebla 
##                   NA                   NA                   NA 
##           Guanajuato              Nayarit              Tabasco 
##                   NA                   NA                   NA 
##     Estado de México              Hidalgo            Querétaro 
##                   NA                   NA                   NA 
##               Colima       Aguascalientes              Morelos 
##                   NA                   NA                   NA 
##             Tlaxcala     Ciudad de México 
##                   NA                   NA

# En R existe una diferencia entre NA (not available) y NaN (Not a Number), el primero indica un valor vacío, mientras que el segundo valores imposibles (0 / 0 = NaN).

for(n in 1:nrow(data)){ # La variable iterante puede llevar cualquier nombre
  areas.futbol[n] <- data[n, 3] / 0.00714 # Indexamos de manera dinámica y asignamos el resultado de la operación
}
areas.futbol
##            Chihuahua               Sonora Coahuila de Zaragoza 
##           34657563.0           25119747.9           21227170.9 
##              Durango               Oaxaca           Tamaulipas 
##           17271288.5           13131232.5           11239355.7 
##              Jalisco            Zacatecas  Baja California Sur 
##           11006722.7           10543977.6           10351400.6 
##              Chiapas             Veracruz      Baja California 
##           10267647.1           10059663.9           10007002.8 
##           Nuevo León             Guerrero      San Luis Potosí 
##            8985434.2            8907002.8            8562605.0 
##            Michoacán              Sinaloa             Campeche 
##            8207142.9            8151260.5            8054201.7 
##         Quintana Roo              Yucatán               Puebla 
##            6261204.5            5535574.2            4804761.9 
##           Guanajuato              Nayarit              Tabasco 
##            4286834.7            3901540.6            3463725.5 
##     Estado de México              Hidalgo            Querétaro 
##            3130392.2            2914986.0            1638515.4 
##               Colima       Aguascalientes              Morelos 
##             788095.2             786554.6             683333.3 
##             Tlaxcala     Ciudad de México 
##             562465.0             209383.8

# Los ciclos de iteraciones son muy útiles, pero para determinados procesos que requieren recorrer vectores o matrices no son la opción más eficiente. Con pocos datos el tiempo de cálculo es ridículamente pequeño, pero a medida que aumenta el volumen, los tiempos pueden crecer al orden de días, semanas y meses.

Ahora vamos a crear un condicional para reemplazar los espacios en los nombres de los estados por guiones bajos. El condicional if(){} se puede crear de manera independiente, pero como queremos validar la condición para los 32 estados tendremos que incluirlo dentro de un ciclo de iteraciones. Además, vamos a contar cuántos estados tienen nombres simples y cuántos nombres compuestos.

sim <- 0 # Creamos una variable para contar los nombres simples
com <- 0 # Creamos una variable para contar los nombres compuestos
for(z in 1:length(areas.futbol)){ # La función lenght nos da la longitud de un elemento
  if(grepl(" ", names(areas.futbol)[z]) == TRUE){ # La función grepl nos ayuda a buscar patrones en cadenas de texto
    names(areas.futbol)[z] <- gsub(" ", "_", names(areas.futbol)[z]) # La función gsub sirve reemplazar cadenas de texto
    com <- com + 1
  }else{
    sim <- sim + 1
  }
}
areas.futbol
##            Chihuahua               Sonora Coahuila_de_Zaragoza 
##           34657563.0           25119747.9           21227170.9 
##              Durango               Oaxaca           Tamaulipas 
##           17271288.5           13131232.5           11239355.7 
##              Jalisco            Zacatecas  Baja_California_Sur 
##           11006722.7           10543977.6           10351400.6 
##              Chiapas             Veracruz      Baja_California 
##           10267647.1           10059663.9           10007002.8 
##           Nuevo_León             Guerrero      San_Luis_Potosí 
##            8985434.2            8907002.8            8562605.0 
##            Michoacán              Sinaloa             Campeche 
##            8207142.9            8151260.5            8054201.7 
##         Quintana_Roo              Yucatán               Puebla 
##            6261204.5            5535574.2            4804761.9 
##           Guanajuato              Nayarit              Tabasco 
##            4286834.7            3901540.6            3463725.5 
##     Estado_de_México              Hidalgo            Querétaro 
##            3130392.2            2914986.0            1638515.4 
##               Colima       Aguascalientes              Morelos 
##             788095.2             786554.6             683333.3 
##             Tlaxcala     Ciudad_de_México 
##             562465.0             209383.8
paste("México tiene", com, "estados con nombre compuesto", sep = " ") # La función paste nos ayuda a concatenar elementos
## [1] "México tiene 8 estados con nombre compuesto"
paste("México tiene", sim, "estados con nombre simple", sep = " ")
## [1] "México tiene 24 estados con nombre simple"

7. Exportando y guardando datos

En R y R Studio podemos guardar y exportar nuestros datos para usarlos luego, en otro equipo o compartirlos con alguien más. Pero es importante saber qué es lo que estamos guardando ya que se pueden guardar tres tipos de archivos diferentes (scripts, sesiones o variables específicas).

Para guardar nuestro script basta con darle al botón de guardar (Save current document) del menú superior, o seguir la ruta File > Save, o usar el atajo de teclado CTRL + S. Si usamos R Studio, el color del título de la pestaña donde tenemos nuestro script nos indica de manera explícita si tiene cambios que no hemos guardado (color rojo y un asterisco), o por el contrario, si ya hemos guardado (color negro sin asterisco). Los scripts son archivos de texto plano que se guardan con extensión .R.

Por otro lado, también podemos guardar nuestra sesión, la cual contiene todas las variables y elementos que hemos creado, así no tendremos que volver a correr todo el script para generarlas la próxima vez. Para guardar una sesión vamos a darle al icono de guardar (Save workspace as) del menú superior de la ventana donde vemos las variables, en la pestaña Environment (las sesiones de R se guardan en formado .RData). También podemos guardar una sesión de manera programática usando el comando save.image().

save.image("D:/Daniel/Documents/Introducción a R/Introducción a R.RData")

Otra cosa que podemos guardar es una o varias variables específicas en formato .RData, usando el comando save(). Para el ejercicio guardaremos la variable data que contiene los datos para los estados de México, pero que hemos modificado para que incluya los valores de densidad poblacional. Vale la pena mencionar que, en este formato, los datos solo pueden ser abiertos en R.

save(data, # Indicamos la variable o variables (mediante vector) que queremos guardar
     file = "D:/Daniel/Documents/Introducción a R/Data Mexico.RData")

En caso de que queramos hacer el proceso contrario, es decir, importar, podemos hacerlo directamente desde el ícono que se encuentra del lado izquierdo de guardar en el menú de la ventana de variables, que corresponde a una carpeta con una flecha verde (Load workspace). O de manera programática.

load(file = "D:/Daniel/Documents/Introducción a R/Data Mexico.RData")

Por último, podemos exportar nuestros datos en un formato más universal como .csv (texto delimitado), el cual podremos abrir en algún procesador de hojas de cálculo como Excel.

write.csv(data, # Indicamos la variable que queremos guardar
          file = "D:/Daniel/Documents/Introducción a R/Datos (modificados).csv",
          row.names = FALSE) # Indicamos que no queremos que los datos tengan nombres de filas

8. Algunas operaciones básicas

Como ya hemos visto, R es una calculadora con esteroides donde podemos hacer casi cualquier cosa. A continuación, veremos algunas funciones adicionales que nos van a ayudar a hacer algunos cálculos y gráficos de estadística básica.

min(data$Población) # La función min nos regresa el valor mínimo de una serie de datos
## [1] 731391
max(data$Población) # La función max nos regresa el valor máximo de una serie de datos
## [1] 16992418
range(data$Población) # La función range nos regresa el rango de una serie de datos
## [1]   731391 16992418
mean(data$Población) # # La función mean nos regresa el valor promedio de una serie de datos
## [1] 3937938
sd(data$Población) # La función sd nos regresa la desviación estandar de una serie de datos
## [1] 3278009
var(data$Población) # La función var nos regresa la varianza de una serie de datos (= sd ^ 2)
## [1] 1.074534e+13
quantile(data$Población, # La función quantile nos regresa los cuantiles de una serie de datos
         probs = c(0, 0.25, 0.5, 0.75, 0.1)) # Con el argumento probs indicamos cuales cuantiles queremos
##      0%     25%     50%     75%     10% 
##  731391 1851651 3054892 4947592 1246208
cor(data$Población, data$Area, method = "spearman") # La función cor nos permite obtener un índice de correlación
## [1] 0.1829179
cor.test(data[ , 3], data[ , 5], method = "spearman") # La función cor.test nos permite probar correlación
## 
##  Spearman's rank correlation rho
## 
## data:  data[, 3] and data[, 5]
## S = 4458, p-value = 0.3149
## alternative hypothesis: true rho is not equal to 0
## sample estimates:
##       rho 
## 0.1829179
shapiro.test(data$Población) # La función shapiro.test nos permite probar el supuesto de normalidad
## 
##  Shapiro-Wilk normality test
## 
## data:  data$Población
## W = 0.78087, p-value = 1.818e-05
# Histograma de frecuencias
options(scipen = 10) # Elegimos el número decimales antes de que R use notación científica en la gráfica (no es necesario)
hist(data[, 5], # Definimos los datos, en este caso los valores de todas las filas de la columna «Población»
     xlab = "Población", # Etiqueta del eje X
     ylab = "Frecuencia", # Etiqueta del eje Y
     main = "Histograma de frecuencias", # Título principal
     col = "lightblue") # Color de las barras (puede ser nombre, HEX o RGB)

# Diagrama de caja y bigotes
options(scipen = 10) # Elegimos el número decimales antes de que R use notación científica en la gráfica (no es necesario)
boxplot(data[, 5], # Definimos los datos, en este caso los valores de todas las filas de la columna «Población»
        xlab = "México", # Etiqueta del eje X
        ylab = "Población", # Etiqueta del eje Y
        main = "Diagrama de caja y bigotes", # Título principal
        col = "#ffffff") # Color de las barras (puede ser nombre, HEX o RGB)

Vamos a terminar haciendo una regresión lineal, para lo cual cambiaremos de datos porque los que hemos estado utilizando no tienen variables con relaciones lineales muy buenas. Por defecto, R trae algunos conjuntos de datos precargados, de los cuales seleccionaremos el viejo y confiable iris, que contiene algunas medidas de ancho y longitud para las estructuras florales de tres especies de plantas.

data(iris) # Cargamos el conjunto de datos iris (viene precargado por defecto en R)
head(iris) # Validamos qué datos tenemos
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
## 4          4.6         3.1          1.5         0.2  setosa
## 5          5.0         3.6          1.4         0.2  setosa
## 6          5.4         3.9          1.7         0.4  setosa
iris.lm <- lm(Petal.Width ~ Petal.Length, data = iris) # Creamos el modelo de regresión lineal con la función lm
summary(iris.lm) # Obtenemos los coeficientes del modelo mediante la función summary
## 
## Call:
## lm(formula = Petal.Width ~ Petal.Length, data = iris)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.56515 -0.12358 -0.01898  0.13288  0.64272 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -0.363076   0.039762  -9.131  4.7e-16 ***
## Petal.Length  0.415755   0.009582  43.387  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2065 on 148 degrees of freedom
## Multiple R-squared:  0.9271, Adjusted R-squared:  0.9266 
## F-statistic:  1882 on 1 and 148 DF,  p-value: < 2.2e-16
# Grafica del modelo de regresión lineal
plot(iris$Petal.Length, # Definimos los valores del eje X
     iris$Petal.Width, # Definimos los valores del eje Y
     xlab = "Longitud del pétalo", # Etiqueta del eje X
     ylab = "Ancho del pétalo", # Etiqueta del eje Y
     main = "Modelo de regresión lineal", # Título principal
     pch = 19, # Código de los símbolos (19 = circulo)
     col = "black") # Color de los símbolos
abline(iris.lm, # Agregamos la línea de tendencia con abline y el modelo
       col = "red", # Elegimos un color para la línea
       lwd = 2) # Establecemos el ancho de la línea

9. Ejercicio de práctica

Vamos a poner a prueba todo lo aprendido con un ejercicio simple, en el que tendrán que tomar unos datos en formato Excel (Squamata México.xlsx), curarlos, exportarlos como texto delimitado (.csv), cargarlos a R y generar un script para realizar lo que se pide a continuación.

  1. Crear una nueva columna que contenga los valores del logaritmo base 10 para la masa (en los datos identificada como Mass) de cada una de las especies.

  2. Describir estadísticamente los datos de masa para los reptiles de México (promedio, mediana, desviación estandar y todos los los deciles).

  3. Poner a prueba el supuesto de normalidad para la masa de todos los reptiles y si existe correlación estadísticamente significativa entre la masa y la longitud (en los datos identificada como Max SVL).

  4. Crear una nueva columna vacia que se llame Group, y mediante un ciclo de iteraciones asignarle el valor Snakes si el valor de la columna Monophyletic es igual a Serpentes, si no, asignar el valor Lizards.

  5. Realizar un histograma de frecuencias y un diagrama de caja y bigotes para el logaritmo base 10 de la masa de los lagartos y otro para la masa de las serpientes.

# Pista: Recuerden que los errores más comunes suelen estar asociados a los números o a espacios en blanco, además, para este ejercicio, la columna Species no es el problema.