La première chose à faire quand on est face à des données qui nous sont peu ou pas familières, c’est de regarder la tête qu’elles ont : un petit coup d’œil au summary
et quelques graphes plus tard.
Une autre fonction qui peut être utile pour se faire une première idée sur nos données et qui donne le nombre de valeurs manquantes par variable : skim()
du package {skimr}
, ou encore la fonction glimpse()
du package {dplyr}
:
data(iris)
# introduction de 20% de valeurs manquantes :
iris.miss <- missForest::prodNA(iris, noNA = 0.2)
# summary de base :
summary(iris.miss)
## Sepal.Length Sepal.Width Petal.Length Petal.Width
## Min. :4.300 Min. :2.000 Min. :1.000 Min. :0.100
## 1st Qu.:5.100 1st Qu.:2.800 1st Qu.:1.600 1st Qu.:0.300
## Median :5.800 Median :3.000 Median :4.500 Median :1.300
## Mean :5.869 Mean :3.028 Mean :3.841 Mean :1.167
## 3rd Qu.:6.500 3rd Qu.:3.300 3rd Qu.:5.100 3rd Qu.:1.800
## Max. :7.900 Max. :4.400 Max. :6.900 Max. :2.500
## NA's :29 NA's :34 NA's :35 NA's :29
## Species
## setosa :42
## versicolor:41
## virginica :44
## NA's :23
##
##
##
# fonction str, également un classique
str(iris.miss)
## 'data.frame': 150 obs. of 5 variables:
## $ Sepal.Length: num 5.1 4.9 NA NA 5 5.4 4.6 5 NA NA ...
## $ Sepal.Width : num 3.5 NA NA 3.1 NA NA 3.4 3.4 2.9 3.1 ...
## $ Petal.Length: num 1.4 NA 1.3 1.5 NA NA 1.4 NA 1.4 NA ...
## $ Petal.Width : num 0.2 0.2 0.2 0.2 NA 0.4 0.3 0.2 0.2 0.1 ...
## $ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 NA NA 1 ...
# fonction skim, alternative à la fonction summary :
skimr::skim(iris.miss)
Name | iris.miss |
Number of rows | 150 |
Number of columns | 5 |
_______________________ | |
Column type frequency: | |
factor | 1 |
numeric | 4 |
________________________ | |
Group variables | None |
Variable type: factor
skim_variable | n_missing | complete_rate | ordered | n_unique | top_counts |
---|---|---|---|---|---|
Species | 23 | 0.85 | FALSE | 3 | vir: 44, set: 42, ver: 41 |
Variable type: numeric
skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
---|---|---|---|---|---|---|---|---|---|---|
Sepal.Length | 29 | 0.81 | 5.87 | 0.84 | 4.3 | 5.1 | 5.8 | 6.5 | 7.9 | ▅▇▆▅▂ |
Sepal.Width | 34 | 0.77 | 3.03 | 0.44 | 2.0 | 2.8 | 3.0 | 3.3 | 4.4 | ▂▆▇▂▁ |
Petal.Length | 35 | 0.77 | 3.84 | 1.74 | 1.0 | 1.6 | 4.5 | 5.1 | 6.9 | ▇▁▅▇▂ |
Petal.Width | 29 | 0.81 | 1.17 | 0.77 | 0.1 | 0.3 | 1.3 | 1.8 | 2.5 | ▇▁▆▃▃ |
# fonction glimpse
dplyr::glimpse(iris.miss)
## Rows: 150
## Columns: 5
## $ Sepal.Length <dbl> 5.1, 4.9, NA, NA, 5.0, 5.4, 4.6, 5.0, NA, NA, NA, 4.8, 4.~
## $ Sepal.Width <dbl> 3.5, NA, NA, 3.1, NA, NA, 3.4, 3.4, 2.9, 3.1, NA, 3.4, 3.~
## $ Petal.Length <dbl> 1.4, NA, 1.3, 1.5, NA, NA, 1.4, NA, 1.4, NA, NA, 1.6, 1.4~
## $ Petal.Width <dbl> 0.2, 0.2, 0.2, 0.2, NA, 0.4, 0.3, 0.2, 0.2, 0.1, 0.2, 0.2~
## $ Species <fct> setosa, setosa, setosa, setosa, setosa, setosa, setosa, N~
Mais attention ! Avant de se lancer tête baissée dans l’étude des différentes méthodes d’imputation, faut-il déjà savoir identifier les données manquantes. Elles ne sont en effet pas toujours matérialisées par un clair « NA ». Il est également utile d’en comprendre les raisons.
De nombreux facteurs peuvent entrer en compte, selon la nature et la provenance de vos données. Les données manquantes sont rangées selon le mécanisme qui a conduit à leur absence :
– MCAR : Missing completely at random : La probabilité que la donnée soit manquante pour une variable est indépendante des autres variables, comme perdre un disque qui contient 10% des données, faire tomber un tube d’analyse sanguine, …
– MAR : Missing At Random : La probabilité que la donnée soit manquante pour une variable dépend des autres variables observé es, mais pas de la variable en question. C’est par exemple la mesure du poids qui va dépendre de l’âge (i.e. on pèse moins les adultes que les enfants).
– MNAR : Missing Not At Random : La probabilité que la donnée soit manquante pour une variable dépend de la valeur non observée. C’est l’exemple des personnes ayant des hauts revenus qui répondent moins à la question sur leur salaire, ou des patients séropositifs qui répondront moins à la question sur la séropositivité.
« NA » est le symbole de la donnée manquante dans R, comme beaucoup d’autres langages (ne pas le confondre avec « NaN » qui signifie “not a number”, qui peut apparaître lors d’une division par zéro par exemple). Mais les données manquantes ne sont pas toujours mises à NA. Ci-dessous une liste non exhaustive de cas que nous pouvons rencontrer :
Le cas le plus simple à identifier est le caractère vide ou l’espace pour les variables de type chaînes de caractères. Il est également possible d’avoir à faire à des « no data ».
Dans le même type de cas de figure mais pour les variables numériques, on retrouve régulièrement les « 999 » et autres nombres volontairement incohérents.
Les outliers constituent également des valeurs manquantes Dans les séries chronologiques, plusieurs cas : La dernière observation est répétée jusqu’à ce qu’une nouvelle donnée soit observée.
Des séquences entières sont répétées : jour/semaine/mois précédent 0 au lieu de NA ou parfois autre valeur constante basse.
Dans un certain nombre des cas cités ci-dessus, notamment la répétition de séquences, on se trouve dans le cas où les données ont été déjà traitées par un tiers afin qu’elles ne soient pas manquantes. Les détecter peut représenter un réel enjeu car la méthode de remplacement utilisée à priori n’est peut-être pas la plus adéquate (remplacer une valeur manquante par zéro alors qu’il s’agit d’une variable dont les valeurs sont toujours comprises entre 100 et 150 ne peut pas vraiment être considéré comme une bonne idée). Nous devons garder en tête que sans données de qualité (et donc sans méthode adaptée pour la gestion des données manquantes), il sera impossible de donner du sens à nos analyses.
Il existe en fait nombreux packages R ont des fonctions dédiées à la représentation graphique des données manquantes – donc non ce n’est pas une idée saugrenue. L’idée est de comprendre nos données manquantes, d’en déterminer les patterns s’il y en a.
{visdat}
est un package qui permet de visualiser un jeu de données entier. La fonction vis_miss()
se concentre sur les valeurs manquantes de l’ensemble de nos données : pourcentage de NA pour chaque variable et global, visualisation
library(visdat)
vis_miss(airquality)
Ce package est entièrement dédié aux données manquantes, avec en particulier 4 fonctions permettant de les visualiser, non seulement variable par variable, mais également les relations entre elles.
la fonction geom_miss_points()
remplace les NA par des valeurs 10% plus basses que la valeur minimum observée de la variable, ce qui permet de les visualiser comme ci-dessous :
library(naniar)
library(ggplot2)
ggplot(data = airquality) +
aes(x = Ozone, y = Solar.R) +
geom_miss_point()
La fontion gg_miss_var()
présente une autre approche pour la visualisation des données manquantes :
gg_miss_var(airquality)
## Warning: It is deprecated to specify `guide = FALSE` to remove a guide. Please
## use `guide = "none"` instead.
La fonction gg_miss_case()
:
gg_miss_case(airquality)
La fonction gg_miss_fct()
plot le nombre de valeurs manquantes de chaque colonne en fonction d’une variable catégorielle du jeu de données :
gg_miss_fct(x = riskfactors, fct = marital)
La fonction gg_miss_upset()
peut être utile pour visualiser les combinaisons de NA
les et intersections de variables.
library(UpSetR)
gg_miss_upset(riskfactors)
L’imputation des données manquantes est simplement le fait de remplacer ces valeurs, avec la méthode la plus adéquate.
Si la quantité de données manquantes peut être considérée comme négligeable, au regard de la taille de notre échantillon, il est possible d’envisager de simplement supprimer les observations concernées, et ainsi de ne pas introduire de biais dans notre analyse. L’option R correspondant à cette décisision est de mettre le paramètre na.rm à TRUE, mais ce n’est pas l’option par défaut, en effet cela suppose une perte d’information, et nous ne sommes pas toujours prêts à faire ce sacrifice.
Bon, et sinon, on fait quoi ?
La méthodologie que nous mettons en place pour remplacer nos fameuses données manquantes est très étroitement liées à notre jeu de données ; beaucoup de techniques s’appuient en effet sur les autres variables que nous avons à notre disposition, quelques-uns des exemples ci-dessous illustrent ce propos.
Dans certains cas, des méthodes très simples peuvent être mises en œuvre. Quelques exemples :
Pour les variables catégorielles :
Remplacement par la valeur dominante (ie la plus fréquente)\ Remplacement par la valeur dominante par classe (si on considère par exemple la taille d’une personne, mise sous forme catégorielle : la valeur dominante sera différente selon le sexe)\ Réfléchir au sens de la donnée manquante : n’y a-t-il pas de sens à cette absence de donnée ? Cela mérite peut-être la création d’une nouvelle catégorie
Pour les variables numériques, le package {zoo}
offre de nombreuses solutions :
Remplacement par la moyenne globale ou la moyenne par classe : na.aggregate. C’est la méthode la plus populaire, mais elle ne constitue que rarement une solution viable. Voyons un exemple simple :
# Génération d'un jeu de données bivarié, distribution normale
df_bivar <- data.frame(x = rnorm(50, 0, 1), y = rnorm(50, 10, 10))
# Introduction artificielle de données manquantes (20%) :
df_miss <- df_bivar
df_miss$y[sample(1:nrow(df_miss), size = 10)] <- NA
df_miss$any_na <- ifelse(is.na(df_miss$y), "donnée imputée", "donnée originale")
# Imputation des données manquantes par la moyenne
library(zoo)
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
df_miss$y <- na.aggregate(df_miss$y, FUN = mean)
library(plotly)
##
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
##
## last_plot
## The following object is masked from 'package:stats':
##
## filter
## The following object is masked from 'package:graphics':
##
## layout
# Visualisation
plot_ly(data = df_miss, x = ~x, y = ~y, color = ~any_na, colors = "Set2")
## No trace type specified:
## Based on info supplied, a 'scatter' trace seems appropriate.
## Read more about this trace type -> https://plotly.com/r/reference/#scatter
## No scatter mode specifed:
## Setting the mode to markers
## Read more about this attribute -> https://plotly.com/r/reference/#scatter-mode
library(zoo)
bz <- zoo(c(2, NA, 1, 4, 5, 2))
# répétition de la dernière valeur observée
na.locf(bz)
## 1 2 3 4 5 6
## 2 2 1 4 5 2
# répétition de la prochaine valeur observée
na.locf(bz, fromLast = TRUE)
## 1 2 3 4 5 6
## 2 1 1 4 5 2
Pour les variables triées en fonction du temps ou en fonction d’une autre variable. Interpolation linéaire grâce à la fonction na.approx()
, interpolation spline cubique avec la fonction na.spline()
. Attention, ici on suppose que chaque observation est distante d’une unité, et non indexée par la variable de tri.
library(zoo)
library(plotly)
# simualtion des données
z <- c(rnorm(10, 10), rep(NA, 5), rnorm(10, 10))
# interpolation linéaire
z_lin <- na.approx(z)
# interpolation spline
z_spline <- na.spline(z)
data_z <- data.frame(x = 1:25, z = z, z_lin = z_lin, z_spline = z_spline)
# plot
p <- plot_ly(data_z, x = ~x) %>%
add_trace(y = ~z_lin, name = "linéaire", type = "scatter", mode = "lines") %>%
add_trace(y = ~z_spline, name = "spline", type = "scatter", mode = "lines") %>%
add_trace(y = ~z, name = "z", type = "scatter", mode = "lines+markers") %>%
layout(
title = "Interpolation entre points",
xaxis = list(title = ""),
yaxis = list(title = "")
)
p
l’idée est de calculer les distances entre observations, et d’attribuer aux valeurs manquante la moyenne des valeurs observées chez les k plus proches voisins : fonction kNN()
du package {VIM}
.
library(VIM)
## Loading required package: colorspace
## Loading required package: grid
## VIM is ready to use.
## Suggestions and bug-reports can be submitted at: https://github.com/statistikat/VIM/issues
##
## Attaching package: 'VIM'
## The following object is masked from 'package:datasets':
##
## sleep
# données avec valeurs manquantes :
data(sleep)
# imputation grâce à la méthode des kNN :
kNN(sleep)
## BodyWgt BrainWgt NonD Dream Sleep Span Gest Pred Exp Danger BodyWgt_imp
## 1 6654.000 5712.00 3.2 0.8 3.3 38.6 645.0 3 5 3 FALSE
## 2 1.000 6.60 6.3 2.0 8.3 4.5 42.0 3 1 3 FALSE
## 3 3.385 44.50 12.8 2.4 12.5 14.0 60.0 1 1 1 FALSE
## 4 0.920 5.70 10.4 2.4 16.5 3.2 25.0 5 2 3 FALSE
## 5 2547.000 4603.00 2.1 1.8 3.9 69.0 624.0 3 5 4 FALSE
## 6 10.550 179.50 9.1 0.7 9.8 27.0 180.0 4 4 4 FALSE
## 7 0.023 0.30 15.8 3.9 19.7 19.0 35.0 1 1 1 FALSE
## 8 160.000 169.00 5.2 1.0 6.2 30.4 392.0 4 5 4 FALSE
## 9 3.300 25.60 10.9 3.6 14.5 28.0 63.0 1 2 1 FALSE
## 10 52.160 440.00 8.3 1.4 9.7 50.0 230.0 1 1 1 FALSE
## 11 0.425 6.40 11.0 1.5 12.5 7.0 112.0 5 4 4 FALSE
## 12 465.000 423.00 3.2 0.7 3.9 30.0 281.0 5 5 5 FALSE
## 13 0.550 2.40 7.6 2.7 10.3 7.0 45.0 2 1 2 FALSE
## 14 187.100 419.00 3.2 0.7 3.1 40.0 365.0 5 5 5 FALSE
## 15 0.075 1.20 6.3 2.1 8.4 3.5 42.0 1 1 1 FALSE
## 16 3.000 25.00 8.6 0.0 8.6 50.0 28.0 2 2 2 FALSE
## 17 0.785 3.50 6.6 4.1 10.7 6.0 42.0 2 2 2 FALSE
## 18 0.200 5.00 9.5 1.2 10.7 10.4 120.0 2 2 2 FALSE
## 19 1.410 17.50 4.8 1.3 6.1 34.0 225.0 1 2 1 FALSE
## 20 60.000 81.00 12.0 6.1 18.1 7.0 35.0 1 1 1 FALSE
## 21 529.000 680.00 3.2 0.3 3.8 28.0 400.0 5 5 5 FALSE
## 22 27.660 115.00 3.3 0.5 3.8 20.0 148.0 5 5 5 FALSE
## 23 0.120 1.00 11.0 3.4 14.4 3.9 16.0 3 1 2 FALSE
## 24 207.000 406.00 8.3 1.5 12.0 39.3 252.0 1 4 1 FALSE
## 25 85.000 325.00 4.7 1.5 6.2 41.0 310.0 1 3 1 FALSE
## 26 36.330 119.50 12.8 2.4 13.0 16.2 63.0 1 1 1 FALSE
## 27 0.101 4.00 10.4 3.4 13.8 9.0 28.0 5 1 3 FALSE
## 28 1.040 5.50 7.4 0.8 8.2 7.6 68.0 5 3 4 FALSE
## 29 521.000 655.00 2.1 0.8 2.9 46.0 336.0 5 5 5 FALSE
## 30 100.000 157.00 10.9 2.3 10.8 22.4 100.0 1 1 1 FALSE
## 31 35.000 56.00 11.0 0.9 9.8 16.3 33.0 3 5 4 FALSE
## 32 0.005 0.14 7.7 1.4 9.1 2.6 21.5 5 2 4 FALSE
## 33 0.010 0.25 17.9 2.0 19.9 24.0 50.0 1 1 1 FALSE
## 34 62.000 1320.00 6.1 1.9 8.0 100.0 267.0 1 1 1 FALSE
## 35 0.122 3.00 8.2 2.4 10.6 9.8 30.0 2 1 1 FALSE
## 36 1.350 8.10 8.4 2.8 11.2 3.9 45.0 3 1 3 FALSE
## 37 0.023 0.40 11.9 1.3 13.2 3.2 19.0 4 1 3 FALSE
## 38 0.048 0.33 10.8 2.0 12.8 2.0 30.0 4 1 3 FALSE
## 39 1.700 6.30 13.8 5.6 19.4 5.0 12.0 2 1 1 FALSE
## 40 3.500 10.80 14.3 3.1 17.4 6.5 120.0 2 1 1 FALSE
## 41 250.000 490.00 3.2 1.0 3.1 23.6 440.0 5 5 5 FALSE
## 42 0.480 15.50 15.2 1.8 17.0 12.0 140.0 2 2 2 FALSE
## 43 10.000 115.00 10.0 0.9 10.9 20.2 170.0 4 4 4 FALSE
## 44 1.620 11.40 11.9 1.8 13.7 13.0 17.0 2 1 2 FALSE
## 45 192.000 180.00 6.5 1.9 8.4 27.0 115.0 4 4 4 FALSE
## 46 2.500 12.10 7.5 0.9 8.4 18.0 31.0 5 5 5 FALSE
## 47 4.288 39.20 11.0 1.8 12.5 13.7 63.0 2 2 2 FALSE
## 48 0.280 1.90 10.6 2.6 13.2 4.7 21.0 3 1 3 FALSE
## 49 4.235 50.40 7.4 2.4 9.8 9.8 52.0 1 1 1 FALSE
## 50 6.800 179.00 8.4 1.2 9.6 29.0 164.0 2 3 2 FALSE
## 51 0.750 12.30 5.7 0.9 6.6 7.0 225.0 2 2 2 FALSE
## 52 3.600 21.00 4.9 0.5 5.4 6.0 225.0 3 2 3 FALSE
## 53 14.830 98.20 3.2 0.7 2.6 17.0 150.0 5 5 5 FALSE
## 54 55.500 175.00 3.2 0.6 3.8 20.0 151.0 5 5 5 FALSE
## 55 1.400 12.50 11.0 1.8 11.0 12.7 90.0 2 2 2 FALSE
## 56 0.060 1.00 8.1 2.2 10.3 3.5 42.0 3 1 2 FALSE
## 57 0.900 2.60 11.0 2.3 13.3 4.5 60.0 2 1 2 FALSE
## 58 2.000 12.30 4.9 0.5 5.4 7.5 200.0 3 1 3 FALSE
## 59 0.104 2.50 13.2 2.6 15.8 2.3 46.0 3 2 2 FALSE
## 60 4.190 58.00 9.7 0.6 10.3 24.0 210.0 4 3 4 FALSE
## 61 3.500 3.90 12.8 6.6 19.4 3.0 14.0 2 1 1 FALSE
## 62 4.050 17.00 13.8 3.9 17.4 13.0 38.0 3 1 1 FALSE
## BrainWgt_imp NonD_imp Dream_imp Sleep_imp Span_imp Gest_imp Pred_imp Exp_imp
## 1 FALSE TRUE TRUE FALSE FALSE FALSE FALSE FALSE
## 2 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 3 FALSE TRUE TRUE FALSE FALSE FALSE FALSE FALSE
## 4 FALSE TRUE TRUE FALSE TRUE FALSE FALSE FALSE
## 5 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 6 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 7 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 8 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 9 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 10 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 11 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 12 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 13 FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE
## 14 FALSE TRUE TRUE FALSE FALSE FALSE FALSE FALSE
## 15 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 16 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 17 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 18 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 19 FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE
## 20 FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE
## 21 FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE
## 22 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 23 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 24 FALSE TRUE TRUE FALSE FALSE FALSE FALSE FALSE
## 25 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 26 FALSE TRUE TRUE FALSE FALSE FALSE FALSE FALSE
## 27 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 28 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 29 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 30 FALSE TRUE TRUE FALSE FALSE FALSE FALSE FALSE
## 31 FALSE TRUE TRUE TRUE FALSE FALSE FALSE FALSE
## 32 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 33 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 34 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 35 FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE
## 36 FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE
## 37 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 38 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 39 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 40 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 41 FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE
## 42 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 43 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 44 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 45 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 46 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 47 FALSE TRUE TRUE FALSE FALSE FALSE FALSE FALSE
## 48 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 49 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 50 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 51 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 52 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 53 FALSE TRUE TRUE FALSE FALSE FALSE FALSE FALSE
## 54 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 55 FALSE TRUE TRUE FALSE FALSE FALSE FALSE FALSE
## 56 FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE
## 57 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 58 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 59 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 60 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 61 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## 62 FALSE TRUE TRUE TRUE FALSE FALSE FALSE FALSE
## Danger_imp
## 1 FALSE
## 2 FALSE
## 3 FALSE
## 4 FALSE
## 5 FALSE
## 6 FALSE
## 7 FALSE
## 8 FALSE
## 9 FALSE
## 10 FALSE
## 11 FALSE
## 12 FALSE
## 13 FALSE
## 14 FALSE
## 15 FALSE
## 16 FALSE
## 17 FALSE
## 18 FALSE
## 19 FALSE
## 20 FALSE
## 21 FALSE
## 22 FALSE
## 23 FALSE
## 24 FALSE
## 25 FALSE
## 26 FALSE
## 27 FALSE
## 28 FALSE
## 29 FALSE
## 30 FALSE
## 31 FALSE
## 32 FALSE
## 33 FALSE
## 34 FALSE
## 35 FALSE
## 36 FALSE
## 37 FALSE
## 38 FALSE
## 39 FALSE
## 40 FALSE
## 41 FALSE
## 42 FALSE
## 43 FALSE
## 44 FALSE
## 45 FALSE
## 46 FALSE
## 47 FALSE
## 48 FALSE
## 49 FALSE
## 50 FALSE
## 51 FALSE
## 52 FALSE
## 53 FALSE
## 54 FALSE
## 55 FALSE
## 56 FALSE
## 57 FALSE
## 58 FALSE
## 59 FALSE
## 60 FALSE
## 61 FALSE
## 62 FALSE
Pour les séries temporelles :
On l’a vu précédemment, la répétition d’une séquence peut constituer une solution viable.
La fonction na.StructTS()
du package {zoo}
permet de reproduire la saisonnalité de la série en utilisant le filtre saisonnier de Kalman
z <- zooreg(
rep(10 * seq(8), each = 4) + rep(c(3, 1, 2, 4), times = 8),
start = as.yearqtr(2000), freq = 4
)
z[25] <- NA
zout <- na.StructTS(z)
data_z <- data.frame(z = z, z_out = zout)
# plot
p <- plot_ly(data_z) %>%
add_trace(y = ~zout, name = "Kalman", type = "scatter", mode = "lines") %>%
add_trace(y = ~z, name = "avec NA", type = "scatter", mode = "lines") %>%
layout(
title = "na.StructTS",
xaxis = list(title = ""),
yaxis = list(title = "")
)
p
Si ces méthodes peuvent être satisfaisantes dans certains cas, elles ne sont pas assez solides dans d’autres, c’est pourquoi il existe des algorithmes d’imputation bien plus sophistiqués, et on est chanceux, la formidable communauté R s’est déjà penchée sur le sujet et nous offre des packages très bien faits.
La fonction impute.svd()
du package {bcv}
permet d’appliquer l’algorithme de la décomposition en valeurs singulières afin de prédire les valeurs manquantes.
library(bcv)
# Génération d'une matrice avec données manquantes
u <- rnorm(20)
v <- rnorm(10)
xfull <- u %*% rbind(v) + rnorm(200)
miss <- sample(1:20, 5)
x <- xfull
x[miss] <- NA
# imputation des données manquantes avec une approximation SVD de rang 1
xhat <- impute.svd(x, 1)$x
data_z <- data.frame(x = 1:20, y_miss = x[, 1], y = xhat[, 1])
# plot
p <- plot_ly(data_z, x = ~x) %>%
add_trace(y = ~y, name = "après imputation", type = "scatter", mode = "lines") %>%
add_trace(y = ~y_miss, name = "avec NA", type = "scatter", mode = "lines") %>%
layout(
title = "Imputation SVD",
xaxis = list(title = ""),
yaxis = list(title = "")
)
p
C’est l’une des stars des packages d’imputation de données manquantes. Son utilisation suppose que les données soient MAR (Missing At Random). A chaque variable est associé un modèle d’imputation, conditionnellement aux autres variables du jeu de données : si on a Xk variables, les données manquantes de la variable Xi seront remplacées par les prédictions d’un modèle créé à partir des autres variables. Ce package présente une solution très complète au vu du nombre de méthodes implémentées, je vous invite à consulter l’aide du package pour en avoir la liste exhaustive. Pour n’en citer que quelques-unes, le paramètre method
de la fonction mice()
peut prendre les valeurs suivantes :
Pour tout type de variable :
pmm
: predictive mean matching
cart
: arbres de régression et classification
rf
: modèle random forest
Pour les variables numériques :
norm
: régression linéaire
quadratic
Pour les variables binaires :
logreg
: régression logistique
Pour les variables factorielles :
polyreg
: régression logistique multiple lda
: analyse discriminante linéaire
Pour les variables factorielles ordonnées :
polr
: modèle de probabilité proportionnelle (proportional odds model)
Une autre raison pour laquelle ce package est encensé par la communauté est qu’il présente de nombreuses fonctions permettant de faire un tas de choses, en plus de l’imputation, comme :
Visualiser les données manquantes, Diagnostiquer la qualité des valeurs imputées, Analyser chaque ensemble de données complétées, Incorporer des méthodes d’imputation personnalisées.
Ci-dessous un exemple d’utilisation de ce package :
library(mice)
##
## Attaching package: 'mice'
## The following object is masked from 'package:stats':
##
## filter
## The following objects are masked from 'package:base':
##
## cbind, rbind
# données avec NA :
data_mice <- nhanes2
summary(data_mice)
## age bmi hyp chl
## 20-39:12 Min. :20.40 no :13 Min. :113.0
## 40-59: 7 1st Qu.:22.65 yes : 4 1st Qu.:185.0
## 60-99: 6 Median :26.75 NA's: 8 Median :187.0
## Mean :26.56 Mean :191.4
## 3rd Qu.:28.93 3rd Qu.:212.0
## Max. :35.30 Max. :284.0
## NA's :9 NA's :10
# Visualisation des données manquantes :
md.pattern(data_mice)
## age hyp bmi chl
## 13 1 1 1 1 0
## 3 1 1 1 0 1
## 1 1 1 0 1 1
## 1 1 0 0 1 2
## 7 1 0 0 0 3
## 0 8 9 10 27
Cette visualisation nous permet de déterminer rapidement où sont les valeurs manquantes de notre jeu de données. Ici on voit :
13 observations n’ont aucune valeur manquante
Pour 3 observations la valeur de chl est manquante
Pour 1 observation, la valeur de bmi est manquante
etc…
# Imputation multiple (5) sur données mixtes, avec une méthode différente sur chaque colonne :
imputed <- mice(data_mice, meth = c("sample", "pmm", "logreg", "norm"), m = 5, printFlag = FALSE)
# voir les valeurs imputées :
# 5 imputations par variable
imputed$imp$bmi
## 1 2 3 4 5
## 1 26.3 27.5 27.2 22.7 25.5
## 3 30.1 28.7 29.6 27.2 25.5
## 4 27.5 25.5 24.9 24.9 27.2
## 6 29.6 22.5 22.0 21.7 22.7
## 10 27.2 21.7 22.5 26.3 22.0
## 11 35.3 22.7 35.3 22.5 29.6
## 12 27.5 21.7 27.5 25.5 20.4
## 16 27.5 28.7 27.2 20.4 30.1
## 21 33.2 28.7 28.7 35.3 28.7
Le paramètre m
représentant le nombre d’imputation effectuées. La fonction complete()
permet de sélectionner une des imputations afin de compléter notre jeu de données :
# données complétées avec la 2nde imputation :
imputed_data <- mice::complete(imputed, 2)
La fonction with()
permet de construire des modèles sur chaque jeu de données générés :
fit <- with(data = imputed, exp = lm(bmi ~ hyp + chl))
La fonction pool()
permet comme son nom l’indique de combiner les modèles :
combined <- mice::pool(fit)