Environnement et packages¶

In [133]:
# activer l'environnement
using Pkg
Pkg.activate("env_julia_regression")
  Activating project at `c:\Users\ricco\Desktop\demo\env_julia_regression`
In [134]:
# liste des packages installés
Pkg.status()
Status `C:\Users\ricco\Desktop\demo\env_julia_regression\Project.toml`
  [336ed68f] CSV v0.10.16
  [13f3f980] CairoMakie v0.15.11
  [a93c6f00] DataFrames v1.8.2
  [31c24e10] Distributions v0.25.126
  [38e38edf] GLM v1.9.5
  [7073ff75] IJulia v1.34.4
  [43a3c2be] PairPlots v3.0.3
  [f3b207a7] StatsPlots v0.15.8

Importation des données¶

In [135]:
# packages
import DataFrames as DFR
import CSV

# lecture des données
df_all = CSV.read("vehicules_1.txt", DFR.DataFrame ; delim = "\t")

# premières lignes
DFR.first(df_all,5)
5×5 DataFrame
Rowmodelecylindreepuissancepoidsconso
String31Int64Int64Int64Float64
1Maserati Ghibli GT2789209148514.5
2Daihatsu Cuore846326505.7
3Toyota Corolla13315510107.1
4Fort Escort 1.4i PT13905411108.6
5Mazda Hachtback V2497122133010.8
In [136]:
# isoler les variables actives
df = DFR.select(df_all,DFR.Not(:modele))

# description
# on obtient notamment le type interne des variables (eltype)
DFR.describe(df)
4×7 DataFrame
Rowvariablemeanminmedianmaxnmissingeltype
SymbolFloat64RealFloat64RealInt64DataType
1cylindree2028.648461984.059870Int64
2puissance91.442985.03000Int64
3poids1257.86501155.022500Int64
4conso9.6485.79.018.70Float64
In [137]:
# dimensions
println(DFR.size(df))

# nombre d'observations
n = DFR.nrow(df)
println(n)

# nombre de variables explicatives
p = DFR.ncol(df) - 1
println(p)
(25, 4)
25
3
In [138]:
# inspection rapide des données - boxplot avec StatsPlots
import StatsPlots as STP
# un boxplot par variable
p1 = STP.boxplot(df.cylindree,title="Cylindree",label="")
p2 = STP.boxplot(df.puissance,title="Puissance",label="")
p3 = STP.boxplot(df.poids,title="Poids",label="")
p4 = STP.boxplot(df.conso,title="Conso",label="")

# affichage dans une grille (2 x 2)
STP.plot(p1,p2,p3,p4,layout=(2,2),size=(600,600))
No description has been provided for this image
In [139]:
# et un pairplot (PairPlots)
import PairPlots as PP
import CairoMakie as CM
PP.pairplot(df => (PP.Scatter(markersize=8),
                    PP.MarginHist(),
                    PP.PearsonCorrelation()
                   ))
No description has been provided for this image

Régression linéaire multiple¶

Lancement des calculs¶

In [140]:
# importer la librairie
import GLM
In [141]:
# définir et lancer la régression
modele = GLM.lm(GLM.@formula(conso ~ cylindree + puissance + poids), df)

# affichage
display(modele)
StatsModels.TableRegressionModel{GLM.LinearModel{GLM.LmResp{Vector{Float64}}, GLM.DensePredChol{Float64, LinearAlgebra.CholeskyPivoted{Float64, Matrix{Float64}, Vector{Int64}}}}, Matrix{Float64}}

conso ~ 1 + cylindree + puissance + poids

Coefficients:
─────────────────────────────────────────────────────────────────────────────────
                    Coef.   Std. Error      t  Pr(>|t|)    Lower 95%    Upper 95%
─────────────────────────────────────────────────────────────────────────────────
(Intercept)   1.29984      0.614073      2.12    0.0464   0.0228017   2.57687
cylindree    -0.000473994  0.000497236  -0.95    0.3513  -0.00150805  0.000560066
puissance     0.0302691    0.0074708     4.05    0.0006   0.0147327   0.0458054
poids         0.00520108   0.000806704   6.45    <1e-05   0.00352345  0.00687872
─────────────────────────────────────────────────────────────────────────────────

Accès aux résultats détaillés¶

In [142]:
# informations

# R2
println("R2 = $(GLM.r2(modele))")
# R2 ajusté
println("R2-adj = $(GLM.adjr2(modele))")
# AIC
println("AIC = $(GLM.aic(modele))")
# BIC
println("BIC = $(GLM.bic(modele))")
# degrés de liberté des résidus
println("ddl modele = $(GLM.dof_residual(modele))")
R2 = 0.9567033306282597
R2-adj = 0.9505180921465826
AIC = 58.80056270070373
BIC = 64.89494182504473
ddl modele = 21.0
In [143]:
# SCT
println("SCT = $(GLM.nulldeviance(modele))")
# SCR
println("SCR = $(GLM.deviance(modele))")
# Test F de significativité globale
dev_modele = GLM.deviance(modele)
dev_nulle = GLM.nulldeviance(modele)
FTest = ((dev_nulle - dev_modele)/p)/(dev_modele/GLM.dof_residual(modele))
println("F satistic = $FTest")
SCT = 238.10239999999996
SCR = 10.30904088941784
F satistic = 154.6752535835679
In [144]:
# et pour obtenir la p-value
import Distributions as distrib
p_value = distrib.ccdf(distrib.FDist(p,GLM.dof_residual(modele)),FTest)
println("p_value = $p_value")
p_value = 1.7868899968536525e-14

Comparaison de modèles¶

In [145]:
# régression avec puissance et poids
modele_simple = GLM.lm(GLM.@formula(conso ~ puissance + poids), df)
display(modele_simple)
StatsModels.TableRegressionModel{GLM.LinearModel{GLM.LmResp{Vector{Float64}}, GLM.DensePredChol{Float64, LinearAlgebra.CholeskyPivoted{Float64, Matrix{Float64}, Vector{Int64}}}}, Matrix{Float64}}

conso ~ 1 + puissance + poids

Coefficients:
────────────────────────────────────────────────────────────────────────────
                  Coef.   Std. Error     t  Pr(>|t|)   Lower 95%   Upper 95%
────────────────────────────────────────────────────────────────────────────
(Intercept)  1.3593      0.609627     2.23    0.0363  0.0950152   2.62359
puissance    0.0244309   0.00426939   5.72    <1e-05  0.0155767   0.033285
poids        0.00481375  0.000695453  6.92    <1e-06  0.00337147  0.00625603
────────────────────────────────────────────────────────────────────────────
In [146]:
# f-test de comparaison des modèles imbriqués
# nullité du coefficient de cylindree
GLM.ftest(modele_simple.model,modele.model)
F-test: 2 models fitted on 25 observations
────────────────────────────────────────────────────────────────
     DOF  ΔDOF      SSR     ΔSSR      R²     ΔR²      F*   p(>F)
────────────────────────────────────────────────────────────────
[1]    4        10.7551           0.9548                        
[2]    5     1  10.3090  -0.4461  0.9567  0.0019  0.9087  0.3513
────────────────────────────────────────────────────────────────

Diagnostic de la régression¶

Graphique des résidus¶

In [147]:
# accès aux résidus
residus = GLM.residuals(modele)
# somme des carrés = SCR
sum(residus .^ 2)
10.30904088941784
In [148]:
# graphique des résidus (CairoMakie)
fig = CM.Figure()
ax = CM.Axis(fig[1,1],title = "Graphique des résidus")
CM.scatter!(df.conso,residus,markersize=7,color=:gray)
CM.hlines!([0],linewidth=2,color=:blue)
display(fig)
No description has been provided for this image
CairoMakie.Screen{IMAGE}

Points atypiques et influents¶

In [149]:
# matrice des explicatives incluant la constante
X = GLM.modelmatrix(modele)
X
25×4 Matrix{Float64}:
 1.0  2789.0  209.0  1485.0
 1.0   846.0   32.0   650.0
 1.0  1331.0   55.0  1010.0
 1.0  1390.0   54.0  1110.0
 1.0  2497.0  122.0  1330.0
 1.0  2473.0  125.0  1570.0
 1.0  2438.0   97.0  1800.0
 1.0  2165.0  101.0  1500.0
 1.0  1396.0   66.0  1140.0
 1.0  1984.0   85.0  1155.0
 â‹®                   
 1.0  1195.0   33.0   895.0
 1.0  1597.0   74.0  1080.0
 1.0  1761.0   74.0  1100.0
 1.0  1998.0   66.0  1300.0
 1.0  1998.0   89.0  1140.0
 1.0  1998.0   89.0  1560.0
 1.0   899.0   29.0   730.0
 1.0  1984.0   85.0  1635.0
 1.0  1242.0   55.0   940.0
In [150]:
# calcul (X'X)(-1)
import LinearAlgebra as LA
XtX_inv = LA.inv(X' * X)
XtX_inv
4×4 Matrix{Float64}:
  0.768142      6.31883e-5   0.00213739   -0.000836199
  6.31883e-5    5.03648e-7  -6.20345e-6   -4.11564e-7
  0.00213739   -6.20345e-6   0.000113694   4.05781e-8
 -0.000836199  -4.11564e-7   4.05781e-8    1.32565e-6

Levier¶

In [151]:
# calcul du levier [diagonale de Hat matrix]
# (pas de fonction directe dans GLM)
h = [X[i,:]' * XtX_inv * X[i,:] for i in axes(X,1)]
h
25-element Vector{Float64}:
 0.7218393114156288
 0.17493952912740662
 0.060524388382147576
 0.059843026531024676
 0.058334574215776674
 0.09834564668360823
 0.30693840415549445
 0.09434880660512285
 0.07277268588656306
 0.052437822052703476
 â‹®
 0.10112178818413445
 0.05400760473146257
 0.051215731641375425
 0.10772156064633785
 0.05567033097157892
 0.16884878723671604
 0.1320403074389517
 0.2444283179546619
 0.07603256793299977
In [152]:
# point suspect si levier > (2(p+1))/n
seuil_h = 2.0*(p+1)/n
seuil_h
0.32
In [153]:
# véhicules avec leviers élevés
df_all.modele[h .> seuil_h]
2-element Vector{InlineStrings.String31}:
 "Maserati Ghibli GT"
 "Mercedes S 600"

Résidus standardisés¶

In [154]:
# écart-type des résidus la régression
# sqrt() de SCR / degrés de libertés
sigma_err = sqrt(GLM.deviance(modele)/GLM.dof_residual(modele))
sigma_err
0.7006473499699383
In [155]:
# résidus standardisés
r_stand = residus ./ (sigma_err .* sqrt.(1.0 .- h))
r_stand
25-element Vector{Float64}:
  1.2780867370612505
  0.7099857724709607
 -0.7168816533538167
  0.8114738324543007
  0.10804304213931906
  0.9364392287014734
  0.6132625750534932
  0.851199122920511
 -1.282304557644014
  0.8217310425248503
  â‹®
  0.6213315630020293
 -1.4673536176653375
  0.8407170734490949
 -2.2845031425763938
 -0.2584637430707878
 -0.5642691516935364
  0.8451731756954376
  0.2691584061811583
 -0.9873325108997584

Résidus studentisés¶

In [156]:
# résidus studentisés à partir
# des résidus standardisés et des leviers
r_stud = r_stand .* sqrt.((n-p-2)./((n-p-1).-r_stand.^2))
r_stud
25-element Vector{Float64}:
  1.2988225490017054
  0.7013437529280638
 -0.7083257403648012
  0.804633149800432
  0.10546853024876957
  0.9335710001268489
  0.6039152104492624
  0.845397203104742
 -1.3034722759858302
  0.8151396128568138
  â‹®
  0.6120090372666391
 -1.5115764906124813
  0.8346216631144779
 -2.5718099554542344
 -0.2526369399270822
 -0.5548929864068488
  0.8392008070906738
  0.26312596913356173
 -0.9867117065288005
In [157]:
# calculer le seuil avec la loi de Student
# quantile à 95% bilatéral (c.-à-d. à 0.975)
seuil_r_stud = distrib.quantile(distrib.TDist(n-p-2),0.975)
seuil_r_stud
2.0859634472658644
In [158]:
# atypiques au sens des résidus studentisé
df_all.modele[abs.(r_stud) .> seuil_r_stud]
2-element Vector{InlineStrings.String31}:
 "Hyundai Sonata 3000"
 "Mitsubishi Galant"
In [159]:
# visualiser dans le graphique des résidus
fig = CM.Figure()
ax = CM.Axis(fig[1,1],title = "Graphique des résidus studentisés")
CM.scatter!(df.conso,r_stud,markersize=7,color=:gray)
CM.hlines!([0],linewidth=1,color=:blue)
CM.hlines!([-seuil_r_stud],linewidth=2,color=:red,linestyle=:dash)
CM.hlines!([+seuil_r_stud],linewidth=2,color=:red,linestyle=:dash)
display(fig)
No description has been provided for this image
CairoMakie.Screen{IMAGE}
In [160]:
# même graphique en faisant apparaître le nom des modèles
fig = CM.Figure()
ax = CM.Axis(fig[1,1],title = "Graphique des résidus studentisés")
CM.scatter!(df.conso,r_stud,markersize=7,color=:silver)
CM.hlines!([0],linewidth=1,color=:blue)
CM.hlines!([-seuil_r_stud],linewidth=2,color=:orange,linestyle=:dash)
CM.hlines!([+seuil_r_stud],linewidth=2,color=:orange,linestyle=:dash)
# pour l'ensemble des voitures suspectes
for voiture in df_all.modele[abs.(r_stud) .> seuil_r_stud]
    id = findfirst(==(voiture),df_all.modele)
    CM.text!([df.conso[id]],[r_stud[id]];
            text=voiture,color=:red,align=(:center,:center))    
end
display(fig)
No description has been provided for this image
CairoMakie.Screen{IMAGE}

Points observés vs. prédits¶

In [161]:
# prédiction en resubstitution
# on ne passe pas les données à traiter
# la fonction utilise automatiquement les données d'apprentissage
pred_resub = GLM.predict(modele)
pred_resub
25-element Vector{Float64}:
 14.027711154840901
  5.24815235845113
  7.586843868731834
  8.048717521678618
 10.726541069633335
 12.076984108874377
 12.442289356044935
 11.13244084551341
  8.565134792067665
  8.939554952087917
  â‹®
  6.387263072946842
  8.399949559541943
  8.42623627353742
  9.111963973351832
  8.975979034452104
 11.160434051370197
  5.548310183822661
 11.43607497142288
  7.264953464648552
In [162]:
# graphique y observé vs. y predit
# min et max pour un graphique "carré"
min_val = minimum([minimum(df.conso),minimum(pred_resub)])
max_val = maximum([maximum(df.conso),maximum(pred_resub)])

# graphique
fig = CM.Figure()
ax = CM.Axis(fig[1,1],xlabel="Valeurs Observées",ylabel="Valeurs Prédites")
CM.xlims!(ax,min_val,max_val+1)
CM.ylims!(ax,min_val,max_val+1)
CM.scatter!(df.conso,pred_resub)
CM.ablines!(0,1,color=:green)
display(fig)
No description has been provided for this image
CairoMakie.Screen{IMAGE}

Prédiction ponctuelle et par intervalle¶

Importation des observations supplémentaires

In [163]:
# lecture des données
df_supp = CSV.read("vehicules_2.txt", DFR.DataFrame ; delim = "\t")
df_supp
6×5 DataFrame
Rowmodelecylindreepuissancepoidsconso
String31Int64Int64Int64Float64
1Nissan Primera 2.019979212409.2
2Fiat Tempra 1.6 Liberty15806510809.3
3Opel Omega 2.5i V62496125167011.3
4Subaru Vivio 4WD658327406.8
5Seat Ibiza 2.0 GTI19838510759.5
6Ferrari 456 GT5474325169021.3
In [164]:
# variables explicatives
X_supp = df_supp[:,[:cylindree,:puissance,:poids]]
X_supp
6×3 DataFrame
Rowcylindreepuissancepoids
Int64Int64Int64
11997921240
21580651080
324961251670
465832740
51983851075
654743251690
In [165]:
# nombre d'observation
n_supp = size(X_supp,1)
n_supp
6

Prédiction ponctuelle¶

In [166]:
# prédiction ponctuelle
# on passe cette fois-ci la matrice des descripteurs
pred_supp = GLM.predict(modele,X_supp)
pred_supp
6-element Vector{Union{Missing, Float64}}:
  9.58736854815561
  8.135585902890996
 12.586190593004323
  5.805360662403514
  8.523942275817518
 17.332471445359175
In [169]:
# confrontation points observés vs. prédits
# min et max pour un graphique "carré"
min_val = minimum([minimum(df_supp.conso),minimum(pred_supp)])
max_val = maximum([maximum(df_supp.conso),maximum(pred_supp)])

# graphique
fig = CM.Figure()
ax = CM.Axis(fig[1,1],xlabel="Valeurs Observées",ylabel="Valeurs Prédites")
CM.xlims!(ax,min_val-1,max_val+1)
CM.ylims!(ax,min_val-1,max_val+1)
CM.scatter!(df_supp.conso,pred_supp,color=:blue)
CM.ablines!(0,1,color=:green)
display(fig)
No description has been provided for this image
CairoMakie.Screen{IMAGE}

Variances de l'erreur de prédiction¶

In [170]:
# ajouter une constante 1 à la matrice des X_supp
X1_supp = hcat(ones(1,n_supp)',Matrix(X_supp))
X1_supp
6×4 Matrix{Float64}:
 1.0  1997.0   92.0  1240.0
 1.0  1580.0   65.0  1080.0
 1.0  2496.0  125.0  1670.0
 1.0   658.0   32.0   740.0
 1.0  1983.0   85.0  1075.0
 1.0  5474.0  325.0  1690.0
In [171]:
# rappel - écart-type de la régression
sigma_err
0.7006473499699383
In [172]:
# rappel (X'X)(-1)
XtX_inv
4×4 Matrix{Float64}:
  0.768142      6.31883e-5   0.00213739   -0.000836199
  6.31883e-5    5.03648e-7  -6.20345e-6   -4.11564e-7
  0.00213739   -6.20345e-6   0.000113694   4.05781e-8
 -0.000836199  -4.11564e-7   4.05781e-8    1.32565e-6
In [173]:
# variance de l'erreur de prédiction pour chaque individu
var_err = zeros(n_supp)

# pour chaque individu à traiter
for i in 1:n_supp
    # description - vecteur à caster en matrice
    tmp = reshape(X1_supp[i,:],1,:)
    # produit matriciel
    pm = tmp * XtX_inv * tmp'
    # matrice (1,1) -> transformer en scalaire
    pm = pm[1,1]
    # variance
    var_err[i] = sigma_err^2 * (1.0 + pm)
end

# affichage
var_err
6-element Vector{Float64}:
 0.5108941292423475
 0.5156051122226518
 0.5651575907827666
 0.5649406208483717
 0.5300043071227483
 1.1128200339955032

Intervalle de confiance de prédiction¶

In [174]:
# quantile de la loi de Student
# pour un niveau de confiance à 95%
qt = distrib.quantile(distrib.TDist(n-p-1),0.975)
qt
2.07961384472768
In [175]:
# bornes basses
yb = pred_supp .- qt .* sqrt.(var_err)
yb
6-element Vector{Float64}:
  8.100925904265084
  6.642305700352399
 11.02280004278032
  4.242270242013515
  7.009954392344032
 15.138680862757342
In [177]:
# bornes hautes
yh = pred_supp .+ qt .* sqrt.(var_err)
yh
6-element Vector{Float64}:
 11.073811192046135
  9.628866105429594
 14.149581143228326
  7.368451082793512
 10.037930159291005
 19.52626202796101
In [ ]:
# graphique - intervalle de confiance de prédiction
# vs. valeurs observées de la cible
# pour les individus supplémentaires
fig = CM.Figure()
ax = CM.Axis(fig[1,1],
            xticks=(1:n_supp,df_supp.modele),
            xticklabelrotation=Ï€/2)
CM.rangebars!(ax,1:n_supp,yb,yh,whiskerwidth=10)
CM.scatter!(ax,1:n_supp,df_supp.conso,color=:red)
display(fig)
No description has been provided for this image
CairoMakie.Screen{IMAGE}