FR Tanagra Python StatsModels
FR Tanagra Python StatsModels
FR Tanagra Python StatsModels
1 Objectif
Présentation du package StatsModels pour Python à travers une étude de cas en
économétrie.
Pour le lecteur désireux de faire le lien entre les commandes utilisées dans Python et les
formules économétriques sous-jacentes, je mettrai en référence deux des ouvrages que
j’utilise pour mon cours d’économétrie à l’Université : [Régression], « Econométrie - La
régression linéaire simple et multiple », mai 2015 ; et [Diagnostic], « Pratique de la
Régression Linéaire Multiple - Diagnostic et sélection de variables », mai 2015.
2 Données de modélisation
Nous traitons le fichier « vehicules_1.txt » dans la partie modélisation. Il décrit n = 26
automobiles à partir de leur cylindrée, leur puissance, leur poids et leur consommation.
yi a0 a1 xi1 a2 xi 2 a3 xi 3 i , i 1,, n
L’idée est d’exploiter les données pour estimer au mieux - au sens des moindres carrés
ordinaires (MCO) - le vecteur de paramètres .
Dans l’instruction read_table(), nous indiquons : la première ligne correspond aux noms des
variables (header = 0), la première colonne (n°0) correspond aux identifiants des
observations (index_col = 0).
Nous affichons les dimensions de l’ensemble de données. Nous les récupérons dans des
variables intermédiaires n et p.
#dimensions
print(cars.shape)
#nombre d'observations
n = cars.shape[0]
Les 4 sont numériques (valeurs entières ou réelles). La première (n°0), correspondant aux
identifiants des modèles, n’est pas comptabilisée ici.
cylindree int64
puissance int64
poids int64
conso float64
dtype: object
Nous obtenons :
Index(['Maserati Ghibli GT', 'Daihatsu Cuore', 'Toyota Corolla',
'Fort Escort 1.4i PT', 'Mazda Hachtback V', 'Volvo 960 Kombi aut',
'Toyota Previa salon', 'Renault Safrane 2.2. V',
'Honda Civic Joker 1.4', 'VW Golt 2.0 GTI', 'Suzuki Swift 1.0 GLS',
'Lancia K 3.0 LS', 'Mercedes S 600', 'Volvo 850 2.5', 'VW Polo 1.4 60',
'Hyundai Sonata 3000', 'Opel Corsa 1.2i Eco', 'Opel Astra 1.6i 16V',
'Peugeot 306 XS 108', 'Mitsubishi Galant', 'Citroen ZX Volcane',
'Peugeot 806 2.0', 'Fiat Panda Mambo L', 'Seat Alhambra 2.0',
'Ford Fiesta 1.2 Zetec'],
dtype='object', name='modele')
4 Régression
4.1 Lancement des calculs
#instanciation
reg = smf.ols('conso ~ cylindree + puissance + poids', data = cars)
1
Il est également possible de générer les vecteurs et matrices adéquates à partir d’une formule R en utilisant le
module patsy.
#membres de reg
print(dir(reg))
reg est une instance de la classe ols. Nous avons accès à ses membres avec l’instruction dir()
c.-à-d. ses propriétés et méthodes.
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', '_data_attr', '_df_model', '_df_resid',
'_get_init_kwds', '_handle_data', '_init_keys', 'data', 'df_model', 'df_resid',
'endog', 'endog_names', 'exog', 'exog_names', 'fit', 'fit_regularized', 'formula',
'from_formula', 'hessian', 'information', 'initialize', 'k_constant', 'loglike',
'nobs', 'predict', 'rank', 'score', 'weights', 'wendog', 'wexog', 'whiten']
Nous utilisons la fonction fit() pour lancer le processus de modélisation à partir des données.
#lancement des calculs
res = reg.fit()
L’objet res qui en résulte propose également un grand nombre de propriétés et méthodes.
['HC0_se', 'HC1_se', 'HC2_se', 'HC3_se', '_HCCM', '__class__', '__delattr__',
'__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__',
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__',
'_cache', '_data_attr', '_get_robustcov_results', '_is_nested',
'_wexog_singular_values', 'aic', 'bic', 'bse', 'centered_tss', 'compare_f_test',
'compare_lm_test', 'compare_lr_test', 'condition_number', 'conf_int',
'conf_int_el', 'cov_HC0', 'cov_HC1', 'cov_HC2', 'cov_HC3', 'cov_kwds',
'cov_params', 'cov_type', 'df_model', 'df_resid', 'eigenvals', 'el_test', 'ess',
'f_pvalue', 'f_test', 'fittedvalues', 'fvalue', 'get_influence',
'get_robustcov_results', 'initialize', 'k_constant', 'llf', 'load', 'model',
'mse_model', 'mse_resid', 'mse_total', 'nobs', 'normalized_cov_params',
'outlier_test', 'params', 'predict', 'pvalues', 'remove_data', 'resid',
'resid_pearson', 'rsquared', 'rsquared_adj', 'save', 'scale', 'ssr', 'summary',
'summary2', 't_test', 'tvalues', 'uncentered_tss', 'use_t', 'wald_test', 'wresid']
La présentation est conventionnelle, conforme à ce que l’on observe dans la majorité des
logiciels d’économétrie.
OLS Regression Results
==============================================================================
Dep. Variable: conso R-squared: 0.957
Model: OLS Adj. R-squared: 0.951
Method: Least Squares F-statistic: 154.7
Date: Sun, 27 Sep 2015 Prob (F-statistic): 1.79e-14
Time: 09:35:48 Log-Likelihood: -24.400
No. Observations: 25 AIC: 56.80
Df Residuals: 21 BIC: 61.68
Df Model: 3
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [95.0% Conf. Int.]
------------------------------------------------------------------------------
Intercept 1.2998 0.614 2.117 0.046 0.023 2.577
cylindree -0.0005 0.000 -0.953 0.351 -0.002 0.001
puissance 0.0303 0.007 4.052 0.001 0.015 0.046
poids 0.0052 0.001 6.447 0.000 0.004 0.007
==============================================================================
Omnibus: 0.799 Durbin-Watson: 2.419
Prob(Omnibus): 0.671 Jarque-Bera (JB): 0.772
Skew: -0.369 Prob(JB): 0.680
Kurtosis: 2.558 Cond. No. 1.14e+04
==============================================================================
Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly
specified.
[2] The condition number is large, 1.14e+04. This might indicate that there are
strong multicollinearity or other numerical problems.
L’objet res permet de réaliser des calculs intermédiaires. Certains sont relativement simples.
Dans le code ci-dessous, nous faisons afficher quelques résultats importants (paramètres
estimés, R2) et nous nous amusons à recalculer manuellement la statistique F de
significativité globale. Elle coïncide avec celle directement fournie par res bien évidemment.
#paramètres estimés
print(res.params)
#le R2
print(res.rsquared)
D’autres calculs sont possibles, nous donnant accès à des procédures sophistiquées telles
que les tests de contraintes linéaires sur les coefficients (Régression, section 11.3, page 117 ;
ce test généralise une grande partie des tests sur les coefficients : significativité, conformité
à un standard, comparaisons).
Mettons que nous souhaitons vérifier la nullité de tous les coefficients à l’exception de la
constante. Il s’agit ni plus ni moins du test de significativité globale. L’hypothèse nulle peut
s’écrire sous une forme matricielle2 :
H 0 : Ra 0
Où
0 1 0 0
R 0 0 1 0
0 0 0 1
2
statsmodels s’appuie sur une formulation simplifiée où le vecteur de référence r (Régression, équation 11.5,
page 117) est toujours le vecteur nul c.-à-d. r = 0.
a0
0 1 0 0 0
a
H 0 : 0 0 1 0 1 0
0 0 0 1 a2 0
a
3
a1 0
H 0 : a2 0
a 0
3
Sous Python, nous décrivons la matrice R puis nous appelons la méthode f_test()
#test avec combinaison linéaires de param.
#nullité simultanée de tous les param. sauf la constante
import numpy as np
5 Diagnostic de la régression
5.1 Normalité des résidus
L’inférence dans la régression linéaire multiple (MCO) repose sur l’hypothèse de normalité
des erreurs. Une première vérification importante consiste à vérifier la compatibilité des
résidus (l’erreur observée sur l’échantillon) avec ce présupposé.
Test de Jarque-Bera. Nous utilisons le module stattools de statsmodels pour réaliser le test
de Jarque-Bera, basé sur les indicateurs d’asymétrie (Skewness) et d’aplatissement (Kurtosis)
de la distribution empirique (Diagnostic, section 1.3.3, page 22).
#test de normalité de Jarque-Bera
import statsmodels.api as sm
JB, JBpv,skw,kurt = sm.stats.stattools.jarque_bera(res.resid)
print(JB,JBpv,skw,kurt)
Nous avons respectivement : la statistique de test JB, la probabilité critique associée (p-
value) JBpv, le coefficient d’asymétrie skw et d’aplatissement kurt.
On vérifie très facilement que les indicateurs obtenus (JB, JBpv) sont cohérents avec ceux
affichés par l’objet régression dans son résumé (summary) ci-dessus (page 6). L’hypothèse
de normalité des erreurs est compatible avec les résidus observés au risque 5%.
Le qqplot() confirme à peu près le test de Jarque-Bera, les points sont plus ou moins alignés.
Mais il semble y avoir des problèmes pour les grandes valeurs des résidus. Cela laisse
imaginer l’existence de points atypiques dans nos données.
Nous utilisons un objet dédié, issu du résultat de la régression, pour analyser les points
influents.
#objet pour les mesures d'influences
infl = res.get_influence()
#membres
print(dir(infl))
Levier et résidu standardisé. Nous affichons par exemple les leviers et les résidus
standardisés
#leviers
print(infl.hat_matrix_diag)
#résidus standardisés
print(infl.resid_studentized_internal)
L’intérêt de savoir programmer est que nous pouvons vérifier par le calcul les résultats
proposés par les différentes procédures. Prenons le résidu standardisé (ti), nous pouvons
l’obtenir à partir de résidu observé ˆi y i yˆ i , du levier h i et de la variance estimée de
ˆi
ti
ˆ 1 hi
Les valeurs coïncident exactement avec celles fournies directement la propriété appropriée
(resid_studenized_internal). Heureusement, on aurait été bien ennuyé dans le cas contraire.
Résidus studentisés. Nous réitérons l’opération pour le résidu studentisé (résidu studentisé
externe dans certains logiciels) (Diagnostic, équation 2.6, page 38)
n p2
t i* t i
n p 1 t i2
p 1
sh 2
n
#identification
atyp_levier = leviers > seuil_levier
print(atyp_levier)
Python nous affiche un vecteur de booléens, les True correspondent aux observations
atypiques.
[ True False False False False False False False False False False False
True False False False False False False False False False False False False]
La lecture n’est pas très aisée. Il est plus commode de faire afficher les identifiants des
véhicules incriminés.
#quels véhicules ?
print(cars.index[atyp_levier],leviers[atyp_levier])
Résidus studentisés. Le seuil est défini par le quantile de la loi de Student à (n-p-2) degrés de
liberté (Diagnostic, page 39)
st t10.05 (n p 2)
2
t i* st
#seuil résidus studentisés
import scipy
seuil_stud = scipy.stats.t.ppf(0.975,df=n-p-2)
print(seuil_stud)
#lequels ?
print(cars.index[atyp_stud],res_studs[atyp_stud])
Soit,
Index(['Hyundai Sonata 3000', 'Mitsubishi Galant'], dtype='object', name='modele')
[ 2.33908893 -2.57180996]
Combinaison des critères levier et résidu studentisé. En combinant les deux approches avec
l’opérateur logique OR
#problématique avec un des deux critères
pbm_infl = np.logical_or(atyp_levier,atyp_stud)
print(cars.index[pbm_infl]))
Autres critères. L’objet propose d’autres critères tels que les DFFITS, les distance de Cook ou
encore les DFBETAS (Régression, section 2.5, page 41 et suivantes).
p 1
DFFITSi 2 pour les DFFITS ;
n
4
Di pour la distance de Cook.
n p 1
hat_diag student_resid dffits cooks_d
modele
Maserati Ghibli GT 0.721839 1.298823 2.092292 1.059755
Daihatsu Cuore 0.174940 0.701344 0.322948 0.026720
Toyota Corolla 0.060524 -0.708326 -0.179786 0.008277
Fort Escort 1.4i PT 0.059843 0.804633 0.203004 0.010479
Mazda Hachtback V 0.058335 0.105469 0.026251 0.000181
Volvo 960 Kombi aut 0.098346 0.933571 0.308322 0.023912
Toyota Previa salon 0.306938 0.603915 0.401898 0.041640
Renault Safrane 2.2. V 0.094349 0.845397 0.272865 0.018870
Honda Civic Joker 1.4 0.072773 -1.303472 -0.365168 0.032263
VW Golt 2.0 GTI 0.052438 0.815140 0.191757 0.009342
Suzuki Swift 1.0 GLS 0.112343 -0.473508 -0.168453 0.007366
Lancia K 3.0 LS 0.080713 -0.888366 -0.263232 0.017498
Mercedes S 600 0.723258 -1.525140 -2.465581 1.429505
Volvo 850 2.5 0.053154 0.460959 0.109217 0.003098
VW Polo 1.4 60 0.088934 -0.648581 -0.202639 0.010557
Hyundai Sonata 3000 0.250186 2.339089 1.351145 0.376280
Opel Corsa 1.2i Eco 0.101122 0.612009 0.205272 0.010858
Opel Astra 1.6i 16V 0.054008 -1.511576 -0.361172 0.030731
Peugeot 306 XS 108 0.051216 0.834622 0.193913 0.009538
Mitsubishi Galant 0.107722 -2.571810 -0.893593 0.157516
Citroen ZX Volcane 0.055670 -0.252637 -0.061340 0.000985
Peugeot 806 2.0 0.168849 -0.554893 -0.250103 0.016171
Fiat Panda Mambo L 0.132040 0.839201 0.327318 0.027167
Seat Alhambra 2.0 0.244428 0.263126 0.149659 0.005859
Ford Fiesta 1.2 Zetec 0.076033 -0.986712 -0.283049 0.020054
Remarque : Détecter les points atypiques ou influents est une chose, les traiter en est une
autre. En effet, on ne peut pas se contenter de les supprimer systématiquement. Il faut
identifier pourquoi une observation pose problème, puis déterminer la solution la plus
adéquate, qui peut être éventuellement la suppression, mais pas systématiquement. En
effet, prenons une configuration simple. Un point peut être atypique parce qu’il prend une
valeur inhabituelle sur une variable. Si le processus de sélection des exogènes entraîne son
exclusion du modèle, que faut-il faire alors ? Réintroduire le point ? Laisser tel quel ? Il n’y a
pas de solution préétablie. Le processus de modélisation est par nature exploratoire.
6 Détection de la colinéarité
La corrélation entre les exogènes, et de manière plus générale la colinéarité (il existe
combinaison linéaire de variables qui est - presque - égale à une constante), fausse
l’inférence statistique, notamment parce qu’elle gonfle la variance estimée des coefficients.
Il y a différentes manières de l’identifier et de la traiter. Nous nous contentons de quelques
techniques de détection basées sur l’analyse de la matrice des corrélations dans cette partie.
Matrice des corrélations. Une règle simple de vérification est de calculer les corrélations
croisées entre exogènes et de comparer leur valeur absolue avec 0.8 (« Colinéarité et
sélection de variables », page 5). Sous Python, nous isolons les exogènes dans une matrice
au format numpy. Nous utilisons par la suite la corrcoef() de la librairie scipy.
#matrice des corrélations avec scipy
import scipy
mc = scipy.corrcoef(cars_exog,rowvar=0)
print(mc)
La cylindrée et la puissance du moteur sont liés, on comprend très bien pourquoi. Elles sont
également liées avec le poids des véhicules. Oui, les véhicules lourds ont généralement
besoin de plus de puissance.
Règle de Klein. Elle consiste à comparer le carré des corrélations croisées avec le R² de la
régression. Elle est plus intéressante car elle tient compte des caractéristiques numériques
du modèle. Dans notre cas, R² = 0.957.
#règle de Klein
mc2 = mc**2
print(mc2)
Aucune des valeurs n’excède le R², mais il y a des similarités inquiétantes néanmoins.
[[ 1. 0.89686872 0.76530582]
[ 0.89686872 1. 0.68552706]
[ 0.76530582 0.68552706 1. ]]
Facteur d’inflation de la variance (VIF). Ce critère permet d’évaluer la liaison d’une exogène
avec l’ensemble des autres variables. On dépasse la simple corrélation ici. On le lit sur la
diagonale de l’inverse de la matrice de corrélation (Régression, pages 53 et 54).
#critère VIF
vif = np.linalg.inv(mc)
print(vif)
Une variable pose problème dès lors que VIF > 4 (en étant très restrictif). Nous avons
manifestement des problèmes dans nos données.
[[ 12.992577 -9.20146328 -3.7476397 ]
[ -9.20146328 9.6964853 0.02124552]
[ -3.7476397 0.02124552 4.26091058]]
Dans notre cas, il s’agit avant tout d’un exercice de style. Nous avions mis de côté un certain
nombre d’observations au préalable. Nous disposons donc à la fois les valeurs des exogènes
et mais aussi de l’endogène. Nous pourrions ainsi vérifier la fiabilité de notre modèle en
confrontant les valeurs prédites et observées. C’est une procédure de validation externe en
quelque sorte. Elle est très pratiquée dans la communauté machine learning (apprentissage
supervisé), moins en économétrie3.
3
Durant mes études d’économétrie, je n’ai jamais entendu parler de l’idée d’appliquer en aveugle les modèles
sur un échantillon test pour en mesurer les performances prédictives. Je me rends compte qu’on en parle un
peu plus aujourd’hui dans les ouvrages, notamment en faisant référence à la validation croisée ou à des
indicateurs de type PRESS.
Pour ces n*=6 automobiles, nous devons calculer les prédictions en utilisant les coefficients
estimés du modèle et les valeurs observées des exogènes. Ensuite, nous les comparerons
avec les valeurs réelles de consommation pour en évaluer la qualité.
La structure du fichier (index, colonnes) est identique à notre ensemble de données initial.
cylindree puissance poids conso
modele
Nissan Primera 2.0 1997 92 1240 9.2
Fiat Tempra 1.6 Liberty 1580 65 1080 9.3
Opel Omega 2.5i V6 2496 125 1670 11.3
Subaru Vivio 4WD 658 32 740 6.8
Seat Ibiza 2.0 GTI 1983 85 1075 9.5
Ferrari 456 GT 5474 325 1690 21.3
La prédiction ponctuelle est obtenue en appliquant les coefficients estimés sur la description
(valeurs des exogènes) de l’observation à traiter. Passer par une opération matricielle est
conseillée lorsque le nombre d’observations à manipuler est élevé.
Pour un ensemble d’individus, la formule s’écrit (Régression, section 12.1, page 125) :
y* X * aˆ
Où â est le vecteur des coefficients estimés, de dimension (p+1). Attention, pour que
l’opération soit cohérente, il faut ajouter la constante (valeur 1) à la matrice X* des n*
individus à traiter.
Sous Python, nous devons tout d’abord former la matrice X* en lui adjoignant la
constante avec la commande add_constant() :
#exogènes du fichier
cars2_exog = cars2[['cylindree','puissance','poids']]
plt.xlim(5,22)
plt.ylim(5,22)
plt.show()
La prédiction est plus ou moins crédible pour 5 points. Pour le 6ème, la Ferrari, le modèle
sous-estime fortement la consommation (17.33 vs. 21.3).
Une prédiction ponctuelle donne un ordre d’idées. Une prédiction par intervalle est toujours
préférable parce que l’on peut associer un risque (de se tromper) à la prise de décision.
Variance de l’erreur de prédiction. Le calcul est assez complexe (Régression, section 12.2,
page 126). Elle nécessite le calcul de la variance de l’erreur de prédiction. Pour l’individu i*
(équation 12.2), la formule s’écrit :
ˆ2ˆ ˆ2 1 X i* X ' X 1 X 'i*
i*
Où certaines informations sont extraites du modèle estimé et directement accessibles avec
les objets reg et res (sections 4.1 et 4.2) : ˆ 2 est la variance de l’erreur, X ' X 1 est issue
des données ayant servi à élaboré le modèle. D’autres informations sont relatives à l’individu
i* à traiter : X i* est la description de l’individu incluant la constante. Par exemple, pour le
premier individu (Nissan Primera 2.0), nous aurons le vecteur (1 ; 1997 ; 92 ; 1240).
Nous avons :
[ 0.51089413 0.51560511 0.56515759 0.56494062 0.53000431 1.11282003]
Intervalle de confiance. Il ne nous reste plus qu’à calculer les bornes basses et hautes de
l’intervalle de prédiction à 95% (équation 12.4) en exploitant la prédiction ponctuelle et le
quantile de la loi de Student.
#quantile de la loi de Student pour un intervalle à 95%
qt = scipy.stats.t.ppf(0.975,df=n-p-1)
#borne basse
yb = pred_conso - qt * np.sqrt(var_err)
print(yb)
#borne haute
yh = pred_conso + qt * np.sqrt(var_err)
print(yh)
Confrontation avec les valeurs réelles. La confrontation prend une dimension concrète ici
(on ne juge plus sur une impression) puisqu’il s’agit de vérifier que les fourchettes
contiennent la vraie valeur de l’endogène. Nous organisons la présentation de manière à
faire encadrer les valeurs observées par les bornes basses et hautes des intervalles.
8 Conclusion
Le package StatsModels offre nativement des fonctionnalités intéressantes pour la
modélisation statistique. Couplé avec le langage Python et les autres packages (numpy,
scipy, pandas, etc.), le champ des possibilités devient immense. On retrouve très facilement
ici les reflexes et les compétences que nous avons pu développer sous R. Le plus dur
finalement reste l’accès à la documentation. J’ai du batailler pour trouver la bonne
commande à chaque étape. Les tutoriels en français sont quasiment inexistants à ce jour (fin
septembre 2015). On va essayer d’arranger cela.