library(tibble)
library(dplyr)
library(ggplot2)
En els models lineals, les variables explicatives es combinen linealment per modelar el seu efecte sobre la variable de resposta. De vegades en tot cas la linealitat és insuficient per a capturar l’estructura de les dades. Una alternativa que ja coneixem és la transformació de la variable de resposta, o l’ús de funcions de vincle, incorporades als models lineals generalitzats per tal de linealitzar relacions que lineals no són. Una altra alternativa pot ser representada per la transformació d’una o més variables explicatives (per exemple, termes quadràtics o cúbics dins d’aquestes).
Però en certes ocasions aquestes transformacions resulten insuficients i es necessitarà d’un marc de modelació més flexible. Certs mètodes com els mètodes additius generalitzats, la regressió no lineal, els arbres de classificació i regressió (CART) o les xarxes neuronals, aporten alternatives molt versàtils per a la modelació de relacions no lineals.
En aquest capítol farem servir el data frame
dry de la llibreria ADER que mostra la relació
entre richness, riquesa d’arbres, i
NDVI (Normalized Difference Vegetation Index), que
podem definir com a productivitat. Dades obtingudes amb imatges
de satèl·lit , preses entre 2001 i 2007, sobre parcel·les de 0.1 ha de
bosc tropical a Mèxic. El valor de NDVI oscil·la entre 0 i 1, indicant
els valor més alts més alta productivitat.
library(ADER)
data('dry')
str(dry)
## 'data.frame': 96 obs. of 2 variables:
## $ richness: int 18 22 20 24 19 13 11 10 16 15 ...
## $ ndvi : num 0.802 0.792 0.792 0.756 0.695 ...
Quan les dades no mostren una clara relació lineal, i els models polinòmics són insuficients, els models additius generalitzats poden ser una bona alternativa als LM i els GLM. Els GAM utilitzen corbes de suavització per modelar la relació resposta/explicativa, tot permetent relacions no lineals.
La suavització es pot considerar un entremig entre la recta de regressió i una corba que passi per tots el punts del gràfic de dispersió.
library(patchwork)
a <- dry %>%
ggplot(aes(x=ndvi, richness)) +
geom_point()+
geom_smooth(method = "lm", se = F, color="blue", linewidth= 0.5) +
labs(title = "a.") +
theme_bw()
b <- dry %>%
ggplot(aes(x=ndvi, richness)) +
geom_point()+
geom_line(color="blue") +
labs(title = "b.") +
theme_bw()
a+b
Mentre la recta de regressió (a.) pot arribar a ser exageradament simplista per a representar la relació entre riquesa d’espècies i NDVI, la segona opció (b.) tampoc ens aporta informació. Entremig d’aquestes dues aproximacions, podríem traçar un ajustament no lineal que descrigués com varia \(y\) en front d’\(x\).
La suavització permet obtenir una funció no lineal a través d’estimacions de la variable de resposta, \(y\), per a rangs parcials de valors de \(x\). Els valors estimats d’\(y\) per a cada rang s’uneixen després per formar una recta no lineal.
Existeixen diferents mètodes de suavització. Els dos que se solen fer servir més són les Splines i la regressió local.
Una spline és una regressió polinòmica per trams, on es divideixen les dades d’acord als valors de la variable explicativa i a cada tram de dades s’ajusta una regressió polinòmica normalment de grau baix, posant com a condició que els extrems de cada tram successiu coincideixin. Normalment es fan servir polinomis de grau 3.
La regressió local (LOESS) ajusta múltiples corbes de regressió lineal tenint en compte a cada punt només un subconjunt de dades que siguin pròximes a la dada focal. També aquí caldrà definir quants punts pròxims tindrem en compte per fer l’ajustament.
També es pot assignar més pes als punts més pròxims i menys pes als més allunyats mitjançant ponderació lineal: mètode que es coneix com regressió local ponderada (LOWESS).
Els models GAM poden considerar-se extensions de models GLM en què s’adscriguin funcions no lineals per a variables explicatives individuals, tot mantenint l’additivitat dels efectes. És a dir, el predictor lineal \(\eta\) \[ \small \eta_i =\beta_0+\beta_{1}x_{i1}+\beta_2x_{i2}+...+\beta_p x_{ip} + \epsilon_i \] es converteix ara en \[ \small \eta_i = \beta_0 + f_1(x_{i1}) + f_2(x_{i2})+...+ f_p(x_{ip}) + \epsilon_i \] on cada component lineal \(\small \beta_{ij}\) és substituït per una funció no lineal de suavització, \(\small f_j(x_{ij})\). Aquest model es defineix com additiu perquè s’ajusta una funció individual per a cada variable i, posteriorment, se’n sumen els efectes.
Les funcions poden ser de qualsevol tipus (splines, LOESS, LOWESS, regressions polinòmiques o lineals). Dins del mateix model es poden plantejar funcions diferents per a cada una de las variables explicatives. Per exemple, es poden plantejar relacions lineals amb algunes i no lineals amb d’altres mitjançant l’ajustament de funcions, per exemple \[ \small \eta_i= \beta_0 + \beta_1 x_{1i} + f_2 (x_{2i}) + \epsilon_i \]
NOTA: Les funcions de suavització només es poden aplicar a variable contínues, mai a les categòriques.
gamEl paquet gam disposa de funcions per ajustar i
treballar models additius generalitzats. La funció gam()
d’aquest model permet executar suavitzacions pel mètode splines
i per LOESS.
Per a implementar una suavització amb splines podem fer
servir la funció s() que ajusta splines
suavitzadores sobre la variable explicativa dins la fórmula de
gam().
library(gam)
gam1 <- gam::gam(richness ~ gam::s(ndvi), data = dry, family = poisson)
summary(gam1)
##
## Call: gam::gam(formula = richness ~ gam::s(ndvi), family = poisson,
## data = dry)
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -3.8649 -1.8516 0.2694 1.4236 3.1329
##
## (Dispersion Parameter for poisson family taken to be 1)
##
## Null Deviance: 353.1647 on 95 degrees of freedom
## Residual Deviance: 350.4069 on 94 degrees of freedom
## AIC: 762.348
##
## Number of Local Scoring Iterations: 4
##
## Anova for Parametric Effects
## Df Sum Sq Mean Sq F value Pr(>F)
## gam::s(ndvi) 1 2.768 2.7679 0.8243 0.3662
## Residuals 94 315.638 3.3579
La funció summary ofereix un resum de l’ajustament,
incloent-hi els tests de significació que obtindríem amb
anova().
La funció summary.Gam() retorna dues taules ANOVA: una
per a la part lineal i una altra per a la part aproximada de manera no
paramètrica. A la primera s’inclou també la part lineal del termes no
lineals, amb un grau de llibertat, mentre que la resta de graus de
llibertat s’inclouen a la segona taula. So no s’han especificat,
s() aplica per defecte 4 graus de llibertat. D’acord amb el
test del quocient de versemblança
(Anova for Nonparametric Effects) existe un efecto de
l’NDVI sobre la riquesa d’arbres en boscos tropicals secs.
Però, com és aquest efecte? El model que ajustat és \[ \small \eta_i=\beta_0 + s(NDVI_i) + \epsilon_i \] sent \(\small s(NDVI_i)\) una funció no lineal (amb un nombre variable de nusos) de la variable explicativa NDVI.
Tot i que podem conèixer els coeficients del model
coefficients(gam1)
## (Intercept) gam::s(ndvi)
## 2.1616869 0.6209708
l’efecte de l’NDVI no es pot interpretar en funció del signe i de la magnitud del coeficient, com faríem en el cas de LMs o GLMs: és necessari en canvi representar gràficament les prediccions del model.
La funció plot.Gam per defecte genera un gràfic que
representa la spline en funció de la variable explicativa
(gràfic de l’esquerra), que equival a representar les prediccions del
model, però a diferent escala (veure gràfic de la dreta que, a més a
més, s’ha dibuixat amb ggplot2). La spline està en
l’escala del predictor lineal. Per tant, si volem conèixer el valor
esperat de riquesa per a un valor d’NDVI concret, haurem d’agafar el
valor corresponent de la spline, sumar-lo a l’ordenada a
l’origen i aplicar la funció de vincle inversa (en aquest cas la funció
exponencial) a aquesta suma.
Aquestes operacions es poden obviar aplicant el paràmetre
type = "response" quan executem la funció
predict.
library(grid)
pred <- as.data.frame(predict(gam1, type = "response", se.fit = T)[1:2]) %>%
mutate(upper = fit + 1.96*se.fit,
lower= fit - 1.96*se.fit)
# Gràfic de la dreta
P <- dry %>%
ggplot(aes(x=ndvi,y=richness))+
geom_point()+
geom_line(data=pred,
aes(x=dry$ndvi,y=fit)) +
geom_line(data=pred,
aes(x=dry$ndvi,y=upper), linetype=3) +
geom_line(data=pred,
aes(x=dry$ndvi,y=lower), linetype=3) +
theme_bw()
vp.BottomRight <- viewport(height=unit(.85, "npc"), width=unit(0.47, "npc"),
just=c("left","top"),
y=0.96, x=0.53)
par(mar= c(5,4,1.2,18))
# Gràfic de l'esquerra
plot(gam1,se=T)
print(P, vp=vp.BottomRight)
En tot cas, el model sembla suggerir que, en ambient tropical sec, el valor de la riquesa d’arbres sigui màxim per a valor extrems de l’escala de de NDVI.
Aplicant la fórmula \(\small
D^2=1-D/D_{nul}\) que ja coneixem i, basant-nos en els valors del
summary, obtenim
1-289.1961/353.1647
## [1] 0.1811297
és a dir un 18% aproximat de la variabilitat observada. Cayuela i De la Cruz consideren aquest ordre de magnitud com acceptable, atesa la baixa resolució de les dades espacials. Tot i així, és possible que que incloent més variables al model es pugui arribar a explicar més variabilitat de la variable de resposta.
En la suavització per splines, el nombre de graus de
llibertat efectius (edf) per defecte en la funció és 4. Es pot
canviar el nombre de graus de llibertat especificant-lo dins de la
funció s() en la fórmula del model. Cal destacar que el
nombre de graus de llibertat no ha de ser necessàriament un nombre
sencer.
Exemples:
gam_1df <- gam::gam(richness ~ gam::s(ndvi, df=1), data = dry, family = poisson)
gam_2df <- gam::gam(richness ~ gam::s(ndvi, df=2), data = dry, family = poisson)
gam_4df <- gam::gam(richness ~ gam::s(ndvi, df=4), data = dry, family = poisson)
gam_8df <- gam::gam(richness ~ gam::s(ndvi, df=8), data = dry, family = poisson)
par(mfrow= c(2,2))
plot(gam_1df, main= "Splines amb 1 edf")
plot(gam_2df, main= "Splines amb 2 edf")
plot(gam_4df, main= "Splines amb 4 edf")
plot(gam_8df, main= "Splines amb 8 edf")
Si volem passar d’una suavització amb Splines a una amb
regressió local (LOESS), només hauríem de canviar la funció
s() per la funció lo() dins la fórmula.
gam2 <- gam::gam(richness ~ gam::lo(ndvi), data = dry, family = poisson)
summary(gam2)
##
## Call: gam::gam(formula = richness ~ gam::lo(ndvi), family = poisson,
## data = dry)
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -3.8649 -1.8516 0.2694 1.4236 3.1329
##
## (Dispersion Parameter for poisson family taken to be 1)
##
## Null Deviance: 353.1647 on 95 degrees of freedom
## Residual Deviance: 350.4069 on 94 degrees of freedom
## AIC: 762.348
##
## Number of Local Scoring Iterations: 4
##
## Anova for Parametric Effects
## Df Sum Sq Mean Sq F value Pr(>F)
## gam::lo(ndvi) 1 2.768 2.7679 0.8243 0.3662
## Residuals 94 315.638 3.3579
coefficients(gam2)
## (Intercept) gam::lo(ndvi)
## 2.1616869 0.6209708
Veiem que les dades del summary els coeficients són molt
similars als que hem obtingut amb la suavització per splines,
tot i que la variabilitat \(D^2\)
explicada pel model és lleugerament superior.
1 - 277.0453/353.1647
## [1] 0.2155351
El gràfic també sembla força similar tot i que es nota una tendència de la spline a obrir més l’interval de confiança en els valors extrems.
par(mfrow= c(1,2))
plot(gam1, se=T, main= "Suavització SPLINE", ylim= c(-0.45,0.7))
plot(gam2, se=T, main= "Suavització LOESS", ylim= c(-0.45,0.7))
spanEn la suavització per regressió local, el paràmetre de suavització no
és el nombre de nusos (knots), sinó el nombre de punts
propers que s’utilitzen per a la regressió local que es denomina
span. Per defecte, span agafa el valor de 0.5,
és a dir que es pren en consideració el 50% de punts repartits a la
dreta i a l’esquerra del punt focal. Tot i així, aquest valor es pot
modificar, com es pot veure a sota
gam_1s <- gam::gam(richness ~ gam::lo(ndvi, span = 0.1), data = dry, family = poisson)
gam_2s <- gam::gam(richness ~ gam::lo(ndvi, span = 0.2), data = dry, family = poisson)
gam_5s <- gam::gam(richness ~ gam::lo(ndvi, span = 0.5), data = dry, family = poisson)
gam_9s <- gam::gam(richness ~ gam::lo(ndvi, span = 0.9), data = dry, family = poisson)
par(mfrow= c(2,2))
plot(gam_1s, main= "span = 0.1")
plot(gam_2s, main= "span = 0.2")
plot(gam_5s, main= "span = 0.5")
plot(gam_9s, main= "span = 0.9")
mgcvUn altre mètode per ajustar models additius generalitzats està
disponible amb la llibreria mgcv. La funció principal del
paquet és gam() (com la del paquet gam,
atenció!). Aquesta funció selecciona automàticament el grau de
suavització utilitzant un algoritme específic (Penalized regression
splines) que optimitza la relació entre complexitat (nombre de
nusos i de graus efectius de llibertat) i el grau d’ajustament a les
dades (log-likelihood).
summarymgcv.gam1 <- mgcv::gam(formula = richness ~ s(ndvi), data = dry, family=poisson)
summary(mgcv.gam1)
##
## Family: poisson
## Link function: log
##
## Formula:
## richness ~ s(ndvi)
##
## Parametric coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 2.53575 0.02917 86.93 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Approximate significance of smooth terms:
## edf Ref.df Chi.sq p-value
## s(ndvi) 8.18 8.81 83.4 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## R-sq.(adj) = 0.199 Deviance explained = 24.9%
## UBRE = 1.9527 Scale est. = 1 n = 96
Els resultats s’interpreten de la mateixa manera que els del paquet
gam, tot i que les taules de sortida són bastant més clares
i usables. Entre altres coses interessants, hi podem trobar el
coeficient estimat de l’ordenada a l’origen, la percentual de deviança
explicada (\(D^2\)) i l’\(R^2\) ajustat, tot i que, igual que en els
GLM, en els GAM és més adequat utilitzar la deviana explicada que
l’\(R^2\), com a mesura de la bondat de
l’ajustament a les dades.
Funciona igual que el paquet gam, fent servir
plot()
library(grid)
pred.mgcv <- as.data.frame(predict(mgcv.gam1, type = "response", se.fit = T)[1:2]) %>%
mutate(upper = fit + 1.96*se.fit,
lower= fit - 1.96*se.fit)
# Gràfic de la dreta
P <- dry %>%
ggplot(aes(x=ndvi,y=richness))+
geom_point()+
geom_line(data=pred.mgcv,
aes(x=dry$ndvi,y=fit)) +
geom_line(data=pred.mgcv,
aes(x=dry$ndvi,y=upper), linetype=3) +
geom_line(data=pred.mgcv,
aes(x=dry$ndvi,y=lower), linetype=3) +
theme_bw()
vp.BottomRight <- viewport(height=unit(.85, "npc"), width=unit(0.47, "npc"),
just=c("left","top"),
y=0.96, x=0.53)
par(mar= c(5,4,1.2,18))
# Gràfic de l'esquerra
plot(mgcv.gam1)
print(P, vp=vp.BottomRight)
L’ajustament que se selecciona automàticament és similar al que
obtenim amb el paquet gam seleccionant 8 graus de llibertat
efectius: de fet al summary, a l’apartat
Approximate significance of smooth terms, els graus de
llibertat efectius (edf) són 8.18. Si volguéssim fixar els
graus de llibertat, hauríem de de fer-ho especificant spline
(k=..., fx=TRUE).
Atès que utilitzem funcions de suavització justament per incorporar relacions no lineals, la falta de linealitat no hauria de ser mai un problema d’aquests models. D’altra banda estem assumint una funció de distribució específica per a capturar dels errors. Ens hem de preguntar doncs si la funció és adequada. Hauríem de mirar llavors als gràfics dels residus.
gam.check()La funció gam.check() dibuixa alguns gràfics de
diagnòstic que podrien ser útils. Es pot elegir entre alguns tipus de
residuals.
par(mfrow= c(2,2))
mgcv::gam.check(mgcv.gam1, type = "pearson", pch=19)
##
## Method: UBRE Optimizer: outer newton
## full convergence after 3 iterations.
## Gradient range [6.350042e-07,6.350042e-07]
## (score 1.952694 & scale 1).
## Hessian positive definite, eigenvalue range [0.008173242,0.008173242].
## Model rank = 10 / 10
##
## Basis dimension (k) checking results. Low p-value (k-index<1) may
## indicate that k is too low, especially if edf is close to k'.
##
## k' edf k-index p-value
## s(ndvi) 9.00 8.18 0.92 0.21
Aquí, tot i no ser visibles pautes clares, es pot entreveure cera irregularitat en la distribució dels residus.
També amb GAM podem utilitzar per avaluar l’adequació de la funció de
distribució d’errors seleccionada. Això ho podem fer indiferentment amb
objectes creats amb gam i amb mgcv. Atenció
que per poder utilitzar aquest llibreria és necessària la instal·lació
també del paquet mgcViz.
library(DHARMa)
library(mgcViz)
simul <- DHARMa::simulateResiduals(mgcv.gam1, n=1000)
par(mfrow= c(2,2),
mar= c(4,4,3,2))
DHARMa::plotQQunif(simul,
testUniformity = FALSE,
testOutliers = FALSE,
testDispersion = FALSE,
xaxs = "i", yaxs = "i")
DHARMa::plotResiduals(simul,xaxs = "i", yaxs = "i")
DHARMa::testUniformity(simul, plot = F)
##
## Exact one-sample Kolmogorov-Smirnov test
##
## data: simulationOutput$scaledResiduals
## D = 0.18099, p-value = 0.003182
## alternative hypothesis: two-sided
DHARMa::testOutliers(simul, plot = F)
##
## DHARMa bootstrapped outlier test
##
## data: simul
## outliers at both margin(s) = 2, observations = 96, p-value = 0.02
## alternative hypothesis: two.sided
## percent confidence interval:
## 0.00000000 0.01041667
## sample estimates:
## outlier frequency (expected: 0.00229166666666667 )
## 0.02083333
DHARMa::testDispersion(simul, plot = F)
##
## DHARMa nonparametric dispersion test via sd of residuals fitted vs.
## simulated
##
## data: simulationOutput
## dispersion = 2.47, p-value < 2.2e-16
## alternative hypothesis: two.sided
Els diferents test confirmen els dubtes manifestats a dalt. El gràfic Quantil-Quantil que permet verificar si els residuals del model segueixen la distribució de probabilitat teòrica elegida, manifesta certa desviació. Es manifesten problemes també d’heteroscedasticitat (gràfics de valors predits i residuals i test de kolgomorov-Smirnov) i de valors atípics.
Les problemàtiques podrien ser degudes a problemes de sobredispersió, tal com ens podria indicar el darrer test.
mgcv per a GAM binomial negatiuLa funció gam() de mgcv disposa de la
família de distribució d’errors binomial negativa, però, atenció, no
gam() de gam (disposa com a alternativa de la
quasi-Poisson).
summary del modelmgcv.gam.bn <- mgcv::gam(formula = richness ~ s(ndvi), data = dry, family = nb)
summary(mgcv.gam.bn)
##
## Family: Negative Binomial(5.334)
## Link function: log
##
## Formula:
## richness ~ s(ndvi)
##
## Parametric coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 2.54580 0.05275 48.26 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Approximate significance of smooth terms:
## edf Ref.df Chi.sq p-value
## s(ndvi) 3.814 4.756 15.51 0.00992 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## R-sq.(adj) = 0.165 Deviance explained = 16.1%
## -REML = 318.07 Scale est. = 1 n = 96
library(grid)
pred.mgcv.bn <- as.data.frame(predict(mgcv.gam.bn, type = "response", se.fit = T)[1:2]) %>%
mutate(upper = fit + 1.96*se.fit,
lower= fit - 1.96*se.fit)
# Gràfic de la dreta
P <- dry %>%
ggplot(aes(x=ndvi,y=richness))+
geom_point()+
geom_line(data=pred.mgcv.bn,
aes(x=dry$ndvi,y=fit)) +
geom_line(data=pred.mgcv.bn,
aes(x=dry$ndvi,y=upper), linetype=3) +
geom_line(data=pred.mgcv.bn,
aes(x=dry$ndvi,y=lower), linetype=3) +
theme_bw()
vp.BottomRight <- viewport(height=unit(.85, "npc"), width=unit(0.47, "npc"),
just=c("left","top"),
y=0.96, x=0.53)
par(mar= c(5,4,1.2,18))
# Gràfic de l'esquerra
plot(mgcv.gam.bn)
print(P, vp=vp.BottomRight)
L’aspecte que queda més evident comparant aquest gràfic amb els anteriors és que s’amplia de manera molt significativa l’interval de confiança.
La llibreria DHARMa, des de fa uns mesos, disposa de la
possibilitat d’avaluar també els models GAM de família de distribució
binomial negativa.
simul.bn <- DHARMa::simulateResiduals(mgcv.gam.bn, n=1000)
par(mfrow= c(2,2),
mar= c(4,4,3,2))
DHARMa::plotQQunif(simul.bn,
testUniformity = FALSE,
testOutliers = FALSE,
testDispersion = FALSE,
xaxs = "i", yaxs = "i")
DHARMa::plotResiduals(simul.bn,xaxs = "i", yaxs = "i")
DHARMa::testUniformity(simul.bn, plot = F)
##
## Exact one-sample Kolmogorov-Smirnov test
##
## data: simulationOutput$scaledResiduals
## D = 0.14118, p-value = 0.03929
## alternative hypothesis: two-sided
DHARMa::testOutliers(simul.bn, plot = F)
##
## DHARMa bootstrapped outlier test
##
## data: simul.bn
## outliers at both margin(s) = 0, observations = 96, p-value = 1
## alternative hypothesis: two.sided
## percent confidence interval:
## 0.00000000 0.01041667
## sample estimates:
## outlier frequency (expected: 0.00166666666666667 )
## 0
DHARMa::testDispersion(simul.bn, plot = F)
##
## DHARMa nonparametric dispersion test via sd of residuals fitted vs.
## simulated
##
## data: simulationOutput
## dispersion = 0.76316, p-value = 0.19
## alternative hypothesis: two.sided
El gràfic Quantil-Quantil manifesta, sense estar encara perfecte, indica més adherència dels valors residuals al model. Tant els gràfic (esquerra), com el tast Kolgomorov manifesten encara una certa heteroscedasticitat, tot i així cal destacar la millora del p-valor. D’altra banda ja no tenim problemàtiques de valors atípics.
L’aspecte a destacar és que, tal com veiem en el test de dispersió, ja no apareix la sobredispersió que s’havia manifestat amb el model GAM de la família de distribució d’errors Poisson.
En conclusió, sembla que en conjunt el model GAM amb distribució d’errors binomial negativa s’ajusti millor a l’estructura de les dades.
La relació entre contaminació de l’aire i salut és un tema força controvertit, amb una munió de treballs epidemiològics intentant de posar en focus en aquesta relació.
La present és una variant d’un mètode d’anàlisi en l’epidemiologia de la contaminació, que s’ha tornat fora popular avui en dia.
El conjunt de dades chicago del paquet
gamair proporciona dades de sèries temporals diàries que
analitzen la relació entre els factors ambientals i la mortalitat
diària. Abasta des de l’1 de gener de 1987 fins al 31 de desembre de
2000.
death, i com a possibles
covariables independents que l’expliquin:o3median;so2median;tmpd;pm10median;time que mesura el temps en què es va
dur a terme l’estudi, ja que es creu que el nombre de morts també té a
veure amb el moment estudiat de l’any, independentment de les variables
ambientals.Es pretén trobar un model GAM que relacioni la pol·lució de l’aire amb la salut. Per això es va dur a terme un estudi per Peng i Welty (2004) on es va considerar com a variable dependent el nombre diari de morts a la ciutat de Chicago durant el període que va durar l’estudi.
library(gamair)
data(chicago)
str(chicago)
## 'data.frame': 5114 obs. of 7 variables:
## $ death : int 130 150 101 135 126 130 129 109 125 153 ...
## $ pm10median: num -7.434 NA -0.827 5.566 NA ...
## $ pm25median: num NA NA NA NA NA NA NA NA NA NA ...
## $ o3median : num -19.6 -19 -20.2 -19.7 -19.2 ...
## $ so2median : num 1.928 -0.986 -1.891 6.139 2.278 ...
## $ time : num -2556 -2556 -2554 -2554 -2552 ...
## $ tmpd : num 31.5 33 33 29 32 40 34.5 29 26.5 32.5 ...
Per les característiques de la variable de resposta, sembla raonable considerar un model de regressió Poisson però, per ser més general, considerarem un model GAM de Poisson de manera que la mitjana diària de morts \(µ_i=E[death_i]\) la modelitzem mitjançant el model GAM semiparamètric de la forma \[ \small\log \mu_i= \beta_1 \texttt{o3median}_i+\beta_2\texttt{so2median}_i+\beta_3\texttt{tempd}_i+\beta_4\texttt{pm10median}_i+f(\texttt{time}_i) \] on \(\small\mu_i\) segueix una distribució Poisson i \(\small f\) és una funció de suavització.
Per efectuar l’ajust executarem la funció mgcv::gam() on
hem utilitzat com a base per a la suavització una regressió
spline cúbica (expressada amb cr a l’argument
bs) perquè és el més senzill d’executar en ser moltes dades
(\(n=5114\)). El valor de
k suggerit seria \(k = 20·
5114^{2/9} = 133.4\); agafarem 100 que a més és el màxim valor
utilitzat, de fet, per la funció.
contamin <- mgcv::gam(death ~ s(time,bs="cr",k=100)+o3median+so2median+tmpd+pm10median,
data=chicago,family=poisson)
contamin
##
## Family: poisson
## Link function: log
##
## Formula:
## death ~ s(time, bs = "cr", k = 100) + o3median + so2median +
## tmpd + pm10median
##
## Estimated degrees of freedom:
## 89.8 total = 94.78
##
## UBRE score: 0.3073298
par(mfrow= c(1,2))
mgcv::qq.gam(contamin, cex=0.8, col= "blue", pch= 19)
plot(contamin$fitted.values, contamin$y, col= "blue",
xlab = "Fitted values", ylab = "Resonse")
simul.contamin <- DHARMa::simulateResiduals(contamin, n=1000)
DHARMa::testUniformity(simul.contamin, plot = F)
##
## Asymptotic one-sample Kolmogorov-Smirnov test
##
## data: simulationOutput$scaledResiduals
## D = 0.020865, p-value = 0.02955
## alternative hypothesis: two-sided
DHARMa::testOutliers(simul.contamin, plot = F)
##
## DHARMa outlier test based on exact binomial test with approximate
## expectations
##
## data: simul.contamin
## outliers at both margin(s) = 31, observations = 4841, p-value =
## 3.714e-08
## alternative hypothesis: true probability of success is not equal to 0.001998002
## 95 percent confidence interval:
## 0.004354985 0.009077236
## sample estimates:
## frequency of outliers (expected: 0.001998001998002 )
## 0.006403636
DHARMa::testDispersion(simul.contamin, plot = F)
##
## DHARMa nonparametric dispersion test via sd of residuals fitted vs.
## simulated
##
## data: simulationOutput
## dispersion = 1.3636, p-value < 2.2e-16
## alternative hypothesis: two.sided
Com acabem de veure, aquest model presenta diferents problemes: una certa heteroscedasticitat i uns outliers molt contundents.
Reperesentant la spline estimada, amb i sense valors residuals, podem evidenciar els outliers que en aquest cas són prou importants.
par(mfrow= c(2,1))
plot(contamin)
plot(contamin, residuals = T, cex=4)
Si representem deaths en ordre d’ID, podem veure que els
4 valors més alts, a la sèrie, s’hi esdevenen durant 4 dies seguits.
library(patchwork)
ch1 <- as.data.frame(chicago) |>
ggplot() +
geom_point(aes(x= seq(1,nrow(chicago),by=1), y= death),
size= 0.5) +
ggrepel::geom_text_repel(aes(x= seq(1,nrow(chicago),by=1),
y= death,
label=seq(1,nrow(chicago),by=1)),
size=2, max.overlaps = 20) +
labs(x="Index",
y="death") +
theme_bw()
ch2 <- as.data.frame(chicago) |>
ggplot() +
geom_point(aes(x= seq(1,nrow(chicago),by=1), y= o3median),
size= 0.5) +
ggrepel::geom_text_repel(aes(x= seq(1,nrow(chicago),by=1),
y= o3median,
label=seq(1,nrow(chicago),by=1)),
size=2) +
labs(x="Index",
y="Nivell d'ozó") +
theme_bw()
ch1 / ch2
Si en creem una sèrie temporal i prenem en consideració les variables corresponents a les morts, els nivells de PM10, els nivells d’ozó i la temperatura, veiem que el gran pic de morts coincideix amb el pic més important del nivells d’ozó i amb temperatures altes. N.Wood hi posa molta èmfasi, però a la sèrie hi ha més pics importants d’ozó (encara que no tant), però ni de bon tros coincideixen amb pics similars de mortalitat.
library(forecast)
ts.chicago <- ts(chicago, start = 1)
a <- ts.chicago[,1] %>%
autoplot(colour= "blue") +
labs(title = "Nombre de morts diaries")+
theme_bw()
b <- ts.chicago[,2] %>%
autoplot(colour= "green") +
labs(title = "Nivell de PM10")+
theme_bw()
c <- ts.chicago[,4] %>%
autoplot(colour= "red") +
labs(title = "Nivell d'Ozó")+
theme_bw()
d <- ts.chicago[,7] %>%
autoplot(colour= "violet") +
labs(title = "Temperatura")+
theme_bw()
a/c/d
D’altra banda, és veritat que els diferents tests de Philips-Ouliaris
apunten a l’existència de correlació entre la variable de resposta i les
covariables. Aquí facilitem el resultat del test entre la variable
death i o3median.
tseries::po.test(ts.chicago[,c(1,2)])
##
## Phillips-Ouliaris Cointegration Test
##
## data: ts.chicago[, c(1, 2)]
## Phillips-Ouliaris demeaned = -11816, Truncation lag parameter = 48,
## p-value = 0.01
García Pérez i Wood afirmen (probablement ho han estudiat més a fons…) que les dades anòmales suggereixen que les altes temperatures y uns grans nivells d’ozó poden ser la causa de mortalitat (és a dir, ser bones covariables predictores) però uns dies més tard, ja que els es donen 3 o 4 dies abans de aquests màxims en les morts.
Per aquesta raó, proposen avançar 3 dies els valors de les
covariables o3median, so2median, tmpd, pm10median, sumant
els seus valors, mantenint els de time i els de la variable
dependent death, encara que, en perdre per aquest
avançament 3 dades, cal ometre els primers tres d’aquestes dues. Així,
considerarem les noves variables:
death <- chicago$death[4:5114]
time <- chicago$time[4:5114]
i, per avançar sumant les dades de les altres quatre utilitzarem la
funció lag.suma definida com:
lag.suma<-function(x)
{ n <- length(x)
b<- rep(0,n-3)
for(i in 0:(3-0)) b <- b+x[(i+1):(n-3+i)]
b
}
tot definint ara les noves covariables lagged, amb retard
lag.o3<-lag.suma(chicago$o3median)
lag.tmp<-lag.suma(chicago$tmpd)
lag.pm10<-lag.suma(chicago$pm10median)
lag.so2<-lag.suma(chicago$so2median)
En ajustar el nou model GAM chic1, utilitzem funcions de
suavització de totes les covariables, ajuntant les dues
que, segons l’anàlisi de les dades, semblen tenir un efecte
combinat encara que hem omès en aquest cas la base
cr perquè la spline cúbica només admet una
covariable.
chic1 <- mgcv::gam(death ~ s(time,bs="cr",k=100)+
s(lag.o3,lag.tmp,k=100)+ # efecte combinat ozó-temperatura
s(lag.pm10,bs="cr",k=100)+s(lag.so2,bs="cr",k=100),
family=poisson)
par(mfrow=c(2,2))
par(mfrow= c(1,2))
mgcv::qq.gam(chic1, cex=0.8, col= "blue", pch= 19)
plot(chic1$fitted.values, chic1$y, col= "blue",
xlab = "Fitted values", ylab = "Response")
Observem un clar millorament en la dispersió dels valors residuals.
Del compendi de paràmetres que ens facilita el summary()
cal destacar que l’efecte combinat d’ozó i temperatura és destacable i
que el model explica gairebé un 45% de la variabilitat, que ja podem
començar a considerar de manera positiva.
summary(chic1)
##
## Family: poisson
## Link function: log
##
## Formula:
## death ~ s(time, bs = "cr", k = 100) + s(lag.o3, lag.tmp, k = 100) +
## s(lag.pm10, bs = "cr", k = 100) + s(lag.so2, bs = "cr", k = 100)
##
## Parametric coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 4.742249 0.001422 3335 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Approximate significance of smooth terms:
## edf Ref.df Chi.sq p-value
## s(time) 92.092 97.614 954.50 <2e-16 ***
## s(lag.o3,lag.tmp) 81.500 93.889 697.83 <2e-16 ***
## s(lag.pm10) 6.194 7.695 23.52 0.0024 **
## s(lag.so2) 16.992 20.573 24.38 0.2610
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## R-sq.(adj) = 0.433 Deviance explained = 44.8%
## UBRE = 0.18691 Scale est. = 1 n = 4325
D’altra banda, S.N. Wood suggereix fer servir el logaritmes naturals per a \(PM_{10}\) i \(SO_2\).
chic2 <- mgcv::gam(death ~ s(time,bs="cr",k=100)+
s(lag.o3,lag.tmp,k=100)+ # efecte combinat ozó-temperatura
s(log(lag.pm10),bs="cr",k=100)+s(log(lag.so2),bs="cr",k=100),
family=poisson)
summary(chic2)
##
## Family: poisson
## Link function: log
##
## Formula:
## death ~ s(time, bs = "cr", k = 100) + s(lag.o3, lag.tmp, k = 100) +
## s(log(lag.pm10), bs = "cr", k = 100) + s(log(lag.so2), bs = "cr",
## k = 100)
##
## Parametric coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 4.749723 0.003319 1431 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Approximate significance of smooth terms:
## edf Ref.df Chi.sq p-value
## s(time) 46.794 56.110 137.194 <2e-16 ***
## s(lag.o3,lag.tmp) 62.949 79.577 695.220 <2e-16 ***
## s(log(lag.pm10)) 2.042 2.587 4.262 0.2102
## s(log(lag.so2)) 2.499 3.166 8.744 0.0422 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## R-sq.(adj) = 0.599 Deviance explained = 60.4%
## UBRE = 0.28297 Scale est. = 1 n = 788
Provada la solució, veiem que només l’índex de \(SO_2\) segueix presentant p valors significatius.
Amb la qual cosa apartarem \(PM10 del
model\), deixant només les variables lagged la
combinació de o3 i tmp, a més de
so2.
chic3 <- mgcv::gam(death ~ s(time,bs="cr",k=100)+
s(lag.o3,lag.tmp,k=100)+ # efecte combinat ozó-temperatura
s(log(lag.so2),bs="cr",k=100),
family=poisson)
summary(chic3)
##
## Family: poisson
## Link function: log
##
## Formula:
## death ~ s(time, bs = "cr", k = 100) + s(lag.o3, lag.tmp, k = 100) +
## s(log(lag.so2), bs = "cr", k = 100)
##
## Parametric coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 4.771581 0.002376 2008 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Approximate significance of smooth terms:
## edf Ref.df Chi.sq p-value
## s(time) 84.471 92.944 429.76 < 2e-16 ***
## s(lag.o3,lag.tmp) 68.590 84.872 485.40 < 2e-16 ***
## s(log(lag.so2)) 1.001 1.002 8.96 0.00277 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## R-sq.(adj) = 0.527 Deviance explained = 54.3%
## UBRE = 0.25557 Scale est. = 1 n = 1505
On veiem que la combinació ozó-temperatura el logaritme del índex d’òxid de sofre (ambdós retardats) tenen un efecte clarament significatiu sobre la mortalitat. A més a més el model explica més d’un 54% de la variabilitat.
En un primer anàlisi veiem que el model sembla presentar, pel que fa a homoscedasticitat i adherència al model dels residuals, millor característiques.
par(mfrow= c(2,2))
mgcv::gam.check(chic3)
##
## Method: UBRE Optimizer: outer newton
## full convergence after 8 iterations.
## Gradient range [-7.878913e-07,6.023003e-08]
## (score 0.2555663 & scale 1).
## Hessian positive definite, eigenvalue range [7.868398e-07,0.01616814].
## Model rank = 298 / 298
##
## Basis dimension (k) checking results. Low p-value (k-index<1) may
## indicate that k is too low, especially if edf is close to k'.
##
## k' edf k-index p-value
## s(time) 99.0 84.5 1.06 0.97
## s(lag.o3,lag.tmp) 99.0 68.6 1.06 1.00
## s(log(lag.so2)) 99.0 1.0 1.02 0.65
simul.chic3 <- DHARMa::simulateResiduals(chic3, n=1000)
plot(simul.chic3)
Com acabem de veure, aquest model ja no presenta heteroscedasticitat i es veu adherència dels valors residuals respecte als predits.
Resten certs problemes de outliers i de sobre dispersió.