ANÁLISIS DE REGRESIÓN VARIACIÓN PRECIO
Maestría en Investivación Operativa y Estadística
Desarrollo de modelo de regresión lineal
Procesamiento de los datos
# Funciones
## Función para crear flextable
ftable <- function(x) {
x %>%
flextable() %>%
theme_vanilla() %>%
color(part = "footer", color = "#666666") %>%
color( part = "header", color = "#FFFFFF") %>%
bg( part = "header", bg = "#2c7fb8") %>%
fontsize(size = 11) %>%
font(fontname = 'Calibri') %>%
# Ajustes de ancho y tipo de alineación de las columnas
set_table_properties(layout = "autofit") %>%
# width(j=1, width = 3) %>%
align(i = NULL, j = c(2:ncol(x)), align = "right", part = "all")
}# Cargue y limpieza de datos.
dolar <- read.csv2("datasets/Tasa_de_Cambio_Representativa_del__Mercado_-Historico.csv") %>%
clean_names()
hass <- read.xlsx("datasets/Hass_Precios_Historicos.xlsx") %>% clean_names()# Vamos a sacar un valor promedio de cada variable por mes
#Para el dolar vamos a tomar el campo vigenciadesde
dolar <- dolar %>%
mutate(fecha = as.Date(vigenciadesde, format = "%d/%m/%y")) %>%
mutate(mes = month(fecha), anio = year(fecha)) %>%
mutate(valor = as.double(str_replace(valor,",",""))) %>%
group_by(anio,mes) %>%
summarise(precio_dolar = mean(valor), .groups = "drop") # 🖇️ Se genera el vector de meses para usarlo más abajo con la función match.
meses <- c("Enero", "Febrero", "Marzo", "Abril", "Mayo",
"Junio", "Julio", "Agosto", "Septiembre", "Octubre",
"Noviembre", "Diciembre")
hass <- hass %>%
# Otra forma de sacar el anio.
#mutate(anio = substring(fecha,nchar(fecha)-4,nchar(fecha))) %>%
#mutate(espacio = grepl(", ",fecha))
mutate(mes = sapply(strsplit(fecha, " "), "[", 2),
anio = sapply(strsplit(fecha, " "), "[", 5)) %>%
mutate(anio = as.double(anio)) %>%
# 🖇 Se utiliza la función match con el vector meses.
mutate(mes = match(mes,meses)) %>%
group_by(anio,mes) %>%
summarise(precio_aguacate_kg = mean(precio_kg), .groups = "drop") ## # A tibble: 134 × 3
## anio mes precio_aguacate_kg
## <dbl> <int> <dbl>
## 1 2012 6 3471
## 2 2012 7 3460.
## 3 2012 8 3544.
## 4 2012 9 3792
## 5 2012 10 3661.
## 6 2012 11 3580.
## 7 2012 12 2946
## 8 2013 1 2776.
## 9 2013 2 2732.
## 10 2013 3 3313.
## # ℹ 124 more rows
anio | mes | precio_dolar | precio_aguacate_kg |
|---|---|---|---|
2,012 | 6 | 1,792.232 | 3,471.00 |
2,012 | 7 | 1,785.141 | 3,459.50 |
2,012 | 8 | 1,806.341 | 3,543.50 |
2,012 | 9 | 1,801.948 | 3,792.00 |
2,012 | 10 | 1,805.674 | 3,661.25 |
hass_dolar <- hass_dolar %>% mutate(variacion_aguacate = 100*(precio_aguacate_kg -lag(precio_aguacate_kg))/lag(precio_aguacate_kg)) %>% mutate(variacion_dolar = 100*(precio_dolar -lag(precio_dolar))/lag(precio_dolar)) %>% filter(!is.na(variacion_aguacate))
hass_dolar## # A tibble: 133 × 6
## anio mes precio_dolar precio_aguacate_kg variacion_aguacate
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 2012 7 1785. 3460. -0.331
## 2 2012 8 1806. 3544. 2.43
## 3 2012 9 1802. 3792 7.01
## 4 2012 10 1806. 3661. -3.45
## 5 2012 11 1821. 3580. -2.21
## 6 2012 12 1794. 2946 -17.7
## 7 2013 1 1770. 2776. -5.76
## 8 2013 2 1792. 2732. -1.60
## 9 2013 3 1811. 3313. 21.3
## 10 2013 4 1830. 3038. -8.28
## # ℹ 123 more rows
## # ℹ 1 more variable: variacion_dolar <dbl>
Modelo ingenuo
ggplot(data = hass_dolar,
aes(x= variacion_dolar, y= variacion_aguacate)) +
geom_point()+
geom_hline(yintercept = mean(hass_dolar$variacion_aguacate),
color = "red")+
geom_text(data=hass_dolar,
aes(x= 1, y = mean(hass_dolar$variacion_aguacate),
label = paste("Promedio, [%]:",
round(mean(hass_dolar$variacion_aguacate), 2))),
hjust = 0.5, vjust = -0.1, color = "red"
) +
theme_light()Calidad de ajuste del modelo ingenuo
Sea \(e_i = y_i - \bar{y}\) las diferencia entre la variación del precio del aguacate \(y_i\) y el promedio \(\bar{y}\). Dado el número de diferenciass, se hace necesario definir una métrica resumen.Por conveniencia se elije la suma de los cuadrados de las diferencias, lo cual permite la compensación de las diferencias negativas y positivas.
\[SCE_{\text{ingenuo}} = \sum^{n}_{i=1}e_i²=\sum^{n}_{i=1}(y_i - \bar{y})²\] Como primeras conclusiones se tiene que:
- Entre más grandes sean cada una de las distancias \(e_i\) individuales, más grande será SCE.
- Todas las distancias contribuyen con igual importancia al SCE, excepto en lo que se refiere a su magnitud no hay porque pensar que un distanciamiento de \(u\) unidades sea más importante que otro de las mismas \(u\) unidades. Esto puede ser obvio pero no lo es cuando, por ejemplo, queremos castigar más las distancias que se dan en la zona central del gráfico que las que se dan en los extremos del gráfico.
- El SCE es sensible a la cantidad de datos, razón por la cual entre más datos (es decir entre más grande sea n), más grande será SCE. Esto dificulta seriamente la posibilidad de comparar modelos que fueron calculados sobre una cantidad distinta de puntos.
De acuerdo con lo anterior se modifica la definición de desajuste como sigue:
\[MSE_{\text{ingenuo}} = \color{red}{\frac{1}{n}}\sum^{n}_{i=1}(y_i - \bar{y})²\] La medida de desajuste será el promedio de la suma de las diferencias cuadradas de cada dato \(y_i\) respecto a su media \(\bar{y}\), dicho de otra manera la varianza de la variable \(Y\) a predecir!!!!
# 💡 Varianza de los residuales respecto del modelo ingenuo, es la misma varianza de la variable a predecir.Distancias entre cada dato \(y_i\) observado y el promedio (valor predicho \(\bar{y}\) por el modelo ingenuo)
hass_dolar %>%
ggplot(aes(x= variacion_dolar, y= variacion_aguacate)) +
geom_point()+
geom_hline(yintercept = mean(hass_dolar$variacion_aguacate))+
geom_segment(aes(xend=variacion_dolar, yend=mean(variacion_aguacate)),
col='red', lty='dashed')+
theme_light()
## Modelo de regresión (lineal)
A continuación, se presenta el desarrollo de un modelo lineal que explique la relación entre las variables:
#Modelo de regresión lineal (Analítico)
x = hass_dolar$variacion_dolar
y = hass_dolar$variacion_aguacate
# b1 = cov(x,y) / (sigma_x)²
b1 <- cov(x,y) / var(x)
print(b1)## [1] 0.0930811
## [1] 0.7012181
## [1] 65.35727
# Confirmación mediante la función lm
mod1 <- hass_dolar %>% lm(variacion_aguacate ~ variacion_dolar,.)
print(mod1$coefficients)## (Intercept) variacion_dolar
## 0.7012181 0.0930811
## [1] 65.35727
parametros <- data.frame(metodo = c("Analítico","Función (lm)"),
Beta_0 = c(b0,mod1$coefficients[1]),
Beta_1 = c(b1,mod1$coefficients[2]),
sigma_2 = c(sigma_2, (resumen$sigma)^2))
parametros %>% ftable()metodo | Beta_0 | Beta_1 | sigma_2 |
|---|---|---|---|
Analítico | 0.7012181 | 0.0930811 | 65.35727 |
Función (lm) | 0.7012181 | 0.0930811 | 65.35727 |
Prueba de normalidad.
El mejor modelo para describir la relación entre la variable variación del precio del aguacate (\(y\)) y variación del precio del dólar(\(x\)), pensado desde la perspectiva de minimización de cuadrados del error y estando restringidos a la familia de modelos de regresión lineal con componente de error normal es:
\(y = 0.7 + 0.09x + e \text{ con e~N(0,65.4)}\)
El hecho de encontrar las mejores estimaciones de los betas para minimizar la función de pérdida, no garantiza que nuestro modelo induzca errores normales, por tal motivo conviene recordar lo siguiente:
💭 Cuando el modelo fue planteado fue supuesto que la distribución de los errores sería normal. Una vez construído el modelo, es necesario validar la suposición realizada.
🤔💭 ¿Cómo hacerlo ❔
En la siguiente tabla comparativa se presentan las ventajas y desventajas de las tres pruebas de normalidad (Kolmogorov-Smirnov, Lilliefors y Shapiro-Wilk) más usadas en estadística.
tabla_normalidad <- data.frame(
"Prueba" = c("Kolmogorov-Smirnov (KS)", "Prueba de Lilliefors", "Prueba de Shapiro-Wilk (SW)"),
"Ventajas" = c(
"- Puede utilizarse para cualquier distribución teórica.\n- Es una prueba no paramétrica versátil.\n - Adecuada para muestras grandes.",
"- Es específica para evaluar la normalidad.\n- Puede ser más adecuada para muestras pequeñas.\n - Utiliza estadísticas de prueba que se ajustan a la distribución estándar.",
"- Es especialmente potente para muestras pequeñas.\n- Diseñada específicamente para evaluar la normalidad.\n- Sensible a desviaciones de la normalidad en cualquier parte de la distribución."
), "Desventajas" = c(
"- Puede ser menos poderosa en muestras pequeñas.\n - No es específica para la distribución normal.",
"- Restringida a la comprobación de normalidad. \n - Menos potente en muestras grandes.\n- No es tan versátil como KS para otras distribuciones.",
"- Más compleja de entender y aplicar.\n- No proporciona estimaciones de parámetros.\n- Puede ser menos adecuada para muestras muy grandes.\n- No es tan versátil como KS para otras distribuciones."))
# Imprimir la tabla con formato
tabla_normalidad %>%
ftable() %>%
align(i = NULL, j = c(2:3),
align = "left", part = "all")Prueba | Ventajas | Desventajas |
|---|---|---|
Kolmogorov-Smirnov (KS) | - Puede utilizarse para cualquier distribución teórica. | - Puede ser menos poderosa en muestras pequeñas. |
Prueba de Lilliefors | - Es específica para evaluar la normalidad. | - Restringida a la comprobación de normalidad. |
Prueba de Shapiro-Wilk (SW) | - Es especialmente potente para muestras pequeñas. | - Más compleja de entender y aplicar. |
La elección de la prueba depende de varios factores, incluyendo el tamaño de la muestra, el conocimiento previo sobre la distribución de los datos y los objetivos del análisis. Ninguna prueba es perfecta y es importante considerar el contexto y las características específicas de los datos al seleccionar una prueba de normalidad.
Prueba Lilliefors:
# Paso 1: Ordenamos los datos de menor a mayor
n <- length(mod1$residuals)
df_residuals <- data.frame(residuals = mod1$residuals)
df_residuals <- df_residuals %>%
arrange(residuals)
# Paso 2: Estimamos los parámetros para la distribución normal teórica a partir de los datos
mean_residuals <- mean(df_residuals$residuals)
sd_residuals <- sd(df_residuals$residuals)
# Paso 3: Calculamos la función acumulada para cada valor de residual
cdf_teorica <- pnorm(df_residuals$residuals,
mean = mean_residuals,
sd = sd_residuals)
# Paso 4: Calculamos la máxima diferencia absoluta entre la densidad empírica y la teórica
# Método 1: Construir la función empírica usando fórmulas de R (ecdf) y luego tomar diferencias
# absolutas entre valor empírico y teórico (enfoque KS)
cdf_empirica <- ecdf(df_residuals$residuals)
diferencias_absolutas <- abs(cdf_empirica(df_residuals$residuals) - cdf_teorica)
pos_estadistico_m1 <- which.max(diferencias_absolutas)
estadistico_m1 <- diferencias_absolutas[pos_estadistico_m1]
print(paste0("Este es el valor del estadístico: ", estadistico_m1," y esta es la posición en ",
"la que ocurre la máxima diferencia ", pos_estadistico_m1))## [1] "Este es el valor del estadístico: 0.0567370047041706 y esta es la posición en la que ocurre la máxima diferencia 82"
# Método 2: Construir manualmente los cálculos que permiten hallar la máxima diferencia
# entre la empírica y la teórica usando la definición de la empírica como
# función a trozos 📈
e_plus <- seq(1:n)/n
e_minus <- seq(0:(n-1))/n
D_plus <- e_plus - cdf_teorica
D_minus <- cdf_teorica - e_minus
pos_max_d_plus <- which.max(D_plus)
pos_max_d_minus <- which.max(D_minus)
estadistico_m2 <- max(D_plus[pos_max_d_plus], D_minus[pos_max_d_minus])
pos_estadistico_m2 <- function() {
if(max(D_plus)>=max(D_minus)) {
return(pos_max_d_plus)
}else{
return(pos_max_d_minus)
}
}
print(paste0("Este es el valor del estadístico: ", estadistico_m2," y esta es la posición en ",
"la que ocurre la máxima diferencia ", pos_estadistico_m2()))## [1] "Este es el valor del estadístico: 0.0567370047041706 y esta es la posición en la que ocurre la máxima diferencia 82"
# Comprobamos los resultados invocando la función de R que realiza estos cálculos automáticos
Lilliefors_test <- lillie.test(df_residuals$residuals)
print(Lilliefors_test)##
## Lilliefors (Kolmogorov-Smirnov) normality test
##
## data: df_residuals$residuals
## D = 0.056737, p-value = 0.3681
estadistico_l <- Lilliefors_test$statistic
print(paste0("Este es el estadístico de Lilliefors usando paquetería de R: ", estadistico_l))## [1] "Este es el estadístico de Lilliefors usando paquetería de R: 0.0567370047041706"
#Gráfico de Lilliefors
x_seg <- df_residuals$residuals[pos_estadistico_m1]
y_sup_seg <- cdf_teorica[pos_estadistico_m1]
y_inf_seg <- cdf_empirica(x_seg)
cat("x_seg: ", format(x_seg, digits=5),
" y_inf_seg: ", format(y_inf_seg, digits=5),
" y_sup_seg: ", format(y_sup_seg, digits=5),
sep='')## x_seg: 1.2119 y_inf_seg: 0.61654 y_sup_seg: 0.5598
datos_para_grafico <- data.frame(
residuales = df_residuals$residuals
)
ggplot(datos_para_grafico, aes(x = residuales)) +
# geom_density(color = "blue", fill = "lightblue", alpha = 0.5) +
stat_function(fun = pnorm, args = list(mean = mean_residuals,
sd = sd_residuals),
color = "red", size = 1) +
stat_ecdf(color = "green", size = 1) +
geom_segment(aes(x = x_seg, y = y_inf_seg, xend = x_seg,
yend = y_sup_seg),
color = "purple", size = 1, linetype = "solid") +
geom_point(aes(x = x_seg, y = y_inf_seg),
color = "purple",size = 3) +
geom_point(aes(x = x_seg, y = y_sup_seg),
color = "purple", size = 3) +
annotate("text", x = x_seg , y = (y_inf_seg + y_sup_seg)/2,
label = paste0("Máxima Separación=",
format(estadistico_m1,
digits=5)," 🤔💭", sep=''),
color = "purple", size = 3, hjust = -0.05, vjust = 0) +
coord_cartesian(xlim = c(min(datos_para_grafico$residuales),
max(datos_para_grafico$residuales))) +
xlab("Residuales del modelo") +
ylab("Probabilidad acumulada") +
labs(title = "Esquema de funcionamiento de la prueba de Lilliefors") +
theme_minimal()##
## Shapiro-Wilk normality test
##
## data: mod1$residuals
## W = 0.98669, p-value = 0.2253
##
## Asymptotic one-sample Kolmogorov-Smirnov test
##
## data: mod1$residuals
## D = 0.39524, p-value < 0.00000000000000022
## alternative hypothesis: two-sided
# library(lubridate)
hass_dolar$predicciones <- mod1$fitted.values
hass_dolar$residuales <-mod1$residuals
hass_dolar ## # A tibble: 133 × 8
## anio mes precio_dolar precio_aguacate_kg variacion_aguacate
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 2012 7 1785. 3460. -0.331
## 2 2012 8 1806. 3544. 2.43
## 3 2012 9 1802. 3792 7.01
## 4 2012 10 1806. 3661. -3.45
## 5 2012 11 1821. 3580. -2.21
## 6 2012 12 1794. 2946 -17.7
## 7 2013 1 1770. 2776. -5.76
## 8 2013 2 1792. 2732. -1.60
## 9 2013 3 1811. 3313. 21.3
## 10 2013 4 1830. 3038. -8.28
## # ℹ 123 more rows
## # ℹ 3 more variables: variacion_dolar <dbl>, predicciones <dbl>,
## # residuales <dbl>
De las anteriores pruebas, específicamente para la prueba de Lilliefors y Shapiro - Wilk, se obtuvo un p > 0.05. En este sentido, se concluye que los residuos del modelo siguen una distribución normal.
Prueba de independencia
# Gráfico de residuales vs valores ajustados
hass_dolar %>%
ggplot(aes(x= predicciones, y=residuales)) +
geom_point()+
theme_light()+
ylab("Residuales") +
xlab("Valor predicho")##
## Durbin-Watson test
##
## data: mod1
## DW = 1.6015, p-value = 0.009904
## alternative hypothesis: true autocorrelation is greater than 0
# Prueba de independencia del error: Ljung-Box
p_values <- vector()
for (i in 1:12) {
resultado_ljung_box <- Box.test(mod1$residuals, lag = i,
type = "Ljung-Box")
p_values[i] <-resultado_ljung_box$p.value
}
resultados_lb <- data.frame(
lag = seq(1,12),
p_value = p_values
)
print(resultado_ljung_box)##
## Box-Ljung test
##
## data: mod1$residuals
## X-squared = 85.049, df = 12, p-value = 0.0000000000004454
## lag p_value
## 1 1 0.0208919974880731329
## 2 2 0.0063400452195925272
## 3 3 0.0000007036644118497
## 4 4 0.0000004708457750358
## 5 5 0.0000015209075917566
## 6 6 0.0000002601162144567
## 7 7 0.0000007237077230826
## 8 8 0.0000005256255420916
## 9 9 0.0000000019909318638
## 10 10 0.0000000042534712419
## 11 11 0.0000000025587461039
## 12 12 0.0000000000004454215
La prueba estadística de Durbin-Watson evalúa si existe autocorrelación de primer orden en los residuales, mejora el análisis gráfico por ser una prueba formal. Un valor de Durbin-Watson cercano a 2 indica independencia, mientras que valores significativamente diferentes de 2 pueden indicar autocorrelación.Dado que el estadístico DW = 1.6015, valor cercano a 2, se tiene evidencia estadística de la independencia entre las variables (no existe autocorrelación significativa entre los residuales del modelo). Sin embargo, de acuerdo con la prueba de Box - Ljung, existe evidencia estadística de autocorrelación de los residuales en múltiples rezagos. Por otro lado, del gráfico de Residuales estandarizados vs valores predichos no hay patrones evidentes y los residuales se distribuyen aleatoriamente alrededor de cero, un indicio de independencia.
# Residuales estandarizados vs valores predichos
hass_dolar %>%
ggplot(aes(x= predicciones, y=residuales/sd(residuales))) +
geom_point()+
theme_light()+
ylab("Residuales estandarizados") +
xlab("Valor predicho")# Residuales estandarizados vs precio del dólar
hass_dolar %>%
ggplot(aes(x= variacion_dolar, y=residuales/sd(residuales))) +
geom_point()+
theme_light()+
ylab("Residuales estandarizados") +
xlab("variacion_dolar")
### Prueba de homocedasticidad
# Pruebas estadísticas formales
# Breusch-Pagan
resultado_breusch_pagan <- bptest(mod1)
print(resultado_breusch_pagan)##
## studentized Breusch-Pagan test
##
## data: mod1
## BP = 0.57835, df = 1, p-value = 0.447
# White
resultado_white <- bptest(mod1,
varformula = ~ (variacion_dolar +
I(variacion_dolar^2)),
data=hass_dolar)
print(resultado_white)##
## studentized Breusch-Pagan test
##
## data: mod1
## BP = 0.66416, df = 2, p-value = 0.7174
# Implementación manual del test de White
#1. Construir el dataframe que relaciona el cuadrado del error con
#los términos lineales de la predictora y el término cuadrático
#(por ser modelo lineal simple no hay productos cruzados)
df_squared_error <- data.frame(
squared_error = mod1$residuals ^ 2,
variacion_dolar = hass_dolar$variacion_dolar,
variacion_dolar_2 = hass_dolar$variacion_dolar^2
)
#2. Calcule un modelo lineal ajustado sobre el cuadrado del error regresado por el termino lineal y cuadrático del precio del dolar
modelo_white <- lm(data = df_squared_error,
squared_error ~ variacion_dolar + variacion_dolar_2)
#3. Calcule el estadístico de prueba como n*R^2 (con n=datos del dataframe de errores)
R2 <- summary(modelo_white)$`r.squared`
w_estadistico = nrow(df_squared_error)*R2
#4. Como el estadístico se distribuye ji-cuadrado con 2 grados de libertad (2 variables regresoras) se tiene que el p valor es:
p_valor = 1-pchisq(as.numeric(w_estadistico),2)
paste("BP = ",round(w_estadistico,4)," y p_valor=",round(p_valor,5))## [1] "BP = 0.6642 y p_valor= 0.71743"
De la prueba de Breusch-Pagan y White, se obtuvo un p > 0.05. En este sentido, se concluye queexiste evidencia estadística para asumir homocedasticidad en los residuales del modelo. Por otro lado, del gráfico que se presenta a continuación, no existe un patrón específico que haga sospechar de la presencia de heterocedasticidad.
estimated_squared_error <- modelo_white$coefficients[1] + modelo_white$coefficients[2]*df_squared_error$variacion_dolar +modelo_white$coefficients[3]*df_squared_error$variacion_dolar_2
p<- ggplot(data = df_squared_error, aes(x=variacion_dolar,
y=squared_error))+
geom_point()+
geom_line(aes(y=estimated_squared_error, color="red"))+
labs(title="Modelo white sobre cuadrados del error",
subtitle = paste("R²=",round(R2,4),
"W-Estadístico=",round(w_estadistico,4),
"p_valor=",round(p_valor,5)))+
scale_color_manual(values=c("red"="blue"),
labels=c("Error cuadrado estimado"),
name="Modelo de White")
print(p)
### Conclusiones
En conclusión, se tiene que el modelo funciona, dado que:
- Se tuvo evidencia estadística para grantizar independencia de primer orden en los residuales del modelo.
- Se tuvo evidencia estadística para garantizar normalidad en los residuales del modelo
- Se tuvo evidencia estadística para garantizar homocedasticidad en los residuales del modelo
- Del modelo de regresión lineal planteado se puede concluir que, la variación del precio del aguacate es poco sensible a la variación del precio del dolar. En este sentido, se podría pensar que existen variables exógenas que explican la variación del precio del aguacate en el tiempo, aún cuando puedan presentarse variaciones importantes del precio del dolar.