Stata : programmation
Table des matières
Ce chapitre est consacré aux techniques de programmation avec le langage Stata, et dans une moindre mesure Mata. L'ouvrage de référence demeure l'ouvrage de Mitchell [1] pour tout ce qui relève de l'automatisation à l'aide de commandes Stata pour la gestion de données, ainsi que l'ouvrage de Baum sur la programmation en elle-même [2]. Germán Rodríguez propose également un excellent tutoriel sur son site internet. Concernant la création de commande et l'usage de Mata, l'ouvrage de Bill Gould [3].
Programmation avec Stata
Les macros Stata
Ce que l'on appelle "variable" dans la plupart des langages de programmation correspond au terme "macro" dans le langage Stata. Une variable reste définie dans le sens statistique et donc correspond aux colonnes d'un tableau de données. Quant aux "macros", on en distingue plusieurs types, selon leur nature ou leur portée.
Stata permet de stocker temporairement ou définitivement des valeurs calculées ou retournées par une procédure d’estimation. On parlera de macros au sens large. En voici une illustration avec le jeu de données sur les automobiles :
sysuse auto, clear quietly summarize mpg, meanonly display r(mean)
set more off sysuse auto, clear (1978 Automobile Data) quietly summarize mpg, meanonly display r(mean) 21.297297
La command summarize stocke temporairement la moyenne (mean
) de la variable (ou dans le cas d'une liste de variables, de la dernière variable de la liste) en mémoire et il est possible d'obtenir la valeur de retour stockée dans r()
. Ce type de données peut être stocké globalement (accessible depuis l’espace de travail, les programmes, etc.) ou localement, ou, comme dans l'exemple ci-dessus, affiché à l'écran.
Macros locales
Une macro locale se définit comme une expression : local name [=] text
. Une macro locale permet de stocker sous forme de chaîne de caractères une expression (ou son résultat après évaluation dans le cas ou on utilise le signe =). Voici un exemple d'utilisation :
quietly summarize mpg, meanonly local m1 r(mean) display `m1'
quietly summarize mpg, meanonly local m1 r(mean) display `m1' 21.297297
Attention, l'utilisation de m
comme nom de macro pourrait dans le cas de ce jeu de données prêter à confusion.
L'ajout du symbole = dans l'expression ci-dessus permet de stocker la valeur de retour après évaluation :
quietly regress mpg weight local r2 = e(r2) display `r2'
quietly regress mpg weight local r2 = e(r2) 65153125
L’omission du signe = aurait pour conséquence de produire des résultats variable après l’estimation d’un nouveau modèle de régression. Après une instruction telle que local r2 e(r2)
, c’est la formule e(r2)
qui est stockée et non son résultat.
Il existe également une autre manière de définir des macros locales : scalar. Essentiellement, les deux macros permettent de stocker des valeurs pour une utilisation future. Elles se distinguent sur les points suivants : (1) les scalars sont enregistrés globalement, et non localement, d’où un risque potentiel de conflit de nom [4] ; (2) Stata stocke des valeurs sans perte de précision dans les scalars et non une représentation sous forme de chaîne de caractères (environ 8 chiffres significatifs).
Un usage classique des macros locales consiste à enregistrer une liste de cofacteurs présents dans tous les modèles de régression dont on souhaite estimer les paramètres. Plutôt que de répéter systématiquement dans les modèles une même série de variables explicatives, il est souvent plus judicieux et plus économique de les stocker dans une macro, comme illustré ci-après :
webuse lbw local cofactors age smoke quietly : regress bwt `cofactors' ht estimates store m1 quietly : regress bwt `cofactors' ht i.race estimates store m2 estimates table m*
webuse lbw (Hosmer & Lemeshow data) local cofactors age smoke quietly : regress bwt `cofactors' ht estimates store m1 quietly : regress bwt `cofactors' ht i.race estimates store m2 estimates table m* ---------------------------------------- Variable | m1 m2 -------------+-------------------------- age | 10.879857 1.9908056 smoke | -274.59499 -421.5168 ht | -424.06199 -382.82908 | race | black | -419.8032 other | -445.11538 | _cons | 2825.8965 3302.911 ----------------------------------------
Variables catégorielles
levelsof race, local(lvls) local items = `r(r)'
levelsof race, local(lvls) 1 2 3 local items = `r(r)' invalid syntax r(198);
Consulter également les valeurs retournées par return list
.
Gestion des dates
local date: display %tdd_m_yy date(c(current_date), "DMY") display "`date'" // local date: display %tdd_m_yy `r(max)' // display "`date'" local date2 = subinstr(trim("`date'"), " ", "_", .) display "`date2'"
local date: display %tdd_m_yy date(c(current_date), "DMY") display "`date'" 13 May 2222 local date2 = subinstr(trim("`date'"), " ", "_", .) display "`date2'" 13_May_2222
Voir aussi
// local date = string(date(c(current_date), "DMY"), "%tdd!_m!_Y") // local date = string(date(c(current_date), "DMY"), "%tdCCYYNNDD") local date = string(date(c(current_date), "DMY"), "%tdCYND") display "`date'"
local date = string(date(c(current_date), "DMY"), "%tdCYND") display "`date'" 20220513
Compteurs et boucles
On rappelle que pour la sélection critériée d'observation dans un tableau de données, le qualificateur if est l'un des plus utiles. Voici par exemple une instruction typique : list [varlist] [if] [in] [, options]
, ainsi que quelques illustrations :
sysuse auto, clear list price if mpg <= 13 list price if inrange(mpg, 12, 13) list price if inlist(mpg, 13, 16, 18, 19) & foreign == 1
sysuse auto, clear (1978 Automobile Data) list price if mpg <= 13 +--------+ | price | |--------| 26. | 11,497 | 27. | 13,594 | +--------+ list price if inrange(mpg, 12, 13) +--------+ | price | |--------| 26. | 11,497 | 27. | 13,594 | +--------+ list price if inlist(mpg, 13, 16, 18, 19) & foreign == 1 +-------+ | price | |-------| 67. | 5,899 | 69. | 5,719 | +-------+
Les instructions inrange et inlist permettent de construire des listes régulières ou irrégulières de valeurs numériques. Ce principe de sélection s’applique également aux commandes de résumé numérique (summarize, tabulate) ou de modélisation (regress).
Les différents types de boucles
En dehors des utilitaires classiques de branchement conditionnel (if/else) ou d’itération (while) détaillé dans la section suivante, Stata permet de construire des boucles à partir de nombres ou d’éléments d’une liste. Les deux commandes clés sont :
- forvalues : boucle sur une séquence régulière de nombres entiers
- foreach : boucle sur une séquence d’éléments pris dans une liste (nombres, texte ou variable)
forvalues index = 1/3 { display `index' }
forvalues index = 1/3 { 2. display `index' 3. } 1 2 3
Le principe de la syntaxe est simple bien qu'il fasse faire bien attention aux accolades, en particulier la dernière qui doit figurer seule sur une ligne : forvalues macro locale = séquence {
. Pour construire une séquence régulière de nombres, on utilise l’une des formes suivantes : (1) min/max
(comme dans le cas de list in
) : 1/3
devient ainsi la séquence 1 2 3
; (2) first(step)last
: 10(5)25
devient 10 15 20 25
.
Voici une application simple qui consiste à discrétiser une variable numérique. La commande tabulate dispose de l’option generate
qui per- met de générer des variables indicatrices (à utiliser après xtile, egen cut ou autocode()). Pour créer des indicatrices, on peut utiliser une boucle comme suit :
forvalues low = 12(8)42 { local high = `low' + 7 gen mpg`low'to`high' = mpg >= `low' & mpg <= `high' } list mpg* in 1/3
forvalues low = 12(8)42 { 2. local high = `low' + 7 3. gen mpg`low'to`high' = mpg >= `low' & mpg <= `high' 4. } list mpg* in 1/3 +-------------------------------------------------+ | mpg mpg12~19 mpg20~27 mpg28~35 mpg36~43 | |-------------------------------------------------| 1. | 22 0 1 0 0 | 2. | 17 1 0 0 0 | 3. | 22 0 1 0 0 | +-------------------------------------------------+
La syntaxe générique est dans ce cas foreach macro locale in liste {
. Les élements de la liste peuvent être du texte (ne pas oublier les quotes) ou des nombres :
foreach v in one two three { display "`v'" } foreach num in 1 1 2 3 5 { display `num' }
foreach v in one two three { 2. display "`v'" 3. } one two three foreach num in 1 1 2 3 5 { 2. display `num' 3. } 1 1 2 3 5
L'instruction foreach fonctionne également avec des listes et l'on distingue eux principaux cas de figure :
foreach macro locale of numlist liste { ... }
: Peu de différence avec l’instruction foreach générique, mais Stata vérifiera que la liste contient exclusivement des nombres. La liste de nombres peut être une séquence construite comme dans le cas de forvalues.foreach macro locale of varlist liste { ... }
: Stata vérifiera que les variables figurant dans la liste existent bien, et on peut utiliser les abréviations habituelles (var*
ouvar1-var3
) pour les varlist.
Voici un autre exemple d'application dans lequel on cherche à renommer un ensemble de variables :
xtile weightc = weight, nq(4) quietly tabulate weightc, gen(weightc) drop weightc local i = 0 foreach v of varlist weightc1-weightc4 { local i = `i' + 1 rename `v' w`i' } list w* in 1/5
xtile weightc = weight, nq(4) quietly tabulate weightc, gen(weightc) drop weightc local i = 0 foreach v of varlist weightc1-weightc4 { 2. local i = `i' + 1 3. rename `v' w`i' 4. } list w* in 1/5 +----------------------------+ | weight w1 w2 w3 w4 | |----------------------------| 1. | 2,930 0 1 0 0 | 2. | 3,350 0 0 1 0 | 3. | 2,640 0 1 0 0 | 4. | 3,250 0 0 1 0 | 5. | 4,080 0 0 0 1 | +----------------------------+
Dans la même veine, on peut imaginer : le recodage en masse des niveaux d’une liste de variable (recode), la gestion des données manquantes (mvdecode), etc. Un autre exemple consiste à générer des graphiques avec des instructions non "byable", l'annotation d'une liste de variables, ou enfin le nommage automatique de graphiques :
foreach x of varlist var1-var5 { local v : var label `x' twoway line yvar xvar, title("`v'") }
Structures de contrôle
Les structures de branchement conditionnels telles qu'une instruction if/else peuvent être utilisées directement à l'intérieur d'une boucle foreach, par exemple, ou dans un script do de contrôle de qualité, en conjonction avec assert. Dans la majorité des cas, il convient toutefois de garder en tête la façon dont Stata facilite le travail sur les variables sans avoir à gérer soi-même les boucles d'itération sur les observations (by, recode, egen, etc.).
Attention à ne pas confondre le qualificateur if avec l'instruction de test Stata ou Mata correspondante. L'expression sum foreign if price > 10000
ne fournira pas le même résultat que :
if price > 10000 { sum foreign }
Usage de cond [kantor-2005-depen]
sysuse auto, clear gen v1 = mpg > 20 gen v2 = !inrange(mpg, 0, 20) gen v3 = cond(mpg > 20, 1, 0) recode mpg (0/20 = 0) (21/. = 1), gen(v4) gen v5 = irecode(mpg, 0, 20, .)
sysuse auto, clear (1978 Automobile Data) gen v1 = mpg > 20 gen v2 = !inrange(mpg, 0, 20) gen v3 = cond(mpg > 20, 1, 0) recode mpg (0/20 = 0) (21/. = 1), gen(v4) (74 differences between mpg and v4) gen v5 = irecode(mpg, 0, 20, .)
Automatisation
Gestion d'un package
Références
[1] | M. N. Mitchell, Data Management Using Stata: A Practical Handbook. Stata Press, 2010. |
[2] | C. F. Baum, An Introduction to Stata Programming. Stata Press, 2009. |
[3] | W. Gould, The Mata Book: A Book for Serious Programmers and Those Who Want to Be. Stata Press, 2018. |
[4] | G. Kolev, “Scalar or variable? the problem of ambiguous names,” The Stata Journal, vol. 6, no. 2, pp. 279--280, 2006. |