Section 3 Manipulation des variables
Dans la manipulation des variables, l’une des premières choses à réaliser est de les définir dans le bon format, variables quantitatives/continues ou variables qualitatives/catégorielles.
On l’a vu dans la section précédente, certaines variables sont encore codées comme des nombres entiers (“integer”) alors que sont des variables catégorielles. On va donc corriger cela.
<- RP %>% mutate(COMMUNE=as.factor(COMMUNE),
RP AEMM=as.factor(AEMM),
AEMMR=as.factor(AEMMR),
ANEMR=as.factor(ANEMR),
CATL=as.factor(CATL),
REGION=as.factor(REGION),
STOCD=as.factor(STOCD),
TYPL=as.factor(TYPL))
On peut ainsi vérifier que ces variables sont bien des variables facteurs en regardant combien de modalités elles ont et quelles sont-elles. Par exemple, pour deux de nos variables ci-dessous :
nlevels(RP$REGION)
[1] 1
levels(RP$REGION)
[1] "11"
nlevels(RP$TYPL)
[1] 6
levels(RP$TYPL)
[1] "1" "2" "3" "4" "5" "6"
Si on n’avait pas mis l’option transformant les variables caractères en variables facteurs lors du chargement des données, on pourrait le faire maintenant en utilisant la fonction mutate_if
ou la combinaison de mutate
et across(where())
comme ceci RP %>% mutate_if(is.character, as.factor)
ou RP %>% mutate(across(where(is.character), as.factor))
.
On peut enfin vérifier quelles sont les variables numériques qui restent, en les sélectionnant avec select(where())
ou select_if()
:
# RP %>% select_if(is.numeric) %>% head() %>% gt()
%>% select(where(is.numeric)) %>% head() %>% gt() RP
ANEM | IPONDL |
---|---|
10 | 2.7933037 |
1 | 1.2335216 |
999 | 1.2335042 |
0 | 1.2335216 |
1 | 1.2335216 |
3 | 0.8138559 |
Plus généralement, il est souvent d’usage d’utiliser la fonction summary()
pour donner une idée sur l’ensemble des variables, soit de la distribution pour les variables quantitatives, soit de la répartition des différentes modalités pour les variables qualitatives ; la fonction permet également de nous donner l’information sur l’existence et le nombre de valeurs manquantes pour chaque variable.
summary(RP)
COMMUNE ARM IRIS ACHL
75056 : 555467 ZZZZZ :2295363 ZZZZZZZZZ: 581311 B12 :818140
92012 : 26820 75115 : 60460 913770114: 3552 B11 :649489
93048 : 20906 75118 : 49438 920120303: 3464 C100 :365485
92026 : 19890 75120 : 42859 771830102: 2806 A11 :334104
93066 : 19773 75113 : 42469 930700109: 2790 A12 :272452
95018 : 18179 75117 : 42407 770580104: 2761 C115 : 52035
(Other):2189795 (Other): 317834 (Other) :2254146 (Other):359125
AEMM AEMMR AGEMEN8 ANEM
0 : 305073 9 :1853886 40 :769947 Min. : 0.0
2015 : 206636 0 : 305073 25 :668407 1st Qu.: 3.0
2016 : 188969 8 : 297912 55 :438609 Median : 10.0
2017 : 159198 7 : 196169 65 :375519 Mean :118.9
2014 : 155990 6 : 132864 YY :305073 3rd Qu.: 28.0
2013 : 124943 5 : 42981 80 :175740 Max. :999.0
(Other):1710021 (Other): 21945 (Other):117535
ANEMR ASCEN BAIN BATI CATIRIS CATL
1 :588102 1: 937624 Z:2850830 Z:2850830 A: 30035 1:2545757
3 :519405 2:1608133 D: 5918 2: 37266
2 :459050 Y: 305073 H:2233566 3: 80286
0 :331413 Z: 581311 4: 187521
99 :305073
4 :277251
(Other):370536
CHAU CHFL CHOS CLIM CMBL CUIS
Z:2850830 1:966755 Z:2850830 Z:2850830 1: 379332 Z:2850830
2:817764 2:1028138
3:688397 3: 150297
4: 72841 4: 860916
Y:305073 5: 18780
6: 108294
Y: 305073
DEROU DIPLM EAU EGOUL ELEC
Z:2850830 18 :473997 Z:2850830 Z:2850830 Z:2850830
13 :452331
17 :307120
YY :305073
16 :286439
14 :261403
(Other):764467
EMPLM GARL HLML ILETUDM ILTM
16 :1327619 1:1506531 1: 537003 Z :2421856 Z :863675
ZZ : 863675 2:1039226 2:2008754 Y : 305073 3 :725591
YY : 305073 Y: 305073 Y: 305073 1 : 56301 2 :474425
21 : 117569 3 : 42715 1 :454269
15 : 105008 2 : 16778 Y :305073
22 : 82872 4 : 7948 4 : 24030
(Other): 49014 (Other): 159 (Other): 3767
IMMIM INAIM INEEM INP11M INP15M
1: 577892 1:479110 0 :2148455 0 :1958176 0 :1829727
2:1967865 2:676390 Y : 305073 Y : 305073 1 : 327706
Y: 305073 3:652878 1 : 298391 1 : 304764 Y : 305073
4: 59905 2 : 85141 2 : 210685 2 : 268584
5: 1536 3 : 12186 3 : 58780 3 : 91461
6:675938 4 : 1364 4 : 11010 4 : 21893
Y:305073 (Other): 220 (Other): 2342 (Other): 6386
INP17M INP19M INP24M INP3M
0 :1766013 2 :1183058 0 :1486207 0 :2275034
1 : 341311 1 :1066824 1 : 478506 Y : 305073
Y : 305073 Y : 305073 2 : 380933 1 : 226415
2 : 295837 3 : 203339 Y : 305073 2 : 42123
3 : 106336 4 : 66880 3 : 143992 3 : 2088
4 : 27314 5 : 13938 4 : 40074 4 : 79
(Other): 8946 (Other): 11718 (Other): 16045 (Other): 18
INP60M INP65M INP5M INP75M
0 :1715731 0 :1917057 0 :2181169 0 :2247449
1 : 529250 1 : 424697 Y : 305073 Y : 305073
Y : 305073 Y : 305073 1 : 261137 1 : 231763
2 : 297942 2 : 202800 2 : 92643 2 : 66397
3 : 2606 3 : 1114 3 : 10034 3 : 126
4 : 208 4 : 78 4 : 714 4 : 18
(Other): 20 (Other): 11 (Other): 60 (Other): 4
INPAM INPER INPER1 INPER2
1 :933808 1 :930543 1 :1286415 1 :1401005
2 :824575 2 :712282 0 : 609245 2 : 467215
0 :648315 3 :370524 2 : 442121 0 : 464663
Y :305073 4 :334420 Y : 305073 Y : 305073
3 :107490 Y :305073 3 : 162673 3 : 165457
4 : 26667 5 :136363 4 : 36238 4 : 37596
(Other): 4902 (Other): 61625 (Other): 9065 (Other): 9821
INPOM INPSM IPONDL IRANM
1 :961312 0 :1646565 Min. : 0.000 1 :2259196
0 :779020 1 : 451745 1st Qu.: 1.004 Y : 305073
2 :711470 Y : 305073 Median : 1.183 2 : 88870
Y :305073 2 : 301109 Mean : 2.068 3 : 74285
3 : 76984 3 : 108144 3rd Qu.: 3.106 4 : 69015
4 : 14898 4 : 28347 Max. :506.470 5 : 36523
(Other): 2073 (Other): 9847 (Other): 17868
METRODOM NBPI RECHM REGION SANI SANIDOM
M:2850830 03 :600847 0: 538158 11:2850830 0: 17456 ZZ:2850830
04 :519672 1: 95994 1: 111392
02 :473779 2: 89148 2:2416909
05 :343159 9: 140375 Y: 305073
01 :311807 Y: 305073
YY :305073 Z:1682082
(Other):296493
SEXEM STAT_CONJM STOCD SURF TACTM TPM
1:1429233 1:1028273 0 : 305073 4 :571276 11:1682082 1:1487709
2:1116524 2: 114351 10:1263064 3 :509245 12: 183924 2: 194373
Y: 305073 3: 274279 21: 545666 5 :424140 21: 554149 Y: 305073
4: 186866 22: 515399 Y :305073 22: 49403 Z: 863675
5: 251538 23: 152208 7 :285136 24: 18617
6: 690450 30: 69420 1 :267500 25: 57582
Y: 305073 (Other):488460 YY: 305073
TRANSM TRIRIS TYPC TYPL VOIT
Z :863675 ZZZZZZ : 951742 1: 633682 1: 915178 0: 780747
5 :773301 931411 : 5167 2: 318107 2:1867706 1:1117288
6 :664544 920841 : 4799 3:1555868 3: 37919 2: 540114
Y :305073 910351 : 4770 4: 35025 4: 13999 3: 107608
2 :106503 920331 : 4197 5: 3075 5: 6442 X: 305073
1 : 53186 931071 : 4099 Y: 305073 6: 9586
(Other): 84548 (Other):1876056
WC
Z:2850830
Mais attention, le problème ici est que cela nous donne des fréquences non pondérées pour l’ensemble de nos variables qualitatives, donc qui n’ont finalement pas grand sens. De plus, pour la seule variable numérique (hors pondération), ‘ANEM’, on voit que la moyenne est de 118,9 ans, ce qui signifierait qu’en moyenne l’ancienneté d’emménagement dans le logement en région Île-de-France est de 119 ans environ, hmhm !! On va voir que cela s’explique vraimsemblablement par des valeurs manquantes codées en “999”.
3.1 Manipulation des variables qualitatives
On peut d’abord travailler sur les variables qualitatives qui correspondent ici à l’essentiel de nos variables.
Comme on le sait, on peut regarder les différents niveaux pour chacune d’entre elles, avec la fonction levels()
. Si on veut appliquer la fonction à l’ensemble de nos variables facteurs sans avoir donc à les indiquer une par une, on peut avoir recours à la fonction sapply()
qui permet d’appliquer la fonction indiquée entre parenthèses (ici levels()
) à tous les éléments de notre table de données.
# Pour info, ici cela donnerait cela :
%>% select(where(is.factor)) %>% sapply(levels)
RP # on peut même se passer de la sélection sur les variables :
# RP %>% sapply(levels)
On peut ensuite vouloir retravailler les modalités de ces variables, car par exemple les modalités ne sont pas parlantes puisque nommées par des codes chiffres, ou parce que les modalités sont trop nombreuses et qu’on souhaiterait les rassembler pour une analyse ultérieure.
Par exemple, si l’on veut étudier la répartition des logements en Île-de-France selon leut type, on peut utiliser la variable TYPL
:
levels(RP$TYPL)
[1] "1" "2" "3" "4" "5" "6"
Mais le moins qu’on puisse dire c’est que les 6 modalités de cette variable ne sont pas parlantes, on peut donc recoder les modalités de cette variable dans une étape DATA préalable comme ici ; on pourra bien sûr enchaîner plus tard les lignes de codes et réaliser cette étape dans une même procédure avec le tableau ou le graphique représentant cette variable.
Commençons ici par l’étape DATA :
# On cherche à quoi correspondent les modalités chiffrées de cette variable
# dans le fichier "meta"
%>% filter(COD_VAR=="TYPL") %>% select(COD_MOD, LIB_MOD) meta
# A tibble: 7 x 2
COD_MOD LIB_MOD
<fct> <fct>
1 1 Maison
2 2 Appartement
3 3 Logement-foyer
4 4 Chambre d'hôtel
5 5 Habitation de fortune
6 6 Pièce indépendante (ayant sa propre entrée)
7 Y Hors résidence principale
# On recode à partir de ces libellés, tout en regroupant certaines modalités
# qui sont très spécifiques et nous intéressent moins :
<- RP %>% mutate(TYPL_moda =as.factor(case_when(TYPL == "1" ~ "Maison",
RP == "2" ~ "Appartement",
TYPL %in% c("3","4","5","6") ~ "Autres")))
TYPL levels(RP$TYPL_moda)
[1] "Appartement" "Autres" "Maison"
Si l’on veut changer l’ordre des modalités, qui s’afficheront comme ci-dessus dans un tableau ou un graphique, on peut utiliser la fonction fct_relevel()
du package forcats
(à installer avant puis à appeler avant de l’utiliser) :
# install.package("forcats")
library(forcats)
<- RP %>% mutate(TYPL_moda = fct_relevel(TYPL_moda, c("Maison","Appartement","Autres")))
RP levels(RP$TYPL_moda)
[1] "Maison" "Appartement" "Autres"
Plus largement, pour travailler sur les variables qualitatives en particulier lorsqu’elles sont en format facteur, le package forcats
est très utile. Outre une fonction de transformation d’une variable caractère en facteur (as_factor()
proche de la version de baseR as.factor()
utilisée juste au-dessus), elle contient plein d’autres fonctions : fct_collapse()
utilisée à la fin de la commande précédente pour renommer ou regrouper des modalités d’une variable (au lieu de la double fonction as.factor()
et case_when()
) ; fct_relevel()
utilisée également au-dessus pour trier les modalités comme on le souhaite ; fct_drop()
pour enlever des niveaux de facteurs vides/sans effectifs ; fct_explicit_na()
pour rendre les NA explicites en créant une modalité “(missing)” ; fct_reorder()
et fct_reorder2()
pour réordonner les modalités d’une variable, très utile pour les graphiques car utilisables directement dans ggplot()
; fct_lump()
pour regrouper les modalités les plus communes (ou au contraire les moins communes) en lui indiquant entre parenthèses le nombre n=
de modalités souhaitées ou la proportion minimum souhaitée prop=
, et en sélectionnant la variable avec la fonction pull()
avant car elle doit être en format vecteur et non data.frame ; ou encore fct_recode()
pour changer le niveau des facteurs ; fct_other()
; fct_infreq()
et fct_inorder()
; etc. Un bon récapitulatif de ces fonctions est présenté ici.
3.2 Manipulation des variables quantitatives
Comme nous l’avons vu plus haut, il n’y a que 2 variables quantitatives dans cette base et l’une est la pondération, donc on va regarder plus précisément la variable ANEM
. Cependant, celle-ci aussi est particulière car c’est une variable numérique constituée d’entiers naturels (et non de valeurs réelles) qui vont de 0 à 120, avec une modalité “999” ; dans le dictionnaire des variables (ou le fichier des métadonnées) disponible également sur le site de l’Insee, on se rend compte que la variable a été pensée comme catégorielle avec des modalités d’abord codées comme “000”, “001”, etc. Et quand on regarde à quoi correspond la modalité “999”, on voit qu’elle permet de donner la situation d’un “logement ordinaire inoccupé”.
%>% filter(COD_VAR=="ANEM") %>% select(COD_MOD, LIB_MOD) %>% tail() meta
# A tibble: 6 x 2
COD_MOD LIB_MOD
<fct> <fct>
1 115 Depuis 115 ans
2 116 Depuis 116 ans
3 117 Depuis 117 ans
4 118 Depuis 118 ans
5 120 Depuis 120 ans
6 999 Logement ordinaire inoccupé
On va recoder alors cette modalité comme des valeurs manquantes : dans R
, ces valeurs sont notées NA
. Cela nous permettra de la traiter comme une variable continue sur laquelle on pourra réaliser des statistiques sur sa distribution. Pour cela, dans une fonction mutate()
combinée avec la fonction across()
permettant de spécifier la variable sur laquelle on veut appliquer la transformation, on peut utiliser la fonction na_if
qui permet de convertir des valeurs en NA
.
<- RP %>% mutate(across(ANEM, ~ na_if(ANEM, 999))) RP
On peut alors regarder rapidement la distribution de cette variable.
summary(RP$ANEM)
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
0.00 3.00 8.00 13.49 20.00 117.00 305073
Si ce n’est pas la variable continue idéale (car encore une fois elle n’est pas à valeurs réelles), on va la prendre comme exemple ici pour étudier les différentes procédures que l’on peut réaliser dans une étape “Data wrangling” sur de telles variables.
3.2.1 Détecter et “visualiser” les valeurs manquantes
Même si l’information est donnée par la fonction summary()
(mais qui nous donne d’autres informations), on peut également compter le nombre de valeurs manquantes NA
pour chacune des variables avec la fonction colSums()
, pour les avoir en proportion du nombre total d’observations (lignes), on peut utiliser la fonction colMeans()
; sinon ; on peut utiliser la fonction summarise
combinée avec across(where()), )
:
colSums(is.na(RP))
COMMUNE ARM IRIS ACHL AEMM AEMMR AGEMEN8
0 0 0 0 0 0 0
ANEM ANEMR ASCEN BAIN BATI CATIRIS CATL
305073 0 0 0 0 0 0
CHAU CHFL CHOS CLIM CMBL CUIS DEROU
0 0 0 0 0 0 0
DIPLM EAU EGOUL ELEC EMPLM GARL HLML
0 0 0 0 0 0 0
ILETUDM ILTM IMMIM INAIM INEEM INP11M INP15M
0 0 0 0 0 0 0
INP17M INP19M INP24M INP3M INP60M INP65M INP5M
0 0 0 0 0 0 0
INP75M INPAM INPER INPER1 INPER2 INPOM INPSM
0 0 0 0 0 0 0
IPONDL IRANM METRODOM NBPI RECHM REGION SANI
0 0 0 0 0 0 0
SANIDOM SEXEM STAT_CONJM STOCD SURF TACTM TPM
0 0 0 0 0 0 0
TRANSM TRIRIS TYPC TYPL VOIT WC TYPL_moda
0 0 0 0 0 0 0
# Pour les avoir en proportion par rapport au nombre total d'observations
# et arrondies à 2 chiffres après la virgule :
round(colMeans(is.na(RP)*100), 2)
COMMUNE ARM IRIS ACHL AEMM AEMMR AGEMEN8
0.0 0.0 0.0 0.0 0.0 0.0 0.0
ANEM ANEMR ASCEN BAIN BATI CATIRIS CATL
10.7 0.0 0.0 0.0 0.0 0.0 0.0
CHAU CHFL CHOS CLIM CMBL CUIS DEROU
0.0 0.0 0.0 0.0 0.0 0.0 0.0
DIPLM EAU EGOUL ELEC EMPLM GARL HLML
0.0 0.0 0.0 0.0 0.0 0.0 0.0
ILETUDM ILTM IMMIM INAIM INEEM INP11M INP15M
0.0 0.0 0.0 0.0 0.0 0.0 0.0
INP17M INP19M INP24M INP3M INP60M INP65M INP5M
0.0 0.0 0.0 0.0 0.0 0.0 0.0
INP75M INPAM INPER INPER1 INPER2 INPOM INPSM
0.0 0.0 0.0 0.0 0.0 0.0 0.0
IPONDL IRANM METRODOM NBPI RECHM REGION SANI
0.0 0.0 0.0 0.0 0.0 0.0 0.0
SANIDOM SEXEM STAT_CONJM STOCD SURF TACTM TPM
0.0 0.0 0.0 0.0 0.0 0.0 0.0
TRANSM TRIRIS TYPC TYPL VOIT WC TYPL_moda
0.0 0.0 0.0 0.0 0.0 0.0 0.0
# Ou en langage tidyverse sur les seules variables numériques :
%>% summarise(across(where(is.numeric), ~ sum(is.na(.)))) RP
# A tibble: 1 x 2
ANEM IPONDL
<int> <int>
1 305073 0
Si votre base de données contient plusieurs variables avec un certain nombre de valeurs manquantes, il peut être intéressant d’en faire une analyse plus poussée. Différents packages existent pour détecter et visualiser les données manquantes. L’un d’entre eux est le package naniar
: quelques fonctions permettent d’abord de décrire la base selon ses valeurs manquantes.
library(naniar)
# Ci-dessous : nombre de cellules du tableau ou de n_ij d'une matrice
# qui correspondent à des valeurs manquantes :
n_miss(RP)
[1] 305073
# Pour les avoir en proportion du nombre total de cellules du tableau
# et non des seules lignes comme précédemment,
# le résultat est déjà en pourcentage, sinon utiliser `prop_miss(RP)`)
pct_miss(RP)
[1] 0.1528743
# Ci-dessous : nombre de cellules du tableau ou de n_ij d'une matrice
# qui correspondent à des valeurs renseignées :
n_complete(RP)
[1] 199253027
#en proportion
pct_complete(RP)
[1] 99.84713
On peut ensuite visualiser le nombre de valeurs manquantes par variable, avec la fonction gg_miss_var()
du même package.
# 1er type de visualisation des valeurs manquantes
%>% gg_miss_var() RP
On peut également demander dans gg_miss_var()
à ce que les valeurs soient en pourcentage, avec l’argument show_pct=TRUE
.
On peut aussi réaliser des graphiques montrant le nombre de valeurs manquantes pour l’ensemble des variables numériques de la base, en fonction d’une autre variable de nature ‘factor’, avec l’argument fct=
dans gg_miss_fct()
. Cela est intéressant pour voir si certaines valeurs manquantes des variables se retrouvent davantage dans des modalités précises d’une autre variable. Par exemple, ici, selon le type de logement :
%>% select(where(is.numeric), TYPL_moda) %>%
RP gg_miss_fct(fct = TYPL_moda)
On voit que les valeurs manquantes sont plus nombreuses en proportion pour la modalité “5” et sont moins nombreuses dans les modalités “1” et “3” ; elles ne se distribuent ainsi pas de manière uniforme selon la variable du type de logement.
Plus généralement, la fonction gg_miss_upset()
de ce même package naniar
permet de visualiser des dépendances entre les valeurs manquantes des variables qui en ont, ici, cela ne marchera donc pas puisque nous n’avons qu’une variable avec valeurs manquantes. Voici le code néanmoins :
%>% select(where(is.numeric)) %>% gg_miss_upset() RP
library(VIM)
%>% select(where(is.numeric)) %>% aggr(sortVar=TRUE, only.miss = TRUE) RP
Enfin, il est possible d’appliquer la fonction geom_miss_point()
à une fonction ggplot
, dans ce cas les valeurs manquantes de la ou des variables sont remplacées par des valeurs 10% plus basses que la valeur minimum observée des variables, et cela afin de les visualiser.
Il existe bien sûr bien d’autres packages, comme funModeling
, Amelia
et sa fonction missmap()
, ou encore visdat
et sa fonction vis_miss()
. Enfin, d’autres packages comme VIM
ou MICE
permettent, non seulement de visualiser ces valeurs manquantes, mais également de leur appliquer des techniques pour les “gérer”, c’est ce que l’on va voir maintenant en résumé.
3.2.2 Gérer les valeurs manquantes
Il est bien de connaître le nombre et la proportion de valeurs manquantes dans nos données, comment ces dernières se répartissent entre elles, etc., mais il faut aussi comprendre quel impact elles peuvent avoir sur des analyses statistiques, de régressions ou autres algorithmes.
Dans une base de données tirée d’une enquête, les valeurs manquantes peuvent provenir d’une non-réponse de la part de l’enquêté (que ce soit un individu ou une entreprise), cette non-réponse pouvant être “totale” (on a aucune donnée pour cet enquêté alors qu’il fait partie de l’échantillon) ou “partielle” (on a une partie des réponses mais pas à toutes les questions et donc des variables parfois avec des valeurs manquantes) ; ou bien encore elles peuvent être dues à une mauvaise saisie de l’information par l’enquêteur. La pondération, si elle est présente dans une enquête, peut permettre de corriger cette non-réponse totale, voire partielle.
Les conséquences des valeurs manquantes dans une base de données dépendent de plusieurs choses : on doit d’abord se demander si l’information perdue aurait été pertinente et/ou aurait apporté un élément particulier/supplémentaire. Ensuite, la perte éventuelle d’information est-elle importante, en nombre/en proportion. Et enfin (et surtout), peut-elle créer un biais lors de l’estimation et précision du phénomène que l’on souhaite observer, décrire, analyser, etc. Selon l’importance de ces conséquences, il faut traiter ces valeurs manquantes, c’est-à-dire utiliser une procédure la plus adaptée possible selon le potentiel biais repéré.
Traditionnellement dans la littérature, on distingue 3 types de valeurs manquantes :
- valeur manquante entièrement due au hasard (‘MCAR’ pour Missing completely at random) : il n’y a pas de lien entre la valeur manquante pour une variable donnée et les autres variables, dit autrement la probabilité pour une variable qu’elle ait une valeur manquante est constante dans les données, elle ne diffère pas selon d’autres caractéristiques des individus ;
- valeur manquante due au hasard (‘MAR’ pour Missing at random) : il y a un lien entre la valeur manquante pour une variable donnée et les valeurs observées d’autres variables, c’est-à-dire que la probabilité pour une variable qu’elle ait une valeur manquante dépend d’autres variables (de leurs valeurs observées), elle ne sera donc pas la même selon les individus, c’est ce qu’on essayait de regarder lorsqu’on a utilisé plus haut la fonction
gg_miss_fct(fct=)
; - valeur ne manquant pas au hasard (‘NMAR’ pour Non missing at random) : il y a un lien entre la valeur manquante pour une variable et les valeurs manquantes/non observées d’autres variables. Ce sont celles qui risquent d’entraîner des biais importants si on ne les traite pas, c’est ce qu’on essayait de regarder plus haut également avec la fonction
gg_miss_upset()
cette fois.
Comment alors les gérer ? En pratique, il est d’usage lorsque la proportion de valeurs manquantes ne dépasse pas 5% des données de ne rien faire de particulier ou simplement de les supprimer (vous pouvez pour la savoir utiliser les premières fonctions du package naniar
présentées précédemment). Sinon, on essaye d’appliquer plusieurs méthodes, simples ou plus complexes.
Dans le cas de valeurs manquantes entièrement dues au hasard (MCAR) et/ou d’une faible proportion des valeurs manquantes dans le total de la table de données, on peut décider de supprimer toutes les lignes qui contiennent au moins une valeur manquante, afin d’avoir une table de données complètes, on peut utiliser la fonction na.omit()
ou complete.cases()
; attention à ne pas remplacer votre table de données initiale en réalisant cette procédure.
<- na.omit(RP)
RP_sansNA # OU :
# RP_sansNA <- RP[complete.cases(RP), ]
# Pour vérification :
colSums(is.na(RP_sansNA))
COMMUNE ARM IRIS ACHL AEMM AEMMR AGEMEN8
0 0 0 0 0 0 0
ANEM ANEMR ASCEN BAIN BATI CATIRIS CATL
0 0 0 0 0 0 0
CHAU CHFL CHOS CLIM CMBL CUIS DEROU
0 0 0 0 0 0 0
DIPLM EAU EGOUL ELEC EMPLM GARL HLML
0 0 0 0 0 0 0
ILETUDM ILTM IMMIM INAIM INEEM INP11M INP15M
0 0 0 0 0 0 0
INP17M INP19M INP24M INP3M INP60M INP65M INP5M
0 0 0 0 0 0 0
INP75M INPAM INPER INPER1 INPER2 INPOM INPSM
0 0 0 0 0 0 0
IPONDL IRANM METRODOM NBPI RECHM REGION SANI
0 0 0 0 0 0 0
SANIDOM SEXEM STAT_CONJM STOCD SURF TACTM TPM
0 0 0 0 0 0 0
TRANSM TRIRIS TYPC TYPL VOIT WC TYPL_moda
0 0 0 0 0 0 0
On se retrouve alors avec une table de 2 545 757 observations (lignes), on a donc perdu 305 073 observations, soit 10.70% des observations de la table initiale, ce qui est cohérent avec ce qu’on avait retiré de notre première analyse de détection des valeurs manquantes.
Des techniques d’imputation simple peuvent également être utilisées. On peut par exemple remplacer les valeurs manquantes d’une variable quantitative par sa moyenne ou sa médiane, pour cela on peut utiliser la fonction replace_na()
du package tidyr
, ou impute()
du package Hmisc
, ou encore na.aggregate()
du package zoo
On donne ainsi une valeur “artificielle” pour remplacer la valeur manquante. Dans le cas de variables qualitatives, on peut, de même, imputer la modalité dominante (avec la fonction mode()
du package Hmisc
; ou avec l’argument mode=
du package zoo
). Par exemple, voici les codes pour remplacer les valeurs manquantes de la variable ‘ANEM’ par sa moyenne (ici on ne fait pas tourner ces codes) :
# Comme les valeurs de la variable 'ANEM' sont des entiers naturels,
# il faut arrondir la moyenne à l'unité, sinon la fonction renvoie
# une erreur
%>% mutate(ANEM_bis = replace_na(ANEM, round(mean(ANEM, na.rm=TRUE), 0))) %>% select(ANEM, ANEM_bis)
RP
library(Hmisc)
$ANEM_bis <- with(RP, impute(ANEM, mean))
RP
library(zoo)
$ANEM_bis <- na.aggregate(RP$ANEM, FUN = mean) RP
On peut néanmoins réaliser ce type d’imputation simple de manière un petit peu plus subtile. Par exemple, si la moyenne de la variable diffère sensiblement selon une autre variable (catégorielle), dans ce cas, on va plutôt remplacer les valeurs manquantes de la variable selon la moyenne associée à chaque modalité de cette autre variable en ajoutant un group_by()
avant la fonction mutate()
si on utilise la fonction replace_na()
comme dans l’exemple précédent.
Si on ne veut pas supprimer ces lignes d’observations et perdre ainsi d’autres informations (celles des variables pour lesquelles la valeur était renseignée pour cette même observation), on peut simplement créer une variable indicatrice de valeur manquante, remplacer les NA
par ‘999’ comme dans notre base de données actuelle pour des variables quantitatives, ou par une modalité ‘Manquant’ ou ‘Missing’ pour des variables qualitatives.
Plusieurs autres méthodes existent également dans le cas de valeurs manquantes dues au hasard (MAR), en voici la liste pour information et sans prétention d’exhaustivité : - analyse pondérée pour des valeurs MAR qui consiste à calculer la probabilité qu’une observation soit complète et ensuite à affecter à chacune des observations complètes, un poids inversement proportionnel à cette probabilité ; - imputation de la dernière observation pour des données temporelles ; - imputation “hot-deck” qui consiste à remplacer la valeur manquante par une valeur observée chez un autre individu ayant les mêmes caractéristiques, ou “cold-deck” (même démarche que précédement, sauf que la valeur imputée vient d’une autre source) ; - imputation par le “plus proche voisin” en utilisant une fonction de distance basée sur plusieurs autres variables/caractéristiques de l’individu ; - imputation par un modèle de régression où l’on va remplacer la valeur manquante par une valeur prédite obtenue par régression sur données complètes de la variable comportant des valeurs manquantes.
Il y a aussi des techniques plus complextes d’imputation multiple qui consiste à créer plusieurs valeurs possibles pour une valeur manquante d’une variable, cela peut être adaptée là aussi lorsque les valeurs manquantes sont dues au hasard (MAR).
Vous trouverez de multiples ressources sur internet dans des ouvrages libres d’accès, ou vous pouvez aller voir un des chapitres de l’ouvrage principal support du cours (Husson, 2018), avec des exemples d’utilisation.
3.2.3 Détecter et “visualiser” les valeurs aberrantes
Pour cela, on va utiliser une autre base, téléchargée sur opendatasoft à partir de la fonction créée lors de la séance 1 de M. Grasland. Cette base dont le nom est “aides-entreprises-covid-19-france” renseigne du nombre et du montant des aides financières accordées aux entreprises françaises durant la crise du covid_19, selon en particulier le secteur d’activité et la localisation géographique.
# Rappel fonction (soit vous l'avez encore dans votre environnement, soit
# vous l'avez sauvegardé et dans ce cas il faut l'appeler, soit vous devez
# refaire tourner les lignes de codes suivantes...)
<- function(idtab, rows, start) {
get_data <- paste0("https://public.opendatasoft.com/api/records/1.0/search/?dataset=",idtab,"&q=&rows=",rows,"&start=",start,sep="")
url <- GET(url)
x <- fromJSON(rawToChar((x$content)))
y <- y$records$fields
don return(don)
}library(jsonlite)
library(httr)
<- get_data(idtab = "aides-entreprises-covid-19-france",
dt_aidesCovid rows=2000,
start=0)
%>% glimpse() dt_aidesCovid
Rows: 1,680
Columns: 13
$ reg_code <int> 84, 84, 84, 84, 84, 27, 27, 27, 27, 24, 24, 24~
$ geo_point_2d <list> <46.09983, 5.34885>, <45.728000, 4.164813>, <~
$ naf_section_name <chr> "Commerce", "Industrie manufacturière", "Const~
$ nombre_entreprises <int> 2602, 1042, 3034, 6577, 3021, 61, 268, 45, 233~
$ plan <chr> "Fonds de solidarité", "Fonds de solidarité", ~
$ financial_support_count <int> 10424, 3820, 8936, 19967, 28020, 366, 2212, 19~
$ reg_name <chr> "Auvergne-Rhône-Alpes", "Auvergne-Rhône-Alpes"~
$ naf_section_code <chr> "G", "C", "F", "F", "I", "J", "R", "K", "L", "~
$ total_meuros <dbl> 31.889084, 9.551646, 13.922570, 33.039981, 247~
$ dep_name <chr> "Ain", "Loire", "Loire", "Rhône", "Savoie", "N~
$ iso3_code <chr> "FXX", "FXX", "FXX", "FXX", "FXX", "FXX", "FXX~
$ dep_code <chr> "01", "42", "42", "69", "73", "58", "58", "89"~
$ total <dbl> 31889084, 9551646, 13922570, 33039981, 2478684~
%>% head() %>% gt() dt_aidesCovid
reg_code | geo_point_2d | naf_section_name | nombre_entreprises | plan | financial_support_count | reg_name | naf_section_code | total_meuros | dep_name | iso3_code | dep_code | total |
---|---|---|---|---|---|---|---|---|---|---|---|---|
84 | 46.09983, 5.34885 | Commerce | 2602 | Fonds de solidarité | 10424 | Auvergne-Rhône-Alpes | G | 31.889084 | Ain | FXX | 01 | 31889084 |
84 | 45.728000, 4.164813 | Industrie manufacturière | 1042 | Fonds de solidarité | 3820 | Auvergne-Rhône-Alpes | C | 9.551646 | Loire | FXX | 42 | 9551646 |
84 | 45.728000, 4.164813 | Construction | 3034 | Fonds de solidarité | 8936 | Auvergne-Rhône-Alpes | F | 13.922570 | Loire | FXX | 42 | 13922570 |
84 | 45.871312, 4.640724 | Construction | 6577 | Fonds de solidarité | 19967 | Auvergne-Rhône-Alpes | F | 33.039981 | Rhône | FXX | 69 | 33039981 |
84 | 45.477437, 6.442872 | Hébergement et restauration | 3021 | Fonds de solidarité | 28020 | Auvergne-Rhône-Alpes | I | 247.868466 | Savoie | FXX | 73 | 247868466 |
27 | 47.115383, 3.504115 | Information et communication | 61 | Fonds de solidarité | 366 | Bourgogne-Franche-Comté | J | 0.736895 | Nièvre | FXX | 58 | 736895 |
Avant tout, comme indiqué dans la section précédente (2. Manipulation de la base), il faut comprendre quel est le format de ces données. Ici, les données sont micro-agrégées, c’est-à-dire qu’une observation est le croisement d’un secteur et d’un département ; il faut donc sommer les variables pour avoir une idée des montants de ces aides financières.
Par exemple, on somme dans le code suivant le montant total des aides en euros et milliers d’euros, puis le nombre total des aides et enfin le nombre total d’entreprise concernées par ces aides ; la fonction fmt_number()
ajoutée à la fin du code permet simplement d’avoir une séparation visuelle par milliers (un blanc ici ; le format chez les anglo-saxons étant une virgule…), puisque nous avons ici des montants très importants !
%>% summarise(total_aides=sum(total),
dt_aidesCovid total_aides_meuros=sum(total_meuros),
nb_totales_aides=sum(financial_support_count),
nb_totales_entre=sum(nombre_entreprises)) %>% gt() %>%
fmt_number(columns = c("total_aides", "total_aides_meuros", "nb_totales_aides",
"nb_totales_entre"), sep_mark = " ", decimals = 0)
total_aides | total_aides_meuros | nb_totales_aides | nb_totales_entre |
---|---|---|---|
35 490 463 923 | 35 490 | 10 531 765 | 2 016 218 |
On veut plus précisément étudier dans cette sous-section les possibles valeurs aberrantes. On peut alors afficher les valeurs maximum pour chacune de ces variables.
%>% summarise(aides_max=max(total), aides_meuros_max=max(total_meuros),
dt_aidesCovid nb_max_aides=max(financial_support_count),
nb_max_entre=max(nombre_entreprises)) %>% gt() %>%
fmt_number(columns = 1:4, sep_mark = " ", decimals = 0)
aides_max | aides_meuros_max | nb_max_aides | nb_max_entre |
---|---|---|---|
1 649 963 187 | 1 650 | 184 009 | 35 383 |
Ou faire quelques graphs sur la variable du montant total des aides en milliers d’euros “total_meuros” pour mieux visualiser de possibles valeurs aberrantes, de manière générale avec un histogramme, ou ventilées par région avec des “boîtes à moustache” :
%>% ggplot() + aes(x=total_meuros) + geom_histogram(bins=150) dt_aidesCovid
%>% ggplot() + aes(x = reg_name, y = total_meuros) + geom_boxplot() +
dt_aidesCovid coord_flip()
On voit en effet quelques points qui semblent être des valeurs aberrantes ou “outliers” ; parfois, c’est à tel point qu’on n’arrive même pas à distinguer les différentes valeurs de la distribution marquées par le Boxplot et par conséquent les boxplot sont ramassées. Pour rappel, dans un boxplot, par défaut un point est affiché comme aberrant s’il est en dehors de l’intervalle suivant : \(I=[Q_{1}−1.5×IQR ; Q_{3}+1.5×IQR]\), IQR
étant l’intervalle interquartile donc la différence entre Q1 et Q3.
Mais s’agit-il de “vraies” valeurs aberrantes ? Combien d’observations concernent-elles ? La fonction boxplot.stats()
permet de récupérer les valeurs des observations indiquées comme aberrantes, comme cela on peut créer ensuite une variable indiquant si oui ou non l’observation a une valeur “aberrante”. Faisons-cela pour la variable “total_meuros” par exemple.
# On récupère les valeurs de la partie 'out' des sorties de la fonction
# 'boxplot.stats', qui correspondent aux valeurs de tout point de données
# qui se situe au-delà des extrêmes de la boxplot
<- boxplot.stats(dt_aidesCovid$total_meuros)$out
val_outliers
# On crée une variable dans notre table d'"identification" de ces outliers
# avec comme modalité "vraie" si l'observation a une valeur "outliers",
# sinon "Faux"
<- dt_aidesCovid %>%
dt_aidesCovid mutate(total_meuros_outliers =
case_when(total_meuros %in% c(val_outliers) ~ "Vrai",
TRUE ~ "Faux"))
# Puis on regarde la répartition avec la fonction `tabyl()` du package `janitor()`
library(janitor)
%>% tabyl(total_meuros_outliers) %>%
dt_aidesCovid adorn_pct_formatting() %>% adorn_totals("row")
# A tibble: 3 x 3
total_meuros_outliers n percent
<chr> <dbl> <chr>
1 Faux 1484 88.3%
2 Vrai 196 11.7%
3 Total 1680 -
On y lit que pour cette variable, il y aurait près de 11,7% de valeurs aberrantes telles qu’indiquées par le boxplot, cela fait beaucoup ! On peut regarder plus précisément à quelles observations elles correspondent et à quel montant.
%>% filter(total_meuros_outliers=='Vrai') %>%
dt_aidesCovid select(total_meuros, naf_section_name, dep_name) %>%
arrange(total_meuros) %>% head(10) %>% gt()
total_meuros | naf_section_name | dep_name |
---|---|---|
41.41726 | Transports et entreposage | Nord |
41.54758 | Commerce | Finistère |
41.70506 | Activités spécialisées, scientifiques et techniques | Bas-Rhin |
41.74750 | Hébergement et restauration | Aube |
42.43167 | Hébergement et restauration | Lot |
42.45356 | Hébergement et restauration | Eure-et-Loir |
42.94316 | Hébergement et restauration | Corrèze |
43.24990 | Hébergement et restauration | Haute-Loire |
43.32271 | Commerce | Morbihan |
43.44017 | Commerce | Calvados |
OK on voit donc que cette procédure attribue des valeurs aberrantes pour des valeurs au-dessus de “41.41726” et donc à des observations qui seraient intéressants néanmoins à conserver ; attention par conséquent à identifier les valeurs dites aberrantes, éventuellement avec plusieurs méthodes (cf. ci-dessous) et surtout à les comprendre, il ne s’agit pas juste de les identifier pour les exclure ensuite des analyses.
Il existe en effet bien d’autres méthodes (méthode basée sur les percentiles ; méthode de Hampel), ainsi que des tests : par exemple, le package outliers
vous permet de tester si une valeur (max ou min) est bien une valeur aberrante avec la fonction grubbs.test()
(attention bis : à utiliser avec grande précaution et beaucoup de parcimonie), ou avec le package EnvStats
et la fonction rosnerTest()
pour détecter plusieurs “outliers” à la fois.
Pour gérer ces variables aberrantes, on peut les supprimer bien sûr si l’on est sûr que la valeur de la variable n’est pas “normale”, par exemple si on a une variable de salaire avec des modalités inférieures à 0, oui dans ce cas ce sont des mauvais outliers (et d’ailleurs peut-être même pas identifiés comme tel statistiquement) et on peut les supprimer ; de même pour des variables de résultats économiques, on va souvent élaguer la distribution en retirant les 1% (par exemple) du bas et du haut de la distribution pour supprimer des potentiels outliers.
Sinon, on les isole en créant une variable dichotomique “0/1” ou “Faux/Vrai” ; ou on crée une variable qualitative avec plusieurs catégories.
Dans les graphiques, en particulier les boîtes à moustache, on peut les supprimer avec l’option outlier.shape = NA
et mettre ensuite une échelle plus réduite (avec ylim=c(0,175)
) pour que le graphique soit plus lisible, mais il faut alors bien préciser dans la légende que certaines valeurs ne sont pas visibles sur le graphique car retirées.
%>% ggplot() + aes(x = reg_name, y = total_meuros) +
dt_aidesCovid geom_boxplot(outlier.shape = NA) + coord_flip(ylim = c(0,175)) +
labs(title = "Distribution des aides financières en millions d'euros,
selon la région", y="", x="Région",
caption="Rq : les valeurs au-delà de 175 millions d'euros ne sont pas affichées sur le graphique.") +
theme(plot.caption = element_text(hjust=0))
3.2.4 Découper en classes une variable quantitative
On peut enfin découper en classes une variable quantitative et en faire donc une variable qualitative. On utilise pour cela la fonction cut()
du langage de base de R
. On peut par exemple découper la variable selon les principaux indicateurs de la distribution.
library(rstatix)
%>% get_summary_stats(total_meuros) %>% gt() dt_aidesCovid
variable | n | min | max | median | q1 | q3 | iqr | mad | mean | sd | se | ci |
---|---|---|---|---|---|---|---|---|---|---|---|---|
total_meuros | 1680 | 0.006 | 1649.963 | 6.001 | 1.828 | 17.644 | 15.815 | 7.56 | 21.125 | 60.662 | 1.48 | 2.903 |
$total_meuros_cat <- cut(dt_aidesCovid$total_meuros,
dt_aidesCovidbreaks = c(0,
quantile(dt_aidesCovid$total_meuros,0.25),
mean(dt_aidesCovid$total_meuros),
max(dt_aidesCovid$total_meuros)))
%>% tabyl(total_meuros_cat) %>% adorn_pct_formatting() %>%
dt_aidesCovid adorn_totals("row") %>% gt()
total_meuros_cat | n | percent |
---|---|---|
(0,1.83] | 420 | 25.0% |
(1.83,21.1] | 901 | 53.6% |
(21.1,1.65e+03] | 359 | 21.4% |
Total | 1680 | - |
On a une classe majoritaire (du Q1 à la moyenne), mais cela nous permet de distinguer 2 classes pour lesquelles le montant des aides financières est très faible ou au contraire très élevé.
À noter que si la variable quantitative en question a des valeurs manquantes, il faudra utiliser la fonction fancycut()
ou wafflecut()
du package fancycut
, l’inconvénient est que cela nous oblige à indiquer les valeurs des différents indicateurs de la distribution. Pour trouver le même résultat que précédemment, le code serait alors le suivant :
library(fancycut)
$total_meuros_cat1 <- fancycut(dt_aidesCovid$total_meuros, '1'='(0,1.83]',
dt_aidesCovid'2'='(1.83,21]', '3'='(21,1.64e+03]',
na.bucket='Manquant')
%>% tabyl(total_meuros_cat1) %>% adorn_pct_formatting() %>%
dt_aidesCovid adorn_totals("row") %>% gt()