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)
Data summary
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.

Dénition de quelques type de données manquantes

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é.

Reconnaitre les données manquantes

« 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.

Visualisation des données manquantes

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.

Le package {visdat}

{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)

Le package {naniar}

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)

Le package {UpSetR}

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)

Les différentes stratégies d’imputation

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

Répétition de la dernière ou de la prochaine valeur observée : na.locf()

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

Interpolation entre points

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

Interpolation grâce à la méthode des kNN

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.

Le package {BCV}

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

Le package {mice} (Multivariate Imputation via Chained Equations)

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)