number_of_cameras <- 12
species <- "Raccoon"
camera_active <- TRUEMódulo 3: Estructuras de datos en R
Introducción
En el módulo anterior aprendimos que R almacena información en objetos.
Hasta ahora, la mayoría de nuestros objetos contenían solamente un valor:
Pero las bases de datos reales contienen mucha más información que un solo número o palabra.
R nos permite organizar múltiples valores utilizando diferentes estructuras de datos.
En este módulo nos enfocaremos en cuatro de las más comunes:
- vectores,
- matrices,
- listas,
- data frames.
Entender la diferencia entre estas estructuras hará mucho más sencillo comprender qué está haciendo R cuando comencemos a trabajar con bases de datos reales.
Comencemos
Abre tu proyecto intro-r-course y crea un nuevo script de R.
Guárdalo dentro de la carpeta scripts con el nombre:
module-03.R
Utilizaremos este script durante todo el módulo.
Vectores
Un vector es una de las estructuras de datos más básicas e importantes en R.
Un vector nos permite almacenar varios valores dentro de un mismo objeto.
Podemos crear un vector utilizando la función:
c()
La c significa combine (combinar).
Por ejemplo:
detections <- c(5, 12, 8, 0, 3)
detections[1] 5 12 8 0 3
En lugar de crear cinco objetos diferentes:
detection_1 <- 5
detection_2 <- 12
detection_3 <- 8
detection_4 <- 0
detection_5 <- 3podemos almacenar los cinco valores juntos:
detections <- c(5, 12, 8, 0, 3)Esto hace mucho más sencillo trabajar con varias observaciones relacionadas.
Vectores numéricos
Los vectores pueden contener valores numéricos:
camera_days <- c(30, 28, 30, 25, 29)
camera_days[1] 30 28 30 25 29
Podemos realizar operaciones directamente sobre todo el vector:
camera_days + 1[1] 31 29 31 26 30
o:
camera_days * 2[1] 60 56 60 50 58
R aplica la operación a cada elemento del vector.
También podemos utilizar funciones:
mean(camera_days)[1] 28.4
sum(camera_days)[1] 142
length(camera_days)[1] 5
La función length() nos indica cuántos elementos contiene el vector.
Vectores de tipo character
Los vectores también pueden contener texto:
species <- c(
"Raccoon",
"Coyote",
"Bobcat",
"White-tailed deer"
)
species[1] "Raccoon" "Coyote" "Bobcat"
[4] "White-tailed deer"
Y valores lógicos:
camera_active <- c(TRUE, TRUE, FALSE, TRUE)
camera_active[1] TRUE TRUE FALSE TRUE
Un vector atómico puede contener solamente un tipo básico de datos.
Por ejemplo, todos sus valores pueden ser numéricos, todos pueden ser de tipo character o todos pueden ser lógicos.
¿Qué ocurre si mezclamos diferentes tipos?
mixed_vector <- c(1, 2, "Raccoon", 4)
mixed_vector[1] "1" "2" "Raccoon" "4"
R convierte todos los valores a un tipo compatible.
Compruébalo:
class(mixed_vector)[1] "character"
Como el vector contiene texto, R también convirtió los números en valores de tipo character.
Esta conversión automática se conoce como coerción (coercion).
Si un objeto que esperabas que fuera numérico repentinamente aparece como character, revisa si alguno de sus valores contiene texto.
Crear secuencias
A veces no queremos escribir cada número individualmente.
Por ejemplo:
1:10 [1] 1 2 3 4 5 6 7 8 9 10
Podemos guardar esta secuencia como un objeto:
sampling_days <- 1:10
sampling_days [1] 1 2 3 4 5 6 7 8 9 10
La función seq() nos proporciona mayor control. Esta función tiene varios argumentos: from indica el número inicial, to el número final, length.out permite controlar la longitud del vector y by nos permite especificar el intervalo de la secuencia.
seq(from = 0, to = 30, by = 5)[1] 0 5 10 15 20 25 30
También podemos repetir valores utilizando rep(). El argumento times nos permite especificar el número de veces que se repetirá un valor o vector. Cuando utilizamos each, podemos controlar el número de veces que se repetirá cada valor dentro del vector.
rep("Camera", times = 5)[1] "Camera" "Camera" "Camera" "Camera" "Camera"
Estas funciones son útiles cuando necesitamos generar valores repetidos o secuenciales sin escribirlos manualmente.
Seleccionar elementos de un vector
A veces solamente queremos uno o algunos valores de un vector.
Podemos seleccionar elementos utilizando corchetes:
[]
Vamos a crear un vector:
detections <- c(5, 12, 8, 0, 3)Para seleccionar el primer valor:
detections[1][1] 5
Para seleccionar el tercero:
detections[3][1] 8
Podemos seleccionar varias posiciones:
detections[c(1, 3, 5)][1] 5 8 3
O una secuencia de posiciones:
detections[1:3][1] 5 12 8
R comienza a contar desde 1, no desde 0.
El primer elemento de un vector es:
x[1]También podemos excluir posiciones utilizando números negativos:
detections[-1][1] 12 8 0 3
Esto devuelve todos los valores excepto el primero.
Seleccionar valores utilizando una condición
¿Recuerdas los operadores relacionales del módulo anterior?
Podemos utilizarlos con vectores.
Por ejemplo:
detections > 5[1] FALSE TRUE TRUE FALSE FALSE
R devuelve un valor lógico para cada elemento.
Podemos utilizar esta condición dentro de los corchetes:
detections[detections > 5][1] 12 8
R devuelve solamente los valores que cumplen la condición.
Esta idea será extremadamente importante más adelante cuando comencemos a filtrar bases de datos.
Crea este vector:
camera_days <- c(30, 21, 29, 15, 30, 27)Después:
- Selecciona el segundo valor.
- Selecciona los primeros tres valores.
- Selecciona los valores en las posiciones 2, 4 y 6.
- Identifica cuáles valores son mayores que 25.
- Devuelve solamente los valores mayores que 25.
Matrices
Una matriz es una estructura de datos de dos dimensiones.
A diferencia de un vector, que tiene solamente una dimensión, una matriz tiene:
- filas,
- columnas.
Podemos crear una matriz utilizando la función matrix().
Por ejemplo:
camera_matrix <- matrix(
1:12,
nrow = 4,
ncol = 3
)
camera_matrix [,1] [,2] [,3]
[1,] 1 5 9
[2,] 2 6 10
[3,] 3 7 11
[4,] 4 8 12
De manera predeterminada, R llena las matrices por columnas.
Podemos cambiar este comportamiento utilizando:
camera_matrix <- matrix(
1:12,
nrow = 4,
ncol = 3,
byrow = TRUE
)
camera_matrix [,1] [,2] [,3]
[1,] 1 2 3
[2,] 4 5 6
[3,] 7 8 9
[4,] 10 11 12
Ahora los valores se llenan por filas.
Dimensiones de una matriz
Podemos revisar las dimensiones de una matriz utilizando:
dim(camera_matrix)[1] 4 3
Esto devuelve el número de filas y columnas.
También podemos utilizar:
nrow(camera_matrix)[1] 4
y:
ncol(camera_matrix)[1] 3
Crear matrices a partir de vectores
Podemos combinar vectores para crear una matriz.
Por ejemplo:
station_1 <- c(5, 2, 0)
station_2 <- c(3, 1, 4)
station_3 <- c(8, 0, 2)Podemos combinarlos por filas:
detection_matrix <- rbind(
station_1,
station_2,
station_3
)
detection_matrix [,1] [,2] [,3]
station_1 5 2 0
station_2 3 1 4
station_3 8 0 2
O por columnas:
cbind(
station_1,
station_2,
station_3
) station_1 station_2 station_3
[1,] 5 3 8
[2,] 2 1 0
[3,] 0 4 2
Las funciones son fáciles de recordar:
rbind = row bind
cbind = column bind
Nombrar filas y columnas
Las matrices pueden tener nombres para sus filas y columnas.
Por ejemplo:
rownames(detection_matrix) <- c(
"CT01",
"CT02",
"CT03"
)
colnames(detection_matrix) <- c(
"Raccoon",
"Coyote",
"Bobcat"
)
detection_matrix Raccoon Coyote Bobcat
CT01 5 2 0
CT02 3 1 4
CT03 8 0 2
Ahora nuestra matriz es mucho más fácil de interpretar.
Este tipo de estructura nos resultará familiar más adelante cuando trabajemos con historias de detección de cámaras trampa.
Seleccionar elementos de una matriz
Como las matrices tienen dos dimensiones, debemos especificar:
[fila, columna]
Por ejemplo:
detection_matrix[1, 2][1] 2
significa:
fila 1, columna 2
Podemos seleccionar una fila completa:
detection_matrix[1, ]Raccoon Coyote Bobcat
5 2 0
O una columna completa:
detection_matrix[, 2]CT01 CT02 CT03
2 1 0
También podemos utilizar los nombres:
detection_matrix["CT01", "Coyote"][1] 2
o:
detection_matrix[, "Raccoon"]CT01 CT02 CT03
5 3 8
Una forma útil de recordar cómo seleccionar elementos de una matriz es:
objeto[fila, columna]
La coma separa las dos dimensiones.
También podemos seleccionar los valores de la diagonal utilizando diag:
diag(detection_matrix)[1] 5 1 2
Eliminar la fila 1 y seleccionar la columna 2:
detection_matrix[-1,2]CT02 CT03
1 0
Eliminar las filas 1 y 4, y seleccionar las columnas 1 y 3:
detection_matrix[c(-1,-4), c(1,3)] Raccoon Bobcat
CT02 3 4
CT03 8 2
Cuando utilizamos los selectores en el lado izquierdo de la definición de un objeto, podemos reemplazar los valores por aquellos que estamos especificando.
# Podemos reemplazar valores en matrices
# Reemplazar la segunda fila con 100, 200 y 300
detection_matrix[2,] <- c(100,200, 300)
detection_matrix Raccoon Coyote Bobcat
CT01 5 2 0
CT02 100 200 300
CT03 8 0 2
Las matrices contienen un solo tipo de datos
Al igual que los vectores, las matrices contienen solamente un tipo básico de datos.
Por ejemplo:
numeric_matrix <- matrix(1:9, nrow = 3)
numeric_matrix [,1] [,2] [,3]
[1,] 1 4 7
[2,] 2 5 8
[3,] 3 6 9
Pero si introducimos texto:
mixed_matrix <- cbind(
station = c("CT01", "CT02", "CT03"),
detections = c(5, 8, 2)
)
mixed_matrix station detections
[1,] "CT01" "5"
[2,] "CT02" "8"
[3,] "CT03" "2"
R convierte los valores numéricos a character porque toda la matriz debe compartir un mismo tipo de datos.
Esta limitación es una de las razones por las que los data frames son más útiles para la mayoría de las bases de datos.
Listas
Una lista es una estructura de datos muy flexible en R.
A diferencia de los vectores y matrices, las listas pueden contener objetos de diferentes tipos y estructuras.
Por ejemplo:
camera_information <- list(
station = "CT01",
active = TRUE,
detections = 25,
species = c("Raccoon", "Coyote", "Bobcat")
)
camera_information$station
[1] "CT01"
$active
[1] TRUE
$detections
[1] 25
$species
[1] "Raccoon" "Coyote" "Bobcat"
Esta única lista contiene:
- un valor de tipo
character, - un valor lógico,
- un valor numérico,
- un vector de tipo
character.
Una lista incluso puede contener matrices, data frames u otras listas.
Por ejemplo:
example_list <- list(
numbers = c(1, 2, 3),
matrix = matrix(1:4, nrow = 2),
message = "Hello R"
)
example_list$numbers
[1] 1 2 3
$matrix
[,1] [,2]
[1,] 1 3
[2,] 2 4
$message
[1] "Hello R"
Esta flexibilidad hace que las listas sean extremadamente comunes en R.
Muchas funciones y modelos estadísticos devuelven sus resultados como listas que contienen varios objetos diferentes.
Seleccionar elementos de una lista
Si los elementos tienen nombres, podemos utilizar $:
camera_information$station[1] "CT01"
camera_information$species[1] "Raccoon" "Coyote" "Bobcat"
También podemos utilizar corchetes dobles:
camera_information[[1]][1] "CT01"
o:
camera_information[["station"]][1] "CT01"
También puedes encontrar algo como:
camera_information[1]$station
[1] "CT01"
pero existe una diferencia importante.
camera_information[1] devuelve una lista que contiene el primer elemento.
En cambio:
camera_information[[1]][1] "CT01"
devuelve el contenido del primer elemento.
Esta diferencia puede ser confusa al principio y no necesitas memorizarla todavía.
Por ahora, la idea más importante es:
Las listas pueden almacenar muchos tipos diferentes de objetos juntos.
Data frames
Para el tipo de trabajo que realizaremos en este curso, los data frames probablemente serán la estructura de datos más importante.
Un data frame es una tabla rectangular con:
- filas,
- columnas.
Se parece mucho a una hoja de cálculo de Excel.
Sin embargo, a diferencia de una matriz, las diferentes columnas de un data frame pueden contener diferentes tipos de datos.
Por ejemplo, vamos a crear información de un muestreo hipotético con cámaras trampa:
station <- c(
"CT01",
"CT02",
"CT03",
"CT04"
)
habitat <- c(
"Forest",
"Forest",
"Pasture",
"Pasture"
)
camera_days <- c(
30,
28,
30,
27
)
active <- c(
TRUE,
TRUE,
FALSE,
TRUE
)Cada objeto es un vector.
Ahora podemos combinarlos en un data frame:
camera_data <- data.frame(
station,
habitat,
camera_days,
active
)
camera_data station habitat camera_days active
1 CT01 Forest 30 TRUE
2 CT02 Forest 28 TRUE
3 CT03 Pasture 30 FALSE
4 CT04 Pasture 27 TRUE
Ahora tenemos un solo objeto que contiene varias columnas.
Cada columna puede tener un tipo diferente:
class(camera_data$station)[1] "character"
class(camera_data$camera_days)[1] "numeric"
class(camera_data$active)[1] "logical"
El data frame también tiene su propia clase:
class(camera_data)[1] "data.frame"
R devuelve:
En la mayoría de las bases de datos ecológicas:
- las filas representan observaciones,
- las columnas representan variables.
Entender esta organización es extremadamente importante al preparar datos para un análisis.
En nuestro ejemplo:
station habitat camera_days active
son variables.
Cada fila representa una estación de cámara.
Inspeccionar un data frame
Antes de analizar una base de datos, siempre deberíamos revisar su estructura.
Algunas funciones útiles son:
camera_data station habitat camera_days active
1 CT01 Forest 30 TRUE
2 CT02 Forest 28 TRUE
3 CT03 Pasture 30 FALSE
4 CT04 Pasture 27 TRUE
head()
Muestra las primeras filas:
head(camera_data) station habitat camera_days active
1 CT01 Forest 30 TRUE
2 CT02 Forest 28 TRUE
3 CT03 Pasture 30 FALSE
4 CT04 Pasture 27 TRUE
str()
Muestra la estructura interna del objeto:
str(camera_data)'data.frame': 4 obs. of 4 variables:
$ station : chr "CT01" "CT02" "CT03" "CT04"
$ habitat : chr "Forest" "Forest" "Pasture" "Pasture"
$ camera_days: num 30 28 30 27
$ active : logi TRUE TRUE FALSE TRUE
dim()
Devuelve el número de filas y columnas:
dim(camera_data)[1] 4 4
nrow()
Devuelve el número de filas:
nrow(camera_data)[1] 4
ncol()
Devuelve el número de columnas:
ncol(camera_data)[1] 4
names()
Devuelve los nombres de las columnas:
names(camera_data)[1] "station" "habitat" "camera_days" "active"
Cuando cargues una nueva base de datos en R, una de las primeras cosas que deberías hacer es inspeccionarla.
Funciones como:
head()
str()
dim()
names()pueden ayudarte a identificar problemas antes de comenzar un análisis.
Seleccionar columnas de un data frame
Podemos acceder a una columna utilizando $:
camera_data$station[1] "CT01" "CT02" "CT03" "CT04"
camera_data$camera_days[1] 30 28 30 27
Observa qué ocurre cuando hacemos esto:
class(camera_data$camera_days)[1] "numeric"
La columna en sí misma es un vector.
Esta es una idea importante:
Un data frame es esencialmente una colección de vectores de la misma longitud organizados como columnas.
También podemos utilizar corchetes.
Por ejemplo:
camera_data[1, 2][1] "Forest"
significa:
fila 1, columna 2
Para seleccionar la primera fila:
camera_data[1, ] station habitat camera_days active
1 CT01 Forest 30 TRUE
Para seleccionar la segunda columna:
camera_data[, 2][1] "Forest" "Forest" "Pasture" "Pasture"
También podemos utilizar los nombres de las columnas:
camera_data[, "habitat"][1] "Forest" "Forest" "Pasture" "Pasture"
O seleccionar varias columnas:
camera_data[, c("station", "camera_days")] station camera_days
1 CT01 30
2 CT02 28
3 CT03 30
4 CT04 27
Más adelante aprenderemos formas más sencillas y legibles de seleccionar filas y columnas utilizando dplyr.
Por ahora, es importante entender cómo funcionan los data frames en R base.
Agregar una columna
Podemos agregar una nueva columna utilizando $.
Por ejemplo:
camera_data$elevation <- c(
105,
112,
98,
101
)
camera_data station habitat camera_days active elevation
1 CT01 Forest 30 TRUE 105
2 CT02 Forest 28 TRUE 112
3 CT03 Pasture 30 FALSE 98
4 CT04 Pasture 27 TRUE 101
La nueva columna debe tener el número correcto de valores.
Nuestro data frame tiene cuatro filas, por lo que la nueva columna necesita cuatro valores.
Podemos comprobar el número de filas utilizando:
nrow(camera_data)[1] 4
Cambiar valores
También podemos reemplazar valores específicos.
Por ejemplo:
camera_data$camera_days[2] <- 30
camera_data station habitat camera_days active elevation
1 CT01 Forest 30 TRUE 105
2 CT02 Forest 30 TRUE 112
3 CT03 Pasture 30 FALSE 98
4 CT04 Pasture 27 TRUE 101
O utilizando posiciones de fila y columna:
camera_data[2, "camera_days"] <- 30Esto puede ser útil, pero debemos tener cuidado al modificar manualmente nuestros datos originales.
Siempre que sea posible, evita modificar manualmente tu archivo de datos original.
Generalmente es mejor realizar las correcciones mediante código para que los cambios queden documentados y sean reproducibles.
Diferentes estructuras, diferentes propósitos
Vamos a resumir las cuatro estructuras que hemos visto.
| Estructura | Dimensiones | ¿Mismo tipo de datos? | Uso común |
|---|---|---|---|
| Vector | 1 | Sí | Almacenar una secuencia de valores |
| Matriz | 2 | Sí | Datos numéricos o rectangulares estructurados |
| Lista | 1 | No | Almacenar diferentes objetos juntos |
| Data frame | 2 | Diferentes tipos permitidos por columna | Almacenar bases de datos |
La estructura que elegimos depende del tipo de información que queremos almacenar.
Durante el resto del curso:
- trabajaremos constantemente con vectores,
- la mayoría de nuestras bases de datos serán data frames,
- ocasionalmente encontraremos matrices,
- y muchas funciones de R devolverán sus resultados como listas.
Cuando finalmente trabajemos con camtrapR, estas cuatro ideas volverán a aparecer.
Convertir entre estructuras de datos
R también proporciona funciones para convertir objetos de una estructura a otra.
Por ejemplo:
as.data.frame(detection_matrix) Raccoon Coyote Bobcat
CT01 5 2 0
CT02 100 200 300
CT03 8 0 2
convierte una matriz en un data frame.
Podemos guardar el resultado:
detection_data <- as.data.frame(detection_matrix)
class(detection_data)[1] "data.frame"
De manera similar:
as.matrix(camera_data) station habitat camera_days active elevation
[1,] "CT01" "Forest" "30" "TRUE" "105"
[2,] "CT02" "Forest" "30" "TRUE" "112"
[3,] "CT03" "Pasture" "30" "FALSE" " 98"
[4,] "CT04" "Pasture" "27" "TRUE" "101"
intenta convertir un data frame en una matriz.
Sin embargo, recuerda que una matriz solamente puede contener un tipo básico de datos.
Como camera_data contiene texto, números y valores lógicos, R necesitará convertir todo a un tipo común.
Prueba:
camera_matrix_2 <- as.matrix(camera_data)
camera_matrix_2 station habitat camera_days active elevation
[1,] "CT01" "Forest" "30" "TRUE" "105"
[2,] "CT02" "Forest" "30" "TRUE" "112"
[3,] "CT03" "Pasture" "30" "FALSE" " 98"
[4,] "CT04" "Pasture" "27" "TRUE" "101"
y después:
class(camera_matrix_2)[1] "matrix" "array"
Este es otro buen ejemplo de por qué es importante entender las estructuras de datos.
Una función puede convertir técnicamente un objeto, pero el resultado puede no tener la estructura o el tipo de datos que esperabas.
Práctica en casa
Esta actividad debería tomar aproximadamente 15–20 minutos.
Abre tu proyecto intro-r-course y crea un nuevo script llamado:
practice-03.R
Guárdalo dentro de la carpeta scripts.
Parte 1: Vectores
Crea estos objetos:
stations <- c("CT01", "CT02", "CT03", "CT04", "CT05")
detections <- c(12, 5, 0, 18, 7)
camera_days <- c(30, 28, 30, 25, 29)Después:
- Devuelve el tercer elemento de
stations. - Devuelve los primeros tres valores de
detections. - Determina cuáles valores de
detectionsson mayores que 10. - Devuelve solamente las detecciones mayores que 10.
- Calcula el promedio del número de días-cámara.
Parte 2: Data frame
Crea un data frame llamado survey_data utilizando:
stations[1] "CT01" "CT02" "CT03" "CT04" "CT05"
detections[1] 12 5 0 18 7
camera_days[1] 30 28 30 25 29
Después:
- Muestra el data frame.
- Utiliza
str()para inspeccionar su estructura. - Utiliza
dim()para encontrar sus dimensiones. - Utiliza
names()para obtener los nombres de las columnas. - Selecciona solamente la columna
detections. - Selecciona la información correspondiente a la segunda estación de cámara.
- Agrega una nueva columna llamada
habitatque contenga:
c("Forest", "Forest", "Pasture", "Forest", "Pasture")[1] "Forest" "Forest" "Pasture" "Forest" "Pasture"
- Utiliza nuevamente
str()y observa el tipo de cada columna.
# Práctica del Módulo 3
# ---------------------------
# Parte 1: Vectores
# ---------------------------
stations <- c(
"CT01",
"CT02",
"CT03",
"CT04",
"CT05"
)
detections <- c(
12,
5,
0,
18,
7
)
camera_days <- c(
30,
28,
30,
25,
29
)
# Tercera estación
stations[3][1] "CT03"
# Primeros tres valores de detecciones
detections[1:3][1] 12 5 0
# ¿Cuáles valores son mayores que 10?
detections > 10[1] TRUE FALSE FALSE TRUE FALSE
# Devolver solamente detecciones mayores que 10
detections[detections > 10][1] 12 18
# Promedio de días-cámara
mean(camera_days)[1] 28.4
# ---------------------------
# Parte 2: Data frame
# ---------------------------
survey_data <- data.frame(
stations,
detections,
camera_days
)
# Mostrar los datos
survey_data stations detections camera_days
1 CT01 12 30
2 CT02 5 28
3 CT03 0 30
4 CT04 18 25
5 CT05 7 29
# Inspeccionar la estructura
str(survey_data)'data.frame': 5 obs. of 3 variables:
$ stations : chr "CT01" "CT02" "CT03" "CT04" ...
$ detections : num 12 5 0 18 7
$ camera_days: num 30 28 30 25 29
# Dimensiones
dim(survey_data)[1] 5 3
# Nombres de las columnas
names(survey_data)[1] "stations" "detections" "camera_days"
# Seleccionar detecciones
survey_data$detections[1] 12 5 0 18 7
# Seleccionar la segunda estación de cámara
survey_data[2, ] stations detections camera_days
2 CT02 5 28
# Agregar hábitat
survey_data$habitat <- c(
"Forest",
"Forest",
"Pasture",
"Forest",
"Pasture"
)
# Inspeccionar el data frame actualizado
str(survey_data)'data.frame': 5 obs. of 4 variables:
$ stations : chr "CT01" "CT02" "CT03" "CT04" ...
$ detections : num 12 5 0 18 7
$ camera_days: num 30 28 30 25 29
$ habitat : chr "Forest" "Forest" "Pasture" "Forest" ...
Lo importante es que entiendas la diferencia entre:
- un vector,
- una matriz,
- una lista,
- y un data frame,
y que puedas reconocer cómo se utilizan filas, columnas y posiciones para acceder a la información.