Versus | Native | gtools | % faster
---------- | ------ | ------ | -------- collapse | 1.53 | 1.25 | 18.51%
collapse | 1.68 | 1.17 | 29.91%
reshape | 31.63 | 6.90 | 78.19%
reshape | 60.26 | 10.95 | 81.83%
xtile | 17.74 | 1.12 | 93.67%
pctile | 18.20 | 0.77 | 95.76%
egen | 2.13 | 0.64 | 69.77%
contract | 4.52 | 1.74 | 61.54%
isid | 18.71 | 0.68 | 96.35%
duplicates | 10.07 | 0.86 | 91.42%
levelsof | 2.75 | 0.44 | 83.94%
distinct | 7.24 | 0.44 | 93.88%
winsor | 16.09 | 0.65 | 95.99%
sum_detail | 17.09 | 1.22 | 92.86% tabstat | 11.18 | 0.67 | 94.03%
range_stat | 67.37 | 2.82 | 95.81%
Améliorer la durée d’exécution avec Gtools
Le package gtools
de Mauricio Caceres Bravo permet d’améliorer significativement la durée d’exécution pour un certain nombre d’opérations, en particulier les transpositions de bases (reshape
). Les éléments qui suivent proposent des éléments de benchmark avec les commandes usines et des fonctions équivalentes sous R.
- Stata 17: grosse amélioration du temps d’exécution de certaines commandes, en particulier
sort
etcollapse
. - Depuis de nombreuses années des packages ou commandes standalone amélioraientt le temps d’exécution, en particulier les packages
ftools
de Sergio Correa ou les commandesfastxtile
/fastwpctile
d’egenmisc
. - Le package
gtools
de Mauricio Caceres Bravo donne des résultats vraiment très intéressants lorsqu’on atteint un seuil d’un million d’observations pour les commandes suivantes:greshape
,gquantiles
,gegen
,glevelof
avec une variable caractère, et dans une moindre mesuregcollapse
. - Au delà des durées d’exécution, ces packages et commandes peuvent avoir quelques options propres, par exemple, l’option
by()
degquantiles
ou la possibilité d’enregistrer les valeurs en ordre décroissant avecglevelsof
.
Benchmarks
- Version Stata 17 SE. Les benchmarks réalisés par Mauricio Caceres sont en version MP.
- Configuration PC: i5-10210U CPU et 16GO de RAM.
- Volumétries: 10k, 100k, 1M, 10M.
- Comparaisons avec R si fonctions équivalentes.
- Programme Stata: programme
- To do: faire les tests sur la version serveur-linux (toujours Stata 17 SE)
Sources:
- Stata 17 faster: https://www.stata.com/new-in-stata/faster-stata-speed-improvements/
- ftools: https://github.com/sergiocorreia/ftools
- gtools:
Le package
Auteur: Mauricio Caceres Bravo
Installation:
Les Benchmarks réalisés par l’auteur ont été exécutés avec Stata MP. J’ai fait tourné son programme (lien) avec Stata 17 SE sous windows. Les résultats sont les suivants:
Pour mon propre benchmark, plus gourmand (10 variables quanti et une variable binaire), les données sont générées de la manière suivante:
Création de la base de données (N=10M)
clear
set obs 10000000
tempvar x
gen `x' = runiform()
gen g = `x'>.5
forv i=1/10 {gen y`i' = rnormal()
gen id = _n
}
Pour récupérer les durées d’exécution, j’utilise un fragment du programme de M.Caceres. Les commandes sont exécutées avec le prefixe bench 1:
capture program drop bench
program bench
timer call: 0, p(:)
gettoken p(:)
gettoken colon call: call, timer clear `timer'
cap timer on `timer'
`call'
timer off `timer'
qui timer list
`timer' `=r(t`timer')'
c_local rend
- Les tests sont réalisés avec les équivalents de
xtile
,reshape
,collapse
etlevelsof
. L’équivalent àtabstat
sera ajouté rapidement. - Pour information, les programmes des fonctions R sont également rapidement décris. Les durées d’exécution ont été récupérés avec la librairie
tictoc
.
gquantiles
Commande usine
xtile
etpctile
(help xtile
). Le benchmark est seulement effectué pourxtile
(affectation d’un quantile à une valeur) qui est plus gourmant quepctile
(calcul et report des quantiles).En termes d’options, l’autre intérêt de
gquantile
est de stratifier l’opération avec l’optionby()
.
Syntaxe courte
xtile
*xtile nq(#) [by(var2)]
gquantiles nouvelle_var = var1 ,
pctile
*pctile nq(#) [by(var2)] gquantiles nouvelle_var = var1 ,
Programme
* Fonction bench (voir plus haut)
qui forv i=1/10 {
** XTILE
tempvar yg`i'
xtile `yg`i'' = y`i' , nq(10)
bench 1: local rt1 = `rt1' + `r1'
}di "XTILE runtime =" `rt1'
*** GQUANTILESqui forv i=1/10 {
drop `yg`i''
capt tempvar yg`i'
`yg`i'' = y`i' , xtile nq(10)
bench 1: gquantiles local rt2 = `rt2' + `r1'
}di "GQUANTILES runtime =" `rt2'
Résultats (secondes)
Stata | 10k | 100k | 1M | 10M |
---|---|---|---|---|
xtile | 0.12 | 1.65 | 16.03 | 196.56 |
gquantiles | 0.06 | 0.22 | 1.24 | 14.75 |
R | 10k | 100k | 1M | 10M |
---|---|---|---|---|
quantcut | 0.04 | 0.24 | 2.38 | 29.11 |
ntile | 0.06 | 0.16 | 1.54 | 15.51 |
greshape
- Niveau syntaxe peu de différence avec la commande usine, si ce n’est pour les arguments
i()
etj()
i()
=id()
j()
=key()
- Pour R:
- Fonction de base
reshape
.- Avantage: syntaxe très proche de Stata
- Inconvénients: temps d’exécution pas optimal. Pour 10M d’observations, j’ai arrêté l’exécution au bout de 10 minutes.
- Fonctions
pivot_longer
etpivot_wider
de **tydir
.
- Fonction de base
Si greshape
est nettement plus performant que reshape
, il reste nettement en deçà des deux fonctions de la librairie tydir
de R.
Programme
* Fonction bench (voir plus haut)
**RESHAPEqui bench 1: reshape long y, i(id) j(j)
di "RESHAPE LONG runtime =" `r1'
qui bench 1: reshape wide y, i(id) j(j)
di "RESHAPE WIDE runtime =" `r1'
**GRESHAPEqui bench 1: greshape long y, by(id) keys(j)
di "GRESHAPE LONG runtime =" `r1'
qui bench 1: greshape wide y, by(id) keys(j)
di "GRESHAPE WIDE runtime =" `r1'
Résultats (secondes)
Stata | 10K | 100k | 1M | 10M |
---|---|---|---|---|
reshape long | 0.14 | 1.22 | 12.36 | 245.18 |
greshape long | 0.04 | 0.21 | 3.22 | 61.23 |
R | 10k | 100k | 1M | 10M |
---|---|---|---|---|
reshape | 0.1 | 1.19 | 11.9 | /// |
pivot_longer | 0.01 | 0.12 | 0.6 | 13.39 |
Stata | 10k | 100k | 1M | 10M |
---|---|---|---|---|
reshape wide | 0.37 | 2.18 | 26.58 | 338.10 |
greshape wide | 0.06 | 0.30 | 2.79 | 55.86 |
R | 10k | 100k | 1M | 10M |
---|---|---|---|---|
reshape | 0.37 | 3.69 | 34.93 | /// |
pivot_wider | 0.01 | 0.24 | 1.98 | 38.97 |
gcollapse
- Syntaxe identique à celle de
collapse
. Par défaut, c’est également la moyenne qui est calculée. - Ajout d’une option
merge
replace
qui remplace la valeur des observations par l’indicateur séléctionné. - On ajouté l’option
by()
sur la variable g (deux groupes).
Programme
*** COLLAPSEpreserve
qui bench 1: collapse y1-y10, by(g)
local col `r1'
restore
*** GCOLLAPSEpreserve
qui bench 1: gcollapse y1-y10, by(g)
local gcol `r1'
restore
di "N=`N"
di "COLLAPSEruntime =" `col'
di "GCOLLAPSEruntime =" `gcol'
Résultats (secondes)
Stata | 10K | 100K | 1M | 10 M |
---|---|---|---|---|
collapse | 0.007 | 0.041 | 0.461 | 7.846 |
gcollapse | 0.021 | 0.049 | 0.219 | 2.559 |
R | 10K | 100K | 1M | 10 M |
summarise | 0.03 | 0.06 | 0.3 | 1.91 |
Note: pour Stata le programme exécute preserve
/restore
, ce qui augmente légèrement un temps d’exécution
Programme
gegen
- Syntaxe identique à celle d’
egen
. On a choisi comme fonction la moyenne. - On ajouté l’option
by()
sur la variable g (deux groupes).
forv i=1/10 {qui bench 1: egen my`i' = mean(y`i'), by(g)
local egen = `egen' + `r1'
}
drop my*
forv i=1/10 {qui bench 1: gegen my`i' = mean(y`i'), by(g)
local gegen = `gegen' + `r1'
}
di "N=`N"
di "EGEN runtime =" `egen'
di "GEGEN runtime =" `gegen'
Stata | 10k | 100k | 1M | 10M |
---|---|---|---|---|
egen | 0.23 | 0.41 | 4.82 | 73.6 |
gegen | 0.69 | 0.20 | 0.83 | 8.88 |
R | 10k | 100k | 1M | 10M |
mutate + mean | 0.03 | 0.05 | 0.17 | 1.74 |
glevelsof
glevelsof
- Autorise plusieurs variables. la macro enregistrée concaténera les valeurs et/ou expression avec un séparateur (espace par défaut).
- Permet de trier les valeurs en ordre décroissant en ajoutant - devant le nom de la variable.
benchmark
- Bien évidemment, pas de comparaison possible avec R
- Programme d’origine différent: on va générer une variable qui affecte aléatoirement une lettre de l’alphabet (une version caractère et une version numérique générée avec
encode
). Le programme a été écrit par Paul Picard sur le forum Statalist (lien)
clear
set obs 10000
local c2use ABCDEFGHIJKLMNPQRSTUVWXYZ
gen random_string = substr("`c2use'", runiformint(1,length("`c2use'")),1) + ///
string(runiformint(0,9)) + ///
char(runiformint(65,90)) + ///
char(runiformint(65,90)) + ///
string(runiformint(0,9)) + ///
char(runiformint(65,90))
gen xchar = substr(random_string,1,1)
encode xchar, gen(xnum)
drop random_string
Levelsof :
levelsof xchar
/*
`"A"' `"B"' `"C"' `"D"' `"E"' `"F"' `"G"' `"H"' `"I"' `"J"' `"K"' `"L"' `"M"' `"N"' `"P"' `"Q"' `
> "R"' `"S"' `"T"' `"U"' `"V"' `"W"' `"X"' `"Y"' `"Z"'
*/
levelsof xnum
/*
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
*/
Glevelsof avec valeurs enregistrées en ordre décroissant:
glevelsof -xchar
`"Z"' `"Y"' `"X"' `"W"' `"V"' `"U"' `"T"' `"S"' `"R"' `"Q"' `"P"' `"N"' `"M"' `"L"' `"K"' `"J"'
` "I"' `"H"' `"G"' `"F"' `"E"' `"D"' `"C"' `"B"' `"A"'
glevelsof -xnum
25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
Variable caractère | 10k | 100k | 1M | 10M |
---|---|---|---|---|
levelsof | 0.01 | 0.10 | 2.64 | 42.51 |
glevelsof | 0.01 | 0.01 | 0.11 | 0.62 |
Variable numerique | 10k | 100k | 1M | 10M |
---|---|---|---|---|
levelsof | 0.01 | 0.01 | 0.09 | 1.04 |
glevelsof | 0.00 | 0.01 | 0.04 | 0.32 |