Introduction
On peut avoir besoin d’echantillonner un jeu de donnees pour differentes raisons :
- construire un echantillon d’apprentissage et un echantillon test
- remedier a la rarete de la cible
- …
tout en preservant la distribution de certaines variables.
On utilise R base et le package dplyr (group_by combine a sample_n ou sample_freq) pour realiser les echantillonnages les plus classiques que sont l’echantillonnage simple, l’echantillonnage equilibre et l’echantillonnage stratifie selon une variable qualitative.
Pour les echantillonnages plus complexes, notamment du sur-echantillonnage avec les algorithmes ROSE ou SMOTE dans le cas d’une cible rare, on emploie le package themis qui fait partie de la collection de package tidymodels.
On utilise le jeu de donnees “bankData” du package otvPlots, la modalite cible ‘yes’ de la variable y a une frequence de 12%.
Sur le CRAN, la version 0.1.0 du package themis a un bug dans la fonction step_smote, il a ete corrige dans la version en developpement sur github. En attendant le passage en production sur le CRAN de la nouvelle version, il faut donc installer la version de github avec la commande
remotes::install_github("tidymodels/themis")
.
library("dplyr")
library("recipes")
library("themis")
# remotes::install_github("tidymodels/themis")
data("bankData", package = "otvPlots")
= bankData %>% select(y, everything())
bankData
# frequence de la cible y
%>% count(y, name = "nb") %>%
bankData mutate(freq = scales::percent(nb / sum(nb)),
nb = scales::number(nb, trim = FALSE)) %>% as.data.frame
y nb freq
1 no 39 922 88%
2 yes 5 289 12%
Echantillonnage usuel
Echantillonnage simple / equilibre
Echantillonnage simple : 70% pour l’echantillon d’apprentissage, 30% pour l’echantillon test.
= bankData %>% mutate(simple = sample(x = c("train", "test"),
bankData size = nrow(.),
replace = TRUE,
prob = c(0.7, 0.3)
)
)
%>% count(simple) %>% mutate(freq = scales::percent(n / sum(n))) bankData
simple n freq
1 test 13616 30%
2 train 31595 70%
Echantillonnage equilibre : 3000 modalites ‘no’ et 3000 modalites ‘yes’.
# echantillon (d'apprentissage) equilibre 3000 / 3000
= bankData %>% group_by(y) %>% sample_n(3000) %>% ungroup
dtf_train %>% count(y) dtf_train
# A tibble: 2 x 2
y n
<chr> <int>
1 no 3000
2 yes 3000
Echantillonnage stratifie
Le probleme : certaines modalites rares peuvent etre presentes dans l’echantillon test mais absentes de l’echantillon d’apprentissage, ce qui declenche une erreur quand on applique le modele predictif a l’echantillon test ou qu’on le met en production.
# zoom sur les modalites de la variable 'job'
%>% group_by(job) %>% summarise(nb = n(),
bankData freq_cible = scales::percent(mean(y == 'yes'))) %>%
mutate(freq_modalite = scales::percent(nb / sum(nb)),
nb = scales::number(nb, trim = FALSE)) %>% as.data.frame
# echantillon qui perd la modalite 'unknown' car il n'est pas stratifie
set.seed(2014)
= bankData %>% group_by(y) %>% sample_n(200) %>% ungroup
dtf_train2 %>% count(job) dtf_train2
job nb freq_cible freq_modalite
1 admin. 5 171 12% 11.44%
2 blue-collar 9 732 7% 21.53%
3 entrepreneur 1 487 8% 3.29%
4 housemaid 1 240 9% 2.74%
5 management 9 458 14% 20.92%
6 retired 2 264 23% 5.01%
7 self-employed 1 579 12% 3.49%
8 services 4 154 9% 9.19%
9 student 938 29% 2.07%
10 technician 7 597 11% 16.80%
11 unemployed 1 303 16% 2.88%
12 unknown 288 12% 0.64%
# A tibble: 11 x 2
job n
<chr> <int>
1 admin. 55
2 blue-collar 58
3 entrepreneur 18
4 housemaid 9
5 management 80
6 retired 34
7 self-employed 13
8 services 32
9 student 11
10 technician 74
11 unemployed 16
La stratification preserve les frequences relatives des modalites de la cible et de la variable ‘job’.
= bankData %>% group_by(job, y) %>% sample_frac(0.5)
dtf_strat
%>% group_by(job) %>% summarise(nb = n(),
dtf_strat freq_cible = scales::percent(mean(y == 'yes'))) %>%
mutate(freq_modalite = scales::percent(nb / sum(nb)),
nb = scales::number(nb, trim = FALSE)) %>% as.data.frame
job nb freq_cible freq_modalite
1 admin. 2 586 12% 11.44%
2 blue-collar 4 866 7% 21.53%
3 entrepreneur 744 8% 3.29%
4 housemaid 620 9% 2.74%
5 management 4 728 14% 20.92%
6 retired 1 132 23% 5.01%
7 self-employed 790 12% 3.50%
8 services 2 076 9% 9.18%
9 student 468 29% 2.07%
10 technician 3 798 11% 16.80%
11 unemployed 651 16% 2.88%
12 unknown 144 12% 0.64%
Sur-echantillonnage avec ROSE et SMOTE
Les deux algorithmes qu’on a choisi dans le package themis creent de nouvelles cibles artificielles a partir des vraies cibles contenues dans le jeu de donnees initial. On binarise toutes les variables qualitatives (sauf la cible) avec l’etape step_dummy(all_nominal(), - all_outcomes()) car les algorithmes ROSE et SMOTE ne s’appliquent qu’a des predicteurs numeriques.
Note : le sur-echantillonnage par simple replication des cibles peut deboucher sur du surapprentissage, alors que les methodes qui generent des cibles artificielles ajoutent un certain bruit et reduisent ce risque.
Algorithme ROSE
Cet algorithme cree des cibles supplementaires dans le voisinage de chaque cible reelle a partir d’une estimation par noyau de la distribution des autres cibles dans son voisinage.
%>% count(y)
bankData
= bankData %>% mutate(date = as.integer(date)) %>% recipe(formula = y ~ .) %>%
bankData_num step_dummy(all_nominal(), - all_outcomes()) %>% step_rose(y) %>% prep %>% juice()
%>% count(y) bankData_num
y n
1 no 39922
2 yes 5289
# A tibble: 2 x 2
y n
<fct> <int>
1 no 39967
2 yes 39877
Algorithme SMOTE
La methode SMOTE relie chaque cible reelle aux cibles les plus proches par des segments et elle choisit comme cibles artificielles supplementaires des points situes sur ces segments.
= bankData %>% mutate(date = as.integer(date)) %>% recipe(formula = y ~ .) %>%
bankData_num step_dummy(all_nominal(), - all_outcomes()) %>% step_smote(y) %>% prep %>% juice()
%>% count(y) bankData_num
# A tibble: 2 x 2
y n
<fct> <int>
1 no 39922
2 yes 39922