Dans ce document, les réponses aux exercices sont légèrement différentes de la correction 'standard' car nous allons utiliser les fonctions offertes par le package 'tidyverse'. Ce package regroupe en fait plusieurs packages (dplyr, ggplot2, tibble ...) extremement utiles, ce qui en fait un outil quasiment incontournable aujourd'hui pour la mise en forme et l'analyse de données. Plus généralement, ce document additionel (et les suivants) ont vocation à apporter un certain nombre de conseils et de 'bonnes pratiques' pour coder en R de manière efficace, au delà des simples réponses aux questions. Les codes ci-dessous ne sont qu'un exemple de ce que j'aurais écrit, ce document peut être amené à évoluer, notamment si vous proposez de meilleurs solutions !
Le codage informatique étant un moyen d'effectuer et d'automatiser rapidement des tâches redondantes, chaque script ayant vocation à être réutiliser devrait comporter une première partie permettant l'importation de tous les principaux outils nécessaires à l'analyse, c'est à dire:
## Chargement des packages (n'oubliez pas de les installer la première fois que vous voulez les utiliser)
library(tidyverse)
library(gridExtra)
## Chargement d'un autre de nos scripts contenant des fonctions utiles
# source('chemin_acces/mes_fonctions_utiles.R')
Notez ici qu'une ligne a été laissée en commentaire. Lorsque vous n'utilisez pas pour le moment une commande qui pourrait être utile plus tard, il est bien plus pratique de la mettre en commentaire temporairement. Il est toujours plus rapide d'enlever le '#' que de retrouver et retaper toute une commande.
Raccourcis clavier utiles:
## Définition du répertoire de travail courant
setwd("C:/Users/user/Google Drive/Travail/Enseignement/ADD STID 2A/Codes R/Donnees")
#setwd("autre_chemin_sur_mon_pc_perso_par_exemple")
## Importation de la base de données
raw_db = read_csv2("Recensement_12.csv")
Ci-dessous notez que l'on utilise la fonction 'read_csv2' qui est l'équivalent du classique 'read.csv2', mais qui importe la base de données au format tibble au lieu du traditionnel dataframe. Ce format, plus pratique notamment pour la visualisation, est au centre de l'écriture en 'version tidyverse'. De plus, les fonctions du tidyverse utilisent traditionellement '_' au lieu de '.' comme séparateur, comme dans 'read_csv2'.
raw_db
## # A tibble: 599 x 12
## X1 AGE SEXE REGION STAT_MARI SAL_HOR SYNDICAT CATEGORIE NIV_ETUDES
## <dbl> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr> <chr>
## 1 1 58 F NE C 13.2 non Employe BAC+3
## 2 2 40 M W M 12.5 <NA> Construc~ Sans dipl~
## 3 3 29 M S C 14 <NA> Employe BAC+2
## 4 4 59 M NE D 10.6 <NA> Transpor~ BAC
## 5 5 51 M W M 13 <NA> Transpor~ Sans dipl~
## 6 6 19 M <NA> C NA non <NA> <NA>
## 7 7 64 F S M 19.6 <NA> Producti~ BAC
## 8 8 23 F NE C 13 <NA> Professi~ BAC+3
## 9 9 47 M NW M 20.1 oui Construc~ BAC
## 10 10 66 F S D 12.5 non Employe BAC
## # ... with 589 more rows, and 3 more variables: NB_PERS <dbl>, NB_ENF <dbl>,
## # REV_FOYER <chr>
On voit que le format tibble offre une visualisation des 10 premières lignes de la base de données, et sous le nom de chaque colonne, on peut voir le type de variables qu'elle contient ('chr' : character, 'dbl' : double, ...). On s'aperçoit ainsi facilement que la première colonne est inutile car elle vient probablement d'une sauvegarde non désirée des numéros de ligne. Ainsi, on peut supprimer cette variable avec la commande ci-dessous:
db = raw_db %>% select(- X1)
db
## # A tibble: 599 x 11
## AGE SEXE REGION STAT_MARI SAL_HOR SYNDICAT CATEGORIE NIV_ETUDES NB_PERS
## <dbl> <chr> <chr> <chr> <dbl> <chr> <chr> <chr> <dbl>
## 1 58 F NE C 13.2 non Employe BAC+3 2
## 2 40 M W M 12.5 <NA> Construc~ Sans dipl~ 2
## 3 29 M S C 14 <NA> Employe BAC+2 2
## 4 59 M NE D 10.6 <NA> Transpor~ BAC 4
## 5 51 M W M 13 <NA> Transpor~ Sans dipl~ 8
## 6 19 M <NA> C NA non <NA> <NA> NA
## 7 64 F S M 19.6 <NA> Producti~ BAC 3
## 8 23 F NE C 13 <NA> Professi~ BAC+3 2
## 9 47 M NW M 20.1 oui Construc~ BAC 3
## 10 66 F S D 12.5 non Employe BAC 1
## # ... with 589 more rows, and 2 more variables: NB_ENF <dbl>, REV_FOYER <chr>
Beaucoup de chose doivent être préciser ici. Tout d'abord, notons que l'on définit une nouvelle variabe 'db' pour ne pas modifier la base de données originale 'raw_db', en travaillant plutôt sur une copie. Il s'agit d'une pratique plus sûre pour éviter de perdre des données en cas de mauvaise manipulation.
De plus nous introduisons un symbole %>% qui parait bizarre au premier abord mais qui est très (très (très)) pratique à utiliser lorsque l'on veut éviter l'accumulation de parenthèses et avoir un code claire. On appelle ce symbole le pipe, car il permet d'écrire des instructions à la suite, comme dans un tuyau (pipe en anglais), plutôt qu'entre parenthèses. Formellement, on a simplement l'équivalence d'écriture: f(x,y) = x %>% f(y) . C'est à dire que le pipe utilise la variable à gauche du symbole comme le premier argument de la function à droite du symbole. Cela peut paraitre assez futile au premier abord mais regardez cet exemple pour vous convaincre de son intérêt:
round(sqrt(exp(sin(5))), digits = 3)
## [1] 0.619
5 %>% sin %>% exp %>% sqrt %>% round(digits = 3)
## [1] 0.619
Ces deux instructions donnent le même résultat, mais on voit que la deuxième est bien plus claire, se lit dans l'ordre 'naturel' d'application des fonctions, et évite des erreurs de parenthèses difficile à debugger. Notez également que lorsque la fonction n'a qu'un argument, les parenthèses sont optionnelles, on peut donc écrire exp ou exp() sans différence.
Enfin, tout l'intérêt du tidyverse vient de l'utilisation d'un nombre réduit de 'verbes' associés à des fonctions très courantes pour la manipulation des données. Voici une liste des verbes les plus courants et de leur effet:
db %>% select(AGE, REGION)
## # A tibble: 599 x 2
## AGE REGION
## <dbl> <chr>
## 1 58 NE
## 2 40 W
## 3 29 S
## 4 59 NE
## 5 51 W
## 6 19 <NA>
## 7 64 S
## 8 23 NE
## 9 47 NW
## 10 66 S
## # ... with 589 more rows
db %>% filter(AGE < 20)
## # A tibble: 16 x 11
## AGE SEXE REGION STAT_MARI SAL_HOR SYNDICAT CATEGORIE NIV_ETUDES NB_PERS
## <dbl> <chr> <chr> <chr> <dbl> <chr> <chr> <chr> <dbl>
## 1 19 M <NA> C NA non <NA> <NA> NA
## 2 16 M <NA> C NA <NA> Gestion ~ <NA> NA
## 3 19 F NE <NA> NA <NA> Transpor~ <NA> NA
## 4 18 M S C NA <NA> Transpor~ BAC 6
## 5 17 F NE C 7.9 <NA> Transpor~ Sans dipl~ 5
## 6 19 M NW C 11 <NA> Employe BAC 5
## 7 18 F NE C NA non Professi~ <NA> 6
## 8 18 M NE C 8 non Professi~ BAC 4
## 9 18 F S C 7.25 <NA> Gestion ~ BAC 4
## 10 17 F W C 9 <NA> Transpor~ Sans dipl~ 3
## 11 19 F S C 3.05 <NA> Transpor~ BAC 4
## 12 19 F NE C 8 <NA> Transpor~ Sans dipl~ 3
## 13 19 F <NA> <NA> NA <NA> Gestion ~ <NA> 4
## 14 19 M S C NA <NA> Transpor~ BAC 6
## 15 19 M NW M 9 <NA> Producti~ BAC 4
## 16 16 M NE C 8 <NA> Producti~ Sans dipl~ 4
## # ... with 2 more variables: NB_ENF <dbl>, REV_FOYER <chr>
db %>% mutate(X1 = 1:599, .before = AGE)
## # A tibble: 599 x 12
## X1 AGE SEXE REGION STAT_MARI SAL_HOR SYNDICAT CATEGORIE NIV_ETUDES
## <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr> <chr>
## 1 1 58 F NE C 13.2 non Employe BAC+3
## 2 2 40 M W M 12.5 <NA> Construc~ Sans dipl~
## 3 3 29 M S C 14 <NA> Employe BAC+2
## 4 4 59 M NE D 10.6 <NA> Transpor~ BAC
## 5 5 51 M W M 13 <NA> Transpor~ Sans dipl~
## 6 6 19 M <NA> C NA non <NA> <NA>
## 7 7 64 F S M 19.6 <NA> Producti~ BAC
## 8 8 23 F NE C 13 <NA> Professi~ BAC+3
## 9 9 47 M NW M 20.1 oui Construc~ BAC
## 10 10 66 F S D 12.5 non Employe BAC
## # ... with 589 more rows, and 3 more variables: NB_PERS <dbl>, NB_ENF <dbl>,
## # REV_FOYER <chr>
db %>% summarise(Mean_age = mean(AGE))
## # A tibble: 1 x 1
## Mean_age
## <dbl>
## 1 41.8
db %>% arrange(AGE)
## # A tibble: 599 x 11
## AGE SEXE REGION STAT_MARI SAL_HOR SYNDICAT CATEGORIE NIV_ETUDES NB_PERS
## <dbl> <chr> <chr> <chr> <dbl> <chr> <chr> <chr> <dbl>
## 1 16 M <NA> C NA <NA> Gestion ~ <NA> NA
## 2 16 M NE C 8 <NA> Producti~ Sans dipl~ 4
## 3 17 F NE C 7.9 <NA> Transpor~ Sans dipl~ 5
## 4 17 F W C 9 <NA> Transpor~ Sans dipl~ 3
## 5 18 M S C NA <NA> Transpor~ BAC 6
## 6 18 F NE C NA non Professi~ <NA> 6
## 7 18 M NE C 8 non Professi~ BAC 4
## 8 18 F S C 7.25 <NA> Gestion ~ BAC 4
## 9 19 M <NA> C NA non <NA> <NA> NA
## 10 19 F NE <NA> NA <NA> Transpor~ <NA> NA
## # ... with 589 more rows, and 2 more variables: NB_ENF <dbl>, REV_FOYER <chr>
Finallement, tout l'intérêt vient de la combinaison d'une suite d'instructions à l'aide des différents 'verbes', pour effectuer des tâches complexes. Par exemple, cette commande retourne la moyenne et l'écart type des salaires horaires des personnes de moins de 20 ans, en excluant les valeurs NA:
db %>% filter(AGE < 20) %>%
filter( !is.na(SAL_HOR) ) %>%
summarise(Moyenne = mean(SAL_HOR), Ecart_type = sd(SAL_HOR))
## # A tibble: 1 x 2
## Moyenne Ecart_type
## <dbl> <dbl>
## 1 7.91 2.12
Maintenant que nous avons introduit ces nouveaux outils, utilisons les dans le cadre de l'exercice 2.
db %>% summary
## AGE SEXE REGION STAT_MARI
## Min. :16.00 Length:599 Length:599 Length:599
## 1st Qu.:29.00 Class :character Class :character Class :character
## Median :42.00 Mode :character Mode :character Mode :character
## Mean :41.85
## 3rd Qu.:53.50
## Max. :80.00
##
## SAL_HOR SYNDICAT CATEGORIE NIV_ETUDES
## Min. : 2.0 Length:599 Length:599 Length:599
## 1st Qu.:10.5 Class :character Class :character Class :character
## Median :14.5 Mode :character Mode :character Mode :character
## Mean :17.7
## 3rd Qu.:21.0
## Max. :99.0
## NA's :160
## NB_PERS NB_ENF REV_FOYER
## Min. : 1.000 Min. :0.0000 Length:599
## 1st Qu.: 2.000 1st Qu.:0.0000 Class :character
## Median : 3.000 Median :0.0000 Mode :character
## Mean : 3.119 Mean :0.5443
## 3rd Qu.: 4.000 3rd Qu.:1.0000
## Max. :13.000 Max. :6.0000
## NA's :46 NA's :46
db %>% is.na %>% colMeans %>% mean %>% '*'(100)
## [1] 14.11443
Successivement, nous vérifions si les valeurs du tableau sont manquantes ou non, puis nous calculons la proportion moyenne dans chaque colonne, puis la moyenne des valeurs des colonnes, et enfin nous multiplions le résultat par 100 pour obtenir un pourcentage. N'hésitez pas à executer les commandes morceaux par morceaux pour bien voir l'effet successif de chacune.
var_suppr = db %>% is.na %>% colMeans %>% '*'(100) %>% '>'(70)
db_tidy = db %>% select_if( !var_suppr )
row_suppr = db %>% is.na %>% rowMeans %>% '*'(100) %>% '>'(60)
db = raw_db %>% select( - X1 ) %>%
select_if( !var_suppr) %>%
filter( !row_suppr )
db
## # A tibble: 570 x 10
## AGE SEXE REGION STAT_MARI SAL_HOR CATEGORIE NIV_ETUDES NB_PERS NB_ENF
## <dbl> <chr> <chr> <chr> <dbl> <chr> <chr> <dbl> <dbl>
## 1 58 F NE C 13.2 Employe BAC+3 2 0
## 2 40 M W M 12.5 Construc~ Sans dipl~ 2 0
## 3 29 M S C 14 Employe BAC+2 2 0
## 4 59 M NE D 10.6 Transpor~ BAC 4 1
## 5 51 M W M 13 Transpor~ Sans dipl~ 8 1
## 6 64 F S M 19.6 Producti~ BAC 3 0
## 7 23 F NE C 13 Professi~ BAC+3 2 0
## 8 47 M NW M 20.1 Construc~ BAC 3 0
## 9 66 F S D 12.5 Employe BAC 1 0
## 10 26 M NE M 9.96 Gestion ~ BAC+2 3 0
## # ... with 560 more rows, and 1 more variable: REV_FOYER <chr>
quali = db %>% select_if(is.character)
quali
## # A tibble: 570 x 6
## SEXE REGION STAT_MARI CATEGORIE NIV_ETUDES REV_FOYER
## <chr> <chr> <chr> <chr> <chr> <chr>
## 1 F NE C Employe BAC+3 40 - < 60
## 2 M W M Construction et Maintenance Sans diplome < 40
## 3 M S C Employe BAC+2 >= 100
## 4 M NE D Transports et Service BAC < 40
## 5 M W M Transports et Service Sans diplome >= 100
## 6 F S M Production et Agriculture BAC 60 - < 100
## 7 F NE C Profession liberale BAC+3 40 - < 60
## 8 M NW M Construction et Maintenance BAC 40 - < 60
## 9 F S D Employe BAC < 40
## 10 M NE M Gestion et Vente BAC+2 < 40
## # ... with 560 more rows
quanti = db %>% select_if(is.double)
quanti
## # A tibble: 570 x 4
## AGE SAL_HOR NB_PERS NB_ENF
## <dbl> <dbl> <dbl> <dbl>
## 1 58 13.2 2 0
## 2 40 12.5 2 0
## 3 29 14 2 0
## 4 59 10.6 4 1
## 5 51 13 8 1
## 6 64 19.6 3 0
## 7 23 13 2 0
## 8 47 20.1 3 0
## 9 66 12.5 1 0
## 10 26 9.96 3 0
## # ... with 560 more rows
quanti_imput = quanti %>% mutate(AGE = if_else(is.na(AGE), mean(AGE, na.rm=TRUE), AGE),
SAL_HOR = if_else(is.na(SAL_HOR), mean(SAL_HOR, na.rm=TRUE), SAL_HOR),
NB_PERS = if_else(is.na(NB_PERS), median(NB_PERS, na.rm=TRUE), NB_PERS),
NB_ENF = if_else(is.na(NB_ENF), median(NB_ENF, na.rm=TRUE), NB_ENF))
quanti_imput %>% is.na %>% sum
## [1] 0
Et on vérifie qu'il n'y a en effet plus de valeurs manquantes.
gg1 = ggplot(quali) + geom_bar(aes(x = SEXE, y = ..prop.., group = 1))
gg2 = ggplot(quali) + geom_bar(aes(x = REGION, y = ..prop.., group = 1))
gg3 = ggplot(quali) + geom_bar(aes(x = STAT_MARI, y = ..prop.., group = 1))
gg4 = ggplot(quali) + geom_bar(aes(x = CATEGORIE, y = ..prop.., group = 1), position="dodge") + coord_flip()
gg5 = ggplot(quali) + geom_bar(aes(x = NIV_ETUDES, y = ..prop.., group = 1), position="dodge") + coord_flip()
gg6 = ggplot(quali) + geom_bar(aes(x = REV_FOYER, y = ..prop.., group = 1))
grid.arrange(gg1, gg2, gg3, gg6, gg4, gg5, ncol = 2, nrow = 3)
La fonction grid.arrange(), issue du package gridExtra, permet d'afficher simplement plusieurs plots dans un même cadre structuré.
quanti_imput %>% summary
## AGE SAL_HOR NB_PERS NB_ENF
## Min. :16.00 Min. : 2.00 Min. : 1.000 Min. :0.0000
## 1st Qu.:29.00 1st Qu.:12.00 1st Qu.: 2.000 1st Qu.:0.0000
## Median :42.00 Median :17.73 Median : 3.000 Median :0.0000
## Mean :41.66 Mean :17.73 Mean : 3.116 Mean :0.5281
## 3rd Qu.:53.00 3rd Qu.:18.50 3rd Qu.: 4.000 3rd Qu.:1.0000
## Max. :80.00 Max. :99.00 Max. :13.000 Max. :6.0000
quanti_imput %>% pull(AGE) %>% var
## [1] 196.9684
quanti_imput %>% pull(AGE) %>% scale(center = T, scale = F) %>% '^'(2) %>% mean
## [1] 196.6228
gg7 = ggplot(quanti_imput) + geom_boxplot(aes(y = AGE))
gg8 = ggplot(quanti_imput) + geom_boxplot(aes(y = SAL_HOR))
grid.arrange(gg7, gg8, ncol = 2)
Comme souvent, là où la distribution des âges et relativement homogène, celle des salaires présente de nombreuses valeurs extremes, appelés aussi outliers. On peut égallement présenter la distribution empirique complète à l'aide d'un histogramme:
gg9 = ggplot(quanti_imput) + geom_histogram(aes(x = AGE))
gg10 = ggplot(quanti_imput) + geom_histogram(aes(x = SAL_HOR))
grid.arrange(gg9, gg10, ncol = 2)
gg11 = ggplot(quanti_imput) + geom_density(aes(x = AGE)) + theme_classic() + xlab('Age') +
ylab('Densité de probabilité')
gg12 = ggplot(quanti_imput) + geom_density(aes(x = SAL_HOR)) + theme_classic() + xlab('Salire horaire') +
ylab('Densité de probabilité')
grid.arrange(gg11, gg12, ncol = 2)
db_imput = quanti_imput %>% bind_cols(quali)
gg13 = ggplot(db_imput) + geom_boxplot(aes(x = SEXE , y = AGE)) + ggtitle('Age selon Sexe')
gg14 = ggplot(db_imput) + geom_boxplot(aes(x = NIV_ETUDES , y = AGE, fill = NIV_ETUDES)) + ggtitle('Age selon Etudes')
gg15 = ggplot(db_imput) + geom_boxplot(aes(x = CATEGORIE , y = AGE, fill = CATEGORIE)) + ggtitle('Age selon Catégorie')
gg16 = ggplot(db_imput) + geom_boxplot(aes(x = REV_FOYER , y = AGE)) + ggtitle('Age selon Revenus')
grid.arrange(gg13, gg14, gg15, gg16, ncol = 2, nrow = 2)
Pour ceux qui veulent utiliser d'autres effets visuels pour affichées des informations supplémentaires, ggplot2 propose de nombreuses modifications du concept de boxplot à cet effet. Par exemple, si l'on veut étudier simultanement 3 variables, il est possible d'utiliser les 2 axes ainsi que les couleurs comme élements dedistinctions:
ggplot(db_imput) + geom_boxplot(aes(x = NIV_ETUDES , y = SAL_HOR, fill = SEXE))
Ce graph nous permet d'analyser en un coup d'oeil l'effet du niveau d'étude sur le salaire horaire, ainsi que les inégalités hommes/femmes (malheureusement toujours évidentes sur ce graph).
D'un point de vu plus esthétique, il est également possible d'utiliser la commande geom_violin(), pour afficher un violin plot qui a l'avantage de représenter la distribution des données plus finement tout en restant simple visuellement:
ggplot(db_imput) + geom_violin(aes(x = NIV_ETUDES , y = SAL_HOR, fill = NIV_ETUDES))
Pour finir, lorsque l'on étudie des variables quantitatives continues, il est généralement utile de visualiser directement la totalité du nuage de point avec geom_points():
ggplot(db_imput) + geom_point(aes(x = AGE , y = SAL_HOR))
On voit qu'étonnament l'âge semble influer peu sur le salaire horaire, mais encore une fois, il est possible de faire une distinction par couleur selon une troisième variable qui a son importance: le niveau d'étude.
ggplot(db_imput) + geom_point(aes(x = AGE , y = SAL_HOR, color = NIV_ETUDES))
En faisant cette distinction, il est plus clair que le salaire des dipômés d'un bac+2 ou moins évolue en fait très peu avec l'âge, alors que pour les bac+3, +5 et +8 les augmentations de salaire sont plus fréquentes au cours du temps.