R : package Microbenchmark

Comment utiliser Microbenchmark ?

Nous avons tous déjà challengé différents codes avec la fonction system.time(), mais lorsqu’on possède beaucoup de versions cela peut vite devenir laborieux. Je vous propose ici une présentation rapide du package microbenchmark de R qui permet de benchmarker plusieurs codes donnant le même résultat via la fonction microbenchmark. Pour aller plus loin, je vous propose de parcourir le livre Advanced R de Hadley Wickham.

Un exemple simple pour commencer

Je commence d'ailleurs avec un premier exemple tiré de son livre. Ici nous souhaitons tester la fonction sqrt de R avec la puissance ^0.5 :

library(microbenchmark)
x <- runif(100)
microbenchmark(
  sqrt(x),
  x ^ 0.5
)

Par défaut, microbenchmark exécute chaque argument 100 fois pour avoir un aperçu moyen de la durée de chaque évaluation.

Résultats :

Unit: nanoseconds
    expr  min   lq    mean median     uq   max neval cld
 sqrt(x)  535  678 1246.93    733  802.5 41900   100  a 
   x^0.5 6803 6939 8106.47   7026 7133.5 66989   100   b

La fonction nous renvoie donc des statistiques pour chaque expression, ici en nanosecondes. En moyenne, sqrt prend 1247 nanoseconds contre 8106 pour ^0.5. Autrement dit, la fonction sqrt est plus rapide.

Autre exemple

Cet exemple se base sur le dataset public mtcars de R. Pour chaque véhicule, la colonne cyl représente le nombre de cylindres et la colonne hp le nombre de chevaux de la voiture.

> head(mtcars)
                   mpg cyl disp  hp drat    wt  qsec vs am gear carb
Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

Nous souhaitons par exemple connaître le nombre de chevaux disponibles par cylindre. Nous voulons comparer la performance de différentes façons d'agréger la donnée avec aggregate, summarise et la méthode utilisant les data.table (un post sera dédié à l'utilisation de data.table, n'hésitez pas à utiliser le tag R). Ensuite, nous appliquons une fonction de count unique, comme par exemple length(unique(x)), n_distinct(x) et uniqueN(x). Nous paramétrons le times à 1000 pour tester 1000 fois les expressions.

library(dplyr)
library(data.table)
library(microbenchmark)

df <- mtcars
DT <- data.table(mtcars)
bench <- microbenchmark(
  df_aggregate_1 = aggregate(hp ~ cyl, df, function(x) length(unique(x))),
  df_aggregate_2 = aggregate(hp ~ cyl, df, function(x) n_distinct(x)),
  df_aggregate_3 = aggregate(hp ~ cyl, df, function(x) uniqueN(x)),
  df_summarise_1 = summarise(group_by(df, cyl), count = length(unique(hp))),
  df_summarise_2 = summarise(group_by(df, cyl), count = n_distinct(hp)),
  df_summarise_3 = summarise(group_by(df, cyl), count = uniqueN(hp)),
  dt_1 = DT[, .(count = length(unique((hp)))), by = cyl],
  dt_2 = DT[, .(count = n_distinct(hp)), by = cyl],
  dt_3 = DT[, .(count = uniqueN(hp)), by = cyl],
  times = 1000
)

Pour visualiser le résultat, il suffit de faire un print(bench) comme ceci :

> print(bench)
Unit: microseconds
           expr      min        lq      mean   median       uq        max neval  cld
 df_aggregate_1 1017.966 1203.4595 1386.1786 1280.923 1391.550   4835.111  1000  b  
 df_aggregate_2 1040.142 1230.6290 1410.0126 1304.896 1413.175   6286.693  1000  b  
 df_aggregate_3 1054.525 1250.2575 1438.7838 1332.216 1438.997   9552.352  1000  b  
 df_summarise_1 3439.566 3816.0470 4548.3793 4023.416 4568.558 131784.589  1000    d
 df_summarise_2 2553.854 2855.6680 3294.5842 2996.410 3253.824  12839.488  1000   c 
 df_summarise_3 3486.614 3833.5275 4459.8723 4043.593 4622.347  10376.833  1000    d
           dt_1  698.422  853.7490  993.6400  921.424 1007.428   5586.873  1000 a   
           dt_2  629.399  778.8830  888.8659  841.413  918.028   5157.252  1000 a
           dt_3  635.093  784.6265  925.3732  854.199  939.204   4254.658  1000 a

Nous pouvons donc en conclure que l'agrégation via data.table avec la fonction n_distinct serait la plus appropriée pour avoir un script rapide avec un temps médian d'exécution de 840 µs correspondant au dt_2.

Il est également possible d'afficher un graph des résultats :

library(ggplot2)
autoplot(bench)

microbenchmark



Nous sommes maintenant capable de choisir l'expression R la moins coûteuse en temps. Dans un prochain post, nous verrons comment améliorer le temps d'exécution d'un script en le parallélisant sur plusieurs processeurs de votre machine.