Ga naar hoofdinhoud

Featureselectie in R met het Boruta R-pakket

Pak featureselectie in R aan: verken het Boruta-algoritme, een wrapper rond het Random Forest-classificatiealgoritme, en de implementatie ervan!
Bijgewerkt 2 okt 2026  · 10 min lezen

Verken met AI

ChatGPTClaudePerplexity

Data met hoge dimensionaliteit, in termen van aantal features, komt tegenwoordig steeds vaker voor bij machine learning-problemen. Om nuttige informatie uit deze grote hoeveelheden data te halen, moet je statistische technieken gebruiken om ruis of redundante data te verminderen. Je hebt namelijk vaak niet elke feature nodig om een model te trainen. Je kunt je model verbeteren door alleen die features te gebruiken die niet gecorreleerd en niet-redundant zijn. Hier speelt featureselectie een belangrijke rol. Het helpt niet alleen om je model sneller te trainen, maar vermindert ook de complexiteit, maakt het model makkelijker te interpreteren en verbetert de accuracy, precision of recall, afhankelijk van de gekozen prestatiemaatstaf.

In deze tutorial behandel je de volgende concepten:

  • Eerst leer je meer over featureselectie: je ziet wanneer je het kunt gebruiken en welke soorten methoden beschikbaar zijn om de belangrijkste features voor je model te selecteren!

  • Vervolgens maak je kennis met het Boruta-algoritme. Je ziet hoe je hiermee top-down naar relevante features kunt zoeken door de belangrijkheid van originele attributen te vergelijken met de belangrijkheid die willekeurig haalbaar is, geschat met behulp van gepermuteerde kopieën, en stap voor stap irrelevante features te elimineren.

  • Je werpt ook een korte blik op de dataset waarop je de featureselectie gaat uitvoeren. Je ziet hoe je eenvoudig ontbrekende waarden kunt imputeren met behulp van het Amelia-pakket.

  • Tot slot leer je meer over het Boruta-pakket, waarmee je het algoritme kunt draaien.

Featureselectie

In het algemeen kom je, wanneer je de dimensionaliteit van data wilt verminderen, methoden tegen zoals Principal Component Analysis, Singular Value Decomposition, enzovoort. Het is dus logisch om te vragen waarom je überhaupt andere featureselectiemethoden nodig hebt. Het punt met deze technieken is dat het onbewaakte manieren van featureselectie zijn: neem bijvoorbeeld PCA, dat variantie in data gebruikt om de componenten te vinden. Deze technieken houden geen rekening met de informatie tussen featurewaarden en de doelklasse of -waarden. Ook zijn er bepaalde aannames, zoals normaliteit, verbonden aan dit soort methoden die transformaties vereisen voordat je ze kunt toepassen. Deze beperkingen gelden niet voor alle soorten data.

Er zijn grofweg drie soorten featureselectiemethoden:

  • Filtermethoden: filtermethoden worden meestal als pre-processingstap gebruikt. De selectie van features is onafhankelijk van een machine learning-algoritme. In plaats daarvan worden features geselecteerd op basis van hun scores in verschillende statistische tests voor hun correlatie met de uitkomstvariabele. Enkele veelgebruikte filtermethoden zijn correlatiematen (Pearson, Spearman, Distance), de Chi-kwadraattoets, ANOVA, Fisher’s Score, enz.

  • Wrappermethoden: bij wrappermethoden gebruik je een subset van features en train je een model met die subset. Op basis van de bevindingen uit het vorige model besluit je features toe te voegen of te verwijderen uit de subset. Forward Selection en Backward Elimination zijn voorbeelden van wrappermethoden.

  • Embedded-methoden: dit zijn algoritmen met ingebouwde featureselectie. LASSO-regressie is zo’n voorbeeld.

In deze tutorial gebruik je een van de wrappermethoden die direct beschikbaar is in R via een pakket genaamd Boruta.

Het Boruta-algoritme

Het Boruta-algoritme is een wrapper rond het random forest-classificatiealgoritme. Het probeert alle belangrijke, interessante features in je dataset vast te leggen met betrekking tot een uitkomstvariabele.

  • Eerst dupliceert het de dataset en shuffelt de waarden in elke kolom. Deze waarden worden schaduwfeatures genoemd. * Vervolgens traint het een classifier, zoals een Random Forest Classifier, op de dataset. Hiermee krijg je een idee van de belangrijkheid — via de Mean Decrease Accuracy of Mean Decrease Impurity — voor elk van de features in je dataset. Hoe hoger de score, hoe beter of belangrijker.
  • Vervolgens controleert het algoritme voor elk van je echte features of die een hogere belangrijkheid hebben. Met andere woorden, of de feature een hogere Z-score heeft dan de maximale Z-score van zijn schaduwfeatures, dus hoger dan de beste schaduwfeature. Als dat zo is, wordt dit vastgelegd in een vector. Dit zijn zogenaamde hits. Daarna gaat het door met een volgende iteratie. Na een vooraf bepaald aantal iteraties eindig je met een tabel met deze hits. Onthoud: een Z-score is het aantal standaarddeviaties waarmee een datapunt van het gemiddelde afligt; voor meer info klik hier.
  • Bij elke iteratie vergelijkt het algoritme de Z-scores van de geschudde kopieën van de features en de oorspronkelijke features om te zien of de laatste beter presteren dan de eerste. Als dat zo is, markeert het algoritme de feature als belangrijk. In essentie probeert het algoritme de belangrijkheid van de feature te valideren door te vergelijken met willekeurig geschudde kopieën, wat de robuustheid vergroot. Dit gebeurt eenvoudig door te vergelijken hoe vaak een feature beter scoorde dan de schaduwfeatures met behulp van een binomiale verdeling.

feature selection R boruta

  • Als een feature bijvoorbeeld in 15 iteraties niet als hit is geregistreerd, verwerp je die en verwijder je hem ook uit de oorspronkelijke matrix. Na een vastgesteld aantal iteraties — of als alle features zijn bevestigd of verworpen — stop je.

Boruta-algoritme in R

Laten we het Boruta-algoritme gebruiken op een van de meest gangbare datasets: de Bank Marketing-data. Deze data vertegenwoordigt direct marketingcampagnes (telefoongesprekken) van een Portugese bank. Het doel van de classificatie is te voorspellen of de klant een termijndeposito zal afsluiten of niet.

Tip: bekijk zeker de gedetailleerde beschrijving van de verschillende features hier.

read_file <- read.csv('./bank_bank.csv',header=TRUE,sep=';',stringsAsFactors = F) #read csv into a dataframe
str(read_file)

## 'data.frame':    4521 obs. of  17 variables:
##  $ age      : int  30 33 35 30 59 35 36 39 41 43 ...
##  $ job      : chr  "unemployed" "services" "management" "management" ...
##  $ marital  : chr  "married" "married" "single" "married" ...
##  $ education: chr  "primary" "secondary" "tertiary" "tertiary" ...
##  $ default  : chr  "no" "no" "no" "no" ...
##  $ balance  : int  1787 4789 1350 1476 0 747 307 147 221 -88 ...
##  $ housing  : chr  "no" "yes" "yes" "yes" ...
##  $ loan     : chr  "no" "yes" "no" "yes" ...
##  $ contact  : chr  "cellular" "cellular" "cellular" "unknown" ...
##  $ day      : int  19 11 16 3 5 23 14 6 14 17 ...
##  $ month    : chr  "oct" "may" "apr" "jun" ...
##  $ duration : int  79 220 185 199 226 141 341 151 57 313 ...
##  $ campaign : int  1 1 1 4 1 2 1 2 2 1 ...
##  $ pdays    : int  -1 339 330 -1 -1 176 330 -1 -1 147 ...
##  $ previous : int  0 4 1 0 0 3 2 0 0 2 ...
##  $ poutcome : chr  "unknown" "failure" "failure" "unknown" ...
##  $ y        : chr  "no" "no" "no" "no" ...

Gebruik de functie summary() om de gebruikelijke beschrijvende statistieken van verschillende features in je dataset samen te vatten.

summary(read_file)

##       age            job              marital           education        
##  Min.   :19.00   Length:4521        Length:4521        Length:4521       
##  1st Qu.:33.00   Class :character   Class :character   Class :character  
##  Median :39.00   Mode  :character   Mode  :character   Mode  :character  
##  Mean   :41.17                                                           
##  3rd Qu.:49.00                                                           
##  Max.   :87.00                                                           
##    default             balance        housing              loan          
##  Length:4521        Min.   :-3313   Length:4521        Length:4521       
##  Class :character   1st Qu.:   69   Class :character   Class :character  
##  Mode  :character   Median :  444   Mode  :character   Mode  :character  
##                     Mean   : 1423                                        
##                     3rd Qu.: 1480                                        
##                     Max.   :71188                                        
##    contact               day           month              duration   
##  Length:4521        Min.   : 1.00   Length:4521        Min.   :   4  
##  Class :character   1st Qu.: 9.00   Class :character   1st Qu.: 104  
##  Mode  :character   Median :16.00   Mode  :character   Median : 185  
##                     Mean   :15.92                      Mean   : 264  
##                     3rd Qu.:21.00                      3rd Qu.: 329  
##                     Max.   :31.00                      Max.   :3025  
##     campaign          pdays           previous         poutcome        
##  Min.   : 1.000   Min.   : -1.00   Min.   : 0.0000   Length:4521       
##  1st Qu.: 1.000   1st Qu.: -1.00   1st Qu.: 0.0000   Class :character  
##  Median : 2.000   Median : -1.00   Median : 0.0000   Mode  :character  
##  Mean   : 2.794   Mean   : 39.77   Mean   : 0.5426                     
##  3rd Qu.: 3.000   3rd Qu.: -1.00   3rd Qu.: 0.0000                     
##  Max.   :50.000   Max.   :871.00   Max.   :25.0000                     
##       y            
##  Length:4521       
##  Class :character  
##  Mode  :character  
##                    
##                    
##  

De functie summary() geeft centrummaten voor continue features, zoals gemiddelde, mediaan, kwantielen, enz. Als je categorische features in je dataset hebt, zie je ook de Class en Mode van die features.

Laten we nu de categorische features omzetten naar het datatype factor:

convert <- c(2:5, 7:9,11,16:17)
read_file[,convert] <- data.frame(apply(read_file[convert], 2, as.factor))
str(read_file)

## 'data.frame':    4521 obs. of  17 variables:
##  $ age      : int  30 33 35 30 59 35 36 39 41 43 ...
##  $ job      : Factor w/ 12 levels "admin.","blue-collar",..: 11 8 5 5 2 5 7 10 3 8 ...
##  $ marital  : Factor w/ 3 levels "divorced","married",..: 2 2 3 2 2 3 2 2 2 2 ...
##  $ education: Factor w/ 4 levels "primary","secondary",..: 1 2 3 3 2 3 3 2 3 1 ...
##  $ default  : Factor w/ 2 levels "no","yes": 1 1 1 1 1 1 1 1 1 1 ...
##  $ balance  : int  1787 4789 1350 1476 0 747 307 147 221 -88 ...
##  $ housing  : Factor w/ 2 levels "no","yes": 1 2 2 2 2 1 2 2 2 2 ...
##  $ loan     : Factor w/ 2 levels "no","yes": 1 2 1 2 1 1 1 1 1 2 ...
##  $ contact  : Factor w/ 3 levels "cellular","telephone",..: 1 1 1 3 3 1 1 1 3 1 ...
##  $ day      : int  19 11 16 3 5 23 14 6 14 17 ...
##  $ month    : Factor w/ 12 levels "apr","aug","dec",..: 11 9 1 7 9 4 9 9 9 1 ...
##  $ duration : int  79 220 185 199 226 141 341 151 57 313 ...
##  $ campaign : int  1 1 1 4 1 2 1 2 2 1 ...
##  $ pdays    : int  -1 339 330 -1 -1 176 330 -1 -1 147 ...
##  $ previous : int  0 4 1 0 0 3 2 0 0 2 ...
##  $ poutcome : Factor w/ 4 levels "failure","other",..: 4 1 1 4 4 1 2 4 4 1 ...
##  $ y        : Factor w/ 2 levels "no","yes": 1 1 1 1 1 1 1 1 1 1 ...

Omdat de datapunten worden geschud om schaduwfeatures te creëren en voor elk daarvan de Z-score wordt berekend, is het belangrijk om ontbrekende of lege waarden te behandelen voordat je het boruta-pakket gebruikt, anders krijg je een foutmelding.

(On)gelukkig heeft deze dataset geen van beide. Maar voor educatieve doeleinden voeg je wat NAs toe aan de data.

Zaai ontbrekende waarden in je dataset met de functie prodNA(). Je hebt toegang tot deze functie door het pakket missForest te installeren.

Onthoud dat je install.packages() kunt gebruiken om ontbrekende pakketten te installeren als dat nodig is!

library(missForest)

# Generate 5% missing values at random
bank.mis <- prodNA(read_file, noNA = 0.05)  

Je kunt opnieuw de functie summary() aanroepen op je nieuwe dataframe om het aantal geïntroduceerde NAs te krijgen, maar laten we het eens iets creatiever aanpakken!

Laten we de ontbrekendheid in de data visualiseren met de volgende ggplot2-code:

library(reshape2)
library(ggplot2)
library(dplyr)
ggplot_missing <- function(x){

  x %>% 
    is.na %>%
    melt %>%
    ggplot(data = .,
           aes(x = Var2,
               y = Var1)) +
    geom_raster(aes(fill = value)) +
    scale_fill_grey(name = "",
                    labels = c("Present","Missing")) +
    theme_minimal() + 
    theme(axis.text.x  = element_text(angle=45, vjust=0.5)) + 
    labs(x = "Variables in Dataset",
         y = "Rows / observations")
}
ggplot_missing(bank.mis)

De witte lijnen in de plot laten je visueel zien dat je ontbrekende waarden in elke feature hebt gezaaid, maar zie je hoeveel werk er in die functie ggplot_missing is gaan zitten? Het Amelia-pakket in R, dat je in de volgende sectie zult gebruiken, biedt een éénregelige alternatieve manier om een soortgelijke figuur te plotten:

library(Amelia)
missmap(bank.mis)

Probeer het zelf eens!

Ontbrekende waarden imputeren met Amelia

Je kunt de ontbrekende waarden nu op verschillende manieren imputeren, zoals imputeren met gemiddelde, mediaan of modus (voor categorische features), maar laten we een ander krachtig pakket gebruiken voor het imputeren van ontbrekende waarden: Amelia.

Amelia neemt m bootstrapsteekproeven en past het EMB-algoritme (expectation-maximization met bootstrapping) toe op elke steekproef. De m schattingen van gemiddelden en varianties zullen verschillen. Uiteindelijk wordt de eerste set schattingen gebruikt om de eerste set ontbrekende waarden te imputeren met regressie, vervolgens wordt de tweede set schattingen gebruikt voor de tweede set, enzovoort. Meervoudige imputatie helpt bias te verminderen en de efficiëntie te vergroten. Daarnaast ondersteunt het parallel imputeren met behulp van multicore CPU’s.

Het heeft 3 belangrijke parameters:

  • m: het aantal te creëren geïmputeerde datasets.
  • idvars: bewaar alle ID-variabelen en andere variabelen die je niet wilt imputeren.
  • noms: zet nominale variabelen hier.

library(Amelia)
amelia_bank <- amelia(bank.mis, m=3, parallel = "multicore",noms=c('job','marital','education','default','housing','loan','contact','month','poutcome','y'))

## -- Imputation 1 --
## 
##   1  2  3  4  5  6
## 
## -- Imputation 2 --
## 
##   1  2  3  4  5  6
## 
## -- Imputation 3 --
## 
##   1  2  3  4  5

Om toegang te krijgen tot de geïmputeerde dataframes, kun je de volgende subsetting gebruiken:

amelia_bank$imputations[[1]]

Om de geïmputeerde dataset te exporteren naar csv-bestanden gebruik je:

write.amelia(amelia_bank, file.stem = "imputed_bank_data_set")

Het Boruta-pakket in R

Laten we nu het Boruta-algoritme gebruiken op een van de geïmputeerde datasets. Je kunt hiervoor het Boruta-pakket gebruiken:

library(Boruta)
set.seed(111)
boruta.bank_train <- Boruta(y~., data = amelia_bank$imputations[[1]], doTrace = 2)
print(boruta.bank_train)

## Boruta performed 99 iterations in 18.97234 mins.
##  10 attributes confirmed important: age, contact, day, duration,
## housing and 5 more;
##  3 attributes confirmed unimportant: education, job, marital;
##  3 tentative attributes left: balance, campaign, default;

Boruta geeft een oordeel over de significantie van features in een dataset. Veel ervan zijn al als belangrijk of onbelangrijk geclassificeerd, maar je ziet dat sommige in de categorie "tentative" zijn geplaatst.

Maar wat betekent dat?

Tentative features hebben een belangrijkheid die zo dicht bij die van hun beste schaduwfeatures ligt dat Boruta met het gewenste vertrouwen geen beslissing kan nemen binnen het standaard aantal Random Forest-runs.

Wat doe je daar dan aan?

Je zou kunnen overwegen om de parameter maxRuns te verhogen als er voorlopige (tentative) features overblijven. Let echter op dat je ook waarden kunt meegeven voor de parameters mtry en ntree, die worden doorgegeven aan de functie randomForest(). Met de eerste geef je het aantal variabelen op dat bij elke splitsing willekeurig als kandidaten wordt gekozen, terwijl je met de tweede het aantal bomen opgeeft dat je wilt groeien. Met deze argumenten zal de Random Forest-classifier convergeren bij een minimale waarde van de out-of-bag-fout.

Onthoud dat een out-of-bag-fout een schatting is om de voorspellingsfout te meten van classifiers die bootstrapaggregatie gebruiken om datamonsters voor training te sub-samplen. Het is de gemiddelde voorspellingsfout op elke trainingswaarneming X met alleen die bomen die X niet in hun bootstrapsteekproef hadden.

Als alternatief kun je het argument doTrace ook op 1 of 2 zetten, zodat je een voortgangsrapport krijgt van het proces.

Het boruta-pakket bevat ook een functie TentativeRoughFix(), die kan worden gebruikt om ontbrekende beslissingen op te vullen door de mediane Z-score van de feature eenvoudig te vergelijken met de mediane Z-score van de belangrijkste schaduwfeature:

#take a call on tentative features
boruta.bank <- TentativeRoughFix(boruta.bank_train)
print(boruta.bank)

## Boruta performed 99 iterations in 18.97234 mins.
## Tentatives roughfixed over the last 99 iterations.
##  12 attributes confirmed important: age, campaign, contact, day,
## default and 7 more;
##  4 attributes confirmed unimportant: balance, education, job,
## marital;

Boruta heeft nu zijn werk gedaan: het heeft elke feature succesvol geclassificeerd als belangrijk of onbelangrijk.

Je kunt nu de variabelebelangrijkheidsgrafiek van Boruta plotten door plot(boruta.bank) aan te roepen. De labels op de x-as zullen echter horizontaal staan. Dat oogt niet heel netjes.

Daarom voeg je de featurelabels verticaal toe aan de x-as, zoals in het volgende codeblok:

plot(boruta.bank, xlab = "", xaxt = "n")
lz<-lapply(1:ncol(boruta.bank$ImpHistory),function(i)
boruta.bank$ImpHistory[is.finite(boruta.bank$ImpHistory[,i]),i])
names(lz) <- colnames(boruta.bank$ImpHistory)
Labels <- sort(sapply(lz,median))
axis(side = 1,las=2,labels = names(Labels),
at = 1:ncol(boruta.bank$ImpHistory), cex.axis = 0.7)

Het label Importance op de y-as staat voor de Z-score van elke feature in de geschudde dataset.

De blauwe boxplots komen overeen met de minimale, gemiddelde en maximale Z-score van een schaduwfeature, terwijl de rode en groene boxplots respectievelijk de Z-scores van verworpen en bevestigde features weergeven. Zoals je ziet hebben de rode boxplots een lagere Z-score dan de maximale Z-score van de schaduwfeature, wat precies de reden is dat ze in de categorie onbelangrijk zijn geplaatst.

Je kunt de belangrijkheid van de features bevestigen door te typen:

getSelectedAttributes(boruta.bank, withTentative = F)

##  [1] "age"      "default"  "housing"  "loan"     "contact"  "day"     
##  [7] "month"    "duration" "campaign" "pdays"    "previous" "poutcome"

bank_df <- attStats(boruta.bank)
print(bank_df)

##              meanImp  medianImp     minImp    maxImp   normHits  decision
## age       11.4236197 11.3760979  8.4250222 15.518420 1.00000000 Confirmed
## job        0.0741753  0.3002281 -1.7651336  1.566687 0.01010101  Rejected
## marital    1.8891283  2.0043568 -1.0276720  4.804499 0.22222222  Rejected
## education  1.5969540  1.6188117 -1.6836346  4.629572 0.28282828  Rejected
## default    2.3721979  2.3472820 -0.1434933  5.044653 0.50505051 Confirmed
## balance    2.3349682  2.3214378 -0.8098151  5.567993 0.51515152  Rejected
## housing    8.4147808  8.4384240  4.7392059 10.404609 1.00000000 Confirmed
## loan       4.1872186  4.2797591  2.0325838  6.263155 0.87878788 Confirmed
## contact   18.9482180 18.9757719 16.0937657 22.121461 1.00000000 Confirmed
## day        9.5645192  9.5828766  6.1842233 13.495442 1.00000000 Confirmed
## month     24.1475736 24.2067940 20.0621966 27.200679 1.00000000 Confirmed
## duration  71.5232213 71.1785055 64.3941499 78.249830 1.00000000 Confirmed
## campaign   2.6221456  2.6188180 -0.4144493  4.941482 0.65656566 Confirmed
## pdays     26.5650528 26.7123730 23.7902945 29.067476 1.00000000 Confirmed
## previous  20.9569022 20.9703991 18.7273357 23.117672 1.00000000 Confirmed
## poutcome  28.5166889 28.4885934 25.9855974 31.527154 1.00000000 Confirmed

Je kunt het resultaat eenvoudig valideren, want de feature duration heeft de hoogste belangrijkheid gekregen, wat ook in de databeschrijving wordt vermeld (klik hier) als je goed leest!

Conclusie

Voilà! Je hebt de belangrijkste features uit je dataset gefilterd door slechts een paar regels code te typen. Hiermee heb je ruis uit je data verwijderd, wat erg gunstig is voor elke classifier om een label aan een observatie toe te kennen. Een model trainen op deze belangrijke features zal de prestaties van je model zeker verbeteren — en dat was precies het doel van featureselectie!

Wil je de bronnen bekijken die voor deze tutorial zijn gebruikt? Kijk dan hiernaar:

Onderwerpen
R
Data Science
Machine Learning

R-cursussen

Cursus

Introductie tot R

4 uur
3.1M
Beheers de basis van data-analyse in R, inclusief vectors, lists en data frames, en oefen met echte datasets.
Bekijk detailsRight Arrow
Start Cursus
Meer bekijkenRight Arrow
Gerelateerd

blog

AI vanaf nul leren in 2026: een complete gids van de experts

Ontdek alles wat je moet weten om in 2026 AI te leren, van tips om te beginnen tot handige resources en inzichten van industrie-experts.
Adel Nehme's photo

Adel Nehme

15 min

Meer BekijkenMeer Bekijken