X. Regression linéaire

Auteur·rice

Guillaume Guex

1 Introduction et données

Dans ce TP, nous allons voir comment faire un modèle de régression linéaire en R. La régression linéaire cherche à prédire une variable numérique d’un individu en fonction d’autres de ses variables. C’est une méthode emblématique en statistiques, et il existe de nombreuses méthodes qui dérivent de cette logique. De part son importance en statistiques, les fonctions permettant la construction de modèles linéaires font partie de la base de R.

Les librairies nécessaires pour ce TP sont les suivantes :

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.2.1     ✔ readr     2.2.0
✔ forcats   1.0.1     ✔ stringr   1.6.0
✔ ggplot2   4.0.3     ✔ tibble    3.3.1
✔ lubridate 1.9.5     ✔ tidyr     1.3.2
✔ purrr     1.2.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(fastDummies)
library(leaps)

Nous allons utiliser ici le jeu de données de sociolinguistique pré-traité. On commence par le charger et transformer ses variables de type character en factor :

socioling_df = read_csv("data/sociolinguistique_v2.csv") %>%
  mutate(across(where(is.character), factor))
Rows: 113 Columns: 61
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (12): acc_mode, acc_exclu, acc_accept, acc_enrich, acc_suppr, acc_menac...
dbl  (47): id, annee, duree, duree_num, Hashtag, Design, Selfie, Pull-over, ...
dttm  (2): h_deb, h_fin

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

1.1 Recodage de variables ordinales ou catégorielles

La régression linéaire classique permet de faire des prédictions sur une variable numérique à partir d’une ou plusieurs autres variables numériques uniquement. Cependant, il est possible de recoder les variables ordinales ou catégorielles comme variables numériques, afin de les utiliser dans un modèle de régression.

Pour une variable ordinale, on va utiliser des nombres pour chaque niveau des réponses. Par exemple, ici, on va recoder les différents niveaux d’accord avec la fonction suivante :

recode_to_numeric = function(var_accord){
  var_accord_numeric = case_when(var_accord == "En désaccord total" ~ -2,
                                 var_accord == "Plutôt en désaccord" ~ -1,
                                 var_accord == "Neutre" ~ 0,
                                 var_accord == "Plutôt d'accord" ~ 1,
                                 var_accord == "Tout à fait d'accord" ~ 2)
  return(var_accord_numeric)
}

Que l’on peut ensuite appliquer avec mutate_at() sur toutes les variables d’accord :

socioling_df = socioling_df %>%
  mutate_at(vars(acc_mode:acc_menace), recode_to_numeric)
socioling_df
# A tibble: 113 × 61
      id h_deb               h_fin               acc_mode acc_exclu acc_accept
   <dbl> <dttm>              <dttm>                 <dbl>     <dbl>      <dbl>
 1    23 2020-12-18 19:42:31 2020-12-18 19:45:28        1         1          0
 2    24 2020-12-18 19:51:15 2020-12-18 19:52:28        2         2         -1
 3    25 2020-12-18 19:55:03 2020-12-18 19:59:28       -1         1         -1
 4    26 2020-12-18 21:02:00 2020-12-18 21:10:12        1         0         -1
 5    27 2020-12-18 21:19:17 2020-12-18 21:26:48        1         2          0
 6    28 2020-12-19 17:31:04 2020-12-19 17:38:11        1         1         -1
 7    29 2020-12-19 17:38:18 2020-12-19 17:43:12        1         0         -1
 8    30 2020-12-20 10:49:22 2020-12-20 10:54:39        1         0          0
 9    31 2020-12-20 12:22:03 2020-12-20 12:31:58        2         1          1
10    32 2020-12-20 12:36:40 2020-12-20 12:41:19        0         1          1
# ℹ 103 more rows
# ℹ 55 more variables: acc_enrich <dbl>, acc_suppr <dbl>, acc_menace <dbl>,
#   ou <fct>, evite <fct>, genre <fct>, annee <dbl>, activite <fct>,
#   parle_ang <fct>, lang_fr <fct>, duree <dbl>, duree_num <dbl>,
#   Hashtag <dbl>, Design <dbl>, Selfie <dbl>, `Pull-over` <dbl>,
#   Parking <dbl>, News <dbl>, Coach <dbl>, Meeting <dbl>, Football <dbl>,
#   Cool <dbl>, Short <dbl>, `Pop-up` <dbl>, `Check-up` <dbl>, …

Pour les variables catégorielles, on utilise l’encodage binaire : Chaque modalité devient une colonne qui contiendra un 1 si l’individu a cette modalité et un 0 sinon. On peut obtenir cet encodage avec la fonction dummy_cols() du package fastDummies :

head(dummy_cols(socioling_df$genre))
      .data .data_Non binaire .data_Un homme .data_Une femme
1 Une femme                 0              0               1
2 Une femme                 0              0               1
3  Un homme                 0              1               0
4 Une femme                 0              0               1
5 Une femme                 0              0               1
6 Une femme                 0              0               1

Cependant, nous le verrons plus tard, cet encodage s’effectue automatiquement lorsque l’on utilise des modèles linéaires avec R. Il n’est donc pas nécessaire de l’appliquer explicitement.

Notre jeu de données est prêt, il ne nous reste plus qu’à créer un jeu de données réduit, contenant uniquement les colonnes que nous allons utiliser :

socioling_red_df = socioling_df %>%
  select(c(annee, genre, acc_mode, acc_exclu, acc_accept, acc_enrich, acc_suppr, acc_menace,
           ou, evite))

2 La régression linéaire

2.1 La formule de régression

La régression linéaire s’effectue avec la fonction lm() de R. Cette dernière demande comme premier argument une formule qui a une structure particulière et qui utilise l’opérateur ~.

La structure usuelle d’une formule est la suivante :

<variable réponse> ~ <variable 1> + <variable 2> + ... + <variable p>

Mais on peut également utilisé d’autres syntaxes :

  • En utilisant <variable réponse> ~ ., on indique que l’on prédit avec toutes les variables du jeu de données.
  • En ajoutant -1 à la formule, la droite de régression passera par l’origine.
  • <variable i>^n permet d’ajouter l’ordre \(n\) de la variable
  • <variable i>*<variable j> permet d’ajouter les termes d’interaction.
  • Plusieurs fonctions peuvent être utilisées sur les variables, comme exp() ou log().

2.2 Un modèle simple

Commençons par faire un modèle linéaire extrêmement simple : on va essayer de prédire l’année de naissance de l’individu en fonction de sa réponse à la question « Il faudrait supprimer l’anglais dans les publicités, affiches, magazines et médias ». Ce modèle se construit de la manière suivante :

lm_mini_res = lm(annee ~ acc_suppr, data=socioling_red_df)
lm_mini_res

Call:
lm(formula = annee ~ acc_suppr, data = socioling_red_df)

Coefficients:
(Intercept)    acc_suppr  
   1982.178       -6.137  

En affichant le résultat, on obtient la valeur des coefficients de régression. En utilisant summary() sur notre résultat, plusieurs autres informations sont disponibles, tel que la signicativité des coefficients, le R^2 et le R^2 ajusté :

summary(lm_mini_res)

Call:
lm(formula = annee ~ acc_suppr, data = socioling_red_df)

Residuals:
    Min      1Q  Median      3Q     Max 
-38.904 -13.178   4.685  11.685  33.096 

Coefficients:
            Estimate Std. Error  t value Pr(>|t|)    
(Intercept) 1982.178      1.643 1206.338  < 2e-16 ***
acc_suppr     -6.137      1.368   -4.486 1.78e-05 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 16.57 on 111 degrees of freedom
Multiple R-squared:  0.1535,    Adjusted R-squared:  0.1458 
F-statistic: 20.12 on 1 and 111 DF,  p-value: 1.78e-05

On peut également extraire plusieurs informations du résultat de la régression :

names(lm_mini_res)
 [1] "coefficients"  "residuals"     "effects"       "rank"         
 [5] "fitted.values" "assign"        "qr"            "df.residual"  
 [9] "xlevels"       "call"          "terms"         "model"        

Ce qui nous permet, par exemple, de tracer la droite de régression :

ggplot(socioling_red_df) +
  geom_point(aes(x=acc_suppr, y=annee)) +
  geom_abline(aes(intercept=lm_mini_res$coefficients[1], 
                  slope=lm_mini_res$coefficients[2]), color="red")

2.3 Sélection de modèles

Nous allons maintenant voir comment faire le “meilleur modèle” à partir de nos variables pré-sélectionnées. On commence par faire le modèle “complet”, c’est-à-dire avec toutes les variables contenues dans notre jeu de données :

lm_complet = lm(annee ~ ., data=socioling_red_df)
summary(lm_complet)

Call:
lm(formula = annee ~ ., data = socioling_red_df)

Residuals:
    Min      1Q  Median      3Q     Max 
-43.127  -8.781   2.789  10.680  27.544 

Coefficients:
                                        Estimate Std. Error t value Pr(>|t|)
(Intercept)                            1996.6664    25.1396  79.423   <2e-16
genreUn homme                            -4.4780    16.8306  -0.266   0.7907
genreUne femme                           -1.4719    16.8431  -0.087   0.9305
acc_mode                                 -0.4117     1.8471  -0.223   0.8241
acc_exclu                                -0.6950     1.8250  -0.381   0.7042
acc_accept                               -3.2694     1.6903  -1.934   0.0560
acc_enrich                                3.1082     1.5515   2.003   0.0479
acc_suppr                                -2.1636     1.8574  -1.165   0.2469
acc_menace                               -0.5161     1.6140  -0.320   0.7498
ouAu travail                            -13.1166    17.6316  -0.744   0.4587
ouAutre                                 -21.5100    18.1267  -1.187   0.2382
ouDans la rue                           -12.1939    18.3232  -0.665   0.5073
ouDans les groupes de jeunes personnes   -8.3895    17.4245  -0.481   0.6313
eviteNon                                  1.0467     3.6883   0.284   0.7772
eviteOui                                 -4.3463     5.1441  -0.845   0.4002
                                          
(Intercept)                            ***
genreUn homme                             
genreUne femme                            
acc_mode                                  
acc_exclu                                 
acc_accept                             .  
acc_enrich                             *  
acc_suppr                                 
acc_menace                                
ouAu travail                              
ouAutre                                   
ouDans la rue                             
ouDans les groupes de jeunes personnes    
eviteNon                                  
eviteOui                                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 16.11 on 98 degrees of freedom
Multiple R-squared:  0.293, Adjusted R-squared:  0.192 
F-statistic: 2.901 on 14 and 98 DF,  p-value: 0.001053

On voit que la plupart des coefficients de régression sont loin d’être significatifs. Cependant, ce problème peut être dû au fait que la régression possède de nombreux prédicteurs qui sont fortement liés entre eux.

Le package leaps nous permet de sélectionner le “meilleur modèle” selon différents critères. Pour cela, on utilise la fonction regsubsets(), qui crée une variable contenant l’évaluation de tous les modèles découlant de notre modèle complet :

lm_subset_res = regsubsets(annee ~ ., data=socioling_red_df)

Les valeurs des critères R^2 ajusté (à maximiser), CP et BIC (à minimiser), selon les différents modèles testés peuvent alors s’afficher avec :

plot(lm_subset_res, scale="adjr2")

plot(lm_subset_res, scale="Cp")

plot(lm_subset_res, scale="bic")

Si l’on choisit d’optimiser le BIC, on voit que l’on doit garder acc_accept, acc_enrich, acc_suppr et la modalité Autre de la variable ou. On va donc explicitement construire les modalités de cette variable plus :

socioling_red_new_df = socioling_red_df %>%
  dummy_cols("ou")
socioling_red_new_df
# A tibble: 113 × 15
   annee genre     acc_mode acc_exclu acc_accept acc_enrich acc_suppr acc_menace
   <dbl> <fct>        <dbl>     <dbl>      <dbl>      <dbl>     <dbl>      <dbl>
 1  1999 Une femme        1         1          0          0        -1         -1
 2  1967 Une femme        2         2         -1         -2         2          2
 3  1967 Un homme        -1         1         -1          1        -1         -1
 4  1978 Une femme        1         0         -1         -1         0          1
 5  1970 Une femme        1         2          0         -1         0          1
 6  1967 Une femme        1         1         -1          1        -1          1
 7  1961 Un homme         1         0         -1          1        -2         -2
 8  1994 Un homme         1         0          0          1        -1         -1
 9  1955 Un homme         2         1          1         -1         0          1
10  1966 Une femme        0         1          1          1         1          1
# ℹ 103 more rows
# ℹ 7 more variables: ou <fct>, evite <fct>, `ou_A la maison` <int>,
#   `ou_Au travail` <int>, ou_Autre <int>, `ou_Dans la rue` <int>,
#   `ou_Dans les groupes de jeunes personnes` <int>

Et faire la nouvelle régression :

lm_optimal_res = lm(annee ~ acc_accept + acc_enrich + acc_suppr + ou_Autre, 
                    data=socioling_red_new_df)
summary(lm_optimal_res)

Call:
lm(formula = annee ~ acc_accept + acc_enrich + acc_suppr + ou_Autre, 
    data = socioling_red_new_df)

Residuals:
    Min      1Q  Median      3Q     Max 
-43.339  -9.204   3.449  11.203  27.720 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 1982.255      2.057 963.875   <2e-16 ***
acc_accept    -3.542      1.549  -2.286   0.0242 *  
acc_enrich     3.106      1.394   2.229   0.0279 *  
acc_suppr     -3.327      1.508  -2.206   0.0295 *  
ou_Autre     -11.145      4.765  -2.339   0.0212 *  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 15.75 on 108 degrees of freedom
Multiple R-squared:  0.2557,    Adjusted R-squared:  0.2281 
F-statistic: 9.276 on 4 and 108 DF,  p-value: 1.757e-06

Un bon modèle de régression doit, en principe, suivre plusieurs hypothèses de travail. En particulier, il ne doit pas avoir de “structure particulière” dans ses termes d’erreurs. Cela peut être vérifier en affichant le diagramme des résidus :

ggplot() + 
  geom_point(aes(x=lm_optimal_res$fitted.values, lm_optimal_res$residuals))

Ici, il n’y a pas une structure trop marquée, ce que semble dire que cette régression est valable.

Une fois un modèle construit, on peut faire des prédictions sur des nouveaux individus. Construisons un tableau contenant deux nouvelles entrées :

new_indiv = tibble(acc_accept=c(-2, 2),
                   acc_enrich=c(2, -2),
                   acc_suppr=c(-2, 2),
                   ou_Autre=c(0, 1))
new_indiv
# A tibble: 2 × 4
  acc_accept acc_enrich acc_suppr ou_Autre
       <dbl>      <dbl>     <dbl>    <dbl>
1         -2          2        -2        0
2          2         -2         2        1

Ici, la première ligne représente une personne qui :

  • N’est pas du tout d’accord avec «On est obligé d’utiliser des anglicismes pour se faire accepter en société»,
  • Est totalement d’accord avec «Les anglicismes enrichissent la langue française».
  • N’est pas du tout d’accord avec «Il faudrait supprimer l’anglais dans les publicités, affiches, magazines et médias»
  • Qui a l’impression d’entendre des anglicismes “Dans les groupes de jeunes personnes”, “Au travail”, “Dans la rue” ou “A la maison”.

La deuxième ligne représente une personne qui a répondu des réponses diamétralement opposées à le première.

L’estimation de leurs ages s’obtient alors avec :

predict(lm_optimal_res, new_indiv)
       1        2 
2002.204 1951.161 

Ce qui nous donne une estimation d’une personne née en 2002 dans le premier cas et née en 1951 pour le deuxième cas.