Utilisation de modèles de régression à coefficients variant dans le temps pour la prévision conjoncturelle
Atelier D2E
Auteurs
Claire du Campe de Rosamel
Alain Quartier-la-Tente
Date
16 mars 2023
L’objectif de ce TP est d’apprendre à utiliser quelques fonctionnalités du package tvCoef pour l’estimation de modèles de régression à coefficients variant dans le temps.
Pour ce TP nous utiliserons les données de la base tvCoef::manufacturingpour prévoir l’évolution trimestrielle de la production du secteur des autres industries manufacturières (C5, prod_c5) à partir de :
l’acquis de croissance au premier mois du trimestre de l’indice de production industrielle du même secteur (overhang_ipi1_c5) ;
des soldes d’opinion de l’Insee et de la Banque de France. Ces soldes d’opinion sont trimestrialisés en prenant la place du mois dans le trimestre :
insee_bc_c5_m3 : climat des affaires au 3e mois du trimestre (mars, juin, septembre, décembre)
insee_oscd_c5_m2 : niveau des carnets de commandes au 2e mois du trimestre (février, mai, septembre, novembre)
insee_tppre_c5_m3 : solde d’opinion sur l’évolution future de la production au 3e mois du trimestre (février, mai, septembre, novembre).
bdf_tuc_c5_m2 : taux d’utilisation des capacités de production au deuxième mois du trimestre (février, mai, septembre, novembre).
Les deux dernières variables sont utilisées en différence.
Par simplification, nous estimerons ici le modèle entre le 1993T1 et 2019T4 : pour estimer le modèle au-delà cette date, il faudrait ajouter des indicatrices au cours de l’année 2020 et vérifier si le modèle estimé est toujours bien spécifié.
Le modèle peut alors être estimé en utilisant par exemple la fonction dynlm::dynlm()1 :
Time series regression with "ts" data:
Start = 1993(2), End = 2019(4)
Call:
dynlm(formula = prod_c5 ~ overhang_ipi1_c5 + insee_bc_c5_m3 +
insee_oscd_c5_m2 + diff(insee_tppre_c5_m3, 1) + diff(bdf_tuc_c5_m2,
1), data = data)
Coefficients:
(Intercept) overhang_ipi1_c5
-6.383023 0.102405
insee_bc_c5_m3 insee_oscd_c5_m2
0.061437 -0.005596
diff(insee_tppre_c5_m3, 1) diff(bdf_tuc_c5_m2, 1)
0.039043 0.397070
Les prévisions dans l’échantillon (in sample) peuvent être extraites avec les fonctions fitted() ou predict() et les prévisions en temps-réel (out of sample) avec la fonction tvCoef::oos_prev().
Pour évaluer la qualité en temps-réel, nous utiliserons les résidus à partir de 2000 :
Pour tracer les prévisions, on peut utiliser la fonction plot() :
plot(window(y, start =2000))lines(prev_oos_lm$prevision, col ="red")legend("bottomleft", legend =c("y","Prev. temps réel"),col=c("black", "red"), lty =1)
1 Régression par morceaux
Pour utiliser la régression par morceaux, la première étape est d’analyser les potentielles dates de rupture. Pour cela nous utiliserons la fonction tvCoef::piece_reg() (qui s’appuie sur le package strucchange, voir ?strucchange::breakpoints() pour plus d’informations).
Exercice
Utiliser la fonction tvCoef::piece_reg() sur le modèle précédent et regarder les résultats : y a-t-il des dates de ruptures ? Peuvent-elles être interprétées ?
En utilisant notamment oos_prev(), comparer la qualité prédictive des modèles.
Solution
reg_morceaux <-piece_reg(model_c5)# Ici une date de rupture# Si pas de rupture détectée, le modèle renvoyé est le modèle initialreg_morceaux
IS OOS
rmse_lm 0.6990756 0.8494077
rmse_rm 0.5731039 1.3826525
Elle sont ici réduites dans l’échantillon mais augmentent en temps réel ! En regardant plus précisément, cela vient d’erreurs élevées autour de la date de rupture car l’estimation n’est pas suffisamment robuste !
plot(res_rm_oos, col ="red", main ="Résidus en temps-réel")lines(res_lm_oos, col ="black")legend("topleft", legend =c("LM","Reg. par morceaux"),col=c("black", "red"), lty =1)
En analysant les prévisions à partir de 2010, les erreurs sont réduites par rapport à précédemment mais restent plus élevées que celles de la régression linéaire :
Le modèle précédent suppose une rupture sur toutes les variables : est-ce réaliste dans ce cas ? Appliquer la fonction tvCoef::hansen.test() sur votre modèle et interpréter les résultats.
Le test de Hansen conclut que seul l’acquis d’IPI évolue. Attention à l’interprétation du test sur la constante : si cette variable évolue il est possible que la constante aussi.
On peut également faire des tests de Fisher sur le modèle précédent pour tester si les coefficients sont égaux entre les sous-périodes. Cela peut être fait avec la fonction car::linearHypothesis() :
# on rejette H0 => non constance des coefficientscar::linearHypothesis(reg_morceaux$model, "`(Intercept)_2008.75` = `(Intercept)_2019.75`")
Linear hypothesis test
Hypothesis:
(Intercept)_2008.75` - Intercept)_2019.75` = 0
Model 1: restricted model
Model 2: y ~ 0 + (`(Intercept)_2008.75` + overhang_ipi1_c5_2008.75 + insee_bc_c5_m3_2008.75 +
insee_oscd_c5_m2_2008.75 + `diff(insee_tppre_c5_m3, 1)_2008.75` +
`diff(bdf_tuc_c5_m2, 1)_2008.75` + `(Intercept)_2019.75` +
overhang_ipi1_c5_2019.75 + insee_bc_c5_m3_2019.75 + insee_oscd_c5_m2_2019.75 +
`diff(insee_tppre_c5_m3, 1)_2019.75` + `diff(bdf_tuc_c5_m2, 1)_2019.75`)
Res.Df RSS Df Sum of Sq F Pr(>F)
1 96 37.058
2 95 35.144 1 1.9145 5.1751 0.02516 *
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# on rejette H0 => non constance des coefficientscar::linearHypothesis(reg_morceaux$model, "overhang_ipi1_c5_2019.75 = overhang_ipi1_c5_2008.75")
Linear hypothesis test
Hypothesis:
- overhang_ipi1_c5_2008.75 + overhang_ipi1_c5_2019.75 = 0
Model 1: restricted model
Model 2: y ~ 0 + (`(Intercept)_2008.75` + overhang_ipi1_c5_2008.75 + insee_bc_c5_m3_2008.75 +
insee_oscd_c5_m2_2008.75 + `diff(insee_tppre_c5_m3, 1)_2008.75` +
`diff(bdf_tuc_c5_m2, 1)_2008.75` + `(Intercept)_2019.75` +
overhang_ipi1_c5_2019.75 + insee_bc_c5_m3_2019.75 + insee_oscd_c5_m2_2019.75 +
`diff(insee_tppre_c5_m3, 1)_2019.75` + `diff(bdf_tuc_c5_m2, 1)_2019.75`)
Res.Df RSS Df Sum of Sq F Pr(>F)
1 96 44.941
2 95 35.144 1 9.7974 26.484 1.429e-06 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# on ne rejette pas H0 => constance des coefficientscar::linearHypothesis(reg_morceaux$model, "`diff(insee_tppre_c5_m3, 1)_2019.75` = `diff(insee_tppre_c5_m3, 1)_2008.75`")
Linear hypothesis test
Hypothesis:
- diff(insee_tppre_c5_m3,_2008.75` + diff(insee_tppre_c5_m3,_2019.75` = 0
Model 1: restricted model
Model 2: y ~ 0 + (`(Intercept)_2008.75` + overhang_ipi1_c5_2008.75 + insee_bc_c5_m3_2008.75 +
insee_oscd_c5_m2_2008.75 + `diff(insee_tppre_c5_m3, 1)_2008.75` +
`diff(bdf_tuc_c5_m2, 1)_2008.75` + `(Intercept)_2019.75` +
overhang_ipi1_c5_2019.75 + insee_bc_c5_m3_2019.75 + insee_oscd_c5_m2_2019.75 +
`diff(insee_tppre_c5_m3, 1)_2019.75` + `diff(bdf_tuc_c5_m2, 1)_2019.75`)
Res.Df RSS Df Sum of Sq F Pr(>F)
1 96 35.201
2 95 35.144 1 0.056906 0.1538 0.6958
Exercice
En exploitant les résultats de l’exercice précédent, simplifier le modèle de régression par morceaux.
On pourra pour cela utiliser le paramètre fixed_var de piece_reg() pour fixer certaines variables (i.e. : ne pas découper les régresseurs).
Comparer les prévisions avec les modèles précédents.
Solution
Ici nous allons fixer toutes les variables sauf les deux premières (constante + acquis d’IPI – overhang)
reg_morceaux2 <-piece_reg(model_c5, fixed_var =c(3, 4, 5, 6))# Rmq : la date de rupture est détectée sur le modèle complet et non# sur le sous-modèle avec des variables fixesreg_morceaux2
Dans le package ici utilisé (tvReg), le noyau utilisé par défaut est le noyau tricube :
\[
K(x)=\frac{35}{32}\left(
1-
\left\lvert
x
\right\lvert^2
\right)^3 \mathbb 1_{|x| \leq 1}
\text{ et }
K_b(x)=\frac 1 b K(x/b)
\]
Pour estimer le modèle, nous utilisons la fonction tvReg::tvLM dont le premier paramètre est une formule. Mais contrairement à dynlm, tvLM ne gère pas directement les variables en différence :
Error in model.frame.default(formula = prod_c5 ~ overhang_ipi1_c5 + insee_bc_c5_m3 + : les longueurs des variables diffèrent (trouvé pour 'diff(insee_tppre_c5_m3, 1)')
Pour éviter les problèmes liées au variables en différence, nous récupérons les données transformées de dynlm en utilisant la fonction tvCoef::get_data(model_c5), ce qui permet également de simplifier la formule en prod_c5 ~ .(puisque la base de données alors utilisée ne contient que les exogènes utiles).
Exercice
Estimer le modèle en utilisant les indications précédentes.
Quelle fenêtre est utilisée (paramètre \(b\)) ? Est-ce que les résultats sont différents de ceux de la régression linéaire ? Tracer les coefficients obtenus à chaque date (fonction coef() pour les extraire, il faudrait reconvertir le résultat en objet ts())
Comparer les erreurs de prévision dans l’échantillon avec ceux des modèles précédents.
Solution
La fenêtre retenue est de 0,31 : les résultats seront donc différents de ceux de la régression linéaire.
coefs_tvlm <-ts(coef(tvlm), end =end(data), frequency =frequency(data))plot(coefs_tvlm)
Ici toutes les coefficients sont variables alors que certains pourraient être fixes comme vu dans la partie précédente. Pour fixer certaines variables, on pourrait faire une régression en deux étapes.
IS OOS
rmse_lm 0.6990756 0.8494077
rmse_rm 0.5731039 1.3826525
rmse_rm2 0.6003532 0.8747103
rmse_tvlm 0.5971192 NA
Le RMSE dans l’échantillon est réduit.
Exercice
L’objectif de cet exercice est de calculer les prévisions hors échantillon.
Appliquer la fonction tvCoef::oos_prev() au modèle précédent avec les paramètres end = end(data), frequency = frequency(data) (utiles pour garder la structure temporelle des données) : quels sont les paramètres réestimés ?
Appliquer maintenant la même fonction avec le paramètre fixed_bw = TRUE. À quoi cela correspond ? Comparer les erreurs de prévisions obtenus.
Solution
Dans les modèles de régression locales, il y a deux sources de révisions en temps-réel :
Actualisation des coefficients du fait de l’ajout de nouveaux points (noyau asymétrique utilisé pour les premières estimations)
Actualisation de la fenêtre.
Actualisation de la fenêtre et des coefficients
Par défaut, avec oos_prev() tous les paramètres sont réestimés. C’est en particulier le cas de la fenêtre qui est réestimée à chaque date, à chaque observation :
prev_oos_tvlm_all <-oos_prev(tvlm, end =end(data), frequency =frequency(data))
Le RMSE en temps-réel est réduit par rapport à précédemment mais il reste plus élevé qu’avec la régression linéaire.
Remarque
Pour fixer certaines variables, on pourrait faire une régression en deux étapes. La fonction rmse_prev() permet de calculer les prévisions dans l’échantillon et hors échantillon sur le modèle de régression linéaire, la régression par morceaux, la régression locale en fixant ou non certains coefficients.
Sept modèles différents sont estimés, dans l’ordre :
Modèle de régression linéaire.
Régression linéaire par morceaux où toutes les variables divisées en fonction de la date de rupture.
Régression linéaire par morceaux où toutes les variables, sauf celles spécifiées par fixed_var, sont divisées en fonction de la date de rupture.
Régression locale.
Régression locale avec toutes les variables divisées en fonction de la date de rupture.
Régression locale où toutes les variables, sauf celles spécifiées par fixed_var, sont divisées en fonction de la date de rupture.
Régression locale où les variables celles spécifiées par fixed_var sont estimées par une régression linéaire (coefficients fixes sur l’ensemble de la période).
On peut ensuite récupérer tous les résidus en temps réel :
Dans cette dernière partie nous estimons un modèle espace-état avec coefficients qui varient dans le temps.
Pour rappel, puisque nous avons 6 variables exogènes, le modèle s’écrit :
\[
\begin{cases}
y_t=X_t\alpha_t+\varepsilon_t,\quad&\varepsilon_t\sim\mathcal N(0,\sigma^2)\\
\alpha_{t+1}=\alpha_t+\eta_t,\quad&\eta_t\sim\mathcal N(0,\sigma^2 Q)
\end{cases},\text{ avec }\eta_t\text{ et }\varepsilon_t\text{ indépendants et }
Q = \begin{pmatrix}q_1 & &0 \\ & \ddots \\ 0 & & q_6 \end{pmatrix}
\]
La matrice \(Q\) peut-être imposée par l’utilisateur (par exemple variance nulle si l’on veut fixer tous les coefficients) ou estimée.
Il y a également deux opérations classiques :
smoothing : estimation de \(\hat\alpha_t=E[\alpha_t|y]\) et \(V_t=V[\alpha_t-\hat\alpha_t]=V[\alpha_t|y]\) : coefficients et variances estimés en utilisant l’ensemble des données disponibles ;
filtering : estimation de \(a_{t+1}=E[\alpha_{t+1}|Y_t]\) et \(P_{t+1}=V[\alpha_{t+1}|Y_t]\) : coefficients et variances estimés de manière dynamique en utilisant l’information disponible jusqu’à la date précédente (estimation en temps-réel).
Pour estimer ces modèles nous utiliserons la fonction tvCoef::ssm_lm() dont le premier paramètre est un modèle de régression linéaire (le modèle model_c5).
Exercice
Par défaut, tvCoef::ssm_lm() estime le modèle en forçant \(q_1=q_2=\dots=q_6=0\). Quel modèle retrouve-t-on ? Regarder les résultats de cette fonction et interpréter les quantités de "smoothed_states", "filtering_states","smoothed_stdev" (sauf dernière colonne).
Solution
Le modèle estimé est le modèle de régression linéaire !
La composante smoothed_states contient les coefficients du modèle de régression linéaire estimé en utilisant toutes les données. La dernière colonne ("noise") contient les résidus. La composante smoothed_stdev contient les écart-types associés aux différents coefficients, la dernière colonne s’interprète de manière plus complexe et ne sera pas détaillée ici.
mod_ssm <-ssm_lm(model_c5)summary(model_c5)
Time series regression with "ts" data:
Start = 1993(2), End = 2019(4)
Call:
dynlm(formula = prod_c5 ~ overhang_ipi1_c5 + insee_bc_c5_m3 +
insee_oscd_c5_m2 + diff(insee_tppre_c5_m3, 1) + diff(bdf_tuc_c5_m2,
1), data = data)
Residuals:
Min 1Q Median 3Q Max
-2.3241 -0.4566 0.0140 0.4495 1.6115
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -6.383023 3.213333 -1.986 0.04970 *
overhang_ipi1_c5 0.102405 0.022681 4.515 1.72e-05 ***
insee_bc_c5_m3 0.061437 0.029218 2.103 0.03797 *
insee_oscd_c5_m2 -0.005596 0.015019 -0.373 0.71021
diff(insee_tppre_c5_m3, 1) 0.039043 0.012029 3.246 0.00159 **
diff(bdf_tuc_c5_m2, 1) 0.397070 0.077580 5.118 1.48e-06 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 0.7195 on 101 degrees of freedom
Multiple R-squared: 0.7155, Adjusted R-squared: 0.7014
F-statistic: 50.79 on 5 and 101 DF, p-value: < 2.2e-16
La composante filtering_states donne les estimations des coefficients en temps réel : la valeur à la date \(t\) correspond aux estimations des coefficients en utilisant les données jusqu’en \(t-1\). Ainsi, en estimant le modèle jusqu’en 2010T1, les coefficients obtenus sont ceux de la composante filtering_states de 2010T2 :
Les paramètres fixed_var_intercept = FALSE et fixed_var_variables = FALSE permettent d’indiquer que les variances \(q_1,\dots, q_6\) seront estimées. Les paramètres var_intercept et var_variables n’auront généralement aucun impact sur les résultats (puisque dans notre cas les variances sont estimées), ils interviennent toutefois dans le processus algorithmique : les modifier permet dans certains cas d’éviter des erreurs d’optimisation.
Regarder les coefficients model_ssm$smoothed_states : quelles sont les variables qui varient dans le temps ? À partir de model_ssm$fitted, comparer la qualité prédictive de ce modèle avec les précédents.
Solution
Les variables fixes sont la constante et les carnets de commandes globaux :
En réalité, la composante filtering ne correspond pas exactement à de l’estimation en temps-réel car certains paramètres ne sont pas estimés de manière dynamique. Pour avoir une vraie estimation en temps-réel, il faudrait réestimer le modèle à chaque date : on peut pour cela utiliser la fonction ssm_lm_oos(). Pour que cette fonction marche, il faut parfois jouer sur les paramètres var_intercept et var_variables.
On peut enfin faire un graphique avec toutes les prévisions, en utilisant par exemple le package dygraphs :
library(dygraphs)prevs <-ts.intersect(y, y - res_lm_oos, y - res_rm2_oos, y - res_tvlm_lastbw_oos, y - res_ssm_oos)colnames(prevs) <-c("y", "lm", "Reg par morceaux", "Reg locale", "SSM")dygraph(prevs) %>%dyRangeSelector(dateWindow =c("2010-01-01", "2019-12-01")) %>%dyOptions(colors =c("black", "red", "green", "blue", "purple"))
Exercice facultatif
L’objectif de cet exercice est d’estimer un nouveau modèle jusqu’en 2022T4 pour faire une prévision jusqu’en 2023T1 :
Créer des indicatrices sur les 4 premiers trimestres de l’année 2020.
Estimer un nouveau modèle dynlm en ajoutant ces indicatrices.
Estimer un nouveau modèle espace-état.
Utiliser les variables exogènes du modèle jusqu’en 2023T1 (on peut pour cela appliquer la fonction tvCoef::full_exogeneous_matrix() sur le modèle dynlm) et la dernière ligne de la composante "smoothed_states" pour effectuer des prévisions sur 2023T1.
Indice (création des indicatrices)
On pourra utiliser le programme suivant pour créer les indicatrices :
ind <-cbind(time(manufacturing) ==2020, time(manufacturing) ==2020.25, time(manufacturing) ==2020.5,time(manufacturing) ==2020.75)ind <-ts(apply(ind,2, as.numeric), start =start(manufacturing), frequency =4)colnames(ind) <-sprintf("ind2020Q%i", 1:4)data <-ts.union(manufacturing, ind)colnames(data) <-c(colnames(manufacturing), colnames(ind))
Solution
Estimation du modèle de régression linéaire :
ind <-cbind(time(manufacturing) ==2020, time(manufacturing) ==2020.25, time(manufacturing) ==2020.5,time(manufacturing) ==2020.75)ind <-ts(apply(ind,2, as.numeric), start =start(manufacturing), frequency =4)colnames(ind) <-sprintf("ind2020Q%i", 1:4)data <-ts.union(manufacturing, ind)colnames(data) <-c(colnames(manufacturing), colnames(ind))
L’avantage de dynlm par rapport à lm est qu’il permet de gérer directement la différenciation des variables sans avoir à créer de variable temporaire.↩︎