-
Notifications
You must be signed in to change notification settings - Fork 5
/
05-remuestreo.Rmd
2039 lines (1617 loc) · 79.2 KB
/
05-remuestreo.Rmd
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
# Intervalos de confianza y remuestreo
```{r setup, include=FALSE, message=FALSE}
library(tidyverse)
library(patchwork)
source("R/funciones_auxiliares.R")
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning=FALSE, fig.align = 'center', cache=TRUE)
comma <- function(x) format(x, digits = 2, big.mark = ",")
theme_set(theme_minimal())
```
En la sección anterior, vimos el concepto de distribución de muestreo
de una estadística que queremos utilizar para estimar un valor poblacional, y
vimos que con esta distribución podíamos evaluar **qué tan preciso es nuestro
estimador** evaluando qué tan concentrada está esta distribución alrededor
del valor poblacion que queremos estimar.
Sin embargo, en los ejemplos que vimos la población era conocida: ya sea que
tuviéramos toda la población finita disponible (como el ejemplo de las casas), o
donde la población estaba definida por un modelo teórico de probabilidad (como
los ejemplos de las distribuciones uniforme o exponencial).
Ahora vemos qué hacer en el caso que realmente nos interesa: solo tenemos una
muestra disponible, y la población es desconocida. Todo lo que tenemos es una
muestra y una estimación basada en la muestra, y requerimos estimar la
distribución de muestreo de la estadística de interés. El enfoque que
presentaremos aquí es uno de los más flexibles y poderosos que están disponibles
para este problema: el método **bootstrap** o de **remuestreo**.
En primer lugar explicamos el concepto de intervalo de confianza, que es una
manera resumida de evaluar la precisión de nuestras estimaciones.
## Ejemplo introductorio {-}
Regresamos a nuestro ejemplo anterior donde muestreamos 3 grupos, y nos preguntábamos
acerca de la diferencia de sus medianas. En lugar de hacer pruebas de permutaciones
(ya sea pruebas gráficas o alguna prueba de permutaciones para media o mediana, por ejemplo),
podríamos considerar qué tan precisa es cada una de nuestras estimaciones
para las medianas de los grupos.
Nuestros resultados podríamos presentarlos como sigue. Este código lo explicaremos
más adelante, por el momento consideramos la gŕafica resultante:
```{r, message = FALSE, cache = TRUE}
set.seed(8)
pob_tab <- tibble(id = 1:2000, x = rgamma(2000, 4, 1),
grupo = sample(c("a","b", "c"), 2000, prob = c(4,2,1), replace = T))
muestra_tab <- pob_tab |>
slice_sample(n = 125)
g_1 <- ggplot(muestra_tab, aes(x = grupo, y = x)) +
geom_boxplot(outlier.alpha = 0) +
geom_jitter(alpha = 0.3) +
labs(subtitle = "Muestra \n") + ylim(c(0,14))
## Hacemos bootstrap
fun_boot <- function(datos){
datos |> group_by(grupo) |> slice_sample(prop = 1, replace = TRUE)
}
reps_boot <- map_df(1:2000, function(i){
muestra_tab |>
fun_boot() |>
group_by(grupo) |>
summarise(mediana = median(x), .groups = "drop")},
.id = 'rep')
resumen_boot <- reps_boot |> group_by(grupo) |>
summarise(ymin = quantile(mediana, 0.025),
ymax = quantile(mediana, 0.975), .groups = "drop") |>
left_join(muestra_tab |>
group_by(grupo) |>
summarise(mediana = median(x)))
g_2 <- ggplot(resumen_boot, aes(x = grupo, y = mediana, ymin = ymin,
ymax = ymax)) +
geom_linerange() +
geom_point(colour = "red", size = 2) + ylim(c(0,14)) +
labs(subtitle = "Intervalos de 95% \n para la mediana")
g_1 + g_2
```
Donde:
- En rojo está nuestro = puntual de la mediana de cada
grupo (la mediana muestral), y
- Las segmentos muestran un intervalo de confianza del 95\%
para nuestra estimación de la mediana: esto quiere decir que
los valores poblacionales tienen probabilidad aproximada de 95\% de estar
dentro del intervalo.
Este análisis comunica correctamente que tenemos **incertidumbre** alta acerca
de nuestras estimaciones (especialmente grupos b y c), y que no tenemos mucha
evidencia de que el grupo b tenga una mediana poblacional considerablemente más
alta que a o c. **En muchos casos es más útil presentar la información de esta
manera que usando alguna prueba de hipótesis.**
## La idea del bootstrap {-}
Como explicamos, el problema que tenemos ahora es que normalmente sólo tenemos
una muestra, así que no es posible calcular las distribuciones de muestreo como
hicimos en la sección anterior y así evaluar qué tan preciso es nuestro estimador. Sin embargo,
podemos hacer lo siguiente:
Supongamos que tenemos una muestra $X_1,X_2,\dots, X_n$ independientes de alguna
población desconocida y un estimador $T=t(X_1,\dots, X_n)$
**Mundo poblacional**
1. Si tuviéramos la distribución poblacional, simulamos muestras iid para
aproximar la distribución de muestreo de nuestro estimador, y así entender su
variabilidad.
2. Pero **no** tenemos la distribución poblacional.
3. **Sin embargo, podemos estimar la distribución poblacional con nuestros valores muestrales**.
**Mundo bootstrap**
4. Si usamos la estimación del inciso 3, entonces usando el inciso 1 podríamos
tomar muestras de nuestros datos muestrales, como si fueran de la población, y
usando el mismo tamaño de muestra. El muestreo lo hacemos con reemplazo de
manera que produzcamos muestras independientes de la misma "población estimada",
que es la muestra.
5. Evaluamos nuestra estadística en cada una de estas remuestras, a estas les llamamos
**replicaciones bootstrap**.
6. A la distribución de las replicaciones le llamamos **distribución bootstrap** o
**distribución de remuestreo** del estimador.
7. Usamos la distribución bootstrap para estimar la variabilidad
en nuestra estimación con **la muestra original**.
Veamos que sucede para un ejemplo concreto, donde nos interesa estimar
la media de los precios de venta de una población de casas. Tenemos nuestra muestra:
```{r, cache=TRUE}
set.seed(2112)
poblacion_casas <- read_csv("data/casas.csv")
muestra <- slice_sample(poblacion_casas, n = 200, replace = TRUE)
mean(muestra$precio_miles)
```
Esta muestra nos da nuestro estimador de la distribución poblacional:
```{r, fig.width =5, fig.height = 3}
bind_rows(muestra |> mutate(tipo = "muestra"),
poblacion_casas |> mutate(tipo = "población")) |>
ggplot(aes(sample = precio_miles, colour = tipo, group = tipo)) +
geom_qq(distribution = stats::qunif, alpha = 0.4, size = 1) +
facet_wrap(~ tipo)
```
O con histogramas:
```{r, fig.width =5, fig.height = 3}
bind_rows(muestra |> mutate(tipo = "muestra"),
poblacion_casas |> mutate(tipo = "población")) |>
ggplot(aes(x = precio_miles, group = tipo)) +
geom_histogram(aes(y=..density..), binwidth = 50) +
facet_wrap(~ tipo)
```
Y vemos que la aproximación es razonable en las partes centrales de la
distribución.
Ahora supongamos que nos interesa cuantificar la precisión de nuestra
estimación de la media poblacional de precios de casas, y usaremos la media
muestral para hacer esto. Para nuestra muestra, nuestra estimación puntual es:
```{r}
media <- mean(muestra$precio_miles)
media
```
Y recordamos que para aproximar la distribución de muestreo
podíamos muestrear repetidamente la población y calcular el valor del
estimador en cada una de estas muestras. Aquí no tenemos la población,
**pero tenemos una estimación de la población**: la muestra obtenida.
Así que para evaluar la variabilidad de nuestro estimador, entramos en el mundo
bootstrap, y consideramos que la población es nuestra muestra.
Podemos entonces extraer un número grande de muestras con reemplazo de tamaño
200 **de la muestra**: el muestreo debe ser análogo al que se tomó para nuestra
muestra original. Evaluamos nuestra estadística (en este caso la media) en cada
una de estas remuestras:
```{r, cache = TRUE}
media_muestras <- map_dbl(1:5000, ~ muestra |>
slice_sample(n = 200, replace = TRUE) |>
summarise(media_precio = mean(precio_miles), .groups = "drop") |> pull(media_precio))
```
Y nuestra estimación de la distribución de muestreo para la media es entonces:
```{r, fig.width =6, fig.height = 4}
bootstrap <- tibble(media = media_muestras)
g_cuantiles <- ggplot(bootstrap, aes(sample = media)) + geom_qq(distribution = stats::qunif)
g_histograma <- ggplot(bootstrap, aes(x = media)) + geom_histogram(binwidth = 2)
g_cuantiles + g_histograma
```
A esta le llamamos la distribución bootstrap (o de remuestreo) de la media, que definimos más
abajo. Ahora podemos calcular un intervalo de confianza del 90\% simplemente
calculando los cuantiles de esta distribución (no son los cuantiles de la
muestra original!):
```{r}
limites_ic <- quantile(media_muestras, c(0.05, 0.95)) |> round()
limites_ic
```
Presentaríamos nuestro resultado como sigue: nuestra estimación puntual de la
mediana es `r round(mean(muestra$precio_miles), 1)`, con un intervalo de
confianza del 90\% de (`r limites_ic[1]`, `r limites_ic[2]`)
Otra cosa que podríamos hacer para describir la dispersión de nuestro estimador
es calcular el error estándar de remuestreo, que estima el error estándar de la
distribución de muestreo:
```{r}
ee_boot <- sd(media_muestras)
round(ee_boot, 2)
```
```{block2, type='mathblock'}
**Definición.** Sea $X_1,X_2,\ldots,X_n$ una muestra independiente y idénticamente
distribuida, y $T=t(X_1, X_2, \ldots, X_n)$ una estadística. Supongamos que sus valores
que obervamos son $x_1, x_2,\ldots, x_n$.
La **distribución bootstrap**, o distribución de remuestreo, de $T$ es la
distribución de $T^*=t(X_1^*, X_2^*, \dots X_n^*)$, donde cada $X_i^*$ se obtiene
tomando al azar uno de los valores de $x_1,x_2,\ldots, x_n$.
```
- Otra manera de decir esto es que la remuestra $X_1^*, X_2^*, \ldots, X_n^*$ es una muestra
con reemplazo de los valores observados $x_1, x_2, \ldots, x_n$
**Ejemplo.** Si observamos la muestra
```{r}
muestra <- sample(1:20, 5)
muestra
```
Una remuestra se obtiene:
```{r}
sample(muestra, size = 5, replace = TRUE)
```
Nótese que algunos valores de la muestra original pueden aparecer varias veces, y otros no aparecen del todo.
```{block2, type='comentario'}
**La idea del bootstrap (no paramétrico)**. La muestra original es una aproximación de la población
de donde fue extraída. Así que remuestrear la muestra aproxima lo que pasaría si
tomáramos muestras de la población. La **distribución de remuestreo** de una estadística,
que se construye tomando muchas remuestras, aproxima la distribución de muestreo
de la estadística.
```
Y el proceso que hacemos es:
```{block2, type='comentario'}
**Remuestreo para una población.** Dada una muestra de tamaño $n$ de una población,
1. Obtenemos una remuestra de tamaño $n$ con reemplazo de la muestra original y
calculamos la estadística de interés.
2. Repetimos este remuestreo muchas veces (por ejemplo, 10,000).
3. Construímos la distribución bootstrap, y examinamos sus características
(dónde está centrada, dispersión y forma).
```
## El principio de plug-in {-}
La idea básica detrás del bootstrap es el principio de plug-in para estimar
parámetros poblacionales: si queremos estimar una cantidad poblacional,
calculamos esa cantidad poblacional con la muestra obtenida. Es un principio
común en estadística.
Por ejemplo, si queremos estimar la media o desviación estándar poblacional,
usamos la media muestral o la desviación estándar muestral. Si queremos estimar
un cuantil de la población usamos el cuantil correspondiente de la muestra, y
así sucesivamente.
En todos estos casos, lo que estamos haciendo es:
- Tenemos una fórmula para la cantidad poblacional de interés en términos de la
distribución poblacional.
- Tenemos una muestra, la distribución que da esta muestra se llama distribución
*empírica* ($\hat{F}(x) = \frac{1}{n}\{\#valores \le x\}$).
- Contruimos nuestro estimador, de la cantidad poblacional de interés, "enchufando"
la distribución empírica de la muestra en la fórmula del estimador.
En el bootstrap aplicamos este principio simple a la **distribución de
muestreo**:
- *Si tenemos la población*, podemos *calcular* la distribución de muestreo de
nuestro estimador tomando muchas muestras de la *población*.
- Estimamos la *poblacion* con la *muestra* y enchufamos en la frase anterior:
*estimamos* la distribución de muestreo de nuestro estimador
tomando muchas muestras de la *muestra*.
Nótese que el proceso de muestreo en el último paso **debe ser el mismo** que
se usó para tomar la muestra original. Estas dos imágenes simuladas con base en
un ejemplo de @Chihara muestran lo que acabamos de describir:
```{r mundo-real, warning=FALSE, fig.cap='Mundo Real', echo = FALSE}
library(patchwork)
source("R/distribuciones.R")
# En este ejemplo la población es una mezcla de normales
pob_plot <- ggplot(data_frame(x = -15:20), aes(x)) +
stat_function(fun = dnormm, args = list(p = c(0.3, 0.7), mu = c(-2, 8),
sigma = c(3.5, 3)), alpha = 0.8) +
geom_vline(aes(color = "mu", xintercept = 5), alpha = 0.5) +
scale_colour_manual(values = c('mu' = 'red'), name = '',
labels = expression(mu)) +
scale_y_continuous(breaks = NULL) +
labs(x = "", subtitle = expression("Población "~F), color = "") +
theme_classic()
samples <- data_frame(sample = 1:3) |>
mutate(
sims = rerun(3, rnormm(30, p = c(0.3, 0.7), mu = c(-2, 8),
sigma = c(3.5, 3))),
x_bar = map_dbl(sims, mean))
muestras_plot <- samples |>
unnest(cols = c(sims)) |>
ggplot(aes(x = sims)) +
geom_histogram(binwidth = 2, alpha = 0.5, fill = "darkgray") +
geom_vline(xintercept = 5, color = "red", alpha = 0.5) +
geom_segment(aes(x = x_bar, xend = x_bar, y = 0, yend = 0.8),
color = "blue") +
xlim(-15, 20) +
facet_wrap(~ sample) +
scale_y_continuous(breaks = NULL) +
geom_text(aes(x = x_bar, y = 0.95, label = "bar(x)"), parse = TRUE,
color = "blue", alpha = 0.2, hjust = 1) +
labs(x = "", subtitle = "Muestras") +
theme_classic() +
theme(strip.background = element_blank(), strip.text.x = element_blank())
samples_dist <- tibble(sample = 1:10000) |>
mutate(sims = rerun(10000, rnormm(100, p = c(0.3, 0.7), mu = c(-2, 8),
sigma = c(3.5, 3))),
mu_hat = map_dbl(sims, mean))
dist_muestral_plot <- ggplot(samples_dist, aes(x = mu_hat)) +
geom_density(adjust = 2) +
scale_y_continuous(breaks = NULL) +
labs(x = "", y = "",
subtitle = expression("Distribución muestral de "~hat(mu)==bar(X))) +
geom_vline(xintercept = 5, color = "red", alpha = 0.5) +
theme_classic()
(pob_plot | plot_spacer()) / (muestras_plot | dist_muestral_plot)
```
```{r mundo-bootstrap, warning=FALSE, fig.cap='Mundo Bootstrap', echo = FALSE, fig.width=8}
dist_empirica <- tibble(id = 1:30, obs = samples$sims[[1]])
dist_empirica_plot <- ggplot(dist_empirica, aes(x = obs)) +
geom_histogram(binwidth = 2, alpha = 0.5, fill = "darkgray") +
geom_vline(aes(color = "mu", xintercept = 5), alpha = 0.5) +
geom_vline(aes(xintercept = samples$x_bar[1], color = "x_bar"),
alpha = 0.8, linetype = "dashed") +
xlim(-15, 20) +
geom_vline(xintercept = 5, color = "red", alpha = 0.5) +
labs(x = "", subtitle = expression("Distribución empírica"~hat(F))) +
scale_colour_manual(values = c('mu' = 'red', 'x_bar' = 'blue'), name = '',
labels = c(expression(mu), expression(bar(x)))) +
scale_y_continuous(breaks = NULL) +
theme_classic()
samples_boot <- tibble(sample_boot = 1:3) |>
mutate(
sims_boot = rerun(3, sample(dist_empirica$obs, replace = TRUE)),
x_bar_boot = map_dbl(sims_boot, mean)
)
muestras_boot_plot <- samples_boot |>
unnest(cols = c(sims_boot)) |>
ggplot(aes(x = sims_boot)) +
geom_histogram(binwidth = 2, alpha = 0.5, fill = "darkgray") +
geom_vline(aes(xintercept = samples$x_bar[1]), color = "blue",
linetype = "dashed", alpha = 0.8) +
geom_vline(xintercept = 5, color = "red", alpha = 0.5) +
geom_segment(data = samples_boot, aes(x = x_bar_boot, xend = x_bar_boot, y = 0, yend = 0.8),
color = "black") +
xlim(-15, 20) +
facet_wrap(~ sample_boot) +
geom_text(aes(x = x_bar_boot, y = 0.95, label = "bar(x)^'*'"),
parse = TRUE, color = "black", alpha = 0.3, hjust = 1) +
labs(x = "", subtitle = "Muestras bootstrap") +
scale_y_continuous(breaks = NULL) +
theme_classic() +
theme(strip.background = element_blank(), strip.text.x = element_blank())
boot_dist <- data_frame(sample = 1:10000) |>
mutate(
sims_boot = rerun(10000, sample(dist_empirica$obs, replace = TRUE)),
mu_hat_star = map_dbl(sims_boot, mean))
boot_muestral_plot <- ggplot(boot_dist, aes(x = mu_hat_star)) +
geom_histogram(alpha = 0.5, fill = "darkgray", bins = 30) +
labs(x = "",
subtitle = expression("Distribución bootstrap de "~hat(mu)^'*'==bar(X))) +
geom_vline(xintercept = 5, color = "red", alpha = 0.5) +
geom_vline(aes(xintercept = samples$x_bar[1]), color = "blue",
linetype = "dashed", alpha = 0.8) +
scale_y_continuous(breaks = NULL) +
theme_classic()
(dist_empirica_plot | plot_spacer()) / (muestras_boot_plot | boot_muestral_plot)
```
**Observación 1**. Veremos ejemplos más complejos, pero nótese que si la muestra
original son observaciones independientes obtenidas de la distribución poblacional,
entonces logramos esto en las remuestras tomando observaciones con reemplazo
de la muestra. Igualmente, las remuestras deben ser del mismo tamaño que la muestra
original.
```{block2,type='ejercicio'}
- ¿Porqué no funcionaría tomar muestras sin reemplazo? Piensa si hay independencia
entre las observaciones de la remuestra, y cómo serían las remuestras sin reemplazo.
- ¿Por qué no se puede hacer bootstrap si no conocemos cómo se obtuvo la muestra original?
```
**Observación 2**. Estos argumentos se pueden escribir con fórmulas usando por
ejemplo la función de distribución acumulada $F$ de la población y su estimador,
que es la función empírica $\hat{F}$. Si $\theta = t(F)$ es una
cantidad poblacional que queremos estimar, su estimador plug-in es
$\hat{\theta} = t(\hat{F})$.
**Observación 3**: La distribución empírica $\hat{F}$ es un estimador "razonable" de
la distribución poblacional $F,$ pues por el teorema de Glivenko-Cantelli (ver @Wasserman,
o [aquí](https://en.wikipedia.org/wiki/Glivenko-Cantelli_theorem)),
$\hat{F}$ converge a $F$ cuando el tamaño de muestra $n\to\infty$, lo cual es
intuitivamente claro.
### Ejemplo {-}
En el ejemplo de tomadores de té, podemos estimar la proporción de tomadores
de té que prefiere el té negro usando nuestra muestra:
```{r}
te <- read_csv("data/tea.csv") |>
rowid_to_column() |>
select(rowid, Tea, sugar)
te |> mutate(negro = ifelse(Tea == "black", 1, 0)) |>
summarise(prop_negro = mean(negro), n = length(negro), .groups = "drop")
```
¿Cómo evaluamos la precisión de este estimador? Supondremos que el estudio se
hizo tomando una muestra aleatoria simple de tamaño 300 de la población de tomadores de té que
nos interesa. Podemos entonces usar el bootstrap:
```{r, fig.width=3.5, fig.height=3.5}
# paso 1: define el estimador
calc_estimador <- function(datos){
prop_negro <- datos |>
mutate(negro = ifelse(Tea == "black", 1, 0)) |>
summarise(prop_negro = mean(negro), n = length(negro), .groups = "drop") |>
pull(prop_negro)
prop_negro
}
# paso 2: define el proceso de remuestreo
muestra_boot <- function(datos){
#tomar muestra con reemplazo del mismo tamaño
slice_sample(datos, prop = 1, replace = TRUE)
}
# paso 3: remuestrea y calcula el estimador
prop_negro_tbl <- tibble(prop_negro = map_dbl(1:10000, ~ calc_estimador(muestra_boot(datos = te))))
# paso 4: examina la distribución bootstrap
prop_negro_tbl |>
ggplot(aes(x = prop_negro)) +
geom_histogram(bins = 15) +
geom_vline(xintercept = calc_estimador(te), color = "red")
```
Y podemos evaluar varios aspectos, por ejemplo dónde está centrada y
qué tan dispersa es la distribución bootstrap:
```{r}
prop_negro_tbl |>
summarise(media = mean(prop_negro),
ee = sd(prop_negro),
cuantil_75 = quantile(prop_negro, 0.75),
cuantil_25 = quantile(prop_negro, 0.25),
.groups = "drop") |>
mutate(across(where(is.numeric), round, 3)) |>
pivot_longer(cols = everything())
```
<!-- <!-- comentar --> -->
<!-- agregar entre cambio de clase -->
<!-- ### Resumen -->
<!-- * La idea detrás del bootstrap (no paramétrico) es que la muestra original es -->
<!-- representativa de la población. Entonces las remuestras de la muestra aproximan lo que obtendríamos si tomáramos muestras de la población. -->
<!-- * Es así que la distribución bootstrap de una estadística, basada en un número grande -->
<!-- de remuestras, aproxima la distribución muestral de la estadística. -->
<!-- * Para un gran número de estadísiticas, la distribuciones bootstrap aproximan la dispersón, -->
<!-- sesgo y forma de la distribución muestral. -->
<!-- <!-- comentar --> -->
## Discusión: propiedades de la distribución bootstrap {-}
Uasremos la distribución bootstrap principalmente para evaluar la variabilidad
de nuestros estimadores (y también otros aspectos como sesgo) estimando
la dispersión de la distribución de muestreo. Sin embargo, es importante notar
que no la usamos, por ejemplo, para saber dónde está centrada la distribución
de muestreo, o para "mejorar" la estimación remuestreando.
### Ejemplo {-}
En este ejemplo, vemos 20 muestras de tamaño 200, y
evaluamos cómo se ve la aproximación a la distribución de la población (rojo):
```{r, echo = FALSE, message = FALSE, fig.width =4, fig.height = 3, cache = TRUE}
set.seed(911)
muestras <- map(1:20, function(x) {
muestra <- slice_sample(poblacion_casas, n = 200, replace = T) |>
mutate(rep = x, tipo = "muestras")}) |> bind_rows()
dat_pob <- poblacion_casas |> mutate(tipo = "población", rep = 1)
datos_sim <- bind_rows(dat_pob, muestras)
ggplot(datos_sim, aes(sample = precio_miles, group = interaction(tipo, rep))) +
geom_qq(distribution = stats::qunif, alpha = 0.7, size = 0.5, geom = "line") +
geom_qq(data = dat_pob, aes(sample = precio_miles), colour = "red", size = 0.7,
distribution = stats::qunif, geom="point") +
scale_y_log10(breaks = c(50, 100, 200, 400, 800))
```
Podemos calcular las distribuciones de remuestreo (bootstrap) para cada muestra,
y compararlas con la distribución de muestreo real.
```{r, fig.width =4, fig.height = 3, cache = TRUE}
# paso 1: define el estimador
calc_estimador <- function(datos){
media_precio <- datos |>
summarise(media = mean(precio_miles), .groups = "drop") |>
pull(media)
media_precio
}
# paso 2: define el proceso de remuestreo
muestra_boot <- function(datos, n = NULL){
#tomar muestra con reemplazo del mismo tamaño
if(is.null(n)){
m <- slice_sample(datos, prop = 1, replace = TRUE)}
else {
m <- slice_sample(datos, n = n, replace = TRUE)
}
m
}
dist_boot <- datos_sim |>
filter(tipo == "muestras") |>
select(precio_miles, rep) |>
group_by(rep) |> nest() |>
mutate(precio_miles = map(data, function(data){
tibble(precio_miles = map_dbl(1:1000, ~ calc_estimador(muestra_boot(data))))
})) |>
select(rep, precio_miles) |>
unnest()
dist_muestreo <- datos_sim |>
filter(tipo == "población") |>
group_by(rep) |> nest() |>
mutate(precio_miles = map(data, function(data){
tibble(precio_miles = map_dbl(1:1000, ~ calc_estimador(muestra_boot(data, n = 200))))
})) |>
select(rep, precio_miles) |>
unnest()
```
```{r, echo = FALSE, fig.width =4, fig.height = 3}
ggplot(dist_boot, aes(sample = precio_miles, group = interaction(rep))) +
geom_qq(distribution = stats::qunif, size = 0.1, alpha = 0.1) +
geom_qq(data = dist_muestreo, aes(sample = precio_miles), colour = "red",
distribution = stats::qunif, alpha = 0.1) +
ylim(c(125, 230)) +
geom_hline(yintercept = 183, color = "red") +
labs(subtitle = "Estimaciones de distribución \n de muestreo (media)")
```
Obsérvese que:
- En algunos casos la aproximación es mejor que en otros (a veces
la muestra tiene valores ligeramente más altos o más bajos).
- La dispersión de cada una de estas distribuciones bootstrap es similar a la de la verdadera
distribución de muestreo (en rojo), pero puede está desplazada dependiendo
de la muestra original que utilizamos.
- Adicionalmente, los valores centrales de la distribución de bootstrap
tiende cubrir el verdadero valor que buscamos estimar, que es:
```{r}
poblacion_casas |> summarise(media = mean(precio_miles), .groups = "drop")
```
### Variación en distribuciones bootstrap {-}
En el proceso de estimación bootstrap hay dos fuentes de variación pues:
* La muestra original se selecciona con aleatoriedad de una población.
* Las muestras bootstrap se seleccionan con aleatoriedad de la muestra
original. Esto es: *La estimación bootstrap ideal es un resultado asintótico
$B=\infty$, en esta caso $\hat{\textsf{se}}_B$ iguala la estimación _plug-in_
$se_{P_n}$.*
En el proceso de *bootstrap* podemos controlar la variación del segundo aspecto,
conocida como **implementación de muestreo Monte Carlo**, y la variación
Monte Carlo decrece conforme incrementamos el número de muestras.
Podemos eliminar la variación Monte Carlo si seleccionamos todas las posibles
muestras con reemplazo de tamaño $n$, hay ${2n-1}\choose{n}$ posibles muestras
y si seleccionamos todas obtenemos $\hat{\textsf{se}}_\infty$ (bootstrap ideal), sin
embargo, en la mayor parte de los problemas no es factible proceder así.
```{r, eval = FALSE, echo=FALSE}
set.seed(8098)
pob_plot <- ggplot(data_frame(x = -15:20), aes(x)) +
stat_function(fun = dnormm, args = list(p = c(0.3, 0.7), mu = c(-2, 8),
sigma = c(3.5, 3)), alpha = 0.8) +
geom_vline(aes(color = "mu", xintercept = 5), alpha = 0.5) +
scale_colour_manual(values = c('mu' = 'red'), name = '',
labels = expression(mu)) +
labs(x = "", y = "", subtitle = "Población", color = "") +
theme(axis.text.y = element_blank())
samples <- data_frame(sample = 1:6) |>
mutate(
sims = rerun(6, rnormm(50, p = c(0.3, 0.7), mu = c(-2, 8),
sigma = c(3.5, 3))),
x_bar = map_dbl(sims, mean))
means_boot <- function(n, sims) {
rerun(n, mean(sample(sims, replace = TRUE))) |>
flatten_dbl()
}
samples_boot <- samples |>
mutate(
medias_boot_30_1 = map(sims, ~means_boot(n = 30, .)),
medias_boot_30_2 = map(sims, ~means_boot(n = 30, .)),
medias_boot_1000_1 = map(sims, ~means_boot(n = 1000, .)),
medias_boot_1000_2 = map(sims, ~means_boot(n = 1000, .))
)
emp_dists <- samples_boot |>
unnest(cols = sims) |>
rename(obs = sims)
emp_dists_plots <- ggplot(emp_dists, aes(x = obs)) +
geom_histogram(binwidth = 2, alpha = 0.5, fill = "darkgray") +
geom_vline(aes(color = "mu", xintercept = 5), alpha = 0.5,
show.legend = FALSE) +
geom_vline(aes(xintercept = x_bar, color = "x_bar"), show.legend = FALSE,
alpha = 0.8, linetype = "dashed") +
xlim(-15, 20) +
geom_vline(xintercept = 5, color = "red", alpha = 0.5) +
labs(x = "", y = "", subtitle = expression("Distribución empírica"~P[n])) +
scale_colour_manual(values = c('mu' = 'red', 'x_bar' = 'blue'), name = '',
labels = c(expression(mu), expression(bar(x)))) +
facet_wrap(~ sample, ncol = 1) +
theme(strip.background = element_blank(), strip.text.x = element_blank(),
axis.text.y = element_blank())
boot_dists_30 <- samples_boot |>
unnest(cols = c(medias_boot_30_1, medias_boot_30_2)) |>
pivot_longer(cols = c(medias_boot_30_1, medias_boot_30_2),
values_to = "mu_hat_star", names_to = "boot_trial",
names_prefix = "medias_boot_30_")
boot_dists_30_plot <- ggplot(boot_dists_30, aes(x = mu_hat_star)) +
geom_histogram(alpha = 0.5, fill = "darkgray") +
labs(x = "", y = "",
subtitle = expression("Distribución bootstrap B = 30")) +
geom_vline(xintercept = 5, color = "red", alpha = 0.5) +
geom_vline(aes(xintercept = x_bar), color = "blue",
linetype = "dashed", alpha = 0.8) +
facet_grid(sample~boot_trial) +
theme(strip.background = element_blank(), strip.text.y = element_blank(),
axis.text.y = element_blank())
boot_dists_1000 <- samples_boot |>
unnest(cols = c(medias_boot_1000_1, medias_boot_1000_2)) |>
pivot_longer(cols = c(medias_boot_1000_1, medias_boot_1000_2),
values_to = "mu_hat_star", names_to = "boot_trial",
names_prefix = "medias_boot_1000_")
boot_dists_1000_plot <- ggplot(boot_dists_1000, aes(x = mu_hat_star)) +
geom_histogram(alpha = 0.5, fill = "darkgray") +
labs(subtitle = expression("Distribución bootstrap B = 1000"),
x = "", y = "") +
geom_vline(xintercept = 5, color = "red", alpha = 0.5) +
geom_vline(aes(xintercept = x_bar), color = "blue",
linetype = "dashed", alpha = 0.8) +
facet_grid(sample~boot_trial) +
scale_colour_manual(values = c('mu' = 'red', 'x_bar' = 'blue'), name = '',
labels = c(expression(mu), expression(bar(x)))) +
theme(strip.background = element_blank(), strip.text.y = element_blank(),
strip.text.x = element_blank(), axis.text.y = element_blank())
(pob_plot | plot_spacer() | plot_spacer()) /
(emp_dists_plots | boot_dists_30_plot | boot_dists_1000_plot) +
plot_layout(heights = c(1, 5))
```
En la siguiente gráfica mostramos 6 posibles muestras de tamaño 50 simuladas de
la población, para cada una de ellas se graficó la distribución empírica y se
se realizan histogramas de la distribución bootstrap con $B=30$ y $B=1000$, en
cada caso hacemos dos repeticiones, notemos que cuando el número de muestras
bootstrap es grande las distribuciones bootstrap son muy similares (para una
muestra de la población dada), esto es porque disminuimos el erro Monte Carlo.
También vale la pena recalcar que la distribución bootstrap está centrada en el
valor observado en la muestra (línea azúl punteada) y no en el valor poblacional
sin embargo la forma de la distribución es similar a lo largo de las filas.
![](images/bootstrap_mc_error.png)
Entonces, ¿cuántas muestras bootstrap?
1. Incluso un número chico de replicaciones bootstrap, digamos $B=25$ es
informativo, y $B=50$ con frecuencia es suficiente para dar una buena
estimación de $se_P(\hat{\theta})$ (@Efron).
2. Cuando se busca estimar error estándar @Chihara recomienda $B=1000$ muestras, o
$B=10,000$ muestras dependiendo la presición que se busque.
## Error estándar bootstrap e intervalos normales {-}
Ahora podemos construir nuestra primera versión de intervalos de confianza
basados en la distribución bootstrap.
- Supongamos que queremos estimar una cantidad poblacional $\theta$ con una
estadística $\hat{\theta} = t(X_1,\ldots, X_n)$, donde $X_1,\ldots, X_n$ es una muestra
independiente e idénticamente distribuida de la población.
- Suponemos además que la distribución muestral de $\hat{\theta}$ es aproximadamente normal (el teorema
central del límite aplica), y está centrada en el verdadero valor poblacional $\theta$.
Ahora queremos construir un intervalo que tenga probabilidad 95\% de cubrir al
valor poblacional $\theta$. Tenemos que
$$P(-2\mathsf{ee}(\hat{\theta}) < \hat{\theta} - \theta < 2\mathsf{ee}(\hat{\theta})) \approx 0.95$$
por las propiedades de la distribución normal ($P(-2\sigma < X -\mu < 2\sigma)\approx 0.95$ si $X$ es
normal con media $\mu$ y desviación estándar $\sigma$). Entonces
$$P(\hat{\theta} - 2\mathsf{ee}(\hat{\theta}) < \theta < \hat{\theta} + 2\mathsf{ee}(\hat{\theta})) \approx 0.95$$
Es decir, la probabilidad de que el verdadero valor poblacional $\theta$ esté en el intervalo
$$[\hat{\theta} - 2\mathsf{ee}(\hat{\theta}), \hat{\theta} + 2\mathsf{ee}(\hat{\theta})]$$
es cercano a 0.95. En este intervalo no conocemos el error estándar (es la desviación estándar
de la distribución de muestreo de $\hat{\theta}$), y aquí es donde
entre la distribución bootstrap, que aproxima la distribución de muestreo. Lo estimamos con
$$\hat{\mathsf{ee}}_{\textrm{boot}}(\hat{\theta})$$
que es la desviación estándar de la **distribución bootsrap**.
```{block2, type='mathblock'}
**Definición.** El **error estándar bootstrap** $\hat{\mathsf{ee}}_{\textrm{boot}}(\hat{\theta})$ se
define como la desviación estándar de la distribución bootstrap de $\theta$.
El **intervalo de confianza normal bootstrap** al 95\% está dado por
$$[\hat{\theta} - 2\hat{\mathsf{ee}}_{\textrm{boot}}(\hat{\theta}), \hat{\theta} + 2\hat{\mathsf{ee}}_{\textrm{boot}}(\hat{\theta})].$$
```
Nótese que hay varias cosas qué revisar aquí: que el teorema central del límite aplica y
que la distribución de muestreo de nuestro estimador está centrado en el valor verdadero.
Esto en algunos casos se puede demostrar usando la teoría, pero más abajo veremos
comprobaciones empíricas.
### Ejemplo: tomadores de té negro{-}
Consideremos la estimación que hicimos de el procentaje de tomadores de té que toma
té negro:
```{r}
# paso 1: define el estimador
calc_estimador <- function(datos){
prop_negro <- datos |>
mutate(negro = ifelse(Tea == "black", 1, 0)) |>
summarise(prop_negro = mean(negro), n = length(negro)) |>
pull(prop_negro)
prop_negro
}
prop_hat <- calc_estimador(te)
prop_hat |> round(2)
```
Podemos graficar su distribución bootstrap ---la cual simulamos arriba---.
```{r, fig.width = 7, fig.height = 4}
g_hist <- ggplot(prop_negro_tbl, aes(x = prop_negro)) + geom_histogram(bins = 15)
g_qq_normal <- ggplot(prop_negro_tbl, aes(sample = prop_negro)) +
geom_qq() + geom_qq_line(colour = "red")
g_hist + g_qq_normal
```
Y notamos que la distribución bootstrap es aproximadamente normal.
Adicionalmente, vemos que el sesgo tiene un valor estimado de:
```{r}
media_boot <- prop_negro_tbl |> pull(prop_negro) |> mean()
media_boot - prop_hat
```
De esta forma, hemos verificado que:
- La distribución bootstrap es aproximadamente normal (ver gráfica de cuantiles normales);
- La distribución bootstrap es aproximadamente insesgada.
Lo cual nos lleva a construir intervalos de confianza basados en la distribución
normal. Estimamos el error estándar con la desviación estándar de la
distribución bootstrap
```{r}
ee_boot <- prop_negro_tbl |> pull(prop_negro) |> sd()
ee_boot
```
y construimos un intervalo de confianza del 95%:
```{r}
intervalo_95 <- c(prop_hat - 2 * ee_boot, prop_hat + 2 * ee_boot)
intervalo_95 |> round(2)
```
Este intervalo tiene probabilidad del 95% de capturar al verdadero poblacional. Con
*alta* probabilidad, entonces, el porcentaje de tomadores de té en la población
está entre `r round(intervalo_95[1], 2)` y `r round(intervalo_95[2], 2)`.
## Ejemplo: inventario de casas vendidas {-}
Ahora consideremos el problema de estimar el total del valor de las casas
vendidas en un periodo. Tenemos una muestra de tamaño $n=150$:
```{r, fig.height = 3}
# muestra original
set.seed(121)
muestra_casas <- slice_sample(poblacion_casas, n = 150)
# paso 1: define el estimador
calc_estimador_casas <- function(datos){
N <- nrow(poblacion_casas)
n <- nrow(datos)
total_muestra <- sum(datos$precio_miles)
estimador_total <- (N / n) * total_muestra
estimador_total
}
# paso 2: define el proceso de remuestreo
muestra_boot <- function(datos){
#tomar muestra con reemplazo del mismo tamaño
slice_sample(datos, prop = 1, replace = TRUE)
}
# paso 3: remuestrea y calcula el estimador
totales_boot <- tibble(total_boot = map_dbl(1:5000, ~ calc_estimador_casas(muestra_boot(muestra_casas))))
# paso 4: examina la distribución bootstrap
g_hist <- totales_boot |>
ggplot(aes(x = total_boot)) +
geom_histogram()
g_qq <- totales_boot |>
ggplot(aes(sample = total_boot)) +
geom_qq() + geom_qq_line(colour = "red") +
geom_hline(yintercept = quantile(totales_boot$total_boot, 0.975), colour = "gray") +
geom_hline(yintercept = quantile(totales_boot$total_boot, 0.025), colour = "gray")
g_hist + g_qq
```
En este caso, distribución de muestreo presenta cierta asimetría, pero la
desviación no es grande. En la parte central la aproximación normal es
razonable. Procedemos a revisar sesgo
```{r}
total_est <- calc_estimador_casas(muestra_casas)
sesgo <- mean(totales_boot$total_boot) - total_est
sesgo
```
Este número puede parecer grande, pero sí calculamos la desviación relativa
con respecto al error estándar vemos que es chico en la escala de la distribución bootstrap:
```{r}
ee_boot <- sd(totales_boot$total_boot)
sesgo_relativo <- sesgo / ee_boot
sesgo_relativo
```
De forma que procedemos a construir intervalos de confianza como sigue :
```{r}
c(total_est - 2*ee_boot, total_est + 2*ee_boot)
```
Que está en miles de dólares. En millones de dólares, este intervalo es:
```{r}
intervalo_total <- c(total_est - 2*ee_boot, total_est + 2*ee_boot) / 1000
intervalo_total |> round(1)
```
Así que con 95% de confianza el verdadero total del valor de las casas vendidas
está entre `r round(intervalo_total[1])` y `r round(intervalo_total[2])` millones
de dólares.
**Nota:** en este ejemplo mostraremos una alternativa de intervalos de confianza
que es más apropiado cuando observamos asimetría. Sin embargo, primero tendremos
que hablar de dos conceptos clave con respecto a intervalos de confianza:
calibración e interpretación.
## Calibración de intervalos de confianza {-}
¿Cómo sabemos que nuestros intervalos de confianza del 95% nominal
tienen cobertura real de 95\%? Es decir, tenemos que checar:
- El procedimiento para construir intervalos debe dar intervalos tales
que el valor poblacional está en el intervalo de confianza para 95% de las muestras.
Como solo tenemos una muestra, la calibración depende de argumentos teóricos o
estudios de simulación previos. Para nuestro ejemplo de casas tenemos la
población, así que podemos checar qué cobertura real tienen los intervalos normales:
```{r, cache = FALSE}
simular_intervalos <- function(rep, size = 150){
muestra_casas <- slice_sample(poblacion_casas, n = size)
N <- nrow(poblacion_casas)
n <- nrow(muestra_casas)
total_est <- (N / n) * sum(muestra_casas$precio_miles)
# paso 1: define el estimador
calc_estimador_casas <- function(datos){
total_muestra <- sum(datos$precio_miles)
estimador_total <- (N / n) * total_muestra
estimador_total
}
# paso 2: define el proceso de remuestreo
muestra_boot <- function(datos){
#tomar muestra con reemplazo del mismo tamaño
slice_sample(datos, prop = 1, replace = TRUE)
}
# paso 3: remuestrea y calcula el estimador
totales_boot <- map_dbl(1:2000, ~ calc_estimador_casas(muestra_boot(muestra_casas))) |>
tibble(total_boot = .) |>
summarise(ee_boot = sd(total_boot)) |>
mutate(inf = total_est - 2*ee_boot, sup = total_est + 2*ee_boot) |>
mutate(rep = rep)
totales_boot
}
# Para recrear, correr:
# sims_intervalos <- map(1:100, ~ simular_intervalos(rep = .x))
# write_rds(sims_intervalos, "cache/sims_intervalos.rds")
# Para usar resultados en cache:
sims_intervalos <- read_rds("cache/sims_intervalos.rds")
```
```{r, fig.width =8, fig.height=4}
total <- sum(poblacion_casas$precio_miles)
sims_tbl <- sims_intervalos |>
bind_rows() |>
mutate(cubre = inf < total & total < sup)
ggplot(sims_tbl, aes(x = rep)) +
geom_hline(yintercept = total, colour = "red") +
geom_linerange(aes(ymin = inf, ymax = sup, colour = cubre))
```
La cobertura para estos 100 intervalos simulados da
```{r}
total <- sum(poblacion_casas$precio_miles)
sims_tbl |>
summarise(cobertura = mean(cubre))
```
que es *consistente* con una cobertura real del 95% (¿qué significa
"consistente"? ¿Cómo puedes checarlo con el bootstrap?)
**Observación.** En este caso teníamos la población real, y pudimos verificar
la cobertura de nuestros intervalos. En general no la tenemos. Estos ejercicios
de simulación se pueden hacer con poblaciones sintéticas que se generen con
las características que creemos va a tener nuestra población (por ejemplo, sesgo,
colas largas, etc.).
```{block2 ,type='comentario'}
En general, no importa qué tipo de estimadores o intervalos de confianza usemos,
requerimos checar la calibración. Esto puede hacerse con ejercicios de
simulación con poblaciones sintéticas y tanto los procedimientos de muestreo
como los tamaños de muestra que nos interesa usar.