library(dplyr)
library(ggplot2)
library(readr)
library(rstan)
<- "#e76f51"
c_orange_dark <- "#f4a261"
c_orange_light <- "#e9c46a"
c_yellow <-"#2a9d8f"
c_green_blue <- "#264653"
c_blue_dark <- "#219ebc" c_lightblue
18 - Regresión lineal con {RStan}
(Clima en Australia)
En este artículo se muestra como implementar en R
y {RStan}
algunos de los modelos necesarios en el ejercicio Clima en Australia de la Práctica 4.
Comenzamos cargando las liberías que vamos a usar.
<- read_csv(
df "https://raw.githubusercontent.com/ee-unr/estadistica-bayesiana/main/datos/weather_WU.csv"
)
# Explorar los datos
ggplot(df) +
geom_point(aes(x = temp9am, y = temp3pm, color = location), alpha = 0.7, size = 2) +
scale_color_manual(values = c(c_orange_dark, c_green_blue)) +
labs(
x = "Temperatura a las 9 a.m. (C°)",
y = "Temperatura a las 3 p.m. (C°)",
color = "Ciudad"
+
) theme_bw()
Modelos
A continuación se presentan los modelos que se consideran en el ejercicio. En todos los casos se tiene:
- \(Y_i\): temperatura a las 3 p.m. de la observación i-ésima.
- \(X_i\): temperatura a las 9 a.m. de la observacion i-ésima.
Modelo 1
\[ \begin{aligned} Y_i \mid \mu_i, \sigma &\underset{iid}{\sim} \text{Normal}(\mu_i, \sigma) \\ \mu_i &= \beta_0 + \beta_1 x_i \\ \beta_0 &\sim \text{Normal} \\ \beta_1 &\sim \text{Normal} \\ \sigma &\sim \text{Normal}^+ \\ \end{aligned} \]
Modelo 2
\[ \begin{aligned} Y_i \mid \mu_i, \sigma &\underset{iid}{\sim}\text{Normal}(\mu_i, \sigma) \\ \mu_i &= \beta_{0, j[i]} \\ \beta_{0, j} &\sim \text{Normal} \quad \forall j = 1, 2 \\ \sigma &\sim \text{Normal}^+ \\ \end{aligned} \]
donde \(j\) indexa a las diferentes ciudades.
Para pensar: ¿qué indican \(\beta_{0, 1}\) y \(\beta_{0, 2}\)?
Modelo 3
\[ \begin{aligned} Y_i \mid \mu_i, \sigma &\underset{iid}{\sim} \text{Normal}(\mu_i, \sigma) \\ \mu_i &= \beta_{0, j[i]} + \beta_1 x_i \\ \beta_{0, j} &\sim \text{Normal} \quad \forall j = 1, 2 \\ \beta_1 &\sim \text{Normal} \\ \sigma &\sim \text{Normal}^+ \\ \end{aligned} \]
Para pensar: ¿en qué difieren los \(\beta_{0, 1}\) y \(\beta_{0, 2}\) de este modelo con los del modelo 2? ¿Y en qué difiere \(\beta_1\) de este modelo con el del modelo 1?
Modelo 4
\[ \begin{aligned} Y_i \mid \mu_i, \sigma &\underset{iid}{\sim} \text{Normal}(\mu_i, \sigma) \\ \mu_i &= \beta_{0, j[i]} + \beta_{1, j[i]} x_i \\ \beta_{0, j} &\sim \text{Normal} \quad \forall j = 1, 2 \\ \beta_{1, j} &\sim \text{Normal} \quad \forall j = 1, 2 \\ \sigma &\sim \text{Normal}^+ \\ \end{aligned} \]
Implementación
Modelo 1
stan/australia/modelo_1.stan
data {
int<lower=0> N; // Cantidad de observaciones
vector[N] temp9am; // Temperatura a las 9 am (predictor)
vector[N] temp3pm; // Temperatura a las 3 pm (respuesta)
}parameters {
real beta0; // Intercepto
real beta1; // Pendiente
real<lower=0> sigma; // Desvío estándar del error
}transformed parameters {
vector[N] mu; // Predictor lineal
mu = beta0 + beta1 * temp9am;
}model {
17.5, 6.25);
beta0 ~ normal(0, 10);
beta1 ~ normal(0, 15);
sigma ~ normal(
temp3pm ~ normal(mu, sigma);
}generated quantities {
vector[N] y_rep;
vector[N] log_likelihood;
for (i in 1:N) {
// Obtención de muestras de la distribución predictiva a posteriori
y_rep[i] = normal_rng(mu[i], sigma);
// Cálculo de la log-verosimilitud
log_likelihood[i] = normal_lpdf(temp3pm[i] | mu[i], sigma);
} }
# Datos que pasamos a Stan
<- list(
stan_data_1 N = nrow(df),
temp9am = df$temp9am,
temp3pm = df$temp3pm
)
<- here::here(
ruta_modelo_1 "recursos", "codigo", "stan", "australia", "modelo_1.stan"
)
<- stan_model(file = ruta_modelo_1)
modelo_1 <- sampling(
modelo_1_fit # El modelo
modelo_1, data = stan_data_1, # Datos
chains = 4, # Cantidad de cadenas
seed = 1211, # Para que el resultado sea reproducible
refresh = 0, # Mostrar mensajes del sampler (0: no)
)
# Convertimos las muestras a data.frame, conservando solo los parámetros de interés
<- as.data.frame(extract(modelo_1_fit, c("beta0", "beta1", "sigma"))) df_draws_1
Graficamos la distribución a posteriori marginal de los parámetros.
<- df_draws_1 |>
df_draws_long_1 ::pivot_longer(c("beta0", "beta1", "sigma"), names_to = "parametro")
tidyr
ggplot(df_draws_long_1) +
geom_histogram(
aes(x = value, y = after_stat(density)),
bins = 30,
fill = c_orange_light,
color = c_orange_light
+
) facet_wrap(~ parametro, scales = "free") +
labs(x = "Valor", y = "Densidad") +
theme_bw()
La recta de regresión está dada por un intercepto y una pendiente. Al contar con múltiples valores de interceptos y pendientes, es posible pensar que se tienen múltiples muestras de rectas de regresión. Para graficar algunas de estas rectas simplemente seleccionamos algunos de los pares de (beta0, beta1)
que se obtuvieron del posterior.
ggplot(df) +
geom_point(aes(x = temp9am, y = temp3pm, color = location), alpha = 0.7, size = 2) +
geom_abline(
aes(intercept = beta0, slope = beta1),
alpha = 0.3,
color = "gray30",
data = df_draws_1[sample(4000, 40), ]
+
) scale_color_manual(values = c(c_orange_dark, c_green_blue)) +
labs(
x = "Temperatura a las 9 a.m. (C°)",
y = "Temperatura a las 3 p.m. (C°)",
color = "Ciudad"
+
) theme_bw()
También es posible obtener muestras de la distribución predictiva a posteriori para una grilla de valores de temp9am
, resumirla mediante un intervalo de credibilidad del 95%, y superponerla en el gráfico anterior.
<- 4000
n_muestras <- 200
n_grilla
<- seq(min(df$temp9am), max(df$temp9am), length.out = n_grilla)
temp9am_seq <- matrix(nrow = n_muestras, ncol = n_grilla)
y_pp_matriz
for (j in seq_len(n_grilla)) {
<- df_draws_1$beta0 + df_draws_1$beta1 * temp9am_seq[j] # vector[4000]
mu <- df_draws_1$sigma # vector[4000]
sigma <- rnorm(n_muestras, mu, sigma) # vector[4000]
y_pp_matriz[, j]
}
<- apply(y_pp_matriz, 2, quantile, probs = c(0.025, 0.975)) |>
df_pp_95_ci t()|>
as.data.frame() |>
mutate(temp9am = temp9am_seq) |>
rename(li = `2.5%` , ls = `97.5%`)
ggplot(df) +
geom_ribbon(
aes(x = temp9am, ymin = li, ymax = ls),
alpha = 0.3,
data = df_pp_95_ci
+
) geom_abline(
aes(intercept = beta0, slope = beta1),
alpha = 0.3,
color = "gray30",
data = df_draws_1[sample(4000, 40), ]
+
) geom_point(aes(x = temp9am, y = temp3pm, color = location), alpha = 0.7, size = 2) +
scale_color_manual(values = c(c_orange_dark, c_green_blue)) +
labs(
x = "Temperatura a las 9 a.m. (C°)",
y = "Temperatura a las 3 p.m. (C°)",
color = "Ciudad"
+
) theme_bw()
Modelo 2
stan/australia/modelo_2.stan
data {
int<lower=0> N; // Cantidad de observaciones
array[N] int location_idx; // Índice de la ciudad
vector[N] temp3pm; // Temperatura a las 3 pm (respuesta)
}parameters {
vector[2] beta0; // Interceptos
real<lower=0> sigma; // Desvío estándar del error
}transformed parameters {
vector[N] mu; // Predictor lineal
mu = beta0[location_idx];
}model {
17.5, 6.25);
beta0 ~ normal(0, 15);
sigma ~ normal(
temp3pm ~ normal(mu, sigma);
}generated quantities {
vector[N] y_rep;
vector[N] log_likelihood;
for (i in 1:N) {
// Obtención de muestras de la distribución predictiva a posteriori
y_rep[i] = normal_rng(mu[i], sigma);
// Cálculo de la log-verosimilitud
log_likelihood[i] = normal_lpdf(temp3pm[i] | mu[i], sigma);
} }
Este modelo incorpora la ubicación. Una forma de implementarlo es utilizando índices que indiquen a que ubicación pertence cada observación.
as.factor(df$location)
[1] Uluru Uluru Uluru Uluru Uluru Uluru
[7] Uluru Uluru Uluru Uluru Uluru Uluru
[13] Uluru Uluru Uluru Uluru Uluru Uluru
[19] Uluru Uluru Uluru Uluru Uluru Uluru
[25] Uluru Uluru Uluru Uluru Uluru Uluru
[31] Uluru Uluru Uluru Uluru Uluru Uluru
[37] Uluru Uluru Uluru Uluru Uluru Uluru
[43] Uluru Uluru Uluru Uluru Uluru Uluru
[49] Uluru Uluru Uluru Uluru Uluru Uluru
[55] Uluru Uluru Uluru Uluru Uluru Uluru
[61] Uluru Uluru Uluru Uluru Uluru Uluru
[67] Uluru Uluru Uluru Uluru Uluru Uluru
[73] Uluru Uluru Uluru Uluru Uluru Uluru
[79] Uluru Uluru Uluru Uluru Uluru Uluru
[85] Uluru Uluru Uluru Uluru Uluru Uluru
[91] Uluru Uluru Uluru Uluru Uluru Uluru
[97] Uluru Uluru Uluru Uluru Wollongong Wollongong
[103] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[109] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[115] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[121] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[127] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[133] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[139] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[145] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[151] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[157] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[163] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[169] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[175] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[181] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[187] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[193] Wollongong Wollongong Wollongong Wollongong Wollongong Wollongong
[199] Wollongong Wollongong
Levels: Uluru Wollongong
as.integer(as.factor(df$location))
[1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[38] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[75] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
[112] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[149] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[186] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
<- list(
stan_data_2 N = nrow(df),
location_idx = as.integer(as.factor(df$location)),
temp3pm = df$temp3pm
)
<- here::here(
ruta_modelo_2 "recursos", "codigo", "stan", "australia", "modelo_2.stan"
)
<- stan_model(file = ruta_modelo_2)
modelo_2 <- sampling(
modelo_2_fit
modelo_2,data = stan_data_2,
chains = 4,
seed = 1211,
refresh = 0,
)
# Extraemos las muestras del posterior y renombramos la columnas de beta0
<- as.data.frame(extract(modelo_2_fit, c("beta0", "sigma")))
df_draws_2 colnames(df_draws_2) <- c("beta0[Uluru]", "beta0[Wollongong]", "sigma")
El código para visualizar los posteriors marginales es análogo.
<- df_draws_2 |>
df_draws_long_2 ::pivot_longer(
tidyrc("beta0[Uluru]", "beta0[Wollongong]", "sigma"),
names_to = "parametro"
)
ggplot(df_draws_long_2) +
geom_histogram(
aes(x = value, y = after_stat(density)),
bins = 30,
fill = c_orange_light,
color = c_orange_light
+
) facet_wrap(~ parametro, scales = "free") +
labs(x = "Valor", y = "Densidad") +
theme_bw()
En este segundo modelo se tienen dos interceptos, uno para cada ciudad, y una pendiente nula.
<- data.frame(
data_lines beta0 = c(df_draws_2[["beta0[Uluru]"]], df_draws_2[["beta0[Wollongong]"]]),
location = rep(c("Uluru", "Wollongong"), each = nrow(df_draws_2))
)
ggplot(df) +
geom_point(aes(x = temp9am, y = temp3pm, color = location), alpha = 0.7, size = 2) +
geom_abline(
aes(intercept = beta0, slope = 0, color = location),
alpha = 0.3,
data = data_lines[sample(nrow(data_lines), 80), ]
+
) scale_color_manual(values = c(c_orange_dark, c_green_blue)) +
labs(
x = "Temperatura a las 9 a.m. (C°)",
y = "Temperatura a las 3 p.m. (C°)",
color = "Ciudad"
+
) theme_bw()
El modelo predice una temperatura única para cada ciudad a las 3 p.m., independiente de la temperatura a las 9 a.m.
La banda de credibilidad para las predicciones se obtiene y visualiza debajo.
<- 4000
n_muestras <- 200
n_grilla
<- seq(min(df$temp9am), max(df$temp9am), length.out = n_grilla)
temp9am_seq <- matrix(nrow = n_muestras, ncol = n_grilla)
y_pp_matriz_u <- matrix(nrow = n_muestras, ncol = n_grilla)
y_pp_matriz_w
for (j in seq_len(n_grilla)) {
<- df_draws_2[["beta0[Uluru]"]] # vector[4000]
mu_u <- df_draws_2[["beta0[Wollongong]"]] # vector[4000]
mu_w <- df_draws_2$sigma # vector[4000]
sigma <- rnorm(n_muestras, mu_u, sigma) # vector[4000]
y_pp_matriz_u[, j] <- rnorm(n_muestras, mu_w, sigma) # vector[4000]
y_pp_matriz_w[, j]
}
<- apply(y_pp_matriz_u, 2, quantile, probs = c(0.025, 0.975)) |>
df_pp_95_ci_u t()|>
as.data.frame() |>
mutate(temp9am = temp9am_seq) |>
rename(li = `2.5%` , ls = `97.5%`)
<- apply(y_pp_matriz_w, 2, quantile, probs = c(0.025, 0.975)) |>
df_pp_95_ci_w t()|>
as.data.frame() |>
mutate(temp9am = temp9am_seq) |>
rename(li = `2.5%` , ls = `97.5%`)
ggplot(df) +
geom_ribbon(
aes(x = temp9am, ymin = li, ymax = ls),
alpha = 0.3,
data = df_pp_95_ci_u,
fill = c_orange_dark
+
) geom_ribbon(
aes(x = temp9am, ymin = li, ymax = ls),
alpha = 0.3,
data = df_pp_95_ci_w,
fill = c_green_blue
+
) geom_abline(
aes(intercept = beta0, slope = 0, color = location),
alpha = 0.3,
data = data_lines[sample(nrow(data_lines), 80), ]
+
) geom_point(aes(x = temp9am, y = temp3pm, color = location), alpha = 0.7, size = 2) +
scale_color_manual(values = c(c_orange_dark, c_green_blue)) +
labs(
x = "Temperatura a las 9 a.m. (C°)",
y = "Temperatura a las 3 p.m. (C°)",
color = "Ciudad"
+
) theme_bw()
Modelo 3
stan/australia/modelo_3.stan
data {
int<lower=0> N; // Cantidad de observaciones
array[N] int location_idx; // Índice de la ciudad
vector[N] temp9am; // Temperatura a las 9 am (predictor)
vector[N] temp3pm; // Temperatura a las 3 pm (respuesta)
}parameters {
vector[2] beta0; // Interceptos
real beta1; // Pendiente
real<lower=0> sigma; // Desvío estándar del error
}transformed parameters {
vector[N] mu; // Predictor lineal
mu = beta0[location_idx] + beta1 * temp9am;
}model {
17.5, 6.25);
beta0 ~ normal(0, 10);
beta1 ~ normal(0, 15);
sigma ~ normal(
temp3pm ~ normal(mu, sigma);
}generated quantities {
vector[N] y_rep;
vector[N] log_likelihood;
for (i in 1:N) {
// Obtención de muestras de la distribución predictiva a posteriori
y_rep[i] = normal_rng(mu[i], sigma);
// Cálculo de la log-verosimilitud
log_likelihood[i] = normal_lpdf(temp3pm[i] | mu[i], sigma);
} }
Este último modelo presenta un intercepto distinto para cada ciudad y una pendiente común a ambas. Por lo tanto, es necesario pasarle a Stan el vector de índices para las ciudades y la temperatura a las 9 a.m.
<- list(
stan_data_3 N = nrow(df),
location_idx = as.integer(as.factor(df$location)),
temp9am = df$temp9am,
temp3pm = df$temp3pm
)
<- here::here(
ruta_modelo_3 "recursos", "codigo", "stan", "australia", "modelo_3.stan"
)
<- stan_model(file = ruta_modelo_3)
modelo_3 <- sampling(
modelo_3_fit
modelo_3,data = stan_data_3,
chains = 4,
refresh = 0,
seed = 1211
)
<- as.data.frame(extract(modelo_3_fit, c("beta0", "beta1", "sigma")))
df_draws_3 colnames(df_draws_3) <- c("beta0[Uluru]", "beta0[Wollongong]", "beta1", "sigma")
<- df_draws_3 |>
df_draws_long_3 ::pivot_longer(
tidyrc("beta0[Uluru]", "beta0[Wollongong]", "beta1", "sigma"),
names_to = "parametro"
)
ggplot(df_draws_long_3) +
geom_histogram(
aes(x = value, y = after_stat(density)),
bins = 30,
fill = c_orange_light,
color = c_orange_light
+
) facet_wrap(~ parametro, scales = "free") +
labs(x = "Valor", y = "Densidad") +
theme_bw()
<- data.frame(
data_lines beta0 = c(df_draws_3[["beta0[Uluru]"]], df_draws_3[["beta0[Wollongong]"]]),
beta1 = rep(df_draws_3[["beta1"]], 2),
location = rep(c("Uluru", "Wollongong"), each = nrow(df_draws_3))
)
ggplot(df) +
geom_abline(
aes(intercept = beta0, slope = beta1, color = location),
alpha = 0.3,
data = data_lines[sample(nrow(data_lines), 80), ]
+
) geom_point(aes(x = temp9am, y = temp3pm, color = location), alpha = 0.7, size = 2) +
scale_color_manual(values = c(c_orange_dark, c_green_blue)) +
labs(
x = "Temperatura a las 9 a.m. (C°)",
y = "Temperatura a las 3 p.m. (C°)",
color = "Ciudad"
+
) theme_bw()
De manera cualitativa, es posible ver que este modelo ajusta a la nube de puntos mejor que los anteriores.
Código
<- 4000
n_muestras <- 200
n_grilla
<- seq(min(df$temp9am), max(df$temp9am), length.out = n_grilla)
temp9am_seq <- matrix(nrow = n_muestras, ncol = n_grilla)
y_pp_matriz_u <- matrix(nrow = n_muestras, ncol = n_grilla)
y_pp_matriz_w
for (j in seq_len(n_grilla)) {
<- df_draws_3[["beta0[Uluru]"]] + temp9am_seq[j] * df_draws_3[["beta1"]] # vector[4000]
mu_u <- df_draws_3[["beta0[Wollongong]"]] + temp9am_seq[j] * df_draws_3[["beta1"]] # vector[4000]
mu_w <- df_draws_3$sigma # vector[4000]
sigma <- rnorm(n_muestras, mu_u, sigma) # vector[4000]
y_pp_matriz_u[, j] <- rnorm(n_muestras, mu_w, sigma) # vector[4000]
y_pp_matriz_w[, j]
}
<- apply(y_pp_matriz_u, 2, quantile, probs = c(0.025, 0.975)) |>
df_pp_95_ci_u t()|>
as.data.frame() |>
mutate(temp9am = temp9am_seq) |>
rename(li = `2.5%` , ls = `97.5%`)
<- apply(y_pp_matriz_w, 2, quantile, probs = c(0.025, 0.975)) |>
df_pp_95_ci_w t()|>
as.data.frame() |>
mutate(temp9am = temp9am_seq) |>
rename(li = `2.5%` , ls = `97.5%`)
Código
ggplot(df) +
geom_ribbon(
aes(x = temp9am, ymin = li, ymax = ls),
alpha = 0.3,
data = df_pp_95_ci_u,
fill = c_orange_dark
+
) geom_ribbon(
aes(x = temp9am, ymin = li, ymax = ls),
alpha = 0.3,
data = df_pp_95_ci_w,
fill = c_green_blue
+
) geom_abline(
aes(intercept = beta0, slope = beta1, color = location),
alpha = 0.3,
data = data_lines[sample(nrow(data_lines), 80), ]
+
) geom_point(aes(x = temp9am, y = temp3pm, color = location), alpha = 0.7, size = 2) +
scale_color_manual(values = c(c_orange_dark, c_green_blue)) +
labs(
x = "Temperatura a las 9 a.m. (C°)",
y = "Temperatura a las 3 p.m. (C°)",
color = "Ciudad"
+
) theme_bw()
Modelo 4
Finalmente, un modelo que considera interceptos y pendientes específicas para cada ciudad.
stan/australia/modelo_4.stan
data {
int<lower=0> N; // Cantidad de observaciones
array[N] int location_idx; // Índice de la ciudad
vector[N] temp9am; // Temperatura a las 9 am (predictor)
vector[N] temp3pm; // Temperatura a las 3 pm (respuesta)
}parameters {
vector[2] beta0; // Interceptos
vector[2] beta1; // Pendientes
real<lower=0> sigma; // Desvío estándar del error
}transformed parameters {
vector[N] mu; // Predictor lineal
mu = beta0[location_idx] + beta1[location_idx] .* temp9am;
}model {
17.5, 6.25);
beta0 ~ normal(0, 10);
beta1 ~ normal(0, 15);
sigma ~ normal(
temp3pm ~ normal(mu, sigma);
}generated quantities {
vector[N] y_rep;
vector[N] log_likelihood;
for (i in 1:N) {
// Obtención de muestras de la distribución predictiva a posteriori
y_rep[i] = normal_rng(mu[i], sigma);
// Cálculo de la log-verosimilitud
log_likelihood[i] = normal_lpdf(temp3pm[i] | mu[i], sigma);
} }
<- list(
stan_data_4 N = nrow(df),
location_idx = as.integer(as.factor(df$location)),
temp9am = df$temp9am,
temp3pm = df$temp3pm
)
<- here::here(
ruta_modelo_4 "recursos", "codigo", "stan", "australia", "modelo_4.stan"
)
<- stan_model(file = ruta_modelo_4)
modelo_4 <- sampling(
modelo_4_fit
modelo_4,data = stan_data_4,
chains = 4,
refresh = 0,
seed = 1211
)
<- as.data.frame(extract(modelo_4_fit, c("beta0", "beta1", "sigma")))
df_draws_4 colnames(df_draws_4) <- c(
"beta0[Uluru]", "beta0[Wollongong]", "beta1[Uluru]", "beta1[Wollongong]", "sigma"
)
<- df_draws_4 |>
df_draws_long_4 ::pivot_longer(
tidyrc("beta0[Uluru]", "beta0[Wollongong]", "beta1[Uluru]", "beta1[Wollongong]", "sigma"),
names_to = "parametro"
)
ggplot(df_draws_long_4) +
geom_histogram(
aes(x = value, y = after_stat(density)),
bins = 30,
fill = c_orange_light,
color = c_orange_light
+
) facet_wrap(~ parametro, scales = "free") +
labs(x = "Valor", y = "Densidad") +
theme_bw()
<- data.frame(
data_lines beta0 = c(df_draws_4[["beta0[Uluru]"]], df_draws_4[["beta0[Wollongong]"]]),
beta1 = c(df_draws_4[["beta1[Uluru]"]], df_draws_4[["beta1[Wollongong]"]]),
location = rep(c("Uluru", "Wollongong"), each = nrow(df_draws_4))
)
ggplot(df) +
geom_abline(
aes(intercept = beta0, slope = beta1, color = location),
alpha = 0.3,
data = data_lines[sample(nrow(data_lines), 80), ]
+
) geom_point(aes(x = temp9am, y = temp3pm, color = location), alpha = 0.7, size = 2) +
scale_color_manual(values = c(c_orange_dark, c_green_blue)) +
labs(
x = "Temperatura a las 9 a.m. (C°)",
y = "Temperatura a las 3 p.m. (C°)",
color = "Ciudad"
+
) theme_bw()
Código
<- 4000
n_muestras <- 200
n_grilla
<- seq(min(df$temp9am), max(df$temp9am), length.out = n_grilla)
temp9am_seq <- matrix(nrow = n_muestras, ncol = n_grilla)
y_pp_matriz_u <- matrix(nrow = n_muestras, ncol = n_grilla)
y_pp_matriz_w
for (j in seq_len(n_grilla)) {
<- df_draws_4[["beta0[Uluru]"]] + temp9am_seq[j] * df_draws_4[["beta1[Uluru]"]]
mu_u <- df_draws_4[["beta0[Wollongong]"]] + temp9am_seq[j] * df_draws_4[["beta1[Uluru]"]]
mu_w <- df_draws_4$sigma
sigma <- rnorm(n_muestras, mu_u, sigma)
y_pp_matriz_u[, j] <- rnorm(n_muestras, mu_w, sigma)
y_pp_matriz_w[, j]
}
<- apply(y_pp_matriz_u, 2, quantile, probs = c(0.025, 0.975)) |>
df_pp_95_ci_u t()|>
as.data.frame() |>
mutate(temp9am = temp9am_seq) |>
rename(li = `2.5%` , ls = `97.5%`)
<- apply(y_pp_matriz_w, 2, quantile, probs = c(0.025, 0.975)) |>
df_pp_95_ci_w t()|>
as.data.frame() |>
mutate(temp9am = temp9am_seq) |>
rename(li = `2.5%` , ls = `97.5%`)
Código
ggplot(df) +
geom_ribbon(
aes(x = temp9am, ymin = li, ymax = ls),
alpha = 0.3,
data = df_pp_95_ci_u,
fill = c_orange_dark
+
) geom_ribbon(
aes(x = temp9am, ymin = li, ymax = ls),
alpha = 0.3,
data = df_pp_95_ci_w,
fill = c_green_blue
+
) geom_abline(
aes(intercept = beta0, slope = beta1, color = location),
alpha = 0.3,
data = data_lines[sample(nrow(data_lines), 80), ]
+
) geom_point(aes(x = temp9am, y = temp3pm, color = location), alpha = 0.7, size = 2) +
scale_color_manual(values = c(c_orange_dark, c_green_blue)) +
labs(
x = "Temperatura a las 9 a.m. (C°)",
y = "Temperatura a las 3 p.m. (C°)",
color = "Ciudad"
+
) theme_bw()
Con este modelo es posible también obtener y visualizar la distribución a posteriori de la diferencia entre pendientes.
|>
df_draws_4 mutate(pendiente_diff = `beta1[Uluru]` - `beta1[Wollongong]`) |>
ggplot() +
geom_histogram(
aes(x = pendiente_diff, y = after_stat(density)),
bins = 30,
fill = c_orange_light,
color = c_orange_light
+
) labs(x = expression(beta["1, U"] - beta["1, W"]), y = "Densidad") +
theme_bw()
¿Qué indica el gráfico?
Antes de pasar a la comparación de modelos, podemos visualizar la distribución a posteriori marginal para \(\sigma\) en cada modelo (¿qué nos indica?):
<- data.frame(
df_sigmas sigma = c(df_draws_1$sigma, df_draws_2$sigma, df_draws_3$sigma, df_draws_4$sigma),
modelo = rep(c("Modelo 1", "Modelo 2", "Modelo 3", "Modelo 4"), each = 4000)
)
ggplot(df_sigmas) +
geom_histogram(
aes(x = sigma, y = after_stat(density)),
bins = 40,
fill = c_orange_light,
color = c_orange_light
+
) facet_wrap(~ modelo, scales = "free", nrow = 1) +
labs(x = expression(sigma), y = "Densidad") +
theme_bw()
Comparación
Posterior predictive checks
La librería {bayesplot}
nos permite realizar pruebas predictivas a posteriori mediante el uso de ppc_dens_overlay()
. El gráfico que se obtiene permite comparar la distribución empírica de los datos (marginal) con distribuciones estimadas a partir de valores de la distribución predictiva a posteriori.
Para crear este gráfico es necesario haber obtenido muestras de la distribución predictiva a posteriori con Stan. En nuestro caso, las guardamos en y_rep
.
library(bayesplot)
# Notar que solo se muestran 200 muestras para que el gráfico quede menos cargado.
<- extract(modelo_1_fit, "y_rep")$y_rep
y_rep_1 ppc_dens_overlay(df$temp3pm, y_rep_1[sample(2000, 200), ]) +
labs(x = "Temperatura a las 3 p.m. (C°)", y = "Densidad") +
theme_bw()
<- extract(modelo_2_fit, "y_rep")$y_rep
y_rep_2 ppc_dens_overlay(df$temp3pm, y_rep_2[sample(2000, 200), ]) +
labs(x = "Temperatura a las 3 p.m. (C°)", y = "Densidad") +
theme_bw()
<- extract(modelo_3_fit, "y_rep")$y_rep
y_rep_3 ppc_dens_overlay(df$temp3pm, y_rep_3[sample(2000, 200), ]) +
labs(x = "Temperatura a las 3 p.m. (C°)", y = "Densidad") +
theme_bw()
<- extract(modelo_4_fit, "y_rep")$y_rep
y_rep_4 ppc_dens_overlay(df$temp3pm, y_rep_3[sample(2000, 200), ]) +
labs(x = "Temperatura a las 3 p.m. (C°)", y = "Densidad") +
theme_bw()
Las distribuciones generadas con los modelos 1 y 2 no replican fielmente la distribución empírica de la variable respuesta, mientras que las distribuciones generadas con el modelo 3 proveen una mejor aproximación.
LOO
En esta sección utilizamos la función loo()
de la librería {loo}
para estimar el ELPD (Expected log pointwise predictive density for a new dataset) mediante una aproximación a la validación cruzada conocido como PSIS-LOO CV.
Lo único que necesitamos es el objeto que obtuvimos al muestrear del posterior e indicar el nombre de la variable donde guardamos el cómputo del log-likelihood punto a punto (log_likelihood
en todos los casos).
library(loo)
<- loo(modelo_1_fit, pars = "log_likelihood")
loo_1 loo_1
Computed from 4000 by 200 log-likelihood matrix.
Estimate SE
elpd_loo -568.4 8.4
p_loo 2.5 0.3
looic 1136.9 16.8
------
MCSE of elpd_loo is 0.0.
MCSE and ESS estimates assume MCMC draws (r_eff in [0.3, 1.2]).
All Pareto k estimates are good (k < 0.7).
See help('pareto-k-diagnostic') for details.
<- loo(modelo_2_fit, pars = "log_likelihood")
loo_2 loo_2
Computed from 4000 by 200 log-likelihood matrix.
Estimate SE
elpd_loo -625.7 9.5
p_loo 2.9 0.3
looic 1251.5 19.0
------
MCSE of elpd_loo is 0.0.
MCSE and ESS estimates assume MCMC draws (r_eff in [0.9, 1.0]).
All Pareto k estimates are good (k < 0.7).
See help('pareto-k-diagnostic') for details.
<- loo(modelo_3_fit, pars = "log_likelihood")
loo_3 loo_3
Computed from 4000 by 200 log-likelihood matrix.
Estimate SE
elpd_loo -461.1 22.8
p_loo 7.4 3.4
looic 922.1 45.7
------
MCSE of elpd_loo is NA.
MCSE and ESS estimates assume MCMC draws (r_eff in [0.4, 1.2]).
Pareto k diagnostic values:
Count Pct. Min. ESS
(-Inf, 0.7] (good) 199 99.5% 916
(0.7, 1] (bad) 1 0.5% <NA>
(1, Inf) (very bad) 0 0.0% <NA>
See help('pareto-k-diagnostic') for details.
<- loo(modelo_4_fit, pars = "log_likelihood")
loo_4 loo_4
Computed from 4000 by 200 log-likelihood matrix.
Estimate SE
elpd_loo -462.2 22.9
p_loo 8.6 3.5
looic 924.4 45.9
------
MCSE of elpd_loo is 0.1.
MCSE and ESS estimates assume MCMC draws (r_eff in [0.5, 1.1]).
All Pareto k estimates are good (k < 0.7).
See help('pareto-k-diagnostic') for details.
loo()
devuelve un objeto con varias métricas asociadas al ELPD. Para entender mejor cada una de ellas, se puede echar un vistazo al glosario de loo. elpd_loo
es la estimación de ELPD mediante el método PSIS-LOO CV, para la cual también se tiene una medida de la variabilidad con un error estándar.
{loo}
también provee la función loo_compare()
que toma objetos devueltos por loo()
y los ordena de mejor a peor según este criterio. En este caso se obtiene una estimación de la diferencia de ELPDs y un error estándar de la misma.
loo_compare(loo_1, loo_2, loo_3, loo_4)
elpd_diff se_diff
model3 0.0 0.0
model4 -1.1 0.9
model1 -107.4 19.1
model2 -164.7 22.0
Según este criterio, el mejor modelo es el modelo 3. La diferencia de ELPD entre el modelo 3 y el modelo 1 es -107.4 y el error estándar de la misma es 19.2. Si se construye un intervalo aproximado del 95% restando y sumando 2 desvíos estándar de la estimación puntual, el mismo no cubre el 0 y se puede concluir que la diferencia de ELPDs entre el modelo 3 y el 1 es significativa.
Por otro lado, la diferencia de ELPD entre el modelo 3 y el modelo 4 es -1.1 con un error estándar de 0.9. Si se construye un intervalo aproximado del 95% de la misma forma que antes, el resultado cubre el 0 y se puede concluir que al diferencia de ELPDs entre el modelo 3 y el modelo 4 no es significativa. Como el modelo 3 es mas parsimonioso, se lo elige por sobre el modelo 4.