Entamer l’écriture un nouveau papier est l’occasion de revenir sur des méthodes que j’ai déjà utiliser et d’en découvrir de nouvelles. J’en profites pour réviser et partager quelques réflexions techniques. Il s’agit de travailler des données des panels. Bon rien d’original ici. La grande majorité de mes travaux utilisent ce type d’informations notamment au travers de modèles à effets fixes. Le défaut de la méthode est qu’elle nécessite que l’ensemble des informations traitées varies à la fois entre les individus et dans le temps. Une solution que je viens de découvrir au fil des lectures pour mobiliser en complément des variables fixes dans l’analyse est de passer par des modèles hybrides de type between-within. Ceux-ci sont développés par Allison (2009) et Bell et Jones (2015). Mais avant d’entamer leur analyse, débutons une série de postes pour revenir sur quelques basiques concernant les données des panels.
Pour commencer, chargeons quelques packages: le tidyverse, broom, qui permet de gérer les estimations de modèle comme des tibbles, plm, qui permet d’estimer les modèles de panel classiques, et panelr, qui permet notamment d’estimer les modèles hybrides.
library(tidyverse)
library(broom)
library(plm)
library(panelr)
library(lmtest)
Chargeons également un jeu de données afin de réaliser nos différentes expérimentations. Utilisons le jeu WageData qui est inclus dans panelr.
wages<-WageData
glimpse(wages)
## Rows: 4,165
## Columns: 14
## $ exp <dbl> 3, 4, 5, 6, 7, 8, 9, 30, 31, 32, 33, 34, 35, 36, 6, 7, 8, 9, 10,…
## $ wks <dbl> 32, 43, 40, 39, 42, 35, 32, 34, 27, 33, 30, 30, 37, 30, 50, 51, …
## $ occ <dbl> 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
## $ ind <dbl> 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0…
## $ south <dbl> 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ smsa <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1…
## $ ms <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0…
## $ fem <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1…
## $ union <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0…
## $ ed <dbl> 9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12,…
## $ blk <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1…
## $ lwage <dbl> 5.56068, 5.72031, 5.99645, 5.99645, 6.06146, 6.17379, 6.24417, 6…
## $ t <dbl> 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7, 1…
## $ id <dbl> 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4…
La base comprend 4 165 observations sur 14 variables. Deux d’entre-elles structurent l’ensemble id, identifiant des individus sur lesquels l’information est collectée, et t, qui marque la date de cette collecte.
paste('On a',length(unique(wages$id)),'individus')
## [1] "On a 595 individus"
paste('observés sur',length(unique(wages$t)),'périodes')
## [1] "observés sur 7 périodes"
paste('soit',595*7,'observations')
## [1] "soit 4165 observations"
Le panel est cylindré. La variable que l’on va expliquer ici est lwage, le log du salaire sur la période. Elle sera reprise par la suite comme \(y_{it}\). Voyons à quoi ressemble sa distribution.
wages %>%
summarise(min_=min(lwage),
Prem_Q=quantile(lwage,probs=0.25),
median_=median(lwage),
Trois_Q=quantile(lwage,probs=0.75),
max_=max(lwage),
mean_=mean(lwage),
sd_=sd(lwage))
## min_ Prem_Q median_ Trois_Q max_ mean_ sd_
## 1 4.60517 6.39526 6.68461 6.95273 8.537 6.676346 0.4615122
On a quelque chose proche d’une loi normale. L’hétérogénéité des situations est ici marquée par l’écart type. Cette hétérogénéité a deux sources : la variation des situations individuelles au travers le temps (variance intra-individuelle) et les différences de situations individuelles indépendantes du temps (variance inter-individuelle).
Pour s’en donner une idée, présentons deux graphes s’organisant sur le même principe. On présente la moyenne de la variable expliquée (lwage) et son intervalle de confiance à 95% calculés en fonction du découpage suivi. Pour la premier, consacré à la variation intra-individuelle, le calcule s’opère pour tout les individus (595) sur chaque période.
het_gr <- function(df, var1, var2, titre,mx_){
var1 <- enquo(var1); var2 <- enquo(var2);
df_<-df %>% group_by(!!var1) %>%
summarise(mean_=mean(!!var2),
bas=t.test(!!var2)$conf.int[1],
haut=t.test(!!var2)$conf.int[2],
)
ggplot(data=df_,aes(x=!!var1))+
geom_point(aes(y=mean_),color='red')+
geom_line(aes(y=mean_),color='red',size=0.5)+
geom_segment(aes(xend=!!var1,x=!!var1,yend=haut,y=bas))+
labs(title=titre)+
scale_x_continuous(breaks=mx_)+
theme_minimal()+
theme(plot.title = element_text(hjust=0.5))}
het_gr (df = wages, var1 = t, var2 = lwage,
titre='Hétérogénéité entre les périodes',mx_=1:7)
On voit ici une nette tendance haussière des salaires sur la période d’étude. Le second graphe s’établit sur une base individuel. Les calculs sont fait sur chaque individu (id) pour la période d’étude (7).
het_gr (df = wages, var1 = id, var2 = lwage,
titre='Hétérogénéité entre les individus',mx_=seq(0,600,50))
L’ordre des individus est celui de leur identifiant. Celui-ci est assigné de manière aléatoire. Il n’y a donc pas de tendance qui se dégage. On peut néanmoins observer la variabilité inter-individuelle.
Ceci étant observé, intéressons-nous à la manière dont le nombre de semaines travaillées (wks) influence le salaire (lwage). Cette variable explicative sera nommée de manière générale \(x_{it}\). Le premier réflexe que l’on a souvent est d’ignorer la décomposition de l’hétérogéniété et d’estimer simplement un modèle linéaire classique.
\[y_{it}=\alpha+\beta_{1}.x_{it}+u_{it}\]
Ce type de modèle utilisé sur de données de panel est appelé modèle de pooling. Estimons-le avec la fonction lm().
lm(lwage~wks,data=wages) %>% tidy()
## # A tibble: 2 × 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) 6.43 0.0656 98.1 0
## 2 wks 0.00527 0.00139 3.78 0.000157
Le même résultat est obtenu avec la fonction plm() du package du même nom. Précisons y les variables indexant les individus et les périodes.
g <- plm(lwage ~ wks, data=wages, index=c("id"),model="pooling")
g %>% tidy()
## # A tibble: 2 × 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) 6.43 0.0656 98.1 0
## 2 wks 0.00527 0.00139 3.78 0.000157
Quel que soient la fonction d’estimation utilisée, il s’agit de résumer l’information contenu dans le nuage de points (noirs) à partir de la droite (en rouge) dont nous venons trouver les paramètres.
ggplot(data=wages,aes(x=wks,y=lwage))+
geom_point()+
geom_smooth(method = "lm",color='red',se=FALSE)+
coord_cartesian(expand=FALSE,xlim=c(4,53),ylim=c(4.5,8.7))+
theme_minimal()
cette approche est appropriée si l’erreur \(u_{it}\) est indépendante à la fois du régresseur \(x_{it}\) et de sa composante individuelle de l’erreur \(\gamma_i\) (et/ou sa composante temporelle \(\mu_t\)). Dans le cas contraire, l’estimateur de MCO est inconsistant.
Qu’est-ce que cela signifie d’un point de vue pratique? Simplement que le coefficient estimé via le pooling n’est pas différent (statistiquement) des coefficients estimés sur des bases réduites à chaque individu. Pour le savoir, on procède à un test de Chow (test de stabilité basé sur la statistique de Fisher proposé par Baltagi 2005). Le package plm offre des éléments pour réaliser la procédure. Il y a tout d’abord la fonction pvcm() qui permet de générer les estimations des modèles individus par individus (within intra-individuel).
g_i <- pvcm(lwage~wks, data=wages,index="id", model="within")
g_i$coefficients[1:20,]
## (Intercept) wks
## 1 6.149891 -0.004927473
## 2 6.087555 0.013014641
## 3 7.209684 -0.013861464
## 4 6.838213 -0.009429041
## 5 5.729354 0.025000016
## 6 7.882495 -0.017218049
## 7 5.977440 0.007051670
## 8 6.241745 0.007061485
## 9 10.844273 -0.082230767
## 10 7.335141 -0.019013199
## 11 7.257963 -0.007769501
## 12 11.658995 -0.104738140
## 13 9.833990 -0.055933809
## 14 18.177601 -0.251869798
## 15 7.842392 -0.035667109
## 16 4.293681 0.035446029
## 17 8.650151 -0.036946197
## 18 -13.139425 0.438735008
## 19 6.993980 -0.008029874
## 20 16.573101 -0.195735772
print("reste 575 modèles")
## [1] "reste 575 modèles"
Le résultat des estimations (g_i) peut alors être introduit dans la fonction pooltest() avec le résultat de l’estimation du modèle de pooling (g) pour réaliser le test.
pooltest(g, g_i)
##
## F statistic
##
## data: lwage ~ wks
## F = 9.9386, df1 = 1188, df2 = 2975, p-value < 2.2e-16
## alternative hypothesis: unstability
Le test rejette clairement l’hypothèse d’égalité de coefficients (de stabilité). Le modèle de pooling est donc inconsistant. Le même résultat peut être obtenu (plus rapidement) à partir d’une syntaxe incluant la spécification des modèles testés en lieu est place des objets associés à leur estimation.
pooltest(lwage~wks,data=wages,index='id', model = "within")
##
## F statistic
##
## data: lwage ~ wks
## F = 1.7654, df1 = 594, df2 = 2975, p-value < 2.2e-16
## alternative hypothesis: unstability
Le graphe ci-contre résume bien la situation. Y sont repris de le nuage de point et la droit du modèle de pooling mais aussi une dizaine d’autres droites (tirées au hasard) dont les paramètres sont issus des estimations individuelles.
ggplot(data=wages,aes(x=wks,y=lwage))+
geom_point()+
geom_abline(slope=g_i$coefficients[5,2],
intercept = g_i$coefficients[5,1],color='blue')+
geom_abline(slope=g_i$coefficients[55,2],
intercept = g_i$coefficients[55,1],color='blue')+
geom_abline(slope=g_i$coefficients[555,2],
intercept = g_i$coefficients[555,1],color='blue')+
geom_abline(slope=g_i$coefficients[366,2],
intercept = g_i$coefficients[366,1],color='blue')+
geom_abline(slope=g_i$coefficients[70,2],
intercept = g_i$coefficients[70,1],color='blue')+
geom_abline(slope=g_i$coefficients[66,2],
intercept = g_i$coefficients[66,1],color='blue')+
geom_abline(slope=g_i$coefficients[8,2],
intercept = g_i$coefficients[8,1],color='blue')+
geom_abline(slope=g_i$coefficients[28,2],
intercept = g_i$coefficients[28,1],color='blue')+
geom_abline(slope=g_i$coefficients[82,2],
intercept = g_i$coefficients[82,1],color='blue')+
geom_abline(slope=g_i$coefficients[43,2],
intercept = g_i$coefficients[43,1],color='blue')+
geom_smooth(method = "lm",color='red',se=FALSE)+
coord_cartesian(expand=FALSE,xlim=c(4,53),ylim=c(4.5,8.7))+
theme_minimal()
## `geom_smooth()` using formula 'y ~ x'
La droite en rouge résume difficilement l’information contenue dans les droites en bleu.
Un second test permet de confirmer l’existence d’un problème de variables manquantes et donc la présence d’effets à contrôler. Celui-ci est basé sur le multiplicateur de Lagrange. Il s’agit d’évaluer l’hypothèse nulle postulant que la variance inter- individus (ou période) est nulle et donc qu’il n’y a pas d’effet de panel. Le test est réalisé à partie de la fonction plmtest() de plm. Il suffit d’indiquer le modèle de pooling (ou son équation) et le type de procédure retenue. Ici, nous utiliserons la version la plus ancienne Breusch-Pagan (bp).
plmtest(g, type=c("bp"))
##
## Lagrange Multiplier Test - (Breusch-Pagan) for balanced panels
##
## data: lwage ~ wks
## chisq = 5792.8, df = 1, p-value < 2.2e-16
## alternative hypothesis: significant effects
L’hypothèse d’absence d’effets de panel à prendre en compte est largement rejetée. Ce test est généralement doublé d’un second permettant de comparer le modèle de pooling (MCO simple) avec le modèle à effets fixes (“within”). La fonction pFtest() permet de le mettre en oeuvre en suivant la même type syntaxe. On peut soit indiqué les modèles à comparer avec le modèle “within” en premier et le modèle de pooling en second ou juste l’indication du premier.
pFtest(lwage~wks, data=wages,index='id', effect="individual")
##
## F test for individual effects
##
## data: lwage ~ wks
## F = 16.065, df1 = 594, df2 = 3569, p-value < 2.2e-16
## alternative hypothesis: significant effects
Ici aussi, l’hypothèse d’absence d’effets à prendre en compte est rejetée. Le modèle à effets fixes apparaît meilleur que le modèle de pooling.
Dans les deux cas, H0 est rejetée. Il y a bien un effet de panel. Il reste alors à déterminer qu’elle type d’effets retenir: fixes ou aléatoire.
Estimons les deux et procédons à un test de Durbin–Wu–Hausman afin de déterminer le plus pertinent.
Commençons par le modèle à effets aléatoires.
\[y_{it}=\alpha+\beta_{1}.x_{it}+\mu_i+u_{it}\]
La variabilité intra individuelle est intégrée au terme d’erreur. Il est pour cela nécessaire que l’effet soit à la fois indépendant de la variable explicative et du terme d’erreur du modèle. D’un point de vue pratique, pour l’estimer, il suffit d’utiliser la fonction plm() et de préciser “random” dans l’option model.
alea<-plm(lwage ~ wks, data=wages, index=c("id"),model="random")
alea %>% tidy()
## # A tibble: 2 × 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) 6.61 0.0495 134. 0
## 2 wks 0.00144 0.00100 1.44 0.150
La spécification générale associée aux effets fixes individuels est la suivante:
\[y_{it}=\alpha_i+\beta_{1}.x_{it}+u_{it}\]
La variabilité intra individuelle est prise en compte en faisant varier l’ordonnée à l’origine de modèle. On n’a ainsi un bêta fixe et un alpha différent selon l’individu considéré. Pour l’estimer, il suffit d’utiliser la fonction plm() et de préciser “within” dans l’option model.
wit<-plm(lwage ~ wks, data=wages, index=c("id"),model="within")
wit %>% tidy()
## # A tibble: 1 × 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 wks 0.00101 0.00102 0.988 0.323
Le test est réalisé à partir de la fonction phtest() en intégrant les noms des modèles à comparer.
phtest(wit,alea)
##
## Hausman Test
##
## data: lwage ~ wks
## chisq = 4.3878, df = 1, p-value = 0.0362
## alternative hypothesis: one model is inconsistent
Le modèle à effet aléatoire est diagnostiqué comme inconsistant. Le modèle à effet fixe apparaît ici comme le meilleur. Mais en quoi consiste-t-il au juste?
Dans sa version la plus simple, il s’agit d’une estimation par les MCO d’un modèle incluant une variable indicatrice par individu (id). Ici, on aura donc 595 variables binaire supplémentaire (moins une si on garde la constante dans le modèle).
dum_mod<-lm(lwage~wks+factor(id)-1,data=wages)
dum_mod%>% tidy()
## # A tibble: 596 × 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 wks 0.00101 0.00102 0.988 0.323
## 2 factor(id)1 5.93 0.105 56.3 0
## 3 factor(id)2 6.47 0.103 62.6 0
## 4 factor(id)3 6.46 0.111 58.3 0
## 5 factor(id)4 6.34 0.110 57.8 0
## 6 factor(id)5 6.86 0.109 62.8 0
## 7 factor(id)6 7.05 0.109 64.8 0
## 8 factor(id)7 6.26 0.109 57.3 0
## 9 factor(id)8 6.54 0.110 59.3 0
## 10 factor(id)9 6.85 0.110 62.4 0
## # … with 586 more rows
Si cette version permet bien de contrôler des effets fixes individuels et donc de l’inobservable variant en fonction de l’individu et pas dans le temp, elle reste laborieuse à manipuler. Aussi, il existe une alternative plus pratique produisant le même résultat sans les variables indicatrice individuelles : le modèle “within”. Il s’agit de transformer les variables en leur soustrayant leur moyenne calculé sur une base individuel et d’appliquer les MCO sur le résultat de la transformation. On a ainsi:
\[(y_{it}-\bar{y_i})=\alpha_i+\beta_{1}.(x_{it}-\bar{x_i})+(u_{it}-\bar{u_i})\]
Réalisons la transformation “within” sur nos données.
wi_d<-wages %>% group_by(id) %>%
mutate(m_lwage=mean(lwage),m_wks=mean(wks),
w_lwage=lwage-m_lwage,w_wks=wks-m_wks)
Estimons le modèle à partir de la fonction lm().
w_l<-lm(w_lwage~w_wks-1,data=wi_d)
w_l %>% tidy()
## # A tibble: 1 × 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 w_wks 0.00101 0.000945 1.07 0.286
On retrouve bien le même coefficient estimé que dans la version intégrant les variables indicatrices individuelles et dans celle générée à partir de plm(.,model=“within”). Par contre, l’erreur standard est différente. Voyons ce qu’il en est. Commençons par recalculer en détaillant les différentes étapes la statistique pour nos différents modèle.
Voyons d’abord par le dernière modèle. Mais avant, un petit rappel. Le point de départ pour obtenir l’erreur standard des coefficients est la matrice variance covariance du modèle (\(var(\hat{\beta})\)).
\[VAR(\hat{\beta})=\hat{\sigma}^2.(X'.X)^{-1}\]
Celle-ci est diagonalisée et passée à la racine carrée.
\[s.e.(\hat{\beta})=\sqrt{diag(VAR(\hat{\beta}))}\]
La fonction vcoc() permet d’obtenir la matrice à partir du modèle.
vcov(w_l)
## w_wks
## w_wks 8.929782e-07
Mais on peut aussi détailler le calcul permettant d’obtenir ce résultat
invXtX <- solve(crossprod(wi_d$w_wks))
(sum(w_l$residuals^2)/(w_l$df.residual))*invXtX
## [,1]
## [1,] 8.929782e-07
Reste à diagonaliser et passer l’ensemble à la racine carré.
sqrt(diag(vcov(w_l)))
## w_wks
## 0.0009449752
On retrouve bien notre erreur standard. Voyons ce qu’il en est avec les deux autres modèles.
c(sqrt(diag(vcov(wit))),sqrt(diag(vcov(dum_mod)))[1])
## wks wks
## 0.00102071 0.00102071
Nous avons retrouvons bien les mêmes erreurs standards. Ceci étant posé, il nous reste à comprendre comment le modèle “within” ajuste les choses pour produire la même valeur le modèle à variables indicatives. En fait, il va ajuster sa matrice variance covariance de manière à ce que les mêmes restrictions en matière de degrés de liberté des résidus soit considéré. Pour rappel, dans le modèle dum_mod nous avons un paramètre par individu donc 595 et un paramètre pour wks. On a donc 4165-596 degrés de liberté soit 3569.
invXtX<-solve(crossprod(wi_d$w_wks))
var_b<-(sum(w_l$residuals^2)/(3569))*invXtX
sqrt(diag(var_b))
## [1] 0.00102071
Bon voilà, je pense que ce premier poste est déjà assez long. (suite au prochain numéro… On tient peut être là une série estivale? qui sait?)