Español - Null models in R

Anuncio
Introducción a Modelos Nulos: Ejercicio 1
Creado por: J. Sebastián Tello
Center for Conservation and Sustainable Development
Missouri Botanical Garden
Objetivo: El propósito de esta sesión es familiarizarse con algunas de las capacidades básicas de
R para realizar análisis de modelos nulos. En este ejercicio, vamos a ver algunas opciones que ya
han sido implementadas como funciones en R.
A pesar de que los modelos nulos han sido una herramienta importante en la ecología y la
evolución desde hace varias décadas, todavía hay pocos paquetes que tienen funciones capaces
de análisis de modelos nulos. Esto está cambiando, y es probable que en los próximos años
muchos nuevos modelos nulos esten disponibles como funciones en R. Como ejemplos de cómo
generar resultados de modelos nulos en R, vamos a utilizar dos tipos de análisis: patrones de coocurrencia y estructura filogenética de comunidades.
1. Patrones de co-ocurrencia y la competencia inter-específica
En este primer ejemplo, vamos a realizar un análisis de modelos nulos para buscar evidencia de
exclusión competitiva en la distribución de las especies a través de sitios o comunidades. Más
concretamente, vamos a poner a prueba la predicción de que si la competencia es importante en
la distribución de especies, entonces deberíamos observar bajos niveles de co-ocurrencia (es
decir, alta segregación) entre especies a traves de comunidades. Este tipo de análisis representa
una de las primeras importantes aplicaciones de los modelos nulos en ecología.
Vamos a empezar por eliminar todo de la sesión actual de R. Esto no es necesario, pero se
recomienda. De esta manera las cosas "viejas" no se pueden causar conlictos y genera problemas.
Haga esto sólo si está seguro de que usted no necesita nada en su sesión de R existente:
rm(list=objects())
A continuación, instalar (si es necesario) y cargar los paquetes vegan y bipartite que
necesitaremos más adelante:
install.packages(c("vegan","bipartite"))
library(vegan)
library(bipartite)
Abra el set de datos de ácaros de vegan:
data(mite)
1
Este conjunto de datos fue descrito por Borcard et al. (1992) y consiste en 70 muestras de musgo
donde se contaron y se identificaron los ácaros de la familia orbatidae. Las muestras se
recogieron en una zona 10x2.5m y provienen de una alfombra de musgo entre un bosque y un
lago en Quebec, Canadá. Se trata de una matriz de la composición de especies, donde las filas
son los sitios y las columnas son especies.
Vamos a examinar brevemente las propiedades de este conjunto de datos:
class(mite) # type of object
mite <- as.matrix(mite) # this transforms the data frame into a matrix, which
will simplify the code that follows
class(mite)
head(mite) # prints the first few rows of data – it shows that columns are
species and rows are sites. Cell entries are abundances.
dim(mite) # the dimensions of the matrix – it shows that we have 35 species
and 70 sites
Echemos un vistazo a la matriz en un gráfico. Observe la figura utiliza log + 1 valores de
abundancia. Colores rojos indican abundancias bajas; amarillo a blancos indican abundancias
altas:
image(t(log(mite+1)), axes=FALSE, ylab="sites", xlab="species")
Vamos a trabajar con sólo los datos de presencia / ausencia, así que vamos a crear una copia de
los datos que contiene sólo 1s (presencia) y 0s (ausencia):
mite.PA <- mite>0 # each element (cell) in "mite" is transformed to TRUE or
FALSE depending on whether its value is greater than 0 or not.
mode(mite.PA) <- "integer" # in R, TRUE = 1; FALSE = 0.
head(mite)
head(mite.PA)
par(mfrow=c(1,2)) # creates a window with panels for two figures
image(t(log(mite+1)), axes=FALSE, ylab="Sites", xlab="Species",
main="Abundance")
image(t(mite.PA), axes=FALSE, ylab="Sites", xlab="Species",
main="Presence/Absence")
Hay varias maneras de medir los niveles de co-ocurrencia en una matriz de composición, pero
uno de los índices más utilizados en la literatura es el “C-score”, el cual vamos a utilizer en este
ejemplo como estadístico que mide co-ocurrencia. Para calcular la C-score, primero hay que
2
calcular el número de "unidades de tablero de ajedrez" (checkererboard units) para cada par de
especies. Una unidad de tablero de ajedrez es una sub-matriz de 2x2, donde la especie A está
presente en el sitio 1, pero no en el sitio 2, mientras que las especies B está presente en el sitio 2,
pero no en el sitio 1. Tal sub-matriz se vería así:
A
B
1
1
0
2
0
1
El C-score es simplemente el promedio de unidades de tablero de ajedrez entre todos los pares de
species. Como tal, el C-score es una medida de la segregación, e inversamente relacionada con la
co-ocurrencia. La Función C.score en el paquete bipartite calcula este valor para una matriz de
presencia/ausencia. Vamos a calcular el C-score en los datos empíricos de las comunidades de
ácaros:
emp.C <- C.score(web=mite.PA, normalise=FALSE)
emp.C
En promedio, hay ~ 154 unidades de tablero de ajedrez para cada par de especies. Si la
competencia es importante en la distribución de las especies de ácaros en nuestros datos, las
especies co-existirian raramente con sus competidores conduciendo a un valor alto de C-score.
Un algoritmo de aleatorización puede ayudar a determinar si este valor es mayor o menor de lo
esperado en ausencia de interacciones entre especies.
En vegan, hay funciones que implementan varios algoritmos para aleatorizar una matriz
decomposición de especies. Vamos a comenzar con una prueba simple usando la función
commsimulator:
null.mite.PA <- commsimulator(x=mite.PA, method="r00")
head(null.mite.PA)
null.mite.PA tiene ahora el resultado de una única matriz aleatoria. Vamos a repetir el proceso un
par de veces, y comparar gráficamente las matrices aleatorias con la empírica. En la figura
creada a continuación, la matriz empírica está en el centro:
par(mfrow=c(3,3))
for(i in 1:9)
{
if(i!=5) matrix.i <- commsimulator(x=mite.PA, method="r00")
3
if(i==5) matrix.i <- mite.PA
image(t(matrix.i), axes=FALSE)
}
En todos los paneles, las columnas y las filas son las mismas. Nótese cómo ha cambiado la
distribución de las presencias.
En este ejemplo, el argumento method en commsimulator define el algoritmo utilizado para la
aleatorización. Estamos tratando de encontrar un algoritmo que elimina los efectos de las
interacciones entre especies en la distribución de las especies, en particular que elimina los
efectos de la competencia inter-especifica. También estamos tratando de mantener en los datos
de la estructura que no fue creada por la competencia. En este caso, dando el valor "r00" para el
argumento method, hemos utilizado un algoritmo de "filas aleatorias - columnas aleatorias". Este
algoritmo mantiene el número total de presencias (numero de 1s), pero estas presencias se
aleatorizan completamente a través de la matriz.
sum(mite.PA)
sum(null.mite.PA)
Como se puede ver, el número total de 1s en las matrices empírica y nula son los mismos.
Este algoritmo ciertamente destruye todos los efectos de la competencia que pueden causar una
relación entre la distribución de una especie y otra. Sin embargo, también elimina las diferencias
en ocupancia (número de sitios ocupados, totales de las columnas) entre las especies y las
diferencias en la riqueza de especies (totales de las filas) entre los sitios.
par(mfrow=c(1,2))
plot(colSums(mite.PA)~ colSums(null.mite.PA), ylab="Empirical Occupancy",
xlab="Null Occupancy")
plot(rowSums(mite.PA)~ rowSums(null.mite.PA), ylab="Empirical Richness",
xlab="Null Richness")
Como se puede ver, la ocupancia y la riqueza son diferentes en las matrices empírica y nula.
Los patrones de variación en la ocupancia y la riqueza podrían ser creados por procesos distintos
a la competencia, por lo que puede ser que desee utilizar un algoritmo que 1) aleatoriza la
distribución de las especies entre ellas, pero 2) conserva el número de sitios ocupados por cada
especie y el número de especies observadas en cada sitio. Hay varias maneras de hacer esto, pero
vamos a utilizar el algoritmo de "trial-swap" (method = "tswap").
En el sencillo ejemplo anterior, utilizamos la función commsimulator para crear matrices
aleatorias una por una. La función oecosimu es una función envoltura ("wrapper") para
4
commsimulator la cual se puede utilizar para calcular un número N de de matrices y extraer una
estadística en particular de cada uno.
C.score.2 <- function(x) C.score(web=x, normalise=FALSE) # this is a small
trick we need to calculate C.scores with the option normalize=FALSE.
null.mite.res.r00 <- oecosimu(comm=mite.PA, nestfun=C.score.2, method="r00",
nsimul=999, alternative="greater")
null.mite.res.tswap <- oecosimu(comm=mite.PA, nestfun=C.score.2,
method="tswap", nsimul=999, alternative="greater", burnin=100000,
thin=10000)
Para obtener más información sobre todas las opciones de algoritmos, vaya a la ayuda para la
función oecosimu.
Ahora, vamos a comparar los resultados empíricos y nulos generados por los dos algoritmos.
null.mite.res.r00
hist(null.mite.res.r00$oecosimu$simulated, breaks=30,
xlim=range(c(null.mite.res.r00$oecosimu$simulated,emp.C)),
border="darkorange2", col="darkorange2", ylab="Number of Iterations",
xlab="C-score", main="'r00' algorithm")
lines(y=c(-100, 1000), x=c(emp.C,emp.C), lwd=3, col="black")
El C-score empírico parece ser diferente de los valores nulos producidos por el algoritmo de
"r00", pero lo es en la dirección opuesta a la esperada por competencia – la co-ocurrencia parece
ser mayor de lo esperado por este modelo nulo. ¿Qué sucede con el algoritmo "tswap"?
null.mite.res.tswap
hist(null.mite.res.tswap$oecosimu$simulated, breaks=30,
xlim=range(c(null.mite.res.tswap$oecosimu$simulated,emp.C)),
border="darkorange2", col="darkorange2", ylab="Number of iterations",
xlab="C-score", main="'Trial swap' algorithm")
lines(y=c(-100, 1000), x=c(emp.C,emp.C), lwd=3, col="black")
Cuando se utiliza un algoritmo más restrictivo, los resultados cambian drásticamente. En este
caso, el C-score empírico parece ser significativamente diferente de lo esperado en la dirección
predicha – la co-ocurrencia es menor de lo esperado por este modelo nulo.
Para las comparaciones entre los resultados de diferentes conjuntos de datos (diferentes
localidades, grupos de organismos, etc), a menudo es útil calcular el tamaño del efecto (“effect
size”). Los tamaños del efecto se pueden interpretar típicamente como una medida de la fuerza
de la estructura en los datos y por lo tanto la fuerza de los procesos que quedan fuera de la
5
aleatorización en el algoritmo del modelo nulo. Hay dos valores típicamente usados como
tamaños del efecto: 1) la diferencia entre el valor empírico y la media de los valores nulos, y 2)
esta diferencia estandarizada por la variación (desviación estándar) de los valores nulos.
ES.r00 <- emp.C - mean(null.mite.res.r00$oecosimu$simulated)
ES.tswap <- emp.C - mean(null.mite.res.tswap$oecosimu$simulated)
SES.r00 <- ES.r00 / sd(null.mite.res.r00$oecosimu$simulated)
SES.tswap <- ES.tswap / sd(null.mite.res.tswap$oecosimu$simulated)
par(mfrow=c(1,2))
barplot(c(ES.r00, ES.tswap), ylab="Effect Size",
names.arg=c("'r00'","'tswap'"), col="darkorange2")
lines(x=c(-100, 100), y=c(0, 0), lwd=3, col="black")
barplot(c(SES.r00, SES.tswap), ylab="Standardized Effect Size",
names.arg=c("'r00'","'tswap'"), col="darkorange2")
lines(x=c(-100, 100), y=c(0, 0), lwd=3, col="black")
Al utilizar un modelo nulo, la elección del estadístico y el algoritmo es fundamental! Uno de los
principales beneficios de los modelos nulos es su flexibilidad. Sin embargo, esta misma
flexibilidad también puede ser un problema. Algoritmos deben ser considedos cuidadosamente a
la luz del mecanismo de interés. Si el algoritmo no restringe la aleatorización de los datos lo
suficiente, existe el riesgo de que más que el proceso de interés sea eliminado de los datos. Esto
podría conducir a un rechazo fácil de la hipótesis nula, pero no porque el proceso de interés es
importante en la estructuración de los datos. Por otro lado, si el algoritmo no eleatoriza los datos
lo suficiente, hay un grave riesgo de que el proceso de interés no se elimine completamente de
los datos. Esto puede conducir a un efecto significativo que no es detectado por el análisis (el
efecto Narcissus). En el caso particular de los patrones de co-ocurrencia, ha habido una
considerable cantidad de trabajo sobre las propiedades estadísticas de los modelos nulos.
Desafortunadamente, la mayoría de otros modelos nulos no han recibido el mismo nivel de
evaluación.
2. Estructura filogenética de comunidades
Modificado de Kembel 2010: Una introducción al paquete de picante
En los últimos ~ 15 años, datos filogenéticos se ha aprovechado para obtener información acerca
de los mecanismos ecológicos y evolutivos detrás del ensamblaje de comunidades. Una de las
principales formas en que esta información se ha utilizado es intentar separar los efectos de la
competencia interespecífica y filtros por hábitat. Suponiendo que las especies cercanamente
6
relacionadas tienen ecologías similares y explotan recursos parecidos, la competencia predice
que las especies que coexisten a escala local deben ser más alejadas filogenéticamente de lo
esperado por el ensamblaje aleatorio de comunidades. Por otro lado, el filtrado por hábitat
predice que las especies coexistentes deben estar más cercanamente relacionadas de lo esperado.
En este ejemplo, utilizaremos un análisis de modelo nulo para estudiar si las comunidades
ecológicas están compuestas de especies que son más o menos cercanamente relacionadas de lo
esperado por el ensamblaje aleatorio de comunidades.
Vamos a empezar abriendo de los paquetes y los datos. El paquete picante incluye un conjunto
de datos llamado phylocom. Este set de datos tiene algunos datos artificiales que se incluyen
también con el programa Phylocom.
install.packages("picante")
library(picante)
data(phylocom)
names(phylocom)
Picante utiliza el formato phylo implementado en el paquete ape para representar relaciones
filogenéticas entre taxones. Si usted tiene una filogenia en formato Newick o Nexus puede ser
importado en R con las funciones read.tree o read.nexus.
phy <- phylocom$phylo
plot(phy)
Picante utiliza el mismo formato de datos de comunidades que el paquete vegan - un marco de
datos o matriz con sitios/muestras en las filas y taxones (especies) en las columnas. Los
elementos de esta matriz o marco de datos deben ser valores numéricos que indican la
abundancia o la presencia / ausencia (1/0) de taxones.
comm <- phylocom$sample
head(comm)
image(t(log(comm +1)), axes=FALSE, ylab="Sites", xlab="Species")
Hay muchas medidas de la estructura filogenética de las comunidades. Algunos de los más
utilizados son MPD - la distancia filogenética media por parejas entre todas las especies en cada
comunidad, y MNTD - la distancia filogenética media que separa a cada especie en la comunidad
de su pariente más cercano. En picante, las funciones mpd y mntd pueden ser utilizadas para
calcular estos valores.
7
Antes de utilizar estas funciones, sin embargo, tenemos que transformar la filogenia en una
matriz de distancias filogenéticas utilizando la función cophenetic:
phydist <- cophenetic(phy)
phydist
emp.mpd <- mpd(samp=comm, dis=phydist, abundance.weighted=FALSE)
emp.mpd
emp.mntd <- mntd(samp=comm, dis=phydist, abundance.weighted=FALSE)
emp.mntd
Tenga en cuenta que hay un valor para cada sitio / comunidad (es decir, para cada fila de la
matriz comm). Tenga en cuenta también que, dado que las funciones mpd y mntd pueden utilizar
cualquier matriz de distancia como entrada, podríamos calcular fácilmente medidas de estructura
funcional de comunidades sustituyendo la matriz de distancias filogenética con una matriz de
distancias de caracteres.
Para averiguar si estos valores empíricos de MPD y MNTD son diferentes de lo esperado por el
ensamblaje aleatorio de las comunidades (y la distribución aleatoria de las especies), tenemos
que llevar a cabo un análisis de modelos nulos. Las funciones ses.mpd y ses.mntd en el paquete
picante hacen esto:
ses.mpd.result <- ses.mpd(samp=comm, dis=phydist, null.model="trialswap",
abundance.weighted=FALSE, runs=999, iterations=100000)
ses.mntd.result <- ses.mntd (samp=comm, dis=phydist, null.model="trialswap",
abundance.weighted=FALSE, runs=999, iterations=100000)
En estos ejemplos, tenga en cuenta que hemos utilizado otra vez el algoritmo trial-swap, el cual
aleatoriza la distribución de las especies, pero mantiene la riqueza por comunidad y la ocupancia
por especie. Para obtener información sobre todos los algoritmos posibles, consulte la ayuda R
para estas funciones.
ses.mpd.result
ses.mntd.result
Estos resultados para cada comunidad incluyen los valores observados, los valores medios
producidos por el modelo nulo, y el valor de p que indica si existe una diferencia significativa
8
entre los valores observados y los esperados por la hipótesis nula (ensamblaje aleatorio de
comunidades). Algunas comunidades muestran diferencias significativas con respecto al azar, y
en la mayoría de los casos los valores observados son inferiores a la hipótesis nula. Esto sugiere
que las especies de estas comunidades están más relacionados de lo esperado por la distribución
aleatoria de las especies.
Otra forma de crear distribuciones al azar (con respecto a la filogenia) es aleatorizar los nombres
de taxones vinculados con las puntas del árbol filogenético. Este algoritmo es también una
opción en estas funciones:
ses.mpd.result.tip.rand <- ses.mpd(samp=comm, dis=phydist,
null.model="taxa.labels", abundance.weighted=FALSE, runs=999)
ses.mntd.result.tip.rand <- ses.mntd (samp=comm, dis=phydist,
null.model="taxa.labels", abundance.weighted=FALSE, runs=999)
ses.mpd.result.tip.rand
ses.mntd.result.tip.rand
Las funciones ses.mpd y ses.mntd también producen medidas del tamaño del efecto
estandarizado (SES) de la estructura filogenética de las comunidades (mpd.obs.z o mntd.obs.z en
el resultado de la función). Estos tamaños del efecto estandarizados están calculados como (1) la
diferencia entre las distancias filogenéticas en las comunidades observadas frente a las
comunidades nulas, y (2) esos valores divididos por las desviaciones estándar de las distancias
filogenéticas en los datos nulos.
par(mfrow=c(2,2))
barplot(ses.mpd.result$mpd.obs.z, ylab="MPD Standardized Effect Size",
names.arg=rownames(comm), col="darkorange2", main="Trial-swap Algorithm")
lines(x=c(-100, 100), y=c(0, 0), lwd=3, col="black")
barplot(ses.mntd.result$mntd.obs.z, ylab="MNTD Standardized Effect Size",
names.arg=rownames(comm), col="darkorange2", main="Trial-swap Algorithm ")
lines(x=c(-100, 100), y=c(0, 0), lwd=3, col="black")
barplot(ses.mpd.result.tip.rand$mpd.obs.z, ylab="MPD Standardized Effect
Size", names.arg=rownames(comm), col="darkorange2", main="Tip
Randomization Algorithm")
lines(x=c(-100, 100), y=c(0, 0), lwd=3, col="black")
9
barplot(ses.mntd.result.tip.rand$mntd.obs.z, ylab="MNTD Standardized Effect
Size", names.arg=rownames(comm), col="darkorange2", main="Tip
Randomization Algorithm")
lines(x=c(-100, 100), y=c(0, 0), lwd=3, col="black")
Valores positivos de SES indican “segregación filogenética”, o una mayor distancia filogenética
entre las especies coexistentes de lo esperado por el azar. Valores negativos de SES indican
“agregación filogenética”, o distancias filogenéticas entre las especies coexistentes más pequeñas
de lo esperado por el azar. Generalmente se considera que MPD es más sensibles a los patrones
de agregación o segregación filogenética a nivel de todo el árbol, mientras que MNTD es más
sensible a las patrones de agregación o segregación cerca de las puntas de la filogenia. Por
ejemplo, la comunidad 'clump4' contiene especies que se distribuyen al azar en todo el árbol
(SES de MPD cercano a cero), pero las especies están filogenéticamente agrupadas hacia las
puntas (SES negativo de MNTD). Todas estas medidas pueden incorporar información de
abundancia utilizando el argumento abundance.weighted. Esto va a cambiar la interpretación de
estos medidas de distancias filogenéticas del promedio por especie a el promedio por individuos.
10
Descargar