Plutôt que d'essayer de décomposer la série chronologique de manière explicite, je suggère plutôt de modéliser les données spatio-temporellement car, comme vous le verrez ci-dessous, la tendance à long terme varie probablement spatialement, la tendance saisonnière varie avec la tendance à long terme et spatialement.
J'ai trouvé que les modèles additifs généralisés (GAM) sont un bon modèle pour ajuster des séries temporelles irrégulières comme vous le décrivez.
Ci-dessous, j'illustre un modèle rapide que j'ai préparé pour les données complètes du formulaire suivant
E (yje)= α +F1(ToDje)+F2(DoYje)+F3(Anje)+F4(Xje,yje) +F5(DoYje,Anje)+F6(Xje,yje,ToDje) +F7(Xje,yje,DoYje)+F8(Xje,yje,Anje)
où
- α est l'ordonnée à l'origine du modèle,
- F1(ToDje) est une fonction fluide de l'heure de la journée,
- F2(DoYje) est une fonction régulière du jour de l'année,
- F3(Anje) est une fonction lisse de l'année,
- F4(Xje,yje) est un lissage 2D de longitude et de latitude,
- F5(DoYje,Anje) est un produit tensoriel lisse du jour de l'année et de l'année,
- F6(Xje,yje,ToDje) produit tenseur lisse de l'emplacement et de l'heure de la journée
- F7(Xje,yje,DoYje) produit tensoriel lisse de l'emplacement jour de l'année &
- F8(Xje,yje,Anje produit tenseur lisse d'emplacement et d'année
En effet, les quatre premiers lissages sont les principaux effets de
- moment de la journée,
- saison,
- tendance à long terme,
- variation spatiale
tandis que les quatre autres produits tensoriels lissent les interactions douces du modèle entre les covariables déclarées, qui modélisent
- comment le modèle saisonnier de la température varie au fil du temps,
- comment l’effet de l’heure varie dans l’espace,
- comment l'effet saisonnier varie spatialement, et
- comment la tendance à long terme varie spatialement
Les données sont chargées dans R et massées un peu avec le code suivant
library('mgcv')
library('ggplot2')
library('viridis')
theme_set(theme_bw())
library('gganimate')
galveston <- read.csv('gbtemp.csv')
galveston <- transform(galveston,
datetime = as.POSIXct(paste(DATE, TIME),
format = '%m/%d/%y %H:%M', tz = "CDT"))
galveston <- transform(galveston,
STATION_ID = factor(STATION_ID),
DoY = as.numeric(format(datetime, format = '%j')),
ToD = as.numeric(format(datetime, format = '%H')) +
(as.numeric(format(datetime, format = '%M')) / 60))
Le modèle lui-même est ajusté à l'aide de la bam()
fonction qui est conçue pour adapter les GAM à de plus grands ensembles de données tels que celui-ci. Vous pouvez également l'utiliser gam()
pour ce modèle, mais cela prendra un peu plus de temps pour s'adapter.
knots <- list(DoY = c(0.5, 366.5))
M <- list(c(1, 0.5), NA)
m <- bam(MEASUREMENT ~
s(ToD, k = 10) +
s(DoY, k = 30, bs = 'cc') +
s(YEAR, k = 30) +
s(LONGITUDE, LATITUDE, k = 100, bs = 'ds', m = c(1, 0.5)) +
ti(DoY, YEAR, bs = c('cc', 'tp'), k = c(15, 15)) +
ti(LONGITUDE, LATITUDE, ToD, d = c(2,1), bs = c('ds','tp'),
m = M, k = c(20, 10)) +
ti(LONGITUDE, LATITUDE, DoY, d = c(2,1), bs = c('ds','cc'),
m = M, k = c(25, 15)) +
ti(LONGITUDE, LATITUDE, YEAR, d = c(2,1), bs = c('ds','tp'),
m = M), k = c(25, 15)),
data = galveston, method = 'fREML', knots = knots,
nthreads = 4, discrete = TRUE)
Les s()
termes sont les effets principaux, tandis que les ti()
termes sont des lissages d' interaction des produits tensoriels où les effets principaux des covariables nommées ont été supprimés de la base. Ces ti()
lissages sont un moyen d'inclure les interactions des variables déclarées d'une manière numériquement stable.
L' knots
objet ne fait que définir les points de terminaison du lissage cyclique que j'ai utilisé pour l'effet du jour de l'année - nous voulons que 23:59 le 31 décembre se rejoigne en douceur avec 00:01 le 1er janvier. Cela représente dans une certaine mesure les années bissextiles.
Le résumé du modèle indique que tous ces effets sont importants;
> summary(m)
Family: gaussian
Link function: identity
Formula:
MEASUREMENT ~ s(ToD, k = 10) + s(DoY, k = 12, bs = "cc") + s(YEAR,
k = 30) + s(LONGITUDE, LATITUDE, k = 100, bs = "ds", m = c(1,
0.5)) + ti(DoY, YEAR, bs = c("cc", "tp"), k = c(12, 15)) +
ti(LONGITUDE, LATITUDE, ToD, d = c(2, 1), bs = c("ds", "tp"),
m = list(c(1, 0.5), NA), k = c(20, 10)) + ti(LONGITUDE,
LATITUDE, DoY, d = c(2, 1), bs = c("ds", "cc"), m = list(c(1,
0.5), NA), k = c(25, 12)) + ti(LONGITUDE, LATITUDE, YEAR,
d = c(2, 1), bs = c("ds", "tp"), m = list(c(1, 0.5), NA),
k = c(25, 15))
Parametric coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 21.75561 0.07508 289.8 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Approximate significance of smooth terms:
edf Ref.df F p-value
s(ToD) 3.036 3.696 5.956 0.000189 ***
s(DoY) 9.580 10.000 3520.098 < 2e-16 ***
s(YEAR) 27.979 28.736 59.282 < 2e-16 ***
s(LONGITUDE,LATITUDE) 54.555 99.000 4.765 < 2e-16 ***
ti(DoY,YEAR) 131.317 140.000 34.592 < 2e-16 ***
ti(ToD,LONGITUDE,LATITUDE) 42.805 171.000 0.880 < 2e-16 ***
ti(DoY,LONGITUDE,LATITUDE) 83.277 240.000 1.225 < 2e-16 ***
ti(YEAR,LONGITUDE,LATITUDE) 84.862 329.000 1.101 < 2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
R-sq.(adj) = 0.94 Deviance explained = 94.2%
fREML = 29807 Scale est. = 2.6318 n = 15276
Une analyse plus approfondie voudrait vérifier si nous avons besoin de toutes ces interactions; certains ti()
termes spatiaux n'expliquent que de petites quantités de variation dans les données, comme l'indiqueFstatistique; il y a beaucoup de données ici, donc même de petites tailles d'effets peuvent être statistiquement significatives mais sans intérêt.
Cependant, à titre de vérification rapide, la suppression des trois ti()
lissages spatiaux ( m.sub
) se traduit par un ajustement significativement moins bon, tel qu'évalué par l'AIC:
> AIC(m, m.sub)
df AIC
m 447.5680 58583.81
m.sub 239.7336 59197.05
Nous pouvons tracer les effets partiels des cinq premiers lissages en utilisant la plot()
méthode - les lissages du produit tensoriel 3D ne peuvent pas être tracés facilement et pas par défaut.
plot(m, pages = 1, scheme = 2, shade = TRUE, scale = 0)
L' scale = 0
argument ici met toutes les parcelles à leur propre échelle, pour comparer les ampleurs des effets, nous pouvons désactiver cela:
plot(m, pages = 1, scheme = 2, shade = TRUE)
On voit maintenant que l'effet saisonnier domine. La tendance à long terme (en moyenne) est indiquée dans le graphique supérieur droit. Cependant, pour vraiment regarder la tendance à long terme, vous devez choisir une station, puis prédire à partir du modèle de cette station, en fixant l'heure du jour et le jour de l'année à certaines valeurs représentatives (midi, pour un jour de l'année en été dire). Là, au début de l'année ou deux de la série, certaines valeurs de températures basses par rapport au reste des enregistrements, sont probablement relevées dans tous les lissages impliqués YEAR
. Ces données doivent être examinées de plus près.
Ce n'est pas vraiment l'endroit pour entrer dans cela, mais voici quelques visualisations des ajustements du modèle. Je regarde d'abord le modèle spatial de la température et comment il varie au cours des années de la série. Pour ce faire, je prédis à partir du modèle d'une grille 100x100 sur le domaine spatial, à midi le jour 180 de chaque année:
pdata <- with(galveston,
expand.grid(ToD = 12,
DoY = 180,
YEAR = seq(min(YEAR), max(YEAR), by = 1),
LONGITUDE = seq(min(LONGITUDE), max(LONGITUDE), length = 100),
LATITUDE = seq(min(LATITUDE), max(LATITUDE), length = 100)))
fit <- predict(m, pdata)
puis je mets à manquant, NA
les valeurs prévues fit
pour tous les points de données qui se trouvent à une certaine distance des observations (proportionnel; dist
)
ind <- exclude.too.far(pdata$LONGITUDE, pdata$LATITUDE,
galveston$LONGITUDE, galveston$LATITUDE, dist = 0.1)
fit[ind] <- NA
et joindre les prédictions aux données de prédiction
pred <- cbind(pdata, Fitted = fit)
La définition de valeurs prédites NA
comme celle-ci nous empêche d'extrapoler au-delà du support des données.
Utilisation de ggplot2
ggplot(pred, aes(x = LONGITUDE, y = LATITUDE)) +
geom_raster(aes(fill = Fitted)) + facet_wrap(~ YEAR, ncol = 12) +
scale_fill_viridis(name = expression(degree*C), option = 'plasma',
na.value = 'transparent') +
coord_quickmap() +
theme(legend.position = 'top', legend.key.width = unit(2, 'cm'))
nous obtenons ce qui suit
Nous pouvons voir la variation des températures d'une année à l'autre avec un peu plus de détails si nous animons plutôt que de facette l'intrigue
p <- ggplot(pred, aes(x = LONGITUDE, y = LATITUDE, frame = YEAR)) +
geom_raster(aes(fill = Fitted)) +
scale_fill_viridis(name = expression(degree*C), option = 'plasma',
na.value = 'transparent') +
coord_quickmap() +
theme(legend.position = 'top', legend.key.width = unit(2, 'cm'))+
labs(x = 'Longitude', y = 'Latitude')
gganimate(p, 'galveston.gif', interval = .2, ani.width = 500, ani.height = 800)
Pour examiner plus en détail les tendances à long terme, nous pouvons prévoir des stations particulières. Par exemple, pour STATION_ID
13364 et la prévision des jours au cours des quatre trimestres, nous pouvons utiliser les éléments suivants pour préparer les valeurs des covariables que nous voulons prédire à (midi, le jour de l'année 1, 90, 180 et 270, à la station sélectionnée , et évaluer la tendance à long terme à 500 valeurs également espacées)
pdata <- with(galveston,
expand.grid(ToD = 12,
DoY = c(1, 90, 180, 270),
YEAR = seq(min(YEAR), max(YEAR), length = 500),
LONGITUDE = -94.8751,
LATITUDE = 29.50866))
Ensuite, nous prédisons et demandons des erreurs standard, pour former un intervalle de confiance approximativement ponctuel à 95%
fit <- data.frame(predict(m, newdata = pdata, se.fit = TRUE))
fit <- transform(fit, upper = fit + (2 * se.fit), lower = fit - (2 * se.fit))
pred <- cbind(pdata, fit)
que nous traçons
ggplot(pred, aes(x = YEAR, y = fit, group = factor(DoY))) +
geom_ribbon(aes(ymin = lower, ymax = upper), fill = 'grey', alpha = 0.5) +
geom_line() + facet_wrap(~ DoY, scales = 'free_y') +
labs(x = NULL, y = expression(Temperature ~ (degree * C)))
produire
Évidemment, il y a beaucoup plus à modéliser ces données que ce que je montre ici, et nous voudrions vérifier l'autocorrélation résiduelle et le sur-ajustement des splines, mais aborder le problème comme l'un des modèles des caractéristiques des données permet une analyse plus détaillée examen des tendances.
Vous pouvez bien sûr simplement modéliser chacun STATION_ID
séparément, mais cela gâcherait les données, et de nombreuses stations ont peu d'observations. Ici, le modèle emprunte à toutes les informations de la station pour combler les lacunes et aider à estimer les tendances d'intérêt.
Quelques notes sur bam()
Le bam()
modèle utilise toutes les astuces de mgcv pour estimer rapidement le modèle (plusieurs threads 4 ), la sélection rapide de la fluidité REML ( method = 'fREML'
) et la discrétisation des covariables. Avec ces options activées, le modèle tient en moins d'une minute sur ma station de travail Xeon double 4 cœurs de l'ère 2013 avec 64 Go de RAM.