Introducción al análisis de datos ecológicos en R (2)

Seminario 2, Ecología I

Grado en Biología

Profesor
Departamento

Ecología e Hidrología, Facultad de Biología

Fecha de publicación

8 de noviembre de 2023

Fecha de modificación

15 de marzo de 2024

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:

setwd( "C:/Users/FENIX/Desktop/eco1s2" )

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:

aves2[ c( 1 : 5, 10, 20, 140 ), -c( 1 : 4, 8 : 12, 17 ) ]

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
aveslitoral
dim( 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:

aves2$bosque >= 75
aves2$bosque >= 75 -> aves2$bosque2
head( 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:

aves2$bosque3 <- NA
for ( 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

1. Importación de datos en R

Usando la función read.table:

  • ¿Qué efecto tiene la omisión del argumento header = TRUE en la importación del archivo loc.csv con la función read.table?
  • No se importadan correctamente los nombres de las variables:

    head( read.table( "loc.csv", sep = ";" ) )
          V1      V2      V3      V4
    1   UTMx    UTMy  sierra litoral
    2 691500 4164500 Cenizas       1
    3 692500 4164500 Cenizas       1
    4 693500 4163500 Cenizas       1
    5 693500 4163500 Cenizas       1
    6 647500 4161500 Moreras       1
  • ¿Qué efecto tiene la omisión del argumento dec = "," en la importación del archivo amb.txt?
  • La variable temperatura se importa como una variable de caracteres:

    str( read.table( "amb.txt" ) )
    'data.frame':   140 obs. of  4 variables:
     $ altitud      : int  203 166 137 158 278 270 118 42 409 445 ...
     $ precipitación: int  386 376 368 366 321 322 298 285 357 362 ...
     $ temperatura  : chr  "17,2" "17,4" "17,5" "17,4" ...
     $ bosque       : int  51 52 35 56 26 17 0 0 33 9 ...
2. Manejo de tablas y transformaciones
  • Selecciona los transectos del objeto aves2 localizados a altitudes menores de 500 m s. n. m. y con superficies de bosque por debajo del 50 %. ¿Qué dimensiones tiene esta nueva tabla de datos?
  • En R:

    dim( subset( aves2, aves2$altitud < 500 & aves2$bosque < 50 ) )
    [1] 27 18
  • Selecciona los transectos del objeto aves2 en los que se haya registrado conjuntamente la presencia de Carduelis carduelis, Parus major y Sylvia undata.
  • En R:

    subset( aves2, aves2$Carduelis.carduelis > 0 & aves2$Parus.major > 0 & aves2$Sylvia.undata > 0 )
        Aegithalos.caudatus Carduelis.carduelis Fringilla.coelebs
    46                    7                   7                 5
    87                    1                   2                 0
    114                   0                   4                 2
        Lophophanes.cristatus Loxia.curvirostra Parus.major Periparus.ater
    46                      2                 0           1              3
    87                      3                 0           1              0
    114                     3                 6           2              0
        Serinus.serinus Sylvia.melanocephala Sylvia.undata altitud precipitación
    46                5                    0             2     400           341
    87                0                    1             6     632           351
    114               1                    5             2     753           350
        temperatura bosque   UTMx    UTMy              sierra litoral
    46         16.6     99 663500 4196500 Puerto de la Cadena       0
    87         15.1      7 637500 4255500            Picarcho       0
    114        14.4     53 655500 4278500           Magdalena       0
3. Representaciones gráficas
  • Representa la distribución de la abundancia de Lophophanes cristatus sobre el mapa de la Región de Murcia.
  • En R:

    plot( vect( murcia ) )
    plot( avesmap, add = TRUE, col = 6, cex = aves2$Lophophanes.cristatus, alpha = 0.2 )

Bibliografía

Descripción de los objetos

Utiliza la función info. Por ejemplo: info("aves") o info( "murcia" ).