class: title-slide <style type="text/css"> .huge .remark-code { /*Change made here*/ font-size: 200% !important; } .tiny .remark-code { /*Change made here*/ font-size: 60% !important; } </style> .title[ # Clase 23. Regresión discontinua en R ] .subtitle[ ## Evaluación de Programas ] .author[ ### Irvin Rojas <br> [rojasirvin.com](https://www.rojasirvin.com/) <br> [<i class="fab fa-github"></i>](https://github.com/rojasirvin) [<i class="fab fa-twitter"></i>](https://twitter.com/RojasIrvin) [<i class="ai ai-google-scholar"></i>](https://scholar.google.com/citations?user=FUwdSTMAAAAJ&hl=en) ] .affiliation[ ### Centro de Investigación y Docencia Económicas <br> División de Economía ] --- class: inverse, middle, center # Implementación de regresión discontinua --- # El *efecto seguidor* - En esta aplicación estudiaremos el *efecto seguidor* o *runner-up effect* - Anagol, S., y Fujiwara, T. (2016). [The runner-up effect](https://www.journals.uchicago.edu/doi/abs/10.1086/686746). *Journal of Political Economy*, 124(4), 927-991). - En este estudio, se estima el efecto de ser etiquetado como el *seguidor* - Los segundos y tercer lugares pueden acabar muy cerca el uno del otro en una elección pero el segundo lugar recibe la etiqueta de *seguidor*, lo que genera un salto en la probabilidad de volver a contender y ganar - Usamos regresión discontinua cuando el estado de tratamiento depende del valor que tome una variable de selección con respecto a un corte - El corte puede ser una regla explícita o una discontinuidad generada por un experimento natural --- # El *efecto seguidor* - En esta aplicación la variable de selección es la distancia entre el segundo y tercer lugar en las elecciones municipales en Brasil - Esta variable es positiva para los segundos lugares y negativa para los terceros lugares - Esto define un umbral `\(x_0=0\)` - Los candidatos muy cercanos al umbral tuvieron un desempeño parecido - Si existen discontinuidades, deberían ser aparentes en una gráfica --- # El *efecto seguidor* <div class="figure" style="text-align: center"> <img src="figures/RDrunnerup_brazil.png" alt="Fuente: Anagol & Fujiwara (2016)" width="55%" /> <p class="caption">Fuente: Anagol & Fujiwara (2016)</p> </div> --- # Datos de elecciones en Brasil .pull-left[ ```r data.brasil<-read_csv( "./brazil_runner_up.csv", locale = locale(encoding = "latin1")) ``` - Aquí uso la librería *gtsummary* ```r tab1 <- data.brasil %>% select(run, cand_ran_again,cand_winner) %>% tbl_summary( statistic = list(all_continuous() ~ "{mean} ({sd})", all_categorical() ~ "{n} / {N} ({p}%)"), digits = all_continuous() ~ 2, label = run ~ "Diferencia de votos", missing_text = "(Missing)" ) ``` ] .pull-right[
Characteristic
N = 25,254
1
Diferencia de votos
0.00 (28.23)
cand_ran_again
5,525 / 20,608 (27%)
(Missing)
4,646
cand_winner
2,400 / 20,608 (12%)
(Missing)
4,646
1
Mean (SD); n / N (%)
] --- # Gráficos de discontinuidades .pull-left[ ```r desc1 <- data.brasil %>% ggplot(aes(x=bin_run,y=bin_cand_ran_again))+ geom_point()+ geom_vline(xintercept=0, linetype="dashed", color = "red", size=1) ``` ] .pull-right[ <!-- --> ] --- # Gráficos de discontinuidades - Corremos regresiones con un polinomio cuadrado de la edad para cada lado de la discontinuidad ```r m1 <- lm(cand_ran_again ~ run+I(run^2), data=subset(data.brasil,run>-48 & run<0)) m2 <- lm(cand_ran_again ~ run+I(run^2), data=subset(data.brasil,run>=0 & run<48)) ``` - Luego calculamos los valores ajustados ```r data.brasil <- data.brasil %>% mutate(cand_ran_again_hat_left=ifelse(run>-48 & run<0,predict(m1,.),NA)) %>% mutate(cand_ran_again_hat_right=ifelse(run>=0 & run<48,predict(m2,.),NA)) ``` --- # Gráficos de discontinuidades .pull-left[ - Hacemos el gráfico de puntos y le sobreponemos los valores ajustados ```r g1 <- data.brasil %>% ggplot(aes(x=bin_run,y=bin_cand_ran_again))+ geom_point()+ geom_vline(xintercept=0, linetype="dashed", color = "red", size=1)+ geom_line(aes(x=run, y=cand_ran_again_hat_left)) ``` ] .pull-right[ <!-- --> ] --- # Gráficos de discontinuidades .pull-left[ - El gráfico completo ```r #Con los dos segmentos g2 <- data.brasil %>% ggplot(aes(x=bin_run,y=bin_cand_ran_again))+ geom_point()+ geom_vline(xintercept=0, linetype="dashed", color = "red", size=1)+ geom_line(aes(x=run, y=cand_ran_again_hat_left))+ geom_line(aes(x=run, y=cand_ran_again_hat_right)) ``` ] .pull-right[ <!-- --> ] --- # Gráficos de discontinuidades .pull-left[ - Un gráfico similar se logra para la probabilidad de ganar ```r w1 <- lm(bin_cand_winner ~ run+I(run^2), data=subset(data.brasil,run>-48 & run<0)) w2 <- lm(bin_cand_winner ~ run+I(run^2), data=subset(data.brasil,run>=0 & run<48)) data.brasil <- data.brasil %>% mutate(cand_win_hat_left=ifelse(run>-48 & run<0,predict(w1,.),NA)) %>% mutate(cand_win_hat_right=ifelse(run>=0 & run<48,predict(w2,.),NA)) g3 <- data.brasil %>% ggplot(aes(x=bin_run,y=bin_cand_winner))+ geom_point()+ geom_vline(xintercept=0, linetype="dashed", color = "red", size=1)+ geom_line(aes(x=run, y=cand_win_hat_left))+ geom_line(aes(x=run, y=cand_win_hat_right)) ``` ] .pull-right[ <!-- --> ] --- # Gráficos de discontinuidades .pull-left[ - Personalizamos para generar un gráfico parecido al del artículo ```r g4 <- data.brasil %>% filter(bin_cand_ran_again<.55 , bin_cand_winner <.55) %>% ggplot()+ geom_point(aes(x=bin_run,y=bin_cand_ran_again),shape=17,fill="black")+ geom_point(aes(x=bin_run,y=bin_cand_winner))+ geom_line(aes(x=run, y=cand_win_hat_left))+ geom_line(aes(x=run, y=cand_win_hat_right))+ geom_line(aes(x=run, y=cand_ran_again_hat_left))+ geom_line(aes(x=run, y=cand_ran_again_hat_right))+ geom_vline(xintercept=0, color = "black", size=1)+ xlab("Vote Share Difference Between 2nd and 3rd; t (%)")+ ylab("")+ scale_x_continuous(breaks = c(-50,0,50))+ scale_y_continuous(breaks=seq(0, 0.5, 0.05)) ``` ] .pull-right[ <!-- --> ] --- # Análisis paramétrico - La forma de estimar el efecto del tratamiento paramétricamente es: `$$y_{ict}=\beta \mathcal{I}(x_{ict}>0)+f(x_{ict})+\varepsilon_{ict}$$` - Arrelgamos los datos ```r #Multiplicamos variables por 100 perc.vars <- c( "cand_ran_again", "cand_winner", "cand_ran_lag", "cand_winner_lag", "cand_maj_party", "party_winner", "party_ran_again") data.brasil[perc.vars] <- lapply( data.brasil[perc.vars], function(x) x*100 ) ``` - El *corte* en este caso es el 0 ```r data.brasil <- data.brasil %>% mutate(D=ifelse(run>0,1,0)) ``` --- # Análisis paramétrico .pull-left[ - Aquí uso la librería *lfe* - La sintaxis de la fórmula es ```r y ~ x1 x2 | Efectos fijos | Instrumentos | cluster ``` - En nuestro caso ```r rd1 <- felm(cand_winner ~ D + run |0 | 0 | id_munic, data=data.brasil) ``` ] .tiny[ .pull-right[ ``` ## ## Call: ## felm(formula = cand_winner ~ D + run | 0 | 0 | id_munic, data = data.brasil) ## ## Residuals: ## Min 1Q Median 3Q Max ## -24.570 -18.516 -6.227 -1.137 99.909 ## ## Coefficients: ## Estimate Cluster s.e. t value Pr(>|t|) ## (Intercept) 7.92092 0.48313 16.395 < 2e-16 *** ## D 7.45009 0.93695 7.951 1.94e-15 *** ## run 0.18546 0.01596 11.619 < 2e-16 *** ## --- ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 ## ## Residual standard error: 30.9 on 20605 degrees of freedom ## (4646 observations deleted due to missingness) ## Multiple R-squared(full model): 0.07226 Adjusted R-squared: 0.07217 ## Multiple R-squared(proj model): 0.07226 Adjusted R-squared: 0.07217 ## F-statistic(full model, *iid*):802.4 on 2 and 20605 DF, p-value: < 2.2e-16 ## F-statistic(proj model): 792.7 on 2 and 4470 DF, p-value: < 2.2e-16 ``` ] ] --- # Análisis paramétrico .pull-left[ - Podemos especificar el cuadrado de **run** ```r rd2 <- felm(cand_winner ~ D + run+I(run^2) |0 | 0 | id_munic, data=data.brasil) ``` - O alguna otra función de **run** ```r #Especificando un coeficiente para run antes y despues rd3 <- felm(cand_winner ~ D + run + run*D |0 | 0 | id_munic, data=data.brasil) #O una combinación de no linealidades y cambios de pendiente rd4 <- felm(cand_winner ~ D + run + I(run^2) + run*D + I(run^2)*D |0 | 0 | id_munic, data=data.brasil) ``` ] .tiny[ .pull-right[ ``` ## ## Call: ## felm(formula = cand_winner ~ D + run + I(run^2) | 0 | 0 | id_munic, data = data.brasil) ## ## Residuals: ## Min 1Q Median 3Q Max ## -24.731 -18.468 -6.159 -1.190 99.814 ## ## Coefficients: ## Estimate Cluster s.e. t value Pr(>|t|) ## (Intercept) 7.845e+00 4.415e-01 17.769 < 2e-16 *** ## D 7.450e+00 9.370e-01 7.951 1.94e-15 *** ## run 1.855e-01 1.596e-02 11.619 < 2e-16 *** ## I(run^2) 9.613e-05 3.043e-04 0.316 0.752 ## --- ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 ## ## Residual standard error: 30.9 on 20604 degrees of freedom ## (4646 observations deleted due to missingness) ## Multiple R-squared(full model): 0.07226 Adjusted R-squared: 0.07213 ## Multiple R-squared(proj model): 0.07226 Adjusted R-squared: 0.07213 ## F-statistic(full model, *iid*): 535 on 3 and 20604 DF, p-value: < 2.2e-16 ## F-statistic(proj model): 747.2 on 3 and 4470 DF, p-value: < 2.2e-16 ``` ] ] --- # Análisis paramétrico .pull-left[ - Para un resumen de los resultados puedo usar *stargazer* ```r tab1 <- stargazer(rd1, rd2, rd3, rd4, title="Comparación de especificaciones de RD", type="text", df=FALSE, digits=2) ``` ] .pull-right[ .tiny[ ``` ## [1] "" ## [2] "Comparación de especificaciones de RD" ## [3] "=====================================================" ## [4] " Dependent variable: " ## [5] " ---------------------------------" ## [6] " cand_winner " ## [7] " (1) (2) (3) (4) " ## [8] "-----------------------------------------------------" ## [9] "D 7.45*** 7.45*** 7.45*** 10.46***" ## [10] " (0.94) (0.94) (0.94) (1.40) " ## [11] " " ## [12] "run 0.19*** 0.19*** 0.21*** 0.41*** " ## [13] " (0.02) (0.02) (0.01) (0.06) " ## [14] " " ## [15] "I(run2) 0.0001 0.004***" ## [16] " (0.0003) (0.001) " ## [17] " " ## [18] "D:run -0.04 -0.85***" ## [19] " (0.03) (0.12) " ## [20] " " ## [21] "D:I(run2) 0.01*** " ## [22] " (0.003) " ## [23] " " ## [24] "Constant 7.92*** 7.84*** 8.42*** 9.93*** " ## [25] " (0.48) (0.44) (0.48) (0.74) " ## [26] " " ## [27] "-----------------------------------------------------" ## [28] "Observations 20,608 20,608 20,608 20,608 " ## [29] "R2 0.07 0.07 0.07 0.08 " ## [30] "Adjusted R2 0.07 0.07 0.07 0.07 " ## [31] "Residual Std. Error 30.90 30.90 30.90 30.86 " ## [32] "=====================================================" ## [33] "Note: *p<0.1; **p<0.05; ***p<0.01" ``` ] ] --- # Resultados <div class="figure" style="text-align: center"> <img src="figures/RDrunnerup_brazil_regression.png" alt="Fuente: Anagol & Fujiwara (2016)" width="100%" /> <p class="caption">Fuente: Anagol & Fujiwara (2016)</p> </div> - Vemos las consecuencias de elegir un ancho de ventana más pequeño - El resultado con un ancho de ventana con la muestra completa es de un efecto de 10.46 (error estándar de 1.40) - Con un ancho de ventana de alrededor de 6 puntos porcentuales entre el primer y segundo lugar el error es de 2.54 --- # *rdrobust* .pull-left[ - Hay toda una literatura para analizar el *trade-off* entre sesgo y varianza en la elección del ancho de ventana - [Calonic, Cattaneo & Titiunik, (2015)](https://rdpackages.github.io/references/Calonico-Cattaneo-Titiunik_2015_R.pdf) proponen distintas formas de implementar algoritmos para estimar la discontinudad de la regresión con procedimientos totalmente dependiente de los datos (*data driven*), *rdrobust* - Una de las funciones más útiles de *rdrobust* es crear el gráfico de la discontinuidad, usando un polinomio de orden 4 por defecto ] .pull-right[ ```r rdres <- rdplot(y = data.brasil$cand_winner, x = data.brasil$run, title = "Efecto seguidor", y.label = "Probabilidad de ganar en t+1", x.label = "Distancia con respecto al 3er lugar en t") ``` ] --- # *rdrobust* .pull-left[ - Nos da detalles de cómo realiza la estimación y la selección del número de *bins* .tiny[ ```r summary(rdres) ``` ] ] .pull-right[ - Pueden personalizar su gráfico para hacerlo mucho mejor que la versión por defecto <!-- --> ] --- # *rdrobust* - Otra función es *rdselect*, que permite la estimación del ancho de banda óptimo - Hay un *trade-off* entre sesgo y varianza en la selee - Entre más grande sea la ventana, tenemos más observaciones y nuestros estimadores serán más precisos - Pero al mismo tiempo, entre más grande sea la ventana, más grande será el sesgo en la estimación de la pendiente de la línea de regresión al incluir observaciones cada vez más disimiles - Esta teoría esta formalizada en [Imbens y Kalyanaraman (2012)](https://watermark.silverchair.com/rdr043.pdf?token=AQECAHi208BE49Ooan9kkhW_Ercy7Dm3ZL_9Cf3qfKAc485ysgAAAvwwggL4BgkqhkiG9w0BBwagggLpMIIC5QIBADCCAt4GCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMNLn6ofuEpZYIbPhSAgEQgIICr_9ZaTO9ZtUyeddqejFel2IauNOEoeJKXH5QDHk3IgFt03mG523iBXeq4wIS5CBcK1W7AjY4H3-kJb6r2H09xeES5zsTEXnhxrKoyFmOnWDjj679CIRFcQkZIzasP83tUkwZM0LVPjiVeJAC-HZiR3D1h75wwgwK1od1oycJIc2wZRxcgr4QeUhWtpe6JO34YqiDPOYN7sMSqfVdwXJS53-EXnr0qCQM9PFY5T8cxeDDGhEXhG0DVs3lTwzHDZEbbX8-7EkLeBMaic7aqNaQgiQuc9DaLM1m23p9cpW0JeaiLLvdKm13hHLP3csRaf_TVKFCGGADjweQYGu49CTdNhnDbFyh0W0eVDirPndKSM-2REG7Y-6AZCeCbYl3TmfBLpIXDjN3pPAzLJmbWbik6vrvv9n6if_1eT2CjD3wMRxY01jFHYRZaXlttxTOWS3uBkNo0hNtHZLdvgw6tXH6yA38TPXfG4RIA3JfKtDOGL-9Ph7P02tSvjF2YSjGmoBo9IRjyhgL_7Ur_saa4_z2NWx-FCOmDVGSL2cxG1Nkam58oF7R0aochpxm1VUp1LYizL3UzuhpG_KpRJ1zSQGGXyCpV5gc8HZQ8p-nhnY5wtUSZ5qgILv6sDsccldEJ90LbmSz5fdlPo7X45pYT60oid5aDJql3noGb_UW2KwAXgVxqMSfCTvCAj4TgQna3LtcWWbye0whzQzC1C7Ctu0NuEMt9_0rD-YXnJj69HV-lz7dPKgIEur0IhW_Nq9hPg_EfiWnm8HbUIAiH0OujoTGB2mR-wYYOp_GwXLoCc0ABqX36IdUQIDdgw8rSE0hIAhtD2nMKnwdDzGGKATehLPFR-3tlTWnWGrHiPJVfsFHIiSS5jkRqH349-9nIiRdRDSYiqibOiXHsSP5TUTpi8iAig) --- # *rdrobust* - En la *antiguedad*, es decir, cuando revisé y repliqué por primera vez este artículo, la función *rdbwselect* en Stata permitía recuperar el ancho de banda óptimo aquí reportado (12.57) <div class="figure" style="text-align: center"> <img src="figures/RDrunnerup_brazil_regression.png" alt="Fuente: Anagol & Fujiwara (2016)" width="100%" /> <p class="caption">Fuente: Anagol & Fujiwara (2016)</p> </div> --- # *rdrobust* .pull-left[ - Usando *rdbwselect* se pueden calcular distintos anchos de ventana - Después de las varias actualizaciones al paquete, ya no he podido obtener exactamente el mismo resultado ] .pull-right[ .tiny[ ``` ## Call: rdbwselect ## ## Number of Obs. 20608 ## BW type All ## Kernel Uniform ## VCE method NN ## ## Number of Obs. 10304 10304 ## Order est. (p) 1 1 ## Order bias (q) 2 2 ## Unique Obs. 10292 10292 ## ## ======================================================= ## BW est. (h) BW bias (b) ## Left of c Right of c Left of c Right of c ## ======================================================= ## mserd 11.398 11.398 19.626 19.626 ## msetwo 17.883 10.568 31.155 18.697 ## msesum 13.231 13.231 22.733 22.733 ## msecomb1 11.398 11.398 19.626 19.626 ## msecomb2 13.231 11.398 22.733 19.626 ## cerrd 7.232 7.232 19.626 19.626 ## certwo 11.347 6.705 31.155 18.697 ## cersum 8.395 8.395 22.733 22.733 ## cercomb1 7.232 7.232 19.626 19.626 ## cercomb2 8.395 7.232 22.733 19.626 ## ======================================================= ``` ] ] --- # *rdrobust* .pull-left[ - Noten que si especificamos el ancho de ventana de 12.57, obtenemos el resultado preferido por los autores en el artículo ```r rd5 <- felm(cand_winner ~ D + run |0 | 0 | id_munic, data=subset(data.brasil,bw<12.57)) rd6 <- felm(cand_winner ~ D + run |0 | 0 | id_munic, data=subset(data.brasil,bw<12.57/2)) rd7 <- felm(cand_winner ~ D + run |0 | 0 | id_munic, data=subset(data.brasil,bw<12.57*2)) tab2 <- stargazer(rd5, rd6, rd7, title="Comparación de especificaciones de RD (2)", type="text", df=FALSE, digits=2) ``` ] .pull-right[ .tiny[ ``` ## [1] "" ## [2] "Comparación de especificaciones de RD (2)" ## [3] "=================================================" ## [4] " Dependent variable: " ## [5] " -----------------------------" ## [6] " cand_winner " ## [7] " (1) (2) (3) " ## [8] "-------------------------------------------------" ## [9] "D 8.31*** 7.01*** 8.84*** " ## [10] " (1.81) (2.54) (1.29) " ## [11] " " ## [12] "run 0.18 0.36 0.14*** " ## [13] " (0.13) (0.35) (0.04) " ## [14] " " ## [15] "Constant 8.86*** 9.89*** 7.67*** " ## [16] " (0.94) (1.34) (0.67) " ## [17] " " ## [18] "-------------------------------------------------" ## [19] "Observations 5,946 3,136 10,482 " ## [20] "R2 0.02 0.02 0.04 " ## [21] "Adjusted R2 0.02 0.02 0.04 " ## [22] "Residual Std. Error 33.24 33.76 32.01 " ## [23] "=================================================" ## [24] "Note: *p<0.1; **p<0.05; ***p<0.01" ``` ] ] --- # Más materiales - Consideren echarle un ojo al tutorial que Matias Catteneo dio [en el Chamberlain Seminar](https://www.chamberlainseminar.org/past-seminars/autumn-2020#h.41tsl12q6tcb) .center[ <iframe width="560" height="315" src="https://www.youtube.com/embed/rKH88HK0S-o" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> ] - El tutorial está en Stata, pero las ideas son fácilmente trasladadas a cualquier software --- # Próxima sesión - Empezaremos a estudiar control sintético - Usaremos un par de clásicos para introducir el concepto (de hecho replicarán el primero en la tarea 3) y dar la formulación básica del modelo - Abadie, A., Diamond, A., & Hainmueller, J. (2010). Synthetic control methods for comparative case studies: Estimating the effect of California’s tobacco control program. *Journal of the American statistical Association*, 105(490), 493-505. - Abadie, A., Diamond, A., & Hainmueller, J. (2015). Comparative politics and the synthetic control method. *American Journal of Political Science*, 59(2), 495-510. - El resumen de lo que deben saber está en - Abadie, A. (2019). Using synthetic controls: Feasibility, data requirements, and methodological aspects. *Journal of Economic Literature*. --- class: center, middle Presentación creada usando el paquete [**xaringan**](https://github.com/yihui/xaringan) en R. El *chakra* viene de [remark.js](https://remarkjs.com), [**knitr**](http://yihui.org/knitr), y [R Markdown](https://rmarkdown.rstudio.com). Material de clase en versión preliminar. **No reproducir, no distribuir, no citar.**