forked from ClasesMOW/tutorial_quesirva_ayudantia2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathccs-ayudantia2-tutorial.qmd
1790 lines (1119 loc) · 74.3 KB
/
ccs-ayudantia2-tutorial.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
title: "Datos observacionales y estrategias de identificación"
subtitle: "Ayudantía 2- Curso métodos en CCS"
#author:
# name: Melanie Oyarzún
# affiliation: Universidad del Desarrollo
# email: [email protected]
#date: "`r format(Sys.time(), '%d %B %Y')`"
format:
html:
theme: cosmo
toc: true
toc-depth: 3
embed-resources: true
self-contained-math: true
keep-md: true
editor: source
execute:
eval: true
warning: false
number-sections: false
crossref:
chapters: true
---
# I. Bienvenida al tutorial
Hola, este tutorial está escrito como la actividad de aplicación de la ayudantía 2 del curso
La idea es ir desarrollando el tutorial en paralelo a la sesión y que puedan tomar apuntes adicionales directamente en el notebook. Está escrito como .qmd, un notebook Quarto y puede ser editado en rstudio, vscode o cualquier otro IDE o editor de texto. El lenguaje utilizado para el procesamiento y análisis es R.
Se recomienda crear un fork al repositorio y trabajar en su propia rama.
**Como funciona**
- El tutorial se estructura como un proyecto de investigación ficticio completo, que empezaremos cargando datos brutos hasta obtener e interpretar coficientes de regresión.
- Lo métodos a cubir son:
1. Regresión lineal y método de diferencias (en datos experimentales)
2. Logit y probit
3. Variables Instrumentales
4. Regresión Discontinua
5. Diferencias en Diferencias
6. Efectos fijos, aleatorios y modelos jerárquicos
7. Matching
- Los datos y notebook son descargables en el siguiente [repositorio de proyecto](https://github.com/ClasesMOW/ayudantia2ccs) así que se recomienda correr todo en tu propio computador o en una instancia en la nube.
- Este tutorial es una adaptación del trabajo de [Hans H. Sievertsen](https://hhsievertsen.shinyapps.io/applied_econ_with_R_dynamic), agregando la estimación con métodos de de métodos de matching y algunos cambios a la situación a analizar.
<br>
## 1 Pregunta de investigación y datos
Vamos a usar una situación ficticia, con datos simulados para poder aplicar de manera consisa los diferentes tipos de estrategias de identificación revisadas en clase. Algunas (que se supone ya manejan) las revisaremos muy rápidamente y en otras, vamos a tener mayor énfasis.
### 1.1 Pregunta de investigación
Nuestro objetivo es responder la siguiente pregunta **ficticia** de investigación:
> Asistir a cursos de verano mejora los resultados académicos?
Para responder esta pregunta, usaremos unos datos **ficticios y simulados**
### 1.2 Contexto ficticio
La pregunta de investigación se inspira en trabajos como el de [Matsudaira (2007)](https://www.sciencedirect.com/science/article/pii/S0304407607001194?casa_token=hnnF764CKPoAAAAA:5b9WhCManNDsdW4SmOHnnzNr0fZIarW8s6EsvpQW7MdUt470eNPmN2T8IFCsNc6Iajew5tEeNA) e intervenciones en estudiantes de bajo nivel socioeconómico por [Dietrichson et al ( 2017)](https://journals.sagepub.com/doi/abs/10.3102/0034654316687036).
El **escenario ficticio** es el siguiente:
- Para un conjunto de colegios en una comuna, existe la opción de asistir a un curso de verano intensivo durante el verano entre 5 y 6to básico.
- El curso de verano se enfoca en mejorar las habilidades académicas de preparar la prueba de admisión a la universidad vigente (PSU en ese momento)
- El curso de verano es gratuito, pero para ser matriculados requiere que los padres se involucren en un proceso.
- Estamos interesados en testear el impacto de la participación en el curso en los resultados académicos de los estudiantes.
### 1.3 Datos ficticios dispobibles
1. school_data_1.csv
- Usamos esta data para ejemplificar como cargar data guardada en formato csv.
- Este dataset tiene información sobre cada individuo (con identificador id), la escuela a la que asiste, un indicador si participó en el curso de verano, sexo, ingreso del hogar (en logaritmo), educación de los padres, resultados en una prueba estandarizada que se realiza a nivel de la comuna tanto para el año 5 como para el año 6.
2. school_data_2.dta
- Usamos esta data para ejemplificar como cargar data guardada en formato STATA.
- Este dataset tiene información de cada individuo (con identificador id).
- Este dataset tiene la información si el individuo recibió la carta de invitación para participar del curso de verano.
3. school_data_3.xlsx
- Usamos este dataset para practicar como cargar datos guardados en formato Microsoft Excel.
- Este dataset incluye datos sobre cada individuo (con identificador id)
- Este dataset tiene información de rendimiento académico antes y después del curso de verano.
# II. Preparación de los datos
En esta sección vamos a preparar los datos para el análisis. Este proceso generalmente incluye cargarlos, inspeccionarlos, limpiar y dar la estructura deseada. También revisaremos estadísticas descriptivas que nos den una idea antes de estimar cualquier modelo.
En la vida real, este proceso suele ser bastante largo, laborioso e implica volver sobre lso pasos anteriores múltiples veces. También invlocura tomar desiciones por parte de los investigadores, por lo cual la documentación de esta fase es especialmente importante.
En nuestro caso, será bastante lineal y directo, ya que son datos ficticios simulados. Pero en la realidad, no suele ser así.
## 0. Crear el proyecto y clonar el repositorio
Este proyecto está sustentado en el repositorio de github. Clónelo y luego carguelo en su computador.
## 1 Cargar los datos
### 1.1 Intalar y cargar paquetes
Para poder cargar los datos, necesiamos los paquetes adecuados. Siempre hay multiples formas de hacer las cosas en cualquier lenguaje o programa. En este caso, usaremos la función `read_csv()` del paquete *readr*. Para poder usarlo, debemos estar seguros de que está instalado.
Si no está instalado, podemos hacerlo con la función `install.packages("[nombre paquete a instalar]")`
```{r}
#| eval: false
install.packages("readr")
```
Si el paquete ya está instalado, para poder usarlo necesitamos tenerlo cargado en nuestra librería. Para esto usamos la función `library("[nombre paquete a cargar]")`.
```{r}
library(readr)
```
Cada paquete lo debemos instalar solo una vez por computador, pero debemos cargarlo en cada sesión para poder utilizarlo.
### 1.2 Cargar datos csv
Con *readr* estamos en condiciones de usar la función `read_csv()` para cargar la primera base de datos.
Vamos a cargar *school_data_1.csv* agregando el path a los datos en paréntesis. Puede reemplazar por el path correspondiente o usar el paquete auxiliar `here`
PS. notemos que tambien estamos incluyendo comentarios en los bloques de código. Las líneas que empiezan con el símbolo `#` son ignoradas por R.
```{r}
# cargar readr package
library("readr", "here")
#prueba el directorio que te dice con here
here::here()
```
Entonces, dentro de `here("[Escribes el directorio relativo]")` actuará como el directorio relativo sin errores. Entonces, cargamos los datos y los asignamos a un data frame.
```{r}
school_data_1 <- read.csv(here::here("data_raw/school_data_1.csv"))
```
Usualmente, después de cargar un dataset es útil visualizarlo. Empleamos la función `head()` para ver sus primeras 6 observaciones.
```{r}
#| warning: false
#| eval: true
head(school_data_1)
```
Una segunda alternativa es descargar y cargar los datos directamente en R desde internet. Puede ser cualquier link directo o, si está alojado en github, tienes que asegurarte de que sea un repositorio público.
```{r}
#| eval: false
#school_data_1 <- read.csv("https://github.com/ClasesMOW/ayudantiasccs/blob/main/data_raw/school_data_1.csv")
```
### 1.3 Cargar datos STATA
En Ciencias Sociales y Economía es muy comun contar con datos para ser utilizados en el programa STATA, estos son archivos que terminan en *.dta*. Para cargarlos, vamos a usar *HAVEN* del paquete *tidyverse*. También usaremos muchas otras funciones de ese paquete, asi que es buen momento para cargalo.
```{r}
library(tidyverse)
school_data_2<- haven::read_dta(here::here("data_raw/school_data_2.dta"))
```
Usemos el comando `tail()` para ver las últimas 10 entradas.
```{r}
# print las últimas 8 filas
tail(school_data_2,n=10)
```
### 1.4 Cargar datos Microsoft Excel
Finalmente, cargaremos el tercer dataset guardado como hoja de cálculo de Excel *.xlsx*. Para esto usaremos el paquete *readxl* que viene incluido en *tidyverse*. Luego de asignarlo a un dataframe, démosle una mirada con `glimpse()` (también del tidyverse).
```{r}
school_data_3 <- readxl::read_xlsx(here::here("data_raw/school_data_3.xlsx"))
glimpse(school_data_3)
```
## 2. Unir los datasets
Tenemos 3 bases de datos con información diferente de los mismos individuos. Generalmente es buena idea tener una sola gran tabla con toda esta información, especialmente si estimaremos modelos en base a ésta.
La base de datos 1 y 2 tienen una forma similar: los individuos son filas y las variables columnas y hay una sola fila para cada individuo.
Para hacerlo, podriamos usar varias alternativas.
Una alternativa es usar la función nativa `merge( )`. En esta función, primero mencionamos los datasets a unir, luego informamos cual es la(s) columnas(s) que debe usar para unir ambos datasets con `by=....`. Por defecto, R incluye todas las filas que están en ambos datasets (basados en la variable *by*), pero podemos fijar `all=TRUE` para mantener todas las filas que están en ambos datasets o `all.x=TRUE` para mantener todas las filas coincidentes y las del primer dataset or `all.y=TRUE` para guardar todas las filas del segundo dataset.
Veamos un ejemplo con los dos primeros datasets. Luego usemos `dim()` para conocer las dimensiones del nuevo dataset unido en términos de filas y columnas.
```{r }
# Unir school_data_1 con school_data_2
school_data_merged <-merge(school_data_1,school_data_2, by="person_id")
# Revisamos las dimensiones
dim(school_data_merged)
```
Notemos que el dataset unido tiene 3491 filas y 9 columnas. Unimos todas las filas y agregamos al dataset de información del estudiante si recibió o no la carta (school_data_2)
¿Qué ocurre si las columnas tienen igual nombre? R va a renombrarlas automáticamente agregando un sufijo *.x* (a la columna del primer dataset) e *.y* ( a la columna del segundo dataset).
Entonces, en el siguiente bloque:
1. Unimos *school_data_1* y *school_data_2* usando como variable de unión *person_id* y guardamos el dataset unido como *school_data*.
2. Unimos *school_data_3* con *school_data* y sobre-escribimos *school_data*.
Notar que acá unimos por las columnas *person_id* y *school_id*. Esto no es realmente necesario porque cada estudiante con id única tiene un solo colegio, pero sirve de ejemplo en como usar más de una columna mediante `c()`. 3. Usamos la función `summary()` para obtener una estadística descriptiva de las variables en el dataset unido.
```{r }
# Merge school_data_1 y school_data_2 y guardamos como school_data_merged
school_data_merged<-merge(school_data_1,school_data_2,by="person_id")
# Merge school_data_3 con school_data_merged
school_data_merged<-merge(school_data_merged,school_data_3,by=c("person_id","school_id"))
# Estadísticas descriptivas de cada variable
summary(school_data_merged)
```
Otra opción de hacer lo mismo es con join del paquete `dplyr` del `tidyverse`.
[![Diferentes tipos de join en dplyr](images/jointypes.png){fig-alt="Diferentes tipos de join en dplyr" fig-align="center"}](https://statisticsglobe.com/r-dplyr-join-inner-left-right-full-semi-anti) (foto sacada de [link](https://statisticsglobe.com/r-dplyr-join-inner-left-right-full-semi-anti), puedes ver ejemplos detallados de los diferentes tipos de join)
En este caso, podríamos hacer en un pipe `%>%` dos left joins seguidos.
```{r}
school_data_merged <- school_data_1 %>%
left_join(school_data_2, by="person_id") %>%
left_join(school_data_3, by=c("person_id", "school_id"))
```
## 3. Lipiar los datos
### 3.1 Tidyng los datos
Ahora que hemos unido las bases de datos, trataremos de que satisfazgan los principios de [Tidy Data](https://vita.had.co.nz/papers/tidy-data.pdf).
Un data frame se considera "tidy" (Según Hadley) si se cumplen las siguientes condiciones:
- Cada columna (variable) contiene todas las medidas de la misma data/feature/caracteristica de las observaciones.
- Cada fila contiene medidas de la misma unidad de observación.
(puedes profundizar y ver más ejemplos aplicados en <https://sscc.wisc.edu/sscc/pubs/DWE/book/6-2-tidy-data.html> )
Uno de estos, es que cada columna debe ser una variable y cafa fila una unidad de observación.
Si inspeccionamos el número de columnas:
```{r}
# Alternativamente podemos usar la función nrow() para obtener el número de filas
ncol(school_data_merged)
```
Son 17, pero tenemos 9 variables.
```{r}
head(school_data_merged)
```
Es porque tenemos puntajes de las pruebas del año 2 al 10. Este tipo de datos son de *panel*
![](images/paste-4C87CB1E.png)
Generalmente, que hagamos con este tipo de datos depende del tipo de modelos que queramos usar. Si bien el formato wide es facil de entender, generlamente para modelos y análisi preferimos que esté en formato long. Especialmente cuando modelamos incluyendo efectos fijos También es este el que adhiere a los principios tidy de mejor manera.
Para cambiar a long, usamos `pivot_longer()`
```{r}
# lCargamos el paquete tidyr package. Ya está incluido en tidyverse, pero también se puede llamar por si solo.
library("tidyr")
# make data tidy (make long)
school_data_tidy<-school_data_merged%>%
tidyr::pivot_longer(
cols = starts_with("test_year"),
names_to = "year",
names_prefix = "test_year_",
names_transform = list(year = as.integer),
values_to = "test_score",
)
# ncol nos da el número de columnas del nuevo dataset
ncol(school_data_tidy)
```
Ahora tenemos nuestros datos listos para que los inspeccionemos.
### 3.2 Selección de muestra
Ya que contamos con datos que siguen los principios de tidy data, lo siguiente es seleccionar la muestra apropiada. En este trabajo, los unicos problemas que podríamos enfrentar son relacionados con valores faltantes o missing. Para inspeccionarlos vamos a usar la función `skim()` del paquete `skimr`, esta función nos muestra los vaores faltantes en nuestro dataset de una manera global.
```{r}
# Cargar skimr
library("skimr")
# Usamos skim() para inspeccionar los datos
skim(school_data_tidy)
```
Con esta función podemos ver facilmente cuantas filas y comunas son, los tipos de varables y número de missing values. Además la media, desviación estándar, percentiles e incluso un histograma para cada variable.
En estos casos, para *parental_schooling* tenemos 45 missing y para *test_score* 11. Asumamos que estos valores missing son random y deseamos remover estas filas. Para esto usamos `filter()`. Esta funcion toma dos argumentos, el dataset a filtrar y la condición para que se mantenga en el dataset, en este caso que no sea na o `!is.na(partental_schoolin)`. La función `is.na()` es verdad cuando el elemento en `()` es missing y usamos `!`para mostrar que queremos lo contrario a esto condición. En otras palabras queremos que la educaión parental no esté missing.
```{r}
# Seleccionamos las columnas sin missing values
school_data_selected<-dplyr::filter(school_data_tidy,!is.na(parental_schooling),!is.na(test_score))
# Usamos skim() para revisar los datos nuevamente
skim(school_data_selected)
```
Hemos removido todos los missing.
### 3.3 Modificar los datos
Un último paso que haremos antes de hacer estadística decsriptiva es modificar los nombres de algunas columnas para que se vean bien en la tablas.
Vambos renombrar la variable summpercap a summerschoolo. Lo hacemos con `rename()` del paquete dplyr. Esta función tiene una sintaxix similar a `filter()`
```{r}
# renombremos summercamp a summerschool
analysisdata<-rename(school_data_selected, summerschool=summercamp)
# usamos head para visualizar las primeras 6 observaciones
head(analysisdata)
```
En un siguiente paso, vamos a transformar los puntajes en la pruebas a una variable que tenga media 0 y desviación estándar 1. Es mejor trabajar con variables estandarizadas, ya que no requieren conocer el conexto específico de la medida y es más facil de comunicar y comparar.
```{r}
# Estandarizamos los resultados de las pruebas
# Agrupamos analysisdata por year
analysisdata<-group_by(analysisdata,year)
# Creamos una nueva variable con mutate. Como queremos que reemplace a la anterior, usamos su mismo nombre.
analysisdata<-mutate(analysisdata, test_score=(test_score-mean(test_score))/sd(test_score))
# mostremos la media
print(paste("Mean of test score:",mean(analysisdata$test_score)))
```
Esto tambien podemos hacerlo dentro de un pipe:
```{r}
# Estandarizamos los resultados de las pruebas
analysisdata<- analysisdata %>%
group_by(year) %>% # Agrupamos analysisdata por year
mutate(test_score=(test_score-mean(test_score))/sd(test_score))
# mostremos la media
print(paste("Mean of test score:",mean(analysisdata$test_score)))
```
Podemos comprobar que efectivamente la media y desviación estándar corresponden a dichos valores:
```{r}
# ver la media de test_score
print(paste("Mean of test score:",mean(analysisdata$test_score)))
# Ver la desviación estándar de test_score
print(paste("SD of test score:",sd(analysisdata$test_score)))
```
Notar que si aplicamos `mean()` o `sd()` o cualquier otra función matemática a columnas que tienen valores missing, también dará un valor missing a menos que usemos la opción na.rm=FALSE.
Ya estamos bien, ahora pasamos a conocer mejor nuestros datos con estadística descriptiva.
## 4. Estadística descriptiva
Hasta ahora, cargamos datos en diversos formatos (csv, dta y xlsx) los unimos, re-estructuramos el dataset, removimos valores missing y generamos algunas transformaciones. El siguiente paso es empezar a conocer nuestros datos. Para esto haremos tablas de estadísticas descriptivas y también algunos graficos descriptivos.
### 4.1 Tablas de estadística descriptiva
Hasta ahora, ya conocemos dos maneras de calcular estadísticas resumen:
1. `sumary()` de R base. Esta función en realidad funciona en muchos tipos de objetos de R y suele dar un bien resumen. Pero no en el formato de una tabla exportable a un documento latex, word o etc. que podamos presentar en nuestra investigación o resultados.
2. `skim()` del paquete *skimr*
#### 4.1.1 Tabla de estadísticas descriptivas "lista para llevar"
Una forma rápida de obtener una tabla de estadísticas descriptivas es con un primo de `skim()` del paquete *modelsummary*.
```{r}
# cargar modelsummary
library("modelsummary")
# creamos una tabla de estádisticas descriptivas
analysisdata%>%
filter(year==2)%>%
select(female,starts_with("paren"),letter,summerschool,test_score)%>%
datasummary_skim( fmt="%.2f")
```
Esta se puede exportar a varios formatos, como word o latex con el parámetro `output=["ruta donde guardar la tabla"]`. Primero hagámoslo en word:
```{r}
# load modelsummary
library("modelsummary")
# create a summary stat table in Latex format
analysisdata%>%
filter(year==2)%>%
select(female,starts_with("paren"),letter,summerschool,test_score)%>%
datasummary_skim( fmt="%.2f",
histogram=FALSE, output="tab_summary_statistics.docx")
```
La guardó en la carpeta por default. Si quieremos que esté en nuestra carpeta de output, podemos usar el paquete here::
```{r}
# load modelsummary
library("modelsummary")
# create a summary stat table in Latex format
analysisdata%>%
filter(year==2)%>%
select(female,starts_with("paren"),letter,summerschool,test_score)%>%
datasummary_skim( fmt="%.2f",
histogram=FALSE, output=here::here("output/tab_summary_statistics.docx") )
```
Ahora la hacemos en formato latex:
```{r}
# load modelsummary
#library("modelsummary")
# create a summary stat table in Latex format
#analysisdata%>%
# filter(year==2)%>%
# ungroup() %>%
# select(female,starts_with("paren"),letter,summerschool,test_score)%>%
# datasummary_skim( fmt="%.2f",
# histogram=FALSE, output=here::here("output/tab_summary_statistics.tex"))
```
#### 4.1.2 Tablas customizadas
Para customizar nuestra tabla aun más, podemos usar la función `datasummary()` tambien del pquete modelsummary. Esta función perimte que definamos una *fórmula* de la estructura de la tabla.
```{r}
# creamos una tabla de estadísticas descriptivas resumen
datasummary(female+parental_schooling+
letter+test_score~Factor(summerschool)*(Mean+SD),
sparse_header = FALSE,
data=filter(analysisdata,year==2))
```
En este ejemplo: - Listamos las variables a incluir separadas por un + similar to in a `female+parental_schooling+pa...` - Usamos a `~` para separar la lista de variables en la fórmula - Usamos la formula `Factor(summerschool)*(Mean+SD)` para mostror la media y desviación estándar por separado para cada grupo creado por la variable *summerschool*. - Usamos `Factor()` para indicarle a R que debería considerar *summerschool* como una variable binaria.
- También podríamos haber hecho esto al limpiar y procesar la base de datos. - Podemos tambien invertir el orden `(Mean+SD)*Factor(summerschool)`, lo que entonces daría primero la media y devsicacion estándar y luego separar por los valores de la escuela de verano.
- Usamos la opción `sparce_header=FALSE` para especificar que queremos incluir e *summerschool* como título.
#### 4.1.3 Nombres de variables
Hasta ahora hemos utilizado los nombres de variables directamente en las tablas. Estó no es muy estético, podemos cambiarle el nombre directamente con espacios y mayúsculas en el nombre para darle un emjor aspecto. También es posible asignanrle una "label" o etiqueta cuando creamos la tabla, como lo vemos en el ejemplo:
```{r}
# load modelsummary
library("modelsummary")
# create a summary stat table
datasummary((`Female`=female)+
(`Parental schooling (years)`=parental_schooling)+
(`Parental income (log)`=parental_lincome)+
(`Received reminder letter`=letter)+
(`Test Score`=test_score)~
(`Attended summer school`=Factor(summerschool))*
(Mean+SD),
sparse_header = FALSE,
data=filter(analysisdata,year==2))
```
#### 4.1.4 Exportando nuestras tablas
Podemos exportar nuestras tablas a word o Latex podemos usar la expresión `output="[nombre del archivo y ruta]"`. En `datasummary()` se ve así:
```{r}
# load modelsummary
library("modelsummary")
# create a summary stat table
datasummary((`Female`=female)+
(`Parental schooling (years)`=parental_schooling)+
(`Parental income (log)`=parental_lincome)+
(`Received reminder letter`=letter)+
(`Test Score`=test_score)~
(Mean+SD+P25+P50+P75),
sparse_header = FALSE,
data=filter(analysisdata,year==2),
output = here::here("output/tab_descriptive_statistics.docx"))
```
### 4.2 Gráficos de estadística descriptiva
Vamos a usar principalmente la librería ggplot2 para crear nuestros gráficos.
#### 4.2.1 Scatter plot (o gráfico de dispersión)
Nuestro primer grafico es un gráfico de dispersión. En este queremos ver como dos variables se relacionan en los datos. En estos podemos inlcuir curvas que describan la relación.
En este gráfico
1. Iniciamos un objeto de grafico `ggplot()` usando los datos *analysisdata* que ya procesamos y la vamos a filtrar solo para incluir el año 5.
2. Especificamos que *parental_income* sea el eje x y *test_score* el eje y en `aes()`
3. Usamos `geom_point()` para incluir los puntos que describen la dispersión.
4. Usamos `geom_smooth()` para agregar una linea que describa la relación.
5. Usamos `theme()` para darle formato a los elementos
6. Usamos `labs()` para incorporar etiquetas a los ejes y al título.
```{r}
# load ggplot2
library("ggplot2")
# creamos un scatter plot entre ingreso parental y resultados academicos en el año 5
ggplot(analysisdata%>%filter(year==5),
aes(x=parental_lincome,y=test_score))+
geom_point(alpha=0.1,size=0.85,color="#63a668")+
geom_smooth(color="#145c21") +
theme(panel.background = element_rect(fill="#ededed",color="#ededed"),
plot.background = element_rect(fill="#ededed",color="#ededed"),
panel.grid.major = element_line(colour="#a3a3a3",size=0.1))+
labs(x="Log(Parental Income)",y="Test Score (Mean=0,SD=1)", title="Test scores & Parental income")
```
#### 4.2.2 Graficos de barras y boxplot
Del grafico anteriro podemos observar que los resultados de los test se correlacionan con ingreso parental. Esto no es una sorpresa. Veamos si tambien el asistir a la escuea de verano se correlaciona con estas características individuales.
Primero, creamos un scatter plot de educación de los padres y test score en el año anterior a la escuela de verano. Usemos el mismo código de arriba pero en lugar de ingreso, usamos educación parental. Segundo, creemos un gráfico de barras que muestre que los resultados previos a la asistencia a la escuela de verano. Tercero, creamos un box plot de ingreso de los padres y si asistieron o no a la escula de verano.
Nuestro codigo ahora tiene estos elementos.
- Creamos el objeto `ggplot()` y cargamos la data y el tema. Este objeto es llamado un *rawchart*
- Creamos 3 gráficos basados en *rawchart*, cada uno lo guardamos con un nombre.
- Para el de barras, usamos `geom_bar()` y fijamos `stat="summary", fun="mean"` para decirle a R que cree un grafico de barras con la media de *test_score*\
- usamos `labs()` para decir los ejes y titulos.
- Usamos `geom_boxplot()` para crear el boxplot.
- Usamos el paquete *patchwork* para combinar varios gráficos en uno.
- usamos *ggsave()* para guardar el gráfico combinado como un archivo *png*
```{r}
# Load patchwork
library("patchwork")
# Create raw chart element
rawchart<-ggplot(analysisdata%>%filter(year==4),x=as.factor(fill))+
theme_classic()
# Create bar chart of pre summer school test score and summer school
p1<-rawchart+
geom_smooth(aes(x=parental_schooling,y=test_score)) +
geom_point(aes(x=parental_schooling,y=test_score),alpha=0.1)+
labs(x="Parental schooling", y="Test Score Year 5")
# Create bar chart of pre summer school test score and summer school
p2<-rawchart+
geom_bar(aes(x=as.factor(summerschool),y=test_score),
stat="summary",fun="mean")+
labs(y="Test Score Year 5", x="Attended Summer School")
# Create bar chart of parental schooling and summer school attendance
p3<-rawchart+
geom_boxplot(aes(x=as.factor(summerschool),y=parental_lincome))+
labs(y="Parental Income (log)", x="Attended Summer School")
# Combine charts
p1/(p2+p3)
# Export chart
ggsave("fig1.png")
```
Estos tres gráficos nos muestran que los puntajes en las pruebas se correlacionan con características de los padres (ambos scatters) y que aquellos que asistieron a la escuela de verano tenían mejores puntajes incluso antes de ir a la escuela de verano y que las caraterísticas de los padres se relaciona con la asistencia al éste
Es decir, tenemos **SESGO DE SELECCIÓN**
#### 4.1.3 Histogramas y gráficos de densidad
Comaperemos la distribución de los puntajes depués de asistir a la escuela de verano por quienes fueron y los que no fueron. Creamos un histograma con una linea que muestre las densidades estimadas.
```{r}
# create a histogram and density chart
ggplot(filter(analysisdata,year==6),
aes(x=test_score,fill=as.factor(summerschool)))+
geom_histogram(aes(y=..density..),bins = 50,alpha=0.5,
position="identity",color="white")+
geom_density(alpha=0.0,size=1,show.legend= FALSE)+
theme_minimal()+
labs(y="Densidad",x="Puntaje en prueba año 6 (estandarizado)",fill=" ")+
scale_fill_brewer(palette="Set2",labels=c("No asistió","Asistío a la escuela de verano"))+
theme(legend.position="top")
```
Claramente hay diferencias en sus resultados. Pero la estimación directa va a confundir cuanto de esto proviene del sesgo de selección y cuanto es el efecto real de ir a la escuela de verano.
#### 4.1.4 Correalograma
Muchas veces queremos saber que tán correlacionadas estan las variables en una muestra. Podríamos simplementa calular una tabla de correlaciones, pero el paquete \[PerformanceAnalytics\] tiene una función muy conveniente: `chart.Correlation()` que nos presenta un gráfico con las correlaciones de a pares, su significancia estadística, gráficos de dispersión y distribución.
```{r}
#Cargamos el paquete
library("PerformanceAnalytics")
correlations_graph <- analysisdata %>%
dplyr::filter(year==6) %>%
ungroup() %>%
dplyr::select(female, parental_schooling, parental_lincome, summerschool, letter ) #variables para el correalograma
chart.Correlation(correlations_graph, histogram=TRUE, pch=19)
```
# III. Estimación e identifiación
Tenemos nuestro problema base claro, ahora usemos algunas estrategias de identificaión para encontrar el efecto de asitir a una escuela de verano en esta muestra.
Para esto, partiremos del mejor caso: contamos con un experimento y luego iremos variando la información disponible y la estrategia que es factible emplear.
## 1. Regresión lineal ingenua
Nuestro objetivo es estimar la relación en los resultados académicos y asistira la escuela de verano. Para esto podríamos pensar en un modelo que directamente incluya a estos dos elementos.
$$ Resultado_i = \beta_0 + \beta_1 I(Summerschool=1)_i + u_i $$
El modelo estimado sería:
```{r}
ols <- lm ( test_score ~ summerschool, data= analysisdata )
ols
```
```{r}
summary(ols)
```
Podemos hacer una mejor tabla usando el paquete *stargazer*
```{r}
library(stargazer)
stargazer::stargazer(ols, type = "text", out="tabla1.tex")
```
Stargazer es util, pero esta siendo reemplazado por otros paquetes, particularmente [modelsummary](https://vincentarelbundock.github.io/modelsummary/index.html). Para una refrencia extra recomiendo revisar este [link](https://elbersb.de/public/pdf/web-7-regression-tables-graphs.pdf)
```{r}
# Podemos hacer un mapeo de nombres de variables
cm <- c( "test_score" = "Test Score",
"summerschool" = "Escuela de verano" )
```
```{r}
library(modelsummary)
modelsummary::modelsummary(ols, stars= TRUE, fmt=3,
estimate = "{estimate} ({std.error}){stars}", statistic=NULL, gof_omit = "AIC|BIC|Lik",
coef_map = cm)
```
También permite graficar:
```{r}
modelsummary::modelplot(ols, coef_omit = "intercept")
```
No se ve muy bien para solo 1 modelo. Otra opcion es con el paquete parameters (<https://easystats.github.io/parameters/>)
```{r}
library(parameters)
m <- parameters::model_parameters(ols)
m
```
Este modelo, como ya lo revisamos de la estadística descriptiva, podemos pensar que sufre de sesgo de selección. Es posible que los estudiantes con padres más involucrados, con más educación y mayor ingreso
Si solo fueran el problema la eduación de los padres y su ingreso, podríamos controlar por estos. Incluyámoslos al modelo en "cascada". Una manera de hacerlo es con varios modelos en una lista.
```{r}
#modelos
naive_ols_models <- list(
" (1)" = lm( test_score ~ summerschool , data= analysisdata),
"(2)" = lm ( test_score ~ summerschool + parental_lincome, data= analysisdata ),
"(3)" = lm ( test_score ~ summerschool + parental_schooling, data= analysisdata ),
"(4)" = lm ( test_score ~ summerschool + parental_lincome + parental_schooling, data= analysisdata ) )
# Mapeo de nombre de variables
cm <- c( "test_score" = "Test Score",
"summerschool" = "Summer school indicator",
"parental_lincome" = "Parental Income (log)",
"parental_schooling" = "Parental schooling" )
```
Tabla con stargazer:
```{r}
library(stargazer)
stargazer::stargazer(naive_ols_models, type = "text", out="tabla1.tex")
```
Tabla con modelsummary:
```{r}
modelsummary::modelsummary(naive_ols_models, stars= TRUE, fmt=3,
estimate = "{estimate} ({std.error}){stars}", statistic=NULL, gof_omit = "AIC|BIC|Lik",
coef_map = cm)
```
Sin embargo, si creemos que lo que también correlacona es una inobservable como la preocupación / involucramiento de los padres aun preservan el problema de la autoselección, todavía tenemos presencia de sesgo por variables omitidad.
¿Qué hacer?
Vamos a revisra varias alternativas:
- Experimental
1. Método de diferencias (en datos experimentales)
2. Logit y probit
- Datos observacionales:
3. Variables Instrumentales
4. Regresión Discontinua
5. Diferencias en Diferencias
6. Efectos fijos, aleatorios y modelos jerárquicos
7. Matching
## 2. Método de diferencias (en datos experimentales)
Un primer caso, es que antes de asistir al curso de verano se selecciona al azar un grupo de estudiantes para enviarles una carta de invitación que recuerda sus características y detalla como participar.
¿Podría ser este un buen experimento?
- Asignación aleatoria
- Tratamiento: recibir la carta -\> no es directamente la escuela de verano. Nos permite ver el efecto causal de recibir la carta, pero no de asistir a la escuela de verano. Vamos a usar una técnica para enfrentar esto posteriormente.
Por ahora, supongamos que conocer el efecto de la carta en el puntaje es una pregunta lo suficientemente interesante.
$testscore_i=\beta_0+\beta_1Letter_i+u_i$.
Este tipo de modelos podemos estimarlos con el paquete *linear modelos* `lm()` o también, con `feols()` del paquete *fixtest*
### 2.1 Estimación OLS
#### 2.1.2 Usando lm()
```{r}
#modelos
ols_letter_experiment <- list(
" (1)" = lm( test_score ~ letter , data= analysisdata) )
# Mapeo de nombre de variables
cm <- c( "test_score" = "Test Score",
"summerschool" = "Summer School",
"parental_lincome" = "Parental Income (log)",
"parental_schooling" = "Parental schooling" )
```
Tabla con stargazer:
```{r}
library(stargazer)
stargazer::stargazer(ols_letter_experiment, type = "text", out=here::here("output/tabla2.tex"))
```
Podemos ver que los que recibieron la carta, tienen 0.13 más puntaje que los que no, manteniendo lo demás constante.
Si le creemos a la asignción aleatoria, entonces este efecto podría ser interpetado como casual.
Para saber si la signación aletaoria efectivamente separó a dos grupos comparables de tratados y controles lo que debemos hacer es probar el **balance en obbservables**. Esto quiere decir que al menos en las variables observables, efectivamente ambos grupos no son estadísticamente diferentes antes del tratamiento.
### 2.1 Balance en observables
#### 2.1 Comparación de a pares
Una primera opción es realizar una prueba t en las variables observables en ambos grupos, usamos el comando `t.tes()` . Por ejemplo podriamos comparar el ingreso de los padres con `parental_income~summerschool` para probar si la media de test_score es significantemente diferente en los dos grupos especificados por la asistencia al curso de veranp
```{r}
# Realizamos la prueba T
t.test(parental_lincome~summerschool,data=analysisdata)
```
Podemos notar que no hay diferencias significativas entre los grupos.
Esto tendríamos que realizarlo en todas las variables y se suele presentar en una **tabla de balance**
#### 2.3 Tabla de balance
Una tabla de balance presenta la diferencia de medias para grupos tratados y de control. Lo que deseamos es que no haya diferencias significativas antes del tratamiento en los grupos.
Podemos hacer una tabla que se vea bien usando `datasummary_balance()` del paquete *modelsummary*. Cargamos tambien el paquete *estimatr* porque le permite a `datasummary_balance()` calcular e inluir pruebas t de a pares directamente en la tabla.
```{r}
# Load libraries
library(modelsummary)
library(estimatr)
# Filter and modify data
testdata<-filter(analysisdata,year==5)
testdata<-ungroup(testdata)
testdata<-mutate(testdata,Treated=ifelse(letter==1,"Letter","No Letter"))
testdata<-select(testdata,female,parental_schooling,parental_lincome,test_score,Treated)
testdata<-rename(testdata,`Female`=female,
`Parental schooling (years)`=parental_schooling,
`Parental income (log)`=parental_lincome,
`Test Score`=test_score)
# Table with balancing test
datasummary_balance(~Treated,
data = testdata,
title = "Balance of pre-treatment variables",
notes = "Notes: This is a brilliant table!",
fmt= '%.5f',
dinm_statistic = "p.value")
```
Los reslutados de la tabla sugieren que no hay diferencias significativas entre tratados y controles ANTES del tratamiento. ¡ Muy bien!
Y como antes, podríamos directaente agregar la opción `outpu=tab_balancing.docx` para exportarla en word (o latex). (no olvidar especificar la carperta con `here::here("output/`)
#### 2.4 Aproximación gráfica
Podemos ver el efecto causal propuesto en la siguiente gráfica. En los años antes del tratamiento son iguales y en los años posteriores, las distribuciones se empiezan a separar.
```{r}
# Load ggridges
library("ggridges")
# create a ggridges chart
ggplot(analysisdata,aes(y=as.factor(year),x=test_score,fill=as.factor(letter) ))+
geom_density_ridges( alpha = .7, scale=1.5,color = "white", from = -2.5, to = 2.5)+
theme_minimal()+
theme_ridges(grid = FALSE)+
scale_y_discrete(expand = c(0, 0)) +
scale_x_continuous(expand = c(0, 0)) +
scale_fill_brewer(palette="Set1",labels=c("No letter","Letter"))+
labs(x="Test Score",y="Year",fill=" ",
title="Test score distribution by reminder letter status & year")+
theme(legend.position="top",aspect.ratio=4/3,plot.title = element_text(hjust = 0.5))
```
## 3. Efectos fijos, aleatorios y modelos jerárquicos
Muchas veces, los datos estan agrupados en diferentes categorías o dimensiones.
En nuestro caso los estudiantes están agrupados en colegios y es esperable que dentro de un colegio los resultados de dichos estudiantes estén correlacionados. Esto también ocurre al tener datos temporales o cualquier forma de agrupación lógica de la información.
Podemos ignorar esta estructura en los datos y simplemente considerarlos como un conjunto de unidades comparables, es decir tratarlo como un **pooled cross-section**. Si las diferencias entre grupos no son sistemáticas, no deberíamos observar diferencias, sin embargo esto puede estar haciendo que caigamos en problemas como la *paradoja de Simpson* o el *Mathew effect*
Para profundizar recomiendo: https://ds4ps.org/pe4ps-textbook/docs/p-040-fixed-effects.html o el capítulo del libro
![](images/FEproblems.png)
En nuestro caso, tenemos por ejemplo 30 colegios en los datos.
Otro punto es considerar que no solo el efecto está modulado por la organziación de agrupación de la data, sino también los errores. Esto lo llamamos clusterizar.
#### 3.1.2 Estimación usando lm()
Si cada grupo está afectando los resultados, calculemos el efecto el un OLS solo del año 5, un pooled en los años hasta el 5 y en este ultimo, incluyamos un efecto fijo por colegio y luego por año.
```{r}
# No siempre necesitamos hacer una lista de
OLS_year5_data <-filter(analysisdata,year==5)
OLS_year5 = lm( test_score ~ letter , data= OLS_year5_data )
# Para el pooled, usemos todos los años antes del 5
POLS_data <-filter(analysisdata,year<=5)
Pooled_OLS = lm ( test_score ~ letter , data= POLS_data )
# Fixed effect por colegio
FE = lm ( test_score ~ letter + factor(school_id), data= POLS_data )
# Usemos stargazer para la tabla esta vez
stargazer( OLS_year5, Pooled_OLS, FE,
type = "text",
dep.var.labels = ("Test score"),
column.labels = c("Cross-Sectional OLS (year 5)", "Pooled OLS", "School FE"),
covariate.labels = c("Intercept", "letter"),
keep =c("Intercept", "letter"),
omit.stat = "all",
digits = 2, intercept.bottom = FALSE )
```
#### 3.1.2 Estimación usando feols()
En el caso de que deseemos hacer un modelamiento más complejo, como opr ejemplo incluir efectos fijos (lo revisaremos al final de la sesión) o calcular los errores estándar en cluster podemos usar la función `feols()` del paquete *fixest*.
El paquete *fixest* permite la inclusión de múltiples efectos fijos y clusterización de errores es, nosotros usaremos la función `feols()`
Esta función espera una fórmula de especificación de la forma:
`y~x1+x2+..|fixed effects|IV specification,cluster=..`
Por ejemplo, en ese caso queremos clusterizar los errores a nivel de colegio:
```{r}
# Load packages
library(fixest)
# Select data
regdata<-filter(analysisdata,year==6)
# Regression
m1<-feols(test_score~letter+parental_lincome+female+parental_schooling, cluster="school_id",data=regdata)
# Summary of regression
summary(m1)
```
Quisieramos compararlo con el modelo sin dicha clusterización, podriamos hacer una mejor tabla. Aprovechamos la función `modelsummary()` y:
1. Corremos ambas regresiones y guardamos los modelos en una lista. La primera es sin ajustar los errores, la segunda con cluster, la tercera con un efecto fijo por colegio.
2. Usamos `modelsummary()`para el output de las regresiones.
3. Especificamos que entre paréntesis esté el error estándar y que omita la constante en la tabla.