Vimos en la introducción a MLE en qué consiste el likelihood de un set de datos y cómo podemos buscar combinaciones de parámetros que maximicen la probabilidad de nuestros datos. Cuando tenemos modelos con varios parámetros, claramente no podemos recurrir a búsquedas gráficas o de fuerza bruta como en los ejemplos que vimos antes. Entonces, tenemos que hacer uso de los algoritmos de búsqueda que están disponibles en R, por ejemplo con la función optim. Primero tenemos que definir una función para “optimizar”. Para la función optim, optimizar quiere decir encontrar el mínimo. Además, tenemos que definir por dónde empezar a buscar y con qué método. Veamos por ejemplo cómo haríamos con el ejemplo de la remoción de frutos de quintral:
url <- "https://sites.google.com/site/modelosydatos/quintral.txt"
quintral <- read.table(url, header = TRUE)
# escribimos una función para el - log likelihood
binNLL <- function(theta, k, N) {
-sum(dbinom(k, prob = theta, size = N, log = TRUE))
}
O1 <- optim(fn = binNLL, par = list(theta = 0.5), N = quintral$Frutos, k = quintral$Removidos,
method = "BFGS")Cuando ejecutamos esta función, en este caso aparecen algunos “warnings” de NaNs producidos en dbinom pero nada de qué preocuparse (esto ocurre cuando optim prueba algún valor de tasa de remoción que no tiene sentido, por ejemplo -0.1). El objeto de salida de optim, que en este caso se llama O1 (por falta de creatividad) contiene:
$par, el valor óptimo del parámetro “theta”$value, el valor del log likelihood para $par$counts, hay algunas cosas crípticas acerca de cómo se desarrolló la búsquedaHay que asegurarse de que el algoritmo haya convergido ( $convergence = 0 )
La función optim en R es una función general para optimización, pero para MLE nos conviene usar mle2 desarrollada por Ben Bolker y parte del paquete bbmle. En mle2 la función de negative log-likelihood se llama “minuslogl” en vez de “fn” (en optim).
library(bbmle)
m1 <- mle2(minuslogl = binNLL, start = list(theta = 0.5), data = list(N = quintral$Frutos,
k = quintral$Removidos))En m1 tenemos la función usada, los valores de los coeficientes y el log-likelihood estimado. Podemos ver más detalles con summary(m1)
summary(m1)## Maximum likelihood estimation
##
## Call:
## mle2(minuslogl = binNLL, start = list(theta = 0.5), data = list(N = quintral$Frutos,
## k = quintral$Removidos))
##
## Coefficients:
## Estimate Std. Error z value Pr(z)
## theta 0.5967997 0.0099246 60.133 < 2.2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## -2 log L: 717.3864
Ahora tenemos errores standares y valores de ‘p’ para los coeficientes (poniendo a prueba la hipótesis nula de que el coeficiente es igual a cero, cosa que en este caso no tiene mucho sentido). También aparece la devianza (-2 log-likelihood). Más adelante veremos como usar éstas y otras salidas de mle2 para obtener intervalos de confianza y hacer comparaciones entre modelos.
Una vez que ajustamos el modelo es natural preguntarse qué tan bueno es el modelo para representar el proceso ecológico que nos interesa. Una forma rústica pero efectiva para hacer esta evaluación es graficar la distibución de valores observados con los predichos por el modelo:
k <- quintral$Removidos
hist(k, breaks = 40, freq = FALSE, ylim = c(0, 0.15), main = "")
points(0:50, dbinom(0:50, size = round(mean(quintral$Frutos)), prob = m1@coef),
pch = 21, bg = "gray")Una limitación de la comparación que acabamos de hacer es que graficamos la distribución de valores de frutos removidos para el valor promedio de frutos disponibles, de manera que estamos subestimando la variablidad en los datos que se debe a que el número de frutos disponibles en las plantas muestreadas es variable. Para hacer una mejor comparación podemos usar un bootstrap (remuestreo):
nboot <- 1000
res <- matrix(0, length(quintral$Frutos), nboot)
for (i in 1:nboot) {
n <- sample(quintral$Frutos, length(quintral$Frutos), replace = TRUE)
res[, i] <- rbinom(length(quintral$Frutos), size = n, prob = m1@coef)
}
hist(k, breaks = 40, freq = FALSE, ylim = c(0, 0.15), main = "")
lines(density(res), lwd = 2)Pregunta (1): ¿Qué se puede decir sobre el ajuste del modelo a los datos?
which para separar los datos correspondientes a cada caso.Natacha Chacoff estudió la deposición de polen en flores de pomelo en función del número de visitas de abejas. El trabajo terminado se puede ver aquí
apis <- read.table("https://sites.google.com/site/modelosydatos/polen_Apis.csv",
header = T, sep = ",")
plot(apis$visitas, apis$granos, xlab = "Número de Visitas", ylab = "Granos de Polen Depositados",
pch = 21, bg = "gray", cex.lab = 1.3, las = 1)
title(main = expression(paste("Eficiencia de ", italic(Apis ~ mellifera))))Podemos tratar de descibir esta relación entre visitas y deposición de polen como una función lineal asumiendo una distribución de Poisson para los granos de polen. Una forma de escribir este modelo es:
\[ \begin{aligned} & y_i \sim \textrm{Poisson}(\lambda_i) \\ & \lambda_i = \beta_0 + \beta_1 \times v_i\\ & \textrm{para } i \textrm{ de } 1 \textrm{ a } n\\ \end{aligned} \]
Donde \(y\) son las observaciones (número de granos de polen depositados) y \(v\) la variable predictora o la “covariable” (número de visitas de abejas) y los subíndices \(i\) indican cada una de las \(n\) observaciones. El script que sigue define y ajusta este modelo usando mle2.
poislin <- function(pars) {
b0 <- pars[1]
b1 <- pars[2]
lambda <- b0 + b1 * visitas
-sum(dpois(granos, lambda, log = TRUE))
}
parnames(poislin) <- c("b0", "b1")
fit.lin <- mle2(poislin, start = c(b0 = 10, b1 = 3), data = list(visitas = apis$visitas,
granos = apis$granos))
summary(fit.lin)## Maximum likelihood estimation
##
## Call:
## mle2(minuslogl = poislin, start = c(b0 = 10, b1 = 3), data = list(visitas = apis$visitas,
## granos = apis$granos))
##
## Coefficients:
## Estimate Std. Error z value Pr(z)
## b0 14.87137 0.36203 41.078 < 2.2e-16 ***
## b1 6.85330 0.26371 25.988 < 2.2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## -2 log L: 5416.507
plot(apis$visitas, apis$granos, xlab = "Número de Visitas", ylab = "Granos de Polen Depositados",
pch = 21, bg = "gray", cex.lab = 1.3, las = 1)
title(main = expression(paste("Eficiencia de ", italic(Apis ~ mellifera))))
x = 0:9 # vector de valores de referencia
lines(x, fit.lin@coef[1] + x * fit.lin@coef[2], lwd = 3)Pregunta (2): ¿Qué inconvenientes podríamos enocontrar tratando de ajustar este modelo asumiendo que las observaciones tienen una distribución de Poisson? ***
Pensando un poco sobre modelo, quizás sea esperable que en algún momento la cantidad de polen por estigma se sature ya que no podemos amontonar infinitos granos de polen. Cambiar el modelo anterior a un modelo de saturación no es muy complicado:
poissat <- function(pars) {
y0 <- pars[1]
a <- pars[2]
b <- pars[3]
lambda <- y0 + a * (1 - exp(-b * visitas))
-sum(dpois(granos, lambda, log = TRUE))
}
parnames(poissat) <- c("y0", "a", "b")
fit.sat <- mle2(poissat, start = c(y0 = 10, a = 41, b = 0.16), data = list(visitas = apis$visitas,
granos = apis$granos))
summary(fit.sat)## Maximum likelihood estimation
##
## Call:
## mle2(minuslogl = poissat, start = c(y0 = 10, a = 41, b = 0.16),
## data = list(visitas = apis$visitas, granos = apis$granos))
##
## Coefficients:
## Estimate Std. Error z value Pr(z)
## y0 14.091487 0.363584 38.7572 < 2.2e-16 ***
## a 40.857524 4.488943 9.1018 < 2.2e-16 ***
## b 0.291066 0.052784 5.5143 3.502e-08 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## -2 log L: 5363.868
plot(apis$visitas, apis$granos, xlab = "Número de Visitas", ylab = "Granos de Polen Depositados",
pch = 21, bg = "gray", cex.lab = 1.3, las = 1)
title(main = expression(paste("Eficiencia de ", italic(Apis ~ mellifera))))
lines(x, fit.sat@coef[1] + fit.sat@coef[2] * (1 - exp(-fit.sat@coef[3] * x)),
lwd = 3)Pregunta (3): ¿Cómo ajustan estos modelos a los datos?
Pregunta (4): ¿Qué otros modelos podríamos probar?
sessionInfo()## R version 3.5.2 (2018-12-20)
## Platform: x86_64-apple-darwin15.6.0 (64-bit)
## Running under: macOS Mojave 10.14.3
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRlapack.dylib
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## attached base packages:
## [1] stats4 stats graphics grDevices utils datasets methods
## [8] base
##
## other attached packages:
## [1] bbmle_1.0.20 knitr_1.20
##
## loaded via a namespace (and not attached):
## [1] Rcpp_1.0.0 bookdown_0.8 lattice_0.20-38
## [4] digest_0.6.18 MASS_7.3-51.1 grid_3.5.2
## [7] formatR_1.5 magrittr_1.5 evaluate_0.12
## [10] stringi_1.2.4 rmarkdown_1.10.15 tools_3.5.2
## [13] stringr_1.3.1 numDeriv_2016.8-1 xfun_0.4
## [16] yaml_2.2.0 compiler_3.5.2 htmltools_0.3.6
Juan Manuel Morales. 6 de Septiembre del 2015. Última actualización: 2019-03-02