Master SIAD — Jeudis de la BI

Julien Renault

22/02/2018


La qualité de données avec R

Nous allons procéder dans l’ordre suivant:

  1. Examen rapide des données
  • analyse des formats
  • analyse graphique
  1. Analyse des données
  • analyse des valeurs manquantes
  • analyse des valeurs aberrantes
  1. Recodage et reformatage des données

  2. Traitement des valeurs manquantes

  3. Conclusion


1 - Examen rapide des données

Analyse des formats

On commence par importer les données et par regarder à quoi elles ressemblent :

# répertoire de travail
setwd("~/Projets/SIAD")

# import du fichier de données au format R
mydata <- readRDS("DATA/mydata.rds")

# survol des formats et des valeurs
str(mydata)
## 'data.frame':    10000 obs. of  28 variables:
##  $ COD_CAN         : chr  "16525499" "10555091" "88134" "4493792" ...
##  $ COR_LTD         : num  47.5 49.3 45.5 46.8 44.9 ...
##  $ COR_LGD         : num  2.717 1.017 4.591 -0.564 1.955 ...
##  $ COD_GEN_AMS     : chr  "H" "H" "F" "F" ...
##  $ LBL_ACV_STA     : chr  "FICHE ACTIVE" "FICHE ACTIVE" "FICHE ARCHIVEE" "FICHE ARCHIVEE" ...
##  $ AGE             : num  49 19 28 52 33 40 36 30 23 18 ...
##  $ NB_ANNEE_CRE    : num  NA 0 2 7 1 NA NA 12 5 0 ...
##  $ CTC_MAIL        : chr  "OUI" "OUI" "OUI" "NON" ...
##  $ HAS_MAIL        : chr  "OUI" "OUI" "OUI" "NON" ...
##  $ NATIONALITE     : chr  "NAT_FR" "NAT_FR" "NAT_FR" "NAT_FR" ...
##  $ NB_CLT          : num  1 1 1 2 3 1 NA 2 2 1 ...
##  $ NB_PCS          : num  1 1 1 1 1 1 NA 1 2 1 ...
##  $ CTR_CADRE       : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ CTR_EMPLOY      : num  0 1 2 0 0 0 0 13 1 6 ...
##  $ CTR_INTER       : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ CTR_OUVRIER     : num  3 0 0 11 39 2 0 0 1 0 ...
##  $ CAN_ETATOP1     : num  7280966 5095799 6686745 7367421 5121618 ...
##  $ CAN_ETATOP2     : num  NA NA NA 5089615 5115888 ...
##  $ CAN_ETATOP3     : num  NA NA NA NA 5688705 ...
##  $ NBCONTRATPARCAND: num  3 1 2 11 39 2 NA 13 2 6 ...
##  $ DIST_CAN_MIS    : num  13 6 19 5 10 45 NA 36 2 0 ...
##  $ NB_REF_CAN      : num  0 0 0 0 0 1 0 0 0 0 ...
##  $ NB_REF_CLT      : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ NB_REF_AGE      : num  47 0 16 1 3 11 9 0 6 0 ...
##  $ LBL_PAY_MTD     : chr  "Virement" "Virement" "Virement" "Virement" ...
##  $ LBL_NIV_FOR     : chr  "BAC" "CAP_BEP" "BAC" NA ...
##  $ CERTIF_COMP_PRO : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ CTR_TOT         : num  3 1 2 11 39 2 0 13 2 6 ...

On voit par exemple que les colonnes CAN_ETATOP1, CANETATOP2 et CANETATOP3 sont des variables numériques, alors qu’ils correspondent à des codes clients.

Il serait préférable de les convertir tout de suite en chaines de caractères.

# librarie pour manipulation de données
library(tidyverse)

# conversion
mydata2<-mydata%>%mutate_at(c("CAN_ETATOP1","CAN_ETATOP2","CAN_ETATOP3"), as.character)

# on peut vérifier une variable individuellement
str(mydata2$CAN_ETATOP1)
##  chr [1:10000] "7280966" "5095799" "6686745" "7367421" "5121618" ...

Puis on affiche un résumé statistique.

On pourra y voir par exemple un problème avec la variable COR_LTD.
Celle-ci correspond en effet à la latitude du domicile du candidat, qui devrait être entre 42ème et le 48ème parallèle.

De même, une longitude (COR_LGD) qui est en dehors de l’intervalle [-5,5] est suspecte.

summary(mydata, digits = 2)
##    COD_CAN             COR_LTD       COR_LGD      COD_GEN_AMS       
##  Length:10000       Min.   :-21   Min.   :-4.67   Length:10000      
##  Class :character   1st Qu.: 46   1st Qu.: 0.27   Class :character  
##  Mode  :character   Median : 48   Median : 2.40   Mode  :character  
##                     Mean   : 47   Mean   : 2.38                     
##                     3rd Qu.: 49   3rd Qu.: 4.59                     
##                     Max.   : 51   Max.   :55.31                     
##                     NA's   :255   NA's   :255                       
##  LBL_ACV_STA             AGE       NB_ANNEE_CRE    CTC_MAIL        
##  Length:10000       Min.   : 16   Min.   : 0.0   Length:10000      
##  Class :character   1st Qu.: 22   1st Qu.: 1.0   Class :character  
##  Mode  :character   Median : 28   Median : 2.0   Mode  :character  
##                     Mean   : 31   Mean   : 3.1                     
##                     3rd Qu.: 38   3rd Qu.: 5.0                     
##                     Max.   :117   Max.   :13.0                     
##                     NA's   :20    NA's   :971                      
##    HAS_MAIL         NATIONALITE            NB_CLT         NB_PCS   
##  Length:10000       Length:10000       Min.   : 1.0   Min.   :1.0  
##  Class :character   Class :character   1st Qu.: 1.0   1st Qu.:1.0  
##  Mode  :character   Mode  :character   Median : 1.0   Median :1.0  
##                                        Mean   : 2.1   Mean   :1.4  
##                                        3rd Qu.: 2.0   3rd Qu.:2.0  
##                                        Max.   :35.0   Max.   :7.0  
##                                        NA's   :336    NA's   :336  
##    CTR_CADRE        CTR_EMPLOY      CTR_INTER       CTR_OUVRIER   
##  Min.   :  0.00   Min.   :  0.0   Min.   :  0.00   Min.   :  0.0  
##  1st Qu.:  0.00   1st Qu.:  0.0   1st Qu.:  0.00   1st Qu.:  0.0  
##  Median :  0.00   Median :  0.0   Median :  0.00   Median :  2.0  
##  Mean   :  0.06   Mean   :  2.1   Mean   :  0.25   Mean   :  6.3  
##  3rd Qu.:  0.00   3rd Qu.:  1.0   3rd Qu.:  0.00   3rd Qu.:  6.0  
##  Max.   :125.00   Max.   :211.0   Max.   :110.00   Max.   :394.0  
##  NA's   :51       NA's   :119     NA's   :156      NA's   :176    
##   CAN_ETATOP1       CAN_ETATOP2       CAN_ETATOP3      NBCONTRATPARCAND
##  Min.   :5000018   Min.   :5000009   Min.   :      0   Min.   :  1     
##  1st Qu.:5092506   1st Qu.:5113248   1st Qu.:5149637   1st Qu.:  1     
##  Median :5745480   Median :5903456   Median :6363810   Median :  4     
##  Mean   :5976389   Mean   :6064631   Mean   :6138724   Mean   :  9     
##  3rd Qu.:6745788   3rd Qu.:6767767   3rd Qu.:6874701   3rd Qu.:  9     
##  Max.   :7493404   Max.   :7498030   Max.   :7493450   Max.   :394     
##  NA's   :336       NA's   :6114      NA's   :7954      NA's   :336     
##   DIST_CAN_MIS   NB_REF_CAN       NB_REF_CLT      NB_REF_AGE   
##  Min.   :  0   Min.   : 0.000   Min.   :0.000   Min.   :  0.0  
##  1st Qu.:  5   1st Qu.: 0.000   1st Qu.:0.000   1st Qu.:  0.0  
##  Median : 10   Median : 0.000   Median :0.000   Median :  1.0  
##  Mean   : 25   Mean   : 0.096   Mean   :0.038   Mean   :  7.6  
##  3rd Qu.: 19   3rd Qu.: 0.000   3rd Qu.:0.000   3rd Qu.:  4.0  
##  Max.   :863   Max.   :14.000   Max.   :8.000   Max.   :974.0  
##  NA's   :566                                                   
##  LBL_PAY_MTD        LBL_NIV_FOR        CERTIF_COMP_PRO    CTR_TOT     
##  Length:10000       Length:10000       Min.   :0.000   Min.   :  0.0  
##  Class :character   Class :character   1st Qu.:0.000   1st Qu.:  1.0  
##  Mode  :character   Mode  :character   Median :0.000   Median :  3.0  
##                                        Mean   :0.061   Mean   :  8.8  
##                                        3rd Qu.:0.000   3rd Qu.:  9.0  
##                                        Max.   :1.000   Max.   :236.0  
##                                                        NA's   :492

Analyse graphique

On peut d’abord regarder la distribution des variables discrètes.

On peut remarquer par exemple que la modalité “Chèque” de la variable LBL_PAY_MTD est très peu fréquente, ce qui se vérifie numériquement

## 
##   Chèque Virement 
##      3.7     96.3

Puis on passe aux valeurs continues avec des histogrammes de densité :


2 - Analyse des données

Valeurs manquantes

On compte d’abord les valeurs manquantes :

##          COD_CAN          COR_LTD          COR_LGD      COD_GEN_AMS 
##                0              255              255               20 
##      LBL_ACV_STA              AGE     NB_ANNEE_CRE         CTC_MAIL 
##                0               20              971                0 
##         HAS_MAIL      NATIONALITE           NB_CLT           NB_PCS 
##                0               20              336              336 
##        CTR_CADRE       CTR_EMPLOY        CTR_INTER      CTR_OUVRIER 
##               51              119              156              176 
##      CAN_ETATOP1      CAN_ETATOP2      CAN_ETATOP3 NBCONTRATPARCAND 
##              336             6114             7954              336 
##     DIST_CAN_MIS       NB_REF_CAN       NB_REF_CLT       NB_REF_AGE 
##              566                0                0                0 
##      LBL_PAY_MTD      LBL_NIV_FOR  CERTIF_COMP_PRO          CTR_TOT 
##               12             6420                0              492

Puis on peut regarder graphiquement le taux de valeurs manquantes

Valeurs aberrantes

Nous allons prendre comme exemple les latitudes (COR_LTD) et longitudes (COR_LGD).
En effect, aussi bien dans le résumé statistique que dans les graphs de densités on peut voir que certaines valeurs aberrantes apparaissent

Vu autrement, avec des boites à moustaches (boxplot)

Il existe une fonction boxplot.stats permettant d’afficher les valeurs aberrantes

# Listing des longitudes aberrantes
boxplot.stats(coords$COR_LGD)$out
## [1] 55.3071
# Listing des latitudes aberrantes
boxplot.stats(coords$COR_LTD)$out
##  [1]   0.00000   0.00000   0.00000   0.00000   0.00000   0.00000   0.00000
##  [8]   0.00000 -21.06329   0.00000   0.00000   0.00000   0.00000   0.00000
## [15]   0.00000   0.00000   0.00000   0.00000   0.00000   0.00000   0.00000
## [22]   0.00000   0.00000   0.00000   0.00000   0.00000

On peut aussi afficher les points sur une carte du monde et vérifier visuellement ceux qui sont en dehors dehors de la France métropolitaine :

## Warning: package 'leaflet' was built under R version 3.4.3

3. Recodage et reformatage des données

Variables avec le mauvais format

La variable CERTIF_COMP_PRO est considérée comme continue, mais c’est en fait un booléen (OUI / NON)

## # A tibble: 2 x 2
##   CERTIF_COMP_PRO     n
##             <dbl> <int>
## 1               0  9389
## 2               1   611

On a aussi pu remarquer que certaines variables comme NB_PCS sont des numériques mais devraient être des entiers, on les converti donc comme suit :

# convertir en masse avec mutate
mydata2<-mydata2%>%mutate_at(c("NB_PCS","NB_REF_CAN","NB_REF_CLT","NB_REF_AGE",
            "NBCONTRATPARCAND","NB_ANNEE_CRE","CERTIF_COMP_PRO"), as.integer)

# vérification des formats
glimpse(mydata2)
## Observations: 10,000
## Variables: 28
## $ COD_CAN          <chr> "16525499", "10555091", "88134", "4493792", "...
## $ COR_LTD          <dbl> 47.50707, 49.27932, 45.51443, 46.77528, 44.85...
## $ COR_LGD          <dbl> 2.716651, 1.017395, 4.591332, -0.564001, 1.95...
## $ COD_GEN_AMS      <chr> "H", "H", "F", "F", "H", "H", "F", "F", "F", ...
## $ LBL_ACV_STA      <chr> "FICHE ACTIVE", "FICHE ACTIVE", "FICHE ARCHIV...
## $ AGE              <dbl> 49, 19, 28, 52, 33, 40, 36, 30, 23, 18, 20, 2...
## $ NB_ANNEE_CRE     <int> NA, 0, 2, 7, 1, NA, NA, 12, 5, 0, 2, 1, 4, 3,...
## $ CTC_MAIL         <chr> "OUI", "OUI", "OUI", "NON", "OUI", "OUI", "OU...
## $ HAS_MAIL         <chr> "OUI", "OUI", "OUI", "NON", "OUI", "OUI", "OU...
## $ NATIONALITE      <chr> "NAT_FR", "NAT_FR", "NAT_FR", "NAT_FR", "NAT_...
## $ NB_CLT           <dbl> 1, 1, 1, 2, 3, 1, NA, 2, 2, 1, 6, 1, 1, NA, 3...
## $ NB_PCS           <int> 1, 1, 1, 1, 1, 1, NA, 1, 2, 1, 4, 1, 1, NA, 2...
## $ CTR_CADRE        <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
## $ CTR_EMPLOY       <dbl> 0, 1, 2, 0, 0, 0, 0, 13, 1, 6, 1, 3, 0, 0, 1,...
## $ CTR_INTER        <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
## $ CTR_OUVRIER      <dbl> 3, 0, 0, 11, 39, 2, 0, 0, 1, 0, 16, 0, 2, 0, ...
## $ CAN_ETATOP1      <chr> "7280966", "5095799", "6686745", "7367421", "...
## $ CAN_ETATOP2      <chr> NA, NA, NA, "5089615", "5115888", NA, NA, "64...
## $ CAN_ETATOP3      <chr> NA, NA, NA, NA, "5688705", NA, NA, NA, NA, NA...
## $ NBCONTRATPARCAND <int> 3, 1, 2, 11, 39, 2, NA, 13, 2, 6, 17, 3, 2, N...
## $ DIST_CAN_MIS     <dbl> 13, 6, 19, 5, 10, 45, NA, 36, 2, 0, 5, 0, NA,...
## $ NB_REF_CAN       <int> 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, ...
## $ NB_REF_CLT       <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
## $ NB_REF_AGE       <int> 47, 0, 16, 1, 3, 11, 9, 0, 6, 0, 0, 0, 0, 0, ...
## $ LBL_PAY_MTD      <chr> "Virement", "Virement", "Virement", "Virement...
## $ LBL_NIV_FOR      <chr> "BAC", "CAP_BEP", "BAC", NA, NA, "BAC", "CAP_...
## $ CERTIF_COMP_PRO  <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
## $ CTR_TOT          <dbl> 3, 1, 2, 11, 39, 2, 0, 13, 2, 6, 17, 3, 2, 0,...

Variables avec peu de variance

On peut vouloir éliminer les variables ayant très peu de variance.

La variable CTR_CADRE est très regroupée sur des valeurs basses, et pourrait aussi être recodée en boléen (OUI / NON).

## # A tibble: 17 x 2
##    CTR_CADRE     n
##        <dbl> <int>
##  1         0  9731
##  2         1   134
##  3         2    41
##  4         3    15
##  5         4     6
##  6         5     5
##  7         6     3
##  8         7     4
##  9         8     2
## 10         9     2
## 11        10     1
## 12        13     1
## 13        14     1
## 14        20     1
## 15        21     1
## 16       125     1
## 17        NA    51

On va voir cependant plus loin que cette variable doit être conservée comme telle.

Le package caret offre une fonction qui liste ces variables :

caret::nearZeroVar(mydata2,names = TRUE)
## [1] "CTR_CADRE"   "CTR_INTER"   "NB_REF_CLT"  "LBL_PAY_MTD"

Variables liées entre elles

On peut aussi examiner les variables qui pourraient être une combinaison linéaires d’autres.
Là aussi le package caret permet d’identifier ces colonnes, et le cas échéant les éliminer.

# la verification va se faire sur les observations sans valeurs manquantes,
# ainsi que sur les variables numériques
mydata3<-mydata2%>%select_if(is.numeric)%>%na.omit


combos<-caret::findLinearCombos(mydata3)
# colonnes corrélées
combos$linearCombos[1]
## [[1]]
## [1] 17  7  8  9 10
# colonne à éliminer
combos$remove
## [1] 17
# nom de la variable à éliminer
mydata3%>%dplyr::select(17)%>%colnames()
## [1] "CTR_TOT"

4. Traitement des valeurs manquantes

Nous allons passer en revue 4 méthodes qui permettent d’imputer les valeurs manquantes, au moyen du package simputation :

1 - Classification non supervisée (Knn)
2 - Classification supervisée (arbre de décision)
3 - Imputation par la médiane
4 - Utilisation d’un modèle linéaire simple

(nous ne verrons pas ici les méthodes d’imputation “au hasard”, ou “hotdeck”)

Méthode 1 : classification non supervisée

On a constaté que les variables COD_GEN_AMS, AGE et NATIONALITE ont chacune 20 valeurs manquantes.
Il est alors intéressant de vérifier si cela concerne les mêmes 20 observations. Cela peut se faire grâce au package VIM:

temp<-mydata2%>%dplyr::select(COD_CAN,COD_GEN_AMS,AGE,NATIONALITE)
summary(VIM::aggr(temp,plot = F))
## 
##  Missings per variable: 
##     Variable Count
##      COD_CAN     0
##  COD_GEN_AMS    20
##          AGE    20
##  NATIONALITE    20
## 
##  Missings in combinations of variables: 
##  Combinations Count Percent
##       0:0:0:0  9980    99.8
##       0:1:1:1    20     0.2

L’output ci-dessus nous confirme cette intuition : les 20 valeurs manquantes correspondent effectivement aux mêmes 20 observations.

On d’abord utliser une technique de clustering (les K plus proches voisins) afin que les valeurs imputées correspondent aux observations similaires :

# on met le code candidat de côté afin qu'il ne soit pas prédicteur dans le modèle
ids<-mydata2%>%dplyr::select(COD_CAN)
temp<-mydata2%>%dplyr::select(-COD_CAN)

# on impute les données et on remet le code candidat ensuite
mydata4<-simputation::impute_knn(temp,COD_GEN_AMS+NATIONALITE+AGE~.)%>%bind_cols(ids)

# vérification
colSums(is.na(mydata4))  
##          COR_LTD          COR_LGD      COD_GEN_AMS      LBL_ACV_STA 
##              255              255                0                0 
##              AGE     NB_ANNEE_CRE         CTC_MAIL         HAS_MAIL 
##                0              971                0                0 
##      NATIONALITE           NB_CLT           NB_PCS        CTR_CADRE 
##                0              336              336               51 
##       CTR_EMPLOY        CTR_INTER      CTR_OUVRIER      CAN_ETATOP1 
##              119              156              176              336 
##      CAN_ETATOP2      CAN_ETATOP3 NBCONTRATPARCAND     DIST_CAN_MIS 
##             6114             7954              336              566 
##       NB_REF_CAN       NB_REF_CLT       NB_REF_AGE      LBL_PAY_MTD 
##                0                0                0               12 
##      LBL_NIV_FOR  CERTIF_COMP_PRO          CTR_TOT          COD_CAN 
##             6419                0              492                0

On fait de même pour les variables NB_CLT, NB_PCS et NB_CONTRATPARCAND

# on met le code candidat de côté afin qu'il ne soit pas prédicteur dans le modèle
ids<-mydata4%>%dplyr::select(COD_CAN)
temp<-mydata4%>%dplyr::select(-COD_CAN)

# on impute les données et on remet le code candidat ensuite
mydata4b<-simputation::impute_knn(temp,NB_CLT+NB_PCS+NBCONTRATPARCAND~.)%>%bind_cols(ids)

# vérification
colSums(is.na(mydata4b))  
##          COR_LTD          COR_LGD      COD_GEN_AMS      LBL_ACV_STA 
##              255              255                0                0 
##              AGE     NB_ANNEE_CRE         CTC_MAIL         HAS_MAIL 
##                0              971                0                0 
##      NATIONALITE           NB_CLT           NB_PCS        CTR_CADRE 
##                0                0                0               51 
##       CTR_EMPLOY        CTR_INTER      CTR_OUVRIER      CAN_ETATOP1 
##              119              156              176              336 
##      CAN_ETATOP2      CAN_ETATOP3 NBCONTRATPARCAND     DIST_CAN_MIS 
##             6114             7954                0              566 
##       NB_REF_CAN       NB_REF_CLT       NB_REF_AGE      LBL_PAY_MTD 
##                0                0                0               12 
##      LBL_NIV_FOR  CERTIF_COMP_PRO          CTR_TOT          COD_CAN 
##             6420                0              492                0

Méthode 2 : classification supervisée

On peut construire un modèle de classification (ici arbre de décision de type CART) afin de déterminer par exemple une variable binaire / booléenne comme LBL_PAY_MTD

# on met le code candidat de côté ainsi que certaines autres variables 
# afin qu'elles ne soit pas prédicteurs dans le modèle
drop.cols<-c("COD_CAN","CAN_ETATOP1","CAN_ETATOP2","CAN_ETATOP3")
ids<-mydata4b%>%dplyr::select(one_of(drop.cols))
temp<-mydata4b%>%dplyr::select(-one_of(drop.cols))

# on impute les données et on remet toutes les variables (avec COD_CAN en début de table)
mydata5<-simputation::impute_cart(temp,LBL_PAY_MTD~.)%>%bind_cols(ids)%>%
  dplyr::select(COD_CAN,everything())

# vérification
colSums(is.na(mydata5))  
##          COD_CAN          COR_LTD          COR_LGD      COD_GEN_AMS 
##                0              255              255                0 
##      LBL_ACV_STA              AGE     NB_ANNEE_CRE         CTC_MAIL 
##                0                0              971                0 
##         HAS_MAIL      NATIONALITE           NB_CLT           NB_PCS 
##                0                0                0                0 
##        CTR_CADRE       CTR_EMPLOY        CTR_INTER      CTR_OUVRIER 
##               51              119              156              176 
## NBCONTRATPARCAND     DIST_CAN_MIS       NB_REF_CAN       NB_REF_CLT 
##                0              566                0                0 
##       NB_REF_AGE      LBL_PAY_MTD      LBL_NIV_FOR  CERTIF_COMP_PRO 
##                0                0             6420                0 
##          CTR_TOT      CAN_ETATOP1      CAN_ETATOP2      CAN_ETATOP3 
##              492              336             6114             7954

Méthode 3 : remplacer par la médiane

Dans certains cas on voudra, par souci de simplicité et de rapidité remplacer les valeurs manquantes par la médiane, ce qui a l’avantage de simplement translater la distribution.

Afin de ne pas trop être brutal, on va quand même considérer la médiane de la variable NB_ANNEE_CRE dans des groupes dépendant du sexe (COD_GEN_AMS)

mydata6 <- simputation::impute_median(mydata5, NB_ANNEE_CRE ~ COD_GEN_AMS)

# Vérification
colSums(is.na(mydata6))  
##          COD_CAN          COR_LTD          COR_LGD      COD_GEN_AMS 
##                0              255              255                0 
##      LBL_ACV_STA              AGE     NB_ANNEE_CRE         CTC_MAIL 
##                0                0                0                0 
##         HAS_MAIL      NATIONALITE           NB_CLT           NB_PCS 
##                0                0                0                0 
##        CTR_CADRE       CTR_EMPLOY        CTR_INTER      CTR_OUVRIER 
##               51              119              156              176 
## NBCONTRATPARCAND     DIST_CAN_MIS       NB_REF_CAN       NB_REF_CLT 
##                0              566                0                0 
##       NB_REF_AGE      LBL_PAY_MTD      LBL_NIV_FOR  CERTIF_COMP_PRO 
##                0                0             6420                0 
##          CTR_TOT      CAN_ETATOP1      CAN_ETATOP2      CAN_ETATOP3 
##              492              336             6114             7954

Méthode 4 : remplacer avec un modèle linéaire

Un deuxième exemple correspond au cas où nous supposons qu’il existe un modèle linéaire permettant de calculer les valeurs manquantes.

C’est le cas de CTR_TOT, qui est la somme de 4 autres variables.

library(simputation)
mydata7 <- impute_lm(mydata6, CTR_TOT ~ CTR_CADRE+CTR_EMPLOY+CTR_INTER+CTR_OUVRIER)

# Vérification
colSums(is.na(mydata7))
##          COD_CAN          COR_LTD          COR_LGD      COD_GEN_AMS 
##                0              255              255                0 
##      LBL_ACV_STA              AGE     NB_ANNEE_CRE         CTC_MAIL 
##                0                0                0                0 
##         HAS_MAIL      NATIONALITE           NB_CLT           NB_PCS 
##                0                0                0                0 
##        CTR_CADRE       CTR_EMPLOY        CTR_INTER      CTR_OUVRIER 
##               51              119              156              176 
## NBCONTRATPARCAND     DIST_CAN_MIS       NB_REF_CAN       NB_REF_CLT 
##                0              566                0                0 
##       NB_REF_AGE      LBL_PAY_MTD      LBL_NIV_FOR  CERTIF_COMP_PRO 
##                0                0             6420                0 
##          CTR_TOT      CAN_ETATOP1      CAN_ETATOP2      CAN_ETATOP3 
##              492              336             6114             7954

On constate qu’il y a toujours des valeurs manquantes : il faut d’abord imputer les valeurs des prédicteurs (CTR_CADRE, CTR_EMPLOY, CTR_INTER et CTR_OUVRIER).

Nous allons pour cela reprendre la méthode consistant à remplacer les valeurs manquantes par la médiane, en considérant les médianes par groupes définis par sexe, statut de la fiche et nationalité.

A noter qu’il est possible d’enchainer les imputations grâce à l’opérateur %>%

# on enchaine les imputations
mydata7 <- simputation::impute_median(mydata6, CTR_CADRE~LBL_ACV_STA+NATIONALITE)%>%
  impute_median(., CTR_OUVRIER~COD_GEN_AMS+LBL_ACV_STA+NATIONALITE)%>%
  impute_median(., CTR_INTER~COD_GEN_AMS+LBL_ACV_STA+NATIONALITE)%>%
  impute_median(., CTR_EMPLOY~COD_GEN_AMS+LBL_ACV_STA+NATIONALITE)%>%
  impute_lm(., CTR_TOT ~ CTR_CADRE+CTR_EMPLOY+CTR_INTER+CTR_OUVRIER)

# Vérification
colSums(is.na(mydata7))
##          COD_CAN          COR_LTD          COR_LGD      COD_GEN_AMS 
##                0              255              255                0 
##      LBL_ACV_STA              AGE     NB_ANNEE_CRE         CTC_MAIL 
##                0                0                0                0 
##         HAS_MAIL      NATIONALITE           NB_CLT           NB_PCS 
##                0                0                0                0 
##        CTR_CADRE       CTR_EMPLOY        CTR_INTER      CTR_OUVRIER 
##                0                0                0                0 
## NBCONTRATPARCAND     DIST_CAN_MIS       NB_REF_CAN       NB_REF_CLT 
##                0              566                0                0 
##       NB_REF_AGE      LBL_PAY_MTD      LBL_NIV_FOR  CERTIF_COMP_PRO 
##                0                0             6420                0 
##          CTR_TOT      CAN_ETATOP1      CAN_ETATOP2      CAN_ETATOP3 
##                0              336             6114             7954

5. Conclusion

Pour montrer l’utilité d’une bonne analyse de la qualité de données, construisons un modèle avec les données d’origine et comparons avec les données corrigées.

Ce modèle va chercher à établir le sexe du candidat en fonction de toutes les autres variables du jeu de données, ceci à l’aide d’un arbre de décision de type CART.

Modèle 1 : données d’origine

Comme on aurait pu s’y attendre, R n’aime pas faire tourner un modèle quand des valeurs manquantes figurent au sein du dataset.
On utilise donc un dataset sans ces valeurs manquantes :

# on crée un dataset pour le training du modèle
drop.cols<-c("COD_CAN","COR_LTD","COR_LGD","CAN_ETATOP1","CAN_ETATOP2","CAN_ETATOP3",
"DIST_CAN_MIS","LBL_NIV_FOR")
modata<-mydata%>%dplyr::select(-one_of(drop.cols))


# on ne conserve que les observations sans valeurs manquantes
modata<-modata[complete.cases(modata),]

# on sépare le dataset en 2 parties : 80% pour l'apprentissage, 20% pour le test
inTrain<-caret::createDataPartition(modata$COD_GEN_AMS,
                                    p=0.8,list = FALSE)
train<-modata[inTrain,]
test<-modata[-inTrain,]

set.seed(4567)

# on va faire une validation croisée
ctrl <- caret::trainControl(method = "repeatedcv",number = 5,
                     repeats = 3)

# on tente de faire re-tourner le modèle
 rpartFit <- caret::train(COD_GEN_AMS ~ .,
                 data = train,
                 method = "rpart",
                 trControl=ctrl)
## Warning: package 'caret' was built under R version 3.4.3
 print(rpartFit)
## CART 
## 
## 6626 samples
##   19 predictor
##    2 classes: 'F', 'H' 
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold, repeated 3 times) 
## Summary of sample sizes: 5301, 5300, 5301, 5301, 5301, 5301, ... 
## Resampling results across tuning parameters:
## 
##   cp           Accuracy   Kappa    
##   0.003240021  0.7145585  0.3656933
##   0.033825816  0.7071129  0.3516354
##   0.236780715  0.6579115  0.1814587
## 
## Accuracy was used to select the optimal model using  the largest value.
## The final value used for the model was cp = 0.003240021.
 caret::confusionMatrix(rpartFit)
## Cross-Validated (5 fold, repeated 3 times) Confusion Matrix 
## 
## (entries are percentual average cell counts across resamples)
##  
##           Reference
## Prediction    F    H
##          F 19.0  8.7
##          H 19.8 52.5
##                             
##  Accuracy (average) : 0.7146

On obtient une précision de plus de 70% avec les 8282 observations complètes.

Modèle 2 : données corrigées

# on crée un dataset pour le training du modèle
drop.cols<-c("COD_CAN","COR_LTD","COR_LGD","CAN_ETATOP1","CAN_ETATOP2","CAN_ETATOP3",
"DIST_CAN_MIS","LBL_NIV_FOR")
modata<-mydata7%>%dplyr::select(-one_of(drop.cols))
colSums(is.na(modata))
##      COD_GEN_AMS      LBL_ACV_STA              AGE     NB_ANNEE_CRE 
##                0                0                0                0 
##         CTC_MAIL         HAS_MAIL      NATIONALITE           NB_CLT 
##                0                0                0                0 
##           NB_PCS        CTR_CADRE       CTR_EMPLOY        CTR_INTER 
##                0                0                0                0 
##      CTR_OUVRIER NBCONTRATPARCAND       NB_REF_CAN       NB_REF_CLT 
##                0                0                0                0 
##       NB_REF_AGE      LBL_PAY_MTD  CERTIF_COMP_PRO          CTR_TOT 
##                0                0                0                0
# on sépare le dataset en 2 parties : 80% pour l'apprentissage, 20% pour le test
inTrain<-caret::createDataPartition(modata$COD_GEN_AMS,
                                    p=0.8,list = FALSE)
train<-modata[inTrain,]
test<-modata[-inTrain,]

set.seed(4567)


# on va faire une validation croisée
ctrl <- caret::trainControl(method = "repeatedcv",number = 5,
                     repeats = 3)

# on tente de faire re-tourner le modèle
 rpartFit <- caret::train(COD_GEN_AMS ~ .,
                 data = train,
                 method = "rpart",
                 trControl=ctrl)

print(rpartFit)
## CART 
## 
## 8000 samples
##   19 predictor
##    2 classes: 'F', 'H' 
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold, repeated 3 times) 
## Summary of sample sizes: 6401, 6400, 6399, 6400, 6400, 6400, ... 
## Resampling results across tuning parameters:
## 
##   cp           Accuracy   Kappa    
##   0.006225426  0.7075007  0.3351244
##   0.043577982  0.6985850  0.3278952
##   0.191022280  0.6544078  0.1711933
## 
## Accuracy was used to select the optimal model using  the largest value.
## The final value used for the model was cp = 0.006225426.
caret::confusionMatrix(rpartFit)
## Cross-Validated (5 fold, repeated 3 times) Confusion Matrix 
## 
## (entries are percentual average cell counts across resamples)
##  
##           Reference
## Prediction    F    H
##          F 16.8  7.9
##          H 21.4 54.0
##                             
##  Accuracy (average) : 0.7075

On obtient une précision légèrement moindre avec les données imputées sur les 10000 observations.
Est-ce grave?

Dans le monde “réel”, on ne peut souvent pas se permettre de ne pas faire de prédiction pour presque 20% de la base, ce qui rend cette différence de précision tout à fait acceptable.

De plus, nous avons fait des hypothèses fortes pour imputer les valeurs, en allant parfois un peu trop vite pour ne pas alourdir la présentation.

Un travail plus poussé pourrait sans nul doute améliorer la qualité des imputations, et par là même celle du 2ème modèle…

Enfin, ce travail d’analyse de la qualité de données est crucial pour comprendre le sens du modèle final. Toute modélisation rigoureuse doit donc commencer par cette tâche parfois ingrate!