Sélection de variables

Exercice 2.1

  1. Faites une analyse exploratoire des variables explicatives:
    • Quelles variables devraient êtres exclues de la modélisation? Justifiez votre réponse.
    • Comparez la variable réponse avec les autres variables: y a-t-il des transformations qui améliorerait l’ensemble de variables candidates: interactions, création de variables dychotomiques, transformations (racines carrée, transformation logarithmique, etc.)?
    • Vérifiez s’il y a des variables catégorielles encodées comme des variables numériques.

On remarque en consultant la documentation de la base de données à l’aide de ?college que le nombre de demandes d’admissions (napplications), le nombre d’admissions parmi ces applications nadmission et le nombre d’offres converties par les candidat.e.s, ninscrits, sont reliées et ne peuvent être employées.

Plusieurs variables seront fortement corrélées parce qu’elles dépendent de la capacité d’accueil de l’établissement d’enseignement. Ainsi, on pourrait créer une variable qui représente le pourcentage de temps partiels pour les premiers cycles (plutôt que le décompte). Les variables pourcentdoctorat et pourcentterminal, sont fortement corrélées puisque la plupart des diplômes terminaux (incluant les titres professionnels, les doctorats de premier cycle en médecine, etc.) sont des PhD: le modèle choisira la variable la plus adéquate. Il n’y a pas de variables catégorielles hors variables binaires. Côté vérifications d’usage, on note que tauxdiplom et pourcentdoctorat sont supérieurs à 100%.

On devra retirer la variable catégorielle nom, qui a une modalité différente pour chaque observation.

Code
library(dplyr)
library(hecmulti)
data(college, package = "hecmulti")
str(college)
tibble [777 × 19] (S3: tbl_df/tbl/data.frame)
 $ prive                  : int [1:777] 1 1 1 1 1 1 1 1 1 1 ...
 $ napplications          : num [1:777] 1660 2186 1428 417 193 ...
 $ nadmission             : num [1:777] 1232 1924 1097 349 146 ...
 $ ninscrits              : num [1:777] 721 512 336 137 55 158 103 489 227 172 ...
 $ m10p                   : num [1:777] 23 16 22 60 16 38 17 37 30 21 ...
 $ m25p                   : num [1:777] 52 29 50 89 44 62 45 68 63 44 ...
 $ tempsplein1c           : num [1:777] 2885 2683 1036 510 249 ...
 $ tempspart1c            : num [1:777] 537 1227 99 63 869 ...
 $ fraiscolexternes       : num [1:777] 7440 12280 11250 12960 7560 ...
 $ fraisres               : num [1:777] 3300 6450 3750 5450 4120 ...
 $ fraislivres            : num [1:777] 450 750 400 450 800 500 500 450 300 660 ...
 $ fraisperso             : num [1:777] 2200 1500 1165 875 1500 ...
 $ pourcentdoctorat       : num [1:777] 70 29 53 92 76 67 90 89 79 40 ...
 $ pourcentterminal       : num [1:777] 78 30 66 97 72 73 93 100 84 41 ...
 $ ratioetudprof          : num [1:777] 18.1 12.2 12.9 7.7 11.9 9.4 11.5 13.7 11.3 11.5 ...
 $ pourcentdonationdiplome: num [1:777] 12 16 30 37 2 11 26 37 23 15 ...
 $ depenseparetud         : num [1:777] 7041 10527 8735 19016 10922 ...
 $ tauxdiplom             : num [1:777] 60 56 54 59 15 55 63 73 80 52 ...
 $ nom                    : Factor w/ 777 levels "Abilene Christian University",..: 1 2 3 4 5 6 7 8 9 10 ...
Code
summary(college)
     prive        napplications     nadmission      ninscrits   
 Min.   :0.0000   Min.   :   81   Min.   :   72   Min.   :  35  
 1st Qu.:0.0000   1st Qu.:  776   1st Qu.:  604   1st Qu.: 242  
 Median :1.0000   Median : 1558   Median : 1110   Median : 434  
 Mean   :0.7272   Mean   : 3002   Mean   : 2019   Mean   : 780  
 3rd Qu.:1.0000   3rd Qu.: 3624   3rd Qu.: 2424   3rd Qu.: 902  
 Max.   :1.0000   Max.   :48094   Max.   :26330   Max.   :6392  
                                                                
      m10p            m25p        tempsplein1c    tempspart1c     
 Min.   : 1.00   Min.   :  9.0   Min.   :  139   Min.   :    1.0  
 1st Qu.:15.00   1st Qu.: 41.0   1st Qu.:  992   1st Qu.:   95.0  
 Median :23.00   Median : 54.0   Median : 1707   Median :  353.0  
 Mean   :27.56   Mean   : 55.8   Mean   : 3700   Mean   :  855.3  
 3rd Qu.:35.00   3rd Qu.: 69.0   3rd Qu.: 4005   3rd Qu.:  967.0  
 Max.   :96.00   Max.   :100.0   Max.   :31643   Max.   :21836.0  
                                                                  
 fraiscolexternes    fraisres     fraislivres       fraisperso  
 Min.   : 2340    Min.   :1780   Min.   :  96.0   Min.   : 250  
 1st Qu.: 7320    1st Qu.:3597   1st Qu.: 470.0   1st Qu.: 850  
 Median : 9990    Median :4200   Median : 500.0   Median :1200  
 Mean   :10441    Mean   :4358   Mean   : 549.4   Mean   :1341  
 3rd Qu.:12925    3rd Qu.:5050   3rd Qu.: 600.0   3rd Qu.:1700  
 Max.   :21700    Max.   :8124   Max.   :2340.0   Max.   :6800  
                                                                
 pourcentdoctorat pourcentterminal ratioetudprof   pourcentdonationdiplome
 Min.   :  8.00   Min.   : 24.0    Min.   : 2.50   Min.   : 0.00          
 1st Qu.: 62.00   1st Qu.: 71.0    1st Qu.:11.50   1st Qu.:13.00          
 Median : 75.00   Median : 82.0    Median :13.60   Median :21.00          
 Mean   : 72.66   Mean   : 79.7    Mean   :14.09   Mean   :22.74          
 3rd Qu.: 85.00   3rd Qu.: 92.0    3rd Qu.:16.50   3rd Qu.:31.00          
 Max.   :103.00   Max.   :100.0    Max.   :39.80   Max.   :64.00          
                                                                          
 depenseparetud    tauxdiplom                               nom     
 Min.   : 3186   Min.   : 10.00   Abilene Christian University:  1  
 1st Qu.: 6751   1st Qu.: 53.00   Adelphi University          :  1  
 Median : 8377   Median : 65.00   Adrian College              :  1  
 Mean   : 9660   Mean   : 65.46   Agnes Scott College         :  1  
 3rd Qu.:10830   3rd Qu.: 78.00   Alaska Pacific University   :  1  
 Max.   :56233   Max.   :118.00   Albertson College           :  1  
                                  (Other)                     :771  
Code
# Est-ce que pourcentdoctorat < pourcenterminal?
summary(with(college,
             pourcentdoctorat/pourcentterminal))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.1379  0.8824  0.9390  0.9112  0.9775  1.9167 
Code
# Non
db <- college |>
  mutate(
    tauxdiplom = pmin(tauxdiplom, 100),
    pourcentdoctorat = pmin(pourcentdoctorat, 100),
    pctpart1c = tempspart1c/(tempsplein1c+tempspart1c)) |>
  select(! c(nom, 
             tempsplein1c, 
             tempspart1c, 
             nadmission, 
             ninscrits))
  1. Scindez la base de données en échantillon avec données d’entraînement (environ 2/3 des données) et échantillon de validation; utilisez le germe aléatoire 60602 via set.seed(60602).
    • Sélectionnez un modèle à l’aide d’une des méthodes couvertes, mais en basant votre choix sur l’erreur moyenne quadratique évaluée sur l’échantillon de validation.

On peut désormais considérer une séparation en tiers. Pour ce faire, je vais échantillonner des variables logiques vrais et faux avec une cote de 2 pour 1 et ensuite sélectionner les lignes qui correspondent.

Code
set.seed(60602)
test <- sample(x = c(FALSE, TRUE),
               size = nrow(db),
               replace = TRUE, 
               prob = c(1/3, 2/3))
db_a <- db[test,]
db_v <- db[!test,]

Pour l’estimation, on pourrait simplement calculer l’erreur quadratique moyenne de validation pour tous les cinquante premiers modèles en considérant toutes les interactions d’ordre 2.

Code
# Modèle avec interactions de toutes les variables
formule <- formula(napplications ~ .^2)

# Sélection séquentielle ascendante
rec_seq <- leaps::regsubsets(
  x = formule, 
  data = db_a,
  method = "seqrep",
  nvmax = 50)

Si regsubsets permet de recouvrer les différents modèles, il n’y a pas d’utilitaire pour obtenir le résumé et les prédictions. Le paquet hecmulti inclut une fonction pour faire les prédictions d’un modèle donné. À des fins d’illustration, on considère celui qui a le plus petit critère BIC intra-échantillon d’apprentissage.

Code
# Calculer les BIC 
bic_mod <- summary(rec_seq)$bic
# Prédire du modèle avec id variables
# Ici, celui avec le plus petit BIC du lot
predict(rec_seq, id = which.min(bic_mod))

Ici, on s’intéresse uniquement au calcul de l’erreur quadratique moyenne pour les modèles choisis par la procédure de recherche séquentielle. La fonction eval_EQM_regsubsets fait le calcul et nous retourne un vecteur avec toutes les mesures. On choisit le modèle qui minimise l’erreur.

Code
# Modèle avec la plus petite erreur 
# moyenne quadratique de validation externe
reqm_seq_ve_list <- 
  hecmulti::eval_EQM_regsubsets(
  model = rec_seq, 
  select = 1:50,
  formula = formule,
  data = db_a,
  newdata = db_v)
# La fonction calcule la racine EQM sur 
# les données de validation
mod_seq_ve <- which.min(reqm_seq_ve_list)
nvar_seq_ve <- mod_seq_ve + 1 
# nombre variables + ordonnée à l'origine.
reqm_seq_ve <- reqm_seq_ve_list[mod_seq_ve]

Une logique similaire s’appliquerait avec le LASSO, même si c’est loin d’être la norme (on utilise d’ordinaire la validation croisée). Ici, on ajusterait le modèle avec plusieurs valeurs de \(\lambda\), puis on calculerait l’erreur quadratique moyenne de validation. On prendra la valeur de la pénalisation \(\lambda\) qui minimise cette dernière. Spécifier la séquence de valeurs de \(\lambda\) à essayer nécessite un peu d’essai/erreur.

Code
# Obtenir X et y
Xmat_a <- model.matrix(formule, data = db_a)[,-1]
y_a <- db_a$napplications
lambda_seq <- exp(seq(from = log(0.1),
                  to = log(50), 
                  length.out = 100L))
lasso <- glmnet::glmnet(
    x = Xmat_a,
    y = y_a,
    alpha = 1, 
    lambda = lambda_seq)
# Prédictions et calcul de l'EQM
# On pourrait remplacer `newx` par 
# d'autres données (validation externe)
eqm_lasso_externe <- rep(0, length(lambda_seq))
for(i in seq_along(lambda_seq)){
  pred <- predict(
    object = lasso, 
    s = lambda_seq[i], 
    newx = model.matrix(formule, data = db_v)[,-1])
  eqm_lasso_externe[i] <- 
    mean((pred - db_v$napplications)^2)
}
minl <- which.min(eqm_lasso_externe)
lambda_opt <- lambda_seq[minl]
# Nombre de coefficients non nuls
nvar_lasso_ve <- Matrix::nnzero(lasso$beta[,minl, drop = FALSE])
reqm_lasso_ve <- sqrt(eqm_lasso_externe[minl])
  1. Répétez la sélection, cette fois en prenant comme critère pour l’erreur moyenne quadratique évaluée par validation croisée (aléatoire) à cinq plis.

On peut utiliser boot::cv.glm ou caret::train avec les différentes méthodes pour ajuster les modèles. Je fais varier le nombre maximal de variables pour retourner les différentes solutions. Comme le résultat de la validation croisée est aléatoire, on peut répéter cette étape pour réduire l’incertitude de prédiction et obtenir une meilleur valeur de l’écart-type.

Code
# Matrice du modèle complet, moins ordonnée à l'origine
Xmat <- model.matrix(formule, data = db)[,-1]
# Variable réponse
y <- db$napplications
seq_vc <- 
  caret::train(form = formule,
               data = db,
               method = 'leapSeq',
             tuneGrid = expand.grid(nvmax = 1:50),
             trControl = caret::trainControl(
               method = "repeatedcv",
               number = 10,
               repeats = 10))
# Graphique de la racine de l'EQM en fonction de
# nvmax
plot(seq_vc)

Code
# Meilleur modèle
mod_seq_vc <- which.min(seq_vc$results$RMSE)
reqm_seq_vc <- min(seq_vc$results$RMSE)
nvar_seq_vc <- seq_vc$results$nvmax[mod_seq_vc]
## Calcul des prédictions
# predict(seq_vc, newdata = db)

Les notes de cours donnent une approche pas à pas avec glmnet pour ajuster le LASSO en choisissant la valeur de \(\lambda\) par validation croisée, mais on peut changer le type de model dans caret et utiliser les mêmes fonctionnalités.

Code
# Autre approche que glmnet
lasso_vc <- 
  caret::train(form = formule,
               data = db,
               method = 'glmnet',
             tuneGrid = expand.grid(
               alpha = 1,
               lambda = lambda_seq),
             trControl = caret::trainControl(
               method = "repeatedcv",
               number = 10,
               repeats = 10))
plot(lasso_vc)

Code
# Meilleur modèle
coefs_lasso_cv <- coef(object = lasso_vc$finalModel, 
              s = lasso_vc$bestTune$lambda)
# Nombre de paramètres non-nuls pour la solution
nvar_lasso_vc <- Matrix::nnzero(coefs_lasso_cv)
# Estimation de la racine de l'erreur quadratique moyenne
reqm_lasso_vc <- min(lasso_vc$results$RMSE)
## Calcul des prédictions - comme avec n'importe quel modèle
# predict(lasso_vc, newdata = db)
  1. Créez un tableau avec le nombre de coefficients non-nuls de votre modèle final et un estimé de l’erreur moyenne quadratique obtenu par validation externe ou croisée.

Il suffit de colliger notre estimation de l’erreur et le nombre de coefficients.

Code
resultats <-data.frame(
  erreur = rep(c("validation croisée",
                  "validation externe"),
                  each = 2),
  methode = rep(c("sequentielle","lasso"), 
               length.out = 4),
  reqm = c(reqm_seq_vc,
           reqm_lasso_vc,
           reqm_seq_ve,
           reqm_lasso_ve),
  npar = c(nvar_seq_vc,
           nvar_lasso_vc,
           nvar_seq_ve,
           nvar_lasso_ve))
knitr::kable(resultats)
erreur methode reqm npar
validation croisée sequentielle 2559.108 18
validation croisée lasso 2490.672 56
validation externe sequentielle 2324.801 12
validation externe lasso 2276.097 105
  1. Commentez sur le meilleur modèle parmi les combinaisons.

Si la taille de la base de données est plus conséquente, la validation croisée reste préférable à la validation externe, surtout si on répète cette méthode plusieurs fois pour réduire l’incertitude.1 Le nombre de paramètres dans le modèle LASSO peut sembler élevé, mais il faut garder en tête que les autres paramètres sont rétrécis vers zéro même s’ils sont non-nuls.

Impossible de savoir quel est le meilleur modèle: le modèle avec la plus petite erreur quadratique moyenne est notre meilleur choix ici à défaut d’autres observations. Rappelez-vous qu’un modèle peut donner une bonne performance, mais être choisi parce qu’il surajuste une valeur aberrante.

Le but de l’exercice était davantage de démontrer le travail que de faire une prédiction convaincante. Puisqu’on essaie de prédire une variable de dénombrement, un modèle plus adapté (modèle de régression binomiale négative, ou un modèle pour le log du nombre d’admission) pourrait probablement être plus adéquat dans la mesure où la variance de la réponse dépend de la taille de l’école.

Notes de bas de page

  1. Rien ne nous empêche de faire de même avec la validation externe, soit dit en passant.↩︎