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 tabmiss

Guardar 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 Personal de Stata. Para entrar escriban personal en 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 summarize Stata 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 mpg

Noten 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

  1. Escriba un programa que permita ver todas las etiquetas (labels) de una base de datos.
  2. 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
    }
end

4.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' 
end

tempvar 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

  1. Escriba un programa que reporte la mediana de la diferencia entre dos variables.
  2. 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' 
end

El 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 return nos 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 if o in 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 touse utiliza la información provista por if o in en caso de que estos sean indicados en el programa. Este comando genera una variable local touse que 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 syntax que 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 mat que 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. En egen el prefijo by es una opción.
  • Con el fin de permitirle al programa que pueda producir un rango de percentil separado para cada grupos utilizaremos pctile en vez de summarize.
  • El cambio que haremos en la sintaxis del programa será que incluiremos un touse para 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 egen que 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() 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.

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 args o como lo hicimos con los números entre las comillas.
  • De acuerdo a la gramática del programa utilizando syntax.
  • syntax guarda 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 syntax que 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'|" 
end

Vamos 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'
    }
end

marksample 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'
    }
end

4.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, constant son opciones de on.
  • noreplace, nodetail, noconstant son opciones de off. Ojo las macros que retornan tienen los mismos nombres que en el caso anterior.
  • title y adjust tambié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'
    }
end

4.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

  1. Escriba un programa llamado misuma que sea del tipo r-class.
  2. Este programa debe entregar en la lista de return list el número total de observaciones, la suma total y el promedio de la variable.
  3. 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 if o in 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 touse utiliza la información provista por if o in en caso de que estos sean indicados en el programa.
  • Este comando genera una variable local touse que 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

  1. Ajuste el programa del ejercicio 1 con el fin de incluir la opción if. Guárdelo como misuma2.
  2. 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 syntax que 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 format para cambiar el formato y la opción mat que 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'
end

4.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 prefijo by es una opción.
  • Con el fin de permitirle al programa que pueda producir un rango de percentil separado para distintos grupos utilizaremos pctile en vez de summarize.
* 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 help en Stata. También es el lenguaje con el que se muestran los resultados de display.
  • 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 el ado-file relacionado 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}
  • 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 con ereturn 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ón e(sample).
  • Los coeficientes estimados se guardan en un vector e(b) y la matriz de varianza covarianza se guarda en e(V).
  • El comando ereturn name = exp retorna un escalar, mientras que ereturn local name value y ereturn matrix name matname retorna una macro y una matriz respectivamente.
  • El comando ereturn post envía las estimaciones de b y V a 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

  • marksample se utiliza en programas en los que los argumentos se analizan mediante el comando syntax.
  • 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'
end

Mark

  • mark utiliza la variable temporal touse basada en las expresiones de if e in.
  • Si no hay expresiones de if e in, touse será 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.
  • Mark actualiza touse de forma tal de que revisa missing.

Mark y Markout

  • mark parte con una variable temporal previamente creada.
  • markout modifica la variable creada por mark ponié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'
end

Marksample vs. Markout

  • marksample es mejor que mark. Disminuye la probabilidad de que se olvide alguna restricción.
  • markout puede ser utilizados después de mark o bien marksample.
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'
...
end

markout también puede ser usado con marksample:

program ...
syntax ... [, Denom(varname) ... ]
marksample touse
markout `touse' `denom'
rest of code ... if `touse'
end

Ejemplo 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'
end

4.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.