Nous allons procéder dans l’ordre suivant:
Recodage et reformatage des données
Traitement des valeurs manquantes
Conclusion
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
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é :
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
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
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,...
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"
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"
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”)
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
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
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
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
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.
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.
# 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!