4 Programar ado - files
4.0.1 Escribir programas en Stata
En esta sección aprenderemos a escribir nuestros propios comandos en Stata. Escribir programas en Stata tiene muchas ventajas relacionadas con las buenas practicas que vimos en la Sección 1.
Vamos a utilizar program para escribir los programas. Es posible equipar cualquier comando con las opciones típicas usuales (ej. if o inrange. Discutiremos mayormente programas del tipo r-class y algunos comentarios con respecto a los del tipo e-class.
¿Por qué debiese escribir mis propios programas en Stata? Automatizar procesos que se ejecutan frecuentemente y donde los resultados dependen de algún tipo de heterogeneidad.
4.0.2 ¿Por qué escribir programas?: Abstracción
- Abstraer para eliminar pasos redundantes.
- Abstraer con fines de hacer códigos más claros. No por otras razones.
- Abstracción es esencial para escribir un buen código por al menos dos razones:
- Al eliminar la redundancia se reducen las posibilidades de cometer errores.
- Aumenta la claridad. Para cualquier lector será más facil leer un código no redundante.
Veamos un ejemplo: Supongamos que queremos ver la correlación espacial del consumo de papas fritas. Queremos testear si el consumo per-capita de papas fritas esta correlacionado con el consumo promedio percapita de las otras comunas de la misma región. Primero tenemos que calcular el consumo per-capita del resto:
egen total_pc_papitas = total(pc_papitas), by(region)
egen total_obs = count(pc_papitas), by(region)
gen consumo_papitas_resto_pc = ///
(total_pc_papitas - pc_papitas)/(total_obs - 1)Ahora podemos ver si existe correlación. ¿Pero si queremos cambiar el nivel de agregación? Tal vez si existe correlación, pero a nivel de área metropolitana. Copiemos el código de nuevo y calculemos esto.
egen total_pc_papitas = total(pc_papitas), by(metroarea)
egen total_obs = count(pc_papitas), by(region)
gen consumo_papitas_restometro_pc = ///
(total_pc_papitas - pc_papitas)/(total_obs - 1)Noten que hay un error. Se nos olvido remplazar región por metroarea. Este error se puede propagar si seguimos haciendo operaciones. Una alternativa al copiar y pegar es escribir una función con propósito general que calcule la variable que deseamos bajo distintos parámetros.
program consumo_papitas_resto
syntax, invar(varname) outvar(name) byvar(varname)
tempvar tot_invar count_invar
egen `tot_invar' = total(`invar'), by(`byvar')
egen `count_invar' = count('invar'), by('byvar')
gen `outvar' = (`tot_invar' - `invar') ///
/ (`count_invar' - 1)
end Con el programa podemos escribir los bloques de código anteriores como:
* Caso 1
consumo_papitas_resto, invar(pc_papitas) ///
outvar(consumo_papitas_resto_pc) byvar(region)
* Caso 2
consumo_papitas_resto, invar(pc_papitas) ///
outvar(consumo_papitas_restometro_pc) byvar(metroarea)Hemos escrito la función de forma totalmente general. Podemos cambiar el nivel de agregación sin inducir errores.
Estructura de un programa en Stata: La sintaxis más simple es:
program nombredelprograma
display "Lo que va a hacer el programa"
end Cuando se ha definido un programa con program, este se vuelve indistinguible de cualquier otro comando de Stata. Es importante estar seguros(as) de que no estoy escribiendo el mismo nombre que otro programa. Para garantizar lo anterior, es bueno utilizar el comando which.
which tabmissGuardar un programa en Stata
- Hay dos lugares en donde puedes guardar tus
ado-files. - Cuando se ha definido un programa con
program, este se vuelve indistinguible de cualquier otro comando de Stata. - En el directorio de trabajo del proyecto.
- Se puede hacer una carpeta nueva en la sección de códigos que indique los programas.
- Otra opción es guardarlo en el directorio
Personalde Stata. Para entrar escribanpersonalen la consola.
Nombrar un programa en Stata
- Es posible darle cualquier nombre a un programa mientras no sea un nombre que ya es utilizado por Stata.
- Si por ejemplo, creas un programa llamado
summarizeStata lo va a ignorar y utilizará su propio comando.
4.0.3 Mis primeros programas en Stata
4.0.3.1 Programa 1
Vamos escribir nuestro primer programa:
* Mi primer programa en Stata
program minombrees
display "Hola, mi nombre es "
end Noten que al ejecutarlo no se genera ningún resultado. Lo que hemos hecho es definir un comando llamado minombrees con una simple función. Esta es la idea principal de un programa. Ejecuten nuevamente el programa. Al hacer esto observaran que se genera un error. Esto es porque al igual que las variables, no es posible asignar dos nombres iguales a un programa. Para evitar esto, es importante utilizar program drop minombrees antes de cargar el comando nuevamente. Este es un buen momento para utilizar capture.
4.0.3.2 Programa 2
Vamos escribir un programa que permita calcular un promedio. Vamos a escribir un comando que nos permita crear una nueva variable que contenga los valores promedio y que muestre el resultado en la pantalla. Esto sería igual que escribir:
sysuse auto, clear
egen mimedia_mpg = mean(mpg)
tab mean_mpg Podemos evitar la repetición de estos dos comandos armando un programa:
program drop _all
capture program drop mymean
program mymean
egen media_`1' = mean(`1')
tab media_`1'
end
* Aplicamos este programa
sysuse auto, clear
mimedia mpgNoten que hemos utilizado `1'. Este nos indica cualquier variable que este en la primera posición. Si incluimos más de una variable. Solo va a considerar la primera variable.
Ejercicio 3.5.1:
Preguntas
- Escriba un programa que permita ver todas las etiquetas (
labels) de una base de datos. - Aplique este programa a la base
auto.dta.
4.0.3.3 Programa 3
Vamos a reescribir el programa para permitir un número arbitrario de variables. La macro ‘0’ contiene toda la cadena, ‘1’ el primer elemento, ‘2’ el segundo, etc. Podemos hacer una iteración sobre todos los elementos sin saber cuántos hay utilizando la técnica de desplazamiento incremental que implementaremos con macro_shift.
capture program drop mimedia
program define mimedia
while "`1'"!="" {
egen mean_`1'=mean(`1')
tab mean_`1'
macro shift
}
end
sysuse auto, clear
mimedia price mpg rep78 El comando macro shift sirve como incremental. Termina cuando encuentra un vacio, lo que explica la presencia del while. Notar que "1’“` habla de la posición. Es una buena tecnica para garantizar que el iterador seguirá cuando este vacio.
4.0.3.4 Programa 4
Modificamos un poco el programa para que despliegue los resultados en la consola de Stata. Adicionalmente, agregamos un quietly.
capture program drop mimedia
program define mimedia
while "`1'"!="" {
qui: egen `1'_mean = mean(`1')
display "Media de `1' = " `1'_mean
macro shift
}
end
sysuse auto, clear
mimedia price mpg rep78 4.0.3.5 Programa 5
El comando macro shift es útil, pero puede ser lento. Agregando variables locales usuales y un incremental es mucho mejor. Ojo con las dobles comillas.
capture program drop mimedia
program define mimedia
local i = 1
while "``i''"~="" {
qui: egen ``i''_mean = mean(``i'')
display "Media de `i' = " ``i''_mean
local ++i
}
end4.0.4 Programa con distintos argumentos
El programa de los ejemplos anteriores soporta un solo argumento. Ahora vamos a ver un programa que considere explícitamente que los argumentos de un programa pueden tomar roles distintos.
program drop _all
capture program drop show
program define show
tempvar obs
quietly gen `obs' = `1' ///
if (ctycode == "`2'" & year ==`3')
sort `obs'
display "`1' of country `2' in `3' is: " `obs'
endtempvar crea una variable temporal que existe mientras el programa se ejecuta pero que se elimina automáticamente una vez que el programa termina su ejecución. Es importante estar seguros(as) de que cualquier string están en comillas dobles.
Ejercicio 3.5.2:
Preguntas
- Escriba un programa que reporte la mediana de la diferencia entre dos variables.
- Aplique este programa a la base
auto.dta.
4.0.5 Opción Syntax
Renombrando argumentos:
- De momento hemos escrito los programas utilizando
'1', '2', '3'con el fin de introducir el uso de programas en Stata. - Sin embargo, esta notación puede ser un poco confusa y provocar errores en la codificación. Podemos asignar nombres de mayor significado a los argumentos del programa.
program drop _all
capture program drop show
program define show
args var cty yr
tempvar obs
quietly gen `obs' = `var' ///
if (ctycode == "`cty'" & year ==`yr')
sort `obs'
display "`1' of country `2' in `3' is: " `obs'
endEl comandoargs asigna a las variables locales var, cty, yr los valores de '1', '2', '3'. Noten que si llaman al programa con cuatro argumentos no retorna un error. Un método mucho más robusto y mejor para llamar a los argumentos de un programa es utilizar syntax. En vez de referirnos a cada elemento de un programa por su posición, vamos a especificar la “gramática” del programa.
La sintaxis de Stata es:
[by varlist:] command [varlist] [=exp] [in range] [, options]varlist denota la lista de variables, command el comando a ejecutar, exp denota una expresión algebraica, range denota un rango para las observaciones mientras que ,options denota la lista de opciones propias de un comando. Utilizar syntax en un programa hace que Stata verifique si un programa satisface la sintaxis. En caso de que no la cumpla, arrojara un error.
Syntax
* El comando syntax almacena en macros locales todos los elementos típicos de un comando en Stata.
* Por ejemplo, syntax también puede definir condicionales como if o in.
program ...
syntax varlist(min = 2) [if] [in]Si quieren hacer programas más complejos, con otras características es importante revisar syntax.
Uno de los primeros elementos de syntax es varlist. Es posible indicar el mínimo o máximo de variables.
* Por ejemplo: varlist(min = 2 max = 2).
* Si tienes solo una variable puedes utilizar varname.
* Es equivalente a varlist(min = 1 max = 1).
* La macro que guarda las variables siempre se llama varlist.
Ejemplo 1: programa para calcular percentiles
program pctrange, rclass
version 17
syntax varlist(max = 1 numeric)
quietly summarize `varlist', detail
scalar range = r(max) - r(min)
scalar p7525 = r(p75) - r(p25)
scalar p9010 = r(p90) - r(p10)
display as result _n "Rangos de percentiles para `varlist'"
display as txt "75-25 : " p7525
display as txt "90-10: " p9010
display as txt "Range: " range
end El programa anterior nos permite obtener los percentiles de alguna variable al mismo tiempo que los muestra en la consola. Noten que tambíen he incluido que tipo de comando es. En este caso es un comando r-class. program es quien determina el nombre del programa. syntax permite determinar los elementos de tu programa. En el ejemplo define el tipo y el límite de variables. También puede definir condicionales como if o in.
Los escalares definidos durante el programa se pueden utilizar. Esto no siempre es conveniente. Como recomendación es bueno guardar los programas como variables temporales.
Ejemplo 2: programa para calcular percentiles con variables locales
program pctrange, rclass
version 17
syntax varlist(max = 1 numeric)
local res "range p7525 p9010"
tempname `res'
display as result _n "Rangos de percentiles para `varlist'"
quietly summarize `varlist', detail
scalar `range' = r(max) - r(min)
scalar `p7525' = r(p75) - r(p25)
scalar `p9010' = r(p90) - r(p10)
display as txt "75-25 : " `p7525'
display as txt "90-10: " `p9010'
display as txt "Range: " `range'4.0.6 Return
- Una característica importante de los comandos de Stata es su capacidad de reportar los resultados de forma tal de que los usuarios y usuarias podamos utilizarlos posteriormente.
- El comando
returnnos permite guardar los escalares y hacerlos accesibles, sin tener el problema que vimos en el ejemplo anterior.
- Notar que el lado izquierdo del escalar de retorno se refiere al nombre de la macro, el lado derecho debe hacer referencia a la macro una vez más para extraer el valor almacenado en ese nombre, por lo que debe utilizar dos comillas.
Ejemplo 3: programa para calcular percentiles con return
program pctrange, rclass
version 17
syntax varlist(max = 1 numeric)
local res "range p7525 p9010"
tempname `res'
display as result _n "Rangos de percentiles para `varlist'"
quietly summarize `varlist', detail
scalar `range' = r(max) - r(min)
scalar `p7525' = r(p75) - r(p25)
scalar `p9010' = r(p90) - r(p10)
display as txt "75-25 : " `p7525'
display as txt "90-10: " `p9010 '
display as txt "Range: " `range'
foreach r of local res {
return scalar `r' = ``r''
}
end 4.0.7 Implementar opciones al programa
Podemos agregar distintas opciones al programa. Una opción es agregar como opcional que el programa de un resultado en la consola. Al incluir \([ ]\) en syntax significa un componente opcional en el comando.
Ejemplo 4: implementar opciones al programa (imprimir por defecto)
program pctrange, rclass
version 17
syntax varlist(max = 1 numeric) [, PRINT]
local res "range p7525 p9010"
tempname `res'
display as result _n "Rangos de percentiles para `varlist'"
quietly summarize `varlist', detail
scalar `range' = r(max) - r(min)
scalar `p7525' = r(p75) - r(p25)
scalar `p9010' = r(p90) - r(p10)
if "`print'" == "print" {
display as txt "75-25 : " `p7525'
display as txt "90-10: " `p9010 '
display as txt "Range: " `range'
}
foreach r of local res {
return scalar `r' = ``r''
}
end Ejemplo 5: implementar opciones al programa (no imprimir por defecto)
program pctrange, rclass
version 17
syntax varlist(max = 1 numeric) [, noPRINT]
local res "range p7525 p9010"
tempname `res'
display as result _n "Rangos de percentiles para `varlist'"
quietly summarize `varlist', detail
scalar `range' = r(max) - r(min)
scalar `p7525' = r(p75) - r(p25)
scalar `p9010' = r(p90) - r(p10)
if "`print'" != "noprint" {
display as txt "75-25 : " `p7525'
display as txt "90-10: " `p9010 '
display as txt "Range: " `range'
}
foreach r of local res {
return scalar `r' = ``r''
}
end 4.0.8 Incluir if/in al programa
Incluir un subconjunto de observaciones
- Cualquier comando debiese incluir
ifoin range. - Nuevamente, estas opciones son manejadas dentro del comando
syntax. Para incluir estas opciones hay que agregar[if]y[in]. - Con estos comandos puedo ejecutar el programa para sub-muestras. Es importante asegurar que la sub-muestra no es vacía. Para ello es importante calcular
r(N), chequear que sea distinto de cero y agregar un título que lo indique. - El comando
marksample touseutiliza la información provista porifoinen caso de que estos sean indicados en el programa. Este comando genera una variable localtouseque es igual a 1 si las variables entran en el calculo que hace el programa y 0 en caso contrario. - Utilizaremos
'touse'para calcular el número de observaciones que se utilizan después de aplicar los condicionales. Es necesario agregar'touse'en cada parte del programa que trabaje con la variable de input.
Ejemplo 6: Incluir un subconjunto de observaciones
program pctrange, rclass
version 17
syntax varlist(max = 1 numeric) [, noPRINT]
local res "range p7525 p9010"
tempname `res'
display as result _n "Rangos de percentiles para `varlist'"
quietly summarize `varlist', detail
scalar `range' = r(max) - r(min)
scalar `p7525' = r(p75) - r(p25)
scalar `p9010' = r(p90) - r(p10)
if "`print'" != "noprint" {
display as txt "75-25 : " `p7525'
display as txt "90-10: " `p9010 '
display as txt "Range: " `range'
}
foreach r of local res {
return scalar `r' = ``r''
}
end 4.0.9 Generalizar el comando para incluir múltiples variables
Algunas consideraciones
- Si quiero ejecutar el programa sobre múltiples variables, es necesario ajustar un poco el programa.
- Tengo que indicarle a
syntaxque hay más de una variable. Además, tengo que indicarle al programa que en caso de que existan más variables muestre los resultados en una tabla. - Guardaremos los resultados en matrices (no escalares) y vamos a aplicar una función de macro extendida con el fin de contar el número de filas que esta matriz debiese tener.
- Agregaremos también una la opción para cambiar el formato y la opción
matque permite a la matriz ser guardada automáticamente con el nombre indicado.
Ejemplo 7
program drop _all
program pctrange, rclass
version 17
syntax varlist(min = 1 numeric ts) [if] [in] [, noPRINT FORmat(passthru) MATrix(string)]
marksample touse
quietly count if `touse'
if `r(N)' == 0 {
error 2000
}
local nvar: word count `varlist'
if `nvar' == 1 {
local res range p7525 p9010
tempname `res'
quietly summarize `varlist' if `touse', detail
scalar `range' = r(max) - r(min)
scalar `p7525' = r(p75) - r(p25)
scalar `p9010' = r(p90) - r(p10)
if "`print'" != "noprint" {
display as result _n "Rangos de percentiles para `varlist', N = `r(N)'"
display as txt "75-25 : " `p7525'
display as txt "90-10: " `p9010 '
display as txt "Range: " `range'
}
foreach r of local res {
return scalar `r' = ``r''
}
return scalar N = r(N)
}
else {
tempname rmat
matrix `rmat' = J(`nvar', 3, .)
local i 0
foreach v of varlist `varlist'{
local ++i
quietly summarize `v' if `touse', detail
matrix `rmat'[`i' ,1] = r(max) - r(min)
matrix `rmat'[`i', 2] = r(p75) - r(p25)
matrix `rmat'[`i', 3] = r(p90) - r(p10)
local rown `rown' `v'
}
matrix colnames `rmat' = Range P75-P25 P90-P10
matrix rownames `rmat' = `rown'
if "`print'" != "noprint" {
local form ", noheader"
if "`format'" != "" {
local form "`form' `format'"
}
matrix list `rmat' `form'
}
if "`matrix'" != "" {
matrix `matrix' = `rmat'
}
return matrix rmat = `rmat'
}
return local varname `varlist'
end 4.0.10 Agregar prefijos a los programas
Prefijo by
- Ahora vamos a hacer que nuestro programa pueda utilizar el prefijo
by. - Para agregar esta opción simplemente hay qyue modificar
program.
program pctrange, rclass byable(recall)También podemos permitir que la lista de variables incluya operadores de series de tiempo (ej. L.pib, D.ingreso). Para incorporar estos elementos tenemos que modificar syntax.
syntax varlist(min = 1 numeric ts) [if] [in] ///
[, noPRINT FORmat(passthru) MATrix(string)]4.0.11 Programas para complementar función egen
Es posible programar funciones adicionales de egen. El nombre de estos programas deben empezar con _g. Una diferencia importante entre este tipo de programas y los que ya hemos escrito guarda relación con el hecho de que hay que tener en cuenta la nueva variable que se va a crear. La sintaxis es la siguiente:
egen [type] newvarname = fcn(arguments) [if] [in] [, options]El cambio que haremos en la sintaxis del programa será que incluiremos un touse para hacer la nueva variable.
Ejemplo 8: programa función egen
* Programas de egen
program drop _all
program _gpct9010
syntax newvarname =/exp [if] [in]
tempvar touse
mark `touse' `if' `in'
quietly summarize `exp' if `touse', detail
quietly generate `typlist' `varlist' = r(p90) - r(p10) if `touse'
end
* Aplicación del programa
sysuse auto, clear
egen rango9010 = pct9010(price)Programas con funciones de egen con prefijo by
- Agregamos
[, *]que corresponde a las opciones. Enegenel prefijobyes una opción. - Con el fin de permitirle al programa que pueda producir un rango de percentil separado para cada grupos utilizaremos
pctileen vez desummarize. - El cambio que haremos en la sintaxis del programa será que incluiremos un
tousepara hacer la nueva variable.
Ejemplo 9: programa función egen con prefijo by
* Programa con opción by.
program drop _all
program _gpct9010
syntax newvarname =/exp [if] [in] [, *]
tempvar touse p90 p10
mark `touse' `if' `in'
quietly {
egen double `p90' = pctile(`exp') if `touse', `options' p(90)
egen double `p10' = pctile(`exp') if `touse', `options' p(10)
generate `typlist' `varlist' = `p90' - `p10' if `touse' }
end
* Aplicación del programa
sysuse auto, clear
egen rango9010 = pct9010(price)
bysort rep78 foreign: egen rango9010_prefijoby = pct9010(price)Generalización de la función egen para soportar todos los pares de cuantiles
- Hemos desarrollado una función de
egenque permite calcular un rango entre percentiles para una lista de variables especifica. - Puede ser util tomar ventaja de
egen pctile()para poder calcular cualquier percentil de la lista de variables especificadas. - Vamos a agregar dos opciones a la función
egen:lo()yhi(). En caso de que no especifiquemos, por defecto se calcula el rango interquartil. La función deegenahora se llamará_gpctrange.ado.
Ejemplo 10: función egen más general
* Programa con opción by.
program drop _all
program _gpctrange
syntax newvarname =/exp [if] [in] [, LO(integer 25) HI(integer 75) *]
if `hi' > 99 | `lo' < 1 {
display as error ///
"Percentiles `lo' `hi' deben estar entre 1 y 99."
error 198
}
if `hi' <= `lo' {
display as error ///
"Percentiles `lo' `hi' deben estar en orden ascendente"
error 198
}
tempvar touse phi plo
mark `touse' `if' `in'
quietly {
egen double `phi' = pctile(`exp') if `touse', `options' p(`hi')
egen double `plo' = pctile(`exp') if `touse', `options' p(`lo')
generate `typlist' `varlist' = `phi' - `plo' if `touse'
}
end
sysuse auto, clear
bysort rep78: egen iqr = pctrange(price) if inrange(rep78,3,5)
bysort rep78: egen p8020 = pctrange(price) if inrange(rep78,3,5), hi(80) lo(20)
tabstat iqr if inrange(rep78, 3, 5), by(rep78)
tabstat p8020 if inrange(rep78, 3, 5), by(rep78)4.0.12 Syntax
Algunas consideraciones
- Como hemos visto hay dos formas en los que un programa de Stata puede interpretar lo que ingresamos.
- Por posición tal como lo hace
argso como lo hicimos con los números entre las comillas.
- De acuerdo a la gramática del programa utilizando
syntax. syntaxguarda los componentes en macros locales particulares a las cuales podemos acceder posteriormente.- Por ejemplo
'if''in''varlist'son macros locales a las que podemos acceder tal como vimos la clase pasada. - Ahora vamos a ver algunas opciones de
syntaxque nos permitirán tener más herramientas para escribir nuestros programas. - Al utilizar dentro de un programa paréntesis cuadrados estoy indicando que esas partes son opcionales.
- Por ejemplo, estas dos versiones son equivalentes, salvo que en la segunda linea todo es opcional:
* Nada opcional
syntax varlist if in title(string) adjust(real 1)
* Todo opcional
syntax [varlist] [if] [, adjust (real 1) title(string)]Vamos a mirar las macros generadas por syntax:
capture program drop myprog
program myprog
syntax varlist [if] [in] [, adjust(real 1) title(string)]
display "varlist contiene |`varlist'|"
display "if contiene |`if'|"
display "in contiene |`in'|"
display "adjust contiene |`adjust'|"
display "title contiene |`title'|"
endVamos a aplicar lo aprendido en un ejemplo sencillo:
capture program drop miprograma
program miprograma
syntax varlist [if] [in] [, adjust(real 1) title(string)]
display
if "`title'" != "" {
display "`title':"
}
foreach var of local varlist{
quietly summarize `var' `if' `in'
display "`var'" " "%9.0g r(mean)*`adjust'
}
endmarksample y touse: Un error común es utilizar una muestra en una parte del programa y otra distinta en otra parte. La solución es crear una variable que contiene un 1 si la observación fue utilizada y un 0 en caso contrario.
capture program drop miprograma
program miprograma
syntax varlist [if] [in] [, adjust(real 1) title(string)]
marksample touse
display
if "`title'" != "" {
display "`title':"
}
foreach var of local varlist{
quietly summarize `var' `if' `touse'
display "`var'" " " r(mean)*`adjust'
}
end4.0.13 Varlist
varlist específica la macro que contiene las variables que van a ingresar al programa como inputs. Las opciones de varlist son:
* default = none. Especifica como la varlist se va a llenar. Por defecto se llena con todas las variables.
* min, max especifica el número de variables permitidas.
* numeric, string especifican que criterio deben cumplir todas las variables que ingresas al programa.
* ts permite que la varlist contenga operadores de series de tiempo.
* fv permite que la varlist contenga variables categóricas.
4.0.14 Opciones
Las opciones permiten hacer requeridos o opcionales. Por ejemplo, regress, noconstant. Las opciones pueden ser una numlist, una varlist (ej. by(varlist) option, una namelist tal como el nombre de una matriz o de una nueva variable. Como regla general, cualquier característica que pudieran encontrar en un comando de Stata, la pueden agregar en un programa. Para las opciones es importante recordar que las mayúsculas indican la menor abreviación posible.
replace,detail,constantson opciones de on.noreplace,nodetail,noconstantson opciones de off. Ojo las macros que retornan tienen los mismos nombres que en el caso anterior.titleyadjusttambién son otros opcionales. El primero permite ingresar un título al comando mientras que el segundo permite ajustar los resultados por algún escalar.- Hay muchas otras. Veamos un ejemplo…
capture program drop miprograma
program miprograma
syntax varlist [if] [in] [, adjust(real 1) title(string)]
display
if "`title'" != "" {
displa "`title':"
}
foreach var of local varlist{
quietly summarize `var' `if' `in'
display "`var'" " "%9.0g r(mean)*`adjust'
}
end4.0.15 Programa para calcular percentiles
Una versión simple para una variable:
program pctrange, rclass
version 17
syntax varlist(max = 1 numeric)
quietly summarize `varlist', detail
scalar range = r(max) - r(min)
scalar p7525 = r(p75) - r(p25)
scalar p9010 = r(p90) - r(p10)
display as result _n "Rangos de percentiles para `varlist'"
display as txt "75-25 : " p7525
display as txt "90-10: " p9010
display as txt "Range: " range
end Algunas consideraciones
- El programa anterior nos permite obtener los percentiles de alguna variable al mismo tiempo que los muestra en la consola.
- Noten que también he incluido que tipo de comando es. En este caso es un comando
r-class. - Los escalares definidos en el programa se pueden utilizar. Esto no siempre es conveniente.
- Como recomendación es bueno guardar los resultados esperados de los programas como variables temporales.
Versión que incluye variables guardadas localmente:
program pctrange, rclass
version 17
syntax varlist(max = 1 numeric)
local res "range p7525 p9010"
tempname `res'
display as result _n "Rangos de percentiles para `varlist'"
quietly summarize `varlist', detail
scalar `range' = r(max) - r(min)
scalar `p7525' = r(p75) - r(p25)
scalar `p9010' = r(p90) - r(p10)
display as txt "75-25 : " `p7525'
display as txt "90-10: " `p9010'
display as txt "Range: " `range'4.0.16 Programa con opción return
Una característica importante de los comandos de Stata es su capacidad de reportar los resultados de forma tal de que los usuarios y usuarias podamos utilizarlos posteriormente. El comando return nos permite guardar los escalares y hacerlos accesibles, sin tener el problema de los escalares que vimos en los ejemplos anteriores.
Programa para calcular percentiles con return:
program pctrange, rclass
version 17
syntax varlist(max = 1 numeric)
local res "range p7525 p9010"
tempname `res'
display as result _n "Rangos de percentiles para `varlist'"
quietly summarize `varlist', detail
scalar `range' = r(max) - r(min)
scalar `p7525' = r(p75) - r(p25)
scalar `p9010' = r(p90) - r(p10)
display as txt "75-25 : " `p7525'
display as txt "90-10: " `p9010 '
display as txt "Range: " `range'
foreach r of local res {
return scalar `r' = ``r''
}
end Sobre la forma en que llamamos a return:
foreach r of local res {
return scalar `r' = ``r''
}Notar que el lado izquierdo de scalar('r') se refiere al nombre de los elementos de la macro res. El lado derecho hace referencia a la macro una vez más para extraer el valor almacenado en ese nombre. En este caso es importante notar que se deben utilizar dos comillas (ej. "range"). Noten también que si utilizamos scalar list no hay resultados, sin embargo, al utilizar la opción return ahora podemos rescatar los resultados con return list.
Ejercicio 3.6.3:
Preguntas
- Escriba un programa llamado
misumaque sea del tipor-class. - Este programa debe entregar en la lista de
return listel número total de observaciones, la suma total y el promedio de la variable. - Aplicarla en
auto.dta.
4.0.17 Agregar opciones al programa
Podemos agregar distintas opciones al programa. Para este ejemplo vamos a agregar como opcional que el programa de un resultado en la consola. Recordemos que incluir \([ ]\) en syntax significa un componente opcional en el comando.
Implementar opciones al programa (imprimir por defecto):
program pctrange, rclass
version 17
syntax varlist(max = 1 numeric) [, PRINT]
local res "range p7525 p9010"
tempname `res'
display as result _n "Rangos de percentiles para `varlist'"
quietly summarize `varlist', detail
scalar `range' = r(max) - r(min)
scalar `p7525' = r(p75) - r(p25)
scalar `p9010' = r(p90) - r(p10)
if "`print'" == "print" {
display as txt "75-25 : " `p7525'
display as txt "90-10: " `p9010 '
display as txt "Range: " `range'
}
foreach r of local res {
return scalar `r' = ``r''
}
end Implementar opciones al programa (no imprimir por defecto):
program pctrange, rclass
version 17
syntax varlist(max = 1 numeric) [, noPRINT]
local res "range p7525 p9010"
tempname `res'
display as result _n "Rangos de percentiles para `varlist'"
quietly summarize `varlist', detail
scalar `range' = r(max) - r(min)
scalar `p7525' = r(p75) - r(p25)
scalar `p9010' = r(p90) - r(p10)
if "`print'" != "noprint" {
display as txt "75-25 : " `p7525'
display as txt "90-10: " `p9010 '
display as txt "Range: " `range'
}
foreach r of local res {
return scalar `r' = ``r''
}
end Incluir un subconjunto de observaciones
- Cualquier comando debiese incluir
ifoin range. - Nuevamente, estas opciones son manejadas dentro del comando
syntax. Para incluir estas opciones hay que agregar[if]y[in]. - Con estos comandos puedo ejecutar el programa para sub-muestras. Es importante asegurar que la sub-muestra no este vacía.
- Para ello es importante calcular
r(N), chequear que sea distinto de cero y agregar un título que lo indique. - El comando
marksample touseutiliza la información provista porifoinen caso de que estos sean indicados en el programa. - Este comando genera una variable local
touseque es igual a 1 si las variables entran en el calculo que hace el programa y 0 en caso contrario. - Utilizaremos `
touse'para calcular el número de observaciones que se utilizan después de aplicar los condicionales. - Es necesario agregar `
touse'en cada parte del programa que trabaje con la variable de input.
Incluir un subconjunto de observaciones:
program drop _all
program pctrange, rclass
syntax varlist(max = 1 numeric) [if] [in] [, noPRINT]
marksample touse
quietly count if `touse'
if `r(N)' == 0 {
error 2000
}
local res range p7525 p9010
tempname `res'
quietly summarize `varlist' if `touse', detail
scalar `range' = r(max) - r(min)
scalar `p7525' = r(p75) - r(p25)
scalar `p9010' = r(p90) - r(p10)
if "`print'" != "noprint" {
display as result _n "Rangos de percentiles para `varlist', N = `r(N)'"
display as txt "75-25 : " `p7525'
display as txt "90-10: " `p9010 '
display as txt "Range: " `range'
}
foreach r of local res {
return scalar `r' = ``r''
}
return scalar N = r(N)
return local varname `varlist'
end Ejercicio 3.6.2:
Preguntas
- Ajuste el programa del ejercicio 1 con el fin de incluir la opción
if. Guárdelo comomisuma2. - Aplicarla en
auto.dta.
4.0.18 Generalizar el comando para incluir múltiples variables
Algunas consideraciones
- Si quiero ejecutar el programa sobre múltiples variables, es necesario ajustar un poco el programa.
- Tengo que indicarle a
syntaxque hay más de una variable. Además, tengo que indicarle al programa que en caso de que existan más variables muestre los resultados en una tabla. - Guardaremos los resultados en matrices (no escalares) y vamos a aplicar una función de macro extendida con el fin de contar el número de filas que esta matriz debiese tener.
- Agregaremos también una la opción
formatpara cambiar el formato y la opciónmatque permite a la matriz ser guardada automáticamente con el nombre indicado.
program drop _all
program pctrange, rclass
version 17
syntax varlist(min = 1 numeric ts) [if] [in] [, noPRINT FORmat(passthru) MATrix(string)]
marksample touse
quietly count if `touse'
if `r(N)' == 0 {
error 2000
}
local nvar: word count `varlist'
if `nvar' == 1 {
local res range p7525 p9010
tempname `res'
quietly summarize `varlist' if `touse', detail
scalar `range' = r(max) - r(min)
scalar `p7525' = r(p75) - r(p25)
scalar `p9010' = r(p90) - r(p10)
if "`print'" != "noprint" {
display as result _n "Rangos de percentiles para `varlist', N = `r(N)'"
display as txt "75-25 : " `p7525'
display as txt "90-10: " `p9010 '
display as txt "Range: " `range'
}
foreach r of local res {
return scalar `r' = ``r''
}
return scalar N = r(N)
}
else {
tempname rmat
matrix `rmat' = J(`nvar', 3, .)
local i 0
foreach v of varlist `varlist'{
local ++i
quietly summarize `v' if `touse', detail
matrix `rmat'[`i' ,1] = r(max) - r(min)
matrix `rmat'[`i', 2] = r(p75) - r(p25)
matrix `rmat'[`i', 3] = r(p90) - r(p10)
local rown `rown' `v'
}
matrix colnames `rmat' = Range P75-P25 P90-P10
matrix rownames `rmat' = `rown'
if "`print'" != "noprint" {
local form ", noheader"
if "`format'" != "" {
local form "`form' `format'"
}
matrix list `rmat' `form'
}
if "`matrix'" != "" {
matrix `matrix' = `rmat'
}
return matrix rmat = `rmat'
}
return local varname `varlist'
end4.0.19 Agregar prefijos a los programas
prefijo by: Ahora vamos a hacer que nuestro programa pueda utilizar el prefijo by. Para agregar esta opción simplemente hay que modificar program.
program pctrange, rclass byable(recall)También podemos permitir que la lista de variables incluya operadores de series de tiempo (ej. L.pib, D.ingreso).
syntax varlist(min = 1 numeric ts) 4.0.20 Programas para complementar función egen
Es posible programar funciones adicionales de egen (extended generate). El nombre de estos programas deben empezar con _g. Una diferencia entre este tipo de programas y los ya hechos es que en estos hay que tener en cuenta la nueva variable que se va a crear. La sintaxis es la siguiente:
egen [type] newvarname = fcn(arguments) [if] [in] [, options]El cambio que vamos a hacer en la sintaxis del programa será que incluiremos un touse para hacer la nueva variable.
Vamos a escribir un programa para calcular un rango en particular:
* Programas de egen
program drop _all
program _gpct9010
syntax newvarname =/exp [if] [in]
tempvar touse
mark `touse' `if' `in'
quietly summarize `exp' if `touse', detail
quietly generate `typlist' `varlist' = r(p90) - r(p10) if `touse'
end
* Aplicación del programa
sysuse auto, clear
egen rango9010 = pct9010(price)Programas con funciones de egen con prefijo by:
- Agregamos [, *] que corresponde a las opciones. En
egenel prefijobyes una opción. - Con el fin de permitirle al programa que pueda producir un rango de percentil separado para distintos grupos utilizaremos
pctileen vez desummarize.
* Programa con opción by.
program drop _all
program _gpct9010
syntax newvarname =/exp [if] [in] [, *]
tempvar touse p90 p10
mark `touse' `if' `in'
quietly {
egen double `p90' = pctile(`exp') if `touse', `options' p(90)
egen double `p10' = pctile(`exp') if `touse', `options' p(10)
generate `typlist' `varlist' = `p90' - `p10' if `touse' }
end
* Aplicación del programa
sysuse auto, clear
egen rango9010 = pct9010(price)
bysort rep78 foreign: egen rango9010_prefijoby = pct9010(price)4.0.21 Generalización de la función egen
Hemos desarrollado una función de egen que permite calcular un rango entre percentiles para una lista de variables especifica. Puede ser útil tomar ventaja de egen pctile() para poder calcular cualquier percentil de la lista de variables especificadas. Vamos a agregar dos opciones a la función egen : lo() y hi(). En caso de que no especifiquemos, por defecto se calcula el rango interquartil. La función de egen ahora se llamará _gpctrange.ado.
Función egen más general para percentiles:
* Programa con opción by.
program drop _all
program _gpctrange
syntax newvarname =/exp [if] [in] [, LO(integer 25) HI(integer 75) *]
if `hi' > 99 | `lo' < 1 {
display as error ///
"Percentiles `lo' `hi' deben estar entre 1 y 99."
error 198
}
if `hi' <= `lo' {
display as error ///
"Percentiles `lo' `hi' deben estar en orden ascendente"
error 198
}
tempvar touse phi plo
mark `touse' `if' `in'
quietly {
egen double `phi' = pctile(`exp') if `touse', `options' p(`hi')
egen double `plo' = pctile(`exp') if `touse', `options' p(`lo')
generate `typlist' `varlist' = `phi' - `plo' if `touse'
}
end
sysuse auto, clear
bysort rep78: egen iqr = pctrange(price) if inrange(rep78,3,5)
bysort rep78: egen p8020 = pctrange(price) if inrange(rep78,3,5), hi(80) lo(20)
bysort rep78: egen p8020 = pctrange(price) if inrange(rep78,3,5) ///
& foreign==1, hi(80) lo(20)
* Utilizar estas nuevas variables con otros comandos
tabstat iqr if inrange(rep78, 3, 5), by(rep78)
tabstat p8020 if inrange(rep78, 3, 5), by(rep78)4.0.22 Documentar tu programa
Escribir un help
- Es necesario y recomendado mantener una documentación de los programas que se escriban para un proyecto.
- Esta documentación debe estar actualizada e incluir cualquier modificación. Importante hacerlo mientras se hace el programa y no al final.
- Vamos a aprender un poco de SMCL (Stata Markup and Control Language file). Básicamente es el lenguaje con el que se escriben los
helpen Stata. También es el lenguaje con el que se muestran los resultados dedisplay. - Los archivos se pueden escribir en cualquier procesador de texto, pero deben ser guardados en formato
.smcl. También deben ser guardados en la misma carpeta en donde se encuentra elado-filerelacionado con el archivo. - Hay que empezar los códigos con \(\{smcl\}\) con el fin de indicarle a Stata que el texto que viene será en formato SMCL.
- Las etiquetas de SMCL van entre llaves (\(\{\}\)) y se pueden leer de dos formas principalmente:
- \(\{tag:text\}\) etiquetar el texto que se esta escribiendo.
- \(\{tag\}\) etiquetar todo lo que viene hasta que se cambie en otra parte del texto.
Por ejemplo, si quiero poner texto en itálica tengo que ocupar la etiqueta it:
* Una palabra/frase en particular
{it:este texto aparecerá en itálica}
* Todo el bloque de texto
{it} Todo lo que este aquí aparecerá en itálica. ///
Esto va a ocurrir hasta que aparezca un nuevo ///
tipo de etiqueta. Etiquetas de SMCL
- Texto: {it}, {bf}, {sf}, {ul}. Itálica, negrita, texto normal, subrayado, repectivamente.
- Texto en formato Stata: {cmd}, {error}, {result}, {text}.
- {Destacar una referencia: {hi}.
- Opciones de comando: {opt}.
- Insertar linea horizontal: {hline}.
- Volver a dejar el texto a su estado normal: {reset}.
- Formato de documento: {title:text}, {center}, {ralign}, {lalign}, {tab}.
- Párrafos: Hay dos opciones para escribirlos.
- {p #1 #2 #3 #4}.
- {p} = {p 0 0 0 0}
- {p #1 #2 #3 #4}.
- Los números indican los siguientes elementos de un párrafo:
- El primer número (#1) es cuántos caracteres hay que sangrar en la primera línea.
- El segundo número (#2) es cuántos caracteres hay que sangrar en la segunda y tercera línea.
- El tercer número (#3) es cuán lejos de la derecha debe estar el margen.
- El cuarto número (#4) es para el ancho total del párrafo.
- {phang}: es equivalente a {p 4 8 2}.
- {pstd}: es equivalente a {p 4 4 2}.
- {phang2}: es equivalente a {p 8 12 2}.
- {p2col}: Para separar el texto en dos columnas.
- {p_end}: Para terminar un párrafo. Útil cuando tienes dos parrafos en formatos distintos.
4.0.23 Escribir programas e-class
Vamos a aprender algunos elementos que nos van a permitir escribir nuestros propios comandos de estimación en Stata. Muchos de los conceptos que hemos visto aplican también para este tipo de comandos. Es necesario recordar algunas convenciones que nos van a ser útiles para poder escribir nuestros programas.
- Los resultados se guardan en
|e()y se puede acceder a ellos conereturn list. - El número de observaciones es
e(N)y para identificar que observaciones fueron incluidas en la estimación es necesario utilizar la funcióne(sample). - Los coeficientes estimados se guardan en un vector
e(b)y la matriz de varianza covarianza se guarda ene(V). - El comando
ereturn name = expretorna un escalar, mientras queereturn local name valueyereturn matrix name matnameretorna una macro y una matriz respectivamente. - El comando
ereturn postenvía las estimaciones debyVa sus ubicaciones oficiales. - Para devolver el vector de coeficientes y su matriz de varianza, es necesario crear el vector de coeficientes, digamos \(beta\), y su matriz de varianza-covarianza, digamos \(vce\).
ereturn post `beta' 'vce', esample(`touse')También podemos definir la muestra de estimación incluida en la estimación con touse. Ahora es posible guardar los elementos en e(). Por ejemplo, es posible utilizar ereturn scalar, ereturn local o ereturn matrix.
Es conveniente utilizar los nombres típicamente asignados para guardar resultados de los programas e(df_m) o e(df_r). Sin embargo, se pueden nombrar como deseen.
4.0.24 Marksample, Mark y Markout
marksample y mark son alternativas. mark no es muy utilizado. Ambos comandos crean un indicador que marcan que observaciones será utilizadas. Los ocupadmos en los programas en Stata. La idea es indicarle al programa la muestra relevante. markout marca la variable con un indicador igual a 0 si cualquier variable en varlist indicada contiene un missing.
Marksample
marksamplese utiliza en programas en los que los argumentos se analizan mediante el comandosyntax.- Crea una variable temporal, almacena el nombre de la variable temporal en un local, y rellena la variable temporal con 0 y 1 según si la observación debe ser utilizada.
program ....
syntax ...
marksample touse
rest of code .... if `touse'
endMark
markutiliza la variable temporaltousebasada en las expresiones deifein.- Si no hay expresiones de
ifein,touseserá 1 para cada observación en los datos. - Si indico una condición, solo las observaciones que cumplan esta condición tendrán un 1 en
touse. Markactualizatousede forma tal de que revisa missing.
Mark y Markout
markparte con una variable temporal previamente creada.
markoutmodifica la variable creada pormarkponiéndola a cero en las observaciones que tienen valores perdidos registrados para cualquiera de las variables en varlist.
program ....
tempvar touse
mark `touse' ...
markout `touse' ...
rest of code ... if `touse'
endMarksample vs. Markout
marksamplees mejor quemark. Disminuye la probabilidad de que se olvide alguna restricción.markoutpuede ser utilizados después demarko bienmarksample.
program ...
tempvar touse
mark `touse' ...
markout `touse' ...
rest of code ... if `touse'
end
prgraom myprog
syntax varlist [if] [in]
marksample touse
...
end
* Equivale a:
program myprog
version 17.0
syntax varlist [if] [in]
tempvar touse
mark `touse' `if' `in'
markout `touse' `varlist'
...
endmarkout también puede ser usado con marksample:
program ...
syntax ... [, Denom(varname) ... ]
marksample touse
markout `touse' `denom'
rest of code ... if `touse'
endEjemplo Marksample y Markout:
program cwsumm
syntax [varlist(fv ts)] [if] [in] [aweight fweight] [, Detail noFormat]
marksample touse
summarize `varlist' [`weight'`exp'] if `touse', `detail' `format'
end4.0.25 Sortpreserve
Si está escribiendo un programa de Stata que cambia temporalmente el orden de los datos y quieres que los datos se ordenen en su orden original al final de la ejecución, puede ahorrar un poco de programación incluyendo sortpreserve. Para ellos debemos escribir: program miprograma, sortpreserve. Stata automáticamente reordenara las variables como estaban originalmente. Al agregar esta opción se genera una variable temporal llamada _sortindex la que contiene el orden original de los datos.