setwd( "C:/Users/FENIX/Desktop/eco1s2" )
Introducción al análisis de datos ecológicos en R (2)
Seminario 2, Ecología I
Grado en Biología
Introducción
En este seminario realizaremos varios ejercicios, a través del uso de una tabla de datos reales, para aprender diversos procedimientos relacionados con la introducción y grabación en soporte informático de datos procedentes de estudios ecológicos, así como de su importación en R a partir de varios tipos de formatos de archivo (xlsx, texto plano, csv). Aprenderemos también a utilizar procedimientos y funciones para el manejo, y transformación de los datos contenidos en las tablas de datos.
Finalmente realizaremos representaciones gráficas de tipo comparativo entre variables, de relaciones entre variables biológicas y ambientales, y de carácter espacial. Puedes encontrar más información sobre estos aspectos en: Wilke (2019), R Core Team (2023) y Hijmans (2023).
Preparación
En primer lugar, descargaremos en nuestro ordenador varios archivos necesarios para el desarrollo de la práctica desde la URL https://webs.um.es/jfcalvo/eco1s2.zip. Una vez descargado el archivo descomprimiremos la carpeta que contiene (por ejemplo, en el Escritorio de Windows).
Una vez iniciado R es conveniente cambiar el directorio de trabajo a dicha carpeta (> Archivo > Cambiar dir…), de forma que podamos acceder directamente a los archivos descargados.
Alternativamente, podemos cambiar el directorio de trabajo directamente con la función setwd
. En el caso de los ordenadores de las aulas informáticas de la Universidad de Murcia, si descomprimimos eco1s2.zip
en un directorio en el Escritorio de Windows, por ejemplo:
También necesitaremos datos disponibles en el archivo de datos y funciones de la asignatura (eco1.RData
), que cargaremos desde el servidor:
load( url( "http://webs.um.es/jfcalvo/eco1.RData" ) )
Usando ls()
podemos ver los objetos cargados. Teclea info()
para más información. Puedes utilizar también la función info
para obtener la descripción de los objetos; por ejemplo: info( "aves" )
o info( "murcia" )
.
Por último, para realizar este seminario se necesita la instalación adicional de los paquetes de R xlsx
y terra
. Si no los tenemos instalados ejecutaremos:
install.packages( c( "xlsx", "terra" ) )
Una vez instalados las cargaremos en memoria:
library( xlsx )
library( terra )
Grabación de datos e importación en R
Comenzaremos introduciendo los datos proporcionados en papel en una hoja de cálculo disponible en la carpeta de archivos descargados (eco1s2.xlsx
). Los datos corresponden a las supuestas hojas de campo de un estudio real de 2017, en el que se realizaron conteos de individuos de especies de aves forestales en 140 transectos (itinerarios de censo de 1 km de longitud) en la Región de Murcia. Cada estudiante tiene asignados 3 o 4 itinerarios para su introducción en la hoja de cálculo.
Al abrir el archivo aparecerá la primera hoja del libro, en la que encontraremos una fila de cabecera (“número de transecto” y los nombres científicos de 10 especies de aves muestreadas en el estudio). En este caso debemos introducir datos en todas las celdas, por lo que hay que introducir también los ceros correspondientes a las especies no observadas en el transecto.
Una vez introducidos los datos, guardaremos el archivo con el mismo nombre y lo importaremos en R usando la función read.xlsx
. Hay que indicar el número de hoja del libro, en este caso la primera:
read.xlsx( "eco1s2.xlsx", sheetIndex = 1) -> datos
datos
La tabla de datos de aves forestales completa, con todos los transectos, está disponible en el objeto aves
del archivo eco1.Rdata
:
aves
Podemos examinar las características de ambos objetos con la función str
:
str( aves )
str( datos )
Para comprobar que hemos introducido los datos correctamente, podemos recurrir a las función colSums
, que realiza la suma de los datos por columnas de una tabla. Especificaremos en cada caso el número de los transectos introducidos, seleccionando las filas apropiadas. Así, si hubiésemos importado, por ejemplo, los datos de los transectos 32 y 36, usaríamos:
colSums( aves[ c( 32, 36 ), ])
Las sumas deben coincidir con las de nuestra hoja importada, excluyendo la primera columna donde figura el nombre (número) de cada transecto:
colSums( datos[ , -1 ] )
Si aparece algún NA
en nuestro objeto datos
será debido a la existencia de celdas vacías en la hoja importada, ya que la función read.xlsx
sustituye las celdas vacías por el valor NA
.
Existen varias funciones útiles para visualizar y obtener información resumida sobre las tablas de datos (además de str
). Cuando son muy grandes podemos visualizar cómodamente su estructura con la función head
, obtener sus dimensiones con dim
o consultar los nombres de sus variables con names
(o colnames
):
head( aves )
dim( aves )
names( aves )
Otras formas de importación
Existen diversas formas de importación de datos que se adecúan a una gran variedad de formatos de archivos. En esta sección veremos cómo importar archivos de texto sin formato o texto plano (ASCII, American Standard Code for Information Interchange), y archivos con formato csv (datos separados por comas, o por puntos y comas). Concretamente, importaremos en R los archivos amb.txt
y loc.csv
, que contienen información ambiental y de localización de los 140 transectos de la tabla de datos de aves forestales del apartado anterior.
El archivo amb.txt
es el resultado del procedimiento de “copiar y pegar” la hoja 2 del archivo eco1s2.xlsx
. Podemos visualizarlo, por ejemplo, con el Bloc de Notas de Windows. Por su parte el archivo loc.csv
es el resultado de guardar en formato csv la hoja 3 del archivo eco1s2.xlsx
. También podemos examinar su estructura abriéndolo con el Bloc de Notas.
La importación de archivos de texto en R se puede realizar con la función read.table
. Un aspecto muy importante para tener en cuenta es el separador de decimales de nuestros datos. Aunque en español se utiliza la coma, lo habitual es usar puntos (así es en R). Por tanto, en el caso del archivo amb.txt
, necesitamos usar el argumento dec = ","
:
read.table( "amb.txt", dec = ",") -> amb
dim( amb )
head( amb )
Como la primera columna no lleva cabecera, la función interpreta que corresponde al nombre de las filas, y la importación se realiza adecuadamente; en caso contrario, tendríamos que introducir el argumento header = TRUE
.
Para leer archivos csv se utilizan las funciones read.csv
(cuando el separador de datos es la coma), y read.csv2
(cuando el separador de datos es el punto y coma). En este caso usaremos read.csv2
. Como loc.csv
no contiene valores con decimales no es necesario utilizar el argumento dec
. Y aunque sí contiene cabecera, como las funciones read.csv
y read.csv2
incorporan por defecto el argumento header = TRUE
, tampoco es necesario especificarlo:
read.csv2( "loc.csv" ) -> loc
head( loc )
Alternativamente podemos usar también la función read.table
, especificando el separador con el argumento sep = ";"
:
read.table( "loc.csv", sep = ";", header = TRUE )
Manejo de tablas de datos
Para unir dos o más tablas usaremos la función data.frame
:
data.frame( aves, amb, loc ) -> aves2
head( aves2 )
Si por alguna circunstancia no pudiésemos importar los archivos amb.txt
o loc.csv
, para seguir realizando los ejercicios del seminario podemos cargar directamente la tabla aves2
con:
load( url( "http://webs.um.es/jfcalvo/eco1_aves2.RData" ) )
La selección de partes de la tabla se puede hacer indicando simultáneamente los índices de filas y columnas elegidas. Por ejemplo:
c( 1 : 5, 10, 20, 140 ), -c( 1 : 4, 8 : 12, 17 ) ] aves2[
Más habitual es la selección de partes elegidas en función, por ejemplo, de alguna de las variables. Así, si quisiéramos trabajar solo con los transectos de las sierras litorales, usaríamos:
subset(aves2, aves2$litoral == 1 ) -> aveslitoral
aveslitoraldim( aveslitoral )
Si quisiéramos usar más de una condición para la selección, emplearíamos operadores lógicos:
subset( aves2, aves2$bosque == 100 & aves2$temperatura > 15 )
subset( aves2, aves2$precipitación > 450 | aves2$sierra == "Espuña" )
subset( aves2, aves2$Loxia.curvirostra != 0 & aves2$Parus.major == 0 )
Transformaciones
La transformación de variables es una tarea habitual en la gestión de tablas de datos. Por ejemplo, si nos interesara identificar los transectos con una elevada proporción de bosque (≥ 75 %), podemos crear fácilmente una nueva variable cualitativa y añadirla a la tabla de datos:
$bosque >= 75
aves2$bosque >= 75 -> aves2$bosque2
aves2head( aves2 )
También se pueden realizar codificaciones más complejas, aunque el procedimiento es más laborioso y requiere unas sencillas líneas de programación en R. Por ejemplo, comprueba el efecto de las siguientes órdenes para una nueva codificación de la variable bosque
:
$bosque3 <- NA
aves2for ( i in 1 : 140 ) if ( aves2$bosque[ i ] <= 25 ) aves2$bosque3[ i ] <- "baja"
for ( i in 1 : 140 ) if ( aves2$bosque[ i ] > 25 & aves2$bosque[ i ] < 75 ) aves2$bosque3[ i ] <- "media"
for ( i in 1 : 140 ) if ( aves2$bosque[ i ] >= 75 ) aves2$bosque3[ i ] <- "alta"
head( aves2 )
Por lo que respecta a los datos de abundancia de las especies, una transformación muy común es convertirlos en datos de presencia/ausencia (unos y ceros), lo que permitiría calcular fácilmente el número de especies registrados en cada itinerario de censo:
data.frame( ifelse( aves > 0, 1, 0 ) ) -> avespres
head( avespres )
rowSums( avespres ) -> avespres$riqueza
head( avespres )
Representaciones gráficas
Las representaciones gráficas aportan información muy relevante sobre las características de los datos. Para datos de conteos son muy útiles los diagramas de barras que representan las frecuencias de números de individuos registrados en la muestra. Usaremos la función barplot
, aplicada sobre una tabla de frecuencias. Por ejemplo, para Parus major:
table( aves2$Parus.major )
barplot( table( aves2$Parus.major ) )
Podemos mejorar la figura añadiendo las leyendas de los ejes y un título:
barplot( table( aves2$Parus.major ), xlab = "Número de individuos", ylab = "Frecuencia", main = "Parus major" )
Más habituales son las representaciones de datos biológicos en función de variables ambientales. Por ejemplo, podemos examinar la distribución de la abundancia de Serinus serinus, en función de la altitud:
plot( Serinus.serinus ~ altitud, data = aves2 )
La representación puede realizarse utilizando distintos tipos de símbolos (pch
) y diferentes colores (col
), en función, por ejemplo, de los valores de una variable de tipo factor:
plot( Serinus.serinus ~ altitud, data = aves2, pch = 19, col = factor( litoral ) )
También podemos representar conjuntamente datos de dos o más especies (usando points
), e incluso añadir curvas polinómicas ajustadas a los datos (con lines
y smooth.spline
). Por ejemplo:
points( Fringilla.coelebs ~ altitud, data = aves2, pch = 19, col = 4 )
lines( smooth.spline( aves2$Serinus.serinus ~ aves2$altitud ) )
lines( smooth.spline( aves2$Fringilla.coelebs ~ aves2$altitud), col = 4 )
Si preferimos dos gráficas separadas, podemos modificar los parámetros de la ventana gráfica:
par( mfrow = c( 1, 2 ) )
plot( Serinus.serinus ~ altitud, data = aves2, pch = 19, cex = 1.2 )
lines( smooth.spline( aves2$Serinus.serinus ~ aves2$altitud ), lwd = 3 )
plot( Fringilla.coelebs ~ altitud, data = aves2, pch = 19, col = 4, cex = 1.2, ylim = c( 0, 30 ) )
lines( smooth.spline( aves2$Fringilla.coelebs ~ aves2$altitud), lwd = 3, col = 4 )
par( mfrow = c( 1, 1 ) )
Observa que en la figura anterior hemos cambiado también el grosor de las líneas (lwd
), el tamaño de los puntos (cex
) y la escala del eje y del segundo panel (ylim
).
Por otra parte, la comparación de las abundancias medias de las distintas especies entre sí puede realizarse utilizando la función boxplot
, que representa diagramas de caja (box plots):
boxplot( aves )
En un box plot se representan la mediana, el rango intercuartílico, el rango (máximo y mínimo) y los valores extremos (outliers) de las variables.
La figura anterior se puede mejorar utilizando diversos argumentos. Por ejemplo:
par( mar= c( 10.1, 4.1, 4.1, 2.1 ) )
boxplot( aves, las = 2, col = rainbow( 10 ) )
La función boxplot
también se puede usar en combinación con variables ambientales cualitativas:
boxplot( Serinus.serinus ~ litoral, data = aves2 )
boxplot( Serinus.serinus ~ litoral * (bosque > 50), data = aves2 )
En el ejemplo de las aves, al disponer de información espacial (coordenadas geográficas de los itinerarios), también podemos realizar representaciones gráficas en forma de mapas, usando funciones del paquete terra
. Primero representaremos el mapa con los límites de la Región de Murcia (objeto murcia
incluido en el archivo eco1.RData
), y sobre él la localización de los transectos (argumento add = TRUE
):
plot( vect( murcia ) )
vect( aves2, geom = c( "UTMx", "UTMy" ) ) -> avesmap
plot( avesmap, add = TRUE, cex = 0.5 )
Para visualizar la distribución de la abundancia de una determinada especie podemos representar el tamaño del punto de cada transecto en función del número de individuos de esa especie contados en cada transecto:
plot( avesmap, add = TRUE, col = 6, cex = aves2$Sylvia.undata, alpha = 0.2 )
Ejercicios
Bibliografía
Hijmans R (2023) _terra: Spatial Data Analysis_. R package version 1.7-55, https://CRAN.R-project.org/package=terra
R Core Team (2023) R Data Import/Export. Version 4.3.2. https://cran.r-project.org/
Wilke CO (2019) Fundamentals of Data Visualization: A Primer on Making Informative and Compelling Figures. O’Reilly.
Descripción de los objetos
Utiliza la función info
. Por ejemplo: info("aves")
o info( "murcia" )
.