ANALYSE EXPLORATOIRE - DONNEES MANQUANTES ET IMPUTATION
Lea Lê Dinh & Camille Prince : ESIEE Paris 2022-2023
Nous disposons d’un jeu de données de n = 70 000. Pour chaque cas, on a 12 variables mesurées :
La premiere chose à faire avant tout c’est de supprimer tout grace à :
rm(list = ls())
Une fois que c’est fait, nous allons utiliser ici differentes librairies telles que :
#-> Chargement des paquets
library(ggplot2) # graphiques
library(kableExtra) # tableau
library(readr)
library(corrplot)
## corrplot 0.92 loaded
LoadPack <- function(Packrequired)
{
for(i in 1:length(Packrequired))
{
if(require(Packrequired[i], character.only = TRUE) == FALSE)
{install.packages(Packrequired[i])}
library(Packrequired[i],character.only = TRUE)
}
}
# les paquets
mypack <- c('VIM','mice', 'kableExtra','ggplot2', 'dplyr')
LoadPack(mypack)
## Le chargement a nécessité le package : VIM
## Le chargement a nécessité le package : colorspace
## Le chargement a nécessité le package : grid
## VIM is ready to use.
## Suggestions and bug-reports can be submitted at: https://github.com/statistikat/VIM/issues
##
## Attachement du package : 'VIM'
## L'objet suivant est masqué depuis 'package:datasets':
##
## sleep
## Le chargement a nécessité le package : mice
##
## Attachement du package : 'mice'
## L'objet suivant est masqué depuis 'package:stats':
##
## filter
## Les objets suivants sont masqués depuis 'package:base':
##
## cbind, rbind
## Le chargement a nécessité le package : dplyr
##
## Attachement du package : 'dplyr'
## L'objet suivant est masqué depuis 'package:kableExtra':
##
## group_rows
## Les objets suivants sont masqués depuis 'package:stats':
##
## filter, lag
## Les objets suivants sont masqués depuis 'package:base':
##
## intersect, setdiff, setequal, union
On charge ensuite notre jeu de données et l’affichons :
load(file="TP_MISS_2022.Rda")
head(dfw,25) %>% kbl(digits=3) %>% kable_styling(bootstrap_options = "striped", full_width = F, position = "center", latex_options = 'stripped') %>% scroll_box(width = "1000px", height = "400px")
| id | age | gender | height | ap_hi | ap_lo | cholesterol | gluc | smoke | alco | active | cardio |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 18393 | 2 | 168 | 110 | 80 | 1 | 1 | 0 | 0 | 1 | 0 |
| 1 | 20228 | 1 | 156 | 140 | 90 | 3 | 1 | 0 | 0 | 1 | 1 |
| 2 | 18857 | 1 | 165 | 130 | 70 | 3 | 1 | 0 | 0 | 0 | 1 |
| 3 | 17623 | 2 | 169 | 150 | 100 | 1 | 1 | 0 | 0 | 1 | 1 |
| 4 | 17474 | 1 | 156 | 100 | 60 | 1 | 1 | 0 | 0 | 0 | 0 |
| 8 | 21914 | 1 | 151 | 120 | 80 | 2 | 2 | 0 | 0 | 0 | 0 |
| 9 | 22113 | 1 | 157 | 130 | NA | 3 | 1 | 0 | 0 | 1 | 0 |
| 12 | 22584 | 2 | 178 | 130 | NA | 3 | 3 | 0 | 0 | 1 | 1 |
| 13 | 17668 | 1 | 158 | 110 | NA | 1 | 1 | 0 | 0 | 1 | 0 |
| 14 | 19834 | 1 | 164 | 110 | 60 | 1 | 1 | 0 | 0 | 0 | 0 |
| 15 | 22530 | 1 | 169 | 120 | 80 | 1 | 1 | 0 | 0 | 1 | 0 |
| 16 | 18815 | 2 | 173 | 120 | 80 | 1 | 1 | 0 | 0 | 1 | 0 |
| 18 | 14791 | 2 | 165 | 120 | 80 | 1 | 1 | 0 | 0 | 0 | 0 |
| 21 | 19809 | 1 | 158 | 110 | 70 | 1 | 1 | 0 | 0 | 1 | 0 |
| 23 | 14532 | 2 | 181 | 130 | NA | 1 | NA | 1 | 1 | 1 | 0 |
| 24 | 16782 | 2 | 172 | 120 | 80 | 1 | 1 | 0 | 0 | 0 | 1 |
| 25 | 21296 | 1 | 170 | 130 | 70 | 1 | 1 | 0 | 0 | 0 | 0 |
| 27 | 16747 | 1 | 158 | 110 | 70 | 1 | 3 | 0 | 0 | 1 | 0 |
| 28 | 17482 | 1 | 154 | 100 | NA | NA | 1 | 0 | 0 | 0 | 0 |
| 29 | 21755 | 2 | 162 | 120 | 70 | 1 | 1 | 1 | 0 | 1 | 0 |
| 30 | 19778 | 2 | 163 | 120 | 80 | 1 | 1 | 0 | 0 | 1 | 0 |
| 31 | 21413 | 1 | 157 | 130 | 80 | 1 | 1 | 0 | 0 | 1 | 0 |
| 32 | 23046 | 1 | 158 | 145 | 85 | 2 | 2 | 0 | 0 | 1 | 1 |
| 33 | 23376 | 2 | 156 | 110 | 60 | 1 | 1 | 0 | 0 | 1 | 0 |
| 35 | 16608 | 1 | 170 | 150 | 90 | 3 | 1 | 0 | 0 | 1 | 1 |
Pour anayser correctement notre jeu de données, il est indispensable de faire une statistique descriptive afin de :
On peut en premier regarder de quoi il s’agit pour avoir une estimation :
summary(dfw) # permet de visualiser des statistiques de base pour chaque variable
## id age gender height
## Min. : 0 Min. :10798 Min. :1.00 Min. : 55.0
## 1st Qu.:25007 1st Qu.:17664 1st Qu.:1.00 1st Qu.:159.0
## Median :50002 Median :19703 Median :1.00 Median :165.0
## Mean :49972 Mean :19469 Mean :1.35 Mean :164.4
## 3rd Qu.:74889 3rd Qu.:21327 3rd Qu.:2.00 3rd Qu.:170.0
## Max. :99999 Max. :23713 Max. :2.00 Max. :250.0
##
## ap_hi ap_lo cholesterol gluc
## Min. : -150.0 Min. : -70.00 Min. :1.000 Min. :1.000
## 1st Qu.: 120.0 1st Qu.: 80.00 1st Qu.:1.000 1st Qu.:1.000
## Median : 120.0 Median : 80.00 Median :1.000 Median :1.000
## Mean : 128.8 Mean : 96.52 Mean :1.367 Mean :1.227
## 3rd Qu.: 140.0 3rd Qu.: 90.00 3rd Qu.:2.000 3rd Qu.:1.000
## Max. :16020.0 Max. :11000.00 Max. :3.000 Max. :3.000
## NA's :5014 NA's :4561 NA's :5398
## smoke alco active cardio
## Min. :0.00000 Min. :0.00000 Min. :0.0000 Min. :0.0000
## 1st Qu.:0.00000 1st Qu.:0.00000 1st Qu.:1.0000 1st Qu.:0.0000
## Median :0.00000 Median :0.00000 Median :1.0000 Median :0.0000
## Mean :0.08813 Mean :0.05377 Mean :0.8037 Mean :0.4997
## 3rd Qu.:0.00000 3rd Qu.:0.00000 3rd Qu.:1.0000 3rd Qu.:1.0000
## Max. :1.00000 Max. :1.00000 Max. :1.0000 Max. :1.0000
##
On peut notamment voir le nombre de données manquantes dites “NA”, sur lesquelles on reviendra. Cette fonction nous permet ausside voir le minimum et le maximum de chaque variable ce qui nous ammène à penser qu’il y a des données aberrantes dans les variables ap_hi et ap_lo au vu des résultats obtenus.
Avant de rentrer complétement dans la statistique descriptive, on formate notre jeu de données pour le rendre le plus lisible possible. Pour cela on effectue :
gender <- factor(dfw$gender , levels = c(1,2), labels = c('F','H'))
# la fonction factor retoune un vecteur dont les données sont catégorielles
YN_cat <- c('smoke', 'alco', 'active', 'cardio')
OuiNon <- sapply(YN_cat, function(x){dfw[[x]] <- factor(dfw[[x]],levels = c(0,1), labels = c('Non','Oui'))})
dfw <- data.frame(dfw[,c(1,2,4,5,6,7,8)], gender, OuiNon)
# on ajoute a notre dataframe les modifications
dfw$age <- dfw$age%/%365 # age en années
head(dfw,25) %>% kbl(digits=3) %>% kable_styling(bootstrap_options = "striped", full_width = F, position = "center", latex_options = 'stripped') %>% scroll_box(width = "1000px", height = "400px")
| id | age | height | ap_hi | ap_lo | cholesterol | gluc | gender | smoke | alco | active | cardio |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 50 | 168 | 110 | 80 | 1 | 1 | H | Non | Non | Oui | Non |
| 1 | 55 | 156 | 140 | 90 | 3 | 1 | F | Non | Non | Oui | Oui |
| 2 | 51 | 165 | 130 | 70 | 3 | 1 | F | Non | Non | Non | Oui |
| 3 | 48 | 169 | 150 | 100 | 1 | 1 | H | Non | Non | Oui | Oui |
| 4 | 47 | 156 | 100 | 60 | 1 | 1 | F | Non | Non | Non | Non |
| 8 | 60 | 151 | 120 | 80 | 2 | 2 | F | Non | Non | Non | Non |
| 9 | 60 | 157 | 130 | NA | 3 | 1 | F | Non | Non | Oui | Non |
| 12 | 61 | 178 | 130 | NA | 3 | 3 | H | Non | Non | Oui | Oui |
| 13 | 48 | 158 | 110 | NA | 1 | 1 | F | Non | Non | Oui | Non |
| 14 | 54 | 164 | 110 | 60 | 1 | 1 | F | Non | Non | Non | Non |
| 15 | 61 | 169 | 120 | 80 | 1 | 1 | F | Non | Non | Oui | Non |
| 16 | 51 | 173 | 120 | 80 | 1 | 1 | H | Non | Non | Oui | Non |
| 18 | 40 | 165 | 120 | 80 | 1 | 1 | H | Non | Non | Non | Non |
| 21 | 54 | 158 | 110 | 70 | 1 | 1 | F | Non | Non | Oui | Non |
| 23 | 39 | 181 | 130 | NA | 1 | NA | H | Oui | Oui | Oui | Non |
| 24 | 45 | 172 | 120 | 80 | 1 | 1 | H | Non | Non | Non | Oui |
| 25 | 58 | 170 | 130 | 70 | 1 | 1 | F | Non | Non | Non | Non |
| 27 | 45 | 158 | 110 | 70 | 1 | 3 | F | Non | Non | Oui | Non |
| 28 | 47 | 154 | 100 | NA | NA | 1 | F | Non | Non | Non | Non |
| 29 | 59 | 162 | 120 | 70 | 1 | 1 | H | Oui | Non | Oui | Non |
| 30 | 54 | 163 | 120 | 80 | 1 | 1 | H | Non | Non | Oui | Non |
| 31 | 58 | 157 | 130 | 80 | 1 | 1 | F | Non | Non | Oui | Non |
| 32 | 63 | 158 | 145 | 85 | 2 | 2 | F | Non | Non | Oui | Oui |
| 33 | 64 | 156 | 110 | 60 | 1 | 1 | H | Non | Non | Oui | Non |
| 35 | 45 | 170 | 150 | 90 | 3 | 1 | F | Non | Non | Oui | Oui |
On a maintenant les âges des patients en années, le sexe en “F” ou “H” et toutes les variables binaires en “Oui” ou “Non” pour plus de lisibilité.
On cherche à savoir s’il existe des valeurs aberrantes dans nos variables. Pour cela, on utilise la fonction ggplot2 pour pouvoir visualiser des boxplot. Ici, on étudie les variables ap_lo et ap_hi :
gr1 <- ggplot(dfw) + geom_boxplot(aes(y = ap_hi), fill = '#0066CC', colour = 'navy' ) + xlab('') + theme( axis.text.x = element_blank())
gr1
gr2 <- ggplot(dfw) + geom_boxplot(aes(y = ap_lo), fill = '#0066CC', colour = 'navy' ) + xlab('') + theme( axis.text.x = element_blank())
gr2
## Warning: Removed 5014 rows containing non-finite values (stat_boxplot).
On visualise donc bien qu’il y a certaines données qui sont aberrantes
et qui donc vont devoir être supprimées. En cherchant plus précisément,
la pression artérielle systolique doit être comprise entre 100 et 190 et
la pression artérielle diastolique entre 60 et 120. Pour cela, on fait
:
dfw$ap_lo <- ifelse (dfw$ap_lo < 60, NA, dfw$ap_lo)
dfw$ap_lo <- ifelse (dfw$ap_lo > 140, NA, dfw$ap_lo)
dfw$ap_hi <- ifelse (dfw$ap_hi < 100, NA, dfw$ap_hi)
dfw$ap_hi <- ifelse (dfw$ap_hi > 190, NA, dfw$ap_hi)
Maintenant que nos données aberrantes ont été remplacées par des valeurs manquantes, on pourra les imputer par la suite. On réutilise les boxplots pour bien visualiser ce qu’on vient de faire et ils paraissent nettement plus cohérents.
gr1 <- ggplot(dfw) + geom_boxplot(aes(y = ap_hi), fill = '#0066CC', colour = 'navy' ) + xlab('') + theme( axis.text.x = element_blank())
gr1
## Warning: Removed 1570 rows containing non-finite values (stat_boxplot).
gr2 <- ggplot(dfw) + geom_boxplot(aes(y = ap_lo), fill = '#0066CC', colour = 'navy' ) + xlab('') + theme( axis.text.x = element_blank())
gr2
## Warning: Removed 6076 rows containing non-finite values (stat_boxplot).
## 4. Visualisation des données manquantes
On utilise ici le graphique d’aggrégation qui nous permet de visualiser la proportion de données manquantes par variable.
aggr(dfw) # visualisation de la proportion de données manquantes
On voit donc bien que les données manquantes sont concentrées dans les colonnes ap_lo, ap_hi, cholesterol et gluc.
On utilise a présent, le marginplot pour interpréter davantage nos données manquantes.
marginplot(dfw[,c('ap_hi','ap_lo')])
Le boxplot bleu résume la distribution des ap_lo observées. Le boxplot
rouge résume la distibution des ap_lo lorsque l’on ôte les valeurs
manquantes de ap_hi. OOn en conclut donc que les données manquantes de
ap_hi sont indépendantes de ap_lo puisque les boxplot sont analogues (ce
qui reflète la même distribution) et donc une typologie MCAR.
pbox(dfw[,c('ap_lo','ap_hi')])
## Warning in createPlot(main, sub, xlab, ylab, labels, ca$at): not enough space to
## display frequencies
##
## Click in in the left margin to switch to the previous variable or in the right margin to switch to the next variable.
## To regain use of the VIM GUI and the R console, click anywhere else in the graphics window.
Ici, grâce à pbox, on peut visualiser dans le boxplot blanc la distributionde toutes les valeurs observées de ap_lo, dans le boxplot bleu la distribution des valeurs observées de ap_lo en fonction des valeurs observées de ap_hi et dans le boxplot rouge la distribution des valeurs observées de ap_lo en fonction des valeurs manquantes de ap_hi.
matrixplot(dfw) # localisation des données manquantes
##
## Click in a column to sort by the corresponding variable.
## To regain use of the VIM GUI and the R console, click outside the plot region.
Grâce à cette fonction, on voit donc bien la répartition des données manquantes.
On en conclut donc grâce à ces graphes que les données manquantes sont MCAR (donc manquantes de façon strictement aléatoires)
Maintenant que nous savons que les données manquantes sont MCAR, on va utiliser la méthode Cold-Deck. Cette méthode consiste à remplacer une donnée manquante par une valeur observée chez un individu ayant les mêmes caractéristiques mais provenant d’une source d’information différente.
On choisit ici d’utiliser la fonction du k plus proche voisin KNN. Cette fonction nous retourne un dataframe en deux parties : la première partie correspond aux jeu de données complet avec imputation et la deuxième partie correspond aux valeurs imputées sous forme booléenne.
imput_knn <- kNN(data = dfw, variable = c('gluc','cholesterol','ap_lo','ap_hi'), k = 3)
head(imput_knn,25) %>% kbl(digits=3) %>% kable_styling(bootstrap_options = "striped", full_width = F, position = "center", latex_options = 'stripped') %>% scroll_box(width = "1000px", height = "400px")
| id | age | height | ap_hi | ap_lo | cholesterol | gluc | gender | smoke | alco | active | cardio | gluc_imp | cholesterol_imp | ap_lo_imp | ap_hi_imp |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 50 | 168 | 110 | 80 | 1 | 1 | H | Non | Non | Oui | Non | FALSE | FALSE | FALSE | FALSE |
| 1 | 55 | 156 | 140 | 90 | 3 | 1 | F | Non | Non | Oui | Oui | FALSE | FALSE | FALSE | FALSE |
| 2 | 51 | 165 | 130 | 70 | 3 | 1 | F | Non | Non | Non | Oui | FALSE | FALSE | FALSE | FALSE |
| 3 | 48 | 169 | 150 | 100 | 1 | 1 | H | Non | Non | Oui | Oui | FALSE | FALSE | FALSE | FALSE |
| 4 | 47 | 156 | 100 | 60 | 1 | 1 | F | Non | Non | Non | Non | FALSE | FALSE | FALSE | FALSE |
| 8 | 60 | 151 | 120 | 80 | 2 | 2 | F | Non | Non | Non | Non | FALSE | FALSE | FALSE | FALSE |
| 9 | 60 | 157 | 130 | 80 | 3 | 1 | F | Non | Non | Oui | Non | FALSE | FALSE | TRUE | FALSE |
| 12 | 61 | 178 | 130 | 90 | 3 | 3 | H | Non | Non | Oui | Oui | FALSE | FALSE | TRUE | FALSE |
| 13 | 48 | 158 | 110 | 70 | 1 | 1 | F | Non | Non | Oui | Non | FALSE | FALSE | TRUE | FALSE |
| 14 | 54 | 164 | 110 | 60 | 1 | 1 | F | Non | Non | Non | Non | FALSE | FALSE | FALSE | FALSE |
| 15 | 61 | 169 | 120 | 80 | 1 | 1 | F | Non | Non | Oui | Non | FALSE | FALSE | FALSE | FALSE |
| 16 | 51 | 173 | 120 | 80 | 1 | 1 | H | Non | Non | Oui | Non | FALSE | FALSE | FALSE | FALSE |
| 18 | 40 | 165 | 120 | 80 | 1 | 1 | H | Non | Non | Non | Non | FALSE | FALSE | FALSE | FALSE |
| 21 | 54 | 158 | 110 | 70 | 1 | 1 | F | Non | Non | Oui | Non | FALSE | FALSE | FALSE | FALSE |
| 23 | 39 | 181 | 130 | 80 | 1 | 1 | H | Oui | Oui | Oui | Non | TRUE | FALSE | TRUE | FALSE |
| 24 | 45 | 172 | 120 | 80 | 1 | 1 | H | Non | Non | Non | Oui | FALSE | FALSE | FALSE | FALSE |
| 25 | 58 | 170 | 130 | 70 | 1 | 1 | F | Non | Non | Non | Non | FALSE | FALSE | FALSE | FALSE |
| 27 | 45 | 158 | 110 | 70 | 1 | 3 | F | Non | Non | Oui | Non | FALSE | FALSE | FALSE | FALSE |
| 28 | 47 | 154 | 100 | 60 | 1 | 1 | F | Non | Non | Non | Non | FALSE | TRUE | TRUE | FALSE |
| 29 | 59 | 162 | 120 | 70 | 1 | 1 | H | Oui | Non | Oui | Non | FALSE | FALSE | FALSE | FALSE |
| 30 | 54 | 163 | 120 | 80 | 1 | 1 | H | Non | Non | Oui | Non | FALSE | FALSE | FALSE | FALSE |
| 31 | 58 | 157 | 130 | 80 | 1 | 1 | F | Non | Non | Oui | Non | FALSE | FALSE | FALSE | FALSE |
| 32 | 63 | 158 | 145 | 85 | 2 | 2 | F | Non | Non | Oui | Oui | FALSE | FALSE | FALSE | FALSE |
| 33 | 64 | 156 | 110 | 60 | 1 | 1 | H | Non | Non | Oui | Non | FALSE | FALSE | FALSE | FALSE |
| 35 | 45 | 170 | 150 | 90 | 3 | 1 | F | Non | Non | Oui | Oui | FALSE | FALSE | FALSE | FALSE |
Pour plus de simplicité à ce stade, on décide de scindé en deux notre dataframe. Notre premier jeu de données sera les données quantitatives et le deuxième, les données qualitatives.
Après vérification, les variables cholesterol et glucose sont des variables qualitatives ordinales (Normal = N, Supérieur à la normale = N+, Bien supérieur à la normale = N++). On décide donc de les passer en facteurs.
glucose <- factor(imput_knn$gluc , levels = c(1,2,3), labels = c('N','N+','N++'), ordered = TRUE)
chol <- factor(imput_knn$cholesterol , levels = c(1,2,3), labels = c('N','N+','N++'), ordered = TRUE)
imput_knn <- data.frame(imput_knn[,c(1,2,3,4,5,8,9,10,11,12)], chol, glucose)
# pour les variables numériques
id_num <- which(sapply(imput_knn, is.numeric))
dfw_num <- imput_knn[, id_num]
head(dfw_num,25) %>% kbl(digits=3) %>% kable_styling(bootstrap_options = "striped", full_width = F, position = "center", latex_options = 'stripped') %>% scroll_box(width = "1000px", height = "400px")
| id | age | height | ap_hi | ap_lo |
|---|---|---|---|---|
| 0 | 50 | 168 | 110 | 80 |
| 1 | 55 | 156 | 140 | 90 |
| 2 | 51 | 165 | 130 | 70 |
| 3 | 48 | 169 | 150 | 100 |
| 4 | 47 | 156 | 100 | 60 |
| 8 | 60 | 151 | 120 | 80 |
| 9 | 60 | 157 | 130 | 80 |
| 12 | 61 | 178 | 130 | 90 |
| 13 | 48 | 158 | 110 | 70 |
| 14 | 54 | 164 | 110 | 60 |
| 15 | 61 | 169 | 120 | 80 |
| 16 | 51 | 173 | 120 | 80 |
| 18 | 40 | 165 | 120 | 80 |
| 21 | 54 | 158 | 110 | 70 |
| 23 | 39 | 181 | 130 | 80 |
| 24 | 45 | 172 | 120 | 80 |
| 25 | 58 | 170 | 130 | 70 |
| 27 | 45 | 158 | 110 | 70 |
| 28 | 47 | 154 | 100 | 60 |
| 29 | 59 | 162 | 120 | 70 |
| 30 | 54 | 163 | 120 | 80 |
| 31 | 58 | 157 | 130 | 80 |
| 32 | 63 | 158 | 145 | 85 |
| 33 | 64 | 156 | 110 | 60 |
| 35 | 45 | 170 | 150 | 90 |
# pour les variables catégorielles
id_cat <- which(!sapply(imput_knn, is.numeric))
dfw_cat <- imput_knn[, id_cat]
head(dfw_cat,25) %>% kbl(digits=3) %>% kable_styling(bootstrap_options = "striped", full_width = F, position = "center", latex_options = 'stripped') %>% scroll_box(width = "1000px", height = "400px")
| gender | smoke | alco | active | cardio | chol | glucose |
|---|---|---|---|---|---|---|
| H | Non | Non | Oui | Non | N | N |
| F | Non | Non | Oui | Oui | N++ | N |
| F | Non | Non | Non | Oui | N++ | N |
| H | Non | Non | Oui | Oui | N | N |
| F | Non | Non | Non | Non | N | N |
| F | Non | Non | Non | Non | N+ | N+ |
| F | Non | Non | Oui | Non | N++ | N |
| H | Non | Non | Oui | Oui | N++ | N++ |
| F | Non | Non | Oui | Non | N | N |
| F | Non | Non | Non | Non | N | N |
| F | Non | Non | Oui | Non | N | N |
| H | Non | Non | Oui | Non | N | N |
| H | Non | Non | Non | Non | N | N |
| F | Non | Non | Oui | Non | N | N |
| H | Oui | Oui | Oui | Non | N | N |
| H | Non | Non | Non | Oui | N | N |
| F | Non | Non | Non | Non | N | N |
| F | Non | Non | Oui | Non | N | N++ |
| F | Non | Non | Non | Non | N | N |
| H | Oui | Non | Oui | Non | N | N |
| H | Non | Non | Oui | Non | N | N |
| F | Non | Non | Oui | Non | N | N |
| F | Non | Non | Oui | Oui | N+ | N+ |
| H | Non | Non | Oui | Non | N | N |
| F | Non | Non | Oui | Oui | N++ | N |
On cherche donc maintenant à s’intéresser aux valeurs de notre jeu de données.
PROC_stat_desc <- function(df)
{
v <- sapply(dfw_num, function(x){is.numeric(x)})
if(all(v != T)) {stop('donnnées non numériques détectées')}
#--> calcul et mise en forme directe
ret <- data.frame( "moy_arith" = sapply(dfw_num[,c(2,3,4,5)], mean) ,
"moy_har" = sapply(dfw_num[,c(2,3,4,5)], function(x){ length(x) /sum(1/x)}) ,
"moy_geo" = sapply(dfw_num[,c(2,3,4,5)],function(x){exp(mean(log(x)))} ) ,
"mediane" = sapply(dfw_num[,c(2,3,4,5)], median) ,
"variance" = sapply(dfw_num[,c(2,3,4,5)], var) ,
"ecart_type" = sapply(dfw_num[,c(2,3,4,5)], sd)
)
return(ret)
}
# appel de a fonction
PROC_stat_desc(dfw_num)
## moy_arith moy_har moy_geo mediane variance ecart_type
## age 52.84067 51.92101 52.38945 53 45.78923 6.766774
## height 164.35923 163.91520 164.14535 165 67.40617 8.210126
## ap_hi 127.24946 125.36848 126.28265 120 261.03130 16.156463
## ap_lo 81.53561 80.45171 80.99419 80 89.09869 9.439210
On va s’intéresser en premier aux paramètres de position donc aux moyennes et à la médiane. Pour rappel la médiane partage une série de données d’une variable quantitative en deux groupes d’effectifs égaux. Ici par exemple, on voit que la médiane de l’âge de nos patients est de 53ans ce qui signifie qu’il y a autant de patients moins âgés que plus âgés que 53ans. De plus, on voit bien que la moyenne arithmétique d’âge des patients est d’environ 53ans.
Enfin, on étudie les paramètres de dispersion qui sont l’écart-type et la variance. La variance résume l’ensemble des écarts de chaque valeur d’une distribution par rapport à la moyenne. Si celle-ci est élevée alors la dipersion est importante et à l’inverse, si celle-ci est faible alors la dispersion est petite. Ici, on remarque que la variance de l’âge ou de la taille sont beaucoup plus petites que la variance de la pression artérielle systolique ce qui nous indique donc que l’âge et la taille des patients ne varient pas énormément mais à l’inverse de la pression artérielle systolique connait une grande dispersion.
En ce qui concerne l’écart-type, il mesure aussi l’écart par rapport à la moyenne. Il nous confirme donc bien nos premières estimations en ce qui concerne l’âge et la taille. On obtient un écart-type relativement faible qui indique une faible dispersion. A l’inverse, pour la pression artérielle systolique, l’écart-type est relativement élevé ce qui indique une grande dipersion.
On remarque que pour les pressions artérielles systolique et diastolique leur médiane est de 120 et 80 ce qui représente une valeur normale de pression artérielle ce qui veut dire qu’il y a autant de patients avec des pressions artérielles normale que avec de l’hypertension.
On étudie maintenant la distribution des données quantitatives en intervalles. On choisit ici de scindé notre variable age en quatre classes pour pouvoir étudier l’effectif de notre variable sélectionnée.
age <- dfw_num$age
n_class <- 4
step <- (max(age) - min(age)) / n_class
intervalles <- seq(min(age) - 0.01, max(age) + 0.1, step)
# la variable classif est une variable catégorielle
classif_patient <- cut(age, breaks = intervalles)
# la variable étant catégorielle, on utilise la fonction table
table_patient <- table(classif_patient)
table_patient
## classif_patient
## (29,37.7] (37.7,46.5] (46.5,55.2] (55.2,64]
## 4 13980 28688 25141
On voit donc qu’il y a très peu de patients dans notre jeu de données âgés entre 29 ans et 38 ans. La plus grosse partie des patients enregistrés ont plus de 47 ans, ils représentent 77% des patients de notre base de données.
On calcule ensuite le centre de chacune de nos classes:
mid_step <- step/2
mid_intervalles <- NULL
for (i in 2 : length(intervalles)){mid_intervalles[i-1] <- (intervalles[i] + intervalles[i-1])/2}
mid_intervalles
## [1] 33.365 42.115 50.865 59.615
Une fois que tout est exécuté, on dispose de tous les éléments pour construire le tableau de distribution observée sous forme d’un dataframe.
dist_obs <- data.frame( 'xi' = mid_intervalles, 'ni' = as.numeric(table_patient))
rownames(dist_obs) <- names(table_patient)
head(dist_obs,25) %>% kbl(digits=3) %>% kable_styling(bootstrap_options = "striped", full_width = F, position = "center", latex_options = 'stripped') %>% scroll_box(width = "1000px", height = "400px")
| xi | ni | |
|---|---|---|
| (29,37.7] | 33.365 | 4 |
| (37.7,46.5] | 42.115 | 13980 |
| (46.5,55.2] | 50.865 | 28688 |
| (55.2,64] | 59.615 | 25141 |
On obtient alors sous forme d’un tableau nos valeurs observées. On discrétise nos variables pour les analyser. La première colonne correspond à nos différentes classe avec leurs bornes inférieures et supérieures. La deuxième colonne correspond au centre de la classe que nous avons préalablement calculée et enfin la troisième colonne correspond au nombre d’observations (ici de patients) observées dans l’intervalle de classe.
A partir du tableau de distribution, nous calculons les éléments nécessaires à l’estimation de la moyenne et de la variance.
dist_obs['Ni'] <- cumsum(dist_obs$ni)
dist_obs['fi'] <- dist_obs$ni / sum(dist_obs$ni)
dist_obs['Fi'] <- dist_obs$Ni / sum(dist_obs$ni)
dist_obs['xifi'] <- dist_obs$xi * dist_obs$fi
dist_obs['xi2'] <- dist_obs$xi * dist_obs$xi
dist_obs['xi2fi'] <- dist_obs$xi2 * dist_obs$fi
head(dist_obs,25) %>% kbl(digits=3) %>% kable_styling(bootstrap_options = "striped", full_width = F, position = "center", latex_options = 'stripped') %>% scroll_box(width = "1000px", height = "400px")
| xi | ni | Ni | fi | Fi | xifi | xi2 | xi2fi | |
|---|---|---|---|---|---|---|---|---|
| (29,37.7] | 33.365 | 4 | 4 | 0.000 | 0.000 | 0.002 | 1113.223 | 0.066 |
| (37.7,46.5] | 42.115 | 13980 | 13984 | 0.206 | 0.206 | 8.682 | 1773.673 | 365.652 |
| (46.5,55.2] | 50.865 | 28688 | 42672 | 0.423 | 0.629 | 21.518 | 2587.248 | 1094.524 |
| (55.2,64] | 59.615 | 25141 | 67813 | 0.371 | 1.000 | 22.102 | 3553.948 | 1317.591 |
On observe donc ici: - la colonne Ni : le dénombrement cumulé de chaque classe - la colonne fi : l’estimation en pourcentage d’observations dans la classe - la colonne Fi : l’estimation en pourcentage d’observation cumulées dans la classe - la colonne xifi : la milieu de la classe multiplié par l’estimation en pourcentage d’observations dans la classe - la colonne xi2 : le milieu de la classe au carré - la colonne xi2fi : le milieu de la classe au carré multiplié par l’estimation en pourcentage d’observations dans la classe.
Toutes ces observations sur la distribution vont nous servir à déterminer la variance et la moyenne :
moyenne <- sum(dist_obs$xifi)
moyenne
## [1] 52.30409
variance <- sum(dist_obs$xi2fi)
variance
## [1] 2777.833
gr = ggplot(dfw_num) + geom_histogram(aes(x = age), bins = (n_class -1), fill = '#0066CC', colour = 'navy')
gr
On voit donc bien la distribution en trois intervalles qui nous indique
donc bien que la plus grande distribution est dans l’intervalle des
patients ayant entre 44 et 62ans environ.
On utilise ici la fonction quantiles qui permet d’estimer la distribution en fréquence. Par exemple, pour estimer la distribution en fréquence de l’âge des patients on fait :
int_freq <- seq(0,1,0.25) # on peut ici modifier les intervalles
quant_age <- quantile(dfw_num$age, probs = int_freq)
quant_age
## 0% 25% 50% 75% 100%
## 29 48 53 58 64
gr <- ggplot(dfw_num) + geom_boxplot(aes(y = age), fill = '#0066CC', colour = 'navy' ) + xlab('') + theme( axis.text.x = element_blank())
gr
Grâce au boxplot, on voit bien la distribution en fréquence de notre
varaible age. On voit par exemple bien que à 50% de la distribution les
patients ont environ 53ans.
On regarde la distribution des données qualitatives de notre jeu de données :
lapply(dfw_cat, table)
## $gender
##
## F H
## 45530 24470
##
## $smoke
##
## Non Oui
## 63831 6169
##
## $alco
##
## Non Oui
## 66236 3764
##
## $active
##
## Non Oui
## 13739 56261
##
## $cardio
##
## Non Oui
## 35021 34979
##
## $chol
##
## N N+ N++
## 52619 9488 7893
##
## $glucose
##
## N N+ N++
## 59732 5060 5208
On voit donc bien les différentes distributions. On a dans notre jeu de données : - 65% de femmes enregistrés pour 35% d’hommes, - 91% de non fumeurs contre 9% de fumeurs, - 95% de personnes qui ne boivent pas contre 5% de personnes qui boivent, - 20% de personnes non sportives contre 80% de personnes pratiquant une activité sportive, - 50% de personnes non cardiaques et 50% de personnes cardiaques, - 75% de personnes ont un taux de cholesterol normal, 14% ont un taux de cholesterol supérieur à la normale et 11% ont un taux de cholesterol bien supérieur à la normale, - 85% de personnes ont un taux de glucose normal, 7% ont un taux de glucose supérieur à la normale et 8% ont un taux de glucose bien supérieur à la normale.
On remarque donc une majorité de personnes (hommes ou femmes) ayant une bonne qualité de vie (ne fumant pas, ne buvant pas et pratiquant du sport) contre une petite partie. Néanmoins, on observe que 50% de ces patients ont une maladie cardiovasculaire. Et concernant les taux de glucose et de cholesterol, une grande majorité ont des taux normaux.
On étudie à présent les statistiques bivariées qui ont pour objectif principale de mesurer et de visualiser la dépendance entre deux variables On va procéder en 3 étapes :
On va donc avoir un coefficient de corrélation qui va nous permettre d’estimer la dépendance entre deux variables quantitatives.
round(cor(dfw_num),2)
## id age height ap_hi ap_lo
## id 1 0.00 0.00 0.00 0.00
## age 0 1.00 -0.08 0.21 0.16
## height 0 -0.08 1.00 0.01 0.03
## ap_hi 0 0.21 0.01 1.00 0.72
## ap_lo 0 0.16 0.03 0.72 1.00
corrplot(cor(dfw_num), method = 'ellipse', type = 'upper')
On remarque donc ici que le coefficient de corrélation est relativement
faible pour l’ensemble des variables quantitatives. La seule corrélation
nettement remarquée est la dépendance entre la pression artérielle
systolique et la pression artérielle diastolique ce qui est donc
logique. Le pourcentage du coefficient de corrélation est de 72% entre
ces deux pressions artérielles. On observe aussi un taux de corrélation
légèrement plus élevé entre les pressions artérielles et l’âge. Ce qui
indique donc que plus l’âge est élevé plus il y a de chance d’avoir de
l’hypertension.
gr <- ggplot(data = dfw_num) + geom_point (aes(x = ap_lo, y = ap_hi), size = 1, colour = "#0066CC") +
geom_smooth(method= 'lm', aes(x = ap_lo, y = ap_hi), size = 1, colour = "0666CC")
gr
## `geom_smooth()` using formula 'y ~ x'
On remarque donc bien la dépendance entre les deux pressions
artérielles. En effet, pour une grande majorité on observe que si la
pression systolique est faible alors la pression diastolique le sera
aussi car ces deux dernières sont dépendantes l’une de l’autre. De plus,
en ce qui concerne la droite de régression en rouge, elle nous montre
très clairement la dépendance entre les pressions artérielles.
On s’intéresse ici à la dépendance de deux variables qualitatives. Dans un premier temps, nous voulions étudier l’intégralité des dépendance avec la fonction ‘tab1 <- table(dfw_cat)’ mais cela faisait beaucoup et n’avait plus de sens. Nous avons donc décider de représenter les dépendances les plus intéressantes.
tab1 <- table(dfw_cat$cardio, dfw_cat$glucose)
tab1
##
## N N+ N++
## Non 31022 2060 1939
## Oui 28710 3000 3269
tab2 <- table(dfw_cat$cardio, dfw_cat$chol)
tab2
##
## N N+ N++
## Non 29461 3715 1845
## Oui 23158 5773 6048
Après une étude, les plus intéressantes à étudier sont les maladies cardiovasculaire en fonction du taux de glucose ainsi que du taux de cholesterol. En effet, on voit bien que lorsque le patient a un taux de glucose bien supérieur à la normale, il y a une plus forte probabilité que cette même personne ait une maladie cardiovasculaire. Il en est de même avec le taux de cholesterol. On voit bien que si la personne à un taux de cholesterol bien supérieur à la normale alors il y a plus de chance que celui ci présente une maladie cardiovasculaire.
On utilise la fonction suivante pour estimer la probabilité jointe. Cette fréquence relative représente la proportion de la population observée dans certaines des classes de la variable.
ptab1 <- prop.table(tab1) # proportion de patients cardiaques dans la variable gender
round(ptab1*100,1) # permet d'arrondir la fréquence et de la mettre sous forme de pourentage
##
## N N+ N++
## Non 44.3 2.9 2.8
## Oui 41.0 4.3 4.7
ptab2 <- prop.table(tab2) # proportion de patients cardiaques dans la variable gender
round(ptab2*100,1) # permet d'arrondir la fréquence et de la mettre sous forme de pourentage
##
## N N+ N++
## Non 42.1 5.3 2.6
## Oui 33.1 8.2 8.6
On voit bien ici qu’une personne avec un taux de glucose bien supérieur à la normale à une plus grande probabilité en pourcentage d’avoir une maladie cardivasculaire 4.7% contre 2.8%. Et idem pour le taux de cholesterol qui est de 8.6% contre 2.6%.
On veut désormais étudié les statistiques entre les variables quantitatives et qualitatives. Pour cela, on cherche à estimer les paramètres statistiques par niveau de facteurs de la variable qualitative. Ici, on sélectionne comme variable qualitative le genre (homme ou femme). On décide d’étudier la moyenne et l’écart-type en fonction du sexe des patients. On va ainsi pouvoir déterminer la corrélation entre ces différentes variables.
out <- aggregate(dfw_num[,c(2,3,4,5)], by = list(dfw_cat$gender), mean) # moyenne
out
## Group.1 age height ap_hi ap_lo
## 1 F 52.95434 161.3556 126.5622 81.06427
## 2 H 52.62918 169.9479 128.5281 82.41263
On voit donc ici, que la moyenne d’âge des hommes et des femmes dans notre jeu de données est similaire tout comme la moyenne des pressions artérielles. La seule différence observée est sur la moyenne de la taille qui diffère.
Finalement, on étudie les pressiosn artérielles en fonction de toutes les variables qualitatives :
out1 <- aggregate(dfw_num[,c(4,5)], by = list(dfw_cat$chol), mean)
out1
## Group.1 ap_hi ap_lo
## 1 N 125.3976 80.66510
## 2 N+ 131.6994 83.45700
## 3 N++ 134.2459 85.02927
out2 <- aggregate(dfw_num[,c(4,5)], by = list(dfw_cat$glucose), mean)
out2
## Group.1 ap_hi ap_lo
## 1 N 126.5426 81.20609
## 2 N+ 131.7980 83.60395
## 3 N++ 130.9376 83.30549
out3 <- aggregate(dfw_num[,c(4,5)], by = list(dfw_cat$smoke), mean)
out3
## Group.1 ap_hi ap_lo
## 1 Non 127.1133 81.46258
## 2 Oui 128.6586 82.29130
out4 <- aggregate(dfw_num[,c(4,5)], by = list(dfw_cat$cardio), mean)
out4
## Group.1 ap_hi ap_lo
## 1 Non 120.1710 78.23569
## 2 Oui 134.3365 84.83950
out5 <- aggregate(dfw_num[,c(4,5)], by = list(dfw_cat$alco), mean)
out5
## Group.1 ap_hi ap_lo
## 1 Non 127.1076 81.44091
## 2 Oui 129.7455 83.20218
out6 <- aggregate(dfw_num[,c(4,5)], by = list(dfw_cat$active), mean)
out6
## Group.1 ap_hi ap_lo
## 1 Non 127.2266 81.51285
## 2 Oui 127.2550 81.54117
On voit donc ici que le glucose et le cholesterol influent légèrement sur les différentes pressions artérielles. En effet, avec un taux de glucose normal les moyennes des pressions artérielles sont plus faible que avec un taux de glucose supérieur et bien supérieur à la normale. Et idem pour le cholesterol.
Concernant les autres variables qualitatives, on remarque que le fait d’être fumeur ou de boire de l’alcool n’influent quasiment pas sur les pressions artérielles de notre jeu de données puisque la différence entre les moyennes est très faible. Pour la pratique d’activités sportives, il n’y a aucune différence si oui ou non on pratique une activité sur les moyennes des pressions artérielles.
Enfin, on voit qu’il y a une forte dépendance entre les maladie cardivasculaire et les pressions artérielles. En effet, on voit bien que les moyennes des pressions artérielles sont plus basses lorsque le patient n’a pas de maladie cardiovasculaire et inversement.
Maintenant, on va étudier l’écart-type
out1 <- aggregate(dfw_num[,c(4,5)], by = list(dfw_cat$chol), sd)
out1
## Group.1 ap_hi ap_lo
## 1 N 15.23993 9.074694
## 2 N+ 18.39742 10.405029
## 3 N++ 16.25547 9.454028
out2 <- aggregate(dfw_num[,c(4,5)], by = list(dfw_cat$glucose), sd)
out2
## Group.1 ap_hi ap_lo
## 1 N 15.84816 9.305292
## 2 N+ 18.45828 10.456551
## 3 N++ 16.04578 9.472438
out3 <- aggregate(dfw_num[,c(4,5)], by = list(dfw_cat$smoke), sd)
out3
## Group.1 ap_hi ap_lo
## 1 Non 16.11324 9.403192
## 2 Oui 16.53266 9.772921
out4 <- aggregate(dfw_num[,c(4,5)], by = list(dfw_cat$cardio), sd)
out4
## Group.1 ap_hi ap_lo
## 1 Non 11.74932 8.112946
## 2 Oui 16.84569 9.517830
out5 <- aggregate(dfw_num[,c(4,5)], by = list(dfw_cat$alco), sd)
out5
## Group.1 ap_hi ap_lo
## 1 Non 16.09194 9.367044
## 2 Oui 17.06275 10.491650
out6 <- aggregate(dfw_num[,c(4,5)], by = list(dfw_cat$active), sd)
out6
## Group.1 ap_hi ap_lo
## 1 Non 15.93149 9.305739
## 2 Oui 16.21106 9.471592
L’écart-type nous reconfirme les premières estimations faites grâce à la moyenne.
On utilise ensuite des boxplots pour visualier les statistiques bivariées :
gr <- ggplot(dfw) + geom_boxplot(aes(x = cardio, y = age, fill = gender) )
gr
On étudie ici des âges en fonction des maladies cardiovasculaire et on
ajoute une variable qualitative le genre (homme ou femme). On remarque
donc qu’en majorité les personnes atteintes de maladies cardiovasculaire
sont plus âgées et que celles qui ne sont pas atteintes sont en général
plus jeunes. Enfin en ce qui concerne le genre, il n’a pas d’influence
ici.
On peut donc conclure de nos statistiques descriptives qu’il y a plus de chance qu’une personne âgée ait une maladie cardiovasculaire et que ces maladies sont souvent signes d’hypertension artérielle qui elle peut être liée à un taux trop élevé de glucose et/ou de cholesterol.