diff --git a/01-exploratorio.md b/01-exploratorio.md index 7280763..605a89f 100644 --- a/01-exploratorio.md +++ b/01-exploratorio.md @@ -67,23 +67,23 @@ slice_sample(propinas, n = 10) |> gt() ``` ```{=html} -
Y vemos una muestra
slice_sample(propinas, n = 10) |> gt()
En esta sección discutiremos cuál el objetivo general del proceso de estimación. +
En esta sección discutiremos cuál el objetivo general del proceso de estimación, y cómo entender y manejar la variabilidad que se produce cuando aleatorizamos la selección de las muestras que utilizamos para hacer análisis.
A diferencia de las pruebas de permutación, donde evaluábamos como cambiaría una @@ -413,7 +413,7 @@
estimar_total(sample_n(marco_casas, 100), N) |>
-mutate(across(where(is.numeric), round, 2))
## # A tibble: 1 × 3
## total_muestra factor_exp est_total_millones
## <dbl> <dbl> <dbl>
@@ -867,13 +867,13 @@ Ejemplo valores_est <- map_dbl(1:m, ~ est(rexp(n, lambda)))
tibble(id_muestra = 1:m, estimacion = valores_est)
- }<- replicar_muestreo_exp(sum, 4000, n = 150)
+ sim_estimador_1 <- replicar_muestreo_exp(sum, 4000, n = 15)
sim_estimador_1 <- tibble(x = seq(0, 35, 0.001)) |>
- teorica mutate(f_dens = dgamma(x, shape = 150, rate = 1))
+ mutate(f_dens = dgamma(x, shape = 15, rate = 1))
# graficar aprox de distribución de muestreo
ggplot(sim_estimador_1) +
-geom_histogram(aes(x = estimacion, y = ..density..), bins = 35) +
- geom_line(data = teorica, aes(x = x, y = f_dens), colour = "red", size = 1.2)
ggplot(replicaciones_2, aes(sample = est_total_millones)) +
geom_qq(alpha = 0.3) +
geom_qq_line(colour = "red")
--Y vemos que en efecto el TCL aplica en este ejemplo, y la aproximación es buena. +
+Y vemos que en efecto el TCL aplica en este ejemplo, y la aproximación es buena. Aunque la población original es sesgada, la descripción de la distribución de muestreo es sorprendemente compacta:
<- map_df(1:2000, ~ tibble(suma = mean(rgamma(30, 0.1, 1))),
+ sims_gamma <- map_df(1:2000, ~ tibble(suma = sum(rgamma(30, 0.1, 1))),
sims_gamma .id = "n_sim")
ggplot(sims_gamma, aes(x = suma)) + geom_histogram()
-
+
El teorema central del límite aplica a situaciones más generales que -las del enunciado del teorema básico. Por ejemplo, aplica a poblaciones -finitas (como vimos en el ejemplo de las casas) bajo muestreo sin -reemplazo, y aplica también a otras estadísticas como los cuantiles muestrales.
+
+Es importante notar que la calidad de la aproximación del TCL depende de características de la población y también del tamaño de muestra \(n\). Para ver si el TCL aplica, podemos hacer ejercicios de simulación bajo diferentes supuestos acerca de la población. También veremos más adelante, con remuestreo, maneras de checar si es factible el TCL dependiendo del análisis de una muestra dada que tengamos.
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}\), como en Efron and Tibshirani (1993). -Si \(\theta = t(F)\) es una +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 @@ -682,6 +681,18 @@
En nuestro ejemplo, podemos ver varias muestras (por ejemplo 20) de tamaño 200, y -vemos cómo se ve la aproximación a la distribución de la población:
+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):
Podemos calcular las distribuciones de remuestreo (bootstrap) para cada muestra, y compararlas con la distribución de muestreo real.
@@ -816,7 +827,7 @@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. @@ -1357,7 +1368,7 @@
Podemos hacer bootstrap para juzgar la estabilidad del suavizador:
<- function(x, data){
@@ -1994,7 +2005,7 @@ suaviza_boot Bootstrap en R# tamaño por muestra
object_size(computos_boot)/nrow(computos_boot)
## 54.92 kB
-# el incremento en tamaño es << 100
+# el incremento en tamaño es << 1000
as.numeric(object_size(computos_boot)/object_size(muestra_computos))
## [1] 3.895024
Adicionalmente incluye funciones para el cálculo de intervalos bootstrap:
diff --git a/search_index.json b/search_index.json
index 4b25d24..a96197b 100644
--- a/search_index.json
+++ b/search_index.json
@@ -1 +1 @@
-[["index.html", "Fundamentos de Estadística con Remuestreo Información del curso", " Fundamentos de Estadística con Remuestreo Teresa Ortiz, Felipe González, Alfredo Garbuno Información del curso Notas del curso Fundamentos de Estadística con Remuestreo, este curso busca explicar los principios básicos de la estadística y su papel en el análisis de datos. Nuestro punto de vista es uno de fundamentos, con menos énfasis en recetas o técnicas particulares. Ligas Notas: https://tereom.github.io/fundamentos-2024/ Repositorio con material: https://github.com/tereom/fundamentos-2024 Correo: teresa.ortiz.mancera@gmail.com Zoom clase (miércoles 4:00-7:00 pm): https://itam.zoom.us/j/92745909276 Zoom sesiones de dudas (lunes de 5:30-6:30 pm): https://itam.zoom.us/j/92518922348 Canvas: https://itam.instructure.com/courses/13328 x Este trabajo está bajo una Licencia Creative Commons Atribución 4.0 Internacional. "],["temario.html", "Temario Plan semanal Evaluación", " Temario Plan semanal Datos y análisis exploratorio Referencias: (W. S. Cleveland 1994), (Chihara and Hesterberg 2018) Visualización1 Análisis exploratorio Tipos de datos o estudios Muestras diseñadas y muestras naturales Experimentos y datos observacionales Introducción a Pruebas de Hipótesis Referencias: (Chihara and Hesterberg 2018) Introducción a pruebas de hipótesis. Pruebas de permutaciones Muestras pareadas y otros ejemplos Estimación y distribución de muestreo Referencias: (Chihara and Hesterberg 2018), (Tim C. Hesterberg 2015b) Estimadores y su distribución de muestreo Repaso de probabilidad y Teorema del límite central Introducción a estimación por intervalos Referencias: (Chihara and Hesterberg 2018), (Efron and Tibshirani 1993), (Tim C. Hesterberg 2015b) El método plugin y el boostrap Bootstrap e Intervalos de confianza. Ejemplos. Estimación Referencias: (Chihara and Hesterberg 2018), (Wasserman 2013) Estimación por máxima verosimilitud Ejemplos de estimación por máxima verosimilitud y Bootstrap paramétrico Propiedades de estimadores de máxima verosimilitud Más de pruebas de hipótesis Referencias: (Chihara and Hesterberg 2018), (Wasserman 2013) Pruebas de hipótesis para medias y proporciones: una y dos poblaciones. Introducción a inferencia bayesiana Referencias: (Kruschke 2015) Introducción a inferencia bayesiana Ejemplos de distribuciones conjugadas Introducción a métodos computacionales básicos: Muestreadores Metrópolis y Gibbs Ejemplos de inferencia bayesiana en Stan Evaluación Se evaluará mediante tareas semanales y dos exámenes: Tareas semanales (20%) Examen parcial en clase y a casa (40%) Examen final a casa (40%) Referencias "],["análisis-exploratorio.html", "Sección 1 Análisis exploratorio El papel de la exploración en el análisis de datos Preguntas y datos Algunos conceptos básicos Ejemplos Suavizamiento loess Caso de estudio: nacimientos en México", " Sección 1 Análisis exploratorio “Exploratory data analysis can never be the whole story, but nothing else can serve as the foundation stone –as the first step.” — John Tukey El papel de la exploración en el análisis de datos El estándar científico para contestar preguntas o tomar decisiones es uno que se basa en el análisis de datos. Es decir, en primer lugar se deben reunir todos los datos que puedan contener o sugerir alguna guía para entender mejor la pregunta o la decisión a la que nos enfrentamos. Esta recopilación de datos —que pueden ser cualitativos, cuantitativos, o una mezcla de los dos— debe entonces ser analizada para extraer información relevante para nuestro problema. En análisis de datos existen dos distintos tipos de trabajo: El trabajo exploratorio o de detective: ¿cuáles son los aspectos importantes de estos datos? ¿qué indicaciones generales muestran los datos? ¿qué tareas de análisis debemos empezar haciendo? ¿cuáles son los caminos generales para formular con precisión y contestar algunas preguntas que nos interesen? El trabajo inferencial, confirmatorio, o de juez: ¿cómo evaluar el peso de la evidencia de los descubrimientos del paso anterior? ¿qué tan bien soportadas están las respuestas y conclusiones por nuestro conjunto de datos? Preguntas y datos Cuando observamos un conjunto de datos, independientemente de su tamaño, el paso inicial más importante es entender bajo qué proceso se generan los datos. A grandes rasgos, cuanto más sepamos de este proceso, mejor podemos contestar preguntas de interés. En muchos casos, tendremos que hacer algunos supuestos de cómo se generan estos datos para dar respuestas (condicionales a esos supuestos). Algunos conceptos básicos Empezamos explicando algunas ideas que no serán útiles más adelante. El primer concepto se refiere a entender cómo se distribuyen los datos a los largo de su escala de medición. Comenzamos con un ejemplo: los siguientes datos fueron registrados en un restaurante durante cuatro días consecutivos. library(tidyverse) library(patchwork) # organizar gráficas library(gt) # formatear tablas source("R/funciones_auxiliares.R") # usamos los datos tips del paquete reshape2 propinas <- read_csv("./data/propinas.csv") Y vemos una muestra slice_sample(propinas, n = 10) |> gt() #glqxwnsyoa table { font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #glqxwnsyoa thead, #glqxwnsyoa tbody, #glqxwnsyoa tfoot, #glqxwnsyoa tr, #glqxwnsyoa td, #glqxwnsyoa th { border-style: none; } #glqxwnsyoa p { margin: 0; padding: 0; } #glqxwnsyoa .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; } #glqxwnsyoa .gt_caption { padding-top: 4px; padding-bottom: 4px; } #glqxwnsyoa .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; } #glqxwnsyoa .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; } #glqxwnsyoa .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #glqxwnsyoa .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #glqxwnsyoa .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #glqxwnsyoa .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; } #glqxwnsyoa .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; } #glqxwnsyoa .gt_column_spanner_outer:first-child { padding-left: 0; } #glqxwnsyoa .gt_column_spanner_outer:last-child { padding-right: 0; } #glqxwnsyoa .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; } #glqxwnsyoa .gt_spanner_row { border-bottom-style: hidden; } #glqxwnsyoa .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; } #glqxwnsyoa .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; } #glqxwnsyoa .gt_from_md > :first-child { margin-top: 0; } #glqxwnsyoa .gt_from_md > :last-child { margin-bottom: 0; } #glqxwnsyoa .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; } #glqxwnsyoa .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; } #glqxwnsyoa .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; } #glqxwnsyoa .gt_row_group_first td { border-top-width: 2px; } #glqxwnsyoa .gt_row_group_first th { border-top-width: 2px; } #glqxwnsyoa .gt_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #glqxwnsyoa .gt_first_summary_row { border-top-style: solid; border-top-color: #D3D3D3; } #glqxwnsyoa .gt_first_summary_row.thick { border-top-width: 2px; } #glqxwnsyoa .gt_last_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #glqxwnsyoa .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #glqxwnsyoa .gt_first_grand_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; } #glqxwnsyoa .gt_last_grand_summary_row_top { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; } #glqxwnsyoa .gt_striped { background-color: rgba(128, 128, 128, 0.05); } #glqxwnsyoa .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #glqxwnsyoa .gt_footnotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #glqxwnsyoa .gt_footnote { margin: 0px; font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #glqxwnsyoa .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #glqxwnsyoa .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #glqxwnsyoa .gt_left { text-align: left; } #glqxwnsyoa .gt_center { text-align: center; } #glqxwnsyoa .gt_right { text-align: right; font-variant-numeric: tabular-nums; } #glqxwnsyoa .gt_font_normal { font-weight: normal; } #glqxwnsyoa .gt_font_bold { font-weight: bold; } #glqxwnsyoa .gt_font_italic { font-style: italic; } #glqxwnsyoa .gt_super { font-size: 65%; } #glqxwnsyoa .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; } #glqxwnsyoa .gt_asterisk { font-size: 100%; vertical-align: 0; } #glqxwnsyoa .gt_indent_1 { text-indent: 5px; } #glqxwnsyoa .gt_indent_2 { text-indent: 10px; } #glqxwnsyoa .gt_indent_3 { text-indent: 15px; } #glqxwnsyoa .gt_indent_4 { text-indent: 20px; } #glqxwnsyoa .gt_indent_5 { text-indent: 25px; } #glqxwnsyoa .katex-display { display: inline-flex !important; margin-bottom: 0.75em !important; } #glqxwnsyoa div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { height: 0px !important; } cuenta_total propina fumador dia momento num_personas 30.06 2.00 Si Sab Cena 3 24.01 2.00 Si Sab Cena 4 45.35 3.50 Si Dom Cena 3 30.14 3.09 Si Sab Cena 4 20.08 3.15 No Sab Cena 3 19.65 3.00 No Sab Cena 2 38.07 4.00 No Dom Cena 3 11.69 2.31 No Jue Comida 2 26.41 1.50 No Sab Cena 2 13.81 2.00 Si Sab Cena 2 Aquí la unidad de observación es una cuenta particular. Tenemos tres mediciones numéricas de cada cuenta: cúanto fue la cuenta total, la propina, y el número de personas asociadas a la cuenta. Los datos están separados según se fumó o no en la mesa, y temporalmente en dos partes: el día (Jueves, Viernes, Sábado o Domingo), cada uno separado por Cena y Comida. Denotamos por \\(x\\) el valor de medición de una unidad de observación. Usualmente utilizamos sub-índices para identificar entre diferentes puntos de datos (observaciones), por ejemplo, \\(x_n\\) para la \\(n-\\)ésima observación. De tal forma que una colección de \\(N\\) observaciones la escribimos como \\[\\begin{align} \\{x_1, \\ldots, x_N\\}. \\end{align}\\] El primer tipo de comparaciones que nos interesa hacer es para una medición: ¿Varían mucho o poco los datos de un tipo de medición? ¿Cuáles son valores típicos o centrales? ¿Existen valores atípicos? Supongamos entonces que consideramos simplemente la variable de cuenta_total. Podemos comenzar por ordenar los datos, y ver cuáles datos están en los extremos y cuáles están en los lugares centrales: propinas <- propinas |> mutate(orden_cuenta = rank(cuenta_total, ties.method = "first"), f = (orden_cuenta - 0.5) / n()) cuenta <- propinas |> select(orden_cuenta, f, cuenta_total) |> arrange(f) bind_rows(head(cuenta), tail(cuenta)) |> gt() |> fmt_number(columns = f, decimals = 3) #yqxfzojwpz table { font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #yqxfzojwpz thead, #yqxfzojwpz tbody, #yqxfzojwpz tfoot, #yqxfzojwpz tr, #yqxfzojwpz td, #yqxfzojwpz th { border-style: none; } #yqxfzojwpz p { margin: 0; padding: 0; } #yqxfzojwpz .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; } #yqxfzojwpz .gt_caption { padding-top: 4px; padding-bottom: 4px; } #yqxfzojwpz .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; } #yqxfzojwpz .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; } #yqxfzojwpz .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #yqxfzojwpz .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #yqxfzojwpz .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #yqxfzojwpz .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; } #yqxfzojwpz .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; } #yqxfzojwpz .gt_column_spanner_outer:first-child { padding-left: 0; } #yqxfzojwpz .gt_column_spanner_outer:last-child { padding-right: 0; } #yqxfzojwpz .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; } #yqxfzojwpz .gt_spanner_row { border-bottom-style: hidden; } #yqxfzojwpz .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; } #yqxfzojwpz .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; } #yqxfzojwpz .gt_from_md > :first-child { margin-top: 0; } #yqxfzojwpz .gt_from_md > :last-child { margin-bottom: 0; } #yqxfzojwpz .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; } #yqxfzojwpz .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; } #yqxfzojwpz .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; } #yqxfzojwpz .gt_row_group_first td { border-top-width: 2px; } #yqxfzojwpz .gt_row_group_first th { border-top-width: 2px; } #yqxfzojwpz .gt_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #yqxfzojwpz .gt_first_summary_row { border-top-style: solid; border-top-color: #D3D3D3; } #yqxfzojwpz .gt_first_summary_row.thick { border-top-width: 2px; } #yqxfzojwpz .gt_last_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #yqxfzojwpz .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #yqxfzojwpz .gt_first_grand_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; } #yqxfzojwpz .gt_last_grand_summary_row_top { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; } #yqxfzojwpz .gt_striped { background-color: rgba(128, 128, 128, 0.05); } #yqxfzojwpz .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #yqxfzojwpz .gt_footnotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #yqxfzojwpz .gt_footnote { margin: 0px; font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #yqxfzojwpz .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #yqxfzojwpz .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #yqxfzojwpz .gt_left { text-align: left; } #yqxfzojwpz .gt_center { text-align: center; } #yqxfzojwpz .gt_right { text-align: right; font-variant-numeric: tabular-nums; } #yqxfzojwpz .gt_font_normal { font-weight: normal; } #yqxfzojwpz .gt_font_bold { font-weight: bold; } #yqxfzojwpz .gt_font_italic { font-style: italic; } #yqxfzojwpz .gt_super { font-size: 65%; } #yqxfzojwpz .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; } #yqxfzojwpz .gt_asterisk { font-size: 100%; vertical-align: 0; } #yqxfzojwpz .gt_indent_1 { text-indent: 5px; } #yqxfzojwpz .gt_indent_2 { text-indent: 10px; } #yqxfzojwpz .gt_indent_3 { text-indent: 15px; } #yqxfzojwpz .gt_indent_4 { text-indent: 20px; } #yqxfzojwpz .gt_indent_5 { text-indent: 25px; } #yqxfzojwpz .katex-display { display: inline-flex !important; margin-bottom: 0.75em !important; } #yqxfzojwpz div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { height: 0px !important; } orden_cuenta f cuenta_total 1 0.002 3.07 2 0.006 5.75 3 0.010 7.25 4 0.014 7.25 5 0.018 7.51 6 0.023 7.56 239 0.977 44.30 240 0.982 45.35 241 0.986 48.17 242 0.990 48.27 243 0.994 48.33 244 0.998 50.81 También podemos graficar los datos en orden, interpolando valores consecutivos. A esta función le llamamos la función de cuantiles para la variable cuenta_total. Nos sirve para comparar directamente los distintos valores que observamos los datos según el orden que ocupan. En particular, podemos estudiar la dispersión y valores centrales de los datos observados: El rango de datos va de unos 3 dólares hasta 50 dólares Los valores centrales, por ejemplo el 50% de los valores más centrales, están entre unos 13 y 25 dólares. El valor que divide en dos mitades iguales a los datos es de alrededor de 18 dólares. El cuantil \\(f\\), que denotamos por \\(q(f)\\) es valor a lo largo de la escala de medición de los datos tal que aproximadamente una fracción \\(f\\) de los datos son menores o iguales a \\(q(f)\\). Al cuantil \\(f=0.5\\) le llamamos la mediana. A los cuantiles \\(f=0.25\\) y \\(f=0.75\\) les llamamos cuartiles inferior y superior. En nuestro ejemplo: Los valores centrales —del cuantil 0.25 al 0.75, por decir un ejemplo— están entre unos 13 y 25 dólares. Estos dos cuantiles se llaman cuartil inferior y cuartil superior respectivamente El cuantil 0.5 (o también conocido como mediana) está alrededor de 18 dólares. Éste último puede ser utilizado para dar un valor central de la distribución de valores para cuenta_total. Asimismo podemos dar resúmenes más refinados si es necesario. Por ejemplo, podemos reportar que: El cuantil 0.95 es de unos 35 dólares — sólo 5% de las cuentas son de más de 35 dólares El cuantil 0.05 es de unos 8 dólares — sólo 5% de las cuentas son de 8 dólares o menos. Finalmente, la forma de la gráfica se interpreta usando su pendiente (tasa de cambio) haciendo comparaciones en diferentes partes de la gráfica: La distribución de valores tiene asimetría: el 10% de las cuentas más altas tiene considerablemente más dispersión que el 10% de las cuentas más bajas. Entre los cuantiles 0.2 y 0.7 es donde existe mayor densidad de datos: la pendiente (tasa de cambio) es baja, lo que significa que al avanzar en los valores observados, los cuantiles (el porcentaje de casos) aumenta rápidamente. Cuando la pendiente alta, quiere decir que los datos tienen más dispersión local o están más separados. Observación: Hay varias maneras de definir los cuantiles (ver (William S. Cleveland 1993)): Supongamos que queremos definir \\(q(f)\\), y denotamos los datos ordenados como \\(x_{(1)}, x_{(2)}, \\ldots, x_{(N)}\\), de forma que \\(x_{(1)}\\) es el dato más chico y \\(x_{(N)}\\) es el dato más grande. Para cada \\(x_{(i)}\\) definimos \\[f_i = i / N\\] entonces definimos el cuantil \\(q(f_i)=x_{(i)}\\). Para cualquier \\(f\\) entre 0 y 1, podemos definir \\(q(f)\\) como sigue: si \\(f\\) está entre \\(f_i\\) y \\(f_{i+1}\\) interpolamos linealmente los valores correspondientes \\(x_{(i)}\\) y \\(x_{(i+1)}\\). En la práctica, es más conveniente usar \\(f_i= \\frac{i - 0.5}{N}\\). La gráfica de cuantiles no cambia mucho comparado con la difinición anterior, y esto nos permitirá comparar de mejor manera con distribuciones teóricas que no tienen definido su cuantil 0 y el 1, pues tienen soporte en los números reales (como la distribución normal, por ejemplo). Asociada a la función de cuantiles \\(q\\) tenemos la distribución acumulada empírica de los datos, que es aproximadamente inversa de la función de cuantiles, y se define como: \\[\\hat{F}(x) = i/N\\] si \\(x_{(i)} \\leq x < x_{(i+1)}\\). Nótese que \\(\\hat{F}(q(f_i)) = i/N = f_i\\) (demuéstralo). acum_cuenta <- ecdf(cuenta$cuenta_total) cuenta <- cuenta |> mutate(dea_cuenta_total = acum_cuenta(cuenta_total)) g_acum <- ggplot(cuenta, aes(x = cuenta_total, y = dea_cuenta_total)) + geom_point() + labs(subtitle = "Distribución acum empírica de cuenta total", x = "") g_cuantiles + g_acum La función de distribución acumulada empírica es otra forma de graficar la dispersión de los datos. En su gráfica vemos que proporción de los datos que son iguales o están por debajo de cada valor en el eje horizontal. Nota: En análisis de datos, es más frecuente utilizar la función de cuantiles pues existen versiones más generales que son útiles, por ejemplo, para evaluar ajuste de modelos probabilísticos En la teoría, generalmente es más común utilizar la fda empírica, que tiene una única definición que veremos coincide con definiciones teóricas. Histogramas En algunos casos, es más natural hacer un histograma, donde dividimos el rango de la variable en cubetas o intervalos (en este caso de igual longitud), y graficamos por medio de barras cuántos datos caen en cada cubeta: Es una gráfica más popular, pero perdemos cierto nivel de detalle, y distintas particiones resaltan distintos aspectos de los datos. ¿Cómo se ve la gráfica de cuantiles de las propinas? ¿Cómo crees que esta gráfica se compara con distintos histogramas? g_1 <- ggplot(propinas, aes(sample = propina)) + geom_qq(distribution = stats::qunif) + labs(x = "f", y = "propina") g_1 Finalmente, una gráfica más compacta que resume la gráfica de cuantiles o el histograma es el diagrama de caja y brazos. Mostramos dos versiones, la clásica de Tukey (T) y otra versión menos común de Spear/Tufte (ST): library(ggthemes) cuartiles <- quantile(cuenta$cuenta_total) cuartiles |> round(2) ## 0% 25% 50% 75% 100% ## 3.07 13.35 17.80 24.13 50.81 g_1 <- ggplot(cuenta, aes(x = f, y = cuenta_total)) + labs(subtitle = "Gráfica de cuantiles: Cuenta total") + geom_hline(yintercept = cuartiles[2:4], colour = "gray") + geom_point(alpha = 0.5) + geom_line() g_2 <- ggplot(cuenta, aes(x = factor("ST", levels = "ST"), y = cuenta_total)) + geom_tufteboxplot() + labs(subtitle = "", x = "", y = "") g_3 <- ggplot(cuenta, aes(x = factor("T"), y = cuenta_total)) + geom_boxplot() + labs(subtitle = "", x = "", y = "") g_4 <- ggplot(cuenta, aes(x = factor("P"), y = cuenta_total)) + geom_jitter(height = 0, width =0.2, alpha = 0.5) + labs(subtitle = "", x = "", y = "") g_5 <- ggplot(cuenta, aes(x = factor("V"), y = cuenta_total)) + geom_violin() + labs(subtitle = "", x = "", y = "") g_1 + g_2 + g_3 + g_4 + plot_layout(widths = c(8, 2, 2, 2)) El diagrama de abajo explica los elementos de la versión típica del diagrama de caja y brazos (boxplot). RIC se refiere al Rango Intercuantílico, definido por la diferencia entre los cuantiles 25% y 75%. Figura: Jumanbar / CC BY-SA Ventajas en el análisis inicial En un principio del análisis, estos resúmenes (cuantiles) pueden ser más útiles que utilizar medias y varianzas, por ejemplo. La razón es que los cuantiles: Son cantidades más fácilmente interpretables Los cuantiles centrales son más resistentes a valores atípicos que medias o varianzas Permiten identificar valores extremos Es fácil comparar cuantiles de distintos bonches de datos en la misma escala Nota: Existen diferentes definiciones para calcular cuantiles de una muestra de datos, puedes leer más en este artículo. Media y desviación estándar Las medidas más comunes de localización y dispersión para un conjunto de datos son la media muestral y la desviación estándar muestral. En general, no son muy apropiadas para iniciar el análisis exploratorio, pues: Son medidas más difíciles de interpretar y explicar que los cuantiles. En este sentido, son medidas especializadas. Por ejemplo, compara una explicación intuitiva de la mediana contra una explicación intuitiva de la media. No son resistentes a valores atípicos. Su falta de resistencia los vuelve poco útiles en las primeras etapas de limpieza y descripción y en resúmenes deficientes para distribuciones irregulares (con colas largas por ejemplo). La media, o promedio, se denota por \\(\\bar x\\) y se define como \\[\\begin{align} \\bar x = \\frac1N \\sum_{n = 1}^N x_n. \\end{align}\\] La desviación estándar muestral se define como \\[\\begin{align} \\text{std}(x) = \\sqrt{\\frac1{N-1} \\sum_{n = 1}^N (x_n - \\bar x)^2}. \\end{align}\\] Observación: Si \\(N\\) no es muy chica, no importa mucho si dividimos por \\(N\\) o por \\(N-1\\) en la fórmula de la desviación estándar. La razón de que típicamente se usa \\(N-1\\) la veremos más adelante, en la parte de estimación. Por otro lado, ventajas de estas medidas de centralidad y dispersión son: La media y desviación estándar son computacionalmente convenientes. Por lo tanto regresaremos a estas medidas una vez que estudiemos modelos de probabilidad básicos. En muchas ocasiones conviene usar estas medidas pues permite hacer comparaciones históricas o tradicionales —pues análisis anteriores pudieran estar basados en éstas. Considera el caso de tener \\(N\\) observaciones y asume que ya tienes calculado el promedio para dichas observaciones. Este promedio lo denotaremos por \\(\\bar x_N\\). Ahora, considera que has obtenido \\(M\\) observaciones más. Escribe una fórmula recursiva para la media del conjunto total de datos \\(\\bar x_{N+M}\\) en función de lo que ya tenías precalculado \\(\\bar x_N.\\) ¿En qué situaciones esta propiedad puede ser conveniente? Ejemplos Precios de casas En este ejemplo consideremos los datos de precios de ventas de la ciudad de Ames, Iowa. En particular nos interesa entender la variación del precio de las casas. Por este motivo calculamos los cuantiles que corresponden al 25%, 50% y 75% (cuartiles), así como el mínimo y máximo de los precios de las casas: quantile(casas |> pull(precio_miles)) ## 0% 25% 50% 75% 100% ## 37.9 132.0 165.0 215.0 755.0 Comprueba que el mínimo y máximo están asociados a los cuantiles 0% y 100%, respectivamente. Una posible comparación es considerar los precios y sus variación en función de zona de la ciudad en que se encuentra una vivienda. Podemos usar diagramas de caja y brazos para hacer una comparación burda de los precios en distintas zonas de la ciudad: ggplot(casas, aes(x = nombre_zona, y = precio_miles)) + geom_boxplot() + coord_flip() La primera pregunta que nos hacemos es cómo pueden variar las características de las casas dentro de cada zona. Para esto, podemos considerar el área de las casas. En lugar de graficar el precio, graficamos el precio por metro cuadrado, por ejemplo: ggplot(casas, aes(x = nombre_zona, y = precio_m2)) + geom_boxplot() + coord_flip() Podemos cuantificar la variación que observamos de zona a zona y la variación que hay dentro de cada una de las zonas. Una primera aproximación es observar las variación del precio al calcular la mediana dentro de cada zona, y después cuantificar por medio de cuantiles cómo varía la mediana entre zonas: casas |> group_by(nombre_zona) |> summarise(mediana_zona = median(precio_m2), .groups = "drop") |> arrange(mediana_zona) |> pull(mediana_zona) |> quantile() |> round() ## 0% 25% 50% 75% 100% ## 963 1219 1298 1420 1725 Por otro lado, las variaciones con respecto a las medianas dentro de cada zona, por grupo, se resume como: quantile(casas |> group_by(nombre_zona) |> mutate(residual = precio_m2 - median(precio_m2)) |> pull(residual)) |> round() ## 0% 25% 50% 75% 100% ## -765 -166 0 172 1314 Nótese que este último paso tiene sentido pues la variación dentro de las zonas, en términos de precio por metro cuadrado, es similar. Esto no lo podríamos haber hecho de manera efectiva si se hubiera utilizado el precio de las casas sin ajustar por su tamaño. Podemos resumir este primer análisis con un par de gráficas de cuantiles (William S. Cleveland (1993)): mediana <- median(casas$precio_m2) resumen <- casas |> select(nombre_zona, precio_m2) |> group_by(nombre_zona) |> mutate(mediana_zona = median(precio_m2)) |> mutate(residual = precio_m2 - mediana_zona) |> ungroup() |> mutate(mediana_zona = mediana_zona - mediana) |> select(nombre_zona, mediana_zona, residual) |> pivot_longer(mediana_zona:residual, names_to = "tipo", values_to = "valor") ggplot(resumen, aes(sample = valor)) + geom_qq(distribution = stats::qunif) + facet_wrap(~ tipo) + ylab("Precio por m2") + xlab("f") + labs(subtitle = "Precio por m2 por zona", caption = paste0("Mediana total de ", round(mediana))) Vemos que la mayor parte de la variación del precio por metro cuadrado ocurre dentro de cada zona, una vez que controlamos por el tamaño de las casas. La variación dentro de cada zona es aproximadamente simétrica, aunque la cola derecha es ligeramente más larga con algunos valores extremos. Podemos seguir con otro indicador importante: la calificación de calidad de los terminados de las casas. Como primer intento podríamos hacer: Lo que indica que las calificaciones de calidad están distribuidas de manera muy distinta a lo largo de las zonas, y que probablemente no va ser simple desentrañar qué variación del precio se debe a la zona y cuál se debe a la calidad. Prueba Enlace Consideremos la prueba Enlace (2011) de matemáticas para primarias. Una primera pregunta que alguien podría hacerse es: ¿cuáles escuelas son mejores en este rubro, las privadas o las públicas? enlace_tbl <- enlace |> group_by(tipo) |> summarise(n_escuelas = n(), cuantiles = list(cuantil(mate_6, c(0.05, 0.25, 0.5, 0.75, 0.95)))) |> unnest(cols = cuantiles) |> mutate(valor = round(valor)) enlace_tbl |> spread(cuantil, valor) |> gt() #sykxxonvyi table { font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #sykxxonvyi thead, #sykxxonvyi tbody, #sykxxonvyi tfoot, #sykxxonvyi tr, #sykxxonvyi td, #sykxxonvyi th { border-style: none; } #sykxxonvyi p { margin: 0; padding: 0; } #sykxxonvyi .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; } #sykxxonvyi .gt_caption { padding-top: 4px; padding-bottom: 4px; } #sykxxonvyi .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; } #sykxxonvyi .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; } #sykxxonvyi .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #sykxxonvyi .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #sykxxonvyi .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #sykxxonvyi .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; } #sykxxonvyi .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; } #sykxxonvyi .gt_column_spanner_outer:first-child { padding-left: 0; } #sykxxonvyi .gt_column_spanner_outer:last-child { padding-right: 0; } #sykxxonvyi .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; } #sykxxonvyi .gt_spanner_row { border-bottom-style: hidden; } #sykxxonvyi .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; } #sykxxonvyi .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; } #sykxxonvyi .gt_from_md > :first-child { margin-top: 0; } #sykxxonvyi .gt_from_md > :last-child { margin-bottom: 0; } #sykxxonvyi .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; } #sykxxonvyi .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; } #sykxxonvyi .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; } #sykxxonvyi .gt_row_group_first td { border-top-width: 2px; } #sykxxonvyi .gt_row_group_first th { border-top-width: 2px; } #sykxxonvyi .gt_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #sykxxonvyi .gt_first_summary_row { border-top-style: solid; border-top-color: #D3D3D3; } #sykxxonvyi .gt_first_summary_row.thick { border-top-width: 2px; } #sykxxonvyi .gt_last_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #sykxxonvyi .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #sykxxonvyi .gt_first_grand_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; } #sykxxonvyi .gt_last_grand_summary_row_top { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; } #sykxxonvyi .gt_striped { background-color: rgba(128, 128, 128, 0.05); } #sykxxonvyi .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #sykxxonvyi .gt_footnotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #sykxxonvyi .gt_footnote { margin: 0px; font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #sykxxonvyi .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #sykxxonvyi .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #sykxxonvyi .gt_left { text-align: left; } #sykxxonvyi .gt_center { text-align: center; } #sykxxonvyi .gt_right { text-align: right; font-variant-numeric: tabular-nums; } #sykxxonvyi .gt_font_normal { font-weight: normal; } #sykxxonvyi .gt_font_bold { font-weight: bold; } #sykxxonvyi .gt_font_italic { font-style: italic; } #sykxxonvyi .gt_super { font-size: 65%; } #sykxxonvyi .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; } #sykxxonvyi .gt_asterisk { font-size: 100%; vertical-align: 0; } #sykxxonvyi .gt_indent_1 { text-indent: 5px; } #sykxxonvyi .gt_indent_2 { text-indent: 10px; } #sykxxonvyi .gt_indent_3 { text-indent: 15px; } #sykxxonvyi .gt_indent_4 { text-indent: 20px; } #sykxxonvyi .gt_indent_5 { text-indent: 25px; } #sykxxonvyi .katex-display { display: inline-flex !important; margin-bottom: 0.75em !important; } #sykxxonvyi div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { height: 0px !important; } tipo n_escuelas 0.05 0.25 0.5 0.75 0.95 Indígena/Conafe 13599 304 358 412 478 588 General 60166 380 454 502 548 631 Particular 6816 479 551 593 634 703 Para un análisis exploratorio podemos utilizar distintas gráficas. Por ejemplo, podemos utilizar nuevamente las gráficas de caja y brazos, así como graficar los percentiles. Nótese que en la gráfica 1 se utilizan los cuantiles 0.05, 0.25, 0.5, 0.75 y 0.95: ## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0. ## ℹ Please use `linewidth` instead. ## This warning is displayed once every 8 hours. ## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was ## generated. Se puede discutir qué tan apropiada es cada gráfica con el objetivo de realizar comparaciones. Sin duda, graficar más cuantiles es más útil para hacer comparaciones. Por ejemplo, en la Gráfica 1 podemos ver que la mediana de las escuelas generales está cercana al cuantil 5% de las escuelas particulares. Por otro lado, el diagrama de caja y brazos muestra también valores “atípicos”. Antes de contestar prematuramente la pregunta: ¿cuáles son las mejores escuelas? busquemos mejorar la interpretabilidad de nuestras comparaciones. Podemos comenzar por agregar, por ejemplo, el nivel del marginación del municipio donde se encuentra la escuela. Para este objetivo, podemos usar páneles (pequeños múltiplos útiles para hacer comparaciones) y graficar: Esta gráfica pone en contexto la pregunta inicial, y permite evidenciar la dificultad de contestarla. En particular: Señala que la pregunta no sólo debe concentarse en el tipo de “sistema”: pública, privada, etc. Por ejemplo, las escuelas públicas en zonas de marginación baja no tienen una distribución de calificaciones muy distinta a las privadas en zonas de marginación alta. El contexto de la escuela es importante. Debemos de pensar qué factores –por ejemplo, el entorno familiar de los estudiantes– puede resultar en comparaciones que favorecen a las escuelas privadas. Un ejemplo de esto es considerar si los estudiantes tienen que trabajar o no. A su vez, esto puede o no ser reflejo de la calidad del sistema educativo. Si esto es cierto, entonces la pregunta inicial es demasiado vaga y mal planteada. Quizá deberíamos intentar entender cuánto “aporta” cada escuela a cada estudiante, como medida de qué tan buena es cada escuela. Estados y calificaciones en SAT ¿Cómo se relaciona el gasto por alumno, a nivel estatal, con sus resultados académicos? Hay trabajo considerable en definir estos términos, pero supongamos que tenemos el siguiente conjunto de datos (Guber 1999), que son datos oficiales agregados por estado de Estados Unidos. Consideremos el subconjunto de variables sat, que es la calificación promedio de los alumnos en cada estado (para 1997) y expend, que es el gasto en miles de dólares por estudiante en (1994-1995). sat <- read_csv("data/sat.csv") sat_tbl <- sat |> select(state, expend, sat) |> gather(variable, valor, expend:sat) |> group_by(variable) |> summarise(cuantiles = list(cuantil(valor))) |> unnest(cols = c(cuantiles)) |> mutate(valor = round(valor, 1)) |> spread(cuantil, valor) sat_tbl |> gt() #trvhlcrcph table { font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #trvhlcrcph thead, #trvhlcrcph tbody, #trvhlcrcph tfoot, #trvhlcrcph tr, #trvhlcrcph td, #trvhlcrcph th { border-style: none; } #trvhlcrcph p { margin: 0; padding: 0; } #trvhlcrcph .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; } #trvhlcrcph .gt_caption { padding-top: 4px; padding-bottom: 4px; } #trvhlcrcph .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; } #trvhlcrcph .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; } #trvhlcrcph .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #trvhlcrcph .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #trvhlcrcph .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #trvhlcrcph .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; } #trvhlcrcph .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; } #trvhlcrcph .gt_column_spanner_outer:first-child { padding-left: 0; } #trvhlcrcph .gt_column_spanner_outer:last-child { padding-right: 0; } #trvhlcrcph .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; } #trvhlcrcph .gt_spanner_row { border-bottom-style: hidden; } #trvhlcrcph .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; } #trvhlcrcph .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; } #trvhlcrcph .gt_from_md > :first-child { margin-top: 0; } #trvhlcrcph .gt_from_md > :last-child { margin-bottom: 0; } #trvhlcrcph .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; } #trvhlcrcph .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; } #trvhlcrcph .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; } #trvhlcrcph .gt_row_group_first td { border-top-width: 2px; } #trvhlcrcph .gt_row_group_first th { border-top-width: 2px; } #trvhlcrcph .gt_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #trvhlcrcph .gt_first_summary_row { border-top-style: solid; border-top-color: #D3D3D3; } #trvhlcrcph .gt_first_summary_row.thick { border-top-width: 2px; } #trvhlcrcph .gt_last_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #trvhlcrcph .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #trvhlcrcph .gt_first_grand_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; } #trvhlcrcph .gt_last_grand_summary_row_top { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; } #trvhlcrcph .gt_striped { background-color: rgba(128, 128, 128, 0.05); } #trvhlcrcph .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #trvhlcrcph .gt_footnotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #trvhlcrcph .gt_footnote { margin: 0px; font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #trvhlcrcph .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #trvhlcrcph .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #trvhlcrcph .gt_left { text-align: left; } #trvhlcrcph .gt_center { text-align: center; } #trvhlcrcph .gt_right { text-align: right; font-variant-numeric: tabular-nums; } #trvhlcrcph .gt_font_normal { font-weight: normal; } #trvhlcrcph .gt_font_bold { font-weight: bold; } #trvhlcrcph .gt_font_italic { font-style: italic; } #trvhlcrcph .gt_super { font-size: 65%; } #trvhlcrcph .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; } #trvhlcrcph .gt_asterisk { font-size: 100%; vertical-align: 0; } #trvhlcrcph .gt_indent_1 { text-indent: 5px; } #trvhlcrcph .gt_indent_2 { text-indent: 10px; } #trvhlcrcph .gt_indent_3 { text-indent: 15px; } #trvhlcrcph .gt_indent_4 { text-indent: 20px; } #trvhlcrcph .gt_indent_5 { text-indent: 25px; } #trvhlcrcph .katex-display { display: inline-flex !important; margin-bottom: 0.75em !important; } #trvhlcrcph div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { height: 0px !important; } variable 0 0.25 0.5 0.75 1 expend 3.7 4.9 5.8 6.4 9.8 sat 844.0 897.2 945.5 1032.0 1107.0 Esta variación es considerable para promedios del SAT: el percentil 75 es alrededor de 1050 puntos, mientras que el percentil 25 corresponde a alrededor de 800. Igualmente, hay diferencias considerables de gasto por alumno (miles de dólares) a lo largo de los estados. Ahora hacemos nuestro primer ejercico de comparación: ¿Cómo se ven las calificaciones para estados en distintos niveles de gasto? Podemos usar una gráfica de dispersión: library(ggrepel) ggplot(sat, aes(x = expend, y = sat, label = state)) + geom_point(colour = "red", size = 2) + geom_text_repel(colour = "gray50") + xlab("Gasto por alumno (miles de dólares)") + ylab("Calificación promedio en SAT") Estas comparaciones no son de alta calidad, solo estamos usando 2 variables —que son muy pocas— y no hay mucho que podamos decir en cuanto explicación. Sin duda nos hace falta una imagen más completa. Necesitaríamos entender la correlación que existe entre las demás características de nuestras unidades de estudio. Las unidades que estamos comparando pueden diferir fuertemente en otras propiedades importantes (o dimensiones), lo cual no permite interpretar la gráfica de manera sencilla. Una variable que tenemos es el porcentaje de alumnos de cada estado que toma el SAT. Podemos agregar como sigue: ggplot(sat, aes(x = expend, y = math, label=state, colour = frac)) + geom_point() + geom_text_repel() + xlab("Gasto por alumno (miles de dólares)") + ylab("Calificación promedio en SAT") Esto nos permite entender por qué nuestra comparación inicial es relativamente pobre. Los estados con mejores resultados promedio en el SAT son aquellos donde una fracción relativamente baja de los estudiantes toma el examen. La diferencia es considerable. En este punto podemos hacer varias cosas. Una primera idea es intentar comparar estados más similares en cuanto a la población de alumnos que asiste. Podríamos hacer grupos como sigue: set.seed(991) k_medias_sat <- kmeans(sat |> select(frac), centers = 4, nstart = 100, iter.max = 100) sat$clase <- k_medias_sat$cluster sat <- sat |> group_by(clase) |> mutate(clase_media = round(mean(frac))) |> ungroup() |> mutate(clase_media = factor(clase_media)) sat <- sat |> mutate(rank_p = rank(frac, ties= "first") / length(frac)) ggplot(sat, aes(x = rank_p, y = frac, label = state, colour = clase_media)) + geom_point(size = 2) Estos resultados indican que es más probable que buenos alumnos decidan hacer el SAT. Lo interesante es que esto ocurre de manera diferente en cada estado. Por ejemplo, en algunos estados era más común otro examen: el ACT. Si hacemos clusters de estados según el % de alumnos, empezamos a ver otra historia. Para esto, ajustemos rectas de mínimos cuadrados como referencia: Esto da una imagen muy diferente a la que originalmente planteamos. Nota que dependiendo de cómo categorizamos, esta gráfica puede variar (puedes intentar con más o menos grupos, por ejemplo). Tablas de conteos Consideremos los siguientes datos de tomadores de té (del paquete FactoMineR (Lê et al. 2008)): tea <- read_csv("data/tea.csv") # nombres y códigos te <- tea |> select(how, price, sugar) |> rename(presentacion = how, precio = price, azucar = sugar) |> mutate( presentacion = fct_recode(presentacion, suelto = "unpackaged", bolsas = "tea bag", mixto = "tea bag+unpackaged"), precio = fct_recode(precio, marca = "p_branded", variable = "p_variable", barato = "p_cheap", marca_propia = "p_private label", desconocido = "p_unknown", fino = "p_upscale"), azucar = fct_recode(azucar, sin_azúcar = "No.sugar", con_azúcar = "sugar")) sample_n(te, 10) ## # A tibble: 10 × 3 ## presentacion precio azucar ## <fct> <fct> <fct> ## 1 bolsas marca sin_azúcar ## 2 bolsas variable sin_azúcar ## 3 bolsas marca con_azúcar ## 4 bolsas fino con_azúcar ## 5 mixto variable con_azúcar ## 6 mixto fino con_azúcar ## 7 bolsas marca sin_azúcar ## 8 bolsas fino sin_azúcar ## 9 mixto variable con_azúcar ## 10 mixto variable sin_azúcar Nos interesa ver qué personas compran té suelto, y de qué tipo. Empezamos por ver las proporciones que compran té según su empaque (en bolsita o suelto): precio <- te |> count(precio) |> mutate(prop = round(100 * n / sum(n))) |> select(-n) tipo <- te |> count(presentacion) |> mutate(pct = round(100 * n / sum(n))) tipo |> gt() #wiljiluquk table { font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #wiljiluquk thead, #wiljiluquk tbody, #wiljiluquk tfoot, #wiljiluquk tr, #wiljiluquk td, #wiljiluquk th { border-style: none; } #wiljiluquk p { margin: 0; padding: 0; } #wiljiluquk .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; } #wiljiluquk .gt_caption { padding-top: 4px; padding-bottom: 4px; } #wiljiluquk .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; } #wiljiluquk .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; } #wiljiluquk .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #wiljiluquk .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #wiljiluquk .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #wiljiluquk .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; } #wiljiluquk .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; } #wiljiluquk .gt_column_spanner_outer:first-child { padding-left: 0; } #wiljiluquk .gt_column_spanner_outer:last-child { padding-right: 0; } #wiljiluquk .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; } #wiljiluquk .gt_spanner_row { border-bottom-style: hidden; } #wiljiluquk .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; } #wiljiluquk .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; } #wiljiluquk .gt_from_md > :first-child { margin-top: 0; } #wiljiluquk .gt_from_md > :last-child { margin-bottom: 0; } #wiljiluquk .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; } #wiljiluquk .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; } #wiljiluquk .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; } #wiljiluquk .gt_row_group_first td { border-top-width: 2px; } #wiljiluquk .gt_row_group_first th { border-top-width: 2px; } #wiljiluquk .gt_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #wiljiluquk .gt_first_summary_row { border-top-style: solid; border-top-color: #D3D3D3; } #wiljiluquk .gt_first_summary_row.thick { border-top-width: 2px; } #wiljiluquk .gt_last_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #wiljiluquk .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #wiljiluquk .gt_first_grand_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; } #wiljiluquk .gt_last_grand_summary_row_top { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; } #wiljiluquk .gt_striped { background-color: rgba(128, 128, 128, 0.05); } #wiljiluquk .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #wiljiluquk .gt_footnotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #wiljiluquk .gt_footnote { margin: 0px; font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #wiljiluquk .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #wiljiluquk .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #wiljiluquk .gt_left { text-align: left; } #wiljiluquk .gt_center { text-align: center; } #wiljiluquk .gt_right { text-align: right; font-variant-numeric: tabular-nums; } #wiljiluquk .gt_font_normal { font-weight: normal; } #wiljiluquk .gt_font_bold { font-weight: bold; } #wiljiluquk .gt_font_italic { font-style: italic; } #wiljiluquk .gt_super { font-size: 65%; } #wiljiluquk .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; } #wiljiluquk .gt_asterisk { font-size: 100%; vertical-align: 0; } #wiljiluquk .gt_indent_1 { text-indent: 5px; } #wiljiluquk .gt_indent_2 { text-indent: 10px; } #wiljiluquk .gt_indent_3 { text-indent: 15px; } #wiljiluquk .gt_indent_4 { text-indent: 20px; } #wiljiluquk .gt_indent_5 { text-indent: 25px; } #wiljiluquk .katex-display { display: inline-flex !important; margin-bottom: 0.75em !important; } #wiljiluquk div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { height: 0px !important; } presentacion n pct bolsas 170 57 mixto 94 31 suelto 36 12 La mayor parte de las personas toma té en bolsas. Sin embargo, el tipo de té (en términos de precio o marca) que compran es muy distinto dependiendo de la presentación: tipo <- tipo |> select(presentacion, prop_presentacion = pct) tabla_cruzada <- te |> count(presentacion, precio) |> # porcentajes por presentación group_by(presentacion) |> mutate(prop = round(100 * n / sum(n))) |> select(-n) tabla_cruzada |> pivot_wider(names_from = presentacion, values_from = prop, values_fill = list(prop = 0)) |> gt() #sxkvcchtyg table { font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #sxkvcchtyg thead, #sxkvcchtyg tbody, #sxkvcchtyg tfoot, #sxkvcchtyg tr, #sxkvcchtyg td, #sxkvcchtyg th { border-style: none; } #sxkvcchtyg p { margin: 0; padding: 0; } #sxkvcchtyg .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; } #sxkvcchtyg .gt_caption { padding-top: 4px; padding-bottom: 4px; } #sxkvcchtyg .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; } #sxkvcchtyg .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; } #sxkvcchtyg .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #sxkvcchtyg .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #sxkvcchtyg .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #sxkvcchtyg .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; } #sxkvcchtyg .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; } #sxkvcchtyg .gt_column_spanner_outer:first-child { padding-left: 0; } #sxkvcchtyg .gt_column_spanner_outer:last-child { padding-right: 0; } #sxkvcchtyg .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; } #sxkvcchtyg .gt_spanner_row { border-bottom-style: hidden; } #sxkvcchtyg .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; } #sxkvcchtyg .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; } #sxkvcchtyg .gt_from_md > :first-child { margin-top: 0; } #sxkvcchtyg .gt_from_md > :last-child { margin-bottom: 0; } #sxkvcchtyg .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; } #sxkvcchtyg .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; } #sxkvcchtyg .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; } #sxkvcchtyg .gt_row_group_first td { border-top-width: 2px; } #sxkvcchtyg .gt_row_group_first th { border-top-width: 2px; } #sxkvcchtyg .gt_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #sxkvcchtyg .gt_first_summary_row { border-top-style: solid; border-top-color: #D3D3D3; } #sxkvcchtyg .gt_first_summary_row.thick { border-top-width: 2px; } #sxkvcchtyg .gt_last_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #sxkvcchtyg .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #sxkvcchtyg .gt_first_grand_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; } #sxkvcchtyg .gt_last_grand_summary_row_top { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; } #sxkvcchtyg .gt_striped { background-color: rgba(128, 128, 128, 0.05); } #sxkvcchtyg .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #sxkvcchtyg .gt_footnotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #sxkvcchtyg .gt_footnote { margin: 0px; font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #sxkvcchtyg .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #sxkvcchtyg .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #sxkvcchtyg .gt_left { text-align: left; } #sxkvcchtyg .gt_center { text-align: center; } #sxkvcchtyg .gt_right { text-align: right; font-variant-numeric: tabular-nums; } #sxkvcchtyg .gt_font_normal { font-weight: normal; } #sxkvcchtyg .gt_font_bold { font-weight: bold; } #sxkvcchtyg .gt_font_italic { font-style: italic; } #sxkvcchtyg .gt_super { font-size: 65%; } #sxkvcchtyg .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; } #sxkvcchtyg .gt_asterisk { font-size: 100%; vertical-align: 0; } #sxkvcchtyg .gt_indent_1 { text-indent: 5px; } #sxkvcchtyg .gt_indent_2 { text-indent: 10px; } #sxkvcchtyg .gt_indent_3 { text-indent: 15px; } #sxkvcchtyg .gt_indent_4 { text-indent: 20px; } #sxkvcchtyg .gt_indent_5 { text-indent: 25px; } #sxkvcchtyg .katex-display { display: inline-flex !important; margin-bottom: 0.75em !important; } #sxkvcchtyg div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { height: 0px !important; } precio bolsas mixto suelto marca 41 21 14 barato 3 1 3 marca_propia 9 4 3 desconocido 6 1 0 fino 8 20 56 variable 32 52 25 Estos datos podemos examinarlos un rato y llegar a conclusiones, pero esta tabla no necesariamente es la mejor manera de mostrar patrones en los datos. Tampoco son muy útiles gráficas como la siguiente: ggplot(tabla_cruzada |> ungroup() |> mutate(price = fct_reorder(precio, prop)), aes(x = precio, y = prop, group = presentacion, colour = presentacion)) + geom_point() + coord_flip() + geom_line() En lugar de eso, calcularemos perfiles columna. Esto es, comparamos cada una de las columnas con la columna marginal (en la tabla de tipo de estilo de té): num_grupos <- n_distinct(te |> select(presentacion)) tabla <- te |> count(presentacion, precio) |> group_by(presentacion) |> mutate(prop_precio = (100 * n / sum(n))) |> group_by(precio) |> mutate(prom_prop = sum(prop_precio)/num_grupos) |> mutate(perfil = 100 * (prop_precio / prom_prop - 1)) tabla ## # A tibble: 17 × 6 ## # Groups: precio [6] ## presentacion precio n prop_precio prom_prop perfil ## <fct> <fct> <int> <dbl> <dbl> <dbl> ## 1 bolsas marca 70 41.2 25.4 61.8 ## 2 bolsas barato 5 2.94 2.26 30.1 ## 3 bolsas marca_propia 16 9.41 5.48 71.7 ## 4 bolsas desconocido 11 6.47 2.51 158. ## 5 bolsas fino 14 8.24 28.0 -70.6 ## 6 bolsas variable 54 31.8 36.3 -12.5 ## 7 mixto marca 20 21.3 25.4 -16.4 ## 8 mixto barato 1 1.06 2.26 -52.9 ## 9 mixto marca_propia 4 4.26 5.48 -22.4 ## 10 mixto desconocido 1 1.06 2.51 -57.6 ## 11 mixto fino 19 20.2 28.0 -27.8 ## 12 mixto variable 49 52.1 36.3 43.6 ## 13 suelto marca 5 13.9 25.4 -45.4 ## 14 suelto barato 1 2.78 2.26 22.9 ## 15 suelto marca_propia 1 2.78 5.48 -49.3 ## 16 suelto fino 20 55.6 28.0 98.4 ## 17 suelto variable 9 25 36.3 -31.1 tabla_perfil <- tabla |> select(presentacion, precio, perfil, pct = prom_prop) |> pivot_wider(names_from = presentacion, values_from = perfil, values_fill = list(perfil = -100.0)) if_profile <- function(x){ any(x < 0) & any(x > 0) } marcar <- marcar_tabla_fun(25, "red", "black") tab_out <- tabla_perfil |> arrange(desc(bolsas)) |> select(-pct, everything()) |> mutate(across(where(is.numeric), \\(x) round(x, 0))) |> mutate(across(where(if_profile), \\(x) marcar(x))) |> knitr::kable(format_table_salida(), escape = FALSE, digits = 0, booktabs = T) |> kableExtra::kable_styling(latex_options = c("striped", "scale_down"), bootstrap_options = c( "hover", "condensed"), full_width = FALSE) tab_out precio bolsas mixto suelto pct desconocido 158 -58 -100 3 marca_propia 72 -22 -49 5 marca 62 -16 -45 25 barato 30 -53 23 2 variable -12 44 -31 36 fino -71 -28 98 28 Leemos esta tabla como sigue: por ejemplo, los compradores de té suelto compran té fino a una tasa casi el doble (98%) que el promedio. También podemos graficar como: tabla_graf <- tabla_perfil |> ungroup() |> mutate(precio = fct_reorder(precio, bolsas)) |> select(-pct) |> pivot_longer(cols = -precio, names_to = "presentacion", values_to = "perfil") g_perfil <- ggplot(tabla_graf, aes(x = precio, xend = precio, y = perfil, yend = 0, group = presentacion)) + geom_point() + geom_segment() + facet_wrap(~presentacion) + geom_hline(yintercept = 0 , colour = "gray")+ coord_flip() g_perfil Observación: hay dos maneras de construir la columna promedio: tomando los porcentajes sobre todos los datos, o promediando los porcentajes de las columnas. Si los grupos de las columnas están desbalanceados, estos promedios son diferentes. Cuando usamos porcentajes sobre la población, perfiles columna y renglón dan el mismo resultado Sin embargo, cuando hay un grupo considerablemente más grande que otros, las comparaciones se vuelven vs este grupo particular. No siempre queremos hacer esto. Interpretación En el último ejemplo de tomadores de té utilizamos una muestra de personas, no toda la población de tomadores de té. Eso quiere decir que tenemos cierta incertidumbre de cómo se generalizan o no los resultados que obtuvimos en nuestro análisis a la población general. Nuestra respuesta depende de cómo se extrajo la muestra que estamos considerando. Si el mecanismo de extracción incluye algún proceso probabilístico, entonces es posible en principio entender qué tan bien generalizan los resultados de nuestro análisis a la población general, y entender esto depende de entender qué tanta variación hay de muestra a muestra, de todas las posibles muestras que pudimos haber extraido. En las siguientes secciones discutiremos estos aspectos, en los cuales pasamos del trabajo de “detective” al trabajo de “juez” en nuestro trabajo analítico. Suavizamiento loess Las gráficas de dispersión son la herramienta básica para describir la relación entre dos variables cuantitativas, y como vimos en ejemplo anteriores, muchas veces podemos apreciar mejor la relación entre ellas si agregamos una curva loess. Veamos un ejemplo, los siguientes datos muestran los premios ofrecidos y las ventas totales de una lotería a lo largo de 53 sorteos (las unidades son cantidades de dinero indexadas). Graficamos en escalas logarítmicas y agregamos una curva loess. # cargamos los datos load(here::here("data", "ventas_sorteo.Rdata")) ggplot(ventas.sorteo, aes(x = premio, y = ventas.tot.1)) + geom_point() + geom_smooth(method = "loess", span = 0.6, method.args = list(degree = 1), se = FALSE) + scale_x_log10(breaks = c(20000, 40000, 80000)) + scale_y_log10(breaks = c(10000, 15000, 22000, 33000)) El patrón no era difícil de ver en los datos originales, sin embargo, la curva lo hace más claro, el logaritmo de las ventas tiene una relación no lineal con el logaritmo del premio: para premios no muy grandes no parece haber gran diferencia, pero cuando los premios empiezan a crecer por encima de 20,000, las ventas crecen más rápidamente que para premios menores. Este efecto se conoce como bola de nieve, y es frecuente en este tipo de loterías. Antes de adentrarnos a los modelos loess comenzamos explicando cómo se ajustan familias paramétricas de curvas a conjuntos de datos dados. El modelo de regresion lineal ajusta una recta a un conjunto de datos. Por ejemplo, consideremos la familia \\[f_{a,b}(x) = a x + b,\\] para un conjunto de datos bivariados \\(\\{ (x_1, y_1), \\ldots, (x_N, y_N)\\}\\). Buscamos encontrar \\(a\\) y \\(b\\) tales que \\(f_{a,b}\\) de un ajuste óptimo a los datos. Para esto, se minimiza la suma de errores cuadráticos \\[\\frac1N \\sum_{n = 1}^N ( y_n - a x_n - b)^2.\\] En este caso, las constantes \\(a\\) y \\(b\\) se pueden encontrar diferenciando la función de mínimos cuadrados. Nótese que podemos repetir el argumento con otras familias de funciones (por ejemplo cuadráticas). ggplot(ventas.sorteo, aes(x = premio, y = ventas.tot.1)) + geom_point() + geom_smooth(method = "lm", se = FALSE) + scale_x_log10(breaks = c(20000, 40000, 80000)) + scale_y_log10(breaks = c(10000, 15000, 22000, 33000)) Si observamos la gráfica notamos que este modelo lineal (en los logaritmos) no resumen adecuadamente estos datos. Podríamos experimentar con otras familias (por ejemplo, una cuadrática o cúbica, potencias, exponenciales, etc.); sin embargo, en la etapa exploratoria es mejor tomar una ruta de ajuste más flexible y robusta. Regresión local nos provee de un método con estas características: Curvas loess (regresión local): Una manera de mejorar la flexibilidad de los modelos lineales es considerar rectas de manera local. Es decir, en cada \\(x\\) posible consideramos cuál es la recta que mejor ajusta a los datos, considerando solamente valores de \\(x_n\\) que están cercanos a \\(x\\). La siguiente gráfica muestra qué recta se ajusta alrededor de cada punto, y cómo queda el suavizador completo, con distintos valores de suavizamiento. El tono de los puntos indican en cada paso que ventana de datos es considerada: Escogiendo de los parámetros. El parámetro de suavizamiento se encuentra por ensayo y error. La idea general es que debemos encontrar una curva que explique patrones importantes en los datos (que ajuste los datos) pero que no muestre variaciones a escalas más chicas difíciles de explicar (que pueden ser el resultado de influencias de otras variables, variación muestral, ruido o errores de redondeo, por ejemplo). En el proceso de prueba y error iteramos el ajuste y en cada paso hacemos análisis de residuales, con el fin de seleccionar un suavizamiento adecuado. En lugar de usar ajustes locales lineales, podemos usar ajustes locales cuadráticos que nos permiten capturar formas locales cuadráticas sin tener que suavizar demasiado poco: Opcional: cálculo del suavizador La idea es producir ajustes locales de rectas o funciones lineales o cuadráticas. Consideremos especificar dos parámetros: Parámetro de suavizamiento \\(\\alpha\\): toma valores en \\((0,1)\\), cuando \\(\\alpha\\) es más grande, la curva ajustada es más suave. Grado de los polinomios locales que ajustamos \\(\\lambda\\): generalmente se toma \\(\\lambda=1,2\\). Entonces, supongamos que los datos están dados por \\((x_1,y_1), \\ldots, (x_N, y_N)\\), y sean \\(\\alpha\\) un parámetro de suavizamiento fijo, y \\(\\lambda=1\\). Denotamos como \\(\\hat{g}(x)\\) la curva loess ajustada, y como \\(w_n(x)\\) a una función de peso (que depende de x) para la observación \\((x_n, y_n)\\). Para poder calcular \\(w_n(x)\\) debemos comenzar calculando \\(q=\\lfloor{N\\alpha}\\rfloor\\) que suponemos mayor que uno, esta \\(q\\) es el número de puntos que se utilizan en cada ajuste local. Ahora definimos la función tricubo: \\[\\begin{align} T(u)=\\begin{cases} (1-|u|^3)^3, & \\text{para $|u| < 1$}.\\\\ 0, & \\text{en otro caso}. \\end{cases} \\end{align}\\] entonces, para el punto \\(x\\) definimos el peso correspondiente al dato \\((x_n,y_n)\\), denotado por \\(w_n(x)\\) como: \\[w_n(x)=T\\bigg(\\frac{|x-x_n|}{d_q(x)}\\bigg)\\] donde \\(d_q(x)\\) es el valor de la \\(q\\)-ésima distancia más chica (la más grande entre las \\(q\\) más chicas) entre los valores \\(|x-x_j|\\), \\(j=1,\\ldots,N\\). De esta forma, las observaciones \\(x_n\\) reciben más peso cuanto más cerca estén de \\(x\\). En palabras, de \\(x_1,...,x_N\\) tomamos los \\(q\\) datos más cercanos a \\(x\\), que denotamos \\(x_{i_1}(x) \\leq x_{i_2}(x) \\leq \\cdots \\leq x_{i_q}(x)\\). Los re-escalamos a \\([0,1]\\) haciendo corresponder \\(x\\) a \\(0\\) y el punto más alejado de \\(x\\) (que es \\(x_{i_q}\\)) a 1. Aplicamos el tricubo (gráfica de abajo), para encontrar los pesos de cada punto. Los puntos que están a una distancia mayor a \\(d_q(x)\\) reciben un peso de cero, y los más cercanos un peso que depende de que tan cercanos están a \\(x\\). Nótese que \\(x\\) es el punto ancla en dónde estamos ajustando la regresión local. tricubo <- function(x) { ifelse(abs(x) < 1, (1 - abs(x) ^ 3) ^ 3, 0) } curve(tricubo, from = -1.5, to = 1.5) Finalmente, para cada valor de \\(x_k\\) que está en el conjunto de datos \\(\\{x_1,...,x_n\\}\\), ajustamos una recta de mínimos cuadrados ponderados por los pesos \\(w_n(x)\\), es decir, minimizamos (en el caso lineal): \\[\\sum_{i=1}^nw_n(x_k)(y_i-ax_n-b)^2.\\] Observaciones: Cualquier función (continua y quizás diferenciable) con la forma de flan del tricubo que se desvanece fuera de \\((-1,1),\\) es creciente en \\((-1,0)\\) y decreciente en \\((0, 1)\\) es un buen candidato para usarse en lugar del tricubo. La razón por la que escogemos precisamente esta forma algebráica no tiene que ver con el análisis exploratorio, sino con las ventajas teóricas adicionales que tiene en la inferencia. El caso \\(\\lambda=2\\) es similar. La única diferencia es en el paso de ajuste, donde usamos funciones cuadráticas, y obtendríamos entonces tres familias de parámetros \\(a(x_k), b_1(x_k), b_2(x_k),\\) para cada \\(k \\in \\{1, \\ldots, N\\}\\). Caso de estudio: nacimientos en México Podemos usar el suavizamiento loess para entender y describir el comportamiento de series de tiempo, en las cuáles intentamos entender la dependencia de una serie de mediciones indexadas por el tiempo. Típicamente es necesario utilizar distintas componentes para describir exitosamente una serie de tiempo, y para esto usamos distintos tipos de suavizamientos. Veremos que distintas componentes varían en distintas escalas de tiempo (unas muy lentas, como la tendencia, otras más rapidamente, como variación quincenal, etc.). Este caso de estudio esta basado en un análisis propuesto por A. Vehtari y A. Gelman, junto con un análisis de serie de tiempo de William S. Cleveland (1993). En nuestro caso, usaremos los datos de nacimientos registrados por día en México desde 1999. Los usaremos para contestar las preguntas: ¿cuáles son los cumpleaños más frecuentes? y ¿en qué mes del año hay más nacimientos? Podríamos utilizar una gráfica popular (ver por ejemplo esta visualización) como: Sin embargo, ¿cómo criticarías este análisis desde el punto de vista de los tres primeros principios del diseño analítico? ¿Las comparaciones son útiles? ¿Hay aspectos multivariados? ¿Qué tan bien explica o sugiere estructura, mecanismos o causalidad? Datos de natalidad para México library(lubridate) library(ggthemes) theme_set(theme_minimal(base_size = 14)) natalidad <- read_rds("./data/nacimientos/natalidad.rds") |> mutate(dia_semana = weekdays(fecha)) |> mutate(dia_año = yday(fecha)) |> mutate(año = year(fecha)) |> mutate(mes = month(fecha)) |> ungroup() |> mutate(dia_semana = recode(dia_semana, Monday = "Lunes", Tuesday = "Martes", Wednesday = "Miércoles", Thursday = "Jueves", Friday = "Viernes", Saturday = "Sábado", Sunday = "Domingo")) |> #necesario pues el LOCALE puede cambiar mutate(dia_semana = recode(dia_semana, lunes = "Lunes", martes = "Martes", miércoles = "Miércoles", jueves = "Jueves", viernes = "Viernes", sábado = "Sábado", domingo = "Domingo")) |> mutate(dia_semana = fct_relevel(dia_semana, c("Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"))) Consideremos los datos agregados del número de nacimientos (registrados) por día desde 1999 hasta 2016. Un primer intento podría ser hacer una gráfica de la serie de tiempo. Sin embargo, vemos que no es muy útil: Hay varias características que notamos. Primero, parece haber una tendencia ligeramente decreciente del número de nacimientos a lo largo de los años. Segundo, la gráfica sugiere un patrón anual. Y por último, encontramos que hay dispersión producida por los días de la semana. Sólo estas características hacen que la comparación entre días sea difícil de realizar. Supongamos que comparamos el número de nacimientos de dos miércoles dados. Esa comparación será diferente dependiendo: del año donde ocurrieron, el mes donde ocurrieron, si semana santa ocurrió en algunos de los miércoles, y así sucesivamente. Como en nuestros ejemplos anteriores, la idea del siguiente análisis es aislar las componentes que observamos en la serie de tiempo: extraemos componentes ajustadas, y luego examinamos los residuales. En este caso particular, asumiremos una descomposición aditiva de la serie de tiempo (William S. Cleveland 1993). En el estudio de series de tiempo una estructura común es considerar el efecto de diversos factores como tendencia, estacionalidad, ciclicidad e irregularidades de manera aditiva. Esto es, consideramos la descomposición \\[\\begin{align} y(t) = f_{t}(t) + f_{e}(t) + f_{c}(t) + \\varepsilon. \\end{align}\\] Una estrategia de ajuste, como veremos más adelante, es proceder de manera modular. Es decir, se ajustan los componentes de manera secuencial considerando los residuales de los anteriores. Tendencia Comenzamos por extraer la tendencia, haciendo promedios loess (William S. Cleveland 1979) con vecindades relativamente grandes. Quizá preferiríamos suavizar menos para capturar más variación lenta, pero si hacemos esto en este punto empezamos a absorber parte de la componente anual: mod_1 <- loess(n ~ as.numeric(fecha), data = natalidad, span = 0.2, degree = 1) datos_dia <- natalidad |> mutate(ajuste_1 = fitted(mod_1)) |> mutate(res_1 = n - ajuste_1) Notemos que a principios de 2000 el suavizador está en niveles de alrededor de 7000 nacimientos diarios, hacia 2015 ese número es más cercano a unos 6000. Componente anual Al obtener la tendencia podemos aislar el efecto a largo plazo y proceder a realizar mejores comparaciones (por ejemplo, comparar un día de 2000 y de 2015 tendria más sentido). Ahora, ajustamos los residuales del suavizado anterior, pero con menos suavizamiento. Así evitamos capturar tendencia: mod_anual <- loess(res_1 ~ as.numeric(fecha), data = datos_dia, degree = 2, span = 0.005) datos_dia <- datos_dia |> mutate(ajuste_2 = fitted(mod_anual)) |> mutate(res_2 = res_1 - ajuste_2) Día de la semana Hasta ahora, hemos aislado los efectos por plazos largos de tiempo (tendencia) y hemos incorporado las variaciones estacionales (componente anual) de nuestra serie de tiempo. Ahora, veremos cómo capturar el efecto por día de la semana. En este caso, podemos hacer suavizamiento loess para cada serie de manera independiente datos_dia <- datos_dia |> group_by(dia_semana) |> nest() |> mutate(ajuste_mod = map(data, ~ loess(res_2 ~ as.numeric(fecha), data = .x, span = 0.1, degree = 1))) |> mutate(ajuste_3 = map(ajuste_mod, fitted)) |> select(-ajuste_mod) |> unnest(cols = c(data, ajuste_3)) |> mutate(res_3 = res_2 - ajuste_3) |> ungroup() Residuales Por último, examinamos los residuales finales quitando los efectos ajustados: ## `geom_smooth()` using formula = 'y ~ x' Observación: nótese que la distribución de estos residuales presenta irregularidades interesantes. La distribución es de colas largas, y no se debe a unos cuantos datos atípicos. Esto generalmente es indicación que hay factores importantes que hay que examinar mas a detalle en los residuales: Reestimación Cuando hacemos este proceso secuencial de llevar el ajuste a los residual, a veces conviene iterarlo. La razón es que en una segunda o tercera pasada podemos hacer mejores estimaciones de cada componente, y es posible suavizar menos sin capturar componentes de más alta frecuencia. Así que podemos regresar a la serie original para hacer mejores estimaciones, más suavizadas: # Quitamos componente anual y efecto de día de la semana datos_dia <- datos_dia |> mutate(n_1 = n - ajuste_2 - ajuste_3) # Reajustamos mod_1 <- loess(n_1 ~ as.numeric(fecha), data = datos_dia, span = 0.02, degree = 2, family = "symmetric") Y ahora repetimos con la componente de día de la semana: Análisis de componentes Ahora comparamos las componentes estimadas y los residuales en una misma gráfica. Por definición, la suma de todas estas componentes da los datos originales. Este último paso nos permite diversas comparaciones que explican la variación que vimos en los datos. Una gran parte de los residuales está entre \\(\\pm 250\\) nacimientos por día. Sin embargo, vemos que las colas tienen una dispersión mucho mayor: quantile(datos_dia$res_6, c(00, .01,0.05, 0.10, 0.90, 0.95, 0.99, 1)) |> round() ## 0% 1% 5% 10% 90% 95% 99% 100% ## -2238 -1134 -315 -202 188 268 516 2521 ¿A qué se deben estas colas tan largas? Viernes 13? Podemos empezar con una curosidad. Los días Viernes o Martes 13, ¿nacen menos niños? Nótese que fue útil agregar el indicador de Semana santa por el Viernes 13 de Semana Santa que se ve como un atípico en el panel de los viernes 13. Residuales: antes y después de 2006 Veamos primero una agregación sobre los años de los residuales. Lo primero es observar un cambio que sucedió repentinamente en 2006: La razón es un cambio en la ley acerca de cuándo pueden entrar los niños a la primaria. Antes era por edad y había poco margen. Ese exceso de nacimientos son reportes falsos para que los niños no tuvieran que esperar un año completo por haber nacido unos cuantos días después de la fecha límite. Otras características que debemos investigar: Efectos de Año Nuevo, Navidad, Septiembre 16 y otros días feriados como Febrero 14. Semana santa: como la fecha cambia, vemos que los residuales negativos tienden a ocurrir dispersos alrededor del día 100 del año. Otros días especiales: más de residuales Ahora promediamos residuales (es posible agregar barras para indicar dispersión a lo largo de los años) para cada día del año. Podemos identificar ahora los residuales más grandes: se deben, por ejemplo, a días feriados, con consecuencias adicionales que tienen en días ajuntos (excesos de nacimientos): Semana santa Para Semana Santa tenemos que hacer unos cálculos. Si alineamos los datos por días antes de Domingo de Pascua, obtenemos un patrón de caída fuerte de nacimientos el Viernes de Semana Santa, y la característica forma de “valle con hombros” en días anteriores y posteriores estos Viernes. ¿Por qué ocurre este patrón? Nótese un defecto de nuestro modelo: el patrón de “hombros” alrededor del Viernes Santo no es suficientemente fuerte para equilibrar los nacimientos faltantes. ¿Cómo podríamos mejorar nuestra descomposición? Referencias "],["tipos-de-estudio-y-experimentos.html", "Sección 2 Tipos de estudio y experimentos Muestreo aleatorio Pero si no podemos hacer muestreo aleatorio? El estimador estándar Experimentos tradicionales Bloqueo Variables desconocidas Aleatorizando el tratamiento Selección de unidades y tratamiento Asignación natural del tratamiento", " Sección 2 Tipos de estudio y experimentos Motivación Pregunta de entrevista de Google (Chihara and Hesterberg 2018) Imagina que eres consultor y te preguntan lo siguiente (ver siguiente figura): Estoy haciendo una comparación de antes y después donde la hipótesis alternativa es pre.media.error > post.media.error. La distribución de ambas muestras es sesgada a la derecha. ¿Qué prueba me recomiendas para ésta situación? Figure 2.1: Error CPR, gráfica de densidad. Far better an approximate answer to the right question, which is often vague, than an exact answer to the wrong question, which can always be made precise. — John Tukey La siguiente imagen Roger Peng representa una situación común a la que se enfrenta el analista de datos, y se desarrolló en el contexto de preguntas vagas. En el esquema hay tres caminos: uno es uno ideal que pocas veces sucede, otro produce respuestas poco útiles pero es fácil, y otro es tortuoso pero que caracteriza el mejor trabajo de análisis de datos: Figure 2.2: Adaptado de R. Peng: Tukey, design thinking and better questions. Ejemplos: Alguien nos pregunta cuáles son las tiendas que mas venden de una cadena. Podríamos consultar bases de datos, hacer extracciones, definir periodos, etc. y reportar el promedio de ventas en el último mes, esta respuesta probablemente es poco útil. Nos damos cuenta, por ejemplo, porque la peor tienda es una que abrió hace relativamente poco, y la mejor es una de las tiendas más grandes que está en una zona de tráfico de alto costo. Una pregunta más interesante es, ¿qué equipos de ventas tienen mejor desempeño? ¿Cuánto aporta tener una cafetería dentro de la tienda en términos de ventas?, etc. Proceso Generador de Datos Entre las preguntas que se debe hacer el analista de datos una fundamental es entender el proceso generador de datos, pues esto determinará que otras preguntas son relevantes, y que análisis son adecuados, tanto en términos prácticos como estadísticos. La inferencia estadística busca hacer afirmaciones, cuantificadas de manera probabilista, acerca de datos que no tenemos, usando regularidades y conocimiento de datos que sí tenemos disponibles y métodos cuantitativos. Para hacer afirmaciones inferenciales eficientes y bien calibradas (con garantías estadísticas de calibración) a preguntas donde queremos generalizar de muestra a población, se requiere conocer con precisión el proceso que genera los datos muestrales. Esto incluye saber con detalle cómo se seleccionaron los datos a partir de los que se quiere hacer inferencia. En este caso, eficiente quiere decir que aprovechamos toda la información que está en los datos observados de manera que nuestros rangos de incertidumbre son lo más chico posibles (además de estar correctamente calibrados). Por su parte, probabilísticamente bien calibrados se refiere a que, lo que decimos que puede ocurrir con 10% de probabilidad ocurre efectivamente 1 de cada 10 veces, si decimos 20% entonces ocurre 2 de 20, etc. Veremos que para muestras dadas naturalmente, a veces es muy difiícil entender a fondo el proceso que generó la muestra y por tanto no tenemos las garantías de eficiencia y calibración. Ejemplo: Prevalencia de anemia Supongamos que nos interesa conocer el porcentaje de menores en edad escolar, (entre 6 y 15 años), con anemia en México. La fuente de datos disponible corresponde a registros del IMSS de hospitalizaciones de menores, ya sea por anemia o por otra causa (infecciones gastrointestinales, apendicitis, tratamiento de leucemia, …), se registró si el menor tenía anemia. En nuestra muestra el 47% de los niños tiene anemia. head(paciente) #> # A tibble: 6 × 4 #> edad padecimiento sexo anemia #> <int> <chr> <chr> <int> #> 1 8 picadura alacrán mujer 0 #> 2 10 infección intestinal hombre 1 #> 3 7 mordedura de perro hombre 1 #> 4 8 asma hombre 1 #> 5 13 infección intestinal mujer 0 #> 6 7 picadura alacrán hombre 0 ¿Qué nos dice esta cantidad acerca de la anemia en la población? ¿Podemos hacer inferencia estadística? ¿Cómo calculamos intervalos de confianza? # Si calculo el error estándar de la p estimada como sigue, es correcto? p <- mean(paciente$anemia) sqrt(p * (1 - p) / 5000) #> [1] 0.007060751 Muestreo aleatorio En la situación ideal diseñaríamos una muestra aleatoria de menores de edad, por ejemplo, utilizando el registro en educación primaria de la SEP, y mediríamos la prevalencia de anemia en la muestra, usaríamos esta muestra para estimar la prevalencia en la población y tendríamos además las herramientas para medir la incertidumbre de nuestra estimación (reportar intervalos, o errores estándar). El elemento clave, es la aleatorización en la selección de la muestra, la idea es distribuir los efecros desconcidos o no controlables que pueden introducir sesgos o variabilidad no conocida en los resultados. Pero si no podemos hacer muestreo aleatorio? En el caso de prevalencia de anemia, discutiendo con médicos e investigadores nos informan que la anemia se presenta en tasas más altas en niños más chicos. paciente |> count(edad) |> mutate(prop = round(100 * n / sum(n))) #> # A tibble: 10 × 3 #> edad n prop #> <int> <int> <dbl> #> 1 6 1001 20 #> 2 7 931 19 #> 3 8 980 20 #> 4 9 445 9 #> 5 10 484 10 #> 6 11 489 10 #> 7 12 246 5 #> 8 13 239 5 #> 9 14 90 2 #> 10 15 95 2 Y consultando con las proyecciones de población notamos que los niños chicos están sobrerepresentados en la muestra. Lo que nos hace considerar que debemos buscar una manera de ponderar nuestras observaciones para que reflejen a la población. Más aún, investigamos que algunas enfermedades están asociadas a mayor prevalencia de anemia: paciente |> count(padecimiento) |> arrange(-n) #> # A tibble: 7 × 2 #> padecimiento n #> <chr> <int> #> 1 infección respiratoria 745 #> 2 mordedura de perro 723 #> 3 úlcera 723 #> 4 asma 713 #> 5 apendcitis 704 #> 6 picadura alacrán 701 #> 7 infección intestinal 691 Utilizamos esta información para modelar y corregir nuestra estimación original. Por ejemplo con modelos de regresión. Sin embargo, debemos preguntarnos: ¿Hay más variables qué nos falta considerar? Ejemplo: Policías y tráfico Supongamos que nos preguntan en cuánto reduce un policía el tráfico en un crucero grande de la ciudad. La cultura popular ha establecido que los policías en cruceros hacen más tráfico porque no saben mover los semáforos. Nosotros decidimos buscar unos datos para entender esto. Escogemos entonces un grupo de cruceros problemáticos, registramos el tráfico cuando visitamos, y si había un policía o no. Después de este esfuerzo, obtenemos los siguientes datos: #> # A tibble: 10 × 2 #> # Groups: policia [2] #> policia tiempo_espera_min #> <int> <dbl> #> 1 0 2.27 #> 2 0 2.65 #> 3 0 3.4 #> 4 0 0.39 #> 5 0 1.1 #> 6 1 10.8 #> 7 1 4.67 #> 8 1 7.77 #> 9 1 6.3 #> 10 1 6.99 Lo que sabemos ahora es que la presencia de un policía es indicador de tráfico alto. El análisis prosiguiría calculando medias y medidas de error (escogimos una muestra aleatoria): Si somos ingenuos, entonces podríamos concluir que los policías efectivamente empeoran la situación cuando manipulan los semáforos, y confirmaríamos la sabiduría popular. Para juzgar este argumento desde el punto de vista causal, nos preguntamos primero: ¿Cuáles son los contrafactuales (los contrafactuales explican que pasaría si hubiéramos hecho otra cosa que la que efectivamente hicimos) de las observaciones? Efectos causales y el esquema de resultados potenciales Consideramos un tratamiento binario: Se manda policía o no se manda policía. Un resultado potencial es aquél que se observaría bajo un tratamiento particular. En cada semáforo, a una hora dada, hay dos resultados potenciales, uno por cada valor del tratamiento: \\(y_1:\\) tiempo de espera si se envía policía. \\(y_0:\\) tiempo de espera si no se envía policía. Para cada semáforo, en el momento de registro, uno observa únicamente uno de los dos resultados potenciales. El resultado no observado se conoce como resultado contrafactual. El estimador estándar A la comparación anterior - la diferencia de medias de tratados y no tratados - le llamamos usualmente el estimador estándar del efecto causal. Muchas veces este es un estimador malo del efecto causal. En nuestro ejemplo, para llegar a la conclusión errónea que confirma la sabiduría popular, hicimos un supuesto importante: En nuestra muestra, los casos con policía actúan como contrafactuales de los casos sin policía. Asi que asumimos que los casos con policía y sin policía son similares, excepto por la existencia o no de policía. En nuestro ejemplo, quizá un analista más astuto nota que tienen categorías históricas de qué tan complicado es cada crucero. Junta a sus datos, y obtiene: #> # A tibble: 10 × 3 #> # Groups: policia [2] #> policia tiempo_espera_min categoria #> <int> <dbl> <fct> #> 1 0 2.27 Fluido #> 2 0 2.65 Fluido #> 3 0 3.4 Típico #> 4 0 0.39 Fluido #> 5 0 1.1 Fluido #> 6 1 10.8 Complicado #> 7 1 4.67 Típico #> 8 1 7.77 Complicado #> 9 1 6.3 Complicado #> 10 1 6.99 Típico El analista argumenta entonces que los policías se enviaron principalmente a cruceros que se consideran Complicados según datos históricos. Esto resta credibilidad a la comparación que hicimos inicialmente: La comparación del estimador estándar no es de peras con peras: estamos comparando qué efecto tienen los policías en cruceros difíciles, con cruceros no difíciles donde no hay policía. La razón de esto es que el proceso generador de los datos incluye el hecho de que no se envían policías a lugares donde no hay tráfico. ¿Cómo producir contrafactuales para hacer la comparación correcta? Experimentos tradicionales Idealmente, quisiéramos observar un mismo crucero en las dos condiciones: con y sin policías. Esto no es posible. En un experimento “tradicional”, como nos lo explicaron en la escuela, nos aproximamos a esto preparando dos condiciones idénticas, y luego alteramos cada una de ellas con nuestra intervención. Si el experimento está bien hecho, esto nos da observaciones en pares, y cada quien tiene su contrafactual. La idea del experimiento tradicional es controlar todos los factores que intervienen en los resultados, y sólo mover el tratamiento para producir los contrafactuales. Más en general, esta estrategia consiste en hacer bloques de condiciones, donde las condiciones son prácticamente idénticas dentro de cada bloque. Comparamos entonces unidades tratadas y no tratadas dentro de cada bloque. Por ejemplo, si queremos saber si el tiempo de caída libre es diferente para un objeto más pesado que otro, prepararíamos dos pesos con el mismo tamaño pero de peso distinto. Soltaríamos los dos al mismo tiempo y compararíamos el tiempo de caída de cada uno. En nuestro caso, como es usual en problemas de negocio o sociales, hacer esto es considerablemente más difícil. No podemos “preparar” cruceros con condiciones idénticas. Sin embargo, podríamos intentar \\(bloquear\\) los cruceros según información que tenemos acerca de ellos, para hacer más comparaciones de peras con peras. Bloqueo Podemos acercanos en lo posible a este ideal de experimentación usando información existente. En lugar de hacer comparaciones directas entre unidades que recibieron el tratamiento y las que no (que pueden ser diferentes en otros aspectos, como vimos arriba), podemos refinar nuestras comparaciones bloquéandolas con variables conocidas. En el ejemplo de los policías, podemos hacer lo siguiente: dentro de cada categoría de cruceros (fluido, típico o complicado), tomaremos una muestra de cruceros, algunos con policía y otros sin. Haremos comparaciones dentro de cada categoría. Obtenemos una muestra con estas características (6 casos en cada categoría de crucero, 3 con policía y 3 sin policía): categoria policia n Fluido 0 3 Fluido 1 3 Típico 0 3 Típico 1 3 Complicado 0 3 Complicado 1 3 Y ahora hacemos comparaciones dentro de cada bloque creado por categoría: #> # A tibble: 3 × 3 #> # Groups: categoria [3] #> categoria `policia =0` `policia =1` #> <fct> <dbl> <dbl> #> 1 Fluido 2.1 0.8 #> 2 Típico 5.6 4.2 #> 3 Complicado 10.4 8.6 Y empezamos a ver otra imagen en estos datos: comparando tipos e cruceros similares, los que tienen policía tienen tiempos de espera ligeramente más cortos. ¿Hemos termniado? ¿Podemos concluir que el efecto de un policía es beneficiosos pero considerablemente chico? ¿Qué problemas puede haber con este análisis? Variables desconocidas El problema con el análisis anterior es que controlamos por una variable que conocemos, pero muchas otras variables pueden estar ligadas con el proceso de selección de cruceros para enviar policías. Por ejemplo, envían o policías a cruceros Típicos solo cuando reportan mucho tráfico. No envían a un polícia a un crucero Complicado si no presenta demasiado tráfico. Existen otras variables desconocidas que los tomadores de decisiones usan para enviar a los policías. En este caso, por ejemplo, los expertos hipotéticos nos señalan que hay algunos cruceros que aunque problemáticos, a veces su tráfico se resuelve rápidamente, mientras que otros tienen tráfico más persistente, y prefieren enviar policías a los de tráfico persistente. La lista de cruceros persistentes están en una hoja de excel que se comparte de manera informal. En resumen, no tenemos conocimiento detallado del proceso generador de datos en cuanto a cómo se asignan los policías a los cruceros. Igual que en la sección anterior, podemos cortar esta complejidad usando aleatorización. Nótese que los expertos no están haciendo nada malo: en su trabajo están haciendo el mejor uso de los recursos que tienen. El problema es que por esa misma razón no podemos saber el resultado de sus esfuerzos, y si hay maneras de optimizar la asignación que hacen actualmente. Aleatorizando el tratamiento Tomamos la decisión entonces de hacer un experimento que incluya aletorización. En un dia particular, escogeremos algunos cruceros. Dicidimos usar solamente cruceros de la categoría Complicada y Típica, pues esos son los más interesantes para hacer intervenciones. Usaremos un poco de código para entener el detalle: en estos datos, tenemos para cada caso los dos posibles resultados hipotéticos \\(y_0\\) y \\(y_1\\) (con policia y sin policia). En el experimento asignamos el tratamiento al azar: muestra_exp <- trafico_tbl |> filter(categoria != "Fluido") |> sample_n(200) |> # asignar tratamiento al azar, esta es nuestra intervención: mutate(tratamiento_policia = rbernoulli(length(y_0), 0.5)) |> # observar resultado mutate(tiempo_espera_exp = ifelse(tratamiento_policia == 1, y_1, y_0)) Nótese la diferencia si tomamos la asignación natural del tratamiento (policía o no): set.seed(134) muestra_natural <- trafico_tbl |> filter(categoria != "Fluido") |> sample_n(200) |> # usamos el tratamiento que se asignó # policia indica si hubo o no policía en ese crucero # observar resultado mutate(tiempo_espera_obs = ifelse(policia == 1, y_1, y_0)) Resumimos nuestros resultados del experimento son: #> # A tibble: 2 × 3 #> # Groups: categoria [2] #> categoria `policia=0` `policia=1` #> <fct> <dbl> <dbl> #> 1 Típico 6.24 4.97 #> 2 Complicado 15.8 8.47 Sin embargo, la muestra natural da: #> # A tibble: 2 × 3 #> # Groups: categoria [2] #> categoria `policia=0` `policia=1` #> <fct> <dbl> <dbl> #> 1 Típico 5.49 4.35 #> 2 Complicado 10.8 8.93 ¿Cuál de los dos análisis da la respuesta correcta a la pregunta: ayudan o no los policías a reducir el tráfico en los cruceros problemáticos? El experimento establece que un policía en promedio reduce a la mitad el tiempo de espera en un crucero complicado Selección de unidades y tratamiento Vimos dos tipos de inferencia que requieren distintos diseños de estudio: a poblaciones (ejemplo anemia) y causal (ejemplo policías). En el escenario ideal de cada uno de estos ejemplos requerimos un mecanismo de aleatorización, sin embargo, la aleatorización requerida en cada caso es distinta y distinguir esto es fundamental para entender las inferencias que podemos hacer en distintos escenarios. Inferencia estadística de acuerdo al tipo del diseño (Ramsey and Schafer (2012)). El cuadro arriba a la izquierda es donde el análisis es más simple y los resultados son más fáciles de interpretar. En este escenario don de la aleatorización es tanto en unidades como en grupos no hacen falta supuestos adicionales para tener las garantías de métodos de inferencia. Es posible hacer análisis fuera de este cuadro, pero el proceso es más complicado, requieren más supuestos, conocimiento del dominio y habilidades de análisis. En general resultan conclusiones menos sólidas. Muchas veces no nos queda otra más que trabajar fuera del cuadro ideal. El punto crucial para entender las medidas de incertidumbre estadística es visualizar de manera hipotética, replicaciones del estudio y las condiciones que llevaron a la selección de la muestra. Esto es, entender el proceso generador de datos e imaginar replicarlo. Ubica los siguientes tipos de análisis: Pruebas clínicas para medicinas Analizar cómo afecta tener seguro médico a los ingresos, usando datos del ENIGH. Estimación de retorno sobre inversión en modelos de marketing mix. Asignación natural del tratamiento Cuando consideramos un sistema donde se “asignan” tratamientos de manera natural, generalmente los tratamientos se asignan bajo un criterio de optimización o conveniencia (por ejemplo los policías a cruceros problemáticos). La cara buena de este hecho es que de alguna forma los resultados están intentando optimizarse, y la gente está haciendo su trabajo. La cara mala de este hecho es que no podemos evaluar de manera simple la efectividad de los tratamientos. Y esto hace difícil optimizar de forma cuantificable los procesos, o entender qué funciona y qué no. Referencias "],["pruebas-de-hipótesis.html", "Sección 3 Pruebas de hipótesis Comparación con poblaciones de referencia Comparando distribuciones Prueba de permutaciones y el lineup Comparaciones usando lineup (continuación) Prueba de permutaciones para proporciones Pruebas de hipótesis tradicionales Tomadores de té (continuación) Pruebas de permutación: implementación. Ejemplo: tiempos de fusión Ejemplo: tiempos de fusión (continuación) Separación de grupos La “crisis de replicabilidad” El jardín de los senderos que se bifurcan Ejemplo: decisiones de análisis y valores p Alternativas o soluciones", " Sección 3 Pruebas de hipótesis Las primeras técnicas inferenciales que veremos intentan contestar la siguiente pregunta: Si observamos cierto patrón en los datos, ¿cómo podemos cuantificar la evidencia de que es un patrón notable y no sólo debido a fluctuaciones en los datos particulares que tenemos? ¿Cómo sabemos que no estamos sobreinterpretando esas fluctuaciones? Por ejemplo: Un sistema tiene cierto comportamiento “usual” para el cual tenemos datos históricos. El sistema presenta fluctuaciones en el tiempo. Observamos la última salida de nuestro sistema. Naturalmente, tiene fluctuaciones. ¿Esas fluctuaciones son consistentes con la operación usual del sistema? ¿Existe evidencia para pensar que algo en el sistema cambió? Comparación con poblaciones de referencia En las prueba de hipótesis, tratamos de construir distribuciones de referencia para comparar resultados que obtengamos con un “estándar” de variación, y juzgar si nuestros resultados son consistentes con la referencia o no (Box et al. 1978). En algunos casos, ese estándar de variación puede construirse con datos históricos. Ejemplo Supongamos que estamos considerando cambios rápidos en una serie de tiempo de alta frecuencia. Hemos observado la serie en su estado “normal” durante un tiempo considerable, y cuando observamos nuevos datos quisiéramos juzgar si hay indicaciones o evidencia en contra de que el sistema sigue funcionando de manera similar. Digamos que monitoreamos ventanas de tiempo de tamaño 20 y necesitamos tomar una decisión. Abajo mostramos cinco ejemplos donde el sistema opera normalmente, que muestra la variabilidad en el tiempo en ventanas cortas del sistema. Ahora suponemos que obtenemos una nueva ventana de datos. ¿Hay evidencia en contra de que el sistema sigue funcionando de manera similar? Nuestra primera inclinación debe ser comparar: en este caso, compararamos ventanas históricas con nuestra nueva serie: # usamos datos simulados para este ejemplo set.seed(8812) historicos <- simular_serie(2000) ¿Vemos algo diferente en los datos nuevos (el panel de color diferente)? Indpendientemente de la respuesta, vemos que hacer este análisis de manera tan simple no es siempre útil: seguramente podemos encontrar maneras en que la nueva muestra (4) es diferente a muestras históricas. Por ejemplo, ninguna de muestras tiene un “forma de montaña” tan clara. Nos preguntamos si no estamos sobreinterpretando variaciones que son parte normal del proceso. Podemos hacer un mejor análisis si extraemos varias muestras del comportamiento usual del sistema, graficamos junto a la nueva muestra, y revolvemos las gráficas para que no sepamos cuál es cuál. Entonces la pregunta es: ¿Podemos detectar donde están los datos nuevos? Esta se llama una prueba de lineup, o una prueba de ronda de sospechosos (Hadley Wickham et al. 2010). En la siguiente gráfica, en uno de los páneles están los datos recientemente observados. ¿Hay algo en los datos que distinga al patrón nuevo? # nuevos datos obs <- simular_serie(500, x_inicial = last(obs$obs)) # muestrear datos históricos prueba_tbl <- muestrear_ventanas(historicos, obs[1:20, ], n_ventana = 20) # gráfica de pequeños múltiplos ggplot(prueba_tbl$lineup, aes(x = t_0, y = obs)) + geom_line() + facet_wrap(~rep, nrow = 4) + scale_y_log10() ¿Cuáles son los datos nuevos (solo hay un panel con los nuevos datos)? ¿Qué implica que la gráfica que escojamos como “más diferente” no sean los datos nuevos? ¿Qué implica que le “atinemos” a la gráfica de los datos nuevos? Ahora observamos al sistema en otro momento y repetimos la comparación. En el siguiente caso obtenemos: Aunque es imposible estar seguros de que ha ocurrido un cambio, la diferencia de una de las series es muy considerable. Si identificamos los datos correctos, la probabilidad de que hayamos señalado la nueva serie “sobreinterpretando” fluctuaciones en un proceso que sigue comportándose normalente es 0.05 - relativamente baja. Detectar los datos diferentes es evidencia en contra de que el sistema sigue funcionando de la misma manera que antes. En el ejemplo anterior se encontraban en la posición: prueba_tbl$pos ## [1] 18 Observaciones y terminología: Llamamos hipótesis nula a la hipótesis de que los nuevos datos son producidos bajo las mismas condiciones que los datos de control o de referencia. Si no escogemos la gráfica de los nuevos datos, nuestra conclusión es que la prueba no aporta evidencia en contra de la hipótesis nula. Si escogemos la gráfica correcta, nuestra conclusión es que la prueba aporta evidencia en contra de la hipótesis nula. ¿Qué tan fuerte es la evidencia, en caso de que descubrimos los datos no nulos? Cuando el número de paneles es más grande y detectamos los datos, la evidencia es más alta en contra de la nula. Decimos que el nivel de significancia de la prueba es la probabilidad de seleccionar a los datos correctos cuando la hipótesis nula es cierta (el sistema no ha cambiado). En el caso de 20 paneles, la significancia es de 1/20 = 0.05. Cuando detectamos los datos nuevos, niveles de significancia más bajos implican más evidencia en contra de la nula. Si acertamos, y la diferencia es más notoria y fue muy fácil detectar la gráfica diferente (pues sus diferencias son más extremas), esto también sugiere más evidencia en contra de la hipótesis nula. Finalmente, esta prueba rara vez (o nunca) nos da seguridad completa acerca de ninguna conclusión, aún cuando hiciéramos muchos páneles. Comparando distribuciones Ahora intentamos un ejemplo más típico. Supongamos que tenemos muestras para tres grupos a, b y c, esto es que dentro de cada grupo, el proceso de selección de los elementos se hace al azar y de manera simétrica (por ejemplo cada elemento tiene a misma probabiidad de ser seleccionado, y las extracciones se hacen de manera independiente.) Queremos comparar las distribuciones de los datos obtenidos para cada grupo. Quizá la pregunta detrás de esta comparación es: el grupo de clientes b recibió una promoción especial. ¿Están gastando más? La medición que comparamos es el gasto de los clientes. En la muestra observamos diferencias entre los grupos. Pero notamos adicionalmente que hay mucha variación dentro de cada grupo. Nos podríamos preguntar entonces si las diferencias que observamos se deben variación muestral, por ejemplo. Podemos construir ahora una hipótesis nula, que establece que las observaciones provienen de una población similar: Las tres poblaciones (a, b, c) son prácticamente indistiguibles. En este caso, la variación que observamos se debería a que tenemos información incompleta. Como en el ejemplo anterior necesitamos construir u obtener una distribución de referencia para comparar qué tan extremos o diferentes son los datos que observamos. Esa distribución de referencia debería estar basada en el supuesto de que los grupos producen datos de distribuciones similares. Si tuvieramos mediciones similares históricas de estos tres grupos, quizá podríamos extraer datos de referencia y comparar, como hicimos en el ejempo anterior. Pero esto es menos común en este tipo de ejemplos. Prueba de permutaciones y el lineup Para abordar este problema podemos pensar en usar permutaciones de los grupos de la siguiente forma ((Box et al. 1978), (Tim C. Hesterberg 2015a)): Si los grupos producen datos bajo procesos idénticos, entonces los grupos a, b, c solo son etiquetas que no contienen información. Podríamos permutar al azar las etiquetas y observar nuevamente la gráfica de caja y brazos por grupos. Si la hipótesis nula es cierta (grupos idénticos), esta es una muestra tan verosímil como la que obtuvimos. Así que podemos construir datos de referencia permutando las etiquetas de los grupos al azar, y observando la variación que ocurre. Si la hipótesis nula es cercana a ser cierta, no deberíamos de poder distinguir fácilmente los datos observados de los producidos con las permutaciones al azar. Vamos a intentar esto, por ejemplo usando una gráfica de cuantiles simplificada. Hacemos un lineup, o una rueda de sospechosos (usamos el paquete (H. Wickham, Chowdhury, and Cook 2012), ver (Hadley Wickham et al. 2010)), donde 19 de los acusados son generados mediante permutaciones al azar de la variable del grupo, y el culpable (los verdaderos datos) están en una posición escogida al azar. ¿Podemos identificar los datos verdaderos? Para evitar sesgarnos, también ocultamos la etiqueta verdadera. Usamos una gráfica que muestra los cuantiles 0.10, 0.50, 0.90: set.seed(88) reps <- lineup(null_permute("grupo"), muestra_tab, n = 20) ## decrypt("M7xA 2S8S Jj dUyJ8JUj ZW") reps_mezcla <- reps |> mutate(grupo_1 = factor(digest::digest2int(grupo) %% 177)) grafica_cuantiles(reps_mezcla, grupo_1, x) + coord_flip() + facet_wrap(~.sample, ncol = 5) + ylab("x") + labs(caption = "Mediana y percentiles 10% y 90%") + geom_point(aes(colour = grupo_1)) Y la pregunta que hacemos es ¿podemos distinguir nuestra muestra entre todas las replicaciones producidas con permutaciones? ¿Dónde están los datos observados? Según tu elección, ¿qué tan diferentes son los datos observados de los datos nulos? En este ejemplo, es difícil indicar cuáles son los datos. Los grupos tienen distribuciones similares y es factible que las diferencias que observamos se deban a variación muestral. Si la persona escoge los verdaderos datos, encontramos evidencia en contra de la hipótesis nula (los tres grupos son equivalentes). En algunos contextos, se dice que los datos son significativamente diferentes al nivel 0.05. Esto es evidencia en contra de que los datos se producen de manera homogénea, independientemente del grupo. Si la persona escoge uno de los datos permutados, no encontramos evidencia en contra de que los tres grupos producen datos con distribuciones similares. Comparaciones usando lineup (continuación) Repetimos el ejemplo para otra muestra (en este ejemplo el proceso generador de datos es diferente para el grupo b): Hacemos primero la prueba del lineup: set.seed(121) reps <- lineup(null_permute("grupo"), muestra_tab, n = 20) grafica_cuantiles(reps |> mutate(grupo_escondido = factor(digest::digest2int(grupo) %% 177)), grupo_escondido, x) + facet_wrap(~.sample) + ylab("x") + coord_flip() + geom_point(aes(colour = grupo_escondido)) Podemos distinguir más o menos claramente que está localizada en valores más altos y tiene mayor dispersión. En este caso, como en general podemos identificar los datos, obtenemos evidencia en contra de que los tres grupos tienen distribuciones iguales. Estos ejemplos siguen la idea de inferencia visual propuestas en (Hadley Wickham et al. 2010), (Hofmann et al. 2012) son pruebas muy flexibles y estadísticamente rigurosas. Prueba de permutaciones para proporciones Veremos otro ejemplo donde podemos hacer más concreta la idea de distribución nula o de referencia usando pruebas de permutaciones. Supongamos que con nuestra muestra de tomadores de té, queremos probar la siguiente hipótesis nula: Los tomadores de té en bolsas exclusivamente, usan azúcar a tasas simillares que los tomadores de té suelto (que pueden o no también tomar té en bolsita). Los datos que obtuvimos en nuestra encuesta, en conteos, son: sugar bolsa_exclusivo suelto o bolsa No.sugar 81 74 sugar 89 56 Y en proporciones tenemos que: how prop_azucar n bolsa_exclusivo 0.52 170 suelto o bolsa 0.43 130 Pero distintas muestras podrían haber dado distintos resultados. Nos preguntamos qué tan fuerte es la evidencia en contra de que en realidad los dos grupos de personas usan azúcar en proporciones similares, y la diferencia que vemos se puede atribuir a variación muestral. En este ejemplo, podemos usar una estadística de prueba numérica, por ejemplo, la diferencia entre las dos proporciones: \\[\\hat p_1 - \\hat p_2,\\] (tomadores de té en bolsa solamente vs. suelto y bolsa). El proceso sería entonces: La hipótesis nula es que los dos grupos tienen distribuciones iguales. Este caso quiere decir que en la población, tomadores de té solo en bolsa usan azúcar a las mismas tasas que tomadores de suelto o bolsas. Bajo nuestra hipótesis nula (proporciones iguales), producimos una cantidad grande (por ejemplo 10 mil o más) de muestras permutando las etiquetas de los grupos. Evaluamos nuestra estadística de prueba en cada una de las muestras permutadas. El conjunto de valores obtenidos nos da nuestra distribución de referencia (ya no estamos limitados a 20 replicaciones como en las pruebas gráficas). Y la pregunta clave es: ¿el valor de la estadística en nuestra muestra es extrema en comparación a la distribución de referencia? dif_obs <- te_azucar |> mutate(usa_azucar = as.numeric(sugar == "sugar")) |> group_by(how) |> summarise(prop_azucar = mean(usa_azucar), .groups = 'drop') |> pivot_wider(names_from = how, values_from = prop_azucar) |> mutate(diferencia_prop = bolsa_exclusivo - `suelto o bolsa`) |> pull(diferencia_prop) La diferencia observada es: dif_obs |> round(3) ## [1] 0.093 Ahora construimos nuestra distribución nula o de referencia: reps <- lineup(null_permute("how"), te_azucar, n = 50000) glimpse(reps) ## Rows: 15,000,000 ## Columns: 3 ## $ how <chr> "bolsa_exclusivo", "bolsa_exclusivo", "suelto o bolsa", "suelt… ## $ sugar <chr> "sugar", "No.sugar", "No.sugar", "sugar", "No.sugar", "No.suga… ## $ .sample <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,… valores_ref <- reps |> mutate(usa_azucar = as.numeric(sugar == "sugar")) |> group_by(.sample, how) |> summarise(prop_azucar = mean(usa_azucar), .groups = 'drop') |> pivot_wider(names_from = how, values_from = prop_azucar) |> mutate(diferencia = bolsa_exclusivo - `suelto o bolsa`) Y graficamos nuestros resultados (con un histograma y una gráfica de cuantiles, por ejemplo). la estadística evaluada un cada una de nuestras muestras permutadas: g_1 <- ggplot(valores_ref, aes(sample = diferencia)) + geom_qq(distribution = stats::qunif) + xlab("f") + ylab("diferencia") + labs(subtitle = "Distribución nula o de referencia") g_2 <- ggplot(valores_ref, aes(x = diferencia)) + geom_histogram(binwidth = 0.04) + coord_flip() + xlab("") + labs(subtitle = " ") g_1 + g_2 Este es el rango de fluctuación usual para nuestra estadística bajo la hipótesis de que los dos grupos de tomadores de té consumen té a la misma tasa. El valor que obtuvimos en nuestros datos es: 0.09. Mismo que no es un valor extremo en la distribución de referencia que vimos arriba. Ésta muestra no aporta mucha evidencia en contra de que los grupos tienen distribuciones similares. Podemos graficar otra vez marcando el valor observado: # Función de distribución acumulada (inverso de función de cuantiles) dist_perm <- ecdf(valores_ref$diferencia) # Calculamos el percentil del valor observado percentil_obs <- dist_perm(dif_obs) g_1 <- ggplot(valores_ref, aes(sample = diferencia)) + geom_qq(distribution = stats::qunif) + xlab("f") + ylab("diferencia") + labs(subtitle = "Distribución nula o de referencia") + geom_hline(yintercept = dif_obs, colour = "red") + annotate("text", x = 0.35, y = dif_obs - 0.03, label = "diferencia observada", colour = "red") g_2 <- ggplot(valores_ref, aes(x = diferencia)) + geom_histogram(binwidth = 0.04) + coord_flip() + xlab("") + labs(subtitle = " ") + geom_vline(xintercept = dif_obs, colour = "red") + annotate("text", x = dif_obs, y = N_rep * .3, label = percentil_obs,vjust = -0.2, colour = "red") g_1 + g_2 Y vemos que es un valor algo (pero no muy) extremo en la distribución de referencia que vimos arriba: esta muestra no aporta una gran cantidad de evidencia en contra de que los grupos tienen distribuciones similares, que en este caso significa que los dos grupos usan azúcar a tasas similares. Es decir, sobre la hipótesis \\[H_0: p_1 = p_2,\\] o bien, \\[H_0: p_1 - p_2 = 0.\\] Pruebas de hipótesis tradicionales Comencemos recordando la definición de parámetro y estadística. Definición. Un parámetro es una característica (numérica) de una población o de una distribución de probabilidad. Usualmente denotado por \\(\\theta \\in \\mathbb{R},\\) o por \\(\\theta \\in \\mathbb{R}^p.\\) Una estadística es una característica (numérica) de los datos. Usualmente denotado por \\(\\hat \\theta.\\) Cualquier función de un parámetro es también un parámetro \\(\\varphi = h(\\theta),\\) y cualquier función de una estadística es también una estadística \\(\\hat \\varphi = h(\\hat \\theta).\\) Cuando la estadística se calcula de una muestra aleatoria (\\(T(X),\\) para \\(X_i \\sim \\pi_x\\) para \\(i = 1, \\ldots, n\\)), es por consiguiente aleatoria y es por tanto una variable aleatoria (\\(T \\sim \\pi_T\\)). Por ejemplo \\(\\mu\\) y \\(\\sigma\\) son parámetros de la distribución normal con función de densidad \\(\\pi(x) = (1/\\sqrt{2\\pi}\\sigma)e^{(x-\\mu)^2/(2\\sigma^2)}\\). La varianza \\(\\sigma^2\\), y el coeficiente de variación (cociente de señal a ruido) \\(\\mu/\\sigma\\) también son parámetros. Si \\(X_1, \\ldots ,X_n\\) son una muestra aleatoria, entonces la media \\(\\bar{X}=\\frac1n\\sum_i X_i\\) es una estadística. Ahora podemos pasar a las definiciones correspondientes a pruebas de hipótesis (o pruebas de significancia). Definición. Denotamos por \\(H_0\\) a la hipótesis nula la cual usualmente tratamos como la afirmación del status quo. La hipótesis alternativa la denotamos por \\(H_1\\) y representa el supuesto que está a prueba y para el cual buscamos evidencia en los datos. Definición. La hipótesis normalmente se plantea en términos de un parámetro (\\(\\theta\\in\\mathbb{R}\\)) o conjunto de parámetros (\\(\\theta\\in\\mathbb{R}^p\\)) de la distribución de interés (por ejemplo media, moda, varianza). Para una hipótesis nula del estilo \\(H_0: \\theta = \\theta_0,\\) la hipótesis a contrastar se puede denominar como: Hipótesis alternativa de una cola \\(H_1: \\theta \\gt \\theta_0\\) Hipótesis alternativa de dos colas \\(H_1: \\theta \\neq \\theta_0\\) En el ejemplo anterior planteamos hipótesis nula (proporciones iguales) e hipótesis alternativa que la proporción de tomadores de te suelto que usan azúcar en menor proporción, esto corresponde a una hipótesis alternativa a dos colas: \\(H_0: p_1 = p_2\\), y \\(H_1:p_1 > p_2\\). Definición. Una estadística de prueba es una función numérica de los datos cuyo valor determina el resultado de la prueba. La función usualmente es denotada por \\(T(\\bf X)\\) donde \\(\\bf X\\) representa los datos como variable aleatoria. Por ejemplo, \\(T = T(X_1, \\ldots, X_n)\\) si sólo tenemos una muestra, o por \\(T = T(X_1, \\ldots, X_n, Y_1, \\ldots, Y_m)\\) en el caso de tener dos muestras. Al evaluar la prueba para un conjunto de datos dado, \\(x\\), ésta se denomina estadística de prueba observada, \\(t = T(x).\\) La estadística de prueba correspondiente al ejemplo es \\(T = \\hat p_1 - \\hat p_2.\\) Definición. El valor p es la probabilidad de que bajo la hipótesis nula los datos generen un valor tan extremo como la estadística de prueba observada. Por ejemplo, si consideramos la hipótesis nula admite valores grandes, el valor p se calcula como \\(P(T \\geq t).\\) En el ejemplo de tomadores de té: el valor p lo calculamos usando el percentil donde nuestra observación cae en la distribución generada por las permutación (valor p de una cola). 1 - dist_perm(dif_obs) ## [1] 0.04344 Por otro lado, podemos calcular: Valor p de dos colas: Si la hipótesis nula es cierta, ¿cuál es la probabilidad de observar una diferencia tan extrema o más extrema de lo que observamos? Considerando este caso interpretamos extrema como qué tan lejos cae del centro de masa de la distribución. De tal forma que podemos calcular el valor p como sigue. A partir del valor observado, consideramos cuál dato es menor: la probabilidad bajo lo hipótesis nula de observar una diferencia mayor de la que observamos, o la probabilidad de observar una diferencia menor a la que observamos. Tomamos el mínimo y multiplicamos por dos (Tim C. Hesterberg 2015a): 2 * min(dist_perm(dif_obs), (1 - dist_perm(dif_obs))) ## [1] 0.08688 Este valor p se considera como evidencia moderada en contra de la hipótesis nula. Valores p chicos (observaciones más extremas en comparación con la referencia) aportan más evidencia en contra de la hipótesis nula, y valores más grandes aportan menos evidencia en contra. Definición. Un resultado es estadisticamente significativo si tiene muy baja probabilidad de suceder al azar. Entre más pequeño requiramos un valor p oara declarar un resultado estadísticamente significativo, somos más conservadores. Las pruebas de hipótesis con frecuencia inician contestando una pregunta más general que los valores p: ¿Cuál es la distribución de la estadística de prueba cuando no hay un efecto real? Definición. La distribución nula es la distribución de la estadística de prueba si la hipótesis nula es cierta. En ocasiones también nos referimos a ella como la distribución de referencia pues estamos comparando la estadística de prueba observada a su referencia para determinar que tan inusual es. En el ejemplo de tomadores de té aproximamos la distribución nula (y los valores p) con simulación; sin embargo, para algunas estadísticas hay métodos exactos. En particular, usamos el método de pruebas de permutación. Para dicha prueba el algoritmo para en el caso de dos grupos sería como sigue: Prueba de permutación para dos muestras Supongamos que tenemos m observaciones de una población y n de otra. Combina los m+n valores. Repite: Obtén un remuestra de tamaño m sin reemplazo del total. Usa las n observaciones restantes para obtener la otra muestra. Calcula la estadística de prueba (que compara las muestras). Calcula el valor p como la fracción de las veces que la estadística sobrepasó la estadística observada, multiplica por 2 para una prueba de dos lados. La distribución de la estadística a lo largo de las remuestras de permutación es la distribución de permutación. Ésta puede ser exacta, si se calcula exhaustivamente (como cuando tenemos pocas observaciones) o aproximada (cuando enlistar todas las posible combinaciones es prohibitivo). Tomadores de té (continuación) Ahora hacemos una prueba de permutaciones para otro par de proporciones utilizando el mismo método. La hipótesis nula ahora es: Los tomadores de té Earl Gray usan azúcar a una tasa similar a los tomadores de té negro. Los datos que obtuvimos en nuestra encuesta se muestran en la siguiente tabla: sugar Earl Grey black No.sugar 84 51 sugar 109 23 Y en porcentajes tenemos que: prop_azucar <- te_azucar |> count(Tea, sugar) |> group_by(Tea) |> mutate(prop = 100 * n / sum(n), n = sum(n)) |> filter(sugar == "sugar") |> select(Tea, prop_azucar = prop, n) |> mutate('% usa azúcar' = round(prop_azucar)) |> select(-prop_azucar) prop_azucar |> formatear_tabla() Tea n % usa azúcar Earl Grey 193 56 black 74 31 Pero distintas muestras podrían haber dado distintos resultados. Nos preguntamos qué tan fuerte es la evidencia en contra de que en realidad los dos grupos de personas usan azúcar en proporciones similares considerando que la diferencia que vemos se puede atribuir a variación muestral. Escribimos la función que calcula diferencias para cada muestra: calc_diferencia_2 <- function(datos){ datos |> mutate(usa_azucar = as.numeric(sugar == "sugar")) |> group_by(Tea) |> summarise(prop_azucar = mean(usa_azucar), .groups = 'drop') |> pivot_wider(names_from = Tea, values_from = prop_azucar) |> mutate(diferencia_prop = `Earl Grey` - black) |> pull(diferencia_prop) } La diferencia observada es: ## [1] 0.254 Ahora construimos nuestra distribución nula o de referencia: set.seed(2) reps <- lineup(null_permute("Tea"), te_azucar, n = N_rep) valores_ref <- reps |> group_by(.sample) |> nest() |> mutate(diferencia = lapply(data, calc_diferencia_2)) |> unnest(diferencia) Y podemos graficar la distribución de referencia otra vez marcando el valor observado En este caso, la evidencia es muy fuerte en contra de la hipótesis nula, pues el resultado que obtuvimos es muy extremo en relación a la distribución de referencia. El valor p es cercano a 0. Haz una prueba de permutaciones para diferencia de medias para comparar la propina en cena vs en comidas. Grafica la distribución de referencia. Calcula el valor p (dos colas). Pruebas de permutación: implementación. Hasta ahora nos hemos centrado en ejemplos de diferencias en medias. Podemos extender las pruebas de permutación a \\(\\bar{X}\\) (la media de la primera muestra), \\(n\\bar{X}\\) (la suma de las observaciones en la primera muestra), y más. Teorema. En pruebas de permutación, si dos estadísticas de prueba \\(T_1\\) y \\(T_2\\) están relacionadas por una función estríctamente monótona, \\(T_1(X^*)=f(T_2(X^*))\\) donde \\(X^*\\) es una remuestra de permutación de los datos originales, entonces los valores p serán los mismos en las pruebas de permutación. Muestras con reemplazo de la Distribución Nula. En la implementación de muestreo, no nos aseguramos que las remuestras sean únicas. Sería más acertado tomar muestras sin reemplazo, sin embargo, el costo computacional es demasiado alto. Por simplicidad consideramos muestras con reemplazo del total de \\[m+n\\choose n\\] posibles remuestras. Por lo tanto, al remuestrar obtenemos una muestra de la distribución nula. Entre más muestras, más exactitud. Hemos usado \\(B = 10^3\\) remuestras (N_rep en el código), en general entre más remuestras tendremos una mejor estimación del valor p. Si el verdadero valor es \\(p\\) el estimado tendrá una varianza aproximadamente de \\(p(1- p)/B\\) donde \\(B\\) es el número de remuestras generadas. Observación. Así como los \\(n\\) datos originales son una muestra de la población, también las \\(B\\) remuestras de la estadística son una muestra de una población, en este caso de la distribución nula. La pruebas de permutaciones son más útiles cuando nuestra hipótesis nula se refiere que la distribución de los grupos son muy similares, o la independencia entre observaciones y grupo. Esto también aplica cuando queremos probar por ejemplo, que una variable numérica \\(Y\\) es independiente de \\(X.\\) Hay algunas hipótesis que no se pueden probar con este método, como por ejemplo, las que se refieren a una sola muestra: ¿los datos son consistentes con que su media es igual a 5? Adicionalmente, en algunas ocasiones queremos probar aspectos más específicos de las diferencias: como ¿son iguales las medias o medianas de dos grupos de datos? ¿Tienen dispersión similar? Es común aplicar pruebas de permutaciones a este segundo problema, sin embargo, no están tan perfectamente adaptadas a el, pues prueban todos los aspectos de las distribuciones que se comparan, aún cuando escojamos una estadística particular que pretende medir. Por ejemplo, cuando trabajamos con la diferencia de medias. Eso quiere decir que podemos rechazar igualdad de medias, por ejemplo, cuando en realidad otra característica de las distribuciones es la que difiere mucho en las poblaciones. En algunas referencias (ver (Chihara and Hesterberg 2018), (Efron and Tibshirani 1993)) se argumenta que de todas formas las pruebas de permutaciones son relativamente robustas a esta desadaptación. Un caso excepcional, por ejemplo, es cuando las poblaciones que comparamos resultan tener dispersión extremadamente distinta, y adicionalmente los tamaños de muestra de los grupos son muy desiguales (otra vez, ver ejemplos en (Chihara and Hesterberg 2018)). Ejemplo: tiempos de fusión Veamos el siguiente ejemplo, que es un experimento donde se midió el tiempo que tardan distintas personas en fusionar un estereograma para ver una imagen 3D. (William S. Cleveland (1993)). Existen dos condiciones: en una se dio indicaciones de qué figura tenían que buscar (VV) y en otra no se dio esa indicación. ¿Las instrucciones verbales ayudan a fusionar más rápido el estereograma? ## ## ── Column specification ──────────────────────────────────────────────────────── ## cols( ## n = col_double(), ## time = col_double(), ## nv.vv = col_character() ## ) La situación es la siguiente: considerando que hay mucha variación en el tiempo de fusión dentro de cada tratamiento, necesitamos calificar la evidencia de nuestra conclusión (el tiempo de fusión se reduce con información verbal). Podemos usar una prueba de permutaciones, esta vez justificándola por el hecho de que los tratamientos se asignan al azar: si los tratamientos son indistinguibles, entonces las etiquetas de los grupos son sólo etiquetas, y permutarlas daría muestras igualmente verosímiles. En este caso, compararemos gráficas de cuantiles de los datos con los producidos por permutaciones (transformamos los datos pues en este caso es más apropiado una comparación multiplicativa): ¿Podemos identificar los datos? En general, muy frecuentemente las personas identifican los datos correctamente, lo que muestra evidencia considerable de que la instrucción verbal altera los tiempos de respuesta de los partipantes, y en este caso ayuda a reducir el tiempo de fusión de los estereogramas. Ejemplo: tiempos de fusión (continuación) Podemos usar las pruebas de permutaciones para distintos tipos de estadísticas: medianas, medias, comparar dispersión usando rangos intercuartiles o varianzas, etc. Regresamos a los tiempos de fusión. Podemos hacer una prueba de permutaciones para la diferencia de las medias o medianas, por ejemplo. En este ejemplo usaremos una medida de centralidad un poco diferente, como ilustración: el promedio de los cuartiles superior e inferior de las dos distribuciones. Usaremos el cociente de estas dos cantidades para medir su diferencia # esta función hace permutaciones y calcula la diferencia para cada una permutaciones_est <- function(datos, variable, calc_diferencia, n = 1000){ # calcular estadística para cada grupo permutar <- function(variable){ sample(variable, length(variable)) } tbl_perms <- tibble(.sample = seq(1, n-1, 1)) |> mutate(diferencia = map_dbl(.sample, ~ datos |> mutate({{variable}}:= permutar({{variable}})) |> calc_diferencia())) bind_rows(tbl_perms, tibble(.sample = n, diferencia = calc_diferencia(datos))) } stat_fusion <- function(x){ (quantile(x, 0.75) + quantile(x, 0.25))/2 } calc_fusion <- function(stat_fusion){ fun <- function(datos){ datos |> group_by(nv.vv) |> summarise(est = stat_fusion(time), .groups = 'drop') |> pivot_wider(names_from = nv.vv, values_from = est) |> mutate(dif = VV / NV ) |> pull(dif) } fun } calc_cociente <- calc_fusion(stat_fusion) dif_obs <- calc_cociente(fusion) # permutar valores_ref <- permutaciones_est(fusion, nv.vv, calc_cociente, n = N_rep) dist_perm_nv <- ecdf(valores_ref$diferencia) cuantil_obs <- dist_perm_nv(dif_obs) Y el valor p de dos colas es dist_perm_nv <- ecdf(valores_ref$diferencia) 2 * min(dist_perm_nv(dif_obs), 1 - dist_perm_nv(dif_obs)) ## [1] 0.028 Lo que muestra evidencia considerable, aunque no muy fuerte, de que la instrucción verbal ayuda a reducir el tiempo de fusión de los estereogramas: la caja del diagrama de caja y brazos para el grupo VV está encogida por un factor menor a 1. Separación de grupos Este ejemplo tomado de (Chowdhury et al. 2015) (tanto la idea como el código). La pregunta que se aborda en ese estudio es: Existen métodos de clasificación (supervisados o no supervisados) para formar grupos en términos de variables que describen a los individuos Estos métodos (análisis discriminante, o k-means, por ejemplo), pretenden formar grupos compactos, bien separados entre ellos. Cuando aplicamos el método, obtenemos clasificadores basados en las variables de entrada. La pregunta es: ¿los grupos resultantes son producto de patrones que se generalizan a la población, o capitalizaron en variación aleatoria para formarse? Especialmente cuando tenemos muchas mediciones de los individuos, y una muestra relativamente chica, Es relativamente fácil encontrar combinaciones de variables que separan los grupos, aunque estas combinaciones y diferencias están basadas en ruido y no generalizan a la población. Como muestran en (Chowdhury et al. 2015), el lineup es útil para juzgar si tenemos evidencia en contra de que los grupos en realidad son iguales, y usamos variación muestral para separarlos. Avispas (opcional) En el siguiente ejemplo, tenemos 4 grupos de avispas (50 individuos en total), y para cada individuo se miden expresiones de 42 genes distintos. La pregunta es: ¿Podemos separar a los grupos de avispas dependiendo de sus mediciones? En este se usó análisis discriminante (LDA) para buscar proyecciones de los datos en dimensión baja de forma que los grupos sean lo más compactos y separados posibles. Para probar qué tan bien funciona este método, podemos hacer una prueba de permutación, aplicamos LDA y observamos los resultados. Y vemos que incluso permutando los grupos, es generalmente posible separarlos en grupos bien definidos: la búsqueda es suficientemente agresiva para encontrar combinaciones lineales que los separan. Que no podamos distinguir los datos verdaderos de las replicaciones nulas indica que este método difícilmente puede servir para separar los grupos claramente. Otro enfoque sería separar los datos en una muestra de entrenamiento y una de prueba (que discutiremos en la última sesión). Aplicamos el procedimiento a la muestra de entrenamiento y luego vemos qué pasa con los datos de prueba: set.seed(8) wasps_1 <- wasps |> mutate(u = runif(nrow(wasps), 0, 1)) wasps_entrena <- wasps_1 |> filter(u <= 0.8) wasps_prueba <- wasps_1 |> filter(u > 0.8) wasp.lda <- MASS::lda(Group ~ ., data=wasps_entrena[,-1]) wasp_ld_entrena <- predict(wasp.lda, dimen=2)$x |> as_tibble(.name_repair = "universal") |> mutate(tipo = "entrenamiento") |> mutate(grupo = wasps_entrena$Group) wasp_ld_prueba <- predict(wasp.lda, newdata = wasps_prueba, dimen=2)$x |> as_tibble(.name_repair = "universal") |> mutate(tipo = "prueba")|> mutate(grupo = wasps_prueba$Group) wasp_lda <- bind_rows(wasp_ld_entrena, wasp_ld_prueba) ggplot(wasp_lda, aes(x = LD1, y = LD2, colour = grupo)) + geom_point(size = 3) + facet_wrap(~tipo) Aunque esta separación de datos es menos efectiva en este ejemplo por la muestra chica, podemos ver que la separación lograda en los datos de entrenamiento probablemente se debe a variación muestral. La “crisis de replicabilidad” Recientemente (Ioannidis 2005) se ha reconocido en campos como la psicología la crisis de replicabilidad. Varios estudios que recibieron mucha publicidad inicialmente no han podido ser replicados posteriormente por otros investigadores. Por ejemplo: Hacer poses poderosas produce cambios fisiológicos que mejoran nuestro desempeño en ciertas tareas. Mostrar palabras relacionadas con “viejo” hacen que las personas caminen más lento (efectos de priming). En todos estos casos, el argumento de la evidencia de estos efectos fue respaldada por una prueba de hipótesis nula con un valor p menor a 0.05. La razón es que ese es el estándar de publicación seguido por varias áreas y revistas arbitradas. La tasa de no replicabilidad parece ser mucho más alta (al menos la mitad o más, según algunas fuentes como la señalada arriba) que la sugerida por la tasa de falsos positivos (menos de 5%). Este problema de replicabilidad parece ser más frecuente cuando: Se trata de estudios de potencia baja: mediciones ruidosas y tamaños de muestra chicos. El plan de análisis no está claramente definido desde un principio (lo cual es difícil cuando se están investigando “fenómenos no estudiados antes”). ¿A qué se atribuye esta crisis de replicabilidad? El jardín de los senderos que se bifurcan Aunque haya algunos ejemplos de manipulaciones conscientes —e incluso, en menos casos, malintencionadas— para obtener resultados publicables o significativos (p-hacking), como vimos en ejemplos anteriores, hay varias decisiones, todas razonables, que podemos tomar cuando estamos buscando las comparaciones correctas. Algunas pueden ser: Transformar los datos (tomar o no logaritmos, u otra transformación). Editar datos atípicos (razonable si los equipos pueden fallar, o hay errores de captura, por ejemplo). Distintas maneras de interpretar los criterios de inclusión de un estudio (por ejemplo, algunos participantes mostraron tener gripa, o revelaron que durmieron muy poco la noche anterior, etc. ¿los dejamos o los quitamos?). Dado un conjunto de datos, las justificaciones de las decisiones que se toman en cada paso son razonables, pero con datos distintos las decisiones podrían ser diferentes. Este es el jardín de los senderos que se bifurcan (ver referencia en Gelman), que invalida en parte el uso valores p como criterio de evidencia contra la hipótesis nula. Esto es exacerbado por: Tamaños de muestra chicos y efectos “inestables” que se quieren medir (por ejemplo en psicología). El hecho de que el criterio de publicación es obtener un valor \\(p < 0.05\\), y la presión fuerte sobre los investigadores para producir resultados publicables (\\(p < 0.05\\)). El que estudios o resultados similares que no obtuvieron valores \\(p\\) por debajo del umbral no son publicados o reportados. Ver por ejemplo el comunicado de la ASA. Ojo: esas presiones de publicación no sólo ocurre para investigadores en psicología. Cuando trabajamos en problemas de análisis de datos que son de importancia, es común que existan intereses de algunas partes o personas involucradas por algunos resultados u otros (por ejemplo, nuestros clientes de consultoría o clientes internos). Eso puede dañar nuestro trabajo como analistas, y el avance de nuestro equipo. Aunque esas presiones son inevitables, se vuelven manejables cuando hay una relación de confianza entre las partes involucradas. Ejemplo: decisiones de análisis y valores p En el ejemplo de datos de fusión, decidimos probar, por ejemplo, el promedio de los cuartiles inferior y superior, lo cual no es una decisión típica pero usamos como ilustración. Ahora intentamos usar distintas mediciones de la diferencia entre los grupos, usando distintas medidas resumen y transformaciones (por ejemplo, con o sin logaritmo). Aquí hay unas 12 combinaciones distintas para hacer el análisis (multiplicadas por criterios de “aceptación de datos en la muestra”, que simulamos tomando una submuestra al azar): calc_fusion <- function(stat_fusion, trans, comparacion){ fun <- function(datos){ datos |> group_by(nv.vv) |> summarise(est = stat_fusion({{ trans }}(time)), .groups = 'drop') |> pivot_wider(names_from = nv.vv, values_from = est) |> mutate(dif = {{ comparacion }}) |> pull(dif) } fun } valor_p <- function(datos, variable, calc_diferencia, n = 1000){ # calcular estadística para cada grupo permutar <- function(variable){ sample(variable, length(variable)) } tbl_perms <- tibble(.sample = seq(1, n-1, 1)) |> mutate(diferencia = map_dbl(.sample, ~ datos |> mutate({{variable}} := permutar({{variable}})) |> calc_diferencia())) perms <- bind_rows(tbl_perms, tibble(.sample = n, diferencia = calc_diferencia(datos))) perms_ecdf <- ecdf(perms$diferencia) dif <- calc_diferencia(datos) 2 * min(perms_ecdf(dif), 1- perms_ecdf(dif)) } set.seed(7272) media_cuartiles <- function(x){ (quantile(x, 0.75) + quantile(x, 0.25))/2 } # nota: usar n=10000 o más, esto solo es para demostración: ejemplo <- list() calc_dif <- calc_fusion(mean, identity, VV - NV) ejemplo$media_dif <- valor_p(fusion |> sample_frac(0.95), nv.vv, calc_dif, n = N_rep) calc_dif <- calc_fusion(mean, log, VV - NV) ejemplo$media_dif_log <- valor_p(fusion |> sample_frac(0.95), nv.vv, calc_dif, n = N_rep) calc_dif <- calc_fusion(median, identity, VV / NV) ejemplo$mediana_razon <- valor_p(fusion |> sample_frac(0.95), nv.vv, calc_dif, n = N_rep) calc_dif <- calc_fusion(media_cuartiles, identity, VV / NV) ejemplo$cuartiles_razon <- valor_p(fusion |> sample_frac(0.95), nv.vv, calc_dif, n = N_rep) ejemplo <- read_rds("cache/ejemplo_p_val.rds") ejemplo$media_dif ## [1] 0.0658 ejemplo$media_dif_log ## [1] 0.018 ejemplo$mediana_razon ## [1] 0.049 ejemplo$cuartiles_razon ## [1] 0.0464 Si existen grados de libertad —muchas veces necesarios para hacer un análisis exitoso—, entonces los valores p pueden tener poco significado. Alternativas o soluciones El primer punto importante es reconocer que la mayor parte de nuestro trabajo es exploratorio (recordemos el proceso complicado del análisis de datos de refinamiento de preguntas). En este tipo de trabajo, reportar valores p puede tener poco sentido, y mucho menos tiene sentido aceptar algo verdadero cuando pasa un umbral de significancia dado. Nuestro interés principal al hacer análisis es: expresar correctamente, y de manera útil, la incertidumbre asociada a las conclusiones o patrones que mostramos (asociada a variación muestral, por ejemplo) con el objetivo que el proceso de toma de decisiones sea informado. Un resumen de un número (valor p, o el que sea) no puede ser tomado como criterio para tomar una decisión que generalmente es compleja. En la siguiente sección veremos cómo podemos mostrar parte de esa incertidumbre de manera más útil. Por otra parte, los estudios confirmatorios (donde se reportan valores p) también tienen un lugar. En áreas como la psicología, existen ahora movimientos fuertes en favor de la repetición de estudios prometedores pero donde hay sospecha de grados de libertad del investigador. Este movimiento sugiere dar valor a los estudios exploratorios que no reportan valor p, y posteriormente, si el estudio es de interés, puede intentarse una replicación confirmatoria, con potencia más alta y con planes de análisis predefinidos. Referencias "],["estimación-y-distribución-de-muestreo-1.html", "Sección 4 Estimación y distribución de muestreo Ejemplo: precios de casas Distribución de muestreo Más ejemplos El error estándar Calculando la distribución de muestreo Teorema central del límite Normalidad y gráficas de cuantiles normales Prueba de hipótesis de normalidad Ejemplo Más del Teorema central del límite", " Sección 4 Estimación y distribución de muestreo En esta sección discutiremos cuál el objetivo general del proceso de estimación. y cómo entender y manejar la variabilidad que se produce cuando aleatorizamos la selección de las muestras que utilizamos para hacer análisis. A diferencia de las pruebas de permutación, donde evaluábamos como cambiaría una estadísitica si un tratamiento o grupo se hubiera asignado de forma distinta, en la siguiente sección nos preguntamos como varía una estadística entre muestras. Por ejemplo, pasaremos de preguntar si una vacuna reduce el riesgo de una enfermedad a evaluar en que magnitud se reduce el riesgo de contraer la enfermedad. Ejemplo: precios de casas Supongamos que queremos conocer el valor total de las casas que se vendieron recientemente en una zona particular. Supondremos que tenemos un listado de las casas que se han vendido recientemente, pero en ese listado no se encuentra el precio de venta. Decidimos entonces tomar una muestra aleatoria de 100 de esas casas. Para esas casas hacemos trabajo de campo para averiguar el precio de venta. marco_casas <- read_csv("data/casas.csv") set.seed(841) muestra_casas <- sample_n(marco_casas, 100) |> select(id, nombre_zona, area_habitable_sup_m2, precio_miles) sprintf("Hay %0.0f casas en total, tomamos muestra de %0.0f", nrow(marco_casas), nrow(muestra_casas)) ## [1] "Hay 1144 casas en total, tomamos muestra de 100" head(muestra_casas) ## # A tibble: 6 × 4 ## id nombre_zona area_habitable_sup_m2 precio_miles ## <dbl> <chr> <dbl> <dbl> ## 1 287 NAmes 161. 159 ## 2 755 NAmes 95.3 156 ## 3 1190 Gilbert 168. 189 ## 4 36 NridgHt 228. 309 ## 5 32 Sawyer 114. 149. ## 6 538 NAmes 80.3 111. Como tomamos una muestra aleatoria, intentamos estimar el valor total de las casas que se vendieron expandiendo el total muestral, es decir nuestro estimador \\(\\hat{t} = t(X_1,\\ldots X_{100})\\) del total poblacional \\(t\\) es \\[\\hat{t} = \\frac{N}{n} \\sum_{i=1}^{100} X_i = N\\bar{x}\\] Esta función implementa el estimador: n <- nrow(muestra_casas) # tamaño muestra N <- nrow(marco_casas) # tamaño población estimar_total <- function(muestra_casas, N){ total_muestral <- sum(muestra_casas$precio_miles) n <- nrow(muestra_casas) # cada unidad de la muestra representa a N/n f_exp <- N / n # estimador total es la expansión del total muestral estimador_total <- f_exp * total_muestral res <- tibble(total_muestra = total_muestral, factor_exp = f_exp, est_total_millones = estimador_total / 1000) res } estimar_total(muestra_casas, N) |> mutate(across(where(is.numeric), \\(x) round(x, 2))) ## # A tibble: 1 × 3 ## total_muestra factor_exp est_total_millones ## <dbl> <dbl> <dbl> ## 1 18444. 11.4 211 Sin embargo, si hubiéramos obtenido otra muestra, hubiéramos obtenido otra estimación diferente. Por ejemplo: estimar_total(sample_n(marco_casas, 100), N) |> mutate(across(where(is.numeric), round, 2)) ## # A tibble: 1 × 3 ## total_muestra factor_exp est_total_millones ## <dbl> <dbl> <dbl> ## 1 17916. 11.4 205. El valor poblacional que buscamos estimar (nótese que en la práctica este no lo conocemos) es: # multiplicar por 1000 para que sea en millones de dólares total_pob <- sum(marco_casas |> pull(precio_miles)) / 1000 total_pob ## [1] 209.7633 Así que: Para algunas muestras esta estadística puede estar muy cercana al valor poblacional, pero para otras puede estar más lejana. Para entender qué tan buena es una estimación particular, entonces, tenemos que entender cuánta variabilidad hay de muestra a muestra debida a la aleatorización. Esto depende del diseño de la muestra y de la población de precios de casas (que no conocemos). Distribución de muestreo La distribución de muestreo de una estadística enumera los posibles resultados que puede tomar esa estadística sobre todas las muestras posibles. Este es el concepto básico para poder entender qué tan bien o mal estima un parámetro poblacional dado. En nuestro ejemplo anterior de precio de casas, no podemos calcular todas las posibles estimaciones bajo todas las posibles muestras, pero podemos aproximar repitiendo una gran cantidad de veces el proceso de muestreo, como hicimos al aproximar la distribución de permutaciones de estadísticas de prueba de las secciones anteriores. Empezamos repitiendo 10 veces y examinamos cómo varía nuestra estadística: replicar_muestreo <- function(marco_casas, m = 500, n){ # n es el tamaño de muestra que se saca de marco_casas # m es el número de veces que repetimos el muestro de tamaño n resultados <- map_df(1:m, function(id) { sample_n(marco_casas, n) |> estimar_total(N) }, .id = "id_muestra") } replicar_muestreo(marco_casas, m = 10, n = 100) |> mutate(across(where(is.numeric), round, 1)) |> formatear_tabla() id_muestra total_muestra factor_exp est_total_millones 1 17594.8 11.4 201.3 2 17423.9 11.4 199.3 3 18444.3 11.4 211.0 4 17696.6 11.4 202.4 5 17275.8 11.4 197.6 6 17867.6 11.4 204.4 7 18450.8 11.4 211.1 8 18187.2 11.4 208.1 9 18604.2 11.4 212.8 10 19144.4 11.4 219.0 Como vemos, hay variación considerable en nuestro estimador del total, pero la estimación que haríamos con cualquiera de estas muestras no es muy mala. Ahora examinamos un número más grande de simulaciones: replicaciones_1 <- replicar_muestreo(marco_casas, m = 1500, n = 100) Y el siguiente histograma nos dice qué podemos esperar de la variación de nuestras estimaciones, y donde es más probable que una estimación particular caiga: graf_1 <- ggplot(replicaciones_1, aes(x = est_total_millones)) + geom_histogram() + geom_vline(xintercept = total_pob, colour = "red") + xlab("Millones de dólares") + scale_x_continuous(breaks = seq(180, 240, 10), limits = c(180, 240)) graf_1 Con muy alta probabilidad el error no será de más de unos 30 millones de dólares (o no más de 20% del valor poblacional). Definición Sea \\(X_1, X_2, \\ldots X_n\\) una muestra, y \\(T = t(X_1, X_2, \\ldots, X_n)\\) una estadística. La distribución de muestreo de \\(T\\) es la función de distribución de \\(T\\). Esta distribución es sobre todas las posibles muestras que se pueden obtener. Cuando usamos \\(T\\) para estimar algún parámetro poblacional \\(\\theta\\), decimos informalmente que el estimador es preciso si su distribución de muestreo está muy concentrada alrededor del valor \\(\\theta\\) que queremos estimar. Si la distribución de muestreo está concentrada en un conjunto muy grande o muy disperso, quiere decir que con alta probabilidad cuando obtengamos nuestra muestra y calculemos nuestra estimación, el resultado estará lejano del valor poblacional que nos interesa estimar. Veamos qué pasa cuando hacemos la muestra más grande en nuestro ejemplo: replicaciones_2 <- replicar_muestreo(marco_casas, m = 1500, n = 250) Graficamos las dos distribuciones de muestreo juntas, y vemos cómo con mayor muestra obtenemos un estimador más preciso, y sin considerar el costo, preferimos el estimador mejor concentrado alrededor del valor que buscamos estimar. library(patchwork) graf_2 <- ggplot(replicaciones_2, aes(x = est_total_millones)) + geom_histogram() + geom_vline(xintercept = total_pob, colour = "red") + xlab("Millones de dólares") + scale_x_continuous(breaks = seq(180, 240, 10), limits = c(180, 240)) graf_1 + graf_2 Observación: a veces este concepto se confunde la distribución poblacional de las \\(X_i\\). Esto es muy diferente. Por ejemplo, en nuestro caso, el histograma de la distribución de valores poblacionales es ggplot(marco_casas, aes(x = precio_miles)) + geom_histogram() que en general no tiene ver mucho en escala o forma con la distribución de muestreo de nuestro estimador del total. Más ejemplos Podemos también considerar muestrear de poblaciones sintéticas o modelos probabilísticos que usamos para modelar poblaciones reales. Por ejemplo, supongamos que tomamos una muestra de tamaño 15 de la distribución uniforme en \\([0,1]\\). Es decir, cada \\(X_i\\) es un valor uniformemente distribuido en \\([0,1]\\), y las \\(X_i\\) se extraen independientemente unas de otras. Consideramos dos estadísticas de interés: La media muestral \\(T_1(X) = \\frac{1}{15}\\sum_{i = 1}^{15} X_i\\) El cuantil 0.75 de la muestra \\(T_2(X) = q_{0.75}(X)\\) ¿Cómo crees que se vean las distribuciones muestrales de estas estadísticas? ¿Alrededor de qué valores crees que concentren? ¿Crees que tendrán mucha o poca dispersión? ¿Qué forma crees que tengan? Para el primer caso hacemos: # simular replicar_muestreo_unif <- function(est = mean, m, n = 15){ valores_est <- map_dbl(1:m, ~ est(runif(n))) tibble(id_muestra = 1:m, estimacion = valores_est) } sim_estimador_1 <- replicar_muestreo_unif(mean, 4000, 15) # graficar aprox de distribución de muestreo ggplot(sim_estimador_1, aes(x = estimacion)) + geom_histogram(bins = 40) + xlim(c(0, 1)) # simular para el máximo cuantil_75 <- function(x) quantile(x, 0.75) sim_estimador_2 <- replicar_muestreo_unif(cuantil_75, 4000, 15) # graficar distribución de muestreo ggplot(sim_estimador_2, aes(x = estimacion)) + geom_histogram(breaks = seq(0, 1, 0.02)) + xlim(c(0, 1)) Supón que tenemos una muestra de 30 observaciones de una distribución uniforme \\([0,b]\\). ¿Qué tan buen estimador de \\(b/2\\) es la media muestral? ¿Cómo lo cuantificarías? ¿Qué tan buen estimador del cuantil 0.8 de la distribución uniforme es el cuantil 0.8 muestral? ¿Qué desventajas notas en este estimador? El error estándar Una primera medida útil de la dispersión de la distribución de muestreo es su desviación estándar: la razón específica tiene qué ver con un resultado importante, el teorema central del límite, que veremos más adelante. En este caso particular, a esta desviación estándar se le llama error estándar: Definición A la desviación estándar de una estadística \\(T\\) le llamamos su error estándar, y la denotamos por \\(\\text{ee}(T)\\). A cualquier estimador de este error estándar lo denotamos como \\(\\hat{\\text{ee}}(T)\\). Este error estándar mide qué tanto varía el estimador \\(T\\) de muestra a muestra. Observación: es importante no confundir el error estándar con la desviación estándar de una muestra (o de la población). En nuestro ejemplo de las uniformes, la desviación estándar de las muestras varía como: map_dbl(1:10000, ~ sd(runif(15))) |> quantile() |> round(2) ## 0% 25% 50% 75% 100% ## 0.11 0.26 0.29 0.31 0.41 Mientras que el error estándar de la media es aproximadamente map_dbl(1:10000, ~ mean(runif(15))) |> sd() ## [1] 0.07439575 y el error estándar del máximo es aproximadamente map_dbl(1:10000, ~ max(runif(15))) |> sd() ## [1] 0.05928675 Como ejercicio para contrastar estos conceptos, puedes considerar: ¿Qué pasa con la desviación estándar de una muestra muy grande de uniformes? ¿Qué pasa con el error estándar de la media muestral de una muestra muy grande de uniformes? Ejemplo: valor de casas Consideramos el error estándar del estimador del total del inventario vendido, usando una muestra de 250 con el estimador del total que describimos arriba. Como aproximamos con simulación la distribución de muestreo, podemos hacer: ee_2 <- replicaciones_2 |> pull(est_total_millones) |> sd() round(ee_2, 1) ## [1] 5.2 que está en millones de pesos y cuantifica la dispersión de la distribución de muestreo del estimador del total. Para tamaño de muestra 100, obtenemos más dispersión: ee_1 <- replicaciones_1 |> pull(est_total_millones) |> sd() round(ee_1, 1) ## [1] 8.9 Nótese que esto es muy diferente, por ejemplo, a la desviación estándar poblacional o de una muestra. Estas dos cantidades miden la variabilidad del estimador del total. Calculando la distribución de muestreo En los ejemplos anteriores usamos simulación para obtener aproximaciones de la distribución de muestreo de algunos estimadores. También es posible: Hacer cálculos exactos a partir de modelos probabilísticos. Hacer aproximaciones asintóticas para muestras grandes (de las cuales la más importante es la que da el teorema central del límite). En los ejemplos de arriba, cuando muestreamos de la poblaciones, extrajimos las muestras de manera aproximadamente independiente. Cada observación \\(X_i\\) tiene la misma distribución y las \\(X_i\\)’s son independientes. Este tipo de diseños aleatorizados es de los más simples, y se llama muestreo aleatorio simple. En general, en esta parte haremos siempre este supuesto: Una muestra es iid (independiente e idénticamente distribuida) si es es un conjunto de observaciones \\(X_1,X_2, \\ldots X_n\\) independientes, y cada una con la misma distribución. En términos de poblaciones, esto lo logramos obteniendo cada observación de manera aleatoria con el mismo procedimiento. En términos de modelos probabilísticos, cada \\(X_i\\) se extrae de la misma distribución fija \\(F\\) (que pensamos como la “población”) de manera independiente. Esto lo denotamos por \\(X_i \\overset{iid}{\\sim} F.\\) Ejemplo Si \\(X_1, X_2, \\ldots X_n\\) es una muestra de uniformes independientes en \\([0,1]\\), ¿cómo calcularíamos la distribución de muestreo del máximo muestra \\(T_2 = \\max\\)? En este caso, es fácil calcular su función de distribución acumulada de manera exacta: \\[F_{\\max}(x) = P(\\max\\{X_1,X_2,\\ldots X_n\\} \\leq x)\\] El máximo es menor o igual a \\(x\\) si y sólo si todas las \\(X_i\\) son menores o iguales a \\(x\\), así que \\[F_{\\max} (x) = P(X_1\\leq x, X_2\\leq x, \\cdots, X_n\\leq x)\\] como las \\(X_i\\)’s son independientes entonces \\[F_{\\max}(x) = P(X_1\\leq x)P(X_2\\leq x)\\cdots P(X_n\\leq x) = x^n\\] para \\(x\\in [0,1]\\), pues para cada \\(X_i\\) tenemos \\(P(X_i\\leq x) = x\\). Así que no es necesario usar simulación para conocer esta distribución de muestreo. Derivando esta distribución acumulada obtenemos su densidad, que es \\[f(x) = nx^{n-1}\\] para \\(x\\in [0,1]\\), y es cero en otro caso. Si comparamos con nuestra simulación: teorica <- tibble(x = seq(0, 1 ,0.001)) |> mutate(f_dens = 15 * x^14) sim_estimador_3 <- replicar_muestreo_unif(max, 4000, 15) ggplot(sim_estimador_3) + geom_histogram(aes(x = estimacion), breaks = seq(0, 1, 0.02)) + xlim(c(0.5, 1)) + # el histograma es de ancho 0.02 y el número de simulaciones 4000 geom_line(data = teorica, aes(x = x, y = (4000 * 0.02) * f_dens), colour = "red", linewidth = 1.3) Y vemos que con la simulación obtuvimos una buena aproximación Nota: ¿cómo se relaciona un histograma con la función de densidad que genera los datos? Supón que \\(f(x)\\) es una función de densidad, y obtenemos un número \\(n\\) de simulaciones independientes. Si escogemos un histograma de ancho \\(\\Delta\\), ¿cuántas observaciones esperamos que caigan en un intervalo \\(I = [a - \\Delta/2, a + \\Delta/2]\\)?. La probabilidad de que una observación caiga en \\(I\\) es igual a \\[P(X\\in I) = \\int_I f(x)\\,dx = \\int_{a - \\Delta/2}^{a + \\Delta/2} f(x)\\,dx \\approx f(a) \\text{long}(I) = f(a) \\Delta\\] para \\(\\Delta\\) chica. Si nuestra muestra es de tamaño \\(n\\), el número esperado de observaciones que caen en \\(I\\) es entonces \\(nf(a)\\Delta\\). Eso explica el ajuste que hicimos en la gráfica de arriba. Otra manera de hacer es ajustando el histograma: si en un intervalo el histograma alcanza el valor \\(y\\), \\[f(a) = \\frac{y}{n\\Delta}\\] teorica <- tibble(x = seq(0, 1 ,0.001)) |> mutate(f_dens = 15*x^{14}) ggplot(sim_estimador_3) + geom_histogram(aes(x = estimacion, y = after_stat(density)), breaks = seq(0, 1, 0.02)) + xlim(c(0.5, 1)) + # el histograma es de ancho 0.02 y el número de simulaciones 4000 geom_line(data = teorica, aes(x = x, y = f_dens), colour = "red", size = 1.3) Ejemplo Supongamos que las \\(X_i\\)’s son independientes y exponenciales con tasa \\(\\lambda > 0\\). ¿Cuál es la distribución de muestreo de la suma \\(S = X_1 + \\cdots + X_n\\)? Sabemos que la suma de exponenciales independientes es una distribución gamma con parámetros \\((n, \\lambda)\\), y esta es la distribución de muestreo de nuestra estadística \\(S\\) bajo las hipótesis que hicimos. Podemos checar este resultado con simulación, por ejemplo para una muestra de tamaño \\(n=15\\) con \\(\\lambda = 1\\): replicar_muestreo_exp <- function(est = mean, m, n = 150, lambda = 1){ valores_est <- map_dbl(1:m, ~ est(rexp(n, lambda))) tibble(id_muestra = 1:m, estimacion = valores_est) } sim_estimador_1 <- replicar_muestreo_exp(sum, 4000, n = 150) teorica <- tibble(x = seq(0, 35, 0.001)) |> mutate(f_dens = dgamma(x, shape = 150, rate = 1)) # graficar aprox de distribución de muestreo ggplot(sim_estimador_1) + geom_histogram(aes(x = estimacion, y = ..density..), bins = 35) + geom_line(data = teorica, aes(x = x, y = f_dens), colour = "red", size = 1.2) Teorema central del límite Si consideramos los ejemplos de arriba donde tratamos con estimadores basados en una suma, total o una media —y en menor medida cuantiles muestrales—, vimos que las distribución de muestreo de las estadísticas que usamos tienden a tener una forma común. Estas son manifestaciones de una regularidad estadística importante que se conoce como el teorema central del límite: las distribuciones de muestreo de sumas y promedios son aproximadamente normales cuando el tamaño de muestra es suficientemente grande. Teorema central del límite Si \\(X_1,X_2, \\ldots, X_n\\) son independientes e idénticamente distribuidas con media \\(\\mu\\) y desviación estándar \\(\\sigma\\) finitas. Si el tamaño de muestra \\(n\\) es grande, entonces la distribución de muestreo de la media \\[\\bar{X} = \\frac{X_1 + X_2 +\\cdots + X_n}{n}\\] es aproximadamente normal con media \\(\\mu\\) y desviación estándar \\(\\sigma/\\sqrt{n}\\), que escribimos como \\[\\bar{X} \\xrightarrow{} \\mathsf{N}\\left( \\mu, \\frac{\\sigma}{\\sqrt{n}} \\right)\\] Adicionalmente, la distribución de la media estandarizada converge a una distribución normal estándar cuando \\(n\\) es grande: \\[\\sqrt{n} \\, \\left( \\frac{\\bar{X}-\\mu}{\\sigma} \\right) \\xrightarrow{} \\mathsf{N}(0, 1)\\] El error estándar de \\(\\bar{X}\\) es \\(\\text{ee}(\\bar{X}) = \\frac{\\sigma}{\\sqrt{n}}\\). Si tenemos una muestra, podemos estimar \\(\\sigma\\) con de la siguiente forma: \\[\\hat{\\sigma} =\\sqrt{\\frac{1}{n}\\sum_{i=1}^n (X_i - \\bar{X})^2}\\] o el más común (que explicaremos más adelante) \\[\\hat{s} = \\sqrt{\\frac{1}{n-1}\\sum_{i=1}^n (X_i - \\bar{X})^2}\\] Este hecho junto con el teorema del límite central nos dice cuál es la dispersión, y cómo se distribuyen las posibles desviaciones de la media muestral alrededor de la verdadera media poblacional. ¿Qué tan grande debe ser \\(n\\). Depende de cómo es la población. Cuando la población tiene una distribución muy sesgada, por ejemplo, \\(n\\) típicamente necesita ser más grande que cuando la población es simétrica si queremos obtener una aproximación “buena”. En algunos textos se afirma que \\(n\\geq 30\\) es suficiente para que la aproximación del Teorema central del límite (TCL) sea buena siempre y cuando la distribución poblacional no sea muy sesgada. Esta regla es más o menos arbitraria y es mejor no confiarse, pues fácilmente puede fallar. En la práctica es importante checar este supuesto, por ejemplo usando remuestreo (que veremos más adelante) Revisa los ejemplos que hemos visto hasta ahora (precios de casas, simulaciones de uniformes y exponenciales según las distintas estadísticas que consideramos). ¿Qué distribuciones de muestreo parecen tener una distribución normal? ¿Cómo juzgamos si estas distribuciones están cerca o lejos de una distribución normal? Normalidad y gráficas de cuantiles normales Para checar si una distribución de datos dada es similar a la normal, la herramienta mas común en estádística es la gráfica de cuantiles teóricos, que es una generalización de la gráfica de cuantiles que vimos anteriormente. En primer lugar, definimos la función de cuantiles de una distribución teórica, que es análoga a la que definimos para conjuntos de datos: Supongamos que tenemos una distribución acumulada teórica \\(\\Phi\\). Podemos definir el cuantil-\\(f\\) \\(q(f)\\) de \\(\\Phi\\) como el valor \\(q(f)\\) tal que \\[q(f) = \\text{argmin}\\{x \\,| \\, \\Phi(x)\\geq f \\}\\] En el caso de que \\(\\Phi\\) tiene densidad \\(\\phi\\), y su soporte es un intervalo (que puede ser de longitud infinita), entonces podemos también escribir \\(q(f)\\) como el valor único donde acumulamos \\(f\\) de la probabilidad \\[\\int_{-\\infty}^{q(f)} \\phi(x)\\,dx= f\\] Por ejemplo, para una densidad normal, abajo mostramos los cuantiles \\(f=0.5\\) (mediana) y \\(f=0.95\\) densidad_tbl <- tibble(x = seq(0, 10, 0.01)) |> mutate(densidad = dnorm(x, 5, 1)) # qnorm es la función de cuantiles de una normal cuantil_50 <- qnorm(0.50, 5, 1) cuantil_90 <- qnorm(0.95, 5, 1) # graficamos densidad_tbl <- densidad_tbl |> mutate(menor_50 = x >= cuantil_50) |> mutate(menor_90 = x >= cuantil_90) g_normal_50 <- ggplot(densidad_tbl, aes(y = densidad)) + ylab('f(x)') + geom_area(aes(x = x, fill = menor_50)) + geom_line(aes(x = x), alpha = 0.1) + geom_vline(xintercept = cuantil_50) + theme(legend.position = "none") + annotate("text", 4.3, 0.2, label = "50%") + labs(subtitle = paste0("q(0.5)=", round(cuantil_50,1))) g_normal_90 <- ggplot(densidad_tbl, aes(y = densidad)) + ylab('f(x)') + geom_area(aes(x = x, fill = menor_90)) + geom_line(aes(x = x), alpha = 0.1) + geom_vline(xintercept = cuantil_90) + theme(legend.position = "none") + annotate("text", 5.0, 0.2, label = "95%") + labs(subtitle = paste0("q(0.95)=", round(cuantil_90,1))) g_normal_50 + g_normal_90 Como todas las distribuciones normales tienen la misma forma, y para obtener una de otra solo basta reescalar y desplazar, para calcular los cuantiles de una variable con distribución normal \\(\\mathsf{N}(\\mu, \\sigma)\\) sólo tenemos que saber los cuantiles de la distribución normal estándar \\(\\mathsf{N}(0,1)\\) y escalarlos apropiadamente por su media y desviación estándar \\[q(f, \\mu, \\sigma) = \\mu + \\sigma q(f, 0, 1)\\] Puedes demostrar esto sin mucha dificultad empezando con \\(P(X\\leq q) = f\\) y estandarizando: \\[P(X\\leq q(f, \\mu, \\sigma)) = f \\implies P\\left (Z\\leq \\frac{q(f,\\mu,\\sigma) - \\mu}{\\sigma}\\right)=f\\] y esto implica que \\[q(f, 0, 1) = \\frac{q(f,\\mu,\\sigma) - \\mu}{\\sigma} \\implies q(f, \\mu, \\sigma) = \\mu + \\sigma q(f, 0, 1)\\] De modo que si graficáramos los cuantiles de una distribución \\(\\mathsf{N}(\\mu, \\sigma)\\) contra los cuantiles de una distribución \\(\\mathsf{N}(0,1)\\), estos cuantiles aparecen en una línea recta: comparacion_tbl <- tibble(f = seq(0.01, 0.99, 0.01)) |> mutate(cuantiles_normal = qnorm(f, 5, 3), cuantiles_norm_estandar = qnorm(f, 0, 1)) ggplot(comparacion_tbl, aes(cuantiles_norm_estandar, cuantiles_normal)) + geom_point() Ahora supongamos que tenemos una muestra \\(X_1, \\ldots, X_n\\). ¿Cómo podemos checar si estos datos tienen una distribución aproximadamente normal? Si la muestra tiene una distribución aproximadamente \\(\\mathsf{N}(\\mu, \\sigma)\\), entonces sus cuantiles muestrales y los cuantiles respectivos de la normal estándar están aproximadamente en una línea recta. Primero veamos un ejemplo donde los datos son generados según una normal. set.seed(21) muestra <- tibble(x_1 = rnorm(60, 10, 3), x_2 = rgamma(60, 2, 5)) graf_1 <- ggplot(muestra, aes(sample = x_1)) + geom_qq(distribution = stats::qnorm) + geom_qq_line(colour = "red") graf_2 <- ggplot(muestra, aes(sample = x_2)) + geom_qq(distribution = stats::qnorm) + geom_qq_line(colour = "red") graf_1 + graf_2 ¿Cuáles son los datos aproximadamente normales? ¿Cómo interpretas las desviaciones de la segunda gráfica en términos de la forma de la distribución normal? Prueba de hipótesis de normalidad Para interpretar las gráficas de cuantiles normales se requiere práctica, pues claramente los datos, aún cuando provengan de una distribución normal, no van a caer justo sobre una línea recta y observaremos variabilidad. Esto no descarta necesariamente que los datos sean aproximadamente normales. Con la práctica, generalmente esta gráfica nos da una buena indicación si el supuesto de normalidad es apropiado. Sin embargo, podemos hacer una prueba de hipótesis formal de normalidad si quisiéramos. La hipótesis nula es la siguiente: Los datos provienen de una distribución normal, y las desviaciones que observamos de una línea recta se deben a variación muestral. Podemos generar datos nulos tomando la media y desviación estándar muestrales, y generando muestras normales \\(\\mathsf{N}(\\bar{x}, s)\\). Usamos el lineup, produciendo datos bajo la hipótesis nula y viendo si podemos distinguir los datos. Por ejemplo: library(nullabor) lineup_normal <- lineup(null_dist("x_2", dist = "normal"), muestra) ggplot(lineup_normal, aes(sample = x_2)) + geom_qq(distribution = stats::qnorm) + geom_qq_line(colour = "red") + facet_wrap(~ .sample) En esta gráfica claramente rechazaríamos la hipótesis de normalidad. Sin embargo, para la primera muestra, obtenemos: lineup_normal <- lineup(null_dist("x_1", dist = "normal"), muestra) ggplot(lineup_normal, aes(sample = x_1)) + geom_qq(distribution = stats::qnorm) + geom_qq_line(colour = "red") + facet_wrap(~ .sample) Los datos verdaderos están en attr(lineup_normal, "pos") ## [1] 4 Ejemplo Consideremos el problema de estimar el total poblacional de los precios de las casas que se vendieron. El estimador que usamos fue la suma muestral expandida por un factor. Vamos a checar qué tan cerca de la normalidad está la distribución de meustreo de esta estadística (\\(n=250\\)): replicaciones_2 ## # A tibble: 1,500 × 4 ## id_muestra total_muestra factor_exp est_total_millones ## <chr> <dbl> <dbl> <dbl> ## 1 1 47089. 4.58 215. ## 2 2 45654. 4.58 209. ## 3 3 43973. 4.58 201. ## 4 4 45665. 4.58 209. ## 5 5 43551. 4.58 199. ## 6 6 46066. 4.58 211. ## 7 7 46626. 4.58 213. ## 8 8 47944. 4.58 219. ## 9 9 45381. 4.58 208. ## 10 10 46519. 4.58 213. ## # ℹ 1,490 more rows ggplot(replicaciones_2, aes(sample = est_total_millones)) + geom_qq(alpha = 0.3) + geom_qq_line(colour = "red") Y vemos que en efecto el TCL aplica en este ejemplo, y la aproximación es buena. Aunque la población original es sesgada, la descripción de la distribución de muestreo es sorprendemente compacta: La distribución de muestreo de nuestro estimador del total \\(\\hat{t}\\) es aproximadamente normal con media \\(\\bar{x}\\) y desviación estándar \\(s\\), donde: mu <- mean(replicaciones_2$est_total_millones) s <- sd(replicaciones_2$est_total_millones) c(mu = mu, s = s) |> round(2) ## mu s ## 209.90 5.24 Estas cantidades están en millones de dólares. Ejemplo Supongamos que queremos calcular la probabilidad que la suma de 30 variables uniformes en \\([0,1]\\) independientes sea mayor que 18. Podríamos aproximar esta cantidad usando simulación. Otra manera de aproximar esta cantidad es con el TCL, de la siguiente forma: Si \\(S=X_1 + X_2 + X_{30}\\), entonces la media de \\(S\\) es 15 (¿cómo se calcula?) y su desviación estándar es \\(\\sqrt{\\frac{30}{12}}\\). La suma es entonces aproximadamente \\(\\mathsf{N}\\left(15, \\sqrt{\\frac{30}{12}}\\right)\\). Entonces \\[P(S > 18) = P \\left (\\frac{S - 15}{\\sqrt{\\frac{30}{12}}} > \\frac{18 - 15}{\\sqrt{\\frac{30}{12}}}\\right) \\approx P(Z > 1.897)\\] donde \\(Z\\) es normal estándar. Esta última cantidad la calculamos usando la función de distribución de la normal estándar, y nuestra aproximación es 1 - pnorm(1.897) ## [1] 0.02891397 Podemos checar nuestro cálculo usando simulación: tibble(n_sim = 1:100000) |> mutate(suma = map_dbl(n_sim, ~ sum(runif(30)))) |> summarise(prob_may_18 = mean(suma > 18), .groups = "drop") ## # A tibble: 1 × 1 ## prob_may_18 ## <dbl> ## 1 0.0280 Y vemos que la aproximación normal es buena para fines prácticos. Usando simulaciones haz un histograma que aproxime la distribución de muestreo de \\(S\\). Haz una gráfica de cuantiles normales para checar la normalidad de esta distribución. Ejemplo Cuando el sesgo de la distribución poblacional es grande, puede ser necesario que \\(n\\) sea muy grande para que la aproximación normal sea aceptable para el promedio o la suma. Por ejemplo, si tomamos una gamma con parámetro de forma chico, \\(n = 30\\) no es suficientemente bueno, especialmente si quisiéramos aproximar probabilidades en las colas de la distribución: sims_gamma <- map_df(1:2000, ~ tibble(suma = mean(rgamma(30, 0.1, 1))), .id = "n_sim") ggplot(sims_gamma, aes(x = suma)) + geom_histogram() Más del Teorema central del límite El teorema central del límite aplica a situaciones más generales que las del enunciado del teorema básico. Por ejemplo, aplica a poblaciones finitas (como vimos en el ejemplo de las casas) bajo muestreo sin reemplazo, y aplica también a otras estadísticas como los cuantiles muestrales. Es importante notar que la calidad de la aproximación del TCL depende de características de la población y también del tamaño de muestra \\(n\\). Para ver si el TCL aplica, podemos hacer ejercicios de simulación bajo diferentes supuestos acerca de la población. También veremos más adelante, con remuestreo, maneras de checar si es factible el TCL dependiendo del análisis de una muestra dada que tengamos. El TCL era particularmente importante en la práctica antes de que pudiéramos hacer simulación por computadora. Era la única manera de aproximar y entender la distribución muestral fuera de cálculos analíticos (como los que hicimos para el máximo de un conjunto de uniformes, por ejemplo). Hoy en día, veremos que podemos hacer simulación para obtener respuestas más exactas, particularmente en la construcción de intervalos de confianza, por ejemplo. Dependemos menos de resultados asintóticos, como el TCL. Cuando aproximamos una distribución discreta mediante la distribución normal, conviene hacer correcciones de continuidad, como se explica en (Chihara and Hesterberg 2018), 4.3.2. Referencias "],["intervalos-de-confianza-y-remuestreo.html", "Sección 5 Intervalos de confianza y remuestreo Ejemplo introductorio La idea del bootstrap El principio de plug-in Discusión: propiedades de la distribución bootstrap Error estándar bootstrap e intervalos normales Ejemplo: inventario de casas vendidas Calibración de intervalos de confianza Interpretación de intervalos de confianza Sesgo Intervalos bootstrap de percentiles Bootstrap para dos muestras Bootstrap y otras estadísticas Bootstrap y estimadores complejos: tablas de perfiles Bootstrap y muestras complejas Bootstrap en R Conclusiones y observaciones", " Sección 5 Intervalos de confianza y remuestreo 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: 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 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. Pero no tenemos la distribución poblacional. Sin embargo, podemos estimar la distribución poblacional con nuestros valores muestrales. Mundo bootstrap 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. Evaluamos nuestra estadística en cada una de estas remuestras, a estas les llamamos replicaciones bootstrap. A la distribución de las replicaciones le llamamos distribución bootstrap o distribución de remuestreo del estimador. 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: set.seed(2112) poblacion_casas <- read_csv("data/casas.csv") muestra <- slice_sample(poblacion_casas, n = 200, replace = TRUE) mean(muestra$precio_miles) ## [1] 179.963 Esta muestra nos da nuestro estimador de la distribución poblacional: 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: 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: media <- mean(muestra$precio_miles) media ## [1] 179.963 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: 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: 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!): limites_ic <- quantile(media_muestras, c(0.05, 0.95)) |> round() limites_ic ## 5% 95% ## 171 189 Presentaríamos nuestro resultado como sigue: nuestra estimación puntual de la mediana es 180, con un intervalo de confianza del 90% de (171, 189) 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: ee_boot <- sd(media_muestras) round(ee_boot, 2) ## [1] 5.39 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 muestra <- sample(1:20, 5) muestra ## [1] 6 10 7 3 14 Una remuestra se obtiene: sample(muestra, size = 5, replace = TRUE) ## [1] 7 3 7 10 6 Nótese que algunos valores de la muestra original pueden aparecer varias veces, y otros no aparecen del todo. 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: Remuestreo para una población. Dada una muestra de tamaño \\(n\\) de una población, Obtenemos una remuestra de tamaño \\(n\\) con reemplazo de la muestra original y calculamos la estadística de interés. Repetimos este remuestreo muchas veces (por ejemplo, 10,000). 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: Podemos estimar 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 and Hesterberg (2018) muestran lo que acabamos de describir: Figure 5.1: Mundo Real Figure 5.2: Mundo Bootstrap 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. ¿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}\\), como en Efron and Tibshirani (1993). 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 (2013), o aquí), \\(\\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: 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") ## # A tibble: 1 × 2 ## prop_negro n ## <dbl> <int> ## 1 0.247 300 ¿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: # 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: 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()) ## # A tibble: 4 × 2 ## name value ## <chr> <dbl> ## 1 media 0.247 ## 2 ee 0.025 ## 3 cuantil_75 0.263 ## 4 cuantil_25 0.23 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 nuestro ejemplo, podemos ver varias muestras (por ejemplo 20) de tamaño 200, y vemos cómo se ve la aproximación a la distribución de la población: Podemos calcular las distribuciones de remuestreo (bootstrap) para cada muestra, y compararlas con la distribución de muestreo real. # 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() 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: poblacion_casas |> summarise(media = mean(precio_miles), .groups = "drop") ## # A tibble: 1 × 1 ## media ## <dbl> ## 1 183. 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í. 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. Entonces, ¿cuántas muestras bootstrap? 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 and Tibshirani (1993)). Cuando se busca estimar error estándar Chihara and Hesterberg (2018) 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. 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\\mathsf{ee}(\\hat{\\theta}), \\hat{\\theta} + 2\\mathsf{ee}(\\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: # 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) ## [1] 0.25 Podemos graficar su distribución bootstrap —la cual simulamos arriba—. 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: media_boot <- prop_negro_tbl |> pull(prop_negro) |> mean() media_boot - prop_hat ## [1] 0.0004393333 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 ee_boot <- prop_negro_tbl |> pull(prop_negro) |> sd() ee_boot ## [1] 0.02485138 y construimos un intervalo de confianza del 95%: intervalo_95 <- c(prop_hat - 2 * ee_boot, prop_hat + 2 * ee_boot) intervalo_95 |> round(2) ## [1] 0.2 0.3 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 0.2 y 0.3. 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\\): # 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 total_est <- calc_estimador_casas(muestra_casas) sesgo <- mean(totales_boot$total_boot) - total_est sesgo ## [1] 110.0851 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: ee_boot <- sd(totales_boot$total_boot) sesgo_relativo <- sesgo / ee_boot sesgo_relativo ## [1] 0.01522088 De forma que procedemos a construir intervalos de confianza como sigue : c(total_est - 2*ee_boot, total_est + 2*ee_boot) ## [1] 203366.6 232296.6 Que está en miles de dólares. En millones de dólares, este intervalo es: intervalo_total <- c(total_est - 2*ee_boot, total_est + 2*ee_boot) / 1000 intervalo_total |> round(1) ## [1] 203.4 232.3 Así que con 95% de confianza el verdadero total del valor de las casas vendidas está entre 203 y 232 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: 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") 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 total <- sum(poblacion_casas$precio_miles) sims_tbl |> summarise(cobertura = mean(cubre)) ## # A tibble: 1 × 1 ## cobertura ## <dbl> ## 1 0.96 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.). 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. Verificar la cobertura de nuestros intervalos de confianza por medio simulación está bien estudiado para algunos casos. Por ejemplo, cuando trabajamos con estimaciones para poblaciones teóricas. En general sabemos que los procedimientos funcionan bien en casos: con distribuciones simétricas que tengan colas no muy largas; estimación de proporciones donde no tratamos con casos raros o casos seguros (probabilidades cercanas a 0 o 1). Interpretación de intervalos de confianza Como hemos visto, “intervalo de confianza” (de 90% de confianza, por ejemplo) es un término frecuentista, que significa: Cada muestra produce un intervalo distinto. Para el 90% de las muestras posibles, el intervalo cubre al valor poblacional. La afirmación es sobre el intervalo y el mecanismo para construirlo. Así que con alta probabilidad, el intervalo contiene el valor poblacional. Intervalos más anchos nos dan más incertidumbre acerca de dónde está el verdadero valor poblacional (y al revés para intervalos más angostos). Existen también “intervalos de credibilidad” (de 90% de probabilidad, por ejemplo), que se interpetan de forma bayesiana: Con 90% de probabilidad (relativamente alta), creemos que el valor poblacional está dentro del intervalo de credibilidad. Esta última interpretación es más natural. Obsérvese que para hablar de intervalos de confianza frecuentista tenemos que decir: Este intervalo particular cubre o no al verdadero valor, pero nuestro procedimiento produce intervalos que contiene el verdadero valor para el 90% de las muestras. Esta es una interpretación relativamente débil, y muchos intervalos poco útiles pueden satisfacerla. La interpretación bayesiana es más natural porque expresa más claramente incertidumbre acerca del valor poblacional. Por otro lado, La interpretación frecuentista nos da maneras empíricas de probar si los intervalos de confianza están bien calibrados o no: es un mínimo que “intervalos del 90%” deberían satisfacer. Así que tomamos el punto de vista bayesiano en la intepretación, pero buscamos que nuestros intervalos cumplan o aproximen bien garantías frecuentistas (discutimos esto más adelante). Los intervalos que producimos en esta sección pueden interpretarse de las dos maneras. Sesgo Notemos que hemos revisado el sesgo en varias ocasiones, esto es porque algunos estimadores comunes (por ejemplo, cociente de dos cantidades aleatorias) pueden sufrir de sesgo grande, especialmente en el caso de muestras chicas. Esto a su vez afecta la cobertura, pues es posible que nuestros intervalos no tengan “cobertura simétrica”, por ejemplo. Para muchos estimadores, y muestras no muy chicas, esté sesgo tiende a ser poco importante y no es necesario hacer correcciones. Si el tamaño del sesgo es grande comparado con la dispersión de la distribución bootstrap generalmente consideramos que bajo el diseño actual el estimador que estamos usando no es apropiado, y podemos proponer otro estimador u otro procedimiento para construir intervalos (ver Efron and Tibshirani (1993) intervalos BC_{a}), Efron and Tibshirani (1993) sugieren más de 20% de la desviación estándar, mientras que en Chihara and Hesterberg (2018) se sugiere 2% de la desviación estándar. Dependiendo que tan crítico es que los intervalos estén bien calibrados podemos evaluar nuestro problema particular. Intervalos bootstrap de percentiles Retomemos nuestro ejemplo del valor total del precio de las casas. A través de remuestras bootstrap hemos verificado gráficamente que la distribución de remuestreo es ligeramente asimétrica (ver la figura de abajo). Anteriormente hemos calculado intervalos de confianza basados en supuestos normales por medio del error éstandar. Este intervalo está dado por ## [1] 203.4 232.3 y por construcción sabemos que es simétrico con respecto al valor estimado, pero como podemos ver la distribución de muestreo no es simétrica, lo cual podemos confirmar por ejemplo calculando el porcentaje de muestras bootstrap que caen por arriba y por debajo del intervalo construido: ## # A tibble: 1 × 2 ## prop_inf prop_sup ## <dbl> <dbl> ## 1 0.0192 0.026 los cuales se han calculado como el porcentaje de medias bootstrap por debajo (arriba) de la cota inferior (superior), y vemos que no coinciden con el nivel de confianza prestablecido (2.5% para cada extremo). Otra opción común que se usa específicamente cuando la distribución bootstrap no es muy cercana a la normal son los intervalos de percentiles bootstrap: Definición. El intervalo de percentiles bootstrap al 95% de confianza está dado por \\[[q_{0.025}, q_{0.975}]\\] donde \\(q_f\\) es el percentil \\(f\\) de la distribución bootstrap. Otros intervalos comunes son el de 80% o 90% de confianza, por ejemplo, que corresponden a \\([q_{0.10}, q_{0.90}]\\) y \\([q_{0.05}, q_{0.95}]\\). Ojo: intervalos de confianza muy alta (por ejemplo 99.5%) pueden tener mala calibración o ser muy variables en su longitud pues dependen del comportamiento en las colas de la distribución. Para el ejemplo de las casas, calcularíamos simplemente intervalo_95 <- totales_boot |> pull(total_boot) |> quantile(probs = c(0.025, 0.975)) / 1000 (intervalo_95) |> round(1) ## 2.5% 97.5% ## 204.3 232.5 que está en millones de dólares. Nótese que es similar al intervalo de error estándar. Otro punto interesante sobre los intervalos bootstrap de percentiles es que lidian naturalmente con la asímetría de la distribución bootstrap. Ilustramos esto con la distancia de las extremos del intervalo con respecto a la media: abs(intervalo_95 - total_est/1000) ## 2.5% 97.5% ## 13.53912 14.64674 Los intervalos de confianza nos permiten presentar un rango de valores posibles para el parámetro de interés. Esto es una notable diferencia con respecto a presentar sólo un candidato como estimador. Nuestra fuente de información son los datos. Es por esto que si vemos valores muy chicos (grandes) en nuestra muestra, el intervalo se tiene que extender a la izquierda (derecha) para compensar dichas observaciones. Explica por qué cuando la aproximación normal es apropiada, el intervalo de percentiles al 95% es muy similar al intervalo normal de 2 errores estándar. Ejemplo Consideramos los datos de propinas. Queremos estimar la media de cuentas totales para la comida y la cena. Podemos hacer bootstrap de cada grupo por separado: # en este ejemplo usamos rsample, pero puedes # escribir tu propio código library(rsample) propinas <- read_csv("data/propinas.csv") estimador <- function(split, ...){ muestra <- analysis(split) |> group_by(momento) muestra |> summarise(estimate = mean(cuenta_total), .groups = 'drop') |> mutate(term = momento) } intervalo_propinas_90 <- bootstraps(propinas, strata = momento, 1000) |> mutate(res_boot = map(splits, estimador)) |> int_pctl(res_boot, alpha = 0.10) |> mutate(across(where(is.numeric), round, 2)) intervalo_propinas_90 ## # A tibble: 2 × 6 ## term .lower .estimate .upper .alpha .method ## <chr> <dbl> <dbl> <dbl> <dbl> <chr> ## 1 Cena 19.8 20.8 22.0 0.1 percentile ## 2 Comida 15.6 17.1 18.8 0.1 percentile Nota: .estimate es la media de los valores de la estadística sobre las remuestras, no es el estimador original. De la tabla anterior inferimos que la media en la cuenta en la cena es más grande que la de la comida. Podemos graficar agregando los estimadores plugin: estimadores <- propinas |> group_by(momento) |> rename(term = momento) |> summarise(media = mean(cuenta_total)) ggplot(intervalo_propinas_90, aes(x = term)) + geom_linerange(aes(ymin = .lower, ymax = .upper)) + geom_point(data = estimadores, aes(y = media), colour = "red", size = 3) + xlab("Momento") + ylab("Media de cuenta total (dólares)") + labs(subtitle = "Intervalos de 90% para la media") Nótese que el bootstrap lo hicimos por separado en cada momento del día (por eso el argumento strata en la llamada a bootstraps): Justifica el procedimiento de hacer el bootstrap separado para cada grupo. ¿Qué supuestos acerca del muestreo se deben satisfacer? ¿Deben ser muestras aleatorias simples de cada momento del día, por ejemplo? ¿Qué harías si no fuera así, por ejemplo, si se escogieron al azar tickets de todos los disponibles en un periodo? Bootstrap para dos muestras En el ejemplo anterior consideramos cómo hacer bootstrap cuando tenemos muestras independientes. También podemos aplicarlo a estimadores que comparen directamente las dos muestras: Bootstrap para comparar poblaciones. Dadas muestras independientes de tamaños \\(m\\) y \\(n\\) de dos poblaciones: Extraer una remuestra de tamaño \\(m\\) con reemplazo de la primera muestra y una remuestra separada de tamaño \\(n\\) de la segunda muestra. Calcula la estadística que compara los dos grupos (por ejemplo, diferencia de medias) Repetir este proceso muchas veces (por ejemplo, 1000 - 10000). Construir la distribución bootstrap de la estadística. Examinar dispersión, sesgo y forma. Ejemplo Supongamos que queremos comparar directamente la media de la cuenta total en comida y cena. Podemos hacer: estimador_dif <- function(split, ...){ muestra <- analysis(split) |> group_by(momento) muestra |> summarise(estimate = mean(cuenta_total), .groups = "drop") |> pivot_wider(names_from = momento, values_from = estimate) |> mutate(estimate = Cena - Comida, term = "diferencia") } dist_boot <- bootstraps(propinas, strata = momento, 2000) |> mutate(res_boot = map(splits, estimador_dif)) g_1 <- ggplot(dist_boot |> unnest(res_boot), aes(x = estimate)) + geom_histogram(bins = 20) + xlab("Diferencia Comida vs Cena") g_2 <- ggplot(dist_boot |> unnest(res_boot), aes(sample = estimate)) + geom_qq() + geom_qq_line(colour = 'red') g_1 + g_2 Y podemos calcular un intervalo de confianza para la diferencia de medias: dist_boot |> int_pctl(res_boot, alpha = 0.01) |> mutate(across(where(is.numeric), round, 2)) |> select(term, .lower, .upper) ## # A tibble: 1 × 3 ## term .lower .upper ## <chr> <dbl> <dbl> ## 1 diferencia 0.73 6.54 Que nos indica que con alta probabilidad las cuentas son más altas que en la cena que en la comida. La diferencia puede ir de un poco menos de un dólar hasta seis dólares con 99% de confianza. Datos pareados En otros casos, las muestras no son independientes y están pareadas. Por ejemplo, este es un estudio dende a 10 personas una noche se les dio una medicina para dormir y otra noche otra medicina. Se registraron cuántas horas de sueño extra comparados con un día donde no tomaron medicina. dormir <- sleep |> pivot_wider(names_from = group, names_prefix = "medicina_", values_from = extra) dormir ## # A tibble: 10 × 3 ## ID medicina_1 medicina_2 ## <fct> <dbl> <dbl> ## 1 1 0.7 1.9 ## 2 2 -1.6 0.8 ## 3 3 -0.2 1.1 ## 4 4 -1.2 0.1 ## 5 5 -0.1 -0.1 ## 6 6 3.4 4.4 ## 7 7 3.7 5.5 ## 8 8 0.8 1.6 ## 9 9 0 4.6 ## 10 10 2 3.4 En este caso, el bootstrap se hace sobre individuos, y quisiéramos comparar la medición de la medicina_1 con la medicina_2. Usaremos la media de al diferencia entre horas de sueño entre las dos medicinas. Nuestro estimador puntual es: estimador_dif <- dormir |> mutate(dif_2_menos_1 = medicina_2 - medicina_1) |> summarise(dif_media = mean(dif_2_menos_1)) estimador_dif ## # A tibble: 1 × 1 ## dif_media ## <dbl> ## 1 1.58 Esto indica que en promedio duermen hora y media más con la medicina 2 que con la medicina 1. Como hay variabilildad considerable en el número de horas extra de cada medicina dependiendo del individuo, es necesario hacer una intervalo de confianza para descartar que esta diferencia haya aparecido por azar debido a la variación muestral. Nótese que aquí no tenemos estratos, pues solo hay una muestra de individuo con dos mediciones. estimador_dif <- function(split, ...){ muestra <- analysis(split) muestra |> mutate(dif_2_menos_1 = medicina_2 - medicina_1) |> summarise(estimate = mean(dif_2_menos_1), .groups = "drop") |> mutate(term = "diferencia 2 vs 1") } dist_boot <- bootstraps(dormir, 2000) |> mutate(res_boot = map(splits, estimador_dif)) g_1 <- ggplot(dist_boot |> unnest(res_boot), aes(x = estimate)) + geom_histogram(bins = 20) g_2 <- ggplot(dist_boot |> unnest(res_boot), aes(sample = estimate)) + geom_qq() + geom_qq_line(colour = 'red') g_1 + g_2 Nuestro intervalo de percentiles al 90% es de dist_boot |> int_pctl(res_boot, 0.10) ## # A tibble: 1 × 6 ## term .lower .estimate .upper .alpha .method ## <chr> <dbl> <dbl> <dbl> <dbl> <chr> ## 1 diferencia 2 vs 1 1.04 1.57 2.22 0.1 percentile Lo que indica con alta probabilidad que la medicina 2 da entre 1 y 2 horas extras de sueño. Nota que en este ejemplo también podríamos hacer una prueba de hipótesis por permutaciones, suponiendo como hipótesis nula que las dos medicinas son equivalentes. Sin embargo, usualmente es más informativo presentar este tipo de intervalos para estimar la diferencia. Bootstrap y otras estadísticas El bootstrap es una técnica versátil. Un ejemplo son estimadores de razón, que tienen la forma \\[ \\hat{r} = \\frac{\\overline y}{\\overline x}\\] Por ejemplo, ¿cómo haríamos estimación para el procentaje de área area habitable de las casas en relación al tamaño del lote? Una manera de estimar esta cantidad es dividiendo la suma del área habitable de nuestra muestra y dividirlo entre la suma del área de los lotes de nuestra muestra, como en la fórmula anterior. Esta fórmula es más difícil pues tanto numerador como denominador tienen variabilidad, y estas dos cantidades no varían independientemente. Con el bootstrap podemos atacar estos problemas Ejemplo: estimadores de razón Nuestra muestra original es: set.seed(250) casas_muestra <- slice_sample(poblacion_casas, n = 200) El estimador de interés es: estimador_razon <- function(split, ...){ muestra <- analysis(split) muestra |> summarise(estimate = sum(area_habitable_sup_m2) / sum(area_lote_m2), .groups = "drop") |> mutate(term = "% area del lote construida") } Y nuestra estimación puntual es estimacion <- muestra_casas |> summarise(estimate = sum(area_habitable_sup_m2) / sum(area_lote_m2)) estimacion ## # A tibble: 1 × 1 ## estimate ## <dbl> ## 1 0.148 Es decir que en promedio, un poco más de 15% del lote total es ocupado por área habitable. Ahora hacemos bootstrap para construir un intervalo: dist_boot <- bootstraps(casas_muestra, 2000) |> mutate(res_boot = map(splits, estimador_razon)) g_1 <- ggplot(dist_boot |> unnest(res_boot), aes(x = estimate)) + geom_histogram(bins = 20) g_2 <- ggplot(dist_boot |> unnest(res_boot), aes(sample = estimate)) + geom_qq() + geom_qq_line(colour = 'red') g_1 + g_2 En este caso la cola derecha parece tener menos dispersión que una distribución normal. Usamos un intervalo de percentiles para obtener: dist_boot |> int_pctl(res_boot) |> mutate(estimador = estimacion$estimate) |> rename(media_boot = .estimate) |> mutate(bias = media_boot - estimador) |> pivot_longer(is_double) |> mutate(value = round(value, 3)) ## # A tibble: 6 × 4 ## term .method name value ## <chr> <chr> <chr> <dbl> ## 1 % area del lote construida percentile .lower 0.121 ## 2 % area del lote construida percentile media_boot 0.142 ## 3 % area del lote construida percentile .upper 0.159 ## 4 % area del lote construida percentile .alpha 0.05 ## 5 % area del lote construida percentile estimador 0.148 ## 6 % area del lote construida percentile bias -0.006 De modo que en esta zona, entre 12% y 16% de toda el área disponible es ocupada por área habitable: estas son casas que tienen jardines o terrenos, garage relativamente grandes. Ejemplo: suavizadores Podemos usar el bootstrap para juzgar la variabilidad de un suavizador, que consideramos como nuestra estadística: graf_casas <- function(data){ ggplot(data |> filter(calidad_gral < 7), aes(x = area_habitable_sup_m2)) + geom_point(aes(y = precio_m2_miles), alpha = 0.75) + geom_smooth(aes(y = precio_m2_miles), method = "loess", span = 0.7, se = FALSE, method.args = list(degree = 1, family = "symmetric")) } graf_casas(muestra_casas) Podemos hacer bootstrap para juzgar la estabilidad del suavizador: suaviza_boot <- function(x, data){ # remuestreo muestra_boot <- slice_sample(data, prop = 1, replace = T) ajuste <- loess(precio_m2_miles ~ area_habitable_sup_m2, data = muestra_boot, degree = 1, span = 0.7, family = "symmetric") datos_grafica <- tibble(area_habitable_sup_m2 = seq(25, 250, 5)) ajustados <- predict(ajuste, newdata = datos_grafica) datos_grafica |> mutate(ajustados = ajustados) |> mutate(rep = x) } reps <- map(1:10, ~ suaviza_boot(.x, casas_muestra |> filter(calidad_gral < 7))) |> bind_rows() # ojo: la rutina loess no tienen soporte para extrapolación graf_casas(casas_muestra) + geom_line(data = reps, aes(y = ajustados, group = rep), alpha = 1, colour = "red") Donde vemos que algunas cambios de pendiente del suavizador original no son muy interpretables (por ejemplo, para áreas chicas) y alta variabilidad en general en los extremos. Podemos hacer más iteraciones para calcular bandas de confianza: reps <- map(1:200, ~ suaviza_boot(.x, casas_muestra |> filter(calidad_gral < 7))) |> bind_rows() # ojo: la rutina loess no tienen soporte para extrapolación graf_casas(casas_muestra) + geom_line(data = reps, aes(y = ajustados, group = rep), alpha = 0.2, colour = "red") Donde observamos cómo tenemos incertidumbre en cuanto al nivel y forma de las curvas en los extremos de los datos (casas grandes y chicas), lo cual es natural. Aunque podemos resumir para hacer bandas de confianza, mostrar remuestras de esta manera es informativo: por ejempo: vemos cómo es probable también que para casas de menos de 70 metros cuadrados el precio por metro cuadrado no cambia tanto (líneas constantes) Bootstrap y estimadores complejos: tablas de perfiles Podemos regresar al ejemplo de la primera sesión donde calculamos perfiles de los tomadores de distintos tés: en bolsa, suelto, o combinados. Caundo hacemos estos tipos de análisis no es raro que los prefiles tengan variabilidad considerable que es necesario cuantificar. price tea bag tea bag+unpackaged unpackaged promedio p_upscale -0.71 -0.28 0.98 28 p_variable -0.12 0.44 -0.31 36 p_cheap 0.3 -0.53 0.23 2 p_branded 0.62 -0.16 -0.45 25 p_private label 0.72 -0.22 -0.49 5 p_unknown 1.58 -0.58 -1 3 Hacemos bootstrap sobre toda la muestra, y repetimos exactamente el mismo proceso de construción de perfiles: boot_perfiles <- map(1:1000, function(x){ te_boot <- te |> slice_sample(prop = 1, replace = TRUE) calcular_perfiles(te_boot) |> mutate(rep = x) }) |> bind_rows() Ahora resumimos y graficamos, esta vez de manera distinta: resumen_perfiles <- boot_perfiles |> group_by(how, price) |> summarise(perfil_media = mean(perfil), ymax = quantile(perfil, 0.9), ymin = quantile(perfil, 0.10)) resumen_bolsa <- resumen_perfiles |> ungroup() |> filter(how == "tea bag") |> select(price, perfil_bolsa = perfil_media) resumen_perfiles <- resumen_perfiles |> left_join(resumen_bolsa) |> ungroup() |> mutate(price = fct_reorder(price, perfil_bolsa)) ggplot(resumen_perfiles, aes(x = price, y = perfil_media, ymax = ymax, ymin = ymin)) + geom_point(colour = "red") + geom_linerange() + facet_wrap(~how) + coord_flip() + geom_hline(yintercept = 0, colour = "gray") + ylab("Perfil") + xlab("Precio") Nótese una deficiencia clara del bootstrap: para los que compran té suelto, en la muestra no existen personas que desconocen de dónde provienen su té (No sabe/No contestó). Esto produce un intervalo colapsado en 0 que no es razonable. Podemos remediar esto de varias maneras: quitando del análisis los que no sabe o no contestaron, agrupando en otra categoría, usando un modelo, o regularizar usando proporciones calculadas con conteos modificados: por ejemplo, agregando un caso de cada combinación (agregaría 18 personas “falsas” a una muestra de 290 personas). Bootstrap y muestras complejas La necesidad de estimaciones confiables junto con el uso eficiente de recursos conllevan a diseños de muestras complejas. Estos diseños típicamente usan las siguientes técnicas: muestreo sin reemplazo de una población finita, muestreo sistemático, estratificación, conglomerados, ajustes a no-respuesta, postestratificación. Como consecuencia, los valores de la muestra suelen no ser independientes y los análisis de los mismos dependerá del diseño de la muestra. Comenzaremos con definiciones para entender el problema. set.seed(3872999) n_escuelas <- 5000 tipo <- sample(c("rural", "urbano", "indigena"), n_escuelas, replace = TRUE, prob = c(0.3, 0.5, 0.2)) escuela <- tibble(ind_escuela = 1:n_escuelas, tipo, media_tipo = case_when(tipo == "urbano" ~ 550, tipo == "rural" ~ 400, TRUE ~ 350), media_escuela = rnorm(n_escuelas, media_tipo, 30), n_estudiantes = round(rnorm(n_escuelas, 30, 4))) estudiantes <- uncount(escuela, n_estudiantes, .id = "id_estudiante") %>% rowwise() %>% mutate(calif = rnorm(1, media_escuela, 70)) %>% ungroup() Imaginemos que tenemos una población de 5000 escuelas, y queremos estimar la media de las calificaciones de los estudiantes en una prueba. head(estudiantes) ## # A tibble: 6 × 6 ## ind_escuela tipo media_tipo media_escuela id_estudiante calif ## <int> <chr> <dbl> <dbl> <int> <dbl> ## 1 1 urbano 550 561. 1 488. ## 2 1 urbano 550 561. 2 574. ## 3 1 urbano 550 561. 3 456. ## 4 1 urbano 550 561. 4 507. ## 5 1 urbano 550 561. 5 598. ## 6 1 urbano 550 561. 6 527. La primera idea sería tomar una muestra aleatoria (MAS, muestreo aleatorio simple), donde todos los estudiantes tienen igual probabilidad de ser seleccionados. Con estas condiciones el presupuesto alcanza para seleccionar a 60 estudiantes, hacemos esto y calculamos la media. muestra <- slice_sample(estudiantes, n = 60) round(mean(muestra$calif), 2) ## [1] 466.73 Este número es muy cercano a la media verdadera de la población: 466.51, pero esta es una de muchas posibles muestras. medias_mas <- rerun(1000, mean(sample(estudiantes$calif, 60))) %>% flatten_dbl() sd(medias_mas) ## [1] 14.75242 hist_mas <- ggplot(tibble(medias_mas), aes(x = medias_mas)) + geom_histogram(binwidth = 10) + geom_vline(xintercept = mean(estudiantes$calif), color = "red") + xlim(410, 520) qq_mas <- ggplot(tibble(medias_mas), aes(sample = medias_mas)) + geom_qq(distribution = stats::qunif) + ylim(410, 520) hist_mas + qq_mas Algunas de las muestras generan valores alejados de la verdadera media, para minimizar la probabilidad de seleccionar muestras que lleven a estimaciones alejadas del verdadero valor poblacional podríamos tomar muestras más grandes. Pero usualmente los costos limitan el tamaño de muestra. Una alternativa es estratificar, supongamos que sabemos el tipo de cada escuela (urbana, rural o indígena) y sabemos también que la calificación de los estudiantes de escuelas urbanas tiende a ser distinta a las calificaciones que los estudiantes de escuelas rurales o indígenas. En esta caso un diseño más eficiente consiste en tomar muestras independientes dentro de cada estrato. muestra_estrat <- estudiantes %>% group_by(tipo) %>% sample_frac(0.0004) dim(muestra_estrat) ## [1] 60 6 muestrea_estrat <- function(){ muestra <- estudiantes %>% group_by(tipo) %>% sample_frac(0.0004) mean(muestra$calif) } medias_estrat <- rerun(1000, muestrea_estrat()) %>% flatten_dbl() Notamos que la distribución muestral está más concentrada que el caso de MAS, el error estándar se reduce de 14.75 a 10.2 hist_estrat <- ggplot(tibble(medias_estrat), aes(x = medias_estrat)) + geom_histogram(binwidth = 6) + geom_vline(xintercept = mean(estudiantes$calif), color = "red") + xlim(410, 520) qq_estrat <- ggplot(tibble(medias_estrat), aes(sample = medias_estrat)) + geom_qq(distribution = stats::qunif) + ylim(410, 520) hist_estrat + qq_estrat Entonces, la estratificación nos sirve para reducir el error estándar de las estimaciones. Otro procedimiento común en muestreo es introducir conglomerados, a diferencia del muestreo estratificado, el propósito principal de los conglomerados es reducir costos. Veamos cuantas escuelas tendría que visitar en una muestra dada (con diseño estratificado). n_distinct(muestra_estrat$ind_escuela) ## [1] 60 Es fácil ver que visitar una escuela para aplicar solo uno o dos exámenes no es muy eficiente en cuestión de costos. Es por ello que se suelen tomar muestras considerando conglomerados naturales, en este caso la escuela. En nuestro ejemplo es razonable suponer que una parte grande del costo del muestreo sea mandar al examinador a la escuela, y que una vez en la escuela el costo de evaluar a todo sexto, en lugar de a un único alumno, es relativamente bajo. Podemos imaginar que considerando estos costos por visita de escuela nos alcance para visitar 40 escuelas y en cada una examinar a todos los estudiantes. muestra_escuelas <- escuela %>% group_by(tipo) %>% sample_frac(size = 0.008) muestra_cgl <- muestra_escuelas %>% left_join(estudiantes) mean(muestra_cgl$calif) ## [1] 462.5677 muestrea_cgl <- function(){ muestra_escuelas <- escuela %>% group_by(tipo) %>% sample_frac(size = 0.008) muestra_cgl <- muestra_escuelas %>% left_join(estudiantes, by = c("ind_escuela", "tipo")) mean(muestra_cgl$calif) } medias_cgl <- rerun(1000, muestrea_cgl()) %>% flatten_dbl() En este caso, el número de estudiantes examinados es mucho mayor que en MAS y muestreo estratificado, notemos que el número de estudiantes evaluados cambiará de muestra a muestra dependiendo del número de alumnos en las escuelas seleccionadas. sd(medias_cgl) ## [1] 5.337327 hist_cgl <- ggplot(tibble(medias_cgl), aes(x = medias_cgl)) + geom_histogram(binwidth = 6) + geom_vline(xintercept = mean(estudiantes$calif), color = "red") + xlim(410, 520) qq_cgl <- ggplot(tibble(medias_cgl), aes(sample = medias_cgl)) + geom_qq(distribution = stats::qunif) + ylim(410, 520) hist_cgl + qq_cgl Ejemplo: ENIGH La complejidad de los diseños de encuestas conlleva a que el cálculo de errores estándar sea muy complicado, para atacar este problema hay dos técnicas básicas: 1) un enfoque analítico usando linearización, 2) métodos de remuestreo como bootstrap. El incremento en el poder de cómputo ha favorecido los métodos de remuestreo pues la linearización requiere del desarrollo de una fórmula para cada estimación y supuestos adicionales para simplificar. En 1988 Rao and Wu (1988) propusieron un método de bootstrap para diseños estratificados multietápicos con reemplazo de UPMs (Unidades Primarias de Muestreo) que describimos a continuación. ENIGH. Usaremos como ejemplo la Encuesta Nacional de Ingresos y Gastos de los Hogares, ENIGH 2018 (INEGI 2018), esta encuesta usa un diseño de conglomerados estratificado. Antes de proceder a bootstrap debemos entender como se seleccionaron los datos, esto es, el diseño de la muestra: Unidad primaria de muestreo (UPM). Las UPMs están constituidas por agrupaciones de viviendas. Se les denomina unidades primarias pues corresponden a la primera etapa de selección, las unidades secundarias (USMs) serían los hogares. Estratificación. Los estratos se construyen en base a estado, ámbito (urbano, complemento urbano, rural), características sociodemográficas de los habitantes de las viviendas, características físicas y equipamiento. El proceso de estratificación resulta en 888 subestratos en todo el ámbito nacional. La selección de la muestra es independiente para cada estrato, y una vez que se obtiene la muestra se calculan los factores de expansión que reflejan las distintas probabilidades de selección. Después se llevan a cabo ajustes por no respuesta y por proyección (calibración), esta última busca que distintos dominios de la muestra coincidan con la proyección de población de INEGI. concentrado_hogar <- read_csv(here::here("data", "conjunto_de_datos_enigh_2018_ns_csv", "conjunto_de_datos_concentradohogar_enigh_2018_ns", "conjunto_de_datos", "conjunto_de_datos_concentradohogar_enigh_2018_ns.csv")) # seleccionar variable de ingreso corriente hogar <- concentrado_hogar %>% mutate( upm = as.integer(upm), edo = str_sub(ubica_geo, 1, 2) ) %>% select(folioviv, foliohog, est_dis, upm, factor, ing_cor, edad_jefe, edo) %>% group_by(est_dis) %>% mutate(n = n_distinct(upm)) %>% # número de upms por estrato ungroup() hogar ## # A tibble: 74,647 × 9 ## folioviv foliohog est_dis upm factor ing_cor edad_jefe edo n ## <dbl> <dbl> <dbl> <int> <dbl> <dbl> <dbl> <chr> <int> ## 1 100013601 1 2 1 175 76404. 74 10 106 ## 2 100013602 1 2 1 175 42988. 48 10 106 ## 3 100013603 1 2 1 175 580698. 39 10 106 ## 4 100013604 1 2 1 175 46253. 70 10 106 ## 5 100013606 1 2 1 175 53837. 51 10 106 ## 6 100026701 1 2 2 189 237743. 41 10 106 ## 7 100026703 1 2 2 189 32607. 57 10 106 ## 8 100026704 1 2 2 189 169918. 53 10 106 ## 9 100026706 1 2 2 189 17311. 30 10 106 ## 10 100027201 1 2 3 186 120488. 69 10 106 ## # ℹ 74,637 more rows Para el cálculo de estadísticos debemos usar los factores de expansión, por ejemplo el ingreso trimestral total sería: sum(hogar$factor * hogar$ing_cor / 1000) ## [1] 1723700566 y ingreso trimestral medio (miles pesos) sum(hogar$factor * hogar$ing_cor / 1000) / sum(hogar$factor) ## [1] 49.61029 La estimación del error estándar, por otro lado, no es sencilla y requiere usar aproximaciones, en la metodología de INEGI proponen una aproximación con series de Taylor. Figure 5.3: Extracto de estimación de errores de muestreo, ENIGH 2018. Veamos ahora como calcular el error estándar siguiendo el bootstrap de Rao y Wu: En cada estrato se seleccionan con reemplazo \\(m_h\\) UPMs de las \\(n_h\\) de la muestra original. Denotamos por \\(m_{hi}^*\\) el número de veces que se seleccionó la UPM \\(i\\) en el estrato \\(h\\) (de tal manera que \\(\\sum m_{hi}^*=m_h\\)). Creamos una replicación del ponderador correspondiente a la \\(k\\)-ésima unidad (USM) como: \\[d_k^*=d_k \\bigg[\\bigg(1-\\sqrt{\\frac{m_h}{n_h - 1}}\\bigg) + \\bigg(\\sqrt{\\frac{m_h}{n_h - 1}}\\frac{n_h}{m_h}m_{h}^*\\bigg)\\bigg]\\] donde \\(d_k\\) es el inverso de la probabilidad de selección. Si \\(m_h <(n_h -1)\\) todos los pesos definidos de esta manera serán no negativos. Calculamos el peso final \\(w_k^*\\) aplicando a \\(d_k^*\\) los mismos ajustes que se hicieron a los ponderadores originales. Calculamos el estadístico de interés \\(\\hat{\\theta}\\) usando los ponderadores \\(w_k^*\\) en lugar de los originales \\(w_k\\). Repetimos los pasos 1 y 2 \\(B\\) veces para obtener \\(\\hat{\\theta}^{*1},\\hat{\\theta}^{*2},...,\\hat{\\theta}^{*B}\\). Calculamos el error estándar como: \\[\\hat{\\textsf{se}}_B = \\bigg\\{\\frac{\\sum_{b=1}^B[\\hat{\\theta}^*(b)-\\hat{\\theta}^*(\\cdot)]^2 }{B}\\bigg\\}^{1/2}\\] En principio podemos elegir cualquier valor de \\(m_h \\geq 1\\), el más común es elegir \\(m_h=n_h-1\\), en este caso: \\[d_k^*=d_k \\frac{n_h}{n_h-1}m_{hi}^*\\] en este escenario las unidades que no se incluyen en la muestra tienen un valor de cero como ponderador. Si elegimos \\(n_h \\ne n_h-1\\) las unidades que no están en la muestra tienen ponderador distinto a cero, si \\(m_h=n_h\\) el ponderador podría tomar valores negativos. Implementemos el bootstrap de Rao y Wu a la ENIGH, usaremos \\(m_h=n_h-1\\) # creamos una tabla con los estratos y upms est_upm <- hogar %>% distinct(est_dis, upm, n) %>% arrange(upm) hogar_factor <- est_upm %>% group_by(est_dis) %>% # dentro de cada estrato tomamos muestra (n_h-1) sample_n(size = first(n) - 1, replace = TRUE) %>% add_count(est_dis, upm, name = "m_hi") %>% # calculamos m_hi* left_join(hogar, by = c("est_dis", "upm", "n")) %>% mutate(factor_b = factor * m_hi * n / (n - 1)) # unimos los pasos anteriores en una función para replicar en cada muestra bootstrap svy_boot <- function(est_upm, hogar){ m_hi <- est_upm %>% group_split(est_dis) %>% map(~sample(.$upm, size = first(.$n) - 1, replace = TRUE)) %>% flatten_int() %>% plyr::count() %>% select(upm = x, m_h = freq) m_hi %>% left_join(hogar, by = c("upm")) %>% mutate(factor_b = factor * m_h * n / (n - 1)) } set.seed(1038984) boot_rep <- rerun(500, svy_boot(est_upm, hogar)) # Aplicación a ingreso medio wtd_mean <- function(w, x, na.rm = FALSE) { sum(w * x, na.rm = na.rm) / sum(w, na.rm = na.rm) } # La media es: hogar %>% summarise(media = wtd_mean(factor, ing_cor)) ## # A tibble: 1 × 1 ## media ## <dbl> ## 1 49610. Y el error estándar: map_dbl(boot_rep, ~wtd_mean(w = .$factor_b, x = .$ing_cor)) %>% quantile(c(0.025, 0.975)) ## 2.5% 97.5% ## 48742.12 50519.02 El método bootstrap está implementado en el paquete survey y más recientemente en srvyr que es una versión tidy que utiliza las funciones en survey. Podemos comparar nuestros resultados con la implementación en survey. # 1. Definimos el diseño de la encuesta library(survey) library(srvyr) enigh_design <- hogar %>% as_survey_design(ids = upm, weights = factor, strata = est_dis) # 2. Elegimos bootstrap como el método para el cálculo de errores estándar set.seed(7398731) enigh_boot <- enigh_design %>% as_survey_rep(type = "subbootstrap", replicates = 500) # 3. Así calculamos la media enigh_boot %>% srvyr::summarise(mean_ingcor = survey_mean(ing_cor)) ## # A tibble: 1 × 2 ## mean_ingcor mean_ingcor_se ## <dbl> <dbl> ## 1 49610. 459. enigh_boot %>% srvyr::summarise(mean_ingcor = survey_mean(ing_cor, vartype = "ci")) ## # A tibble: 1 × 3 ## mean_ingcor mean_ingcor_low mean_ingcor_upp ## <dbl> <dbl> <dbl> ## 1 49610. 48709. 50512. # por estado enigh_boot %>% group_by(edo) %>% srvyr::summarise(mean_ingcor = survey_mean(ing_cor)) ## # A tibble: 30 × 3 ## edo mean_ingcor mean_ingcor_se ## <chr> <dbl> <dbl> ## 1 10 50161. 942. ## 2 11 46142. 1252. ## 3 12 29334. 1067. ## 4 13 38783. 933. ## 5 14 60541. 1873. ## 6 15 48013. 1245. ## 7 16 42653. 1239. ## 8 17 42973. 1675. ## 9 18 48148. 1822. ## 10 19 68959. 3625. ## # ℹ 20 more rows Resumiendo: El bootstrap de Rao y Wu genera un estimador consistente y aproximadamente insesgado de la varianza de estadísticos no lineales y para la varianza de un cuantil. Este método supone que la seleccion de UPMs es con reemplazo; hay variaciones del estimador bootstrap de Rao y Wu que extienden el método que acabamos de estudiar; sin embargo, es común ignorar este aspecto, por ejemplo Mach et al estudian las propiedades del estimador de varianza bootstrap de Rao y Wu cuando la muestra se seleccionó sin reemplazo. Bootstrap en R Es común crear nuestras propias funciones cuando usamos bootstrap, sin embargo, en R también hay alternativas que pueden resultar convenientes, mencionamos 3: El paquete rsample (forma parte de la colección tidymodels) y tiene una función bootstraps() que regresa un arreglo cuadrangular (tibble, data.frame) que incluye una columna con las muestras bootstrap y un identificador del número y tipo de muestra. Veamos un ejemplo donde seleccionamos muestras del conjunto de datos muestra_computos que contiene 10,000 observaciones. library(rsample) load("data/election_2012.rda") muestra_computos <- slice_sample(election_2012, n = 10000) muestra_computos ## # A tibble: 10,000 × 23 ## state_code state_name state_abbr district_loc_17 district_fed_17 polling_id ## <chr> <chr> <chr> <int> <int> <int> ## 1 15 México MEX 45 40 77364 ## 2 28 Tamaulipas TAM 6 9 126685 ## 3 18 Nayarit NAY 7 2 86587 ## 4 09 Ciudad de M… CDMX 1 1 22214 ## 5 29 Tlaxcala TLAX 10 1 127221 ## 6 13 Hidalgo HGO 5 2 49224 ## 7 07 Chiapas CHPS 15 10 13835 ## 8 19 Nuevo León NL 3 6 90042 ## 9 25 Sinaloa SIN 6 3 113407 ## 10 15 México MEX 22 17 67597 ## # ℹ 9,990 more rows ## # ℹ 17 more variables: section <int>, region <chr>, polling_type <chr>, ## # section_type <chr>, pri_pvem <int>, pan <int>, panal <int>, ## # prd_pt_mc <int>, otros <int>, total <int>, nominal_list <int>, ## # pri_pvem_pct <dbl>, pan_pct <dbl>, panal_pct <dbl>, prd_pt_mc_pct <dbl>, ## # otros_pct <dbl>, winner <chr> Generamos 100 muestras bootstrap, y la función nos regresa un arreglo con 100 renglones, cada uno corresponde a una muestra bootstrap. set.seed(839287482) computos_boot <- bootstraps(muestra_computos, times = 100) computos_boot ## # Bootstrap sampling ## # A tibble: 100 × 2 ## splits id ## <list> <chr> ## 1 <split [10000/3647]> Bootstrap001 ## 2 <split [10000/3623]> Bootstrap002 ## 3 <split [10000/3724]> Bootstrap003 ## 4 <split [10000/3682]> Bootstrap004 ## 5 <split [10000/3696]> Bootstrap005 ## 6 <split [10000/3716]> Bootstrap006 ## 7 <split [10000/3679]> Bootstrap007 ## 8 <split [10000/3734]> Bootstrap008 ## 9 <split [10000/3632]> Bootstrap009 ## 10 <split [10000/3692]> Bootstrap010 ## # ℹ 90 more rows La columna splits tiene información de las muestras seleccionadas, para la primera vemos que de 10,000 observaciones en la muestra original la primera muestra bootstrap contiene 10000-3647=6353. first_computos_boot <- computos_boot$splits[[1]] first_computos_boot ## <Analysis/Assess/Total> ## <10000/3647/10000> Y podemos obtener los datos de la muestra bootstrap con la función as.data.frame() as.data.frame(first_computos_boot) ## # A tibble: 10,000 × 23 ## state_code state_name state_abbr district_loc_17 district_fed_17 polling_id ## <chr> <chr> <chr> <int> <int> <int> ## 1 14 Jalisco JAL 14 14 57620 ## 2 31 Yucatán YUC 7 3 139226 ## 3 14 Jalisco JAL 14 14 57998 ## 4 16 Michoacán MICH 13 3 79390 ## 5 21 Puebla PUE 13 7 101237 ## 6 14 Jalisco JAL 12 12 57145 ## 7 10 Durango DGO 4 1 34549 ## 8 11 Guanajuato GTO 20 10 42863 ## 9 03 Baja Califo… BCS 9 1 5541 ## 10 14 Jalisco JAL 16 16 58584 ## # ℹ 9,990 more rows ## # ℹ 17 more variables: section <int>, region <chr>, polling_type <chr>, ## # section_type <chr>, pri_pvem <int>, pan <int>, panal <int>, ## # prd_pt_mc <int>, otros <int>, total <int>, nominal_list <int>, ## # pri_pvem_pct <dbl>, pan_pct <dbl>, panal_pct <dbl>, prd_pt_mc_pct <dbl>, ## # otros_pct <dbl>, winner <chr> Una de las principales ventajas de usar este paquete es que es eficiente en el uso de memoria. library(pryr) object_size(muestra_computos) ## 1.41 MB object_size(computos_boot) ## 5.49 MB # tamaño por muestra object_size(computos_boot)/nrow(computos_boot) ## 54.92 kB # el incremento en tamaño es << 100 as.numeric(object_size(computos_boot)/object_size(muestra_computos)) ## [1] 3.895024 Adicionalmente incluye funciones para el cálculo de intervalos bootstrap: intervalo_propinas_90 <- bootstraps(propinas, strata = momento, 1000) |> mutate(res_boot = map(splits, estimador)) |> int_pctl(res_boot, alpha = 0.10) El paquete boot está asociado al libro Bootstrap Methods and Their Applications (Davison and Hinkley (1997)) y tiene, entre otras, funciones para calcular replicaciones bootstrap y para construir intervalos de confianza usando bootstrap: calculo de replicaciones bootstrap con la función boot(), intervalos normales, de percentiles y \\(BC_a\\) con la función boot.ci(), intevalos ABC con la función `abc.ci(). El paquete bootstrap contiene datos usados en Efron and Tibshirani (1993), y la implementación de funciones para calcular replicaciones y construir intervalos de confianza: calculo de replicaciones bootstrap con la función bootstrap(), intervalos \\(BC_a\\) con la función bcanon(), intevalos ABC con la función abcnon(). Conclusiones y observaciones El principio fundamental del Bootstrap no paramétrico es que podemos estimar la distribución poblacional con la distribución empírica. Por tanto para hacer inferencia tomamos muestras con reemplazo de la muestra y analizamos la variación de la estadística de interés a lo largo de las remuestras. El bootstrap nos da la posibilidad de crear intervalos de confianza cuando no contamos con fórmulas para hacerlo de manera analítica y sin supuestos distribucionales de la población. Hay muchas opciones para construir intervalos bootstrap, los que tienen mejores propiedades son los intervalos \\(BC_a\\), sin embargo, los más comunes son los intervalos normales con error estándar bootstrap y los intervalos de percentiles de la distribución bootstrap. Antes de hacer intervalos normales vale la pena graficar la distribución bootstrap y evaluar si el supuesto de normalidad es razonable. En cuanto al número de muestras bootstrap se recomienda al menos \\(1,000\\) al hacer pruebas, y \\(10,000\\) o \\(15,000\\) para los resultados finales, sobre todo cuando se hacen intervalos de confianza de percentiles. La función de distribución empírica es una mala estimación en las colas de las distribuciones, por lo que es difícil construir intervalos de confianza (usando bootstrap no paramétrico) para estadísticas que dependen mucho de las colas. O en general para estadísticas que dependen de un número chico de observaciones de una muestra grande. Referencias "],["apéndice-principios-de-visualizacion.html", "Apéndice: Principios de visualizacion Introducción Visualización popular de datos Teoría de visualización de datos Ejemplo: gráfica de Minard", " Apéndice: Principios de visualizacion “The simple graph has brought more information to the data analyst’s mind than any other device.” — John Tukey El cuarteto de Anscombe En 1971 un estadístico llamado Frank Anscombe (fundador del departamento de Estadística de la Universidad de Yale) publicó cuatro conjuntos de dato. Cada uno consiste de 11 observaciones. La peculariedad de estos conjuntos es que tienen las mismas propiedades estadísticas. Sin embargo, cuando analizamos los datos de manera gráfica en un histograma encontramos rápidamente que los conjuntos de datos son muy distintos. Media de \\(x\\): 9 Varianza muestral de \\(x\\): 11 Media de \\(y\\): 7.50 Varianza muestral de \\(y\\): 4.12 Correlación entre \\(x\\) y \\(y\\): 0.816 Línea de regresión lineal: \\(y = 3.00 + 0.500x\\) En la gráfica del primer conjunto de datos, se ve clara una relación lineal simple con un modelo que cumple los supuestos de normalidad. La segunda gráfica (arriba a la derecha) muestra unos datos que tienen una asociación pero definitivamente no es lineal. En la tercera gráfica (abajo a la izquierda) están puntos alineados perfectamente en una línea recta, excepto por uno de ellos. En la última gráfica podemos ver un ejemplo en el cual basta tener una observación atípica para que se produzca un coeficiente de correlación alto aún cuando en realidad no existe una asociación lineal entre las dos variables. El cuarteto de Ascombe inspiró una técnica reciente para crear datos que comparten las mismas propiedades estadísticas al igual que en el cuarteto, pero que producen gráficas muy distintas (Matejka, Fitzmaurice). Introducción La visualización de datos no trata de hacer gráficas “bonitas” o “divertidas”, ni de simplificar lo complejo o ayudar a una persona “que no entiende mucho” a entender ideas complejas. Más bien, trata de aprovechar nuestra gran capacidad de procesamiento visual para exhibir de manera clara aspectos importantes de los datos. El siguiente ejemplo de (Tufte 2006), ilustra claramente la diferencia entre estos dos enfoques. A la izquierda están gráficas (más o menos típicas de Powerpoint) basadas en la filosofía de simplificar, de intentar no “ahogar” al lector con datos. El resultado es una colección incoherente, de bajo contenido, que no tiene mucho qué decir y que es, “indeferente al contenido y la evidencia”. A la derecha está una variación del rediseño de Tufte en forma de tabla, que en este caso particular es una manera eficiente de mostrar claramente los patrones que hay en este conjunto simple de datos. ¿Qué principios son los que soportan la efectividad de esta tabla sobre la gráfica de la derecha? Veremos que hay dos conjuntos de principios importantes: unos relacionados con el diseño y otros con la naturaleza del análisis de datos, independientemente del método de visualización. Visualización popular de datos Publicaciones populares (periódicos, revistas, sitios internet) muchas veces incluyen visualización de datos como parte de sus artículos o reportajes. En general siguen el mismo patrón que en la visión tradicionalista de la estadística: sirven más para divertir que para explicar, tienden a explicar ideas simples y conjuntos chicos de datos, y se consideran como una “ayuda” para los “lectores menos sofisticados”. Casi siempre se trata de gráficas triviales (muchas veces con errores graves) que no aportan mucho a artículos que tienen un nivel de complejidad mucho mayor (es la filosofía: lo escrito para el adulto, lo graficado para el niño). Teoría de visualización de datos Existe teoría fundamentada acerca de la visualización. Después del trabajo pionero de Tukey, los principios e indicadores de Tufte se basan en un estudio de la historia de la graficación y ejercicios de muestreo de la práctica gráfica a lo largo de varias disciplinas (¿cuáles son las mejores gráficas? ¿por qué?) El trabajo de Cleveland es orientado a la práctica del análisis de datos (¿cuáles gráficas nos han ayudado a mostrar claramente los resultados del análisis?), por una parte, y a algunos estudios de percepción visual. En resumen, hablaremos de las siguientes guías: Principios generales del diseño analítico Aplicables a una presentación o análisis completos, y como guía para construir nuevas visualizaciones (Tufte 2006). Principio 1. Muestra comparaciones, contrastes, diferencias. Principio 2. Muestra causalidad, mecanismo, explicación, estructura sistemática. Principio 3. Muestra datos multivariados, es decir, más de una o dos variables. Principio 4. Integra palabras, números, imágenes y diagramas. Principio 5. Describe la totalidad de la evidencia. Muestra fuentes usadas y problemas relevantes. Principio 6. Las presentaciones analíticas, a fin de cuentas, se sostienen o caen dependiendo de la calidad, relevancia e integridad de su contenido. Técnicas de visualización Esta categoría incluye técnicas específicas que dependen de la forma de nuestros datos y el tipo de pregunta que queremos investigar (Tukey (1977), William S. Cleveland (1993), W. S. Cleveland (1994), Tufte (2006)). Tipos de gráficas: cuantiles, histogramas, caja y brazos, gráficas de dispersión, puntos/barras/ líneas, series de tiempo. Técnicas para mejorar gráficas: Transformación de datos, transparencia, vibración, banking 45, suavizamiento y bandas de confianza. Pequeños múltiplos Indicadores de calidad gráfica Aplicables a cualquier gráfica en particular. Estas son guías concretas y relativamente objetivas para evaluar la calidad de una gráfica (Tufte 1986). Integridad Gráfica. El factor de engaño, es decir, la distorsión gráfica de las cantidades representadas, debe ser mínimo. Chartjunk. Minimizar el uso de decoración gráfica que interfiera con la interpretación de los datos: 3D, rejillas, rellenos con patrones. Tinta de datos. Maximizar la proporción de tinta de datos vs. tinta total de la gráfica. For non-data- ink, less is more. For data-ink, less is a bore. Densidad de datos. Las mejores gráficas tienen mayor densidad de datos, que es la razón entre el tamaño del conjunto de datos y el área de la gráfica. Las gráficas se pueden encoger mucho. Percepción visual. Algunas tareas son más fáciles para el ojo humano que otras (W. S. Cleveland 1994). Factor de engaño y Chartjunk El factor de engaño es el cociente entre el efecto mostrado en una gráfica y el efecto correspondiente en los datos. Idealmente, el factor de engaño debe ser 1 (ninguna distorsión). El chartjunk son aquellos elementos gráficos que no corresponden a variación de datos, o que entorpecen la interpretación de una gráfica. Estos son los indicadores de calidad más fáciles de entender y aplicar, y afortunadamente cada vez son menos comunes. Un diseño popular que califica como chartjunk y además introduce factores de engaño es el pie de 3D. En la gráfica de la derecha, podemos ver como la rebanada C se ve más grande que la rebanada A, aunque claramente ese no es el caso (factor de engaño). La razón es la variación en la perspectiva que no corresponde a variación en los datos (chartjunk). Crítica gráfica: Gráfica de pie Todavía elementos que pueden mejorar la comprensión de nuestra gráfica de pie: se trata de la decodificiación que hay que hacer categoría - color - cuantificación. Podemos agregar las etiquetas como se muestra en la serie de la derecha, pero entonces: ¿por qué no mostrar simplemente la tabla de datos? ¿qué agrega el pie a la interpretación? La deficiencias en el pie se pueden ver claramente al intentar graficar más categorías (13) . En el primer pie no podemos distinguir realmente cuáles son las categorías grandes y cuáles las chicas, y es muy difícil tener una imagen mental clara de estos datos. Agregar los porcentajes ayuda, pero entonces, otra vez, preguntamos cuál es el propósito del pie. La tabla de la izquierda hace todo el trabajo (una vez que ordenamos las categrías de la más grande a la más chica). Es posible hacer una gráfica de barras como la de abajo a la izquierda. Hay otros tipos de chartjunk comunes: uno es la textura de barras, por ejemplo. El efecto es la producción de un efecto moiré que es desagradable y quita la atención de los datos, como en la gráfica de barras de abajo. Otro común son las rejillas, como mostramos en las gráficas de la izquierda. Nótese como en estos casos hay efectos ópticos no planeados que degradan la percepción de los patrones en los datos. Pequeños múltiplos y densidad gráfica La densidad de una gráfica es el tamaño del conjunto de datos que se grafica comparado con el área total de la gráfica. En el siguiente ejemplo, graficamos en logaritmo-10 de millones de cabezas de ganado en Francia (cerdos, res, ovejas y caballos). La gráfica de la izquierda es pobre en densidad pues sólo representa 4 datos. La manera más fácil de mejorar la densidad es hacer más chica la gráfica: La razón de este encogimiento es una que tiene qué ver con las oportunidades perdidas de una gráfica grande. Si repetimos este mismo patrón (misma escala, mismos tipos de ganado) para distintos países obtenemos la siguiente gráfica: Esta es una gráfica de puntos. Es útil como sustituto de una gráfica de barras, y es superior en el sentido de que una mayor proporción de la tinta que se usa es tinta de datos. Otra vez, mayor proporción de tinta de datos representa más oportunidades que se pueden capitalizar. Más pequeños múltiplos Los pequeños múltiplos presentan oportunidades para mostrar más acerca de nuestro problema de interés. Consideramos por ejemplo la relación de radiación solar y niveles de ozono: En el ejemplo anterior incluyendo una variable adicional (velocidad del viento) podemos entender más acerca de la relación de radiación solar y niveles de ozono: Tinta de datos Maximizar la proporción de tinta de datos en nuestras gráficas tiene beneficios inmediatos. La regla es: si hay tinta que no representa variación en los datos, o la eliminación de esa tinta no representa pérdidas de significado, esa tinta debe ser eliminada. El ejemplo más claro es el de las rejillas en gráficas y tablas: ¿Por qué usar grises en lugar de negros? La respuesta tiene qué ver con el principio de tinta de datos: si marcamos las diferencias sutil pero claramente, tenemos más oportunidades abiertas para hacer énfasis en lo que nos interesa: a una gráfica o tabla saturada no se le puede hacer más - es difícil agregar elementos adicionales que ayuden a la comprensión. Si comenzamos marcando con sutileza, entonces se puede hacer más. Los mapas geográficos son un buen ejemplo de este principio. El espacio en blanco es suficientemente bueno para indicar las fronteras en una tabla, y facilita la lectura: Para un ejemplo del proceso de rediseño de una tabla, ver aquí. Finalmente, podemos ver un ejemplo que intenta incorporar los elementos del diseño analítico, incluyendo pequeños múltiplos: Decoración Percepción de escala Entre la percepción visual y la interpretación de una gráfica están implícitas tareas visuales específicas que las personas debemos realizar para ver correctamente la gráfica. En la década de los ochenta, William S. Cleveland y Robert McGill realizaron algunos experimentos identificando y clasificando estas tareas para diferentes tipos de gráficos (William S. Cleveland and McGill 1984). En estos, se le pregunta a la persona que compare dos valores dentro de una gráfica, por ejemplo, en dos barras en una gráfica de barras, o dos rebanadas de una gráfica de pie. Los resultados de Cleveland y McGill fueron replicados por Heer y Bostock en 2010 y los resultados se muestran en las gráficas de la derecha: Ejemplo: gráfica de Minard Concluimos esta sección con una gráfica que, aunque poco común, ejemplifica los principios de una buena gráfica, y es reconocida como una de las mejores visualizaciones de la historia. Una gráfica excelente, presenta datos interesantes de forma bien diseñada: es una cuestión de fondo, de diseño, y estadística… [Se] compone de ideas complejas comunicadas con claridad, precisión y eficiencia. … [Es] lo que da al espectador la mayor cantidad de ideas, en el menor tiempo, con la menor cantidad de tinta, y en el espacio más pequeño. … Es casi siempre multivariado. … Una excelente gráfica debe decir la verdad acerca de los datos. (Tufte, 1983) La famosa visualización de Charles Joseph Minard de la marcha de Napoleón sobre Moscú, ilustra los principios de una buena gráfica. Tufte señala que esta imagen “bien podría ser el mejor gráfico estadístico jamás dibujado”, y sostiene que “cuenta una historia rica y coherente con sus datos multivariados, mucho más esclarecedora que un solo número que rebota en el tiempo”. Se representan seis variables: el tamaño del ejército, su ubicación en una superficie bidimensional, la dirección del movimiento del ejército y la temperatura en varias fechas durante la retirada de Moscú”. Hoy en día Minard es reconocido como uno de los principales contribuyentes a la teoría de análisis de datos y creación de infografías con un fundamento estadístico. Se grafican 6 variables: el número de tropas de Napoleón, la distancia, la temperatura, la latitud y la longitud, la dirección en que viajaban las tropas y la localización relativa a fechas específicas. La gráfica de Minard, como la describe E.J. Marey, parece “desafiar la pluma del historiador con su brutal elocuencia”, la combinación de datos del mapa, y la serie de tiempo, dibujados en 1869, “retratan una secuencia de pérdidas devastadoras que sufrieron las tropas de Napoleón en 1812”. Comienza en la izquierda, en la frontera de Polonia y Rusia, cerca del río Niemen. La línea gruesa dorada muestra el tamaño de la Gran Armada (422,000) en el momento en que invadía Rusia en junio de 1812. El ancho de esta banda indica el tamaño de la armada en cada punto del mapa. En septiembre, la armada llegó a Moscú, que ya había sido saqueada y dejada desértica, con sólo 100,000 hombres. El camino del retiro de Napoleón desde Moscú está representado por la línea oscura (gris) que está en la parte inferior, que está relacionada a su vez con la temperatura y las fechas en el diagrama de abajo. Fue un invierno muy frío, y muchos se congelaron en su salida de Rusia. Como se muestra en el mapa, cruzar el río Berezina fue un desastre, y el ejército de Napoleón logró regresar a Polonia con tan sólo 10,000 hombres. También se muestran los movimientos de las tropas auxiliaries, que buscaban proteger por atrás y por la delantera mientras la armada avanzaba hacia Moscú. La gráfica de Minard cuenta una historia rica y cohesiva, coherente con datos multivariados y con los hechos históricos, y que puede ser más ilustrativa que tan sólo representar un número rebotando a lo largo del tiempo. Referencias "],["apéndice-transformaciones.html", "Apéndice: Transformaciones", " Apéndice: Transformaciones En ocasiones es conveniente transformar los datos para el análisis, el objetivo de los ajustes es simplificar la interpretación y el análisis al eliminar fuentes de variación conocidas, también es común realizan transformaciones para simplificar los patrones. Algunos ejemplos donde eliminamos efectos conocidos: Cuando analizamos el precio de venta de las casas podemos eliminar la variación debida al tamaño de las casas al pasar de precio de venta a precio de venta por metro cuadrado. De manera similar al analizar las propinas puede convenir considerar la propina como porcentaje de la cuenta. En series de tiempo cuando los datos están relacionados con el tamaño de la población podemos ajustar a mediciones per capita (en series de tiempo PIB). También es común ajustar por inflación, o poner cantidades monetarias en valor presente. mex_dat <- global_economy |> filter(Code == "MEX") pib <- ggplot(mex_dat, aes(x = Year, y = GDP / 1e6)) + geom_line() pib_pc <- ggplot(mex_dat, aes(x = Year, y = GDP / Population)) + geom_line() pib / pib_pc Adicionalmente podemos recurrir a otras transformaciones matemáticas (e.g. logaritmo, raíz cuadrada) que simplifiquen el patrón en los datos y la interpretación. Veamos un ejemplo donde es apropiado la transformación logaritmo. Usamos los datos Animals con información de peso corporal promedio y peso cerebral promedio para 28 especies. Buscamos entender la relación entre estas dos variables, e inspeccionar que especies se desvían (residuales) del esperado. Comenzamos con un diagrama de dispersión usando las unidades originales animals_tbl <- as_tibble(Animals, rownames = "animal") p1 <- ggplot(animals_tbl, aes(x = body, y = brain, label = animal)) + geom_point() + labs(subtitle = "Unidades originales") p2 <- ggplot(animals_tbl, aes(x = body, y = brain, label = animal)) + geom_point() + xlim(0, 500) + ylim(0, 1500) + geom_text_repel() + labs(subtitle = "Unidades originales, eliminando 'grandes'") (p1 + p2) Incluso cuando nos limitamos a especies de menos de 500 kg de masa corporal, la relación no es fácil de descrubir.En la suguiente gráfica hacemos la transformación logaritmo y obtenemos una gráfica más fácil de leer, además los datos se modelarán con más facilidad. p3 <- ggplot(animals_tbl, aes(x = log(body), y = log(brain), label = animal)) + geom_smooth(method = "lm", se = FALSE, color = "red") + geom_point() + geom_text_repel() + stat_poly_eq(use_label(c("eq"))) p3 ## `geom_smooth()` using formula = 'y ~ x' La transformación logaritmo tiene también ventajas en interpretación, para diferencias chicas en escala log, las diferencias corresponden a diferencias porcentuales en la escala original, por ejempo consideremos la diferencia entre el peso en escala log de humano y borrego: 4.13 - 4.02 = 0.11. Confirmamos que el humano es aproximadamente 11% más pesado que el borrego en la escala original: 62/55.5 - 1 = 0.12 animals_tbl <- animals_tbl |> mutate(log_body = log(body), log_brain = log(brain)) animals_tbl |> filter(animal == "Human" | animal == "Sheep") |> arrange(body) |> gt::gt() |> gt::fmt_number() #lhakecnvts table { font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #lhakecnvts thead, #lhakecnvts tbody, #lhakecnvts tfoot, #lhakecnvts tr, #lhakecnvts td, #lhakecnvts th { border-style: none; } #lhakecnvts p { margin: 0; padding: 0; } #lhakecnvts .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; } #lhakecnvts .gt_caption { padding-top: 4px; padding-bottom: 4px; } #lhakecnvts .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; } #lhakecnvts .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; } #lhakecnvts .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #lhakecnvts .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #lhakecnvts .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #lhakecnvts .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; } #lhakecnvts .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; } #lhakecnvts .gt_column_spanner_outer:first-child { padding-left: 0; } #lhakecnvts .gt_column_spanner_outer:last-child { padding-right: 0; } #lhakecnvts .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; } #lhakecnvts .gt_spanner_row { border-bottom-style: hidden; } #lhakecnvts .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; } #lhakecnvts .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; } #lhakecnvts .gt_from_md > :first-child { margin-top: 0; } #lhakecnvts .gt_from_md > :last-child { margin-bottom: 0; } #lhakecnvts .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; } #lhakecnvts .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; } #lhakecnvts .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; } #lhakecnvts .gt_row_group_first td { border-top-width: 2px; } #lhakecnvts .gt_row_group_first th { border-top-width: 2px; } #lhakecnvts .gt_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #lhakecnvts .gt_first_summary_row { border-top-style: solid; border-top-color: #D3D3D3; } #lhakecnvts .gt_first_summary_row.thick { border-top-width: 2px; } #lhakecnvts .gt_last_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #lhakecnvts .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #lhakecnvts .gt_first_grand_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; } #lhakecnvts .gt_last_grand_summary_row_top { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; } #lhakecnvts .gt_striped { background-color: rgba(128, 128, 128, 0.05); } #lhakecnvts .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #lhakecnvts .gt_footnotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #lhakecnvts .gt_footnote { margin: 0px; font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #lhakecnvts .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #lhakecnvts .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #lhakecnvts .gt_left { text-align: left; } #lhakecnvts .gt_center { text-align: center; } #lhakecnvts .gt_right { text-align: right; font-variant-numeric: tabular-nums; } #lhakecnvts .gt_font_normal { font-weight: normal; } #lhakecnvts .gt_font_bold { font-weight: bold; } #lhakecnvts .gt_font_italic { font-style: italic; } #lhakecnvts .gt_super { font-size: 65%; } #lhakecnvts .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; } #lhakecnvts .gt_asterisk { font-size: 100%; vertical-align: 0; } #lhakecnvts .gt_indent_1 { text-indent: 5px; } #lhakecnvts .gt_indent_2 { text-indent: 10px; } #lhakecnvts .gt_indent_3 { text-indent: 15px; } #lhakecnvts .gt_indent_4 { text-indent: 20px; } #lhakecnvts .gt_indent_5 { text-indent: 25px; } #lhakecnvts .katex-display { display: inline-flex !important; margin-bottom: 0.75em !important; } #lhakecnvts div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { height: 0px !important; } animal body brain log_body log_brain Sheep 55.50 175.00 4.02 5.16 Human 62.00 1,320.00 4.13 7.19 Y podemos usarlo también para interpretar la recta de referencia \\(y = 2.55 + 0.5 x\\) , para cambios chicos: Un incremento de 10% en masa total corresponde en un incremento de 5% en masa cerebral. El coeficiente de la regresión log-log, en nuestro ejemplo 0.5, es la elasticidad y es un concepto común en economía. Justificación Para entender la interpretación como cambio porcentual recordemos primero que la representación con series de Taylor de la función exponencial es: \\[e^x = \\sum_{n=0}^\\infty \\frac{x^n}{n!}\\] Más aún podemos tener una aproximación usando polinomios de Taylor, en el caso de la exponencial el \\(k\\)-ésimo polinomio de Taylor está dado por: \\[e^\\delta \\approx 1 + \\delta + \\frac{1}{2!}\\delta^2 + \\dots + \\frac{1}{k!}\\delta^k\\] y si \\(\\delta\\) es chica (digamos menor a 0.15), entonces la aproximación de primer grado es razonable y tenemos: \\[Ae^{\\delta} \\approx A(1+\\delta)\\] dat <- tibble(delta = seq(0, 1, 0.01), exp_delta = exp(delta), uno_mas_delta = 1 + delta) ggplot(dat, aes(x = uno_mas_delta, y = exp_delta)) + geom_line() + geom_abline(color = "red") + annotate("text", x = 1.20, y = 1.18, label = "y = x", color = "red", size = 6) "],["referencias.html", "Referencias", " Referencias "],["404.html", "Page not found", " Page not found The page you requested cannot be found (perhaps it was moved or renamed). You may want to try searching to find the page's new location, or use the table of contents to find the page you are looking for. "]]
+[["index.html", "Fundamentos de Estadística con Remuestreo Información del curso", " Fundamentos de Estadística con Remuestreo Teresa Ortiz, Felipe González, Alfredo Garbuno Información del curso Notas del curso Fundamentos de Estadística con Remuestreo, este curso busca explicar los principios básicos de la estadística y su papel en el análisis de datos. Nuestro punto de vista es uno de fundamentos, con menos énfasis en recetas o técnicas particulares. Ligas Notas: https://tereom.github.io/fundamentos-2024/ Repositorio con material: https://github.com/tereom/fundamentos-2024 Correo: teresa.ortiz.mancera@gmail.com Zoom clase (miércoles 4:00-7:00 pm): https://itam.zoom.us/j/92745909276 Zoom sesiones de dudas (lunes de 5:30-6:30 pm): https://itam.zoom.us/j/92518922348 Canvas: https://itam.instructure.com/courses/13328 x Este trabajo está bajo una Licencia Creative Commons Atribución 4.0 Internacional. "],["temario.html", "Temario Plan semanal Evaluación", " Temario Plan semanal Datos y análisis exploratorio Referencias: (W. S. Cleveland 1994), (Chihara and Hesterberg 2018) Visualización1 Análisis exploratorio Tipos de datos o estudios Muestras diseñadas y muestras naturales Experimentos y datos observacionales Introducción a Pruebas de Hipótesis Referencias: (Chihara and Hesterberg 2018) Introducción a pruebas de hipótesis. Pruebas de permutaciones Muestras pareadas y otros ejemplos Estimación y distribución de muestreo Referencias: (Chihara and Hesterberg 2018), (Tim C. Hesterberg 2015b) Estimadores y su distribución de muestreo Repaso de probabilidad y Teorema del límite central Introducción a estimación por intervalos Referencias: (Chihara and Hesterberg 2018), (Efron and Tibshirani 1993), (Tim C. Hesterberg 2015b) El método plugin y el boostrap Bootstrap e Intervalos de confianza. Ejemplos. Estimación Referencias: (Chihara and Hesterberg 2018), (Wasserman 2013) Estimación por máxima verosimilitud Ejemplos de estimación por máxima verosimilitud y Bootstrap paramétrico Propiedades de estimadores de máxima verosimilitud Más de pruebas de hipótesis Referencias: (Chihara and Hesterberg 2018), (Wasserman 2013) Pruebas de hipótesis para medias y proporciones: una y dos poblaciones. Introducción a inferencia bayesiana Referencias: (Kruschke 2015) Introducción a inferencia bayesiana Ejemplos de distribuciones conjugadas Introducción a métodos computacionales básicos: Muestreadores Metrópolis y Gibbs Ejemplos de inferencia bayesiana en Stan Evaluación Se evaluará mediante tareas semanales y dos exámenes: Tareas semanales (20%) Examen parcial en clase y a casa (40%) Examen final a casa (40%) Referencias "],["análisis-exploratorio.html", "Sección 1 Análisis exploratorio El papel de la exploración en el análisis de datos Preguntas y datos Algunos conceptos básicos Ejemplos Suavizamiento loess Caso de estudio: nacimientos en México", " Sección 1 Análisis exploratorio “Exploratory data analysis can never be the whole story, but nothing else can serve as the foundation stone –as the first step.” — John Tukey El papel de la exploración en el análisis de datos El estándar científico para contestar preguntas o tomar decisiones es uno que se basa en el análisis de datos. Es decir, en primer lugar se deben reunir todos los datos que puedan contener o sugerir alguna guía para entender mejor la pregunta o la decisión a la que nos enfrentamos. Esta recopilación de datos —que pueden ser cualitativos, cuantitativos, o una mezcla de los dos— debe entonces ser analizada para extraer información relevante para nuestro problema. En análisis de datos existen dos distintos tipos de trabajo: El trabajo exploratorio o de detective: ¿cuáles son los aspectos importantes de estos datos? ¿qué indicaciones generales muestran los datos? ¿qué tareas de análisis debemos empezar haciendo? ¿cuáles son los caminos generales para formular con precisión y contestar algunas preguntas que nos interesen? El trabajo inferencial, confirmatorio, o de juez: ¿cómo evaluar el peso de la evidencia de los descubrimientos del paso anterior? ¿qué tan bien soportadas están las respuestas y conclusiones por nuestro conjunto de datos? Preguntas y datos Cuando observamos un conjunto de datos, independientemente de su tamaño, el paso inicial más importante es entender bajo qué proceso se generan los datos. A grandes rasgos, cuanto más sepamos de este proceso, mejor podemos contestar preguntas de interés. En muchos casos, tendremos que hacer algunos supuestos de cómo se generan estos datos para dar respuestas (condicionales a esos supuestos). Algunos conceptos básicos Empezamos explicando algunas ideas que no serán útiles más adelante. El primer concepto se refiere a entender cómo se distribuyen los datos a los largo de su escala de medición. Comenzamos con un ejemplo: los siguientes datos fueron registrados en un restaurante durante cuatro días consecutivos. library(tidyverse) library(patchwork) # organizar gráficas library(gt) # formatear tablas source("R/funciones_auxiliares.R") # usamos los datos tips del paquete reshape2 propinas <- read_csv("./data/propinas.csv") Y vemos una muestra slice_sample(propinas, n = 10) |> gt() #erfsxygdfk table { font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #erfsxygdfk thead, #erfsxygdfk tbody, #erfsxygdfk tfoot, #erfsxygdfk tr, #erfsxygdfk td, #erfsxygdfk th { border-style: none; } #erfsxygdfk p { margin: 0; padding: 0; } #erfsxygdfk .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; } #erfsxygdfk .gt_caption { padding-top: 4px; padding-bottom: 4px; } #erfsxygdfk .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; } #erfsxygdfk .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; } #erfsxygdfk .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #erfsxygdfk .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #erfsxygdfk .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #erfsxygdfk .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; } #erfsxygdfk .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; } #erfsxygdfk .gt_column_spanner_outer:first-child { padding-left: 0; } #erfsxygdfk .gt_column_spanner_outer:last-child { padding-right: 0; } #erfsxygdfk .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; } #erfsxygdfk .gt_spanner_row { border-bottom-style: hidden; } #erfsxygdfk .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; } #erfsxygdfk .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; } #erfsxygdfk .gt_from_md > :first-child { margin-top: 0; } #erfsxygdfk .gt_from_md > :last-child { margin-bottom: 0; } #erfsxygdfk .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; } #erfsxygdfk .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; } #erfsxygdfk .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; } #erfsxygdfk .gt_row_group_first td { border-top-width: 2px; } #erfsxygdfk .gt_row_group_first th { border-top-width: 2px; } #erfsxygdfk .gt_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #erfsxygdfk .gt_first_summary_row { border-top-style: solid; border-top-color: #D3D3D3; } #erfsxygdfk .gt_first_summary_row.thick { border-top-width: 2px; } #erfsxygdfk .gt_last_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #erfsxygdfk .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #erfsxygdfk .gt_first_grand_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; } #erfsxygdfk .gt_last_grand_summary_row_top { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; } #erfsxygdfk .gt_striped { background-color: rgba(128, 128, 128, 0.05); } #erfsxygdfk .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #erfsxygdfk .gt_footnotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #erfsxygdfk .gt_footnote { margin: 0px; font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #erfsxygdfk .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #erfsxygdfk .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #erfsxygdfk .gt_left { text-align: left; } #erfsxygdfk .gt_center { text-align: center; } #erfsxygdfk .gt_right { text-align: right; font-variant-numeric: tabular-nums; } #erfsxygdfk .gt_font_normal { font-weight: normal; } #erfsxygdfk .gt_font_bold { font-weight: bold; } #erfsxygdfk .gt_font_italic { font-style: italic; } #erfsxygdfk .gt_super { font-size: 65%; } #erfsxygdfk .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; } #erfsxygdfk .gt_asterisk { font-size: 100%; vertical-align: 0; } #erfsxygdfk .gt_indent_1 { text-indent: 5px; } #erfsxygdfk .gt_indent_2 { text-indent: 10px; } #erfsxygdfk .gt_indent_3 { text-indent: 15px; } #erfsxygdfk .gt_indent_4 { text-indent: 20px; } #erfsxygdfk .gt_indent_5 { text-indent: 25px; } #erfsxygdfk .katex-display { display: inline-flex !important; margin-bottom: 0.75em !important; } #erfsxygdfk div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { height: 0px !important; } cuenta_total propina fumador dia momento num_personas 16.32 4.30 Si Vie Cena 2 32.68 5.00 Si Jue Comida 2 14.00 3.00 No Sab Cena 2 9.94 1.56 No Dom Cena 2 34.65 3.68 Si Dom Cena 4 10.29 2.60 No Dom Cena 2 20.92 4.08 No Sab Cena 2 10.65 1.50 No Jue Comida 2 17.31 3.50 No Dom Cena 2 32.90 3.11 Si Dom Cena 2 Aquí la unidad de observación es una cuenta particular. Tenemos tres mediciones numéricas de cada cuenta: cúanto fue la cuenta total, la propina, y el número de personas asociadas a la cuenta. Los datos están separados según se fumó o no en la mesa, y temporalmente en dos partes: el día (Jueves, Viernes, Sábado o Domingo), cada uno separado por Cena y Comida. Denotamos por \\(x\\) el valor de medición de una unidad de observación. Usualmente utilizamos sub-índices para identificar entre diferentes puntos de datos (observaciones), por ejemplo, \\(x_n\\) para la \\(n-\\)ésima observación. De tal forma que una colección de \\(N\\) observaciones la escribimos como \\[\\begin{align} \\{x_1, \\ldots, x_N\\}. \\end{align}\\] El primer tipo de comparaciones que nos interesa hacer es para una medición: ¿Varían mucho o poco los datos de un tipo de medición? ¿Cuáles son valores típicos o centrales? ¿Existen valores atípicos? Supongamos entonces que consideramos simplemente la variable de cuenta_total. Podemos comenzar por ordenar los datos, y ver cuáles datos están en los extremos y cuáles están en los lugares centrales: propinas <- propinas |> mutate(orden_cuenta = rank(cuenta_total, ties.method = "first"), f = (orden_cuenta - 0.5) / n()) cuenta <- propinas |> select(orden_cuenta, f, cuenta_total) |> arrange(f) bind_rows(head(cuenta), tail(cuenta)) |> gt() |> fmt_number(columns = f, decimals = 3) #lhcneythau table { font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #lhcneythau thead, #lhcneythau tbody, #lhcneythau tfoot, #lhcneythau tr, #lhcneythau td, #lhcneythau th { border-style: none; } #lhcneythau p { margin: 0; padding: 0; } #lhcneythau .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; } #lhcneythau .gt_caption { padding-top: 4px; padding-bottom: 4px; } #lhcneythau .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; } #lhcneythau .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; } #lhcneythau .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #lhcneythau .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #lhcneythau .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #lhcneythau .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; } #lhcneythau .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; } #lhcneythau .gt_column_spanner_outer:first-child { padding-left: 0; } #lhcneythau .gt_column_spanner_outer:last-child { padding-right: 0; } #lhcneythau .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; } #lhcneythau .gt_spanner_row { border-bottom-style: hidden; } #lhcneythau .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; } #lhcneythau .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; } #lhcneythau .gt_from_md > :first-child { margin-top: 0; } #lhcneythau .gt_from_md > :last-child { margin-bottom: 0; } #lhcneythau .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; } #lhcneythau .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; } #lhcneythau .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; } #lhcneythau .gt_row_group_first td { border-top-width: 2px; } #lhcneythau .gt_row_group_first th { border-top-width: 2px; } #lhcneythau .gt_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #lhcneythau .gt_first_summary_row { border-top-style: solid; border-top-color: #D3D3D3; } #lhcneythau .gt_first_summary_row.thick { border-top-width: 2px; } #lhcneythau .gt_last_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #lhcneythau .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #lhcneythau .gt_first_grand_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; } #lhcneythau .gt_last_grand_summary_row_top { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; } #lhcneythau .gt_striped { background-color: rgba(128, 128, 128, 0.05); } #lhcneythau .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #lhcneythau .gt_footnotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #lhcneythau .gt_footnote { margin: 0px; font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #lhcneythau .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #lhcneythau .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #lhcneythau .gt_left { text-align: left; } #lhcneythau .gt_center { text-align: center; } #lhcneythau .gt_right { text-align: right; font-variant-numeric: tabular-nums; } #lhcneythau .gt_font_normal { font-weight: normal; } #lhcneythau .gt_font_bold { font-weight: bold; } #lhcneythau .gt_font_italic { font-style: italic; } #lhcneythau .gt_super { font-size: 65%; } #lhcneythau .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; } #lhcneythau .gt_asterisk { font-size: 100%; vertical-align: 0; } #lhcneythau .gt_indent_1 { text-indent: 5px; } #lhcneythau .gt_indent_2 { text-indent: 10px; } #lhcneythau .gt_indent_3 { text-indent: 15px; } #lhcneythau .gt_indent_4 { text-indent: 20px; } #lhcneythau .gt_indent_5 { text-indent: 25px; } #lhcneythau .katex-display { display: inline-flex !important; margin-bottom: 0.75em !important; } #lhcneythau div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { height: 0px !important; } orden_cuenta f cuenta_total 1 0.002 3.07 2 0.006 5.75 3 0.010 7.25 4 0.014 7.25 5 0.018 7.51 6 0.023 7.56 239 0.977 44.30 240 0.982 45.35 241 0.986 48.17 242 0.990 48.27 243 0.994 48.33 244 0.998 50.81 También podemos graficar los datos en orden, interpolando valores consecutivos. A esta función le llamamos la función de cuantiles para la variable cuenta_total. Nos sirve para comparar directamente los distintos valores que observamos los datos según el orden que ocupan. En particular, podemos estudiar la dispersión y valores centrales de los datos observados: El rango de datos va de unos 3 dólares hasta 50 dólares Los valores centrales, por ejemplo el 50% de los valores más centrales, están entre unos 13 y 25 dólares. El valor que divide en dos mitades iguales a los datos es de alrededor de 18 dólares. El cuantil \\(f\\), que denotamos por \\(q(f)\\) es valor a lo largo de la escala de medición de los datos tal que aproximadamente una fracción \\(f\\) de los datos son menores o iguales a \\(q(f)\\). Al cuantil \\(f=0.5\\) le llamamos la mediana. A los cuantiles \\(f=0.25\\) y \\(f=0.75\\) les llamamos cuartiles inferior y superior. En nuestro ejemplo: Los valores centrales —del cuantil 0.25 al 0.75, por decir un ejemplo— están entre unos 13 y 25 dólares. Estos dos cuantiles se llaman cuartil inferior y cuartil superior respectivamente El cuantil 0.5 (o también conocido como mediana) está alrededor de 18 dólares. Éste último puede ser utilizado para dar un valor central de la distribución de valores para cuenta_total. Asimismo podemos dar resúmenes más refinados si es necesario. Por ejemplo, podemos reportar que: El cuantil 0.95 es de unos 35 dólares — sólo 5% de las cuentas son de más de 35 dólares El cuantil 0.05 es de unos 8 dólares — sólo 5% de las cuentas son de 8 dólares o menos. Finalmente, la forma de la gráfica se interpreta usando su pendiente (tasa de cambio) haciendo comparaciones en diferentes partes de la gráfica: La distribución de valores tiene asimetría: el 10% de las cuentas más altas tiene considerablemente más dispersión que el 10% de las cuentas más bajas. Entre los cuantiles 0.2 y 0.7 es donde existe mayor densidad de datos: la pendiente (tasa de cambio) es baja, lo que significa que al avanzar en los valores observados, los cuantiles (el porcentaje de casos) aumenta rápidamente. Cuando la pendiente alta, quiere decir que los datos tienen más dispersión local o están más separados. Observación: Hay varias maneras de definir los cuantiles (ver (William S. Cleveland 1993)): Supongamos que queremos definir \\(q(f)\\), y denotamos los datos ordenados como \\(x_{(1)}, x_{(2)}, \\ldots, x_{(N)}\\), de forma que \\(x_{(1)}\\) es el dato más chico y \\(x_{(N)}\\) es el dato más grande. Para cada \\(x_{(i)}\\) definimos \\[f_i = i / N\\] entonces definimos el cuantil \\(q(f_i)=x_{(i)}\\). Para cualquier \\(f\\) entre 0 y 1, podemos definir \\(q(f)\\) como sigue: si \\(f\\) está entre \\(f_i\\) y \\(f_{i+1}\\) interpolamos linealmente los valores correspondientes \\(x_{(i)}\\) y \\(x_{(i+1)}\\). En la práctica, es más conveniente usar \\(f_i= \\frac{i - 0.5}{N}\\). La gráfica de cuantiles no cambia mucho comparado con la difinición anterior, y esto nos permitirá comparar de mejor manera con distribuciones teóricas que no tienen definido su cuantil 0 y el 1, pues tienen soporte en los números reales (como la distribución normal, por ejemplo). Asociada a la función de cuantiles \\(q\\) tenemos la distribución acumulada empírica de los datos, que es aproximadamente inversa de la función de cuantiles, y se define como: \\[\\hat{F}(x) = i/N\\] si \\(x_{(i)} \\leq x < x_{(i+1)}\\). Nótese que \\(\\hat{F}(q(f_i)) = i/N = f_i\\) (demuéstralo). acum_cuenta <- ecdf(cuenta$cuenta_total) cuenta <- cuenta |> mutate(dea_cuenta_total = acum_cuenta(cuenta_total)) g_acum <- ggplot(cuenta, aes(x = cuenta_total, y = dea_cuenta_total)) + geom_point() + labs(subtitle = "Distribución acum empírica de cuenta total", x = "") g_cuantiles + g_acum La función de distribución acumulada empírica es otra forma de graficar la dispersión de los datos. En su gráfica vemos que proporción de los datos que son iguales o están por debajo de cada valor en el eje horizontal. Nota: En análisis de datos, es más frecuente utilizar la función de cuantiles pues existen versiones más generales que son útiles, por ejemplo, para evaluar ajuste de modelos probabilísticos En la teoría, generalmente es más común utilizar la fda empírica, que tiene una única definición que veremos coincide con definiciones teóricas. Histogramas En algunos casos, es más natural hacer un histograma, donde dividimos el rango de la variable en cubetas o intervalos (en este caso de igual longitud), y graficamos por medio de barras cuántos datos caen en cada cubeta: Es una gráfica más popular, pero perdemos cierto nivel de detalle, y distintas particiones resaltan distintos aspectos de los datos. ¿Cómo se ve la gráfica de cuantiles de las propinas? ¿Cómo crees que esta gráfica se compara con distintos histogramas? g_1 <- ggplot(propinas, aes(sample = propina)) + geom_qq(distribution = stats::qunif) + labs(x = "f", y = "propina") g_1 Finalmente, una gráfica más compacta que resume la gráfica de cuantiles o el histograma es el diagrama de caja y brazos. Mostramos dos versiones, la clásica de Tukey (T) y otra versión menos común de Spear/Tufte (ST): library(ggthemes) cuartiles <- quantile(cuenta$cuenta_total) cuartiles |> round(2) ## 0% 25% 50% 75% 100% ## 3.07 13.35 17.80 24.13 50.81 g_1 <- ggplot(cuenta, aes(x = f, y = cuenta_total)) + labs(subtitle = "Gráfica de cuantiles: Cuenta total") + geom_hline(yintercept = cuartiles[2:4], colour = "gray") + geom_point(alpha = 0.5) + geom_line() g_2 <- ggplot(cuenta, aes(x = factor("ST", levels = "ST"), y = cuenta_total)) + geom_tufteboxplot() + labs(subtitle = "", x = "", y = "") g_3 <- ggplot(cuenta, aes(x = factor("T"), y = cuenta_total)) + geom_boxplot() + labs(subtitle = "", x = "", y = "") g_4 <- ggplot(cuenta, aes(x = factor("P"), y = cuenta_total)) + geom_jitter(height = 0, width =0.2, alpha = 0.5) + labs(subtitle = "", x = "", y = "") g_5 <- ggplot(cuenta, aes(x = factor("V"), y = cuenta_total)) + geom_violin() + labs(subtitle = "", x = "", y = "") g_1 + g_2 + g_3 + g_4 + plot_layout(widths = c(8, 2, 2, 2)) El diagrama de abajo explica los elementos de la versión típica del diagrama de caja y brazos (boxplot). RIC se refiere al Rango Intercuantílico, definido por la diferencia entre los cuantiles 25% y 75%. Figura: Jumanbar / CC BY-SA Ventajas en el análisis inicial En un principio del análisis, estos resúmenes (cuantiles) pueden ser más útiles que utilizar medias y varianzas, por ejemplo. La razón es que los cuantiles: Son cantidades más fácilmente interpretables Los cuantiles centrales son más resistentes a valores atípicos que medias o varianzas Permiten identificar valores extremos Es fácil comparar cuantiles de distintos bonches de datos en la misma escala Nota: Existen diferentes definiciones para calcular cuantiles de una muestra de datos, puedes leer más en este artículo. Media y desviación estándar Las medidas más comunes de localización y dispersión para un conjunto de datos son la media muestral y la desviación estándar muestral. En general, no son muy apropiadas para iniciar el análisis exploratorio, pues: Son medidas más difíciles de interpretar y explicar que los cuantiles. En este sentido, son medidas especializadas. Por ejemplo, compara una explicación intuitiva de la mediana contra una explicación intuitiva de la media. No son resistentes a valores atípicos. Su falta de resistencia los vuelve poco útiles en las primeras etapas de limpieza y descripción y en resúmenes deficientes para distribuciones irregulares (con colas largas por ejemplo). La media, o promedio, se denota por \\(\\bar x\\) y se define como \\[\\begin{align} \\bar x = \\frac1N \\sum_{n = 1}^N x_n. \\end{align}\\] La desviación estándar muestral se define como \\[\\begin{align} \\text{std}(x) = \\sqrt{\\frac1{N-1} \\sum_{n = 1}^N (x_n - \\bar x)^2}. \\end{align}\\] Observación: Si \\(N\\) no es muy chica, no importa mucho si dividimos por \\(N\\) o por \\(N-1\\) en la fórmula de la desviación estándar. La razón de que típicamente se usa \\(N-1\\) la veremos más adelante, en la parte de estimación. Por otro lado, ventajas de estas medidas de centralidad y dispersión son: La media y desviación estándar son computacionalmente convenientes. Por lo tanto regresaremos a estas medidas una vez que estudiemos modelos de probabilidad básicos. En muchas ocasiones conviene usar estas medidas pues permite hacer comparaciones históricas o tradicionales —pues análisis anteriores pudieran estar basados en éstas. Considera el caso de tener \\(N\\) observaciones y asume que ya tienes calculado el promedio para dichas observaciones. Este promedio lo denotaremos por \\(\\bar x_N\\). Ahora, considera que has obtenido \\(M\\) observaciones más. Escribe una fórmula recursiva para la media del conjunto total de datos \\(\\bar x_{N+M}\\) en función de lo que ya tenías precalculado \\(\\bar x_N.\\) ¿En qué situaciones esta propiedad puede ser conveniente? Ejemplos Precios de casas En este ejemplo consideremos los datos de precios de ventas de la ciudad de Ames, Iowa. En particular nos interesa entender la variación del precio de las casas. Por este motivo calculamos los cuantiles que corresponden al 25%, 50% y 75% (cuartiles), así como el mínimo y máximo de los precios de las casas: quantile(casas |> pull(precio_miles)) ## 0% 25% 50% 75% 100% ## 37.9 132.0 165.0 215.0 755.0 Comprueba que el mínimo y máximo están asociados a los cuantiles 0% y 100%, respectivamente. Una posible comparación es considerar los precios y sus variación en función de zona de la ciudad en que se encuentra una vivienda. Podemos usar diagramas de caja y brazos para hacer una comparación burda de los precios en distintas zonas de la ciudad: ggplot(casas, aes(x = nombre_zona, y = precio_miles)) + geom_boxplot() + coord_flip() La primera pregunta que nos hacemos es cómo pueden variar las características de las casas dentro de cada zona. Para esto, podemos considerar el área de las casas. En lugar de graficar el precio, graficamos el precio por metro cuadrado, por ejemplo: ggplot(casas, aes(x = nombre_zona, y = precio_m2)) + geom_boxplot() + coord_flip() Podemos cuantificar la variación que observamos de zona a zona y la variación que hay dentro de cada una de las zonas. Una primera aproximación es observar las variación del precio al calcular la mediana dentro de cada zona, y después cuantificar por medio de cuantiles cómo varía la mediana entre zonas: casas |> group_by(nombre_zona) |> summarise(mediana_zona = median(precio_m2), .groups = "drop") |> arrange(mediana_zona) |> pull(mediana_zona) |> quantile() |> round() ## 0% 25% 50% 75% 100% ## 963 1219 1298 1420 1725 Por otro lado, las variaciones con respecto a las medianas dentro de cada zona, por grupo, se resume como: quantile(casas |> group_by(nombre_zona) |> mutate(residual = precio_m2 - median(precio_m2)) |> pull(residual)) |> round() ## 0% 25% 50% 75% 100% ## -765 -166 0 172 1314 Nótese que este último paso tiene sentido pues la variación dentro de las zonas, en términos de precio por metro cuadrado, es similar. Esto no lo podríamos haber hecho de manera efectiva si se hubiera utilizado el precio de las casas sin ajustar por su tamaño. Podemos resumir este primer análisis con un par de gráficas de cuantiles (William S. Cleveland (1993)): mediana <- median(casas$precio_m2) resumen <- casas |> select(nombre_zona, precio_m2) |> group_by(nombre_zona) |> mutate(mediana_zona = median(precio_m2)) |> mutate(residual = precio_m2 - mediana_zona) |> ungroup() |> mutate(mediana_zona = mediana_zona - mediana) |> select(nombre_zona, mediana_zona, residual) |> pivot_longer(mediana_zona:residual, names_to = "tipo", values_to = "valor") ggplot(resumen, aes(sample = valor)) + geom_qq(distribution = stats::qunif) + facet_wrap(~ tipo) + ylab("Precio por m2") + xlab("f") + labs(subtitle = "Precio por m2 por zona", caption = paste0("Mediana total de ", round(mediana))) Vemos que la mayor parte de la variación del precio por metro cuadrado ocurre dentro de cada zona, una vez que controlamos por el tamaño de las casas. La variación dentro de cada zona es aproximadamente simétrica, aunque la cola derecha es ligeramente más larga con algunos valores extremos. Podemos seguir con otro indicador importante: la calificación de calidad de los terminados de las casas. Como primer intento podríamos hacer: Lo que indica que las calificaciones de calidad están distribuidas de manera muy distinta a lo largo de las zonas, y que probablemente no va ser simple desentrañar qué variación del precio se debe a la zona y cuál se debe a la calidad. Prueba Enlace Consideremos la prueba Enlace (2011) de matemáticas para primarias. Una primera pregunta que alguien podría hacerse es: ¿cuáles escuelas son mejores en este rubro, las privadas o las públicas? enlace_tbl <- enlace |> group_by(tipo) |> summarise(n_escuelas = n(), cuantiles = list(cuantil(mate_6, c(0.05, 0.25, 0.5, 0.75, 0.95)))) |> unnest(cols = cuantiles) |> mutate(valor = round(valor)) enlace_tbl |> spread(cuantil, valor) |> gt() #sykxxonvyi table { font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #sykxxonvyi thead, #sykxxonvyi tbody, #sykxxonvyi tfoot, #sykxxonvyi tr, #sykxxonvyi td, #sykxxonvyi th { border-style: none; } #sykxxonvyi p { margin: 0; padding: 0; } #sykxxonvyi .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; } #sykxxonvyi .gt_caption { padding-top: 4px; padding-bottom: 4px; } #sykxxonvyi .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; } #sykxxonvyi .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; } #sykxxonvyi .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #sykxxonvyi .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #sykxxonvyi .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #sykxxonvyi .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; } #sykxxonvyi .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; } #sykxxonvyi .gt_column_spanner_outer:first-child { padding-left: 0; } #sykxxonvyi .gt_column_spanner_outer:last-child { padding-right: 0; } #sykxxonvyi .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; } #sykxxonvyi .gt_spanner_row { border-bottom-style: hidden; } #sykxxonvyi .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; } #sykxxonvyi .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; } #sykxxonvyi .gt_from_md > :first-child { margin-top: 0; } #sykxxonvyi .gt_from_md > :last-child { margin-bottom: 0; } #sykxxonvyi .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; } #sykxxonvyi .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; } #sykxxonvyi .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; } #sykxxonvyi .gt_row_group_first td { border-top-width: 2px; } #sykxxonvyi .gt_row_group_first th { border-top-width: 2px; } #sykxxonvyi .gt_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #sykxxonvyi .gt_first_summary_row { border-top-style: solid; border-top-color: #D3D3D3; } #sykxxonvyi .gt_first_summary_row.thick { border-top-width: 2px; } #sykxxonvyi .gt_last_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #sykxxonvyi .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #sykxxonvyi .gt_first_grand_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; } #sykxxonvyi .gt_last_grand_summary_row_top { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; } #sykxxonvyi .gt_striped { background-color: rgba(128, 128, 128, 0.05); } #sykxxonvyi .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #sykxxonvyi .gt_footnotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #sykxxonvyi .gt_footnote { margin: 0px; font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #sykxxonvyi .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #sykxxonvyi .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #sykxxonvyi .gt_left { text-align: left; } #sykxxonvyi .gt_center { text-align: center; } #sykxxonvyi .gt_right { text-align: right; font-variant-numeric: tabular-nums; } #sykxxonvyi .gt_font_normal { font-weight: normal; } #sykxxonvyi .gt_font_bold { font-weight: bold; } #sykxxonvyi .gt_font_italic { font-style: italic; } #sykxxonvyi .gt_super { font-size: 65%; } #sykxxonvyi .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; } #sykxxonvyi .gt_asterisk { font-size: 100%; vertical-align: 0; } #sykxxonvyi .gt_indent_1 { text-indent: 5px; } #sykxxonvyi .gt_indent_2 { text-indent: 10px; } #sykxxonvyi .gt_indent_3 { text-indent: 15px; } #sykxxonvyi .gt_indent_4 { text-indent: 20px; } #sykxxonvyi .gt_indent_5 { text-indent: 25px; } #sykxxonvyi .katex-display { display: inline-flex !important; margin-bottom: 0.75em !important; } #sykxxonvyi div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { height: 0px !important; } tipo n_escuelas 0.05 0.25 0.5 0.75 0.95 Indígena/Conafe 13599 304 358 412 478 588 General 60166 380 454 502 548 631 Particular 6816 479 551 593 634 703 Para un análisis exploratorio podemos utilizar distintas gráficas. Por ejemplo, podemos utilizar nuevamente las gráficas de caja y brazos, así como graficar los percentiles. Nótese que en la gráfica 1 se utilizan los cuantiles 0.05, 0.25, 0.5, 0.75 y 0.95: ## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0. ## ℹ Please use `linewidth` instead. ## This warning is displayed once every 8 hours. ## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was ## generated. Se puede discutir qué tan apropiada es cada gráfica con el objetivo de realizar comparaciones. Sin duda, graficar más cuantiles es más útil para hacer comparaciones. Por ejemplo, en la Gráfica 1 podemos ver que la mediana de las escuelas generales está cercana al cuantil 5% de las escuelas particulares. Por otro lado, el diagrama de caja y brazos muestra también valores “atípicos”. Antes de contestar prematuramente la pregunta: ¿cuáles son las mejores escuelas? busquemos mejorar la interpretabilidad de nuestras comparaciones. Podemos comenzar por agregar, por ejemplo, el nivel del marginación del municipio donde se encuentra la escuela. Para este objetivo, podemos usar páneles (pequeños múltiplos útiles para hacer comparaciones) y graficar: Esta gráfica pone en contexto la pregunta inicial, y permite evidenciar la dificultad de contestarla. En particular: Señala que la pregunta no sólo debe concentarse en el tipo de “sistema”: pública, privada, etc. Por ejemplo, las escuelas públicas en zonas de marginación baja no tienen una distribución de calificaciones muy distinta a las privadas en zonas de marginación alta. El contexto de la escuela es importante. Debemos de pensar qué factores –por ejemplo, el entorno familiar de los estudiantes– puede resultar en comparaciones que favorecen a las escuelas privadas. Un ejemplo de esto es considerar si los estudiantes tienen que trabajar o no. A su vez, esto puede o no ser reflejo de la calidad del sistema educativo. Si esto es cierto, entonces la pregunta inicial es demasiado vaga y mal planteada. Quizá deberíamos intentar entender cuánto “aporta” cada escuela a cada estudiante, como medida de qué tan buena es cada escuela. Estados y calificaciones en SAT ¿Cómo se relaciona el gasto por alumno, a nivel estatal, con sus resultados académicos? Hay trabajo considerable en definir estos términos, pero supongamos que tenemos el siguiente conjunto de datos (Guber 1999), que son datos oficiales agregados por estado de Estados Unidos. Consideremos el subconjunto de variables sat, que es la calificación promedio de los alumnos en cada estado (para 1997) y expend, que es el gasto en miles de dólares por estudiante en (1994-1995). sat <- read_csv("data/sat.csv") sat_tbl <- sat |> select(state, expend, sat) |> gather(variable, valor, expend:sat) |> group_by(variable) |> summarise(cuantiles = list(cuantil(valor))) |> unnest(cols = c(cuantiles)) |> mutate(valor = round(valor, 1)) |> spread(cuantil, valor) sat_tbl |> gt() #trvhlcrcph table { font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #trvhlcrcph thead, #trvhlcrcph tbody, #trvhlcrcph tfoot, #trvhlcrcph tr, #trvhlcrcph td, #trvhlcrcph th { border-style: none; } #trvhlcrcph p { margin: 0; padding: 0; } #trvhlcrcph .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; } #trvhlcrcph .gt_caption { padding-top: 4px; padding-bottom: 4px; } #trvhlcrcph .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; } #trvhlcrcph .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; } #trvhlcrcph .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #trvhlcrcph .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #trvhlcrcph .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #trvhlcrcph .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; } #trvhlcrcph .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; } #trvhlcrcph .gt_column_spanner_outer:first-child { padding-left: 0; } #trvhlcrcph .gt_column_spanner_outer:last-child { padding-right: 0; } #trvhlcrcph .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; } #trvhlcrcph .gt_spanner_row { border-bottom-style: hidden; } #trvhlcrcph .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; } #trvhlcrcph .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; } #trvhlcrcph .gt_from_md > :first-child { margin-top: 0; } #trvhlcrcph .gt_from_md > :last-child { margin-bottom: 0; } #trvhlcrcph .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; } #trvhlcrcph .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; } #trvhlcrcph .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; } #trvhlcrcph .gt_row_group_first td { border-top-width: 2px; } #trvhlcrcph .gt_row_group_first th { border-top-width: 2px; } #trvhlcrcph .gt_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #trvhlcrcph .gt_first_summary_row { border-top-style: solid; border-top-color: #D3D3D3; } #trvhlcrcph .gt_first_summary_row.thick { border-top-width: 2px; } #trvhlcrcph .gt_last_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #trvhlcrcph .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #trvhlcrcph .gt_first_grand_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; } #trvhlcrcph .gt_last_grand_summary_row_top { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; } #trvhlcrcph .gt_striped { background-color: rgba(128, 128, 128, 0.05); } #trvhlcrcph .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #trvhlcrcph .gt_footnotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #trvhlcrcph .gt_footnote { margin: 0px; font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #trvhlcrcph .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #trvhlcrcph .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #trvhlcrcph .gt_left { text-align: left; } #trvhlcrcph .gt_center { text-align: center; } #trvhlcrcph .gt_right { text-align: right; font-variant-numeric: tabular-nums; } #trvhlcrcph .gt_font_normal { font-weight: normal; } #trvhlcrcph .gt_font_bold { font-weight: bold; } #trvhlcrcph .gt_font_italic { font-style: italic; } #trvhlcrcph .gt_super { font-size: 65%; } #trvhlcrcph .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; } #trvhlcrcph .gt_asterisk { font-size: 100%; vertical-align: 0; } #trvhlcrcph .gt_indent_1 { text-indent: 5px; } #trvhlcrcph .gt_indent_2 { text-indent: 10px; } #trvhlcrcph .gt_indent_3 { text-indent: 15px; } #trvhlcrcph .gt_indent_4 { text-indent: 20px; } #trvhlcrcph .gt_indent_5 { text-indent: 25px; } #trvhlcrcph .katex-display { display: inline-flex !important; margin-bottom: 0.75em !important; } #trvhlcrcph div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { height: 0px !important; } variable 0 0.25 0.5 0.75 1 expend 3.7 4.9 5.8 6.4 9.8 sat 844.0 897.2 945.5 1032.0 1107.0 Esta variación es considerable para promedios del SAT: el percentil 75 es alrededor de 1050 puntos, mientras que el percentil 25 corresponde a alrededor de 800. Igualmente, hay diferencias considerables de gasto por alumno (miles de dólares) a lo largo de los estados. Ahora hacemos nuestro primer ejercico de comparación: ¿Cómo se ven las calificaciones para estados en distintos niveles de gasto? Podemos usar una gráfica de dispersión: library(ggrepel) ggplot(sat, aes(x = expend, y = sat, label = state)) + geom_point(colour = "red", size = 2) + geom_text_repel(colour = "gray50") + xlab("Gasto por alumno (miles de dólares)") + ylab("Calificación promedio en SAT") Estas comparaciones no son de alta calidad, solo estamos usando 2 variables —que son muy pocas— y no hay mucho que podamos decir en cuanto explicación. Sin duda nos hace falta una imagen más completa. Necesitaríamos entender la correlación que existe entre las demás características de nuestras unidades de estudio. Las unidades que estamos comparando pueden diferir fuertemente en otras propiedades importantes (o dimensiones), lo cual no permite interpretar la gráfica de manera sencilla. Una variable que tenemos es el porcentaje de alumnos de cada estado que toma el SAT. Podemos agregar como sigue: ggplot(sat, aes(x = expend, y = math, label=state, colour = frac)) + geom_point() + geom_text_repel() + xlab("Gasto por alumno (miles de dólares)") + ylab("Calificación promedio en SAT") Esto nos permite entender por qué nuestra comparación inicial es relativamente pobre. Los estados con mejores resultados promedio en el SAT son aquellos donde una fracción relativamente baja de los estudiantes toma el examen. La diferencia es considerable. En este punto podemos hacer varias cosas. Una primera idea es intentar comparar estados más similares en cuanto a la población de alumnos que asiste. Podríamos hacer grupos como sigue: set.seed(991) k_medias_sat <- kmeans(sat |> select(frac), centers = 4, nstart = 100, iter.max = 100) sat$clase <- k_medias_sat$cluster sat <- sat |> group_by(clase) |> mutate(clase_media = round(mean(frac))) |> ungroup() |> mutate(clase_media = factor(clase_media)) sat <- sat |> mutate(rank_p = rank(frac, ties= "first") / length(frac)) ggplot(sat, aes(x = rank_p, y = frac, label = state, colour = clase_media)) + geom_point(size = 2) Estos resultados indican que es más probable que buenos alumnos decidan hacer el SAT. Lo interesante es que esto ocurre de manera diferente en cada estado. Por ejemplo, en algunos estados era más común otro examen: el ACT. Si hacemos clusters de estados según el % de alumnos, empezamos a ver otra historia. Para esto, ajustemos rectas de mínimos cuadrados como referencia: Esto da una imagen muy diferente a la que originalmente planteamos. Nota que dependiendo de cómo categorizamos, esta gráfica puede variar (puedes intentar con más o menos grupos, por ejemplo). Tablas de conteos Consideremos los siguientes datos de tomadores de té (del paquete FactoMineR (Lê et al. 2008)): tea <- read_csv("data/tea.csv") # nombres y códigos te <- tea |> select(how, price, sugar) |> rename(presentacion = how, precio = price, azucar = sugar) |> mutate( presentacion = fct_recode(presentacion, suelto = "unpackaged", bolsas = "tea bag", mixto = "tea bag+unpackaged"), precio = fct_recode(precio, marca = "p_branded", variable = "p_variable", barato = "p_cheap", marca_propia = "p_private label", desconocido = "p_unknown", fino = "p_upscale"), azucar = fct_recode(azucar, sin_azúcar = "No.sugar", con_azúcar = "sugar")) sample_n(te, 10) ## # A tibble: 10 × 3 ## presentacion precio azucar ## <fct> <fct> <fct> ## 1 bolsas marca sin_azúcar ## 2 bolsas variable sin_azúcar ## 3 bolsas marca con_azúcar ## 4 bolsas fino con_azúcar ## 5 mixto variable con_azúcar ## 6 mixto fino con_azúcar ## 7 bolsas marca sin_azúcar ## 8 bolsas fino sin_azúcar ## 9 mixto variable con_azúcar ## 10 mixto variable sin_azúcar Nos interesa ver qué personas compran té suelto, y de qué tipo. Empezamos por ver las proporciones que compran té según su empaque (en bolsita o suelto): precio <- te |> count(precio) |> mutate(prop = round(100 * n / sum(n))) |> select(-n) tipo <- te |> count(presentacion) |> mutate(pct = round(100 * n / sum(n))) tipo |> gt() #wiljiluquk table { font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #wiljiluquk thead, #wiljiluquk tbody, #wiljiluquk tfoot, #wiljiluquk tr, #wiljiluquk td, #wiljiluquk th { border-style: none; } #wiljiluquk p { margin: 0; padding: 0; } #wiljiluquk .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; } #wiljiluquk .gt_caption { padding-top: 4px; padding-bottom: 4px; } #wiljiluquk .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; } #wiljiluquk .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; } #wiljiluquk .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #wiljiluquk .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #wiljiluquk .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #wiljiluquk .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; } #wiljiluquk .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; } #wiljiluquk .gt_column_spanner_outer:first-child { padding-left: 0; } #wiljiluquk .gt_column_spanner_outer:last-child { padding-right: 0; } #wiljiluquk .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; } #wiljiluquk .gt_spanner_row { border-bottom-style: hidden; } #wiljiluquk .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; } #wiljiluquk .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; } #wiljiluquk .gt_from_md > :first-child { margin-top: 0; } #wiljiluquk .gt_from_md > :last-child { margin-bottom: 0; } #wiljiluquk .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; } #wiljiluquk .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; } #wiljiluquk .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; } #wiljiluquk .gt_row_group_first td { border-top-width: 2px; } #wiljiluquk .gt_row_group_first th { border-top-width: 2px; } #wiljiluquk .gt_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #wiljiluquk .gt_first_summary_row { border-top-style: solid; border-top-color: #D3D3D3; } #wiljiluquk .gt_first_summary_row.thick { border-top-width: 2px; } #wiljiluquk .gt_last_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #wiljiluquk .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #wiljiluquk .gt_first_grand_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; } #wiljiluquk .gt_last_grand_summary_row_top { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; } #wiljiluquk .gt_striped { background-color: rgba(128, 128, 128, 0.05); } #wiljiluquk .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #wiljiluquk .gt_footnotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #wiljiluquk .gt_footnote { margin: 0px; font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #wiljiluquk .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #wiljiluquk .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #wiljiluquk .gt_left { text-align: left; } #wiljiluquk .gt_center { text-align: center; } #wiljiluquk .gt_right { text-align: right; font-variant-numeric: tabular-nums; } #wiljiluquk .gt_font_normal { font-weight: normal; } #wiljiluquk .gt_font_bold { font-weight: bold; } #wiljiluquk .gt_font_italic { font-style: italic; } #wiljiluquk .gt_super { font-size: 65%; } #wiljiluquk .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; } #wiljiluquk .gt_asterisk { font-size: 100%; vertical-align: 0; } #wiljiluquk .gt_indent_1 { text-indent: 5px; } #wiljiluquk .gt_indent_2 { text-indent: 10px; } #wiljiluquk .gt_indent_3 { text-indent: 15px; } #wiljiluquk .gt_indent_4 { text-indent: 20px; } #wiljiluquk .gt_indent_5 { text-indent: 25px; } #wiljiluquk .katex-display { display: inline-flex !important; margin-bottom: 0.75em !important; } #wiljiluquk div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { height: 0px !important; } presentacion n pct bolsas 170 57 mixto 94 31 suelto 36 12 La mayor parte de las personas toma té en bolsas. Sin embargo, el tipo de té (en términos de precio o marca) que compran es muy distinto dependiendo de la presentación: tipo <- tipo |> select(presentacion, prop_presentacion = pct) tabla_cruzada <- te |> count(presentacion, precio) |> # porcentajes por presentación group_by(presentacion) |> mutate(prop = round(100 * n / sum(n))) |> select(-n) tabla_cruzada |> pivot_wider(names_from = presentacion, values_from = prop, values_fill = list(prop = 0)) |> gt() #sxkvcchtyg table { font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #sxkvcchtyg thead, #sxkvcchtyg tbody, #sxkvcchtyg tfoot, #sxkvcchtyg tr, #sxkvcchtyg td, #sxkvcchtyg th { border-style: none; } #sxkvcchtyg p { margin: 0; padding: 0; } #sxkvcchtyg .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; } #sxkvcchtyg .gt_caption { padding-top: 4px; padding-bottom: 4px; } #sxkvcchtyg .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; } #sxkvcchtyg .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; } #sxkvcchtyg .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #sxkvcchtyg .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #sxkvcchtyg .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #sxkvcchtyg .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; } #sxkvcchtyg .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; } #sxkvcchtyg .gt_column_spanner_outer:first-child { padding-left: 0; } #sxkvcchtyg .gt_column_spanner_outer:last-child { padding-right: 0; } #sxkvcchtyg .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; } #sxkvcchtyg .gt_spanner_row { border-bottom-style: hidden; } #sxkvcchtyg .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; } #sxkvcchtyg .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; } #sxkvcchtyg .gt_from_md > :first-child { margin-top: 0; } #sxkvcchtyg .gt_from_md > :last-child { margin-bottom: 0; } #sxkvcchtyg .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; } #sxkvcchtyg .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; } #sxkvcchtyg .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; } #sxkvcchtyg .gt_row_group_first td { border-top-width: 2px; } #sxkvcchtyg .gt_row_group_first th { border-top-width: 2px; } #sxkvcchtyg .gt_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #sxkvcchtyg .gt_first_summary_row { border-top-style: solid; border-top-color: #D3D3D3; } #sxkvcchtyg .gt_first_summary_row.thick { border-top-width: 2px; } #sxkvcchtyg .gt_last_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #sxkvcchtyg .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #sxkvcchtyg .gt_first_grand_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; } #sxkvcchtyg .gt_last_grand_summary_row_top { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; } #sxkvcchtyg .gt_striped { background-color: rgba(128, 128, 128, 0.05); } #sxkvcchtyg .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #sxkvcchtyg .gt_footnotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #sxkvcchtyg .gt_footnote { margin: 0px; font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #sxkvcchtyg .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #sxkvcchtyg .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #sxkvcchtyg .gt_left { text-align: left; } #sxkvcchtyg .gt_center { text-align: center; } #sxkvcchtyg .gt_right { text-align: right; font-variant-numeric: tabular-nums; } #sxkvcchtyg .gt_font_normal { font-weight: normal; } #sxkvcchtyg .gt_font_bold { font-weight: bold; } #sxkvcchtyg .gt_font_italic { font-style: italic; } #sxkvcchtyg .gt_super { font-size: 65%; } #sxkvcchtyg .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; } #sxkvcchtyg .gt_asterisk { font-size: 100%; vertical-align: 0; } #sxkvcchtyg .gt_indent_1 { text-indent: 5px; } #sxkvcchtyg .gt_indent_2 { text-indent: 10px; } #sxkvcchtyg .gt_indent_3 { text-indent: 15px; } #sxkvcchtyg .gt_indent_4 { text-indent: 20px; } #sxkvcchtyg .gt_indent_5 { text-indent: 25px; } #sxkvcchtyg .katex-display { display: inline-flex !important; margin-bottom: 0.75em !important; } #sxkvcchtyg div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { height: 0px !important; } precio bolsas mixto suelto marca 41 21 14 barato 3 1 3 marca_propia 9 4 3 desconocido 6 1 0 fino 8 20 56 variable 32 52 25 Estos datos podemos examinarlos un rato y llegar a conclusiones, pero esta tabla no necesariamente es la mejor manera de mostrar patrones en los datos. Tampoco son muy útiles gráficas como la siguiente: ggplot(tabla_cruzada |> ungroup() |> mutate(price = fct_reorder(precio, prop)), aes(x = precio, y = prop, group = presentacion, colour = presentacion)) + geom_point() + coord_flip() + geom_line() En lugar de eso, calcularemos perfiles columna. Esto es, comparamos cada una de las columnas con la columna marginal (en la tabla de tipo de estilo de té): num_grupos <- n_distinct(te |> select(presentacion)) tabla <- te |> count(presentacion, precio) |> group_by(presentacion) |> mutate(prop_precio = (100 * n / sum(n))) |> group_by(precio) |> mutate(prom_prop = sum(prop_precio)/num_grupos) |> mutate(perfil = 100 * (prop_precio / prom_prop - 1)) tabla ## # A tibble: 17 × 6 ## # Groups: precio [6] ## presentacion precio n prop_precio prom_prop perfil ## <fct> <fct> <int> <dbl> <dbl> <dbl> ## 1 bolsas marca 70 41.2 25.4 61.8 ## 2 bolsas barato 5 2.94 2.26 30.1 ## 3 bolsas marca_propia 16 9.41 5.48 71.7 ## 4 bolsas desconocido 11 6.47 2.51 158. ## 5 bolsas fino 14 8.24 28.0 -70.6 ## 6 bolsas variable 54 31.8 36.3 -12.5 ## 7 mixto marca 20 21.3 25.4 -16.4 ## 8 mixto barato 1 1.06 2.26 -52.9 ## 9 mixto marca_propia 4 4.26 5.48 -22.4 ## 10 mixto desconocido 1 1.06 2.51 -57.6 ## 11 mixto fino 19 20.2 28.0 -27.8 ## 12 mixto variable 49 52.1 36.3 43.6 ## 13 suelto marca 5 13.9 25.4 -45.4 ## 14 suelto barato 1 2.78 2.26 22.9 ## 15 suelto marca_propia 1 2.78 5.48 -49.3 ## 16 suelto fino 20 55.6 28.0 98.4 ## 17 suelto variable 9 25 36.3 -31.1 tabla_perfil <- tabla |> select(presentacion, precio, perfil, pct = prom_prop) |> pivot_wider(names_from = presentacion, values_from = perfil, values_fill = list(perfil = -100.0)) if_profile <- function(x){ any(x < 0) & any(x > 0) } marcar <- marcar_tabla_fun(25, "red", "black") tab_out <- tabla_perfil |> arrange(desc(bolsas)) |> select(-pct, everything()) |> mutate(across(where(is.numeric), \\(x) round(x, 0))) |> mutate(across(where(if_profile), \\(x) marcar(x))) |> knitr::kable(format_table_salida(), escape = FALSE, digits = 0, booktabs = T) |> kableExtra::kable_styling(latex_options = c("striped", "scale_down"), bootstrap_options = c( "hover", "condensed"), full_width = FALSE) tab_out precio bolsas mixto suelto pct desconocido 158 -58 -100 3 marca_propia 72 -22 -49 5 marca 62 -16 -45 25 barato 30 -53 23 2 variable -12 44 -31 36 fino -71 -28 98 28 Leemos esta tabla como sigue: por ejemplo, los compradores de té suelto compran té fino a una tasa casi el doble (98%) que el promedio. También podemos graficar como: tabla_graf <- tabla_perfil |> ungroup() |> mutate(precio = fct_reorder(precio, bolsas)) |> select(-pct) |> pivot_longer(cols = -precio, names_to = "presentacion", values_to = "perfil") g_perfil <- ggplot(tabla_graf, aes(x = precio, xend = precio, y = perfil, yend = 0, group = presentacion)) + geom_point() + geom_segment() + facet_wrap(~presentacion) + geom_hline(yintercept = 0 , colour = "gray")+ coord_flip() g_perfil Observación: hay dos maneras de construir la columna promedio: tomando los porcentajes sobre todos los datos, o promediando los porcentajes de las columnas. Si los grupos de las columnas están desbalanceados, estos promedios son diferentes. Cuando usamos porcentajes sobre la población, perfiles columna y renglón dan el mismo resultado Sin embargo, cuando hay un grupo considerablemente más grande que otros, las comparaciones se vuelven vs este grupo particular. No siempre queremos hacer esto. Interpretación En el último ejemplo de tomadores de té utilizamos una muestra de personas, no toda la población de tomadores de té. Eso quiere decir que tenemos cierta incertidumbre de cómo se generalizan o no los resultados que obtuvimos en nuestro análisis a la población general. Nuestra respuesta depende de cómo se extrajo la muestra que estamos considerando. Si el mecanismo de extracción incluye algún proceso probabilístico, entonces es posible en principio entender qué tan bien generalizan los resultados de nuestro análisis a la población general, y entender esto depende de entender qué tanta variación hay de muestra a muestra, de todas las posibles muestras que pudimos haber extraido. En las siguientes secciones discutiremos estos aspectos, en los cuales pasamos del trabajo de “detective” al trabajo de “juez” en nuestro trabajo analítico. Suavizamiento loess Las gráficas de dispersión son la herramienta básica para describir la relación entre dos variables cuantitativas, y como vimos en ejemplo anteriores, muchas veces podemos apreciar mejor la relación entre ellas si agregamos una curva loess. Veamos un ejemplo, los siguientes datos muestran los premios ofrecidos y las ventas totales de una lotería a lo largo de 53 sorteos (las unidades son cantidades de dinero indexadas). Graficamos en escalas logarítmicas y agregamos una curva loess. # cargamos los datos load(here::here("data", "ventas_sorteo.Rdata")) ggplot(ventas.sorteo, aes(x = premio, y = ventas.tot.1)) + geom_point() + geom_smooth(method = "loess", span = 0.6, method.args = list(degree = 1), se = FALSE) + scale_x_log10(breaks = c(20000, 40000, 80000)) + scale_y_log10(breaks = c(10000, 15000, 22000, 33000)) El patrón no era difícil de ver en los datos originales, sin embargo, la curva lo hace más claro, el logaritmo de las ventas tiene una relación no lineal con el logaritmo del premio: para premios no muy grandes no parece haber gran diferencia, pero cuando los premios empiezan a crecer por encima de 20,000, las ventas crecen más rápidamente que para premios menores. Este efecto se conoce como bola de nieve, y es frecuente en este tipo de loterías. Antes de adentrarnos a los modelos loess comenzamos explicando cómo se ajustan familias paramétricas de curvas a conjuntos de datos dados. El modelo de regresion lineal ajusta una recta a un conjunto de datos. Por ejemplo, consideremos la familia \\[f_{a,b}(x) = a x + b,\\] para un conjunto de datos bivariados \\(\\{ (x_1, y_1), \\ldots, (x_N, y_N)\\}\\). Buscamos encontrar \\(a\\) y \\(b\\) tales que \\(f_{a,b}\\) de un ajuste óptimo a los datos. Para esto, se minimiza la suma de errores cuadráticos \\[\\frac1N \\sum_{n = 1}^N ( y_n - a x_n - b)^2.\\] En este caso, las constantes \\(a\\) y \\(b\\) se pueden encontrar diferenciando la función de mínimos cuadrados. Nótese que podemos repetir el argumento con otras familias de funciones (por ejemplo cuadráticas). ggplot(ventas.sorteo, aes(x = premio, y = ventas.tot.1)) + geom_point() + geom_smooth(method = "lm", se = FALSE) + scale_x_log10(breaks = c(20000, 40000, 80000)) + scale_y_log10(breaks = c(10000, 15000, 22000, 33000)) Si observamos la gráfica notamos que este modelo lineal (en los logaritmos) no resumen adecuadamente estos datos. Podríamos experimentar con otras familias (por ejemplo, una cuadrática o cúbica, potencias, exponenciales, etc.); sin embargo, en la etapa exploratoria es mejor tomar una ruta de ajuste más flexible y robusta. Regresión local nos provee de un método con estas características: Curvas loess (regresión local): Una manera de mejorar la flexibilidad de los modelos lineales es considerar rectas de manera local. Es decir, en cada \\(x\\) posible consideramos cuál es la recta que mejor ajusta a los datos, considerando solamente valores de \\(x_n\\) que están cercanos a \\(x\\). La siguiente gráfica muestra qué recta se ajusta alrededor de cada punto, y cómo queda el suavizador completo, con distintos valores de suavizamiento. El tono de los puntos indican en cada paso que ventana de datos es considerada: Escogiendo de los parámetros. El parámetro de suavizamiento se encuentra por ensayo y error. La idea general es que debemos encontrar una curva que explique patrones importantes en los datos (que ajuste los datos) pero que no muestre variaciones a escalas más chicas difíciles de explicar (que pueden ser el resultado de influencias de otras variables, variación muestral, ruido o errores de redondeo, por ejemplo). En el proceso de prueba y error iteramos el ajuste y en cada paso hacemos análisis de residuales, con el fin de seleccionar un suavizamiento adecuado. En lugar de usar ajustes locales lineales, podemos usar ajustes locales cuadráticos que nos permiten capturar formas locales cuadráticas sin tener que suavizar demasiado poco: Opcional: cálculo del suavizador La idea es producir ajustes locales de rectas o funciones lineales o cuadráticas. Consideremos especificar dos parámetros: Parámetro de suavizamiento \\(\\alpha\\): toma valores en \\((0,1)\\), cuando \\(\\alpha\\) es más grande, la curva ajustada es más suave. Grado de los polinomios locales que ajustamos \\(\\lambda\\): generalmente se toma \\(\\lambda=1,2\\). Entonces, supongamos que los datos están dados por \\((x_1,y_1), \\ldots, (x_N, y_N)\\), y sean \\(\\alpha\\) un parámetro de suavizamiento fijo, y \\(\\lambda=1\\). Denotamos como \\(\\hat{g}(x)\\) la curva loess ajustada, y como \\(w_n(x)\\) a una función de peso (que depende de x) para la observación \\((x_n, y_n)\\). Para poder calcular \\(w_n(x)\\) debemos comenzar calculando \\(q=\\lfloor{N\\alpha}\\rfloor\\) que suponemos mayor que uno, esta \\(q\\) es el número de puntos que se utilizan en cada ajuste local. Ahora definimos la función tricubo: \\[\\begin{align} T(u)=\\begin{cases} (1-|u|^3)^3, & \\text{para $|u| < 1$}.\\\\ 0, & \\text{en otro caso}. \\end{cases} \\end{align}\\] entonces, para el punto \\(x\\) definimos el peso correspondiente al dato \\((x_n,y_n)\\), denotado por \\(w_n(x)\\) como: \\[w_n(x)=T\\bigg(\\frac{|x-x_n|}{d_q(x)}\\bigg)\\] donde \\(d_q(x)\\) es el valor de la \\(q\\)-ésima distancia más chica (la más grande entre las \\(q\\) más chicas) entre los valores \\(|x-x_j|\\), \\(j=1,\\ldots,N\\). De esta forma, las observaciones \\(x_n\\) reciben más peso cuanto más cerca estén de \\(x\\). En palabras, de \\(x_1,...,x_N\\) tomamos los \\(q\\) datos más cercanos a \\(x\\), que denotamos \\(x_{i_1}(x) \\leq x_{i_2}(x) \\leq \\cdots \\leq x_{i_q}(x)\\). Los re-escalamos a \\([0,1]\\) haciendo corresponder \\(x\\) a \\(0\\) y el punto más alejado de \\(x\\) (que es \\(x_{i_q}\\)) a 1. Aplicamos el tricubo (gráfica de abajo), para encontrar los pesos de cada punto. Los puntos que están a una distancia mayor a \\(d_q(x)\\) reciben un peso de cero, y los más cercanos un peso que depende de que tan cercanos están a \\(x\\). Nótese que \\(x\\) es el punto ancla en dónde estamos ajustando la regresión local. tricubo <- function(x) { ifelse(abs(x) < 1, (1 - abs(x) ^ 3) ^ 3, 0) } curve(tricubo, from = -1.5, to = 1.5) Finalmente, para cada valor de \\(x_k\\) que está en el conjunto de datos \\(\\{x_1,...,x_n\\}\\), ajustamos una recta de mínimos cuadrados ponderados por los pesos \\(w_n(x)\\), es decir, minimizamos (en el caso lineal): \\[\\sum_{i=1}^nw_n(x_k)(y_i-ax_n-b)^2.\\] Observaciones: Cualquier función (continua y quizás diferenciable) con la forma de flan del tricubo que se desvanece fuera de \\((-1,1),\\) es creciente en \\((-1,0)\\) y decreciente en \\((0, 1)\\) es un buen candidato para usarse en lugar del tricubo. La razón por la que escogemos precisamente esta forma algebráica no tiene que ver con el análisis exploratorio, sino con las ventajas teóricas adicionales que tiene en la inferencia. El caso \\(\\lambda=2\\) es similar. La única diferencia es en el paso de ajuste, donde usamos funciones cuadráticas, y obtendríamos entonces tres familias de parámetros \\(a(x_k), b_1(x_k), b_2(x_k),\\) para cada \\(k \\in \\{1, \\ldots, N\\}\\). Caso de estudio: nacimientos en México Podemos usar el suavizamiento loess para entender y describir el comportamiento de series de tiempo, en las cuáles intentamos entender la dependencia de una serie de mediciones indexadas por el tiempo. Típicamente es necesario utilizar distintas componentes para describir exitosamente una serie de tiempo, y para esto usamos distintos tipos de suavizamientos. Veremos que distintas componentes varían en distintas escalas de tiempo (unas muy lentas, como la tendencia, otras más rapidamente, como variación quincenal, etc.). Este caso de estudio esta basado en un análisis propuesto por A. Vehtari y A. Gelman, junto con un análisis de serie de tiempo de William S. Cleveland (1993). En nuestro caso, usaremos los datos de nacimientos registrados por día en México desde 1999. Los usaremos para contestar las preguntas: ¿cuáles son los cumpleaños más frecuentes? y ¿en qué mes del año hay más nacimientos? Podríamos utilizar una gráfica popular (ver por ejemplo esta visualización) como: Sin embargo, ¿cómo criticarías este análisis desde el punto de vista de los tres primeros principios del diseño analítico? ¿Las comparaciones son útiles? ¿Hay aspectos multivariados? ¿Qué tan bien explica o sugiere estructura, mecanismos o causalidad? Datos de natalidad para México library(lubridate) library(ggthemes) theme_set(theme_minimal(base_size = 14)) natalidad <- read_rds("./data/nacimientos/natalidad.rds") |> mutate(dia_semana = weekdays(fecha)) |> mutate(dia_año = yday(fecha)) |> mutate(año = year(fecha)) |> mutate(mes = month(fecha)) |> ungroup() |> mutate(dia_semana = recode(dia_semana, Monday = "Lunes", Tuesday = "Martes", Wednesday = "Miércoles", Thursday = "Jueves", Friday = "Viernes", Saturday = "Sábado", Sunday = "Domingo")) |> #necesario pues el LOCALE puede cambiar mutate(dia_semana = recode(dia_semana, lunes = "Lunes", martes = "Martes", miércoles = "Miércoles", jueves = "Jueves", viernes = "Viernes", sábado = "Sábado", domingo = "Domingo")) |> mutate(dia_semana = fct_relevel(dia_semana, c("Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"))) Consideremos los datos agregados del número de nacimientos (registrados) por día desde 1999 hasta 2016. Un primer intento podría ser hacer una gráfica de la serie de tiempo. Sin embargo, vemos que no es muy útil: Hay varias características que notamos. Primero, parece haber una tendencia ligeramente decreciente del número de nacimientos a lo largo de los años. Segundo, la gráfica sugiere un patrón anual. Y por último, encontramos que hay dispersión producida por los días de la semana. Sólo estas características hacen que la comparación entre días sea difícil de realizar. Supongamos que comparamos el número de nacimientos de dos miércoles dados. Esa comparación será diferente dependiendo: del año donde ocurrieron, el mes donde ocurrieron, si semana santa ocurrió en algunos de los miércoles, y así sucesivamente. Como en nuestros ejemplos anteriores, la idea del siguiente análisis es aislar las componentes que observamos en la serie de tiempo: extraemos componentes ajustadas, y luego examinamos los residuales. En este caso particular, asumiremos una descomposición aditiva de la serie de tiempo (William S. Cleveland 1993). En el estudio de series de tiempo una estructura común es considerar el efecto de diversos factores como tendencia, estacionalidad, ciclicidad e irregularidades de manera aditiva. Esto es, consideramos la descomposición \\[\\begin{align} y(t) = f_{t}(t) + f_{e}(t) + f_{c}(t) + \\varepsilon. \\end{align}\\] Una estrategia de ajuste, como veremos más adelante, es proceder de manera modular. Es decir, se ajustan los componentes de manera secuencial considerando los residuales de los anteriores. Tendencia Comenzamos por extraer la tendencia, haciendo promedios loess (William S. Cleveland 1979) con vecindades relativamente grandes. Quizá preferiríamos suavizar menos para capturar más variación lenta, pero si hacemos esto en este punto empezamos a absorber parte de la componente anual: mod_1 <- loess(n ~ as.numeric(fecha), data = natalidad, span = 0.2, degree = 1) datos_dia <- natalidad |> mutate(ajuste_1 = fitted(mod_1)) |> mutate(res_1 = n - ajuste_1) Notemos que a principios de 2000 el suavizador está en niveles de alrededor de 7000 nacimientos diarios, hacia 2015 ese número es más cercano a unos 6000. Componente anual Al obtener la tendencia podemos aislar el efecto a largo plazo y proceder a realizar mejores comparaciones (por ejemplo, comparar un día de 2000 y de 2015 tendria más sentido). Ahora, ajustamos los residuales del suavizado anterior, pero con menos suavizamiento. Así evitamos capturar tendencia: mod_anual <- loess(res_1 ~ as.numeric(fecha), data = datos_dia, degree = 2, span = 0.005) datos_dia <- datos_dia |> mutate(ajuste_2 = fitted(mod_anual)) |> mutate(res_2 = res_1 - ajuste_2) Día de la semana Hasta ahora, hemos aislado los efectos por plazos largos de tiempo (tendencia) y hemos incorporado las variaciones estacionales (componente anual) de nuestra serie de tiempo. Ahora, veremos cómo capturar el efecto por día de la semana. En este caso, podemos hacer suavizamiento loess para cada serie de manera independiente datos_dia <- datos_dia |> group_by(dia_semana) |> nest() |> mutate(ajuste_mod = map(data, ~ loess(res_2 ~ as.numeric(fecha), data = .x, span = 0.1, degree = 1))) |> mutate(ajuste_3 = map(ajuste_mod, fitted)) |> select(-ajuste_mod) |> unnest(cols = c(data, ajuste_3)) |> mutate(res_3 = res_2 - ajuste_3) |> ungroup() Residuales Por último, examinamos los residuales finales quitando los efectos ajustados: ## `geom_smooth()` using formula = 'y ~ x' Observación: nótese que la distribución de estos residuales presenta irregularidades interesantes. La distribución es de colas largas, y no se debe a unos cuantos datos atípicos. Esto generalmente es indicación que hay factores importantes que hay que examinar mas a detalle en los residuales: Reestimación Cuando hacemos este proceso secuencial de llevar el ajuste a los residual, a veces conviene iterarlo. La razón es que en una segunda o tercera pasada podemos hacer mejores estimaciones de cada componente, y es posible suavizar menos sin capturar componentes de más alta frecuencia. Así que podemos regresar a la serie original para hacer mejores estimaciones, más suavizadas: # Quitamos componente anual y efecto de día de la semana datos_dia <- datos_dia |> mutate(n_1 = n - ajuste_2 - ajuste_3) # Reajustamos mod_1 <- loess(n_1 ~ as.numeric(fecha), data = datos_dia, span = 0.02, degree = 2, family = "symmetric") Y ahora repetimos con la componente de día de la semana: Análisis de componentes Ahora comparamos las componentes estimadas y los residuales en una misma gráfica. Por definición, la suma de todas estas componentes da los datos originales. Este último paso nos permite diversas comparaciones que explican la variación que vimos en los datos. Una gran parte de los residuales está entre \\(\\pm 250\\) nacimientos por día. Sin embargo, vemos que las colas tienen una dispersión mucho mayor: quantile(datos_dia$res_6, c(00, .01,0.05, 0.10, 0.90, 0.95, 0.99, 1)) |> round() ## 0% 1% 5% 10% 90% 95% 99% 100% ## -2238 -1134 -315 -202 188 268 516 2521 ¿A qué se deben estas colas tan largas? Viernes 13? Podemos empezar con una curosidad. Los días Viernes o Martes 13, ¿nacen menos niños? Nótese que fue útil agregar el indicador de Semana santa por el Viernes 13 de Semana Santa que se ve como un atípico en el panel de los viernes 13. Residuales: antes y después de 2006 Veamos primero una agregación sobre los años de los residuales. Lo primero es observar un cambio que sucedió repentinamente en 2006: La razón es un cambio en la ley acerca de cuándo pueden entrar los niños a la primaria. Antes era por edad y había poco margen. Ese exceso de nacimientos son reportes falsos para que los niños no tuvieran que esperar un año completo por haber nacido unos cuantos días después de la fecha límite. Otras características que debemos investigar: Efectos de Año Nuevo, Navidad, Septiembre 16 y otros días feriados como Febrero 14. Semana santa: como la fecha cambia, vemos que los residuales negativos tienden a ocurrir dispersos alrededor del día 100 del año. Otros días especiales: más de residuales Ahora promediamos residuales (es posible agregar barras para indicar dispersión a lo largo de los años) para cada día del año. Podemos identificar ahora los residuales más grandes: se deben, por ejemplo, a días feriados, con consecuencias adicionales que tienen en días ajuntos (excesos de nacimientos): Semana santa Para Semana Santa tenemos que hacer unos cálculos. Si alineamos los datos por días antes de Domingo de Pascua, obtenemos un patrón de caída fuerte de nacimientos el Viernes de Semana Santa, y la característica forma de “valle con hombros” en días anteriores y posteriores estos Viernes. ¿Por qué ocurre este patrón? Nótese un defecto de nuestro modelo: el patrón de “hombros” alrededor del Viernes Santo no es suficientemente fuerte para equilibrar los nacimientos faltantes. ¿Cómo podríamos mejorar nuestra descomposición? Referencias "],["tipos-de-estudio-y-experimentos.html", "Sección 2 Tipos de estudio y experimentos Muestreo aleatorio Pero si no podemos hacer muestreo aleatorio? El estimador estándar Experimentos tradicionales Bloqueo Variables desconocidas Aleatorizando el tratamiento Selección de unidades y tratamiento Asignación natural del tratamiento", " Sección 2 Tipos de estudio y experimentos Motivación Pregunta de entrevista de Google (Chihara and Hesterberg 2018) Imagina que eres consultor y te preguntan lo siguiente (ver siguiente figura): Estoy haciendo una comparación de antes y después donde la hipótesis alternativa es pre.media.error > post.media.error. La distribución de ambas muestras es sesgada a la derecha. ¿Qué prueba me recomiendas para ésta situación? Figure 2.1: Error CPR, gráfica de densidad. Far better an approximate answer to the right question, which is often vague, than an exact answer to the wrong question, which can always be made precise. — John Tukey La siguiente imagen Roger Peng representa una situación común a la que se enfrenta el analista de datos, y se desarrolló en el contexto de preguntas vagas. En el esquema hay tres caminos: uno es uno ideal que pocas veces sucede, otro produce respuestas poco útiles pero es fácil, y otro es tortuoso pero que caracteriza el mejor trabajo de análisis de datos: Figure 2.2: Adaptado de R. Peng: Tukey, design thinking and better questions. Ejemplos: Alguien nos pregunta cuáles son las tiendas que mas venden de una cadena. Podríamos consultar bases de datos, hacer extracciones, definir periodos, etc. y reportar el promedio de ventas en el último mes, esta respuesta probablemente es poco útil. Nos damos cuenta, por ejemplo, porque la peor tienda es una que abrió hace relativamente poco, y la mejor es una de las tiendas más grandes que está en una zona de tráfico de alto costo. Una pregunta más interesante es, ¿qué equipos de ventas tienen mejor desempeño? ¿Cuánto aporta tener una cafetería dentro de la tienda en términos de ventas?, etc. Proceso Generador de Datos Entre las preguntas que se debe hacer el analista de datos una fundamental es entender el proceso generador de datos, pues esto determinará que otras preguntas son relevantes, y que análisis son adecuados, tanto en términos prácticos como estadísticos. La inferencia estadística busca hacer afirmaciones, cuantificadas de manera probabilista, acerca de datos que no tenemos, usando regularidades y conocimiento de datos que sí tenemos disponibles y métodos cuantitativos. Para hacer afirmaciones inferenciales eficientes y bien calibradas (con garantías estadísticas de calibración) a preguntas donde queremos generalizar de muestra a población, se requiere conocer con precisión el proceso que genera los datos muestrales. Esto incluye saber con detalle cómo se seleccionaron los datos a partir de los que se quiere hacer inferencia. En este caso, eficiente quiere decir que aprovechamos toda la información que está en los datos observados de manera que nuestros rangos de incertidumbre son lo más chico posibles (además de estar correctamente calibrados). Por su parte, probabilísticamente bien calibrados se refiere a que, lo que decimos que puede ocurrir con 10% de probabilidad ocurre efectivamente 1 de cada 10 veces, si decimos 20% entonces ocurre 2 de 20, etc. Veremos que para muestras dadas naturalmente, a veces es muy difiícil entender a fondo el proceso que generó la muestra y por tanto no tenemos las garantías de eficiencia y calibración. Ejemplo: Prevalencia de anemia Supongamos que nos interesa conocer el porcentaje de menores en edad escolar, (entre 6 y 15 años), con anemia en México. La fuente de datos disponible corresponde a registros del IMSS de hospitalizaciones de menores, ya sea por anemia o por otra causa (infecciones gastrointestinales, apendicitis, tratamiento de leucemia, …), se registró si el menor tenía anemia. En nuestra muestra el 47% de los niños tiene anemia. head(paciente) #> # A tibble: 6 × 4 #> edad padecimiento sexo anemia #> <int> <chr> <chr> <int> #> 1 8 picadura alacrán mujer 0 #> 2 10 infección intestinal hombre 1 #> 3 7 mordedura de perro hombre 1 #> 4 8 asma hombre 1 #> 5 13 infección intestinal mujer 0 #> 6 7 picadura alacrán hombre 0 ¿Qué nos dice esta cantidad acerca de la anemia en la población? ¿Podemos hacer inferencia estadística? ¿Cómo calculamos intervalos de confianza? # Si calculo el error estándar de la p estimada como sigue, es correcto? p <- mean(paciente$anemia) sqrt(p * (1 - p) / 5000) #> [1] 0.007060751 Muestreo aleatorio En la situación ideal diseñaríamos una muestra aleatoria de menores de edad, por ejemplo, utilizando el registro en educación primaria de la SEP, y mediríamos la prevalencia de anemia en la muestra, usaríamos esta muestra para estimar la prevalencia en la población y tendríamos además las herramientas para medir la incertidumbre de nuestra estimación (reportar intervalos, o errores estándar). El elemento clave, es la aleatorización en la selección de la muestra, la idea es distribuir los efecros desconcidos o no controlables que pueden introducir sesgos o variabilidad no conocida en los resultados. Pero si no podemos hacer muestreo aleatorio? En el caso de prevalencia de anemia, discutiendo con médicos e investigadores nos informan que la anemia se presenta en tasas más altas en niños más chicos. paciente |> count(edad) |> mutate(prop = round(100 * n / sum(n))) #> # A tibble: 10 × 3 #> edad n prop #> <int> <int> <dbl> #> 1 6 1001 20 #> 2 7 931 19 #> 3 8 980 20 #> 4 9 445 9 #> 5 10 484 10 #> 6 11 489 10 #> 7 12 246 5 #> 8 13 239 5 #> 9 14 90 2 #> 10 15 95 2 Y consultando con las proyecciones de población notamos que los niños chicos están sobrerepresentados en la muestra. Lo que nos hace considerar que debemos buscar una manera de ponderar nuestras observaciones para que reflejen a la población. Más aún, investigamos que algunas enfermedades están asociadas a mayor prevalencia de anemia: paciente |> count(padecimiento) |> arrange(-n) #> # A tibble: 7 × 2 #> padecimiento n #> <chr> <int> #> 1 infección respiratoria 745 #> 2 mordedura de perro 723 #> 3 úlcera 723 #> 4 asma 713 #> 5 apendcitis 704 #> 6 picadura alacrán 701 #> 7 infección intestinal 691 Utilizamos esta información para modelar y corregir nuestra estimación original. Por ejemplo con modelos de regresión. Sin embargo, debemos preguntarnos: ¿Hay más variables qué nos falta considerar? Ejemplo: Policías y tráfico Supongamos que nos preguntan en cuánto reduce un policía el tráfico en un crucero grande de la ciudad. La cultura popular ha establecido que los policías en cruceros hacen más tráfico porque no saben mover los semáforos. Nosotros decidimos buscar unos datos para entender esto. Escogemos entonces un grupo de cruceros problemáticos, registramos el tráfico cuando visitamos, y si había un policía o no. Después de este esfuerzo, obtenemos los siguientes datos: #> # A tibble: 10 × 2 #> # Groups: policia [2] #> policia tiempo_espera_min #> <int> <dbl> #> 1 0 2.27 #> 2 0 2.65 #> 3 0 3.4 #> 4 0 0.39 #> 5 0 1.1 #> 6 1 10.8 #> 7 1 4.67 #> 8 1 7.77 #> 9 1 6.3 #> 10 1 6.99 Lo que sabemos ahora es que la presencia de un policía es indicador de tráfico alto. El análisis prosiguiría calculando medias y medidas de error (escogimos una muestra aleatoria): Si somos ingenuos, entonces podríamos concluir que los policías efectivamente empeoran la situación cuando manipulan los semáforos, y confirmaríamos la sabiduría popular. Para juzgar este argumento desde el punto de vista causal, nos preguntamos primero: ¿Cuáles son los contrafactuales (los contrafactuales explican que pasaría si hubiéramos hecho otra cosa que la que efectivamente hicimos) de las observaciones? Efectos causales y el esquema de resultados potenciales Consideramos un tratamiento binario: Se manda policía o no se manda policía. Un resultado potencial es aquél que se observaría bajo un tratamiento particular. En cada semáforo, a una hora dada, hay dos resultados potenciales, uno por cada valor del tratamiento: \\(y_1:\\) tiempo de espera si se envía policía. \\(y_0:\\) tiempo de espera si no se envía policía. Para cada semáforo, en el momento de registro, uno observa únicamente uno de los dos resultados potenciales. El resultado no observado se conoce como resultado contrafactual. El estimador estándar A la comparación anterior - la diferencia de medias de tratados y no tratados - le llamamos usualmente el estimador estándar del efecto causal. Muchas veces este es un estimador malo del efecto causal. En nuestro ejemplo, para llegar a la conclusión errónea que confirma la sabiduría popular, hicimos un supuesto importante: En nuestra muestra, los casos con policía actúan como contrafactuales de los casos sin policía. Asi que asumimos que los casos con policía y sin policía son similares, excepto por la existencia o no de policía. En nuestro ejemplo, quizá un analista más astuto nota que tienen categorías históricas de qué tan complicado es cada crucero. Junta a sus datos, y obtiene: #> # A tibble: 10 × 3 #> # Groups: policia [2] #> policia tiempo_espera_min categoria #> <int> <dbl> <fct> #> 1 0 2.27 Fluido #> 2 0 2.65 Fluido #> 3 0 3.4 Típico #> 4 0 0.39 Fluido #> 5 0 1.1 Fluido #> 6 1 10.8 Complicado #> 7 1 4.67 Típico #> 8 1 7.77 Complicado #> 9 1 6.3 Complicado #> 10 1 6.99 Típico El analista argumenta entonces que los policías se enviaron principalmente a cruceros que se consideran Complicados según datos históricos. Esto resta credibilidad a la comparación que hicimos inicialmente: La comparación del estimador estándar no es de peras con peras: estamos comparando qué efecto tienen los policías en cruceros difíciles, con cruceros no difíciles donde no hay policía. La razón de esto es que el proceso generador de los datos incluye el hecho de que no se envían policías a lugares donde no hay tráfico. ¿Cómo producir contrafactuales para hacer la comparación correcta? Experimentos tradicionales Idealmente, quisiéramos observar un mismo crucero en las dos condiciones: con y sin policías. Esto no es posible. En un experimento “tradicional”, como nos lo explicaron en la escuela, nos aproximamos a esto preparando dos condiciones idénticas, y luego alteramos cada una de ellas con nuestra intervención. Si el experimento está bien hecho, esto nos da observaciones en pares, y cada quien tiene su contrafactual. La idea del experimiento tradicional es controlar todos los factores que intervienen en los resultados, y sólo mover el tratamiento para producir los contrafactuales. Más en general, esta estrategia consiste en hacer bloques de condiciones, donde las condiciones son prácticamente idénticas dentro de cada bloque. Comparamos entonces unidades tratadas y no tratadas dentro de cada bloque. Por ejemplo, si queremos saber si el tiempo de caída libre es diferente para un objeto más pesado que otro, prepararíamos dos pesos con el mismo tamaño pero de peso distinto. Soltaríamos los dos al mismo tiempo y compararíamos el tiempo de caída de cada uno. En nuestro caso, como es usual en problemas de negocio o sociales, hacer esto es considerablemente más difícil. No podemos “preparar” cruceros con condiciones idénticas. Sin embargo, podríamos intentar \\(bloquear\\) los cruceros según información que tenemos acerca de ellos, para hacer más comparaciones de peras con peras. Bloqueo Podemos acercanos en lo posible a este ideal de experimentación usando información existente. En lugar de hacer comparaciones directas entre unidades que recibieron el tratamiento y las que no (que pueden ser diferentes en otros aspectos, como vimos arriba), podemos refinar nuestras comparaciones bloquéandolas con variables conocidas. En el ejemplo de los policías, podemos hacer lo siguiente: dentro de cada categoría de cruceros (fluido, típico o complicado), tomaremos una muestra de cruceros, algunos con policía y otros sin. Haremos comparaciones dentro de cada categoría. Obtenemos una muestra con estas características (6 casos en cada categoría de crucero, 3 con policía y 3 sin policía): categoria policia n Fluido 0 3 Fluido 1 3 Típico 0 3 Típico 1 3 Complicado 0 3 Complicado 1 3 Y ahora hacemos comparaciones dentro de cada bloque creado por categoría: #> # A tibble: 3 × 3 #> # Groups: categoria [3] #> categoria `policia =0` `policia =1` #> <fct> <dbl> <dbl> #> 1 Fluido 2.1 0.8 #> 2 Típico 5.6 4.2 #> 3 Complicado 10.4 8.6 Y empezamos a ver otra imagen en estos datos: comparando tipos e cruceros similares, los que tienen policía tienen tiempos de espera ligeramente más cortos. ¿Hemos termniado? ¿Podemos concluir que el efecto de un policía es beneficiosos pero considerablemente chico? ¿Qué problemas puede haber con este análisis? Variables desconocidas El problema con el análisis anterior es que controlamos por una variable que conocemos, pero muchas otras variables pueden estar ligadas con el proceso de selección de cruceros para enviar policías. Por ejemplo, envían o policías a cruceros Típicos solo cuando reportan mucho tráfico. No envían a un polícia a un crucero Complicado si no presenta demasiado tráfico. Existen otras variables desconocidas que los tomadores de decisiones usan para enviar a los policías. En este caso, por ejemplo, los expertos hipotéticos nos señalan que hay algunos cruceros que aunque problemáticos, a veces su tráfico se resuelve rápidamente, mientras que otros tienen tráfico más persistente, y prefieren enviar policías a los de tráfico persistente. La lista de cruceros persistentes están en una hoja de excel que se comparte de manera informal. En resumen, no tenemos conocimiento detallado del proceso generador de datos en cuanto a cómo se asignan los policías a los cruceros. Igual que en la sección anterior, podemos cortar esta complejidad usando aleatorización. Nótese que los expertos no están haciendo nada malo: en su trabajo están haciendo el mejor uso de los recursos que tienen. El problema es que por esa misma razón no podemos saber el resultado de sus esfuerzos, y si hay maneras de optimizar la asignación que hacen actualmente. Aleatorizando el tratamiento Tomamos la decisión entonces de hacer un experimento que incluya aletorización. En un dia particular, escogeremos algunos cruceros. Dicidimos usar solamente cruceros de la categoría Complicada y Típica, pues esos son los más interesantes para hacer intervenciones. Usaremos un poco de código para entener el detalle: en estos datos, tenemos para cada caso los dos posibles resultados hipotéticos \\(y_0\\) y \\(y_1\\) (con policia y sin policia). En el experimento asignamos el tratamiento al azar: muestra_exp <- trafico_tbl |> filter(categoria != "Fluido") |> sample_n(200) |> # asignar tratamiento al azar, esta es nuestra intervención: mutate(tratamiento_policia = rbernoulli(length(y_0), 0.5)) |> # observar resultado mutate(tiempo_espera_exp = ifelse(tratamiento_policia == 1, y_1, y_0)) Nótese la diferencia si tomamos la asignación natural del tratamiento (policía o no): set.seed(134) muestra_natural <- trafico_tbl |> filter(categoria != "Fluido") |> sample_n(200) |> # usamos el tratamiento que se asignó # policia indica si hubo o no policía en ese crucero # observar resultado mutate(tiempo_espera_obs = ifelse(policia == 1, y_1, y_0)) Resumimos nuestros resultados del experimento son: #> # A tibble: 2 × 3 #> # Groups: categoria [2] #> categoria `policia=0` `policia=1` #> <fct> <dbl> <dbl> #> 1 Típico 6.24 4.97 #> 2 Complicado 15.8 8.47 Sin embargo, la muestra natural da: #> # A tibble: 2 × 3 #> # Groups: categoria [2] #> categoria `policia=0` `policia=1` #> <fct> <dbl> <dbl> #> 1 Típico 5.49 4.35 #> 2 Complicado 10.8 8.93 ¿Cuál de los dos análisis da la respuesta correcta a la pregunta: ayudan o no los policías a reducir el tráfico en los cruceros problemáticos? El experimento establece que un policía en promedio reduce a la mitad el tiempo de espera en un crucero complicado Selección de unidades y tratamiento Vimos dos tipos de inferencia que requieren distintos diseños de estudio: a poblaciones (ejemplo anemia) y causal (ejemplo policías). En el escenario ideal de cada uno de estos ejemplos requerimos un mecanismo de aleatorización, sin embargo, la aleatorización requerida en cada caso es distinta y distinguir esto es fundamental para entender las inferencias que podemos hacer en distintos escenarios. Inferencia estadística de acuerdo al tipo del diseño (Ramsey and Schafer (2012)). El cuadro arriba a la izquierda es donde el análisis es más simple y los resultados son más fáciles de interpretar. En este escenario don de la aleatorización es tanto en unidades como en grupos no hacen falta supuestos adicionales para tener las garantías de métodos de inferencia. Es posible hacer análisis fuera de este cuadro, pero el proceso es más complicado, requieren más supuestos, conocimiento del dominio y habilidades de análisis. En general resultan conclusiones menos sólidas. Muchas veces no nos queda otra más que trabajar fuera del cuadro ideal. El punto crucial para entender las medidas de incertidumbre estadística es visualizar de manera hipotética, replicaciones del estudio y las condiciones que llevaron a la selección de la muestra. Esto es, entender el proceso generador de datos e imaginar replicarlo. Ubica los siguientes tipos de análisis: Pruebas clínicas para medicinas Analizar cómo afecta tener seguro médico a los ingresos, usando datos del ENIGH. Estimación de retorno sobre inversión en modelos de marketing mix. Asignación natural del tratamiento Cuando consideramos un sistema donde se “asignan” tratamientos de manera natural, generalmente los tratamientos se asignan bajo un criterio de optimización o conveniencia (por ejemplo los policías a cruceros problemáticos). La cara buena de este hecho es que de alguna forma los resultados están intentando optimizarse, y la gente está haciendo su trabajo. La cara mala de este hecho es que no podemos evaluar de manera simple la efectividad de los tratamientos. Y esto hace difícil optimizar de forma cuantificable los procesos, o entender qué funciona y qué no. Referencias "],["pruebas-de-hipótesis.html", "Sección 3 Pruebas de hipótesis Comparación con poblaciones de referencia Comparando distribuciones Prueba de permutaciones y el lineup Comparaciones usando lineup (continuación) Prueba de permutaciones para proporciones Pruebas de hipótesis tradicionales Tomadores de té (continuación) Pruebas de permutación: implementación. Ejemplo: tiempos de fusión Ejemplo: tiempos de fusión (continuación) Separación de grupos La “crisis de replicabilidad” El jardín de los senderos que se bifurcan Ejemplo: decisiones de análisis y valores p Alternativas o soluciones", " Sección 3 Pruebas de hipótesis Las primeras técnicas inferenciales que veremos intentan contestar la siguiente pregunta: Si observamos cierto patrón en los datos, ¿cómo podemos cuantificar la evidencia de que es un patrón notable y no sólo debido a fluctuaciones en los datos particulares que tenemos? ¿Cómo sabemos que no estamos sobreinterpretando esas fluctuaciones? Por ejemplo: Un sistema tiene cierto comportamiento “usual” para el cual tenemos datos históricos. El sistema presenta fluctuaciones en el tiempo. Observamos la última salida de nuestro sistema. Naturalmente, tiene fluctuaciones. ¿Esas fluctuaciones son consistentes con la operación usual del sistema? ¿Existe evidencia para pensar que algo en el sistema cambió? Comparación con poblaciones de referencia En las prueba de hipótesis, tratamos de construir distribuciones de referencia para comparar resultados que obtengamos con un “estándar” de variación, y juzgar si nuestros resultados son consistentes con la referencia o no (Box et al. 1978). En algunos casos, ese estándar de variación puede construirse con datos históricos. Ejemplo Supongamos que estamos considerando cambios rápidos en una serie de tiempo de alta frecuencia. Hemos observado la serie en su estado “normal” durante un tiempo considerable, y cuando observamos nuevos datos quisiéramos juzgar si hay indicaciones o evidencia en contra de que el sistema sigue funcionando de manera similar. Digamos que monitoreamos ventanas de tiempo de tamaño 20 y necesitamos tomar una decisión. Abajo mostramos cinco ejemplos donde el sistema opera normalmente, que muestra la variabilidad en el tiempo en ventanas cortas del sistema. Ahora suponemos que obtenemos una nueva ventana de datos. ¿Hay evidencia en contra de que el sistema sigue funcionando de manera similar? Nuestra primera inclinación debe ser comparar: en este caso, compararamos ventanas históricas con nuestra nueva serie: # usamos datos simulados para este ejemplo set.seed(8812) historicos <- simular_serie(2000) ¿Vemos algo diferente en los datos nuevos (el panel de color diferente)? Indpendientemente de la respuesta, vemos que hacer este análisis de manera tan simple no es siempre útil: seguramente podemos encontrar maneras en que la nueva muestra (4) es diferente a muestras históricas. Por ejemplo, ninguna de muestras tiene un “forma de montaña” tan clara. Nos preguntamos si no estamos sobreinterpretando variaciones que son parte normal del proceso. Podemos hacer un mejor análisis si extraemos varias muestras del comportamiento usual del sistema, graficamos junto a la nueva muestra, y revolvemos las gráficas para que no sepamos cuál es cuál. Entonces la pregunta es: ¿Podemos detectar donde están los datos nuevos? Esta se llama una prueba de lineup, o una prueba de ronda de sospechosos (Hadley Wickham et al. 2010). En la siguiente gráfica, en uno de los páneles están los datos recientemente observados. ¿Hay algo en los datos que distinga al patrón nuevo? # nuevos datos obs <- simular_serie(500, x_inicial = last(obs$obs)) # muestrear datos históricos prueba_tbl <- muestrear_ventanas(historicos, obs[1:20, ], n_ventana = 20) # gráfica de pequeños múltiplos ggplot(prueba_tbl$lineup, aes(x = t_0, y = obs)) + geom_line() + facet_wrap(~rep, nrow = 4) + scale_y_log10() ¿Cuáles son los datos nuevos (solo hay un panel con los nuevos datos)? ¿Qué implica que la gráfica que escojamos como “más diferente” no sean los datos nuevos? ¿Qué implica que le “atinemos” a la gráfica de los datos nuevos? Ahora observamos al sistema en otro momento y repetimos la comparación. En el siguiente caso obtenemos: Aunque es imposible estar seguros de que ha ocurrido un cambio, la diferencia de una de las series es muy considerable. Si identificamos los datos correctos, la probabilidad de que hayamos señalado la nueva serie “sobreinterpretando” fluctuaciones en un proceso que sigue comportándose normalente es 0.05 - relativamente baja. Detectar los datos diferentes es evidencia en contra de que el sistema sigue funcionando de la misma manera que antes. En el ejemplo anterior se encontraban en la posición: prueba_tbl$pos ## [1] 18 Observaciones y terminología: Llamamos hipótesis nula a la hipótesis de que los nuevos datos son producidos bajo las mismas condiciones que los datos de control o de referencia. Si no escogemos la gráfica de los nuevos datos, nuestra conclusión es que la prueba no aporta evidencia en contra de la hipótesis nula. Si escogemos la gráfica correcta, nuestra conclusión es que la prueba aporta evidencia en contra de la hipótesis nula. ¿Qué tan fuerte es la evidencia, en caso de que descubrimos los datos no nulos? Cuando el número de paneles es más grande y detectamos los datos, la evidencia es más alta en contra de la nula. Decimos que el nivel de significancia de la prueba es la probabilidad de seleccionar a los datos correctos cuando la hipótesis nula es cierta (el sistema no ha cambiado). En el caso de 20 paneles, la significancia es de 1/20 = 0.05. Cuando detectamos los datos nuevos, niveles de significancia más bajos implican más evidencia en contra de la nula. Si acertamos, y la diferencia es más notoria y fue muy fácil detectar la gráfica diferente (pues sus diferencias son más extremas), esto también sugiere más evidencia en contra de la hipótesis nula. Finalmente, esta prueba rara vez (o nunca) nos da seguridad completa acerca de ninguna conclusión, aún cuando hiciéramos muchos páneles. Comparando distribuciones Ahora intentamos un ejemplo más típico. Supongamos que tenemos muestras para tres grupos a, b y c, esto es que dentro de cada grupo, el proceso de selección de los elementos se hace al azar y de manera simétrica (por ejemplo cada elemento tiene a misma probabiidad de ser seleccionado, y las extracciones se hacen de manera independiente.) Queremos comparar las distribuciones de los datos obtenidos para cada grupo. Quizá la pregunta detrás de esta comparación es: el grupo de clientes b recibió una promoción especial. ¿Están gastando más? La medición que comparamos es el gasto de los clientes. En la muestra observamos diferencias entre los grupos. Pero notamos adicionalmente que hay mucha variación dentro de cada grupo. Nos podríamos preguntar entonces si las diferencias que observamos se deben variación muestral, por ejemplo. Podemos construir ahora una hipótesis nula, que establece que las observaciones provienen de una población similar: Las tres poblaciones (a, b, c) son prácticamente indistiguibles. En este caso, la variación que observamos se debería a que tenemos información incompleta. Como en el ejemplo anterior necesitamos construir u obtener una distribución de referencia para comparar qué tan extremos o diferentes son los datos que observamos. Esa distribución de referencia debería estar basada en el supuesto de que los grupos producen datos de distribuciones similares. Si tuvieramos mediciones similares históricas de estos tres grupos, quizá podríamos extraer datos de referencia y comparar, como hicimos en el ejempo anterior. Pero esto es menos común en este tipo de ejemplos. Prueba de permutaciones y el lineup Para abordar este problema podemos pensar en usar permutaciones de los grupos de la siguiente forma ((Box et al. 1978), (Tim C. Hesterberg 2015a)): Si los grupos producen datos bajo procesos idénticos, entonces los grupos a, b, c solo son etiquetas que no contienen información. Podríamos permutar al azar las etiquetas y observar nuevamente la gráfica de caja y brazos por grupos. Si la hipótesis nula es cierta (grupos idénticos), esta es una muestra tan verosímil como la que obtuvimos. Así que podemos construir datos de referencia permutando las etiquetas de los grupos al azar, y observando la variación que ocurre. Si la hipótesis nula es cercana a ser cierta, no deberíamos de poder distinguir fácilmente los datos observados de los producidos con las permutaciones al azar. Vamos a intentar esto, por ejemplo usando una gráfica de cuantiles simplificada. Hacemos un lineup, o una rueda de sospechosos (usamos el paquete (H. Wickham, Chowdhury, and Cook 2012), ver (Hadley Wickham et al. 2010)), donde 19 de los acusados son generados mediante permutaciones al azar de la variable del grupo, y el culpable (los verdaderos datos) están en una posición escogida al azar. ¿Podemos identificar los datos verdaderos? Para evitar sesgarnos, también ocultamos la etiqueta verdadera. Usamos una gráfica que muestra los cuantiles 0.10, 0.50, 0.90: set.seed(88) reps <- lineup(null_permute("grupo"), muestra_tab, n = 20) ## decrypt("M7xA 2S8S Jj dUyJ8JUj ZW") reps_mezcla <- reps |> mutate(grupo_1 = factor(digest::digest2int(grupo) %% 177)) grafica_cuantiles(reps_mezcla, grupo_1, x) + coord_flip() + facet_wrap(~.sample, ncol = 5) + ylab("x") + labs(caption = "Mediana y percentiles 10% y 90%") + geom_point(aes(colour = grupo_1)) Y la pregunta que hacemos es ¿podemos distinguir nuestra muestra entre todas las replicaciones producidas con permutaciones? ¿Dónde están los datos observados? Según tu elección, ¿qué tan diferentes son los datos observados de los datos nulos? En este ejemplo, es difícil indicar cuáles son los datos. Los grupos tienen distribuciones similares y es factible que las diferencias que observamos se deban a variación muestral. Si la persona escoge los verdaderos datos, encontramos evidencia en contra de la hipótesis nula (los tres grupos son equivalentes). En algunos contextos, se dice que los datos son significativamente diferentes al nivel 0.05. Esto es evidencia en contra de que los datos se producen de manera homogénea, independientemente del grupo. Si la persona escoge uno de los datos permutados, no encontramos evidencia en contra de que los tres grupos producen datos con distribuciones similares. Comparaciones usando lineup (continuación) Repetimos el ejemplo para otra muestra (en este ejemplo el proceso generador de datos es diferente para el grupo b): Hacemos primero la prueba del lineup: set.seed(121) reps <- lineup(null_permute("grupo"), muestra_tab, n = 20) grafica_cuantiles(reps |> mutate(grupo_escondido = factor(digest::digest2int(grupo) %% 177)), grupo_escondido, x) + facet_wrap(~.sample) + ylab("x") + coord_flip() + geom_point(aes(colour = grupo_escondido)) Podemos distinguir más o menos claramente que está localizada en valores más altos y tiene mayor dispersión. En este caso, como en general podemos identificar los datos, obtenemos evidencia en contra de que los tres grupos tienen distribuciones iguales. Estos ejemplos siguen la idea de inferencia visual propuestas en (Hadley Wickham et al. 2010), (Hofmann et al. 2012) son pruebas muy flexibles y estadísticamente rigurosas. Prueba de permutaciones para proporciones Veremos otro ejemplo donde podemos hacer más concreta la idea de distribución nula o de referencia usando pruebas de permutaciones. Supongamos que con nuestra muestra de tomadores de té, queremos probar la siguiente hipótesis nula: Los tomadores de té en bolsas exclusivamente, usan azúcar a tasas simillares que los tomadores de té suelto (que pueden o no también tomar té en bolsita). Los datos que obtuvimos en nuestra encuesta, en conteos, son: sugar bolsa_exclusivo suelto o bolsa No.sugar 81 74 sugar 89 56 Y en proporciones tenemos que: how prop_azucar n bolsa_exclusivo 0.52 170 suelto o bolsa 0.43 130 Pero distintas muestras podrían haber dado distintos resultados. Nos preguntamos qué tan fuerte es la evidencia en contra de que en realidad los dos grupos de personas usan azúcar en proporciones similares, y la diferencia que vemos se puede atribuir a variación muestral. En este ejemplo, podemos usar una estadística de prueba numérica, por ejemplo, la diferencia entre las dos proporciones: \\[\\hat p_1 - \\hat p_2,\\] (tomadores de té en bolsa solamente vs. suelto y bolsa). El proceso sería entonces: La hipótesis nula es que los dos grupos tienen distribuciones iguales. Este caso quiere decir que en la población, tomadores de té solo en bolsa usan azúcar a las mismas tasas que tomadores de suelto o bolsas. Bajo nuestra hipótesis nula (proporciones iguales), producimos una cantidad grande (por ejemplo 10 mil o más) de muestras permutando las etiquetas de los grupos. Evaluamos nuestra estadística de prueba en cada una de las muestras permutadas. El conjunto de valores obtenidos nos da nuestra distribución de referencia (ya no estamos limitados a 20 replicaciones como en las pruebas gráficas). Y la pregunta clave es: ¿el valor de la estadística en nuestra muestra es extrema en comparación a la distribución de referencia? dif_obs <- te_azucar |> mutate(usa_azucar = as.numeric(sugar == "sugar")) |> group_by(how) |> summarise(prop_azucar = mean(usa_azucar), .groups = 'drop') |> pivot_wider(names_from = how, values_from = prop_azucar) |> mutate(diferencia_prop = bolsa_exclusivo - `suelto o bolsa`) |> pull(diferencia_prop) La diferencia observada es: dif_obs |> round(3) ## [1] 0.093 Ahora construimos nuestra distribución nula o de referencia: reps <- lineup(null_permute("how"), te_azucar, n = 50000) glimpse(reps) ## Rows: 15,000,000 ## Columns: 3 ## $ how <chr> "bolsa_exclusivo", "bolsa_exclusivo", "suelto o bolsa", "suelt… ## $ sugar <chr> "sugar", "No.sugar", "No.sugar", "sugar", "No.sugar", "No.suga… ## $ .sample <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,… valores_ref <- reps |> mutate(usa_azucar = as.numeric(sugar == "sugar")) |> group_by(.sample, how) |> summarise(prop_azucar = mean(usa_azucar), .groups = 'drop') |> pivot_wider(names_from = how, values_from = prop_azucar) |> mutate(diferencia = bolsa_exclusivo - `suelto o bolsa`) Y graficamos nuestros resultados (con un histograma y una gráfica de cuantiles, por ejemplo). la estadística evaluada un cada una de nuestras muestras permutadas: g_1 <- ggplot(valores_ref, aes(sample = diferencia)) + geom_qq(distribution = stats::qunif) + xlab("f") + ylab("diferencia") + labs(subtitle = "Distribución nula o de referencia") g_2 <- ggplot(valores_ref, aes(x = diferencia)) + geom_histogram(binwidth = 0.04) + coord_flip() + xlab("") + labs(subtitle = " ") g_1 + g_2 Este es el rango de fluctuación usual para nuestra estadística bajo la hipótesis de que los dos grupos de tomadores de té consumen té a la misma tasa. El valor que obtuvimos en nuestros datos es: 0.09. Mismo que no es un valor extremo en la distribución de referencia que vimos arriba. Ésta muestra no aporta mucha evidencia en contra de que los grupos tienen distribuciones similares. Podemos graficar otra vez marcando el valor observado: # Función de distribución acumulada (inverso de función de cuantiles) dist_perm <- ecdf(valores_ref$diferencia) # Calculamos el percentil del valor observado percentil_obs <- dist_perm(dif_obs) g_1 <- ggplot(valores_ref, aes(sample = diferencia)) + geom_qq(distribution = stats::qunif) + xlab("f") + ylab("diferencia") + labs(subtitle = "Distribución nula o de referencia") + geom_hline(yintercept = dif_obs, colour = "red") + annotate("text", x = 0.35, y = dif_obs - 0.03, label = "diferencia observada", colour = "red") g_2 <- ggplot(valores_ref, aes(x = diferencia)) + geom_histogram(binwidth = 0.04) + coord_flip() + xlab("") + labs(subtitle = " ") + geom_vline(xintercept = dif_obs, colour = "red") + annotate("text", x = dif_obs, y = N_rep * .3, label = percentil_obs,vjust = -0.2, colour = "red") g_1 + g_2 Y vemos que es un valor algo (pero no muy) extremo en la distribución de referencia que vimos arriba: esta muestra no aporta una gran cantidad de evidencia en contra de que los grupos tienen distribuciones similares, que en este caso significa que los dos grupos usan azúcar a tasas similares. Es decir, sobre la hipótesis \\[H_0: p_1 = p_2,\\] o bien, \\[H_0: p_1 - p_2 = 0.\\] Pruebas de hipótesis tradicionales Comencemos recordando la definición de parámetro y estadística. Definición. Un parámetro es una característica (numérica) de una población o de una distribución de probabilidad. Usualmente denotado por \\(\\theta \\in \\mathbb{R},\\) o por \\(\\theta \\in \\mathbb{R}^p.\\) Una estadística es una característica (numérica) de los datos. Usualmente denotado por \\(\\hat \\theta.\\) Cualquier función de un parámetro es también un parámetro \\(\\varphi = h(\\theta),\\) y cualquier función de una estadística es también una estadística \\(\\hat \\varphi = h(\\hat \\theta).\\) Cuando la estadística se calcula de una muestra aleatoria (\\(T(X),\\) para \\(X_i \\sim \\pi_x\\) para \\(i = 1, \\ldots, n\\)), es por consiguiente aleatoria y es por tanto una variable aleatoria (\\(T \\sim \\pi_T\\)). Por ejemplo \\(\\mu\\) y \\(\\sigma\\) son parámetros de la distribución normal con función de densidad \\(\\pi(x) = (1/\\sqrt{2\\pi}\\sigma)e^{(x-\\mu)^2/(2\\sigma^2)}\\). La varianza \\(\\sigma^2\\), y el coeficiente de variación (cociente de señal a ruido) \\(\\mu/\\sigma\\) también son parámetros. Si \\(X_1, \\ldots ,X_n\\) son una muestra aleatoria, entonces la media \\(\\bar{X}=\\frac1n\\sum_i X_i\\) es una estadística. Ahora podemos pasar a las definiciones correspondientes a pruebas de hipótesis (o pruebas de significancia). Definición. Denotamos por \\(H_0\\) a la hipótesis nula la cual usualmente tratamos como la afirmación del status quo. La hipótesis alternativa la denotamos por \\(H_1\\) y representa el supuesto que está a prueba y para el cual buscamos evidencia en los datos. Definición. La hipótesis normalmente se plantea en términos de un parámetro (\\(\\theta\\in\\mathbb{R}\\)) o conjunto de parámetros (\\(\\theta\\in\\mathbb{R}^p\\)) de la distribución de interés (por ejemplo media, moda, varianza). Para una hipótesis nula del estilo \\(H_0: \\theta = \\theta_0,\\) la hipótesis a contrastar se puede denominar como: Hipótesis alternativa de una cola \\(H_1: \\theta \\gt \\theta_0\\) Hipótesis alternativa de dos colas \\(H_1: \\theta \\neq \\theta_0\\) En el ejemplo anterior planteamos hipótesis nula (proporciones iguales) e hipótesis alternativa que la proporción de tomadores de te suelto que usan azúcar en menor proporción, esto corresponde a una hipótesis alternativa a dos colas: \\(H_0: p_1 = p_2\\), y \\(H_1:p_1 > p_2\\). Definición. Una estadística de prueba es una función numérica de los datos cuyo valor determina el resultado de la prueba. La función usualmente es denotada por \\(T(\\bf X)\\) donde \\(\\bf X\\) representa los datos como variable aleatoria. Por ejemplo, \\(T = T(X_1, \\ldots, X_n)\\) si sólo tenemos una muestra, o por \\(T = T(X_1, \\ldots, X_n, Y_1, \\ldots, Y_m)\\) en el caso de tener dos muestras. Al evaluar la prueba para un conjunto de datos dado, \\(x\\), ésta se denomina estadística de prueba observada, \\(t = T(x).\\) La estadística de prueba correspondiente al ejemplo es \\(T = \\hat p_1 - \\hat p_2.\\) Definición. El valor p es la probabilidad de que bajo la hipótesis nula los datos generen un valor tan extremo como la estadística de prueba observada. Por ejemplo, si consideramos la hipótesis nula admite valores grandes, el valor p se calcula como \\(P(T \\geq t).\\) En el ejemplo de tomadores de té: el valor p lo calculamos usando el percentil donde nuestra observación cae en la distribución generada por las permutación (valor p de una cola). 1 - dist_perm(dif_obs) ## [1] 0.04344 Por otro lado, podemos calcular: Valor p de dos colas: Si la hipótesis nula es cierta, ¿cuál es la probabilidad de observar una diferencia tan extrema o más extrema de lo que observamos? Considerando este caso interpretamos extrema como qué tan lejos cae del centro de masa de la distribución. De tal forma que podemos calcular el valor p como sigue. A partir del valor observado, consideramos cuál dato es menor: la probabilidad bajo lo hipótesis nula de observar una diferencia mayor de la que observamos, o la probabilidad de observar una diferencia menor a la que observamos. Tomamos el mínimo y multiplicamos por dos (Tim C. Hesterberg 2015a): 2 * min(dist_perm(dif_obs), (1 - dist_perm(dif_obs))) ## [1] 0.08688 Este valor p se considera como evidencia moderada en contra de la hipótesis nula. Valores p chicos (observaciones más extremas en comparación con la referencia) aportan más evidencia en contra de la hipótesis nula, y valores más grandes aportan menos evidencia en contra. Definición. Un resultado es estadisticamente significativo si tiene muy baja probabilidad de suceder al azar. Entre más pequeño requiramos un valor p oara declarar un resultado estadísticamente significativo, somos más conservadores. Las pruebas de hipótesis con frecuencia inician contestando una pregunta más general que los valores p: ¿Cuál es la distribución de la estadística de prueba cuando no hay un efecto real? Definición. La distribución nula es la distribución de la estadística de prueba si la hipótesis nula es cierta. En ocasiones también nos referimos a ella como la distribución de referencia pues estamos comparando la estadística de prueba observada a su referencia para determinar que tan inusual es. En el ejemplo de tomadores de té aproximamos la distribución nula (y los valores p) con simulación; sin embargo, para algunas estadísticas hay métodos exactos. En particular, usamos el método de pruebas de permutación. Para dicha prueba el algoritmo para en el caso de dos grupos sería como sigue: Prueba de permutación para dos muestras Supongamos que tenemos m observaciones de una población y n de otra. Combina los m+n valores. Repite: Obtén un remuestra de tamaño m sin reemplazo del total. Usa las n observaciones restantes para obtener la otra muestra. Calcula la estadística de prueba (que compara las muestras). Calcula el valor p como la fracción de las veces que la estadística sobrepasó la estadística observada, multiplica por 2 para una prueba de dos lados. La distribución de la estadística a lo largo de las remuestras de permutación es la distribución de permutación. Ésta puede ser exacta, si se calcula exhaustivamente (como cuando tenemos pocas observaciones) o aproximada (cuando enlistar todas las posible combinaciones es prohibitivo). Tomadores de té (continuación) Ahora hacemos una prueba de permutaciones para otro par de proporciones utilizando el mismo método. La hipótesis nula ahora es: Los tomadores de té Earl Gray usan azúcar a una tasa similar a los tomadores de té negro. Los datos que obtuvimos en nuestra encuesta se muestran en la siguiente tabla: sugar Earl Grey black No.sugar 84 51 sugar 109 23 Y en porcentajes tenemos que: prop_azucar <- te_azucar |> count(Tea, sugar) |> group_by(Tea) |> mutate(prop = 100 * n / sum(n), n = sum(n)) |> filter(sugar == "sugar") |> select(Tea, prop_azucar = prop, n) |> mutate('% usa azúcar' = round(prop_azucar)) |> select(-prop_azucar) prop_azucar |> formatear_tabla() Tea n % usa azúcar Earl Grey 193 56 black 74 31 Pero distintas muestras podrían haber dado distintos resultados. Nos preguntamos qué tan fuerte es la evidencia en contra de que en realidad los dos grupos de personas usan azúcar en proporciones similares considerando que la diferencia que vemos se puede atribuir a variación muestral. Escribimos la función que calcula diferencias para cada muestra: calc_diferencia_2 <- function(datos){ datos |> mutate(usa_azucar = as.numeric(sugar == "sugar")) |> group_by(Tea) |> summarise(prop_azucar = mean(usa_azucar), .groups = 'drop') |> pivot_wider(names_from = Tea, values_from = prop_azucar) |> mutate(diferencia_prop = `Earl Grey` - black) |> pull(diferencia_prop) } La diferencia observada es: ## [1] 0.254 Ahora construimos nuestra distribución nula o de referencia: set.seed(2) reps <- lineup(null_permute("Tea"), te_azucar, n = N_rep) valores_ref <- reps |> group_by(.sample) |> nest() |> mutate(diferencia = lapply(data, calc_diferencia_2)) |> unnest(diferencia) Y podemos graficar la distribución de referencia otra vez marcando el valor observado En este caso, la evidencia es muy fuerte en contra de la hipótesis nula, pues el resultado que obtuvimos es muy extremo en relación a la distribución de referencia. El valor p es cercano a 0. Haz una prueba de permutaciones para diferencia de medias para comparar la propina en cena vs en comidas. Grafica la distribución de referencia. Calcula el valor p (dos colas). Pruebas de permutación: implementación. Hasta ahora nos hemos centrado en ejemplos de diferencias en medias. Podemos extender las pruebas de permutación a \\(\\bar{X}\\) (la media de la primera muestra), \\(n\\bar{X}\\) (la suma de las observaciones en la primera muestra), y más. Teorema. En pruebas de permutación, si dos estadísticas de prueba \\(T_1\\) y \\(T_2\\) están relacionadas por una función estríctamente monótona, \\(T_1(X^*)=f(T_2(X^*))\\) donde \\(X^*\\) es una remuestra de permutación de los datos originales, entonces los valores p serán los mismos en las pruebas de permutación. Muestras con reemplazo de la Distribución Nula. En la implementación de muestreo, no nos aseguramos que las remuestras sean únicas. Sería más acertado tomar muestras sin reemplazo, sin embargo, el costo computacional es demasiado alto. Por simplicidad consideramos muestras con reemplazo del total de \\[m+n\\choose n\\] posibles remuestras. Por lo tanto, al remuestrar obtenemos una muestra de la distribución nula. Entre más muestras, más exactitud. Hemos usado \\(B = 10^3\\) remuestras (N_rep en el código), en general entre más remuestras tendremos una mejor estimación del valor p. Si el verdadero valor es \\(p\\) el estimado tendrá una varianza aproximadamente de \\(p(1- p)/B\\) donde \\(B\\) es el número de remuestras generadas. Observación. Así como los \\(n\\) datos originales son una muestra de la población, también las \\(B\\) remuestras de la estadística son una muestra de una población, en este caso de la distribución nula. La pruebas de permutaciones son más útiles cuando nuestra hipótesis nula se refiere que la distribución de los grupos son muy similares, o la independencia entre observaciones y grupo. Esto también aplica cuando queremos probar por ejemplo, que una variable numérica \\(Y\\) es independiente de \\(X.\\) Hay algunas hipótesis que no se pueden probar con este método, como por ejemplo, las que se refieren a una sola muestra: ¿los datos son consistentes con que su media es igual a 5? Adicionalmente, en algunas ocasiones queremos probar aspectos más específicos de las diferencias: como ¿son iguales las medias o medianas de dos grupos de datos? ¿Tienen dispersión similar? Es común aplicar pruebas de permutaciones a este segundo problema, sin embargo, no están tan perfectamente adaptadas a el, pues prueban todos los aspectos de las distribuciones que se comparan, aún cuando escojamos una estadística particular que pretende medir. Por ejemplo, cuando trabajamos con la diferencia de medias. Eso quiere decir que podemos rechazar igualdad de medias, por ejemplo, cuando en realidad otra característica de las distribuciones es la que difiere mucho en las poblaciones. En algunas referencias (ver (Chihara and Hesterberg 2018), (Efron and Tibshirani 1993)) se argumenta que de todas formas las pruebas de permutaciones son relativamente robustas a esta desadaptación. Un caso excepcional, por ejemplo, es cuando las poblaciones que comparamos resultan tener dispersión extremadamente distinta, y adicionalmente los tamaños de muestra de los grupos son muy desiguales (otra vez, ver ejemplos en (Chihara and Hesterberg 2018)). Ejemplo: tiempos de fusión Veamos el siguiente ejemplo, que es un experimento donde se midió el tiempo que tardan distintas personas en fusionar un estereograma para ver una imagen 3D. (William S. Cleveland (1993)). Existen dos condiciones: en una se dio indicaciones de qué figura tenían que buscar (VV) y en otra no se dio esa indicación. ¿Las instrucciones verbales ayudan a fusionar más rápido el estereograma? ## ## ── Column specification ──────────────────────────────────────────────────────── ## cols( ## n = col_double(), ## time = col_double(), ## nv.vv = col_character() ## ) La situación es la siguiente: considerando que hay mucha variación en el tiempo de fusión dentro de cada tratamiento, necesitamos calificar la evidencia de nuestra conclusión (el tiempo de fusión se reduce con información verbal). Podemos usar una prueba de permutaciones, esta vez justificándola por el hecho de que los tratamientos se asignan al azar: si los tratamientos son indistinguibles, entonces las etiquetas de los grupos son sólo etiquetas, y permutarlas daría muestras igualmente verosímiles. En este caso, compararemos gráficas de cuantiles de los datos con los producidos por permutaciones (transformamos los datos pues en este caso es más apropiado una comparación multiplicativa): ¿Podemos identificar los datos? En general, muy frecuentemente las personas identifican los datos correctamente, lo que muestra evidencia considerable de que la instrucción verbal altera los tiempos de respuesta de los partipantes, y en este caso ayuda a reducir el tiempo de fusión de los estereogramas. Ejemplo: tiempos de fusión (continuación) Podemos usar las pruebas de permutaciones para distintos tipos de estadísticas: medianas, medias, comparar dispersión usando rangos intercuartiles o varianzas, etc. Regresamos a los tiempos de fusión. Podemos hacer una prueba de permutaciones para la diferencia de las medias o medianas, por ejemplo. En este ejemplo usaremos una medida de centralidad un poco diferente, como ilustración: el promedio de los cuartiles superior e inferior de las dos distribuciones. Usaremos el cociente de estas dos cantidades para medir su diferencia # esta función hace permutaciones y calcula la diferencia para cada una permutaciones_est <- function(datos, variable, calc_diferencia, n = 1000){ # calcular estadística para cada grupo permutar <- function(variable){ sample(variable, length(variable)) } tbl_perms <- tibble(.sample = seq(1, n-1, 1)) |> mutate(diferencia = map_dbl(.sample, ~ datos |> mutate({{variable}}:= permutar({{variable}})) |> calc_diferencia())) bind_rows(tbl_perms, tibble(.sample = n, diferencia = calc_diferencia(datos))) } stat_fusion <- function(x){ (quantile(x, 0.75) + quantile(x, 0.25))/2 } calc_fusion <- function(stat_fusion){ fun <- function(datos){ datos |> group_by(nv.vv) |> summarise(est = stat_fusion(time), .groups = 'drop') |> pivot_wider(names_from = nv.vv, values_from = est) |> mutate(dif = VV / NV ) |> pull(dif) } fun } calc_cociente <- calc_fusion(stat_fusion) dif_obs <- calc_cociente(fusion) # permutar valores_ref <- permutaciones_est(fusion, nv.vv, calc_cociente, n = N_rep) dist_perm_nv <- ecdf(valores_ref$diferencia) cuantil_obs <- dist_perm_nv(dif_obs) Y el valor p de dos colas es dist_perm_nv <- ecdf(valores_ref$diferencia) 2 * min(dist_perm_nv(dif_obs), 1 - dist_perm_nv(dif_obs)) ## [1] 0.028 Lo que muestra evidencia considerable, aunque no muy fuerte, de que la instrucción verbal ayuda a reducir el tiempo de fusión de los estereogramas: la caja del diagrama de caja y brazos para el grupo VV está encogida por un factor menor a 1. Separación de grupos Este ejemplo tomado de (Chowdhury et al. 2015) (tanto la idea como el código). La pregunta que se aborda en ese estudio es: Existen métodos de clasificación (supervisados o no supervisados) para formar grupos en términos de variables que describen a los individuos Estos métodos (análisis discriminante, o k-means, por ejemplo), pretenden formar grupos compactos, bien separados entre ellos. Cuando aplicamos el método, obtenemos clasificadores basados en las variables de entrada. La pregunta es: ¿los grupos resultantes son producto de patrones que se generalizan a la población, o capitalizaron en variación aleatoria para formarse? Especialmente cuando tenemos muchas mediciones de los individuos, y una muestra relativamente chica, Es relativamente fácil encontrar combinaciones de variables que separan los grupos, aunque estas combinaciones y diferencias están basadas en ruido y no generalizan a la población. Como muestran en (Chowdhury et al. 2015), el lineup es útil para juzgar si tenemos evidencia en contra de que los grupos en realidad son iguales, y usamos variación muestral para separarlos. Avispas (opcional) En el siguiente ejemplo, tenemos 4 grupos de avispas (50 individuos en total), y para cada individuo se miden expresiones de 42 genes distintos. La pregunta es: ¿Podemos separar a los grupos de avispas dependiendo de sus mediciones? En este se usó análisis discriminante (LDA) para buscar proyecciones de los datos en dimensión baja de forma que los grupos sean lo más compactos y separados posibles. Para probar qué tan bien funciona este método, podemos hacer una prueba de permutación, aplicamos LDA y observamos los resultados. Y vemos que incluso permutando los grupos, es generalmente posible separarlos en grupos bien definidos: la búsqueda es suficientemente agresiva para encontrar combinaciones lineales que los separan. Que no podamos distinguir los datos verdaderos de las replicaciones nulas indica que este método difícilmente puede servir para separar los grupos claramente. Otro enfoque sería separar los datos en una muestra de entrenamiento y una de prueba (que discutiremos en la última sesión). Aplicamos el procedimiento a la muestra de entrenamiento y luego vemos qué pasa con los datos de prueba: set.seed(8) wasps_1 <- wasps |> mutate(u = runif(nrow(wasps), 0, 1)) wasps_entrena <- wasps_1 |> filter(u <= 0.8) wasps_prueba <- wasps_1 |> filter(u > 0.8) wasp.lda <- MASS::lda(Group ~ ., data=wasps_entrena[,-1]) wasp_ld_entrena <- predict(wasp.lda, dimen=2)$x |> as_tibble(.name_repair = "universal") |> mutate(tipo = "entrenamiento") |> mutate(grupo = wasps_entrena$Group) wasp_ld_prueba <- predict(wasp.lda, newdata = wasps_prueba, dimen=2)$x |> as_tibble(.name_repair = "universal") |> mutate(tipo = "prueba")|> mutate(grupo = wasps_prueba$Group) wasp_lda <- bind_rows(wasp_ld_entrena, wasp_ld_prueba) ggplot(wasp_lda, aes(x = LD1, y = LD2, colour = grupo)) + geom_point(size = 3) + facet_wrap(~tipo) Aunque esta separación de datos es menos efectiva en este ejemplo por la muestra chica, podemos ver que la separación lograda en los datos de entrenamiento probablemente se debe a variación muestral. La “crisis de replicabilidad” Recientemente (Ioannidis 2005) se ha reconocido en campos como la psicología la crisis de replicabilidad. Varios estudios que recibieron mucha publicidad inicialmente no han podido ser replicados posteriormente por otros investigadores. Por ejemplo: Hacer poses poderosas produce cambios fisiológicos que mejoran nuestro desempeño en ciertas tareas. Mostrar palabras relacionadas con “viejo” hacen que las personas caminen más lento (efectos de priming). En todos estos casos, el argumento de la evidencia de estos efectos fue respaldada por una prueba de hipótesis nula con un valor p menor a 0.05. La razón es que ese es el estándar de publicación seguido por varias áreas y revistas arbitradas. La tasa de no replicabilidad parece ser mucho más alta (al menos la mitad o más, según algunas fuentes como la señalada arriba) que la sugerida por la tasa de falsos positivos (menos de 5%). Este problema de replicabilidad parece ser más frecuente cuando: Se trata de estudios de potencia baja: mediciones ruidosas y tamaños de muestra chicos. El plan de análisis no está claramente definido desde un principio (lo cual es difícil cuando se están investigando “fenómenos no estudiados antes”). ¿A qué se atribuye esta crisis de replicabilidad? El jardín de los senderos que se bifurcan Aunque haya algunos ejemplos de manipulaciones conscientes —e incluso, en menos casos, malintencionadas— para obtener resultados publicables o significativos (p-hacking), como vimos en ejemplos anteriores, hay varias decisiones, todas razonables, que podemos tomar cuando estamos buscando las comparaciones correctas. Algunas pueden ser: Transformar los datos (tomar o no logaritmos, u otra transformación). Editar datos atípicos (razonable si los equipos pueden fallar, o hay errores de captura, por ejemplo). Distintas maneras de interpretar los criterios de inclusión de un estudio (por ejemplo, algunos participantes mostraron tener gripa, o revelaron que durmieron muy poco la noche anterior, etc. ¿los dejamos o los quitamos?). Dado un conjunto de datos, las justificaciones de las decisiones que se toman en cada paso son razonables, pero con datos distintos las decisiones podrían ser diferentes. Este es el jardín de los senderos que se bifurcan (ver referencia en Gelman), que invalida en parte el uso valores p como criterio de evidencia contra la hipótesis nula. Esto es exacerbado por: Tamaños de muestra chicos y efectos “inestables” que se quieren medir (por ejemplo en psicología). El hecho de que el criterio de publicación es obtener un valor \\(p < 0.05\\), y la presión fuerte sobre los investigadores para producir resultados publicables (\\(p < 0.05\\)). El que estudios o resultados similares que no obtuvieron valores \\(p\\) por debajo del umbral no son publicados o reportados. Ver por ejemplo el comunicado de la ASA. Ojo: esas presiones de publicación no sólo ocurre para investigadores en psicología. Cuando trabajamos en problemas de análisis de datos que son de importancia, es común que existan intereses de algunas partes o personas involucradas por algunos resultados u otros (por ejemplo, nuestros clientes de consultoría o clientes internos). Eso puede dañar nuestro trabajo como analistas, y el avance de nuestro equipo. Aunque esas presiones son inevitables, se vuelven manejables cuando hay una relación de confianza entre las partes involucradas. Ejemplo: decisiones de análisis y valores p En el ejemplo de datos de fusión, decidimos probar, por ejemplo, el promedio de los cuartiles inferior y superior, lo cual no es una decisión típica pero usamos como ilustración. Ahora intentamos usar distintas mediciones de la diferencia entre los grupos, usando distintas medidas resumen y transformaciones (por ejemplo, con o sin logaritmo). Aquí hay unas 12 combinaciones distintas para hacer el análisis (multiplicadas por criterios de “aceptación de datos en la muestra”, que simulamos tomando una submuestra al azar): calc_fusion <- function(stat_fusion, trans, comparacion){ fun <- function(datos){ datos |> group_by(nv.vv) |> summarise(est = stat_fusion({{ trans }}(time)), .groups = 'drop') |> pivot_wider(names_from = nv.vv, values_from = est) |> mutate(dif = {{ comparacion }}) |> pull(dif) } fun } valor_p <- function(datos, variable, calc_diferencia, n = 1000){ # calcular estadística para cada grupo permutar <- function(variable){ sample(variable, length(variable)) } tbl_perms <- tibble(.sample = seq(1, n-1, 1)) |> mutate(diferencia = map_dbl(.sample, ~ datos |> mutate({{variable}} := permutar({{variable}})) |> calc_diferencia())) perms <- bind_rows(tbl_perms, tibble(.sample = n, diferencia = calc_diferencia(datos))) perms_ecdf <- ecdf(perms$diferencia) dif <- calc_diferencia(datos) 2 * min(perms_ecdf(dif), 1- perms_ecdf(dif)) } set.seed(7272) media_cuartiles <- function(x){ (quantile(x, 0.75) + quantile(x, 0.25))/2 } # nota: usar n=10000 o más, esto solo es para demostración: ejemplo <- list() calc_dif <- calc_fusion(mean, identity, VV - NV) ejemplo$media_dif <- valor_p(fusion |> sample_frac(0.95), nv.vv, calc_dif, n = N_rep) calc_dif <- calc_fusion(mean, log, VV - NV) ejemplo$media_dif_log <- valor_p(fusion |> sample_frac(0.95), nv.vv, calc_dif, n = N_rep) calc_dif <- calc_fusion(median, identity, VV / NV) ejemplo$mediana_razon <- valor_p(fusion |> sample_frac(0.95), nv.vv, calc_dif, n = N_rep) calc_dif <- calc_fusion(media_cuartiles, identity, VV / NV) ejemplo$cuartiles_razon <- valor_p(fusion |> sample_frac(0.95), nv.vv, calc_dif, n = N_rep) ejemplo <- read_rds("cache/ejemplo_p_val.rds") ejemplo$media_dif ## [1] 0.0658 ejemplo$media_dif_log ## [1] 0.018 ejemplo$mediana_razon ## [1] 0.049 ejemplo$cuartiles_razon ## [1] 0.0464 Si existen grados de libertad —muchas veces necesarios para hacer un análisis exitoso—, entonces los valores p pueden tener poco significado. Alternativas o soluciones El primer punto importante es reconocer que la mayor parte de nuestro trabajo es exploratorio (recordemos el proceso complicado del análisis de datos de refinamiento de preguntas). En este tipo de trabajo, reportar valores p puede tener poco sentido, y mucho menos tiene sentido aceptar algo verdadero cuando pasa un umbral de significancia dado. Nuestro interés principal al hacer análisis es: expresar correctamente, y de manera útil, la incertidumbre asociada a las conclusiones o patrones que mostramos (asociada a variación muestral, por ejemplo) con el objetivo que el proceso de toma de decisiones sea informado. Un resumen de un número (valor p, o el que sea) no puede ser tomado como criterio para tomar una decisión que generalmente es compleja. En la siguiente sección veremos cómo podemos mostrar parte de esa incertidumbre de manera más útil. Por otra parte, los estudios confirmatorios (donde se reportan valores p) también tienen un lugar. En áreas como la psicología, existen ahora movimientos fuertes en favor de la repetición de estudios prometedores pero donde hay sospecha de grados de libertad del investigador. Este movimiento sugiere dar valor a los estudios exploratorios que no reportan valor p, y posteriormente, si el estudio es de interés, puede intentarse una replicación confirmatoria, con potencia más alta y con planes de análisis predefinidos. Referencias "],["estimación-y-distribución-de-muestreo-1.html", "Sección 4 Estimación y distribución de muestreo Ejemplo: precios de casas Distribución de muestreo Más ejemplos El error estándar Calculando la distribución de muestreo Teorema central del límite Normalidad y gráficas de cuantiles normales Prueba de hipótesis de normalidad Ejemplo Más del Teorema central del límite", " Sección 4 Estimación y distribución de muestreo En esta sección discutiremos cuál el objetivo general del proceso de estimación, y cómo entender y manejar la variabilidad que se produce cuando aleatorizamos la selección de las muestras que utilizamos para hacer análisis. A diferencia de las pruebas de permutación, donde evaluábamos como cambiaría una estadísitica si un tratamiento o grupo se hubiera asignado de forma distinta, en la siguiente sección nos preguntamos como varía una estadística entre muestras. Por ejemplo, pasaremos de preguntar si una vacuna reduce el riesgo de una enfermedad a evaluar en que magnitud se reduce el riesgo de contraer la enfermedad. Ejemplo: precios de casas Supongamos que queremos conocer el valor total de las casas que se vendieron recientemente en una zona particular. Supondremos que tenemos un listado de las casas que se han vendido recientemente, pero en ese listado no se encuentra el precio de venta. Decidimos entonces tomar una muestra aleatoria de 100 de esas casas. Para esas casas hacemos trabajo de campo para averiguar el precio de venta. marco_casas <- read_csv("data/casas.csv") set.seed(841) muestra_casas <- sample_n(marco_casas, 100) |> select(id, nombre_zona, area_habitable_sup_m2, precio_miles) sprintf("Hay %0.0f casas en total, tomamos muestra de %0.0f", nrow(marco_casas), nrow(muestra_casas)) ## [1] "Hay 1144 casas en total, tomamos muestra de 100" head(muestra_casas) ## # A tibble: 6 × 4 ## id nombre_zona area_habitable_sup_m2 precio_miles ## <dbl> <chr> <dbl> <dbl> ## 1 287 NAmes 161. 159 ## 2 755 NAmes 95.3 156 ## 3 1190 Gilbert 168. 189 ## 4 36 NridgHt 228. 309 ## 5 32 Sawyer 114. 149. ## 6 538 NAmes 80.3 111. Como tomamos una muestra aleatoria, intentamos estimar el valor total de las casas que se vendieron expandiendo el total muestral, es decir nuestro estimador \\(\\hat{t} = t(X_1,\\ldots X_{100})\\) del total poblacional \\(t\\) es \\[\\hat{t} = \\frac{N}{n} \\sum_{i=1}^{100} X_i = N\\bar{x}\\] Esta función implementa el estimador: n <- nrow(muestra_casas) # tamaño muestra N <- nrow(marco_casas) # tamaño población estimar_total <- function(muestra_casas, N){ total_muestral <- sum(muestra_casas$precio_miles) n <- nrow(muestra_casas) # cada unidad de la muestra representa a N/n f_exp <- N / n # estimador total es la expansión del total muestral estimador_total <- f_exp * total_muestral res <- tibble(total_muestra = total_muestral, factor_exp = f_exp, est_total_millones = estimador_total / 1000) res } estimar_total(muestra_casas, N) |> mutate(across(where(is.numeric), \\(x) round(x, 2))) ## # A tibble: 1 × 3 ## total_muestra factor_exp est_total_millones ## <dbl> <dbl> <dbl> ## 1 18444. 11.4 211 Sin embargo, si hubiéramos obtenido otra muestra, hubiéramos obtenido otra estimación diferente. Por ejemplo: estimar_total(sample_n(marco_casas, 100), N) |> mutate(across(where(is.numeric), \\(x) round(x, 2))) ## # A tibble: 1 × 3 ## total_muestra factor_exp est_total_millones ## <dbl> <dbl> <dbl> ## 1 17916. 11.4 205. El valor poblacional que buscamos estimar (nótese que en la práctica este no lo conocemos) es: # multiplicar por 1000 para que sea en millones de dólares total_pob <- sum(marco_casas |> pull(precio_miles)) / 1000 total_pob ## [1] 209.7633 Así que: Para algunas muestras esta estadística puede estar muy cercana al valor poblacional, pero para otras puede estar más lejana. Para entender qué tan buena es una estimación particular, entonces, tenemos que entender cuánta variabilidad hay de muestra a muestra debida a la aleatorización. Esto depende del diseño de la muestra y de la población de precios de casas (que no conocemos). Distribución de muestreo La distribución de muestreo de una estadística enumera los posibles resultados que puede tomar esa estadística sobre todas las muestras posibles. Este es el concepto básico para poder entender qué tan bien o mal estima un parámetro poblacional dado. En nuestro ejemplo anterior de precio de casas, no podemos calcular todas las posibles estimaciones bajo todas las posibles muestras, pero podemos aproximar repitiendo una gran cantidad de veces el proceso de muestreo, como hicimos al aproximar la distribución de permutaciones de estadísticas de prueba de las secciones anteriores. Empezamos repitiendo 10 veces y examinamos cómo varía nuestra estadística: replicar_muestreo <- function(marco_casas, m = 500, n){ # n es el tamaño de muestra que se saca de marco_casas # m es el número de veces que repetimos el muestro de tamaño n resultados <- map_df(1:m, function(id) { sample_n(marco_casas, n) |> estimar_total(N) }, .id = "id_muestra") } replicar_muestreo(marco_casas, m = 10, n = 100) |> mutate(across(where(is.numeric), round, 1)) |> formatear_tabla() id_muestra total_muestra factor_exp est_total_millones 1 17594.8 11.4 201.3 2 17423.9 11.4 199.3 3 18444.3 11.4 211.0 4 17696.6 11.4 202.4 5 17275.8 11.4 197.6 6 17867.6 11.4 204.4 7 18450.8 11.4 211.1 8 18187.2 11.4 208.1 9 18604.2 11.4 212.8 10 19144.4 11.4 219.0 Como vemos, hay variación considerable en nuestro estimador del total, pero la estimación que haríamos con cualquiera de estas muestras no es muy mala. Ahora examinamos un número más grande de simulaciones: replicaciones_1 <- replicar_muestreo(marco_casas, m = 1500, n = 100) Y el siguiente histograma nos dice qué podemos esperar de la variación de nuestras estimaciones, y donde es más probable que una estimación particular caiga: graf_1 <- ggplot(replicaciones_1, aes(x = est_total_millones)) + geom_histogram() + geom_vline(xintercept = total_pob, colour = "red") + xlab("Millones de dólares") + scale_x_continuous(breaks = seq(180, 240, 10), limits = c(180, 240)) graf_1 Con muy alta probabilidad el error no será de más de unos 30 millones de dólares (o no más de 20% del valor poblacional). Definición Sea \\(X_1, X_2, \\ldots X_n\\) una muestra, y \\(T = t(X_1, X_2, \\ldots, X_n)\\) una estadística. La distribución de muestreo de \\(T\\) es la función de distribución de \\(T\\). Esta distribución es sobre todas las posibles muestras que se pueden obtener. Cuando usamos \\(T\\) para estimar algún parámetro poblacional \\(\\theta\\), decimos informalmente que el estimador es preciso si su distribución de muestreo está muy concentrada alrededor del valor \\(\\theta\\) que queremos estimar. Si la distribución de muestreo está concentrada en un conjunto muy grande o muy disperso, quiere decir que con alta probabilidad cuando obtengamos nuestra muestra y calculemos nuestra estimación, el resultado estará lejano del valor poblacional que nos interesa estimar. Veamos qué pasa cuando hacemos la muestra más grande en nuestro ejemplo: replicaciones_2 <- replicar_muestreo(marco_casas, m = 1500, n = 250) Graficamos las dos distribuciones de muestreo juntas, y vemos cómo con mayor muestra obtenemos un estimador más preciso, y sin considerar el costo, preferimos el estimador mejor concentrado alrededor del valor que buscamos estimar. library(patchwork) graf_2 <- ggplot(replicaciones_2, aes(x = est_total_millones)) + geom_histogram() + geom_vline(xintercept = total_pob, colour = "red") + xlab("Millones de dólares") + scale_x_continuous(breaks = seq(180, 240, 10), limits = c(180, 240)) graf_1 + graf_2 Observación: a veces este concepto se confunde la distribución poblacional de las \\(X_i\\). Esto es muy diferente. Por ejemplo, en nuestro caso, el histograma de la distribución de valores poblacionales es ggplot(marco_casas, aes(x = precio_miles)) + geom_histogram() que en general no tiene ver mucho en escala o forma con la distribución de muestreo de nuestro estimador del total. Más ejemplos Podemos también considerar muestrear de poblaciones sintéticas o modelos probabilísticos que usamos para modelar poblaciones reales. Por ejemplo, supongamos que tomamos una muestra de tamaño 15 de la distribución uniforme en \\([0,1]\\). Es decir, cada \\(X_i\\) es un valor uniformemente distribuido en \\([0,1]\\), y las \\(X_i\\) se extraen independientemente unas de otras. Consideramos dos estadísticas de interés: La media muestral \\(T_1(X) = \\frac{1}{15}\\sum_{i = 1}^{15} X_i\\) El cuantil 0.75 de la muestra \\(T_2(X) = q_{0.75}(X)\\) ¿Cómo crees que se vean las distribuciones muestrales de estas estadísticas? ¿Alrededor de qué valores crees que concentren? ¿Crees que tendrán mucha o poca dispersión? ¿Qué forma crees que tengan? Para el primer caso hacemos: # simular replicar_muestreo_unif <- function(est = mean, m, n = 15){ valores_est <- map_dbl(1:m, ~ est(runif(n))) tibble(id_muestra = 1:m, estimacion = valores_est) } sim_estimador_1 <- replicar_muestreo_unif(mean, 4000, 15) # graficar aprox de distribución de muestreo ggplot(sim_estimador_1, aes(x = estimacion)) + geom_histogram(bins = 40) + xlim(c(0, 1)) # simular para el máximo cuantil_75 <- function(x) quantile(x, 0.75) sim_estimador_2 <- replicar_muestreo_unif(cuantil_75, 4000, 15) # graficar distribución de muestreo ggplot(sim_estimador_2, aes(x = estimacion)) + geom_histogram(breaks = seq(0, 1, 0.02)) + xlim(c(0, 1)) Supón que tenemos una muestra de 30 observaciones de una distribución uniforme \\([0,b]\\). ¿Qué tan buen estimador de \\(b/2\\) es la media muestral? ¿Cómo lo cuantificarías? ¿Qué tan buen estimador del cuantil 0.8 de la distribución uniforme es el cuantil 0.8 muestral? ¿Qué desventajas notas en este estimador? El error estándar Una primera medida útil de la dispersión de la distribución de muestreo es su desviación estándar: la razón específica tiene qué ver con un resultado importante, el teorema central del límite, que veremos más adelante. En este caso particular, a esta desviación estándar se le llama error estándar: Definición A la desviación estándar de una estadística \\(T\\) le llamamos su error estándar, y la denotamos por \\(\\text{ee}(T)\\). A cualquier estimador de este error estándar lo denotamos como \\(\\hat{\\text{ee}}(T)\\). Este error estándar mide qué tanto varía el estimador \\(T\\) de muestra a muestra. Observación: es importante no confundir el error estándar con la desviación estándar de una muestra (o de la población). En nuestro ejemplo de las uniformes, la desviación estándar de las muestras varía como: map_dbl(1:10000, ~ sd(runif(15))) |> quantile() |> round(2) ## 0% 25% 50% 75% 100% ## 0.11 0.26 0.29 0.31 0.41 Mientras que el error estándar de la media es aproximadamente map_dbl(1:10000, ~ mean(runif(15))) |> sd() ## [1] 0.07439575 y el error estándar del máximo es aproximadamente map_dbl(1:10000, ~ max(runif(15))) |> sd() ## [1] 0.05928675 Como ejercicio para contrastar estos conceptos, puedes considerar: ¿Qué pasa con la desviación estándar de una muestra muy grande de uniformes? ¿Qué pasa con el error estándar de la media muestral de una muestra muy grande de uniformes? Ejemplo: valor de casas Consideramos el error estándar del estimador del total del inventario vendido, usando una muestra de 250 con el estimador del total que describimos arriba. Como aproximamos con simulación la distribución de muestreo, podemos hacer: ee_2 <- replicaciones_2 |> pull(est_total_millones) |> sd() round(ee_2, 1) ## [1] 5.2 que está en millones de pesos y cuantifica la dispersión de la distribución de muestreo del estimador del total. Para tamaño de muestra 100, obtenemos más dispersión: ee_1 <- replicaciones_1 |> pull(est_total_millones) |> sd() round(ee_1, 1) ## [1] 8.9 Nótese que esto es muy diferente, por ejemplo, a la desviación estándar poblacional o de una muestra. Estas dos cantidades miden la variabilidad del estimador del total. Calculando la distribución de muestreo En los ejemplos anteriores usamos simulación para obtener aproximaciones de la distribución de muestreo de algunos estimadores. También es posible: Hacer cálculos exactos a partir de modelos probabilísticos. Hacer aproximaciones asintóticas para muestras grandes (de las cuales la más importante es la que da el teorema central del límite). En los ejemplos de arriba, cuando muestreamos de la poblaciones, extrajimos las muestras de manera aproximadamente independiente. Cada observación \\(X_i\\) tiene la misma distribución y las \\(X_i\\)’s son independientes. Este tipo de diseños aleatorizados es de los más simples, y se llama muestreo aleatorio simple. En general, en esta parte haremos siempre este supuesto: Una muestra es iid (independiente e idénticamente distribuida) si es es un conjunto de observaciones \\(X_1,X_2, \\ldots X_n\\) independientes, y cada una con la misma distribución. En términos de poblaciones, esto lo logramos obteniendo cada observación de manera aleatoria con el mismo procedimiento. En términos de modelos probabilísticos, cada \\(X_i\\) se extrae de la misma distribución fija \\(F\\) (que pensamos como la “población”) de manera independiente. Esto lo denotamos por \\(X_i \\overset{iid}{\\sim} F.\\) Ejemplo Si \\(X_1, X_2, \\ldots X_n\\) es una muestra de uniformes independientes en \\([0,1]\\), ¿cómo calcularíamos la distribución de muestreo del máximo muestra \\(T_2 = \\max\\)? En este caso, es fácil calcular su función de distribución acumulada de manera exacta: \\[F_{\\max}(x) = P(\\max\\{X_1,X_2,\\ldots X_n\\} \\leq x)\\] El máximo es menor o igual a \\(x\\) si y sólo si todas las \\(X_i\\) son menores o iguales a \\(x\\), así que \\[F_{\\max} (x) = P(X_1\\leq x, X_2\\leq x, \\cdots, X_n\\leq x)\\] como las \\(X_i\\)’s son independientes entonces \\[F_{\\max}(x) = P(X_1\\leq x)P(X_2\\leq x)\\cdots P(X_n\\leq x) = x^n\\] para \\(x\\in [0,1]\\), pues para cada \\(X_i\\) tenemos \\(P(X_i\\leq x) = x\\). Así que no es necesario usar simulación para conocer esta distribución de muestreo. Derivando esta distribución acumulada obtenemos su densidad, que es \\[f(x) = nx^{n-1}\\] para \\(x\\in [0,1]\\), y es cero en otro caso. Si comparamos con nuestra simulación: teorica <- tibble(x = seq(0, 1 ,0.001)) |> mutate(f_dens = 15 * x^14) sim_estimador_3 <- replicar_muestreo_unif(max, 4000, 15) ggplot(sim_estimador_3) + geom_histogram(aes(x = estimacion), breaks = seq(0, 1, 0.02)) + xlim(c(0.5, 1)) + # el histograma es de ancho 0.02 y el número de simulaciones 4000 geom_line(data = teorica, aes(x = x, y = (4000 * 0.02) * f_dens), colour = "red", linewidth = 1.3) Y vemos que con la simulación obtuvimos una buena aproximación Nota: ¿cómo se relaciona un histograma con la función de densidad que genera los datos? Supón que \\(f(x)\\) es una función de densidad, y obtenemos un número \\(n\\) de simulaciones independientes. Si escogemos un histograma de ancho \\(\\Delta\\), ¿cuántas observaciones esperamos que caigan en un intervalo \\(I = [a - \\Delta/2, a + \\Delta/2]\\)?. La probabilidad de que una observación caiga en \\(I\\) es igual a \\[P(X\\in I) = \\int_I f(x)\\,dx = \\int_{a - \\Delta/2}^{a + \\Delta/2} f(x)\\,dx \\approx f(a) \\text{long}(I) = f(a) \\Delta\\] para \\(\\Delta\\) chica. Si nuestra muestra es de tamaño \\(n\\), el número esperado de observaciones que caen en \\(I\\) es entonces \\(nf(a)\\Delta\\). Eso explica el ajuste que hicimos en la gráfica de arriba. Otra manera de hacer es ajustando el histograma: si en un intervalo el histograma alcanza el valor \\(y\\), \\[f(a) = \\frac{y}{n\\Delta}\\] teorica <- tibble(x = seq(0, 1 ,0.001)) |> mutate(f_dens = 15*x^{14}) ggplot(sim_estimador_3) + geom_histogram(aes(x = estimacion, y = after_stat(density)), breaks = seq(0, 1, 0.02)) + xlim(c(0.5, 1)) + # el histograma es de ancho 0.02 y el número de simulaciones 4000 geom_line(data = teorica, aes(x = x, y = f_dens), colour = "red", size = 1.3) Ejemplo Supongamos que las \\(X_i\\)’s son independientes y exponenciales con tasa \\(\\lambda > 0\\). ¿Cuál es la distribución de muestreo de la suma \\(S = X_1 + \\cdots + X_n\\)? Sabemos que la suma de exponenciales independientes es una distribución gamma con parámetros \\((n, \\lambda)\\), y esta es la distribución de muestreo de nuestra estadística \\(S\\) bajo las hipótesis que hicimos. Podemos checar este resultado con simulación, por ejemplo para una muestra de tamaño \\(n=15\\) con \\(\\lambda = 1\\): replicar_muestreo_exp <- function(est = mean, m, n = 150, lambda = 1){ valores_est <- map_dbl(1:m, ~ est(rexp(n, lambda))) tibble(id_muestra = 1:m, estimacion = valores_est) } sim_estimador_1 <- replicar_muestreo_exp(sum, 4000, n = 15) teorica <- tibble(x = seq(0, 35, 0.001)) |> mutate(f_dens = dgamma(x, shape = 15, rate = 1)) # graficar aprox de distribución de muestreo ggplot(sim_estimador_1) + geom_histogram(aes(x = estimacion, y = after_stat(density)), bins = 35) + geom_line(data = teorica, aes(x = x, y = f_dens), colour = "red", linewidth = 1.2) Teorema central del límite Si consideramos los ejemplos de arriba donde tratamos con estimadores basados en una suma, total o una media —y en menor medida cuantiles muestrales—, vimos que las distribución de muestreo de las estadísticas que usamos tienden a tener una forma común. Estas son manifestaciones de una regularidad estadística importante que se conoce como el teorema central del límite: las distribuciones de muestreo de sumas y promedios son aproximadamente normales cuando el tamaño de muestra es suficientemente grande. Teorema central del límite Si \\(X_1,X_2, \\ldots, X_n\\) son independientes e idénticamente distribuidas con media \\(\\mu\\) y desviación estándar \\(\\sigma\\) finitas. Si el tamaño de muestra \\(n\\) es grande, entonces la distribución de muestreo de la media \\[\\bar{X} = \\frac{X_1 + X_2 +\\cdots + X_n}{n}\\] es aproximadamente normal con media \\(\\mu\\) y desviación estándar \\(\\sigma/\\sqrt{n}\\), que escribimos como \\[\\bar{X} \\xrightarrow{} \\mathsf{N}\\left( \\mu, \\frac{\\sigma}{\\sqrt{n}} \\right)\\] Adicionalmente, la distribución de la media estandarizada converge a una distribución normal estándar cuando \\(n\\) es grande: \\[\\sqrt{n} \\, \\left( \\frac{\\bar{X}-\\mu}{\\sigma} \\right) \\xrightarrow{} \\mathsf{N}(0, 1)\\] El error estándar de \\(\\bar{X}\\) es \\(\\text{ee}(\\bar{X}) = \\frac{\\sigma}{\\sqrt{n}}\\). Si tenemos una muestra, podemos estimar \\(\\sigma\\) con de la siguiente forma: \\[\\hat{\\sigma} =\\sqrt{\\frac{1}{n}\\sum_{i=1}^n (X_i - \\bar{X})^2}\\] o el más común (que explicaremos más adelante) \\[\\hat{s} = \\sqrt{\\frac{1}{n-1}\\sum_{i=1}^n (X_i - \\bar{X})^2}\\] Este hecho junto con el teorema del límite central nos dice cuál es la dispersión, y cómo se distribuyen las posibles desviaciones de la media muestral alrededor de la verdadera media poblacional. ¿Qué tan grande debe ser \\(n\\). Depende de cómo es la población. Cuando la población tiene una distribución muy sesgada, por ejemplo, \\(n\\) típicamente necesita ser más grande que cuando la población es simétrica si queremos obtener una aproximación “buena”. En algunos textos se afirma que \\(n\\geq 30\\) es suficiente para que la aproximación del Teorema central del límite (TCL) sea buena siempre y cuando la distribución poblacional no sea muy sesgada. Esta regla es más o menos arbitraria y es mejor no confiarse, pues fácilmente puede fallar. En la práctica es importante checar este supuesto, por ejemplo usando remuestreo (que veremos más adelante) Revisa los ejemplos que hemos visto hasta ahora (precios de casas, simulaciones de uniformes y exponenciales según las distintas estadísticas que consideramos). ¿Qué distribuciones de muestreo parecen tener una distribución normal? ¿Cómo juzgamos si estas distribuciones están cerca o lejos de una distribución normal? Normalidad y gráficas de cuantiles normales Para checar si una distribución de datos dada es similar a la normal, la herramienta mas común en estádística es la gráfica de cuantiles teóricos, que es una generalización de la gráfica de cuantiles que vimos anteriormente. En primer lugar, definimos la función de cuantiles de una distribución teórica, que es análoga a la que definimos para conjuntos de datos: Supongamos que tenemos una distribución acumulada teórica \\(\\Phi\\). Podemos definir el cuantil-\\(f\\) \\(q(f)\\) de \\(\\Phi\\) como el valor \\(q(f)\\) tal que \\[q(f) = \\text{argmin}\\{x \\,| \\, \\Phi(x)\\geq f \\}\\] En el caso de que \\(\\Phi\\) tiene densidad \\(\\phi\\), y su soporte es un intervalo (que puede ser de longitud infinita), entonces podemos también escribir \\(q(f)\\) como el valor único donde acumulamos \\(f\\) de la probabilidad \\[\\int_{-\\infty}^{q(f)} \\phi(x)\\,dx= f\\] Por ejemplo, para una densidad normal, abajo mostramos los cuantiles \\(f=0.5\\) (mediana) y \\(f=0.95\\) densidad_tbl <- tibble(x = seq(0, 10, 0.01)) |> mutate(densidad = dnorm(x, 5, 1)) # qnorm es la función de cuantiles de una normal cuantil_50 <- qnorm(0.50, 5, 1) cuantil_90 <- qnorm(0.95, 5, 1) # graficamos densidad_tbl <- densidad_tbl |> mutate(menor_50 = x >= cuantil_50) |> mutate(menor_90 = x >= cuantil_90) g_normal_50 <- ggplot(densidad_tbl, aes(y = densidad)) + ylab('f(x)') + geom_area(aes(x = x, fill = menor_50)) + geom_line(aes(x = x), alpha = 0.1) + geom_vline(xintercept = cuantil_50) + theme(legend.position = "none") + annotate("text", 4.3, 0.2, label = "50%") + labs(subtitle = paste0("q(0.5)=", round(cuantil_50,1))) g_normal_90 <- ggplot(densidad_tbl, aes(y = densidad)) + ylab('f(x)') + geom_area(aes(x = x, fill = menor_90)) + geom_line(aes(x = x), alpha = 0.1) + geom_vline(xintercept = cuantil_90) + theme(legend.position = "none") + annotate("text", 5.0, 0.2, label = "95%") + labs(subtitle = paste0("q(0.95)=", round(cuantil_90,1))) g_normal_50 + g_normal_90 Como todas las distribuciones normales tienen la misma forma, y para obtener una de otra solo basta reescalar y desplazar, para calcular los cuantiles de una variable con distribución normal \\(\\mathsf{N}(\\mu, \\sigma)\\) sólo tenemos que saber los cuantiles de la distribución normal estándar \\(\\mathsf{N}(0,1)\\) y escalarlos apropiadamente por su media y desviación estándar \\[q(f, \\mu, \\sigma) = \\mu + \\sigma q(f, 0, 1)\\] Puedes demostrar esto sin mucha dificultad empezando con \\(P(X\\leq q) = f\\) y estandarizando: \\[P(X\\leq q(f, \\mu, \\sigma)) = f \\implies P\\left (Z\\leq \\frac{q(f,\\mu,\\sigma) - \\mu}{\\sigma}\\right)=f\\] y esto implica que \\[q(f, 0, 1) = \\frac{q(f,\\mu,\\sigma) - \\mu}{\\sigma} \\implies q(f, \\mu, \\sigma) = \\mu + \\sigma q(f, 0, 1)\\] De modo que si graficáramos los cuantiles de una distribución \\(\\mathsf{N}(\\mu, \\sigma)\\) contra los cuantiles de una distribución \\(\\mathsf{N}(0,1)\\), estos cuantiles aparecen en una línea recta: comparacion_tbl <- tibble(f = seq(0.01, 0.99, 0.01)) |> mutate(cuantiles_normal = qnorm(f, 5, 3), cuantiles_norm_estandar = qnorm(f, 0, 1)) ggplot(comparacion_tbl, aes(cuantiles_norm_estandar, cuantiles_normal)) + geom_point() Ahora supongamos que tenemos una muestra \\(X_1, \\ldots, X_n\\). ¿Cómo podemos checar si estos datos tienen una distribución aproximadamente normal? Si la muestra tiene una distribución aproximadamente \\(\\mathsf{N}(\\mu, \\sigma)\\), entonces sus cuantiles muestrales y los cuantiles respectivos de la normal estándar están aproximadamente en una línea recta. Primero veamos un ejemplo donde los datos son generados según una normal. set.seed(21) muestra <- tibble(x_1 = rnorm(60, 10, 3), x_2 = rgamma(60, 2, 5)) graf_1 <- ggplot(muestra, aes(sample = x_1)) + geom_qq(distribution = stats::qnorm) + geom_qq_line(colour = "red") graf_2 <- ggplot(muestra, aes(sample = x_2)) + geom_qq(distribution = stats::qnorm) + geom_qq_line(colour = "red") graf_1 + graf_2 ¿Cuáles son los datos aproximadamente normales? ¿Cómo interpretas las desviaciones de la segunda gráfica en términos de la forma de la distribución normal? Prueba de hipótesis de normalidad Para interpretar las gráficas de cuantiles normales se requiere práctica, pues claramente los datos, aún cuando provengan de una distribución normal, no van a caer justo sobre una línea recta y observaremos variabilidad. Esto no descarta necesariamente que los datos sean aproximadamente normales. Con la práctica, generalmente esta gráfica nos da una buena indicación si el supuesto de normalidad es apropiado. Sin embargo, podemos hacer una prueba de hipótesis formal de normalidad si quisiéramos. La hipótesis nula es la siguiente: Los datos provienen de una distribución normal, y las desviaciones que observamos de una línea recta se deben a variación muestral. Podemos generar datos nulos tomando la media y desviación estándar muestrales, y generando muestras normales \\(\\mathsf{N}(\\bar{x}, s)\\). Usamos el lineup, produciendo datos bajo la hipótesis nula y viendo si podemos distinguir los datos. Por ejemplo: library(nullabor) lineup_normal <- lineup(null_dist("x_2", dist = "normal"), muestra) ggplot(lineup_normal, aes(sample = x_2)) + geom_qq(distribution = stats::qnorm) + geom_qq_line(colour = "red") + facet_wrap(~ .sample) En esta gráfica claramente rechazaríamos la hipótesis de normalidad. Sin embargo, para la primera muestra, obtenemos: lineup_normal <- lineup(null_dist("x_1", dist = "normal"), muestra) ggplot(lineup_normal, aes(sample = x_1)) + geom_qq(distribution = stats::qnorm) + geom_qq_line(colour = "red") + facet_wrap(~ .sample) Los datos verdaderos están en attr(lineup_normal, "pos") ## [1] 4 Ejemplo Consideremos el problema de estimar el total poblacional de los precios de las casas que se vendieron. El estimador que usamos fue la suma muestral expandida por un factor. Vamos a checar qué tan cerca de la normalidad está la distribución de meustreo de esta estadística (\\(n=250\\)): replicaciones_2 ## # A tibble: 1,500 × 4 ## id_muestra total_muestra factor_exp est_total_millones ## <chr> <dbl> <dbl> <dbl> ## 1 1 47089. 4.58 215. ## 2 2 45654. 4.58 209. ## 3 3 43973. 4.58 201. ## 4 4 45665. 4.58 209. ## 5 5 43551. 4.58 199. ## 6 6 46066. 4.58 211. ## 7 7 46626. 4.58 213. ## 8 8 47944. 4.58 219. ## 9 9 45381. 4.58 208. ## 10 10 46519. 4.58 213. ## # ℹ 1,490 more rows ggplot(replicaciones_2, aes(sample = est_total_millones)) + geom_qq(alpha = 0.3) + geom_qq_line(colour = "red") Y vemos que en efecto el TCL aplica en este ejemplo, y la aproximación es buena. Aunque la población original es sesgada, la descripción de la distribución de muestreo es sorprendemente compacta: La distribución de muestreo de nuestro estimador del total \\(\\hat{t}\\) es aproximadamente normal con media \\(\\bar{x}\\) y desviación estándar \\(s\\), donde: mu <- mean(replicaciones_2$est_total_millones) s <- sd(replicaciones_2$est_total_millones) c(mu = mu, s = s) |> round(2) ## mu s ## 209.90 5.24 Estas cantidades están en millones de dólares. Ejemplo Supongamos que queremos calcular la probabilidad que la suma de 30 variables uniformes en \\([0,1]\\) independientes sea mayor que 18. Podríamos aproximar esta cantidad usando simulación. Otra manera de aproximar esta cantidad es con el TCL, de la siguiente forma: Si \\(S=X_1 + X_2 + X_{30}\\), entonces la media de \\(S\\) es 15 (¿cómo se calcula?) y su desviación estándar es \\(\\sqrt{\\frac{30}{12}}\\). La suma es entonces aproximadamente \\(\\mathsf{N}\\left(15, \\sqrt{\\frac{30}{12}}\\right)\\). Entonces \\[P(S > 18) = P \\left (\\frac{S - 15}{\\sqrt{\\frac{30}{12}}} > \\frac{18 - 15}{\\sqrt{\\frac{30}{12}}}\\right) \\approx P(Z > 1.897)\\] donde \\(Z\\) es normal estándar. Esta última cantidad la calculamos usando la función de distribución de la normal estándar, y nuestra aproximación es 1 - pnorm(1.897) ## [1] 0.02891397 Podemos checar nuestro cálculo usando simulación: tibble(n_sim = 1:100000) |> mutate(suma = map_dbl(n_sim, ~ sum(runif(30)))) |> summarise(prob_may_18 = mean(suma > 18), .groups = "drop") ## # A tibble: 1 × 1 ## prob_may_18 ## <dbl> ## 1 0.0280 Y vemos que la aproximación normal es buena para fines prácticos. Usando simulaciones haz un histograma que aproxime la distribución de muestreo de \\(S\\). Haz una gráfica de cuantiles normales para checar la normalidad de esta distribución. Ejemplo Cuando el sesgo de la distribución poblacional es grande, puede ser necesario que \\(n\\) sea muy grande para que la aproximación normal sea aceptable para el promedio o la suma. Por ejemplo, si tomamos una gamma con parámetro de forma chico, \\(n = 30\\) no es suficientemente bueno, especialmente si quisiéramos aproximar probabilidades en las colas de la distribución: sims_gamma <- map_df(1:2000, ~ tibble(suma = sum(rgamma(30, 0.1, 1))), .id = "n_sim") ggplot(sims_gamma, aes(x = suma)) + geom_histogram() Más del Teorema central del límite El teorema central del límite aplica a situaciones más generales que las del enunciado del teorema básico. Por ejemplo, aplica a poblaciones finitas (como vimos en el ejemplo de las casas), en 1960 Jaroslav Hajek demostró una versión del TCL bajo muestreo sin reemplazo. Mas allá de la media muestral, el TCL se puede utilizar para más estadísticas ya que muchas pueden verse como promedios, como totales o errores estándar. El TLC se ha generalizado incluso para cuantiles muestrales. Es importante notar que la calidad de la aproximación del TCL depende de características de la población y también del tamaño de muestra \\(n\\). Para ver si el TCL aplica, podemos hacer ejercicios de simulación bajo diferentes supuestos acerca de la población. También veremos más adelante, con remuestreo, maneras de checar si es factible el TCL dependiendo del análisis de una muestra dada que tengamos. El TCL era particularmente importante en la práctica antes de que pudiéramos hacer simulación por computadora. Era la única manera de aproximar y entender la distribución muestral fuera de cálculos analíticos (como los que hicimos para el máximo de un conjunto de uniformes, por ejemplo). Hoy en día, veremos que podemos hacer simulación para obtener respuestas más exactas, particularmente en la construcción de intervalos de confianza, por ejemplo. Dependemos menos de resultados asintóticos, como el TCL. Cuando aproximamos una distribución discreta mediante la distribución normal, conviene hacer correcciones de continuidad, como se explica en (Chihara and Hesterberg 2018), 4.3.2. Referencias "],["intervalos-de-confianza-y-remuestreo.html", "Sección 5 Intervalos de confianza y remuestreo Ejemplo introductorio La idea del bootstrap El principio de plug-in Discusión: propiedades de la distribución bootstrap Error estándar bootstrap e intervalos normales Ejemplo: inventario de casas vendidas Calibración de intervalos de confianza Interpretación de intervalos de confianza Sesgo Intervalos bootstrap de percentiles Bootstrap para dos muestras Bootstrap y otras estadísticas Bootstrap y estimadores complejos: tablas de perfiles Bootstrap y muestras complejas Bootstrap en R Conclusiones y observaciones", " Sección 5 Intervalos de confianza y remuestreo 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: 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 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. Pero no tenemos la distribución poblacional. Sin embargo, podemos estimar la distribución poblacional con nuestros valores muestrales. Mundo bootstrap 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. Evaluamos nuestra estadística en cada una de estas remuestras, a estas les llamamos replicaciones bootstrap. A la distribución de las replicaciones le llamamos distribución bootstrap o distribución de remuestreo del estimador. 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: set.seed(2112) poblacion_casas <- read_csv("data/casas.csv") muestra <- slice_sample(poblacion_casas, n = 200, replace = TRUE) mean(muestra$precio_miles) ## [1] 179.963 Esta muestra nos da nuestro estimador de la distribución poblacional: 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: 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: media <- mean(muestra$precio_miles) media ## [1] 179.963 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: 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: 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!): limites_ic <- quantile(media_muestras, c(0.05, 0.95)) |> round() limites_ic ## 5% 95% ## 171 189 Presentaríamos nuestro resultado como sigue: nuestra estimación puntual de la mediana es 180, con un intervalo de confianza del 90% de (171, 189) 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: ee_boot <- sd(media_muestras) round(ee_boot, 2) ## [1] 5.39 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 muestra <- sample(1:20, 5) muestra ## [1] 6 10 7 3 14 Una remuestra se obtiene: sample(muestra, size = 5, replace = TRUE) ## [1] 7 3 7 10 6 Nótese que algunos valores de la muestra original pueden aparecer varias veces, y otros no aparecen del todo. 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: Remuestreo para una población. Dada una muestra de tamaño \\(n\\) de una población, Obtenemos una remuestra de tamaño \\(n\\) con reemplazo de la muestra original y calculamos la estadística de interés. Repetimos este remuestreo muchas veces (por ejemplo, 10,000). 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 and Hesterberg (2018) muestran lo que acabamos de describir: Figure 5.1: Mundo Real Figure 5.2: Mundo Bootstrap 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. ¿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 (2013), o aquí), \\(\\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: 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") ## # A tibble: 1 × 2 ## prop_negro n ## <dbl> <int> ## 1 0.247 300 ¿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: # 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: 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()) ## # A tibble: 4 × 2 ## name value ## <chr> <dbl> ## 1 media 0.247 ## 2 ee 0.025 ## 3 cuantil_75 0.263 ## 4 cuantil_25 0.23 –> –> 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): Podemos calcular las distribuciones de remuestreo (bootstrap) para cada muestra, y compararlas con la distribución de muestreo real. # 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() 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: poblacion_casas |> summarise(media = mean(precio_miles), .groups = "drop") ## # A tibble: 1 × 1 ## media ## <dbl> ## 1 183. 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í. 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. Entonces, ¿cuántas muestras bootstrap? 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 and Tibshirani (1993)). Cuando se busca estimar error estándar Chihara and Hesterberg (2018) 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. 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: # 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) ## [1] 0.25 Podemos graficar su distribución bootstrap —la cual simulamos arriba—. 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: media_boot <- prop_negro_tbl |> pull(prop_negro) |> mean() media_boot - prop_hat ## [1] 0.0004393333 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 ee_boot <- prop_negro_tbl |> pull(prop_negro) |> sd() ee_boot ## [1] 0.02485138 y construimos un intervalo de confianza del 95%: intervalo_95 <- c(prop_hat - 2 * ee_boot, prop_hat + 2 * ee_boot) intervalo_95 |> round(2) ## [1] 0.2 0.3 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 0.2 y 0.3. 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\\): # 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 total_est <- calc_estimador_casas(muestra_casas) sesgo <- mean(totales_boot$total_boot) - total_est sesgo ## [1] 110.0851 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: ee_boot <- sd(totales_boot$total_boot) sesgo_relativo <- sesgo / ee_boot sesgo_relativo ## [1] 0.01522088 De forma que procedemos a construir intervalos de confianza como sigue : c(total_est - 2*ee_boot, total_est + 2*ee_boot) ## [1] 203366.6 232296.6 Que está en miles de dólares. En millones de dólares, este intervalo es: intervalo_total <- c(total_est - 2*ee_boot, total_est + 2*ee_boot) / 1000 intervalo_total |> round(1) ## [1] 203.4 232.3 Así que con 95% de confianza el verdadero total del valor de las casas vendidas está entre 203 y 232 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: 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") 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 total <- sum(poblacion_casas$precio_miles) sims_tbl |> summarise(cobertura = mean(cubre)) ## # A tibble: 1 × 1 ## cobertura ## <dbl> ## 1 0.96 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.). 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. Verificar la cobertura de nuestros intervalos de confianza por medio simulación está bien estudiado para algunos casos. Por ejemplo, cuando trabajamos con estimaciones para poblaciones teóricas. En general sabemos que los procedimientos funcionan bien en casos: con distribuciones simétricas que tengan colas no muy largas; estimación de proporciones donde no tratamos con casos raros o casos seguros (probabilidades cercanas a 0 o 1). Interpretación de intervalos de confianza Como hemos visto, “intervalo de confianza” (de 90% de confianza, por ejemplo) es un término frecuentista, que significa: Cada muestra produce un intervalo distinto. Para el 90% de las muestras posibles, el intervalo cubre al valor poblacional. La afirmación es sobre el intervalo y el mecanismo para construirlo. Así que con alta probabilidad, el intervalo contiene el valor poblacional. Intervalos más anchos nos dan más incertidumbre acerca de dónde está el verdadero valor poblacional (y al revés para intervalos más angostos). Existen también “intervalos de credibilidad” (de 90% de probabilidad, por ejemplo), que se interpetan de forma bayesiana: Con 90% de probabilidad (relativamente alta), creemos que el valor poblacional está dentro del intervalo de credibilidad. Esta última interpretación es más natural. Obsérvese que para hablar de intervalos de confianza frecuentista tenemos que decir: Este intervalo particular cubre o no al verdadero valor, pero nuestro procedimiento produce intervalos que contiene el verdadero valor para el 90% de las muestras. Esta es una interpretación relativamente débil, y muchos intervalos poco útiles pueden satisfacerla. La interpretación bayesiana es más natural porque expresa más claramente incertidumbre acerca del valor poblacional. Por otro lado, La interpretación frecuentista nos da maneras empíricas de probar si los intervalos de confianza están bien calibrados o no: es un mínimo que “intervalos del 90%” deberían satisfacer. Así que tomamos el punto de vista bayesiano en la intepretación, pero buscamos que nuestros intervalos cumplan o aproximen bien garantías frecuentistas (discutimos esto más adelante). Los intervalos que producimos en esta sección pueden interpretarse de las dos maneras. Sesgo Notemos que hemos revisado el sesgo en varias ocasiones, esto es porque algunos estimadores comunes (por ejemplo, cociente de dos cantidades aleatorias) pueden sufrir de sesgo grande, especialmente en el caso de muestras chicas. Esto a su vez afecta la cobertura, pues es posible que nuestros intervalos no tengan “cobertura simétrica”, por ejemplo. Para muchos estimadores, y muestras no muy chicas, esté sesgo tiende a ser poco importante y no es necesario hacer correcciones. Si el tamaño del sesgo es grande comparado con la dispersión de la distribución bootstrap generalmente consideramos que bajo el diseño actual el estimador que estamos usando no es apropiado, y podemos proponer otro estimador u otro procedimiento para construir intervalos (ver Efron and Tibshirani (1993) intervalos BC_{a}), Efron and Tibshirani (1993) sugieren más de 20% de la desviación estándar, mientras que en Chihara and Hesterberg (2018) se sugiere 2% de la desviación estándar. Dependiendo que tan crítico es que los intervalos estén bien calibrados podemos evaluar nuestro problema particular. Intervalos bootstrap de percentiles Retomemos nuestro ejemplo del valor total del precio de las casas. A través de remuestras bootstrap hemos verificado gráficamente que la distribución de remuestreo es ligeramente asimétrica (ver la figura de abajo). Anteriormente hemos calculado intervalos de confianza basados en supuestos normales por medio del error éstandar. Este intervalo está dado por ## [1] 203.4 232.3 y por construcción sabemos que es simétrico con respecto al valor estimado, pero como podemos ver la distribución de muestreo no es simétrica, lo cual podemos confirmar por ejemplo calculando el porcentaje de muestras bootstrap que caen por arriba y por debajo del intervalo construido: ## # A tibble: 1 × 2 ## prop_inf prop_sup ## <dbl> <dbl> ## 1 0.0192 0.026 los cuales se han calculado como el porcentaje de medias bootstrap por debajo (arriba) de la cota inferior (superior), y vemos que no coinciden con el nivel de confianza prestablecido (2.5% para cada extremo). Otra opción común que se usa específicamente cuando la distribución bootstrap no es muy cercana a la normal son los intervalos de percentiles bootstrap: Definición. El intervalo de percentiles bootstrap al 95% de confianza está dado por \\[[q_{0.025}, q_{0.975}]\\] donde \\(q_f\\) es el percentil \\(f\\) de la distribución bootstrap. Otros intervalos comunes son el de 80% o 90% de confianza, por ejemplo, que corresponden a \\([q_{0.10}, q_{0.90}]\\) y \\([q_{0.05}, q_{0.95}]\\). Ojo: intervalos de confianza muy alta (por ejemplo 99.5%) pueden tener mala calibración o ser muy variables en su longitud pues dependen del comportamiento en las colas de la distribución. Para el ejemplo de las casas, calcularíamos simplemente intervalo_95 <- totales_boot |> pull(total_boot) |> quantile(probs = c(0.025, 0.975)) / 1000 (intervalo_95) |> round(1) ## 2.5% 97.5% ## 204.3 232.5 que está en millones de dólares. Nótese que es similar al intervalo de error estándar. Otro punto interesante sobre los intervalos bootstrap de percentiles es que lidian naturalmente con la asímetría de la distribución bootstrap. Ilustramos esto con la distancia de las extremos del intervalo con respecto a la media: abs(intervalo_95 - total_est/1000) ## 2.5% 97.5% ## 13.53912 14.64674 Los intervalos de confianza nos permiten presentar un rango de valores posibles para el parámetro de interés. Esto es una notable diferencia con respecto a presentar sólo un candidato como estimador. Nuestra fuente de información son los datos. Es por esto que si vemos valores muy chicos (grandes) en nuestra muestra, el intervalo se tiene que extender a la izquierda (derecha) para compensar dichas observaciones. Explica por qué cuando la aproximación normal es apropiada, el intervalo de percentiles al 95% es muy similar al intervalo normal de 2 errores estándar. Ejemplo Consideramos los datos de propinas. Queremos estimar la media de cuentas totales para la comida y la cena. Podemos hacer bootstrap de cada grupo por separado: # en este ejemplo usamos rsample, pero puedes # escribir tu propio código library(rsample) propinas <- read_csv("data/propinas.csv") estimador <- function(split, ...){ muestra <- analysis(split) |> group_by(momento) muestra |> summarise(estimate = mean(cuenta_total), .groups = 'drop') |> mutate(term = momento) } intervalo_propinas_90 <- bootstraps(propinas, strata = momento, 1000) |> mutate(res_boot = map(splits, estimador)) |> int_pctl(res_boot, alpha = 0.10) |> mutate(across(where(is.numeric), round, 2)) intervalo_propinas_90 ## # A tibble: 2 × 6 ## term .lower .estimate .upper .alpha .method ## <chr> <dbl> <dbl> <dbl> <dbl> <chr> ## 1 Cena 19.8 20.8 22.0 0.1 percentile ## 2 Comida 15.6 17.1 18.8 0.1 percentile Nota: .estimate es la media de los valores de la estadística sobre las remuestras, no es el estimador original. De la tabla anterior inferimos que la media en la cuenta en la cena es más grande que la de la comida. Podemos graficar agregando los estimadores plugin: estimadores <- propinas |> group_by(momento) |> rename(term = momento) |> summarise(media = mean(cuenta_total)) ggplot(intervalo_propinas_90, aes(x = term)) + geom_linerange(aes(ymin = .lower, ymax = .upper)) + geom_point(data = estimadores, aes(y = media), colour = "red", size = 3) + xlab("Momento") + ylab("Media de cuenta total (dólares)") + labs(subtitle = "Intervalos de 90% para la media") Nótese que el bootstrap lo hicimos por separado en cada momento del día (por eso el argumento strata en la llamada a bootstraps): Justifica el procedimiento de hacer el bootstrap separado para cada grupo. ¿Qué supuestos acerca del muestreo se deben satisfacer? ¿Deben ser muestras aleatorias simples de cada momento del día, por ejemplo? ¿Qué harías si no fuera así, por ejemplo, si se escogieron al azar tickets de todos los disponibles en un periodo? Bootstrap para dos muestras En el ejemplo anterior consideramos cómo hacer bootstrap cuando tenemos muestras independientes. También podemos aplicarlo a estimadores que comparen directamente las dos muestras: Bootstrap para comparar poblaciones. Dadas muestras independientes de tamaños \\(m\\) y \\(n\\) de dos poblaciones: Extraer una remuestra de tamaño \\(m\\) con reemplazo de la primera muestra y una remuestra separada de tamaño \\(n\\) de la segunda muestra. Calcula la estadística que compara los dos grupos (por ejemplo, diferencia de medias) Repetir este proceso muchas veces (por ejemplo, 1000 - 10000). Construir la distribución bootstrap de la estadística. Examinar dispersión, sesgo y forma. Ejemplo Supongamos que queremos comparar directamente la media de la cuenta total en comida y cena. Podemos hacer: estimador_dif <- function(split, ...){ muestra <- analysis(split) |> group_by(momento) muestra |> summarise(estimate = mean(cuenta_total), .groups = "drop") |> pivot_wider(names_from = momento, values_from = estimate) |> mutate(estimate = Cena - Comida, term = "diferencia") } dist_boot <- bootstraps(propinas, strata = momento, 2000) |> mutate(res_boot = map(splits, estimador_dif)) g_1 <- ggplot(dist_boot |> unnest(res_boot), aes(x = estimate)) + geom_histogram(bins = 20) + xlab("Diferencia Comida vs Cena") g_2 <- ggplot(dist_boot |> unnest(res_boot), aes(sample = estimate)) + geom_qq() + geom_qq_line(colour = 'red') g_1 + g_2 Y podemos calcular un intervalo de confianza para la diferencia de medias: dist_boot |> int_pctl(res_boot, alpha = 0.01) |> mutate(across(where(is.numeric), round, 2)) |> select(term, .lower, .upper) ## # A tibble: 1 × 3 ## term .lower .upper ## <chr> <dbl> <dbl> ## 1 diferencia 0.73 6.54 Que nos indica que con alta probabilidad las cuentas son más altas que en la cena que en la comida. La diferencia puede ir de un poco menos de un dólar hasta seis dólares con 99% de confianza. Datos pareados En otros casos, las muestras no son independientes y están pareadas. Por ejemplo, este es un estudio dende a 10 personas una noche se les dio una medicina para dormir y otra noche otra medicina. Se registraron cuántas horas de sueño extra comparados con un día donde no tomaron medicina. dormir <- sleep |> pivot_wider(names_from = group, names_prefix = "medicina_", values_from = extra) dormir ## # A tibble: 10 × 3 ## ID medicina_1 medicina_2 ## <fct> <dbl> <dbl> ## 1 1 0.7 1.9 ## 2 2 -1.6 0.8 ## 3 3 -0.2 1.1 ## 4 4 -1.2 0.1 ## 5 5 -0.1 -0.1 ## 6 6 3.4 4.4 ## 7 7 3.7 5.5 ## 8 8 0.8 1.6 ## 9 9 0 4.6 ## 10 10 2 3.4 En este caso, el bootstrap se hace sobre individuos, y quisiéramos comparar la medición de la medicina_1 con la medicina_2. Usaremos la media de al diferencia entre horas de sueño entre las dos medicinas. Nuestro estimador puntual es: estimador_dif <- dormir |> mutate(dif_2_menos_1 = medicina_2 - medicina_1) |> summarise(dif_media = mean(dif_2_menos_1)) estimador_dif ## # A tibble: 1 × 1 ## dif_media ## <dbl> ## 1 1.58 Esto indica que en promedio duermen hora y media más con la medicina 2 que con la medicina 1. Como hay variabilildad considerable en el número de horas extra de cada medicina dependiendo del individuo, es necesario hacer una intervalo de confianza para descartar que esta diferencia haya aparecido por azar debido a la variación muestral. Nótese que aquí no tenemos estratos, pues solo hay una muestra de individuo con dos mediciones. estimador_dif <- function(split, ...){ muestra <- analysis(split) muestra |> mutate(dif_2_menos_1 = medicina_2 - medicina_1) |> summarise(estimate = mean(dif_2_menos_1), .groups = "drop") |> mutate(term = "diferencia 2 vs 1") } dist_boot <- bootstraps(dormir, 2000) |> mutate(res_boot = map(splits, estimador_dif)) g_1 <- ggplot(dist_boot |> unnest(res_boot), aes(x = estimate)) + geom_histogram(bins = 20) g_2 <- ggplot(dist_boot |> unnest(res_boot), aes(sample = estimate)) + geom_qq() + geom_qq_line(colour = 'red') g_1 + g_2 Nuestro intervalo de percentiles al 90% es de dist_boot |> int_pctl(res_boot, 0.10) ## # A tibble: 1 × 6 ## term .lower .estimate .upper .alpha .method ## <chr> <dbl> <dbl> <dbl> <dbl> <chr> ## 1 diferencia 2 vs 1 1.04 1.57 2.22 0.1 percentile Lo que indica con alta probabilidad que la medicina 2 da entre 1 y 2 horas extras de sueño. Nota que en este ejemplo también podríamos hacer una prueba de hipótesis por permutaciones, suponiendo como hipótesis nula que las dos medicinas son equivalentes. Sin embargo, usualmente es más informativo presentar este tipo de intervalos para estimar la diferencia. Bootstrap y otras estadísticas El bootstrap es una técnica versátil. Un ejemplo son estimadores de razón, que tienen la forma \\[ \\hat{r} = \\frac{\\overline y}{\\overline x}\\] Por ejemplo, ¿cómo haríamos estimación para el procentaje de área area habitable de las casas en relación al tamaño del lote? Una manera de estimar esta cantidad es dividiendo la suma del área habitable de nuestra muestra y dividirlo entre la suma del área de los lotes de nuestra muestra, como en la fórmula anterior. Esta fórmula es más difícil pues tanto numerador como denominador tienen variabilidad, y estas dos cantidades no varían independientemente. Con el bootstrap podemos atacar estos problemas Ejemplo: estimadores de razón Nuestra muestra original es: set.seed(250) casas_muestra <- slice_sample(poblacion_casas, n = 200) El estimador de interés es: estimador_razon <- function(split, ...){ muestra <- analysis(split) muestra |> summarise(estimate = sum(area_habitable_sup_m2) / sum(area_lote_m2), .groups = "drop") |> mutate(term = "% area del lote construida") } Y nuestra estimación puntual es estimacion <- muestra_casas |> summarise(estimate = sum(area_habitable_sup_m2) / sum(area_lote_m2)) estimacion ## # A tibble: 1 × 1 ## estimate ## <dbl> ## 1 0.148 Es decir que en promedio, un poco más de 15% del lote total es ocupado por área habitable. Ahora hacemos bootstrap para construir un intervalo: dist_boot <- bootstraps(casas_muestra, 2000) |> mutate(res_boot = map(splits, estimador_razon)) g_1 <- ggplot(dist_boot |> unnest(res_boot), aes(x = estimate)) + geom_histogram(bins = 20) g_2 <- ggplot(dist_boot |> unnest(res_boot), aes(sample = estimate)) + geom_qq() + geom_qq_line(colour = 'red') g_1 + g_2 En este caso la cola derecha parece tener menos dispersión que una distribución normal. Usamos un intervalo de percentiles para obtener: dist_boot |> int_pctl(res_boot) |> mutate(estimador = estimacion$estimate) |> rename(media_boot = .estimate) |> mutate(bias = media_boot - estimador) |> pivot_longer(is_double) |> mutate(value = round(value, 3)) ## # A tibble: 6 × 4 ## term .method name value ## <chr> <chr> <chr> <dbl> ## 1 % area del lote construida percentile .lower 0.121 ## 2 % area del lote construida percentile media_boot 0.142 ## 3 % area del lote construida percentile .upper 0.159 ## 4 % area del lote construida percentile .alpha 0.05 ## 5 % area del lote construida percentile estimador 0.148 ## 6 % area del lote construida percentile bias -0.006 De modo que en esta zona, entre 12% y 16% de toda el área disponible es ocupada por área habitable: estas son casas que tienen jardines o terrenos, garage relativamente grandes. Ejemplo: suavizadores Podemos usar el bootstrap para juzgar la variabilidad de un suavizador, que consideramos como nuestra estadística: graf_casas <- function(data){ ggplot(data |> filter(calidad_gral < 7), aes(x = area_habitable_sup_m2)) + geom_point(aes(y = precio_m2_miles), alpha = 0.75) + geom_smooth(aes(y = precio_m2_miles), method = "loess", span = 0.7, se = FALSE, method.args = list(degree = 1, family = "symmetric")) } graf_casas(casas_muestra) Podemos hacer bootstrap para juzgar la estabilidad del suavizador: suaviza_boot <- function(x, data){ # remuestreo muestra_boot <- slice_sample(data, prop = 1, replace = T) ajuste <- loess(precio_m2_miles ~ area_habitable_sup_m2, data = muestra_boot, degree = 1, span = 0.7, family = "symmetric") datos_grafica <- tibble(area_habitable_sup_m2 = seq(25, 250, 5)) ajustados <- predict(ajuste, newdata = datos_grafica) datos_grafica |> mutate(ajustados = ajustados) |> mutate(rep = x) } reps <- map(1:10, ~ suaviza_boot(.x, casas_muestra |> filter(calidad_gral < 7))) |> bind_rows() # ojo: la rutina loess no tienen soporte para extrapolación graf_casas(casas_muestra) + geom_line(data = reps, aes(y = ajustados, group = rep), alpha = 1, colour = "red") Donde vemos que algunas cambios de pendiente del suavizador original no son muy interpretables (por ejemplo, para áreas chicas) y alta variabilidad en general en los extremos. Podemos hacer más iteraciones para calcular bandas de confianza: reps <- map(1:200, ~ suaviza_boot(.x, casas_muestra |> filter(calidad_gral < 7))) |> bind_rows() # ojo: la rutina loess no tienen soporte para extrapolación graf_casas(casas_muestra) + geom_line(data = reps, aes(y = ajustados, group = rep), alpha = 0.2, colour = "red") Donde observamos cómo tenemos incertidumbre en cuanto al nivel y forma de las curvas en los extremos de los datos (casas grandes y chicas), lo cual es natural. Aunque podemos resumir para hacer bandas de confianza, mostrar remuestras de esta manera es informativo: por ejempo: vemos cómo es probable también que para casas de menos de 70 metros cuadrados el precio por metro cuadrado no cambia tanto (líneas constantes) Bootstrap y estimadores complejos: tablas de perfiles Podemos regresar al ejemplo de la primera sesión donde calculamos perfiles de los tomadores de distintos tés: en bolsa, suelto, o combinados. Caundo hacemos estos tipos de análisis no es raro que los prefiles tengan variabilidad considerable que es necesario cuantificar. price tea bag tea bag+unpackaged unpackaged promedio p_upscale -0.71 -0.28 0.98 28 p_variable -0.12 0.44 -0.31 36 p_cheap 0.3 -0.53 0.23 2 p_branded 0.62 -0.16 -0.45 25 p_private label 0.72 -0.22 -0.49 5 p_unknown 1.58 -0.58 -1 3 Hacemos bootstrap sobre toda la muestra, y repetimos exactamente el mismo proceso de construción de perfiles: boot_perfiles <- map(1:1000, function(x){ te_boot <- te |> slice_sample(prop = 1, replace = TRUE) calcular_perfiles(te_boot) |> mutate(rep = x) }) |> bind_rows() Ahora resumimos y graficamos, esta vez de manera distinta: resumen_perfiles <- boot_perfiles |> group_by(how, price) |> summarise(perfil_media = mean(perfil), ymax = quantile(perfil, 0.9), ymin = quantile(perfil, 0.10)) resumen_bolsa <- resumen_perfiles |> ungroup() |> filter(how == "tea bag") |> select(price, perfil_bolsa = perfil_media) resumen_perfiles <- resumen_perfiles |> left_join(resumen_bolsa) |> ungroup() |> mutate(price = fct_reorder(price, perfil_bolsa)) ggplot(resumen_perfiles, aes(x = price, y = perfil_media, ymax = ymax, ymin = ymin)) + geom_point(colour = "red") + geom_linerange() + facet_wrap(~how) + coord_flip() + geom_hline(yintercept = 0, colour = "gray") + ylab("Perfil") + xlab("Precio") Nótese una deficiencia clara del bootstrap: para los que compran té suelto, en la muestra no existen personas que desconocen de dónde provienen su té (No sabe/No contestó). Esto produce un intervalo colapsado en 0 que no es razonable. Podemos remediar esto de varias maneras: quitando del análisis los que no sabe o no contestaron, agrupando en otra categoría, usando un modelo, o regularizar usando proporciones calculadas con conteos modificados: por ejemplo, agregando un caso de cada combinación (agregaría 18 personas “falsas” a una muestra de 290 personas). Bootstrap y muestras complejas La necesidad de estimaciones confiables junto con el uso eficiente de recursos conllevan a diseños de muestras complejas. Estos diseños típicamente usan las siguientes técnicas: muestreo sin reemplazo de una población finita, muestreo sistemático, estratificación, conglomerados, ajustes a no-respuesta, postestratificación. Como consecuencia, los valores de la muestra suelen no ser independientes y los análisis de los mismos dependerá del diseño de la muestra. Comenzaremos con definiciones para entender el problema. set.seed(3872999) n_escuelas <- 5000 tipo <- sample(c("rural", "urbano", "indigena"), n_escuelas, replace = TRUE, prob = c(0.3, 0.5, 0.2)) escuela <- tibble(ind_escuela = 1:n_escuelas, tipo, media_tipo = case_when(tipo == "urbano" ~ 550, tipo == "rural" ~ 400, TRUE ~ 350), media_escuela = rnorm(n_escuelas, media_tipo, 30), n_estudiantes = round(rnorm(n_escuelas, 30, 4))) estudiantes <- uncount(escuela, n_estudiantes, .id = "id_estudiante") %>% rowwise() %>% mutate(calif = rnorm(1, media_escuela, 70)) %>% ungroup() Imaginemos que tenemos una población de 5000 escuelas, y queremos estimar la media de las calificaciones de los estudiantes en una prueba. head(estudiantes) ## # A tibble: 6 × 6 ## ind_escuela tipo media_tipo media_escuela id_estudiante calif ## <int> <chr> <dbl> <dbl> <int> <dbl> ## 1 1 urbano 550 561. 1 488. ## 2 1 urbano 550 561. 2 574. ## 3 1 urbano 550 561. 3 456. ## 4 1 urbano 550 561. 4 507. ## 5 1 urbano 550 561. 5 598. ## 6 1 urbano 550 561. 6 527. La primera idea sería tomar una muestra aleatoria (MAS, muestreo aleatorio simple), donde todos los estudiantes tienen igual probabilidad de ser seleccionados. Con estas condiciones el presupuesto alcanza para seleccionar a 60 estudiantes, hacemos esto y calculamos la media. muestra <- slice_sample(estudiantes, n = 60) round(mean(muestra$calif), 2) ## [1] 466.73 Este número es muy cercano a la media verdadera de la población: 466.51, pero esta es una de muchas posibles muestras. medias_mas <- rerun(1000, mean(sample(estudiantes$calif, 60))) %>% flatten_dbl() sd(medias_mas) ## [1] 14.75242 hist_mas <- ggplot(tibble(medias_mas), aes(x = medias_mas)) + geom_histogram(binwidth = 10) + geom_vline(xintercept = mean(estudiantes$calif), color = "red") + xlim(410, 520) qq_mas <- ggplot(tibble(medias_mas), aes(sample = medias_mas)) + geom_qq(distribution = stats::qunif) + ylim(410, 520) hist_mas + qq_mas Algunas de las muestras generan valores alejados de la verdadera media, para minimizar la probabilidad de seleccionar muestras que lleven a estimaciones alejadas del verdadero valor poblacional podríamos tomar muestras más grandes. Pero usualmente los costos limitan el tamaño de muestra. Una alternativa es estratificar, supongamos que sabemos el tipo de cada escuela (urbana, rural o indígena) y sabemos también que la calificación de los estudiantes de escuelas urbanas tiende a ser distinta a las calificaciones que los estudiantes de escuelas rurales o indígenas. En esta caso un diseño más eficiente consiste en tomar muestras independientes dentro de cada estrato. muestra_estrat <- estudiantes %>% group_by(tipo) %>% sample_frac(0.0004) dim(muestra_estrat) ## [1] 60 6 muestrea_estrat <- function(){ muestra <- estudiantes %>% group_by(tipo) %>% sample_frac(0.0004) mean(muestra$calif) } medias_estrat <- rerun(1000, muestrea_estrat()) %>% flatten_dbl() Notamos que la distribución muestral está más concentrada que el caso de MAS, el error estándar se reduce de 14.75 a 10.2 hist_estrat <- ggplot(tibble(medias_estrat), aes(x = medias_estrat)) + geom_histogram(binwidth = 6) + geom_vline(xintercept = mean(estudiantes$calif), color = "red") + xlim(410, 520) qq_estrat <- ggplot(tibble(medias_estrat), aes(sample = medias_estrat)) + geom_qq(distribution = stats::qunif) + ylim(410, 520) hist_estrat + qq_estrat Entonces, la estratificación nos sirve para reducir el error estándar de las estimaciones. Otro procedimiento común en muestreo es introducir conglomerados, a diferencia del muestreo estratificado, el propósito principal de los conglomerados es reducir costos. Veamos cuantas escuelas tendría que visitar en una muestra dada (con diseño estratificado). n_distinct(muestra_estrat$ind_escuela) ## [1] 60 Es fácil ver que visitar una escuela para aplicar solo uno o dos exámenes no es muy eficiente en cuestión de costos. Es por ello que se suelen tomar muestras considerando conglomerados naturales, en este caso la escuela. En nuestro ejemplo es razonable suponer que una parte grande del costo del muestreo sea mandar al examinador a la escuela, y que una vez en la escuela el costo de evaluar a todo sexto, en lugar de a un único alumno, es relativamente bajo. Podemos imaginar que considerando estos costos por visita de escuela nos alcance para visitar 40 escuelas y en cada una examinar a todos los estudiantes. muestra_escuelas <- escuela %>% group_by(tipo) %>% sample_frac(size = 0.008) muestra_cgl <- muestra_escuelas %>% left_join(estudiantes) mean(muestra_cgl$calif) ## [1] 462.5677 muestrea_cgl <- function(){ muestra_escuelas <- escuela %>% group_by(tipo) %>% sample_frac(size = 0.008) muestra_cgl <- muestra_escuelas %>% left_join(estudiantes, by = c("ind_escuela", "tipo")) mean(muestra_cgl$calif) } medias_cgl <- rerun(1000, muestrea_cgl()) %>% flatten_dbl() En este caso, el número de estudiantes examinados es mucho mayor que en MAS y muestreo estratificado, notemos que el número de estudiantes evaluados cambiará de muestra a muestra dependiendo del número de alumnos en las escuelas seleccionadas. sd(medias_cgl) ## [1] 5.337327 hist_cgl <- ggplot(tibble(medias_cgl), aes(x = medias_cgl)) + geom_histogram(binwidth = 6) + geom_vline(xintercept = mean(estudiantes$calif), color = "red") + xlim(410, 520) qq_cgl <- ggplot(tibble(medias_cgl), aes(sample = medias_cgl)) + geom_qq(distribution = stats::qunif) + ylim(410, 520) hist_cgl + qq_cgl Ejemplo: ENIGH La complejidad de los diseños de encuestas conlleva a que el cálculo de errores estándar sea muy complicado, para atacar este problema hay dos técnicas básicas: 1) un enfoque analítico usando linearización, 2) métodos de remuestreo como bootstrap. El incremento en el poder de cómputo ha favorecido los métodos de remuestreo pues la linearización requiere del desarrollo de una fórmula para cada estimación y supuestos adicionales para simplificar. En 1988 Rao and Wu (1988) propusieron un método de bootstrap para diseños estratificados multietápicos con reemplazo de UPMs (Unidades Primarias de Muestreo) que describimos a continuación. ENIGH. Usaremos como ejemplo la Encuesta Nacional de Ingresos y Gastos de los Hogares, ENIGH 2018 (INEGI 2018), esta encuesta usa un diseño de conglomerados estratificado. Antes de proceder a bootstrap debemos entender como se seleccionaron los datos, esto es, el diseño de la muestra: Unidad primaria de muestreo (UPM). Las UPMs están constituidas por agrupaciones de viviendas. Se les denomina unidades primarias pues corresponden a la primera etapa de selección, las unidades secundarias (USMs) serían los hogares. Estratificación. Los estratos se construyen en base a estado, ámbito (urbano, complemento urbano, rural), características sociodemográficas de los habitantes de las viviendas, características físicas y equipamiento. El proceso de estratificación resulta en 888 subestratos en todo el ámbito nacional. La selección de la muestra es independiente para cada estrato, y una vez que se obtiene la muestra se calculan los factores de expansión que reflejan las distintas probabilidades de selección. Después se llevan a cabo ajustes por no respuesta y por proyección (calibración), esta última busca que distintos dominios de la muestra coincidan con la proyección de población de INEGI. concentrado_hogar <- read_csv(here::here("data", "conjunto_de_datos_enigh_2018_ns_csv", "conjunto_de_datos_concentradohogar_enigh_2018_ns", "conjunto_de_datos", "conjunto_de_datos_concentradohogar_enigh_2018_ns.csv")) # seleccionar variable de ingreso corriente hogar <- concentrado_hogar %>% mutate( upm = as.integer(upm), edo = str_sub(ubica_geo, 1, 2) ) %>% select(folioviv, foliohog, est_dis, upm, factor, ing_cor, edad_jefe, edo) %>% group_by(est_dis) %>% mutate(n = n_distinct(upm)) %>% # número de upms por estrato ungroup() hogar ## # A tibble: 74,647 × 9 ## folioviv foliohog est_dis upm factor ing_cor edad_jefe edo n ## <dbl> <dbl> <dbl> <int> <dbl> <dbl> <dbl> <chr> <int> ## 1 100013601 1 2 1 175 76404. 74 10 106 ## 2 100013602 1 2 1 175 42988. 48 10 106 ## 3 100013603 1 2 1 175 580698. 39 10 106 ## 4 100013604 1 2 1 175 46253. 70 10 106 ## 5 100013606 1 2 1 175 53837. 51 10 106 ## 6 100026701 1 2 2 189 237743. 41 10 106 ## 7 100026703 1 2 2 189 32607. 57 10 106 ## 8 100026704 1 2 2 189 169918. 53 10 106 ## 9 100026706 1 2 2 189 17311. 30 10 106 ## 10 100027201 1 2 3 186 120488. 69 10 106 ## # ℹ 74,637 more rows Para el cálculo de estadísticos debemos usar los factores de expansión, por ejemplo el ingreso trimestral total sería: sum(hogar$factor * hogar$ing_cor / 1000) ## [1] 1723700566 y ingreso trimestral medio (miles pesos) sum(hogar$factor * hogar$ing_cor / 1000) / sum(hogar$factor) ## [1] 49.61029 La estimación del error estándar, por otro lado, no es sencilla y requiere usar aproximaciones, en la metodología de INEGI proponen una aproximación con series de Taylor. Figure 5.3: Extracto de estimación de errores de muestreo, ENIGH 2018. Veamos ahora como calcular el error estándar siguiendo el bootstrap de Rao y Wu: En cada estrato se seleccionan con reemplazo \\(m_h\\) UPMs de las \\(n_h\\) de la muestra original. Denotamos por \\(m_{hi}^*\\) el número de veces que se seleccionó la UPM \\(i\\) en el estrato \\(h\\) (de tal manera que \\(\\sum m_{hi}^*=m_h\\)). Creamos una replicación del ponderador correspondiente a la \\(k\\)-ésima unidad (USM) como: \\[d_k^*=d_k \\bigg[\\bigg(1-\\sqrt{\\frac{m_h}{n_h - 1}}\\bigg) + \\bigg(\\sqrt{\\frac{m_h}{n_h - 1}}\\frac{n_h}{m_h}m_{h}^*\\bigg)\\bigg]\\] donde \\(d_k\\) es el inverso de la probabilidad de selección. Si \\(m_h <(n_h -1)\\) todos los pesos definidos de esta manera serán no negativos. Calculamos el peso final \\(w_k^*\\) aplicando a \\(d_k^*\\) los mismos ajustes que se hicieron a los ponderadores originales. Calculamos el estadístico de interés \\(\\hat{\\theta}\\) usando los ponderadores \\(w_k^*\\) en lugar de los originales \\(w_k\\). Repetimos los pasos 1 y 2 \\(B\\) veces para obtener \\(\\hat{\\theta}^{*1},\\hat{\\theta}^{*2},...,\\hat{\\theta}^{*B}\\). Calculamos el error estándar como: \\[\\hat{\\textsf{se}}_B = \\bigg\\{\\frac{\\sum_{b=1}^B[\\hat{\\theta}^*(b)-\\hat{\\theta}^*(\\cdot)]^2 }{B}\\bigg\\}^{1/2}\\] En principio podemos elegir cualquier valor de \\(m_h \\geq 1\\), el más común es elegir \\(m_h=n_h-1\\), en este caso: \\[d_k^*=d_k \\frac{n_h}{n_h-1}m_{hi}^*\\] en este escenario las unidades que no se incluyen en la muestra tienen un valor de cero como ponderador. Si elegimos \\(n_h \\ne n_h-1\\) las unidades que no están en la muestra tienen ponderador distinto a cero, si \\(m_h=n_h\\) el ponderador podría tomar valores negativos. Implementemos el bootstrap de Rao y Wu a la ENIGH, usaremos \\(m_h=n_h-1\\) # creamos una tabla con los estratos y upms est_upm <- hogar %>% distinct(est_dis, upm, n) %>% arrange(upm) hogar_factor <- est_upm %>% group_by(est_dis) %>% # dentro de cada estrato tomamos muestra (n_h-1) sample_n(size = first(n) - 1, replace = TRUE) %>% add_count(est_dis, upm, name = "m_hi") %>% # calculamos m_hi* left_join(hogar, by = c("est_dis", "upm", "n")) %>% mutate(factor_b = factor * m_hi * n / (n - 1)) # unimos los pasos anteriores en una función para replicar en cada muestra bootstrap svy_boot <- function(est_upm, hogar){ m_hi <- est_upm %>% group_split(est_dis) %>% map(~sample(.$upm, size = first(.$n) - 1, replace = TRUE)) %>% flatten_int() %>% plyr::count() %>% select(upm = x, m_h = freq) m_hi %>% left_join(hogar, by = c("upm")) %>% mutate(factor_b = factor * m_h * n / (n - 1)) } set.seed(1038984) boot_rep <- rerun(500, svy_boot(est_upm, hogar)) # Aplicación a ingreso medio wtd_mean <- function(w, x, na.rm = FALSE) { sum(w * x, na.rm = na.rm) / sum(w, na.rm = na.rm) } # La media es: hogar %>% summarise(media = wtd_mean(factor, ing_cor)) ## # A tibble: 1 × 1 ## media ## <dbl> ## 1 49610. Y el error estándar: map_dbl(boot_rep, ~wtd_mean(w = .$factor_b, x = .$ing_cor)) %>% quantile(c(0.025, 0.975)) ## 2.5% 97.5% ## 48742.12 50519.02 El método bootstrap está implementado en el paquete survey y más recientemente en srvyr que es una versión tidy que utiliza las funciones en survey. Podemos comparar nuestros resultados con la implementación en survey. # 1. Definimos el diseño de la encuesta library(survey) library(srvyr) enigh_design <- hogar %>% as_survey_design(ids = upm, weights = factor, strata = est_dis) # 2. Elegimos bootstrap como el método para el cálculo de errores estándar set.seed(7398731) enigh_boot <- enigh_design %>% as_survey_rep(type = "subbootstrap", replicates = 500) # 3. Así calculamos la media enigh_boot %>% srvyr::summarise(mean_ingcor = survey_mean(ing_cor)) ## # A tibble: 1 × 2 ## mean_ingcor mean_ingcor_se ## <dbl> <dbl> ## 1 49610. 459. enigh_boot %>% srvyr::summarise(mean_ingcor = survey_mean(ing_cor, vartype = "ci")) ## # A tibble: 1 × 3 ## mean_ingcor mean_ingcor_low mean_ingcor_upp ## <dbl> <dbl> <dbl> ## 1 49610. 48709. 50512. # por estado enigh_boot %>% group_by(edo) %>% srvyr::summarise(mean_ingcor = survey_mean(ing_cor)) ## # A tibble: 30 × 3 ## edo mean_ingcor mean_ingcor_se ## <chr> <dbl> <dbl> ## 1 10 50161. 942. ## 2 11 46142. 1252. ## 3 12 29334. 1067. ## 4 13 38783. 933. ## 5 14 60541. 1873. ## 6 15 48013. 1245. ## 7 16 42653. 1239. ## 8 17 42973. 1675. ## 9 18 48148. 1822. ## 10 19 68959. 3625. ## # ℹ 20 more rows Resumiendo: El bootstrap de Rao y Wu genera un estimador consistente y aproximadamente insesgado de la varianza de estadísticos no lineales y para la varianza de un cuantil. Este método supone que la seleccion de UPMs es con reemplazo; hay variaciones del estimador bootstrap de Rao y Wu que extienden el método que acabamos de estudiar; sin embargo, es común ignorar este aspecto, por ejemplo Mach et al estudian las propiedades del estimador de varianza bootstrap de Rao y Wu cuando la muestra se seleccionó sin reemplazo. Bootstrap en R Es común crear nuestras propias funciones cuando usamos bootstrap, sin embargo, en R también hay alternativas que pueden resultar convenientes, mencionamos 3: El paquete rsample (forma parte de la colección tidymodels) y tiene una función bootstraps() que regresa un arreglo cuadrangular (tibble, data.frame) que incluye una columna con las muestras bootstrap y un identificador del número y tipo de muestra. Veamos un ejemplo donde seleccionamos muestras del conjunto de datos muestra_computos que contiene 10,000 observaciones. library(rsample) load("data/election_2012.rda") muestra_computos <- slice_sample(election_2012, n = 10000) muestra_computos ## # A tibble: 10,000 × 23 ## state_code state_name state_abbr district_loc_17 district_fed_17 polling_id ## <chr> <chr> <chr> <int> <int> <int> ## 1 15 México MEX 45 40 77364 ## 2 28 Tamaulipas TAM 6 9 126685 ## 3 18 Nayarit NAY 7 2 86587 ## 4 09 Ciudad de M… CDMX 1 1 22214 ## 5 29 Tlaxcala TLAX 10 1 127221 ## 6 13 Hidalgo HGO 5 2 49224 ## 7 07 Chiapas CHPS 15 10 13835 ## 8 19 Nuevo León NL 3 6 90042 ## 9 25 Sinaloa SIN 6 3 113407 ## 10 15 México MEX 22 17 67597 ## # ℹ 9,990 more rows ## # ℹ 17 more variables: section <int>, region <chr>, polling_type <chr>, ## # section_type <chr>, pri_pvem <int>, pan <int>, panal <int>, ## # prd_pt_mc <int>, otros <int>, total <int>, nominal_list <int>, ## # pri_pvem_pct <dbl>, pan_pct <dbl>, panal_pct <dbl>, prd_pt_mc_pct <dbl>, ## # otros_pct <dbl>, winner <chr> Generamos 100 muestras bootstrap, y la función nos regresa un arreglo con 100 renglones, cada uno corresponde a una muestra bootstrap. set.seed(839287482) computos_boot <- bootstraps(muestra_computos, times = 100) computos_boot ## # Bootstrap sampling ## # A tibble: 100 × 2 ## splits id ## <list> <chr> ## 1 <split [10000/3647]> Bootstrap001 ## 2 <split [10000/3623]> Bootstrap002 ## 3 <split [10000/3724]> Bootstrap003 ## 4 <split [10000/3682]> Bootstrap004 ## 5 <split [10000/3696]> Bootstrap005 ## 6 <split [10000/3716]> Bootstrap006 ## 7 <split [10000/3679]> Bootstrap007 ## 8 <split [10000/3734]> Bootstrap008 ## 9 <split [10000/3632]> Bootstrap009 ## 10 <split [10000/3692]> Bootstrap010 ## # ℹ 90 more rows La columna splits tiene información de las muestras seleccionadas, para la primera vemos que de 10,000 observaciones en la muestra original la primera muestra bootstrap contiene 10000-3647=6353. first_computos_boot <- computos_boot$splits[[1]] first_computos_boot ## <Analysis/Assess/Total> ## <10000/3647/10000> Y podemos obtener los datos de la muestra bootstrap con la función as.data.frame() as.data.frame(first_computos_boot) ## # A tibble: 10,000 × 23 ## state_code state_name state_abbr district_loc_17 district_fed_17 polling_id ## <chr> <chr> <chr> <int> <int> <int> ## 1 14 Jalisco JAL 14 14 57620 ## 2 31 Yucatán YUC 7 3 139226 ## 3 14 Jalisco JAL 14 14 57998 ## 4 16 Michoacán MICH 13 3 79390 ## 5 21 Puebla PUE 13 7 101237 ## 6 14 Jalisco JAL 12 12 57145 ## 7 10 Durango DGO 4 1 34549 ## 8 11 Guanajuato GTO 20 10 42863 ## 9 03 Baja Califo… BCS 9 1 5541 ## 10 14 Jalisco JAL 16 16 58584 ## # ℹ 9,990 more rows ## # ℹ 17 more variables: section <int>, region <chr>, polling_type <chr>, ## # section_type <chr>, pri_pvem <int>, pan <int>, panal <int>, ## # prd_pt_mc <int>, otros <int>, total <int>, nominal_list <int>, ## # pri_pvem_pct <dbl>, pan_pct <dbl>, panal_pct <dbl>, prd_pt_mc_pct <dbl>, ## # otros_pct <dbl>, winner <chr> Una de las principales ventajas de usar este paquete es que es eficiente en el uso de memoria. library(pryr) object_size(muestra_computos) ## 1.41 MB object_size(computos_boot) ## 5.49 MB # tamaño por muestra object_size(computos_boot)/nrow(computos_boot) ## 54.92 kB # el incremento en tamaño es << 1000 as.numeric(object_size(computos_boot)/object_size(muestra_computos)) ## [1] 3.895024 Adicionalmente incluye funciones para el cálculo de intervalos bootstrap: intervalo_propinas_90 <- bootstraps(propinas, strata = momento, 1000) |> mutate(res_boot = map(splits, estimador)) |> int_pctl(res_boot, alpha = 0.10) El paquete boot está asociado al libro Bootstrap Methods and Their Applications (Davison and Hinkley (1997)) y tiene, entre otras, funciones para calcular replicaciones bootstrap y para construir intervalos de confianza usando bootstrap: calculo de replicaciones bootstrap con la función boot(), intervalos normales, de percentiles y \\(BC_a\\) con la función boot.ci(), intevalos ABC con la función `abc.ci(). El paquete bootstrap contiene datos usados en Efron and Tibshirani (1993), y la implementación de funciones para calcular replicaciones y construir intervalos de confianza: calculo de replicaciones bootstrap con la función bootstrap(), intervalos \\(BC_a\\) con la función bcanon(), intevalos ABC con la función abcnon(). Conclusiones y observaciones El principio fundamental del Bootstrap no paramétrico es que podemos estimar la distribución poblacional con la distribución empírica. Por tanto para hacer inferencia tomamos muestras con reemplazo de la muestra y analizamos la variación de la estadística de interés a lo largo de las remuestras. El bootstrap nos da la posibilidad de crear intervalos de confianza cuando no contamos con fórmulas para hacerlo de manera analítica y sin supuestos distribucionales de la población. Hay muchas opciones para construir intervalos bootstrap, los que tienen mejores propiedades son los intervalos \\(BC_a\\), sin embargo, los más comunes son los intervalos normales con error estándar bootstrap y los intervalos de percentiles de la distribución bootstrap. Antes de hacer intervalos normales vale la pena graficar la distribución bootstrap y evaluar si el supuesto de normalidad es razonable. En cuanto al número de muestras bootstrap se recomienda al menos \\(1,000\\) al hacer pruebas, y \\(10,000\\) o \\(15,000\\) para los resultados finales, sobre todo cuando se hacen intervalos de confianza de percentiles. La función de distribución empírica es una mala estimación en las colas de las distribuciones, por lo que es difícil construir intervalos de confianza (usando bootstrap no paramétrico) para estadísticas que dependen mucho de las colas. O en general para estadísticas que dependen de un número chico de observaciones de una muestra grande. Referencias "],["apéndice-principios-de-visualizacion.html", "Apéndice: Principios de visualizacion Introducción Visualización popular de datos Teoría de visualización de datos Ejemplo: gráfica de Minard", " Apéndice: Principios de visualizacion “The simple graph has brought more information to the data analyst’s mind than any other device.” — John Tukey El cuarteto de Anscombe En 1971 un estadístico llamado Frank Anscombe (fundador del departamento de Estadística de la Universidad de Yale) publicó cuatro conjuntos de dato. Cada uno consiste de 11 observaciones. La peculariedad de estos conjuntos es que tienen las mismas propiedades estadísticas. Sin embargo, cuando analizamos los datos de manera gráfica en un histograma encontramos rápidamente que los conjuntos de datos son muy distintos. Media de \\(x\\): 9 Varianza muestral de \\(x\\): 11 Media de \\(y\\): 7.50 Varianza muestral de \\(y\\): 4.12 Correlación entre \\(x\\) y \\(y\\): 0.816 Línea de regresión lineal: \\(y = 3.00 + 0.500x\\) En la gráfica del primer conjunto de datos, se ve clara una relación lineal simple con un modelo que cumple los supuestos de normalidad. La segunda gráfica (arriba a la derecha) muestra unos datos que tienen una asociación pero definitivamente no es lineal. En la tercera gráfica (abajo a la izquierda) están puntos alineados perfectamente en una línea recta, excepto por uno de ellos. En la última gráfica podemos ver un ejemplo en el cual basta tener una observación atípica para que se produzca un coeficiente de correlación alto aún cuando en realidad no existe una asociación lineal entre las dos variables. El cuarteto de Ascombe inspiró una técnica reciente para crear datos que comparten las mismas propiedades estadísticas al igual que en el cuarteto, pero que producen gráficas muy distintas (Matejka, Fitzmaurice). Introducción La visualización de datos no trata de hacer gráficas “bonitas” o “divertidas”, ni de simplificar lo complejo o ayudar a una persona “que no entiende mucho” a entender ideas complejas. Más bien, trata de aprovechar nuestra gran capacidad de procesamiento visual para exhibir de manera clara aspectos importantes de los datos. El siguiente ejemplo de (Tufte 2006), ilustra claramente la diferencia entre estos dos enfoques. A la izquierda están gráficas (más o menos típicas de Powerpoint) basadas en la filosofía de simplificar, de intentar no “ahogar” al lector con datos. El resultado es una colección incoherente, de bajo contenido, que no tiene mucho qué decir y que es, “indeferente al contenido y la evidencia”. A la derecha está una variación del rediseño de Tufte en forma de tabla, que en este caso particular es una manera eficiente de mostrar claramente los patrones que hay en este conjunto simple de datos. ¿Qué principios son los que soportan la efectividad de esta tabla sobre la gráfica de la derecha? Veremos que hay dos conjuntos de principios importantes: unos relacionados con el diseño y otros con la naturaleza del análisis de datos, independientemente del método de visualización. Visualización popular de datos Publicaciones populares (periódicos, revistas, sitios internet) muchas veces incluyen visualización de datos como parte de sus artículos o reportajes. En general siguen el mismo patrón que en la visión tradicionalista de la estadística: sirven más para divertir que para explicar, tienden a explicar ideas simples y conjuntos chicos de datos, y se consideran como una “ayuda” para los “lectores menos sofisticados”. Casi siempre se trata de gráficas triviales (muchas veces con errores graves) que no aportan mucho a artículos que tienen un nivel de complejidad mucho mayor (es la filosofía: lo escrito para el adulto, lo graficado para el niño). Teoría de visualización de datos Existe teoría fundamentada acerca de la visualización. Después del trabajo pionero de Tukey, los principios e indicadores de Tufte se basan en un estudio de la historia de la graficación y ejercicios de muestreo de la práctica gráfica a lo largo de varias disciplinas (¿cuáles son las mejores gráficas? ¿por qué?) El trabajo de Cleveland es orientado a la práctica del análisis de datos (¿cuáles gráficas nos han ayudado a mostrar claramente los resultados del análisis?), por una parte, y a algunos estudios de percepción visual. En resumen, hablaremos de las siguientes guías: Principios generales del diseño analítico Aplicables a una presentación o análisis completos, y como guía para construir nuevas visualizaciones (Tufte 2006). Principio 1. Muestra comparaciones, contrastes, diferencias. Principio 2. Muestra causalidad, mecanismo, explicación, estructura sistemática. Principio 3. Muestra datos multivariados, es decir, más de una o dos variables. Principio 4. Integra palabras, números, imágenes y diagramas. Principio 5. Describe la totalidad de la evidencia. Muestra fuentes usadas y problemas relevantes. Principio 6. Las presentaciones analíticas, a fin de cuentas, se sostienen o caen dependiendo de la calidad, relevancia e integridad de su contenido. Técnicas de visualización Esta categoría incluye técnicas específicas que dependen de la forma de nuestros datos y el tipo de pregunta que queremos investigar (Tukey (1977), William S. Cleveland (1993), W. S. Cleveland (1994), Tufte (2006)). Tipos de gráficas: cuantiles, histogramas, caja y brazos, gráficas de dispersión, puntos/barras/ líneas, series de tiempo. Técnicas para mejorar gráficas: Transformación de datos, transparencia, vibración, banking 45, suavizamiento y bandas de confianza. Pequeños múltiplos Indicadores de calidad gráfica Aplicables a cualquier gráfica en particular. Estas son guías concretas y relativamente objetivas para evaluar la calidad de una gráfica (Tufte 1986). Integridad Gráfica. El factor de engaño, es decir, la distorsión gráfica de las cantidades representadas, debe ser mínimo. Chartjunk. Minimizar el uso de decoración gráfica que interfiera con la interpretación de los datos: 3D, rejillas, rellenos con patrones. Tinta de datos. Maximizar la proporción de tinta de datos vs. tinta total de la gráfica. For non-data- ink, less is more. For data-ink, less is a bore. Densidad de datos. Las mejores gráficas tienen mayor densidad de datos, que es la razón entre el tamaño del conjunto de datos y el área de la gráfica. Las gráficas se pueden encoger mucho. Percepción visual. Algunas tareas son más fáciles para el ojo humano que otras (W. S. Cleveland 1994). Factor de engaño y Chartjunk El factor de engaño es el cociente entre el efecto mostrado en una gráfica y el efecto correspondiente en los datos. Idealmente, el factor de engaño debe ser 1 (ninguna distorsión). El chartjunk son aquellos elementos gráficos que no corresponden a variación de datos, o que entorpecen la interpretación de una gráfica. Estos son los indicadores de calidad más fáciles de entender y aplicar, y afortunadamente cada vez son menos comunes. Un diseño popular que califica como chartjunk y además introduce factores de engaño es el pie de 3D. En la gráfica de la derecha, podemos ver como la rebanada C se ve más grande que la rebanada A, aunque claramente ese no es el caso (factor de engaño). La razón es la variación en la perspectiva que no corresponde a variación en los datos (chartjunk). Crítica gráfica: Gráfica de pie Todavía elementos que pueden mejorar la comprensión de nuestra gráfica de pie: se trata de la decodificiación que hay que hacer categoría - color - cuantificación. Podemos agregar las etiquetas como se muestra en la serie de la derecha, pero entonces: ¿por qué no mostrar simplemente la tabla de datos? ¿qué agrega el pie a la interpretación? La deficiencias en el pie se pueden ver claramente al intentar graficar más categorías (13) . En el primer pie no podemos distinguir realmente cuáles son las categorías grandes y cuáles las chicas, y es muy difícil tener una imagen mental clara de estos datos. Agregar los porcentajes ayuda, pero entonces, otra vez, preguntamos cuál es el propósito del pie. La tabla de la izquierda hace todo el trabajo (una vez que ordenamos las categrías de la más grande a la más chica). Es posible hacer una gráfica de barras como la de abajo a la izquierda. Hay otros tipos de chartjunk comunes: uno es la textura de barras, por ejemplo. El efecto es la producción de un efecto moiré que es desagradable y quita la atención de los datos, como en la gráfica de barras de abajo. Otro común son las rejillas, como mostramos en las gráficas de la izquierda. Nótese como en estos casos hay efectos ópticos no planeados que degradan la percepción de los patrones en los datos. Pequeños múltiplos y densidad gráfica La densidad de una gráfica es el tamaño del conjunto de datos que se grafica comparado con el área total de la gráfica. En el siguiente ejemplo, graficamos en logaritmo-10 de millones de cabezas de ganado en Francia (cerdos, res, ovejas y caballos). La gráfica de la izquierda es pobre en densidad pues sólo representa 4 datos. La manera más fácil de mejorar la densidad es hacer más chica la gráfica: La razón de este encogimiento es una que tiene qué ver con las oportunidades perdidas de una gráfica grande. Si repetimos este mismo patrón (misma escala, mismos tipos de ganado) para distintos países obtenemos la siguiente gráfica: Esta es una gráfica de puntos. Es útil como sustituto de una gráfica de barras, y es superior en el sentido de que una mayor proporción de la tinta que se usa es tinta de datos. Otra vez, mayor proporción de tinta de datos representa más oportunidades que se pueden capitalizar. Más pequeños múltiplos Los pequeños múltiplos presentan oportunidades para mostrar más acerca de nuestro problema de interés. Consideramos por ejemplo la relación de radiación solar y niveles de ozono: En el ejemplo anterior incluyendo una variable adicional (velocidad del viento) podemos entender más acerca de la relación de radiación solar y niveles de ozono: Tinta de datos Maximizar la proporción de tinta de datos en nuestras gráficas tiene beneficios inmediatos. La regla es: si hay tinta que no representa variación en los datos, o la eliminación de esa tinta no representa pérdidas de significado, esa tinta debe ser eliminada. El ejemplo más claro es el de las rejillas en gráficas y tablas: ¿Por qué usar grises en lugar de negros? La respuesta tiene qué ver con el principio de tinta de datos: si marcamos las diferencias sutil pero claramente, tenemos más oportunidades abiertas para hacer énfasis en lo que nos interesa: a una gráfica o tabla saturada no se le puede hacer más - es difícil agregar elementos adicionales que ayuden a la comprensión. Si comenzamos marcando con sutileza, entonces se puede hacer más. Los mapas geográficos son un buen ejemplo de este principio. El espacio en blanco es suficientemente bueno para indicar las fronteras en una tabla, y facilita la lectura: Para un ejemplo del proceso de rediseño de una tabla, ver aquí. Finalmente, podemos ver un ejemplo que intenta incorporar los elementos del diseño analítico, incluyendo pequeños múltiplos: Decoración Percepción de escala Entre la percepción visual y la interpretación de una gráfica están implícitas tareas visuales específicas que las personas debemos realizar para ver correctamente la gráfica. En la década de los ochenta, William S. Cleveland y Robert McGill realizaron algunos experimentos identificando y clasificando estas tareas para diferentes tipos de gráficos (William S. Cleveland and McGill 1984). En estos, se le pregunta a la persona que compare dos valores dentro de una gráfica, por ejemplo, en dos barras en una gráfica de barras, o dos rebanadas de una gráfica de pie. Los resultados de Cleveland y McGill fueron replicados por Heer y Bostock en 2010 y los resultados se muestran en las gráficas de la derecha: Ejemplo: gráfica de Minard Concluimos esta sección con una gráfica que, aunque poco común, ejemplifica los principios de una buena gráfica, y es reconocida como una de las mejores visualizaciones de la historia. Una gráfica excelente, presenta datos interesantes de forma bien diseñada: es una cuestión de fondo, de diseño, y estadística… [Se] compone de ideas complejas comunicadas con claridad, precisión y eficiencia. … [Es] lo que da al espectador la mayor cantidad de ideas, en el menor tiempo, con la menor cantidad de tinta, y en el espacio más pequeño. … Es casi siempre multivariado. … Una excelente gráfica debe decir la verdad acerca de los datos. (Tufte, 1983) La famosa visualización de Charles Joseph Minard de la marcha de Napoleón sobre Moscú, ilustra los principios de una buena gráfica. Tufte señala que esta imagen “bien podría ser el mejor gráfico estadístico jamás dibujado”, y sostiene que “cuenta una historia rica y coherente con sus datos multivariados, mucho más esclarecedora que un solo número que rebota en el tiempo”. Se representan seis variables: el tamaño del ejército, su ubicación en una superficie bidimensional, la dirección del movimiento del ejército y la temperatura en varias fechas durante la retirada de Moscú”. Hoy en día Minard es reconocido como uno de los principales contribuyentes a la teoría de análisis de datos y creación de infografías con un fundamento estadístico. Se grafican 6 variables: el número de tropas de Napoleón, la distancia, la temperatura, la latitud y la longitud, la dirección en que viajaban las tropas y la localización relativa a fechas específicas. La gráfica de Minard, como la describe E.J. Marey, parece “desafiar la pluma del historiador con su brutal elocuencia”, la combinación de datos del mapa, y la serie de tiempo, dibujados en 1869, “retratan una secuencia de pérdidas devastadoras que sufrieron las tropas de Napoleón en 1812”. Comienza en la izquierda, en la frontera de Polonia y Rusia, cerca del río Niemen. La línea gruesa dorada muestra el tamaño de la Gran Armada (422,000) en el momento en que invadía Rusia en junio de 1812. El ancho de esta banda indica el tamaño de la armada en cada punto del mapa. En septiembre, la armada llegó a Moscú, que ya había sido saqueada y dejada desértica, con sólo 100,000 hombres. El camino del retiro de Napoleón desde Moscú está representado por la línea oscura (gris) que está en la parte inferior, que está relacionada a su vez con la temperatura y las fechas en el diagrama de abajo. Fue un invierno muy frío, y muchos se congelaron en su salida de Rusia. Como se muestra en el mapa, cruzar el río Berezina fue un desastre, y el ejército de Napoleón logró regresar a Polonia con tan sólo 10,000 hombres. También se muestran los movimientos de las tropas auxiliaries, que buscaban proteger por atrás y por la delantera mientras la armada avanzaba hacia Moscú. La gráfica de Minard cuenta una historia rica y cohesiva, coherente con datos multivariados y con los hechos históricos, y que puede ser más ilustrativa que tan sólo representar un número rebotando a lo largo del tiempo. Referencias "],["apéndice-transformaciones.html", "Apéndice: Transformaciones", " Apéndice: Transformaciones En ocasiones es conveniente transformar los datos para el análisis, el objetivo de los ajustes es simplificar la interpretación y el análisis al eliminar fuentes de variación conocidas, también es común realizan transformaciones para simplificar los patrones. Algunos ejemplos donde eliminamos efectos conocidos: Cuando analizamos el precio de venta de las casas podemos eliminar la variación debida al tamaño de las casas al pasar de precio de venta a precio de venta por metro cuadrado. De manera similar al analizar las propinas puede convenir considerar la propina como porcentaje de la cuenta. En series de tiempo cuando los datos están relacionados con el tamaño de la población podemos ajustar a mediciones per capita (en series de tiempo PIB). También es común ajustar por inflación, o poner cantidades monetarias en valor presente. mex_dat <- global_economy |> filter(Code == "MEX") pib <- ggplot(mex_dat, aes(x = Year, y = GDP / 1e6)) + geom_line() pib_pc <- ggplot(mex_dat, aes(x = Year, y = GDP / Population)) + geom_line() pib / pib_pc Adicionalmente podemos recurrir a otras transformaciones matemáticas (e.g. logaritmo, raíz cuadrada) que simplifiquen el patrón en los datos y la interpretación. Veamos un ejemplo donde es apropiado la transformación logaritmo. Usamos los datos Animals con información de peso corporal promedio y peso cerebral promedio para 28 especies. Buscamos entender la relación entre estas dos variables, e inspeccionar que especies se desvían (residuales) del esperado. Comenzamos con un diagrama de dispersión usando las unidades originales animals_tbl <- as_tibble(Animals, rownames = "animal") p1 <- ggplot(animals_tbl, aes(x = body, y = brain, label = animal)) + geom_point() + labs(subtitle = "Unidades originales") p2 <- ggplot(animals_tbl, aes(x = body, y = brain, label = animal)) + geom_point() + xlim(0, 500) + ylim(0, 1500) + geom_text_repel() + labs(subtitle = "Unidades originales, eliminando 'grandes'") (p1 + p2) Incluso cuando nos limitamos a especies de menos de 500 kg de masa corporal, la relación no es fácil de descrubir.En la suguiente gráfica hacemos la transformación logaritmo y obtenemos una gráfica más fácil de leer, además los datos se modelarán con más facilidad. p3 <- ggplot(animals_tbl, aes(x = log(body), y = log(brain), label = animal)) + geom_smooth(method = "lm", se = FALSE, color = "red") + geom_point() + geom_text_repel() + stat_poly_eq(use_label(c("eq"))) p3 ## `geom_smooth()` using formula = 'y ~ x' La transformación logaritmo tiene también ventajas en interpretación, para diferencias chicas en escala log, las diferencias corresponden a diferencias porcentuales en la escala original, por ejempo consideremos la diferencia entre el peso en escala log de humano y borrego: 4.13 - 4.02 = 0.11. Confirmamos que el humano es aproximadamente 11% más pesado que el borrego en la escala original: 62/55.5 - 1 = 0.12 animals_tbl <- animals_tbl |> mutate(log_body = log(body), log_brain = log(brain)) animals_tbl |> filter(animal == "Human" | animal == "Sheep") |> arrange(body) |> gt::gt() |> gt::fmt_number() #jqppzkbkar table { font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #jqppzkbkar thead, #jqppzkbkar tbody, #jqppzkbkar tfoot, #jqppzkbkar tr, #jqppzkbkar td, #jqppzkbkar th { border-style: none; } #jqppzkbkar p { margin: 0; padding: 0; } #jqppzkbkar .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; } #jqppzkbkar .gt_caption { padding-top: 4px; padding-bottom: 4px; } #jqppzkbkar .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; } #jqppzkbkar .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; } #jqppzkbkar .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #jqppzkbkar .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #jqppzkbkar .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #jqppzkbkar .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; } #jqppzkbkar .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; } #jqppzkbkar .gt_column_spanner_outer:first-child { padding-left: 0; } #jqppzkbkar .gt_column_spanner_outer:last-child { padding-right: 0; } #jqppzkbkar .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; } #jqppzkbkar .gt_spanner_row { border-bottom-style: hidden; } #jqppzkbkar .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; } #jqppzkbkar .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; } #jqppzkbkar .gt_from_md > :first-child { margin-top: 0; } #jqppzkbkar .gt_from_md > :last-child { margin-bottom: 0; } #jqppzkbkar .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; } #jqppzkbkar .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; } #jqppzkbkar .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; } #jqppzkbkar .gt_row_group_first td { border-top-width: 2px; } #jqppzkbkar .gt_row_group_first th { border-top-width: 2px; } #jqppzkbkar .gt_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #jqppzkbkar .gt_first_summary_row { border-top-style: solid; border-top-color: #D3D3D3; } #jqppzkbkar .gt_first_summary_row.thick { border-top-width: 2px; } #jqppzkbkar .gt_last_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #jqppzkbkar .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #jqppzkbkar .gt_first_grand_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; } #jqppzkbkar .gt_last_grand_summary_row_top { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; } #jqppzkbkar .gt_striped { background-color: rgba(128, 128, 128, 0.05); } #jqppzkbkar .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #jqppzkbkar .gt_footnotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #jqppzkbkar .gt_footnote { margin: 0px; font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #jqppzkbkar .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #jqppzkbkar .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #jqppzkbkar .gt_left { text-align: left; } #jqppzkbkar .gt_center { text-align: center; } #jqppzkbkar .gt_right { text-align: right; font-variant-numeric: tabular-nums; } #jqppzkbkar .gt_font_normal { font-weight: normal; } #jqppzkbkar .gt_font_bold { font-weight: bold; } #jqppzkbkar .gt_font_italic { font-style: italic; } #jqppzkbkar .gt_super { font-size: 65%; } #jqppzkbkar .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; } #jqppzkbkar .gt_asterisk { font-size: 100%; vertical-align: 0; } #jqppzkbkar .gt_indent_1 { text-indent: 5px; } #jqppzkbkar .gt_indent_2 { text-indent: 10px; } #jqppzkbkar .gt_indent_3 { text-indent: 15px; } #jqppzkbkar .gt_indent_4 { text-indent: 20px; } #jqppzkbkar .gt_indent_5 { text-indent: 25px; } #jqppzkbkar .katex-display { display: inline-flex !important; margin-bottom: 0.75em !important; } #jqppzkbkar div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { height: 0px !important; } animal body brain log_body log_brain Sheep 55.50 175.00 4.02 5.16 Human 62.00 1,320.00 4.13 7.19 Y podemos usarlo también para interpretar la recta de referencia \\(y = 2.55 + 0.5 x\\) , para cambios chicos: Un incremento de 10% en masa total corresponde en un incremento de 5% en masa cerebral. El coeficiente de la regresión log-log, en nuestro ejemplo 0.5, es la elasticidad y es un concepto común en economía. Justificación Para entender la interpretación como cambio porcentual recordemos primero que la representación con series de Taylor de la función exponencial es: \\[e^x = \\sum_{n=0}^\\infty \\frac{x^n}{n!}\\] Más aún podemos tener una aproximación usando polinomios de Taylor, en el caso de la exponencial el \\(k\\)-ésimo polinomio de Taylor está dado por: \\[e^\\delta \\approx 1 + \\delta + \\frac{1}{2!}\\delta^2 + \\dots + \\frac{1}{k!}\\delta^k\\] y si \\(\\delta\\) es chica (digamos menor a 0.15), entonces la aproximación de primer grado es razonable y tenemos: \\[Ae^{\\delta} \\approx A(1+\\delta)\\] dat <- tibble(delta = seq(0, 1, 0.01), exp_delta = exp(delta), uno_mas_delta = 1 + delta) ggplot(dat, aes(x = uno_mas_delta, y = exp_delta)) + geom_line() + geom_abline(color = "red") + annotate("text", x = 1.20, y = 1.18, label = "y = x", color = "red", size = 6) "],["referencias.html", "Referencias", " Referencias "],["404.html", "Page not found", " Page not found The page you requested cannot be found (perhaps it was moved or renamed). You may want to try searching to find the page's new location, or use the table of contents to find the page you are looking for. "]]