Le but de ce tutoriel est de faire un TP de modèle linéaire en python, en retrouvant tout les sorties de la fonction lm
usuelle
rstudio
reticulate
On peut appeler python depuis Rstudio
grâce au package reticulate
.
library(reticulate)
Après avoir chargé ce package, toute instruction en python sera reconnue comme tel dans la console!
R
Pour installer les librairies, on utilisera les instructions R
suivantes:
# Run only once
install_miniconda() ## pour installer miniconda
## [1] "/github/home/.local/share/r-miniconda"
py_install("pandas") # Pour la manipulation de données
py_install("numpy") # Librairie scientifique
py_install("scipy") # Librairie scientifique
py_install("scikit-learn") # Librairie de machine learning
py_install("seaborn") # Librairie graphique
py_install("statsmodels") # Librairie de modèles statistiques
py_install("yellowbrick") # Librairie graphique
Ceci étant fait, on peut ne plus faire que du python!
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import sklearn as sk
import numpy as np
import statsmodels.api as sm
import statsmodels.formula.api as smf
import scipy.stats as stats
from yellowbrick.datasets import load_concrete
from yellowbrick.regressor import ResidualsPlot
from yellowbrick.regressor import CooksDistance
from sklearn.linear_model import LinearRegression
from sklearn import datasets
'ggplot') # if you are an R user and want to feel at home
plt.style.use(
from statsmodels.graphics.gofplots import ProbPlot
On utilisera la library de python pandas
pour manipuler les données
Ensuite, on importe la librairie, et on charge les données, à la manière de read.table
.
import pandas
= pandas.read_csv("bats.csv", # Nom du fichier
bats = ";", # Separateur de champs
sep = 3, # On ignore les 3 premieres lignes
skiprows = 0) # La premiere ligne correspond au nom de colonnes
header print(bats)
## Species Diet Clade ... AUD MOB HIP
## 0 Rousettus aegyptiacus 1 I ... 9.88 105.77 125.97
## 1 Epomops franqueti 1 I ... 10.44 107.80 159.80
## 2 Eonycteris spelaea 1 I ... 5.48 67.00 97.70
## 3 Cynopterus sphinx 1 I ... 4.77 65.27 95.40
## 4 Dobsonia praedatrix 1 I ... 7.09 213.43 233.30
## .. ... ... ... ... ... ... ...
## 58 Coleura afra 3 IV ... 4.08 3.98 12.40
## 59 Taphozous saccolaimus 3 IV ... 9.65 10.92 26.38
## 60 Kerivoula papilosa 3 IV ... 4.47 2.52 19.02
## 61 Myotis myotis 3 IV ... 7.50 5.23 16.24
## 62 Miniopterus medius 3 IV ... 4.77 5.31 17.28
##
## [63 rows x 8 columns]
# En-tete des donnees
bats.head()# Dimension
## Species Diet Clade BOW BRW AUD MOB HIP
## 0 Rousettus aegyptiacus 1 I 136.3 2070.00 9.88 105.77 125.97
## 1 Epomops franqueti 1 I 120.0 2210.00 10.44 107.80 159.80
## 2 Eonycteris spelaea 1 I 58.7 1310.00 5.48 67.00 97.70
## 3 Cynopterus sphinx 1 I 48.3 1184.33 4.77 65.27 95.40
## 4 Dobsonia praedatrix 1 I 184.0 3028.00 7.09 213.43 233.30
bats.shape# Type des colonnes
## (63, 8)
bats.dtypes
## Species object
## Diet int64
## Clade object
## BOW float64
## BRW float64
## AUD float64
## MOB float64
## HIP float64
## dtype: object
bats.info()
## <class 'pandas.core.frame.DataFrame'>
## RangeIndex: 63 entries, 0 to 62
## Data columns (total 8 columns):
## # Column Non-Null Count Dtype
## --- ------ -------------- -----
## 0 Species 63 non-null object
## 1 Diet 63 non-null int64
## 2 Clade 63 non-null object
## 3 BOW 63 non-null float64
## 4 BRW 63 non-null float64
## 5 AUD 63 non-null float64
## 6 MOB 63 non-null float64
## 7 HIP 63 non-null float64
## dtypes: float64(5), int64(1), object(2)
## memory usage: 4.1+ KB
Description d’une colonne facteur du data.frame:
"Diet"] = bats["Diet"].apply(str) # Transformation de float a string
bats[# Quelques statistiques bats.Diet.describe()
## count 63
## unique 4
## top 1
## freq 29
## Name: Diet, dtype: object
"Diet"].value_counts() # Comptage des occurences bats[
## 1 29
## 3 27
## 2 5
## 4 2
## Name: Diet, dtype: int64
# On specifie l'ouverture d'une figure
plt.figure() # Toutes les instructions suivantes se superposeront
='BOW', y = 'BRW', kind ="scatter",
bats.plot(x = [8,6],
figsize ="b", alpha = 0.3,
color = 14) fontsize
## <AxesSubplot:xlabel='BOW', ylabel='BRW'>
"BRW vs. BOW",
plt.title(= 24, color="darkred") fontsize
## Text(0.5, 1.0, 'BRW vs. BOW')
"Body weight", fontsize = 18) plt.xlabel(
## Text(0.5, 0, 'Body weight')
"Brain weight", fontsize = 18) plt.ylabel(
## Text(0, 0.5, 'Brain weight')
# On montre la figure plt.show()
## findfont: Font family ['sans-serif'] not found. Falling back to DejaVu Sans.
## findfont: Font family ['sans-serif'] not found. Falling back to DejaVu Sans.
## findfont: Font family ['sans-serif'] not found. Falling back to DejaVu Sans.
Pour extraire une sous partie des données, on peut utiliser la sélection par un vecteur logique.
# bats.Diet recupere la colonne Diet dans bats
= bats.Diet == '1' # Une colonne de condition
condition_phyto = bats.BOW < 400 # Outlier
condition_BOW = bats[condition_phyto & condition_BOW]
batsphyto batsphyto
## Species Diet Clade ... AUD MOB HIP
## 0 Rousettus aegyptiacus 1 I ... 9.88 105.77 125.97
## 1 Epomops franqueti 1 I ... 10.44 107.80 159.80
## 2 Eonycteris spelaea 1 I ... 5.48 67.00 97.70
## 3 Cynopterus sphinx 1 I ... 4.77 65.27 95.40
## 4 Dobsonia praedatrix 1 I ... 7.09 213.43 233.30
## 5 Eidolon helvum 1 I ... 12.77 208.70 258.10
## 7 Macroglossus miniumus 1 I ... 2.40 30.05 52.95
## 8 Syconycteris australis 1 I ... 2.13 31.40 53.10
## 9 Nyctimene albiventer 1 I ... 4.56 68.93 81.40
## 23 Brachyphylla cavernarum 1 II ... 8.63 42.20 78.80
## 24 Lionycteris spurrelli 1 II ... 3.71 10.30 29.50
## 25 Glossophaga soricina 1 II ... 3.74 12.20 35.00
## 26 Leptonycteris curasoae 1 II ... 5.57 18.60 44.95
## 27 Anoura geofroyi 1 II ... 5.20 14.15 41.40
## 28 Phylloderma stenops 1 II ... 10.20 87.40 91.70
## 29 Phyllostomus haustatus 1 II ... 12.74 34.33 65.60
## 30 Mimon crenulatum 1 II ... 5.92 7.30 18.20
## 31 Trachops cirrhosus 1 II ... 16.34 23.50 50.60
## 32 Tonatia bidens 1 II ... 13.37 17.96 28.30
## 33 Vampyrum spectrum 1 II ... 27.60 92.00 110.40
## 34 Micronycteris brachyotis 1 II ... 4.19 13.85 17.10
## 35 Carollia perspicillata 1 II ... 5.27 23.55 40.75
## 36 Rhinophlylla pumilio 1 II ... 4.57 18.80 30.30
## 37 Sturnira lilium 1 II ... 4.77 30.77 49.73
## 38 Artibeus lituratus 1 II ... 7.21 34.38 54.90
## 39 Uroderma bilobatum 1 II ... 5.98 28.70 42.70
## 40 Vampyrops vittatus 1 II ... 11.56 29.22 52.46
## 41 Chiroderma villosum 1 II ... 7.95 28.75 47.58
##
## [28 rows x 8 columns]
scikit-learn
Pour ajuster une regression linéaire, on peut utiliser la librairie scikit-learn
, librairie de machine learning:
import numpy as np
import sklearn.linear_model as sklm
# Les features ("X") doivent être données sous forme de matrice
= np.asarray(batsphyto.BOW).reshape(-1, 1)
X = sklm.LinearRegression()
regression_simple = X, y = batsphyto.BRW)
regression_simple.fit(X
# Coefficients de la régression
## LinearRegression()
= [regression_simple.intercept_, regression_simple.coef_]
beta0, beta1 beta0, beta1
## (346.5451556610758, array([14.50990083]))
Cette librairie ne donnera quasiment aucun diagnostic statistique usuel (test sur les paramètres, intervalles de confiance, etc..)
statsmodels
Pour une approche plus statistique, et proche de celle de R
, on utilisera statsmodels
, dont la documentation est très détaillée
import statsmodels.api as sm
import statsmodels.formula.api as smf
from statsmodels.sandbox.regression.predstd import wls_prediction_std
import matplotlib.pyplot as plt
La syntaxe est très proche de R
!
= smf.ols("BRW ~ BOW", data = batsphyto).fit()
regression print(regression.summary())
## OLS Regression Results
## ==============================================================================
## Dep. Variable: BRW R-squared: 0.978
## Model: OLS Adj. R-squared: 0.977
## Method: Least Squares F-statistic: 1147.
## Date: Thu, 02 Sep 2021 Prob (F-statistic): 4.91e-23
## Time: 10:48:11 Log-Likelihood: -177.41
## No. Observations: 28 AIC: 358.8
## Df Residuals: 26 BIC: 361.5
## Df Model: 1
## Covariance Type: nonrobust
## ==============================================================================
## coef std err t P>|t| [0.025 0.975]
## ------------------------------------------------------------------------------
## Intercept 346.5452 35.492 9.764 0.000 273.590 419.500
## BOW 14.5099 0.429 33.860 0.000 13.629 15.391
## ==============================================================================
## Omnibus: 0.012 Durbin-Watson: 1.654
## Prob(Omnibus): 0.994 Jarque-Bera (JB): 0.099
## Skew: 0.016 Prob(JB): 0.952
## Kurtosis: 2.711 Cond. No. 110.
## ==============================================================================
##
## Notes:
## [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
On peut representer graphiquement la droite de regression ainsi que les intervalles de confiance associés
# Calcul
= sm.graphics.plot_fit(regression, "BOW")
fig plt.show()
## findfont: Font family ['sans-serif'] not found. Falling back to DejaVu Sans.
## findfont: Font family ['sans-serif'] not found. Falling back to DejaVu Sans.
## findfont: Font family ['sans-serif'] not found. Falling back to DejaVu Sans.
On peut tester si une combinaison linéaire des paramètres est égale à 0:
= "Intercept = 0, BOW = 0, BOW - Intercept = 0, 3 * Intercept = 2 * BOW"
combinaisons_lineaires print(regression.t_test(combinaisons_lineaires))
## Test for Constraints
## ==============================================================================
## coef std err t P>|t| [0.025 0.975]
## ------------------------------------------------------------------------------
## c0 346.5452 35.492 9.764 0.000 273.590 419.500
## c1 14.5099 0.429 33.860 0.000 13.629 15.391
## c2 -332.0353 35.775 -9.281 0.000 -405.571 -258.500
## c3 1010.6157 107.040 9.441 0.000 790.592 1230.640
## ==============================================================================
On peut tester les effets
from statsmodels.stats.anova import anova_lm
= smf.ols('BRW ~ BOW + AUD', data = batsphyto).fit()
regression_multiple = anova_lm(regression_multiple, typ = "II") # On peut choisir le type
anovaResults print(anovaResults)
## sum_sq df F PR(>F)
## BOW 1.623042e+07 1.0 788.051516 2.007592e-20
## AUD 7.599175e+03 1.0 0.368970 5.490443e-01
## Residual 5.148909e+05 25.0 NaN NaN
= anova_lm(regression, regression_multiple) # On peut choisir le type
anovaResults print(anovaResults)
## df_resid ssr df_diff ss_diff F Pr(>F)
## 0 26.0 522490.111038 0.0 NaN NaN NaN
## 1 25.0 514890.935969 1.0 7599.175069 0.36897 0.549044
Il semblerait que l’inclusion des variables qualitatives se déroule de la même manière que dans R
. Exemple sur l’ANCOVA:
= smf.ols('BRW ~ Clade * BOW', data = batsphyto).fit()
ancova print(ancova.summary())
## OLS Regression Results
## ==============================================================================
## Dep. Variable: BRW R-squared: 0.979
## Model: OLS Adj. R-squared: 0.977
## Method: Least Squares F-statistic: 380.3
## Date: Thu, 02 Sep 2021 Prob (F-statistic): 2.33e-20
## Time: 10:48:17 Log-Likelihood: -176.38
## No. Observations: 28 AIC: 360.8
## Df Residuals: 24 BIC: 366.1
## Df Model: 3
## Covariance Type: nonrobust
## ===================================================================================
## coef std err t P>|t| [0.025 0.975]
## -----------------------------------------------------------------------------------
## Intercept 379.6310 73.819 5.143 0.000 227.276 531.986
## Clade[T.II] -21.6150 85.999 -0.251 0.804 -199.108 155.878
## BOW 14.5476 0.587 24.803 0.000 13.337 15.758
## Clade[T.II]:BOW -0.8777 1.045 -0.840 0.409 -3.034 1.278
## ==============================================================================
## Omnibus: 1.791 Durbin-Watson: 1.808
## Prob(Omnibus): 0.408 Jarque-Bera (JB): 0.733
## Skew: 0.324 Prob(JB): 0.693
## Kurtosis: 3.457 Cond. No. 350.
## ==============================================================================
##
## Notes:
## [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
Il semblerait que, pour le moment, le calcul des moyennes ajustées doivent se faire à la main!
On accède aux prédictions ou aux résidus avec les différents attributs de l’objet regression
.
# model predictions
= regression.fittedvalues
model_fitted_y # model residuals
= regression.resid model_residuals
Il existe différents graphes de diagnostics qui diffèrent un peu de ceux de R
= plt.figure(figsize=(15,8))
fig 'BOW', fig = fig) sm.graphics.plot_regress_exog(regression,
plt.show()
On peut obtenir le vecteur des distances de Cook grâce à la méthode get_influence
= regression.get_influence().cooks_distance[0] distances_cook
On peut tracer résidus standardisés en fonction du levier grâce à une fonction dédiée, graphique sans légende…
= sm.graphics.influence_plot(regression, criterion="cooks", alpha = 0.005)
fig plt.show()
## findfont: Font family ['sans-serif'] not found. Falling back to DejaVu Sans.
=(6,5)) plt.figure(figsize
= sm.qqplot(regression.resid, stats.t, distargs=(4,), fit=True, line="45")
fig plt.show()
seaborn
# correlation heatmap
=bats[["BRW","BOW","AUD","MOB","HIP"]]
bats3= bats3.corr()
corr = (6,6)) plt.figure(figsize
="RdBu",
sns.heatmap(corr, cmap=corr.columns.values,
xticklabels=corr.columns.values) yticklabels
## <AxesSubplot:>
plt.show()
Attention, la variable groupe doit être en caractère impérativement.
="BOW", y="Clade", data=bats,
sns.boxplot(x=.6, palette="vlag")
width
# Add in points to show each observation
## <AxesSubplot:xlabel='BOW', ylabel='Clade'>
="BOW", y="Clade", data=bats,
sns.stripplot(x=4, color=".3", linewidth=0) size
## <AxesSubplot:xlabel='BOW', ylabel='Clade'>
plt.show()
set(style="ticks", color_codes=True)
sns. sns.pairplot(bats3)
="Clade") sns.pairplot(bats, hue
Attention les intervalles de confiance sont faits par bootstrap!
= (5,5)) plt.figure(figsize
="BOW", y="BRW", data=bats);
sns.regplot(x plt.show()
# le ; est indispensable pour que le graph s'affiche!
="BOW", y="BRW", hue="Diet", data=bats);
sns.lmplot(x plt.show()