cargamos los datos de las acciones de tecnoglass del ultimo año
# simbolo de Tecnoglass -> TGLS
ticker <- "TGLS"
# Calcular la fecha de inicio y fin
end_date <- Sys.Date() # Fecha actual del sistema
start_date <- "2012-05-09" # Año de inicio de las acciones (se puede validar en la pagina de yahoo finance)
# Convertir las fechas a cadenas en el formato "yyyy-mm-dd"
end_date_str <- format(end_date, "%Y-%m-%d")
# Obtener los datos de Yahoo Finance
getSymbols(ticker, src = "yahoo", from = start_date, to = end_date_str)
## [1] "TGLS"
# Ver los datos obtenidos
print(get(ticker))
## TGLS.Open TGLS.High TGLS.Low TGLS.Close TGLS.Volume TGLS.Adjusted
## 2012-05-10 9.97 10.00 9.50 9.80 6900 7.708232
## 2012-05-11 9.70 9.70 9.70 9.70 300 7.629573
## 2012-05-14 9.80 9.80 9.80 9.80 100 7.708232
## 2012-05-15 9.75 9.75 9.75 9.75 300 7.668901
## 2012-05-16 9.75 9.75 9.75 9.75 0 7.668901
## 2012-05-17 9.60 9.60 9.60 9.60 800 7.550916
## 2012-05-18 9.60 9.60 9.60 9.60 0 7.550916
## 2012-05-21 9.60 9.60 9.60 9.60 0 7.550916
## 2012-05-22 9.69 9.69 9.69 9.69 1500 7.621710
## 2012-05-23 9.77 9.77 9.60 9.60 153600 7.550916
## ...
## 2024-04-01 52.08 52.08 50.89 51.55 209300 51.549999
## 2024-04-02 50.88 51.26 50.00 50.75 296600 50.750000
## 2024-04-03 50.58 52.90 50.58 52.58 299700 52.580002
## 2024-04-04 53.00 54.32 52.29 52.46 280300 52.459999
## 2024-04-05 52.54 53.95 52.54 53.94 540700 53.939999
## 2024-04-08 53.98 55.72 53.98 55.44 666700 55.439999
## 2024-04-09 56.63 58.42 56.07 58.41 700000 58.410000
## 2024-04-10 56.88 59.03 56.37 59.00 617900 59.000000
## 2024-04-11 59.50 59.76 57.16 59.00 422500 59.000000
## 2024-04-12 58.90 58.90 57.52 57.67 311800 57.669998
close_column <- get(ticker)[, "TGLS.Close"]
Los convertimos a Dataframe
df_TGLS<- data.frame(date = index(TGLS), coredata(TGLS))
df_Close_TGLS <- df_TGLS$TGLS.Close
Queremos vizualizar como se comportan las acciones del ultimo año de tecnoglass, para eso, usamos un Time Series plot.
ts_plot(close_column,
title = "Value (in USD) of the last year",
Ytitle = "Values",
Xtitle = "Dates")
Como podemos observar para la serie de Close, la serie
de tiempo a traves de los años ha seguido una tendencia al alta a partir
de sus inicios, donde el año 2020 fue un punto positivo para la compañia
ya que hubo un cambio significativo de tendencia. La serie de tiempo a
simple vista no pareciese que tuviera estacionalidad. Vemos que en el
año 2022 tuvo un comportamiento mas o menos constante alrededor de una
media, y en el año 2023 hasta lo que lleva del 2024 se ha comportado de
manera positiva mostrando una tendencia creciente
Ahora queremos ver un resumen de las metricas agrupadas por
columnas
summary(TGLS)
## Index TGLS.Open TGLS.High TGLS.Low
## Min. :2012-05-10 Min. : 2.44 Min. : 2.54 Min. : 2.15
## 1st Qu.:2015-05-05 1st Qu.: 8.83 1st Qu.: 8.98 1st Qu.: 8.69
## Median :2018-04-26 Median :10.20 Median :10.31 Median :10.16
## Mean :2018-04-26 Mean :14.76 Mean :15.04 Mean :14.48
## 3rd Qu.:2021-04-20 3rd Qu.:14.90 3rd Qu.:15.07 3rd Qu.:14.65
## Max. :2024-04-12 Max. :59.50 Max. :59.76 Max. :57.52
## TGLS.Close TGLS.Volume TGLS.Adjusted
## Min. : 2.290 Min. : 0 Min. : 2.176
## 1st Qu.: 8.848 1st Qu.: 5800 1st Qu.: 7.559
## Median :10.180 Median : 30350 Median : 8.221
## Mean :14.762 Mean : 116581 Mean :13.446
## 3rd Qu.:14.910 3rd Qu.: 134150 3rd Qu.:11.910
## Max. :59.000 Max. :11093300 Max. :59.000
Vemos que para la columna de Volume el maximo representa
el ya comentado pico de diciembre del 2021, esto hace que la media esté
muy alejada de la mediana ya que este pico por decirlo asi aleja o jala
la media hacia el tercer cuartil.
Para la columna de Close, evidenciamos que la media se mantiene en 14 aunque en los ultimos 4 años la tendencia haya sido al alza. Esto es porque el valor de las acciones del 2012-2020 arrastan la media y la mediana hacia abajo. Tambien podemos decir que la razon de por qué la media esta tan cerca al tercer cuartil, es precisamente por la tendencia al alza de los ultimos 4 años. Veamoslo mejor en el boxplot
boxplot(close_column~cycle(close_column),xlab="Close", ylab = "Close value in USD" ,main ="Boxplot Close value 2012-2024")
Del mismo modo, vemos ese comportamiento reflejado en el boxplot. Acá la cantidad de datos por arriba del IQR que vemos es debido a los ultimo valores son altisimos con respecto a los anteriores que maneja esta compañia a partir del 2020, por lo que se representan como atipicos, cuando en realidad es un cambio brusco de tendencia
#options(scipen = 999)
hist(close_column, main = "Histgram for Close", xlab = "Close in USD", breaks = "Sturges", probability = TRUE)
lines(density(close_column))
Como esperabamos, la mayoria de datos se concentran entre 5 y 15 USD, ya que el valor del cierre de las acciones oscilaron en este rango por aproximadamente 8 años antes de subir
Ahora estamos empleando el método de predicción conocido como rolling forecasting. Este enfoque proporciona informes que se basan en datos históricos para prever cifras futuras de manera continua a lo largo de un período determinado. Las proyecciones continuas son comúnmente utilizadas en informes financieros, gestión de la cadena de suministro, planificación y presupuesto.
El rolling forecast es una herramienta fundamental para la toma de decisiones empresariales sólidas. Gracias a su capacidad de adaptación, estas proyecciones continuas permiten a las empresas responder con mayor rapidez a los cambios del mercado. Si se aplican de manera efectiva, estas proyecciones continuas pueden ayudar a detectar áreas de bajo rendimiento, reducir los ciclos de planificación y tomar las mejores decisiones para obtener resultados óptimos. En nuestros ejemplos, estaremos previendo un día por adelantado.
Sacamos los conjuntos de datos de train y test para 28 dias.
n_TGLS <- length(df_Close_TGLS)
n_test <- 28 # Esto se puede cambiar
train_size <- n_TGLS - n_test
train <- df_Close_TGLS[1:train_size]
dates_train <- df_TGLS$date[1:train_size]
test_complete <- df_Close_TGLS[(train_size + 1):(train_size + n_test)]
dates_complete <- df_TGLS$date[(train_size + 1):(train_size + n_test)]
Sacamos los orders optimos con 28 dias
best_aic <- Inf
best_order <- NULL
best_mdl <- NULL
pq_rng <- 0:5
d_rng <- 0:3
for (i in pq_rng) {
for (d in d_rng) {
for (j in pq_rng) {
tryCatch({
# print(paste(i, d, j))
tmp_mdl <- try(arima(train, order = c(i, d, j)), silent = TRUE)
if (!inherits(tmp_mdl, "try-error")) {
tmp_aic <- AIC(tmp_mdl)
if (tmp_aic < best_aic) {
best_aic <- tmp_aic
best_order <- c(i, d, j)
best_mdl <- tmp_mdl
}
}
}, error = function(e) {})
}
}
}
## Warning in arima(train, order = c(i, d, j)): possible convergence problem:
## optim gave code = 1
## Warning in arima(train, order = c(i, d, j)): possible convergence problem:
## optim gave code = 1
## Warning in arima(train, order = c(i, d, j)): possible convergence problem:
## optim gave code = 1
## Warning in arima(train, order = c(i, d, j)): possible convergence problem:
## optim gave code = 1
## Warning in arima(train, order = c(i, d, j)): possible convergence problem:
## optim gave code = 1
cat(sprintf("aic: %6.5f | order: %s\n", best_aic, paste(best_order, collapse = ", ")))
## aic: 5339.85558 | order: 2, 1, 4
best_order1<-c(best_order)
Definimos la funcion arimarolling()
arima_rolling <- function(history, test, best_order,d) {
predictions <- vector()
c<-test$column_name[1:d]
for (t in 1:length(c)) {
model <- Arima(history, order=best_order1)
model_fit <- forecast(model,d)
yhat <- model_fit$mean[t]
predictions <- c(predictions, yhat)
obs <- test[t, ]
#history <- rbind(history, obs)
print(paste("predicted=", yhat, ", expected=", obs))
}
return(predictions)
}
La aplicamos
test_complete_df <- data.frame(column_name = test_complete)
yhat_7 = arima_rolling(history=train, test=test_complete_df,best_order=best_order1,d=7)
## [1] "predicted= 42.7400052900077 , expected= 44.25"
## [1] "predicted= 42.6368543800289 , expected= 44.5299987792969"
## [1] "predicted= 42.7173312260312 , expected= 45.0099983215332"
## [1] "predicted= 42.9605225224937 , expected= 44.8800010681152"
## [1] "predicted= 42.8190971779198 , expected= 44.6500015258789"
## [1] "predicted= 42.6330586825423 , expected= 45"
## [1] "predicted= 42.815130467191 , expected= 45.5800018310547"
yhat_14 = arima_rolling(history=train, test=test_complete_df,best_order=best_order1,d=14)
## [1] "predicted= 42.7400052900077 , expected= 44.25"
## [1] "predicted= 42.6368543800289 , expected= 44.5299987792969"
## [1] "predicted= 42.7173312260312 , expected= 45.0099983215332"
## [1] "predicted= 42.9605225224937 , expected= 44.8800010681152"
## [1] "predicted= 42.8190971779198 , expected= 44.6500015258789"
## [1] "predicted= 42.6330586825423 , expected= 45"
## [1] "predicted= 42.815130467191 , expected= 45.5800018310547"
## [1] "predicted= 42.9371009149202 , expected= 45.4599990844727"
## [1] "predicted= 42.7350693381808 , expected= 45.7999992370605"
## [1] "predicted= 42.6778675642528 , expected= 45.4199981689453"
## [1] "predicted= 42.8805279446935 , expected= 47.5499992370605"
## [1] "predicted= 42.8776364731191 , expected= 50.4300003051758"
## [1] "predicted= 42.6909435786998 , expected= 52.9000015258789"
## [1] "predicted= 42.745032380515 , expected= 52.2599983215332"
yhat_21 = arima_rolling(history=train, test=test_complete_df,best_order=best_order1,d=21)
## [1] "predicted= 42.7400052900077 , expected= 44.25"
## [1] "predicted= 42.6368543800289 , expected= 44.5299987792969"
## [1] "predicted= 42.7173312260312 , expected= 45.0099983215332"
## [1] "predicted= 42.9605225224937 , expected= 44.8800010681152"
## [1] "predicted= 42.8190971779198 , expected= 44.6500015258789"
## [1] "predicted= 42.6330586825423 , expected= 45"
## [1] "predicted= 42.815130467191 , expected= 45.5800018310547"
## [1] "predicted= 42.9371009149202 , expected= 45.4599990844727"
## [1] "predicted= 42.7350693381808 , expected= 45.7999992370605"
## [1] "predicted= 42.6778675642528 , expected= 45.4199981689453"
## [1] "predicted= 42.8805279446935 , expected= 47.5499992370605"
## [1] "predicted= 42.8776364731191 , expected= 50.4300003051758"
## [1] "predicted= 42.6909435786998 , expected= 52.9000015258789"
## [1] "predicted= 42.745032380515 , expected= 52.2599983215332"
## [1] "predicted= 42.9028537673927 , expected= 53.4700012207031"
## [1] "predicted= 42.8093512688063 , expected= 53.5999984741211"
## [1] "predicted= 42.6890942913598 , expected= 52.6800003051758"
## [1] "predicted= 42.8087150201464 , expected= 52.0299987792969"
## [1] "predicted= 42.8870269078383 , expected= 51.5499992370605"
## [1] "predicted= 42.754794519759 , expected= 50.75"
## [1] "predicted= 42.7187608578463 , expected= 52.5800018310547"
yhat_28 = arima_rolling(history=train, test=test_complete_df,best_order=best_order1,d=28)
## [1] "predicted= 42.7400052900077 , expected= 44.25"
## [1] "predicted= 42.6368543800289 , expected= 44.5299987792969"
## [1] "predicted= 42.7173312260312 , expected= 45.0099983215332"
## [1] "predicted= 42.9605225224937 , expected= 44.8800010681152"
## [1] "predicted= 42.8190971779198 , expected= 44.6500015258789"
## [1] "predicted= 42.6330586825423 , expected= 45"
## [1] "predicted= 42.815130467191 , expected= 45.5800018310547"
## [1] "predicted= 42.9371009149202 , expected= 45.4599990844727"
## [1] "predicted= 42.7350693381808 , expected= 45.7999992370605"
## [1] "predicted= 42.6778675642528 , expected= 45.4199981689453"
## [1] "predicted= 42.8805279446935 , expected= 47.5499992370605"
## [1] "predicted= 42.8776364731191 , expected= 50.4300003051758"
## [1] "predicted= 42.6909435786998 , expected= 52.9000015258789"
## [1] "predicted= 42.745032380515 , expected= 52.2599983215332"
## [1] "predicted= 42.9028537673927 , expected= 53.4700012207031"
## [1] "predicted= 42.8093512688063 , expected= 53.5999984741211"
## [1] "predicted= 42.6890942913598 , expected= 52.6800003051758"
## [1] "predicted= 42.8087150201464 , expected= 52.0299987792969"
## [1] "predicted= 42.8870269078383 , expected= 51.5499992370605"
## [1] "predicted= 42.754794519759 , expected= 50.75"
## [1] "predicted= 42.7187608578463 , expected= 52.5800018310547"
## [1] "predicted= 42.8510176958389 , expected= 52.4599990844727"
## [1] "predicted= 42.8479313551733 , expected= 53.939998626709"
## [1] "predicted= 42.7264252452463 , expected= 55.439998626709"
## [1] "predicted= 42.7627423894233 , expected= 58.4099998474121"
## [1] "predicted= 42.8651510654064 , expected= 59"
## [1] "predicted= 42.8033500687186 , expected= 59"
## [1] "predicted= 42.7256272457829 , expected= 57.6699981689453"
Ahora creamos la funcion para sacar el error:
forecast_accuracy <- function(forecast, actual, str_name) {
mape <- mean(abs(forecast - actual)/abs(actual)) # MAPE
mae <- mean(abs(forecast - actual)) # MAE
rmse <- sqrt(mean((forecast - actual)^2)) # RMSE
mse <- mean((forecast - actual)^2) # MSE
r2 <- cor(forecast, actual)^2 # R2
df_acc <- data.frame(MAE = mae,
MSE = mse,
MAPE = mape,
RMSE = rmse,
R2 = r2,
row.names = str_name)
return(df_acc)
}
La aplicamos
x7<-forecast_accuracy(forecast=yhat_7,actual=test_complete[1:7], "week 1")
x14<-forecast_accuracy(forecast=yhat_14,actual=test_complete[1:14], "week 2")
x21<-forecast_accuracy(forecast=yhat_21,actual=test_complete[1:21], "week 3")
x28<-forecast_accuracy(forecast=yhat_28,actual=test_complete[1:28], "week 4")
concat<-rbind(x7,x14,x21,x28)
concat
## MAE MSE MAPE RMSE R2
## week 1 2.082572 4.486277 0.04637260 2.118083 4.137932e-02
## week 2 3.918130 23.448831 0.08074401 4.842399 1.259897e-06
## week 3 5.806820 46.545288 0.11473252 6.822411 6.768314e-03
## week 4 7.795749 83.760292 0.14651869 9.152065 6.870161e-03
Como podemos ver el MAE aumenta a medida que aumenta la ventana de predicción, asi como el MSE y el RMSE. Notamos que el R2 esta muy alejado de ser el ideal, por lo que le falta ajuste a todos los modelos. Asi mismo, vemos que el horizonte que menos tiene error es el de 7 dias, lo cual es lógico.
Se recomienda hacer el analisis con datos despues del 2020 o 2021, ya que despues de este año tienen una tendencia mas diferente que su comportamiento en años anteriores a esta fecha. Puede ser dado por factores externos
Entrenamos el modelo 1 sola vez y pronosticamos con la ventana de 7 dias
model <- arima(train, order=best_order1)
forecast_7<-forecast(model,7)
forecast_7
## Point Forecast Lo 80 Hi 80 Lo 95 Hi 95
## 2973 42.74001 41.98012 43.49989 41.57786 43.90215
## 2974 42.63685 41.55979 43.71392 40.98963 44.28408
## 2975 42.71733 41.42229 44.01238 40.73673 44.69793
## 2976 42.96052 41.46910 44.45194 40.67960 45.24145
## 2977 42.81910 41.16358 44.47461 40.28721 45.35099
## 2978 42.63306 40.83356 44.43256 39.88096 45.38516
## 2979 42.81513 40.87376 44.75651 39.84605 45.78421
graficamos y vemos que siguen una tendencia uniforme, que gira alrededor de una media (aprox 42 usd)
plot_forecast(forecast_7,
title = "Tecnoglass cierre - Forecast",
Ytitle = "Cierre (USD)",
Xtitle = "Tiempo")
Vemos que es la misma historia a medida que aumetan las ventanas
model <- arima(train, order=best_order1)
forecast_14<-forecast(model,14)
forecast_14
## Point Forecast Lo 80 Hi 80 Lo 95 Hi 95
## 2973 42.74001 41.98012 43.49989 41.57786 43.90215
## 2974 42.63685 41.55979 43.71392 40.98963 44.28408
## 2975 42.71733 41.42229 44.01238 40.73673 44.69793
## 2976 42.96052 41.46910 44.45194 40.67960 45.24145
## 2977 42.81910 41.16358 44.47461 40.28721 45.35099
## 2978 42.63306 40.83356 44.43256 39.88096 45.38516
## 2979 42.81513 40.87376 44.75651 39.84605 45.78421
## 2980 42.93710 40.86146 45.01275 39.76267 46.11153
## 2981 42.73507 40.54092 44.92922 39.37940 46.09074
## 2982 42.67787 40.37101 44.98472 39.14984 46.20590
## 2983 42.88053 40.45992 45.30114 39.17852 46.58253
## 2984 42.87764 40.35030 45.40498 39.01240 46.74287
## 2985 42.69094 40.06604 45.31585 38.67650 46.70539
## 2986 42.74503 40.02317 45.46690 38.58230 46.90777
model <- arima(train, order=best_order1)
forecast_21<-forecast(model,21)
forecast_21
## Point Forecast Lo 80 Hi 80 Lo 95 Hi 95
## 2973 42.74001 41.98012 43.49989 41.57786 43.90215
## 2974 42.63685 41.55979 43.71392 40.98963 44.28408
## 2975 42.71733 41.42229 44.01238 40.73673 44.69793
## 2976 42.96052 41.46910 44.45194 40.67960 45.24145
## 2977 42.81910 41.16358 44.47461 40.28721 45.35099
## 2978 42.63306 40.83356 44.43256 39.88096 45.38516
## 2979 42.81513 40.87376 44.75651 39.84605 45.78421
## 2980 42.93710 40.86146 45.01275 39.76267 46.11153
## 2981 42.73507 40.54092 44.92922 39.37940 46.09074
## 2982 42.67787 40.37101 44.98472 39.14984 46.20590
## 2983 42.88053 40.45992 45.30114 39.17852 46.58253
## 2984 42.87764 40.35030 45.40498 39.01240 46.74287
## 2985 42.69094 40.06604 45.31585 38.67650 46.70539
## 2986 42.74503 40.02317 45.46690 38.58230 46.90777
## 2987 42.90285 40.08392 45.72178 38.59167 47.21404
## 2988 42.80935 39.90005 45.71865 38.35996 47.25874
## 2989 42.68909 39.69418 45.68401 38.10877 47.26942
## 2990 42.80872 39.72703 45.89040 38.09568 47.52175
## 2991 42.88703 39.72009 46.05396 38.04362 47.73043
## 2992 42.75479 39.50820 46.00139 37.78955 47.72004
## 2993 42.71876 39.39425 46.04327 37.63437 47.80315
model <- arima(train, order=best_order1)
forecast_28<-forecast(model,28)
forecast_28
## Point Forecast Lo 80 Hi 80 Lo 95 Hi 95
## 2973 42.74001 41.98012 43.49989 41.57786 43.90215
## 2974 42.63685 41.55979 43.71392 40.98963 44.28408
## 2975 42.71733 41.42229 44.01238 40.73673 44.69793
## 2976 42.96052 41.46910 44.45194 40.67960 45.24145
## 2977 42.81910 41.16358 44.47461 40.28721 45.35099
## 2978 42.63306 40.83356 44.43256 39.88096 45.38516
## 2979 42.81513 40.87376 44.75651 39.84605 45.78421
## 2980 42.93710 40.86146 45.01275 39.76267 46.11153
## 2981 42.73507 40.54092 44.92922 39.37940 46.09074
## 2982 42.67787 40.37101 44.98472 39.14984 46.20590
## 2983 42.88053 40.45992 45.30114 39.17852 46.58253
## 2984 42.87764 40.35030 45.40498 39.01240 46.74287
## 2985 42.69094 40.06604 45.31585 38.67650 46.70539
## 2986 42.74503 40.02317 45.46690 38.58230 46.90777
## 2987 42.90285 40.08392 45.72178 38.59167 47.21404
## 2988 42.80935 39.90005 45.71865 38.35996 47.25874
## 2989 42.68909 39.69418 45.68401 38.10877 47.26942
## 2990 42.80872 39.72703 45.89040 38.09568 47.52175
## 2991 42.88703 39.72009 46.05396 38.04362 47.73043
## 2992 42.75479 39.50820 46.00139 37.78955 47.72004
## 2993 42.71876 39.39425 46.04327 37.63437 47.80315
## 2994 42.85102 39.44747 46.25457 37.64574 48.05629
## 2995 42.84793 39.36807 46.32780 37.52594 48.16993
## 2996 42.72643 39.17420 46.27865 37.29377 48.15908
## 2997 42.76274 39.13817 46.38732 37.21944 48.30605
## 2998 42.86515 39.16795 46.56235 37.21077 48.51953
## 2999 42.80335 39.03668 46.57002 37.04272 48.56398
## 3000 42.72563 38.89176 46.55949 36.86224 48.58902
Podemos ver que al no reentrenar el modelo en cada iteración, las estimaciones varian mucho mas con respecto al valor real, haciendo que pierdan calidad.
Evidenciamos que la mayoria oscila entre el numero 42, por lo que al graficarlo nos damos cuenta que sigue una linea recta y no alguna tendencia
plot_forecast(forecast_28,
title = "Tecnoglass cierre - Forecast",
Ytitle = "Cierre (USD)",
Xtitle = "Tiempo")
Definimos la funcion para revisar supuestos
analyze_residuals <- function(predictions_list, actual_data,d) {
results <- list()
residuals_list <- list()
residuals <- actual_data - predictions_list
# Prueba de normalidad (Shapiro-Wilk)
shapiro_test <- shapiro.test(residuals)
shapiro_pvalue <- shapiro_test$p.value
# Prueba de independencia (Ljung-Box)
# El número de lags recomendado por defecto es min(10, length(residuals) / 5)
lb_lags <- min(10, length(residuals) / 5)
lb_test <- Box.test(residuals, lag = lb_lags, type = "Ljung-Box")
# Almacenar resultados
results <- list(
"Prediction Length" = length(predictions_list[[d]]),
"Shapiro P-Value" = shapiro_pvalue,
"Ljung-Box P-Value" = lb_test$p.value
)
# Imprimir conclusiones
cat(paste0("Residuos para la predicción de ", d, " días:\n"))
cat(paste0("Test de Shapiro-Wilk P-Value: ", shapiro_pvalue, "\n"))
if (shapiro_pvalue < 0.05) {
cat("Rechazar la hipótesis nula: Los residuos no son normales.\n")
} else {
cat("No rechazar la hipótesis nula: Los residuos parecen ser normales.\n")
}
cat(paste0("Test de Ljung-Box P-Value: ", lb_test$lb_pvalue[1], "\n"))
if (lb_test$p.value < 0.05) {
cat("Rechazar la hipótesis nula: Los residuos muestran autocorrelación significativa.\n")
} else {
cat("No rechazar la hipótesis nula: Los residuos parecen ser independientes.\n")
}
cat("\n")
return(results)
}
Consumimos la funcion para 7 dias
analyze_residuals(yhat_7,test_complete[1:7],7)
## Residuos para la predicción de 7 días:
## Test de Shapiro-Wilk P-Value: 0.810787041182961
## No rechazar la hipótesis nula: Los residuos parecen ser normales.
## Test de Ljung-Box P-Value:
## No rechazar la hipótesis nula: Los residuos parecen ser independientes.
## $`Prediction Length`
## [1] 1
##
## $`Shapiro P-Value`
## [1] 0.810787
##
## $`Ljung-Box P-Value`
## [1] 0.6827367
Vemos que si se puede confiar en el pronostico usando el metodo de rolling forecast, ya que vemos por las pruebas de normalidad y independencia que se cumplen los supuesto, es decir no se rechazan las hipotesis nulas. Veamos que pasa con las demas ventanas
Consumimos la funcion para 14 dias
analyze_residuals(yhat_14,test_complete[1:14],14)
## Residuos para la predicción de 14 días:
## Test de Shapiro-Wilk P-Value: 0.000894617234839748
## Rechazar la hipótesis nula: Los residuos no son normales.
## Test de Ljung-Box P-Value:
## Rechazar la hipótesis nula: Los residuos muestran autocorrelación significativa.
## $`Prediction Length`
## [1] 1
##
## $`Shapiro P-Value`
## [1] 0.0008946172
##
## $`Ljung-Box P-Value`
## [1] 0.005858441
Evidenciamos que si aumentamos la ventana por 1 semana, ya no se puede contar con el modelo para pronosticas, dado que nos arroja una alta autocorrelación de los residuos y su falta de normalidad
Consumimos la funcion para 21 dias
analyze_residuals(yhat_21,test_complete[1:21],21)
## Residuos para la predicción de 21 días:
## Test de Shapiro-Wilk P-Value: 0.00121712931698376
## Rechazar la hipótesis nula: Los residuos no son normales.
## Test de Ljung-Box P-Value:
## Rechazar la hipótesis nula: Los residuos muestran autocorrelación significativa.
## $`Prediction Length`
## [1] 1
##
## $`Shapiro P-Value`
## [1] 0.001217129
##
## $`Ljung-Box P-Value`
## [1] 3.894185e-10
Como era de esperarse, no se puede confiar en una ventana mucho mas grande para pronosticas
Consumimos la funcion para 28 dias
analyze_residuals(yhat_28,test_complete[1:28],28)
## Residuos para la predicción de 28 días:
## Test de Shapiro-Wilk P-Value: 0.0093674942118775
## Rechazar la hipótesis nula: Los residuos no son normales.
## Test de Ljung-Box P-Value:
## Rechazar la hipótesis nula: Los residuos muestran autocorrelación significativa.
## $`Prediction Length`
## [1] 1
##
## $`Shapiro P-Value`
## [1] 0.009367494
##
## $`Ljung-Box P-Value`
## [1] 1.431189e-12
Vemos que entre mas alta sea la ventana, el modelo se aleja de ser consumible para el pronostico
checkresiduals(forecast_7)
##
## Ljung-Box test
##
## data: Residuals from ARIMA(2,1,4)
## Q* = 5.8603, df = 4, p-value = 0.2098
##
## Model df: 6. Total lags used: 10
Evidenciamos en la grafica del ACF y vemos que muchos rezagos pegan en el en los bordes, lo que nos dice que hay muchos rezagos correlacionados, aunque la prueba de Ljung-Box indican que los residuos son white noise, ya que nos indica que p-value > 0.05, por lo que no tiene suficiente evidencia estadística para rechazar la hipótesis nula. Así que no puede asumir que sus valores son dependientes.
checkresiduals(forecast_14)
##
## Ljung-Box test
##
## data: Residuals from ARIMA(2,1,4)
## Q* = 5.8603, df = 4, p-value = 0.2098
##
## Model df: 6. Total lags used: 10
checkresiduals(forecast_21)
##
## Ljung-Box test
##
## data: Residuals from ARIMA(2,1,4)
## Q* = 5.8603, df = 4, p-value = 0.2098
##
## Model df: 6. Total lags used: 10
checkresiduals(forecast_28)
##
## Ljung-Box test
##
## data: Residuals from ARIMA(2,1,4)
## Q* = 5.8603, df = 4, p-value = 0.2098
##
## Model df: 6. Total lags used: 10
Notamos que sucede lo mismo a medida que aumentamos la ventana
Sacamos los conjuntos de datos de train y test para 28 dias.
n_TGLS <- length(df_Close_TGLS)
n_test <- 28 # Esto se puede cambiar
train_size <- n_TGLS - n_test
train <- df_Close_TGLS[1:train_size]
dates_train <- df_TGLS$date[1:train_size]
test_complete <- df_Close_TGLS[(train_size + 1):(train_size + n_test)]
dates_complete <- df_TGLS$date[(train_size + 1):(train_size + n_test)]
Sacamos los orders optimos con 28 dias y usando el criterio BIC
best_aic <- Inf
best_order <- NULL
best_mdl <- NULL
pq_rng <- 0:5
d_rng <- 0:3
for (i in pq_rng) {
for (d in d_rng) {
for (j in pq_rng) {
tryCatch({
# print(paste(i, d, j))
tmp_mdl <- try(arima(train, order = c(i, d, j)), silent = TRUE)
if (!inherits(tmp_mdl, "try-error")) {
tmp_aic <- BIC(tmp_mdl)
if (tmp_aic < best_aic) {
best_aic <- tmp_aic
best_order <- c(i, d, j)
best_mdl <- tmp_mdl
}
}
}, error = function(e) {})
}
}
}
## Warning in arima(train, order = c(i, d, j)): possible convergence problem:
## optim gave code = 1
## Warning in arima(train, order = c(i, d, j)): possible convergence problem:
## optim gave code = 1
## Warning in arima(train, order = c(i, d, j)): possible convergence problem:
## optim gave code = 1
## Warning in arima(train, order = c(i, d, j)): possible convergence problem:
## optim gave code = 1
## Warning in arima(train, order = c(i, d, j)): possible convergence problem:
## optim gave code = 1
cat(sprintf("BIC: %6.5f | order: %s\n", best_aic, paste(best_order, collapse = ", ")))
## BIC: 5369.86658 | order: 0, 1, 0
best_order1<-c(best_order)
Definimos la funcion arimarolling()
arima_rolling <- function(history, test, best_order,d) {
predictions <- vector()
c<-test$column_name[1:d]
for (t in 1:length(c)) {
model <- Arima(history, order=best_order1)
model_fit <- forecast(model,d)
yhat <- model_fit$mean[t]
predictions <- c(predictions, yhat)
obs <- test[t, ]
#history <- rbind(history, obs)
print(paste("predicted=", yhat, ", expected=", obs))
}
return(predictions)
}
La aplicamos
test_complete_df <- data.frame(column_name = test_complete)
yhat_7 = arima_rolling(history=train, test=test_complete_df,best_order=best_order1,d=7)
## [1] "predicted= 42.6800003051758 , expected= 44.25"
## [1] "predicted= 42.6800003051758 , expected= 44.5299987792969"
## [1] "predicted= 42.6800003051758 , expected= 45.0099983215332"
## [1] "predicted= 42.6800003051758 , expected= 44.8800010681152"
## [1] "predicted= 42.6800003051758 , expected= 44.6500015258789"
## [1] "predicted= 42.6800003051758 , expected= 45"
## [1] "predicted= 42.6800003051758 , expected= 45.5800018310547"
yhat_14 = arima_rolling(history=train, test=test_complete_df,best_order=best_order1,d=14)
## [1] "predicted= 42.6800003051758 , expected= 44.25"
## [1] "predicted= 42.6800003051758 , expected= 44.5299987792969"
## [1] "predicted= 42.6800003051758 , expected= 45.0099983215332"
## [1] "predicted= 42.6800003051758 , expected= 44.8800010681152"
## [1] "predicted= 42.6800003051758 , expected= 44.6500015258789"
## [1] "predicted= 42.6800003051758 , expected= 45"
## [1] "predicted= 42.6800003051758 , expected= 45.5800018310547"
## [1] "predicted= 42.6800003051758 , expected= 45.4599990844727"
## [1] "predicted= 42.6800003051758 , expected= 45.7999992370605"
## [1] "predicted= 42.6800003051758 , expected= 45.4199981689453"
## [1] "predicted= 42.6800003051758 , expected= 47.5499992370605"
## [1] "predicted= 42.6800003051758 , expected= 50.4300003051758"
## [1] "predicted= 42.6800003051758 , expected= 52.9000015258789"
## [1] "predicted= 42.6800003051758 , expected= 52.2599983215332"
yhat_21 = arima_rolling(history=train, test=test_complete_df,best_order=best_order1,d=21)
## [1] "predicted= 42.6800003051758 , expected= 44.25"
## [1] "predicted= 42.6800003051758 , expected= 44.5299987792969"
## [1] "predicted= 42.6800003051758 , expected= 45.0099983215332"
## [1] "predicted= 42.6800003051758 , expected= 44.8800010681152"
## [1] "predicted= 42.6800003051758 , expected= 44.6500015258789"
## [1] "predicted= 42.6800003051758 , expected= 45"
## [1] "predicted= 42.6800003051758 , expected= 45.5800018310547"
## [1] "predicted= 42.6800003051758 , expected= 45.4599990844727"
## [1] "predicted= 42.6800003051758 , expected= 45.7999992370605"
## [1] "predicted= 42.6800003051758 , expected= 45.4199981689453"
## [1] "predicted= 42.6800003051758 , expected= 47.5499992370605"
## [1] "predicted= 42.6800003051758 , expected= 50.4300003051758"
## [1] "predicted= 42.6800003051758 , expected= 52.9000015258789"
## [1] "predicted= 42.6800003051758 , expected= 52.2599983215332"
## [1] "predicted= 42.6800003051758 , expected= 53.4700012207031"
## [1] "predicted= 42.6800003051758 , expected= 53.5999984741211"
## [1] "predicted= 42.6800003051758 , expected= 52.6800003051758"
## [1] "predicted= 42.6800003051758 , expected= 52.0299987792969"
## [1] "predicted= 42.6800003051758 , expected= 51.5499992370605"
## [1] "predicted= 42.6800003051758 , expected= 50.75"
## [1] "predicted= 42.6800003051758 , expected= 52.5800018310547"
yhat_28 = arima_rolling(history=train, test=test_complete_df,best_order=best_order1,d=28)
## [1] "predicted= 42.6800003051758 , expected= 44.25"
## [1] "predicted= 42.6800003051758 , expected= 44.5299987792969"
## [1] "predicted= 42.6800003051758 , expected= 45.0099983215332"
## [1] "predicted= 42.6800003051758 , expected= 44.8800010681152"
## [1] "predicted= 42.6800003051758 , expected= 44.6500015258789"
## [1] "predicted= 42.6800003051758 , expected= 45"
## [1] "predicted= 42.6800003051758 , expected= 45.5800018310547"
## [1] "predicted= 42.6800003051758 , expected= 45.4599990844727"
## [1] "predicted= 42.6800003051758 , expected= 45.7999992370605"
## [1] "predicted= 42.6800003051758 , expected= 45.4199981689453"
## [1] "predicted= 42.6800003051758 , expected= 47.5499992370605"
## [1] "predicted= 42.6800003051758 , expected= 50.4300003051758"
## [1] "predicted= 42.6800003051758 , expected= 52.9000015258789"
## [1] "predicted= 42.6800003051758 , expected= 52.2599983215332"
## [1] "predicted= 42.6800003051758 , expected= 53.4700012207031"
## [1] "predicted= 42.6800003051758 , expected= 53.5999984741211"
## [1] "predicted= 42.6800003051758 , expected= 52.6800003051758"
## [1] "predicted= 42.6800003051758 , expected= 52.0299987792969"
## [1] "predicted= 42.6800003051758 , expected= 51.5499992370605"
## [1] "predicted= 42.6800003051758 , expected= 50.75"
## [1] "predicted= 42.6800003051758 , expected= 52.5800018310547"
## [1] "predicted= 42.6800003051758 , expected= 52.4599990844727"
## [1] "predicted= 42.6800003051758 , expected= 53.939998626709"
## [1] "predicted= 42.6800003051758 , expected= 55.439998626709"
## [1] "predicted= 42.6800003051758 , expected= 58.4099998474121"
## [1] "predicted= 42.6800003051758 , expected= 59"
## [1] "predicted= 42.6800003051758 , expected= 59"
## [1] "predicted= 42.6800003051758 , expected= 57.6699981689453"
Ahora creamos la funcion para sacar el error:
forecast_accuracy <- function(forecast, actual, str_name) {
mape <- mean(abs(forecast - actual)/abs(actual)) # MAPE
mae <- mean(abs(forecast - actual)) # MAE
rmse <- sqrt(mean((forecast - actual)^2)) # RMSE
mse <- mean((forecast - actual)^2) # MSE
r2 <- cor(forecast, actual)^2 # R2
df_acc <- data.frame(MAE = mae,
MSE = mse,
MAPE = mape,
RMSE = rmse,
R2 = r2,
row.names = str_name)
return(df_acc)
}
La aplicamos
x7<-forecast_accuracy(forecast=yhat_7,actual=test_complete[1:7], "week 1")
## Warning in cor(forecast, actual): the standard deviation is zero
x14<-forecast_accuracy(forecast=yhat_14,actual=test_complete[1:14], "week 2")
## Warning in cor(forecast, actual): the standard deviation is zero
x21<-forecast_accuracy(forecast=yhat_21,actual=test_complete[1:21], "week 3")
## Warning in cor(forecast, actual): the standard deviation is zero
x28<-forecast_accuracy(forecast=yhat_28,actual=test_complete[1:28], "week 4")
## Warning in cor(forecast, actual): the standard deviation is zero
concat<-rbind(x7,x14,x21,x28)
concat
## MAE MSE MAPE RMSE R2
## week 1 2.162857 4.83280 0.04815886 2.198363 NA
## week 2 4.014285 24.20030 0.08280846 4.919380 NA
## week 3 5.909523 47.79566 0.11684475 6.913441 NA
## week 4 7.902142 85.49215 0.14862598 9.246196 NA
Vemos que considerando el criterio de inferencia Bayesiana (BIC) los errores cambian ligeramente
Entrenamos el modelo 1 sola vez y pronosticamos con la ventana de 7 dias
model <- arima(train, order=best_order1)
forecast_7<-forecast(model,7)
forecast_7
## Point Forecast Lo 80 Hi 80 Lo 95 Hi 95
## 2973 42.68 41.91547 43.44453 41.51076 43.84924
## 2974 42.68 41.59880 43.76120 41.02644 44.33356
## 2975 42.68 41.35580 44.00420 40.65481 44.70519
## 2976 42.68 41.15095 44.20905 40.34152 45.01848
## 2977 42.68 40.97047 44.38953 40.06550 45.29450
## 2978 42.68 40.80730 44.55270 39.81595 45.54405
## 2979 42.68 40.65725 44.70275 39.58648 45.77352
graficamos y vemos que siguen una tendencia uniforme, que gira alrededor de una media, lo que nos podria dar a entender que el criterio BIC no es tan bueno para encontrar lso orders óptimos
plot_forecast(forecast_7,
title = "Tecnoglass cierre - Forecast",
Ytitle = "Cierre (USD)",
Xtitle = "Tiempo")
Vemos que es la misma historia a medida que aumetan las ventanas
model <- arima(train, order=best_order1)
forecast_14<-forecast(model,14)
forecast_14
## Point Forecast Lo 80 Hi 80 Lo 95 Hi 95
## 2973 42.68 41.91547 43.44453 41.51076 43.84924
## 2974 42.68 41.59880 43.76120 41.02644 44.33356
## 2975 42.68 41.35580 44.00420 40.65481 44.70519
## 2976 42.68 41.15095 44.20905 40.34152 45.01848
## 2977 42.68 40.97047 44.38953 40.06550 45.29450
## 2978 42.68 40.80730 44.55270 39.81595 45.54405
## 2979 42.68 40.65725 44.70275 39.58648 45.77352
## 2980 42.68 40.51759 44.84241 39.37289 45.98712
## 2981 42.68 40.38642 44.97358 39.17228 46.18773
## 2982 42.68 40.26236 45.09764 38.98253 46.37747
## 2983 42.68 40.14435 45.21565 38.80206 46.55794
## 2984 42.68 40.03160 45.32840 38.62963 46.73037
## 2985 42.68 39.92346 45.43654 38.46424 46.89576
## 2986 42.68 39.81941 45.54059 38.30510 47.05490
model <- arima(train, order=best_order1)
forecast_21<-forecast(model,21)
forecast_21
## Point Forecast Lo 80 Hi 80 Lo 95 Hi 95
## 2973 42.68 41.91547 43.44453 41.51076 43.84924
## 2974 42.68 41.59880 43.76120 41.02644 44.33356
## 2975 42.68 41.35580 44.00420 40.65481 44.70519
## 2976 42.68 41.15095 44.20905 40.34152 45.01848
## 2977 42.68 40.97047 44.38953 40.06550 45.29450
## 2978 42.68 40.80730 44.55270 39.81595 45.54405
## 2979 42.68 40.65725 44.70275 39.58648 45.77352
## 2980 42.68 40.51759 44.84241 39.37289 45.98712
## 2981 42.68 40.38642 44.97358 39.17228 46.18773
## 2982 42.68 40.26236 45.09764 38.98253 46.37747
## 2983 42.68 40.14435 45.21565 38.80206 46.55794
## 2984 42.68 40.03160 45.32840 38.62963 46.73037
## 2985 42.68 39.92346 45.43654 38.46424 46.89576
## 2986 42.68 39.81941 45.54059 38.30510 47.05490
## 2987 42.68 39.71900 45.64100 38.15155 47.20845
## 2988 42.68 39.62190 45.73810 38.00303 47.35697
## 2989 42.68 39.52778 45.83222 37.85909 47.50091
## 2990 42.68 39.43639 45.92361 37.71933 47.64067
## 2991 42.68 39.34751 46.01249 37.58339 47.77661
## 2992 42.68 39.26094 46.09906 37.45099 47.90901
## 2993 42.68 39.17650 46.18350 37.32186 48.03814
model <- arima(train, order=best_order1)
forecast_28<-forecast(model,28)
forecast_28
## Point Forecast Lo 80 Hi 80 Lo 95 Hi 95
## 2973 42.68 41.91547 43.44453 41.51076 43.84924
## 2974 42.68 41.59880 43.76120 41.02644 44.33356
## 2975 42.68 41.35580 44.00420 40.65481 44.70519
## 2976 42.68 41.15095 44.20905 40.34152 45.01848
## 2977 42.68 40.97047 44.38953 40.06550 45.29450
## 2978 42.68 40.80730 44.55270 39.81595 45.54405
## 2979 42.68 40.65725 44.70275 39.58648 45.77352
## 2980 42.68 40.51759 44.84241 39.37289 45.98712
## 2981 42.68 40.38642 44.97358 39.17228 46.18773
## 2982 42.68 40.26236 45.09764 38.98253 46.37747
## 2983 42.68 40.14435 45.21565 38.80206 46.55794
## 2984 42.68 40.03160 45.32840 38.62963 46.73037
## 2985 42.68 39.92346 45.43654 38.46424 46.89576
## 2986 42.68 39.81941 45.54059 38.30510 47.05490
## 2987 42.68 39.71900 45.64100 38.15155 47.20845
## 2988 42.68 39.62190 45.73810 38.00303 47.35697
## 2989 42.68 39.52778 45.83222 37.85909 47.50091
## 2990 42.68 39.43639 45.92361 37.71933 47.64067
## 2991 42.68 39.34751 46.01249 37.58339 47.77661
## 2992 42.68 39.26094 46.09906 37.45099 47.90901
## 2993 42.68 39.17650 46.18350 37.32186 48.03814
## 2994 42.68 39.09406 46.26595 37.19577 48.16423
## 2995 42.68 39.01346 46.34654 37.07251 48.28749
## 2996 42.68 38.93460 46.42540 36.95191 48.40809
## 2997 42.68 38.85737 46.50263 36.83379 48.52621
## 2998 42.68 38.78167 46.57833 36.71801 48.64199
## 2999 42.68 38.70741 46.65259 36.60444 48.75556
## 3000 42.68 38.63451 46.72549 36.49295 48.86705
Evidenciamos que la mayoria oscila entre el numero 42, por lo que al graficarlo nos damos cuenta que sigue una linea recta y no alguna tendencia
plot_forecast(forecast_28,
title = "Tecnoglass cierre - Forecast",
Ytitle = "Cierre (USD)",
Xtitle = "Tiempo")
Definimos la funcion para revisar supuestos
analyze_residuals <- function(predictions_list, actual_data,d) {
results <- list()
residuals_list <- list()
residuals <- actual_data - predictions_list
# Prueba de normalidad (Shapiro-Wilk)
shapiro_test <- shapiro.test(residuals)
shapiro_pvalue <- shapiro_test$p.value
# Prueba de independencia (Ljung-Box)
# El número de lags recomendado por defecto es min(10, length(residuals) / 5)
lb_lags <- min(10, length(residuals) / 5)
lb_test <- Box.test(residuals, lag = lb_lags, type = "Ljung-Box")
# Almacenar resultados
results <- list(
"Prediction Length" = length(predictions_list[[d]]),
"Shapiro P-Value" = shapiro_pvalue,
"Ljung-Box P-Value" = lb_test$p.value
)
# Imprimir conclusiones
cat(paste0("Residuos para la predicción de ", d, " días:\n"))
cat(paste0("Test de Shapiro-Wilk P-Value: ", shapiro_pvalue, "\n"))
if (shapiro_pvalue < 0.05) {
cat("Rechazar la hipótesis nula: Los residuos no son normales.\n")
} else {
cat("No rechazar la hipótesis nula: Los residuos parecen ser normales.\n")
}
cat(paste0("Test de Ljung-Box P-Value: ", lb_test$lb_pvalue[1], "\n"))
if (lb_test$p.value < 0.05) {
cat("Rechazar la hipótesis nula: Los residuos muestran autocorrelación significativa.\n")
} else {
cat("No rechazar la hipótesis nula: Los residuos parecen ser independientes.\n")
}
cat("\n")
return(results)
}
Consumimos la funcion para 7 dias
analyze_residuals(yhat_7,test_complete[1:7],7)
## Residuos para la predicción de 7 días:
## Test de Shapiro-Wilk P-Value: 0.872707940972841
## No rechazar la hipótesis nula: Los residuos parecen ser normales.
## Test de Ljung-Box P-Value:
## No rechazar la hipótesis nula: Los residuos parecen ser independientes.
## $`Prediction Length`
## [1] 1
##
## $`Shapiro P-Value`
## [1] 0.8727079
##
## $`Ljung-Box P-Value`
## [1] 0.6589102
Vemos que si se puede confiar en el pronostico usando el metodo de rolling forecast, ya que vemos por las pruebas de normalidad y independencia que se cumplen los supuesto, es decir no se rechazan las hipotesis nulas. Veamos que pasa con las demas ventanas
Consumimos la funcion para 14 dias
analyze_residuals(yhat_14,test_complete[1:14],14)
## Residuos para la predicción de 14 días:
## Test de Shapiro-Wilk P-Value: 0.000982208332347104
## Rechazar la hipótesis nula: Los residuos no son normales.
## Test de Ljung-Box P-Value:
## Rechazar la hipótesis nula: Los residuos muestran autocorrelación significativa.
## $`Prediction Length`
## [1] 1
##
## $`Shapiro P-Value`
## [1] 0.0009822083
##
## $`Ljung-Box P-Value`
## [1] 0.005578635
Evidenciamos que si aumentamos la ventana por 1 semana, ya no se puede contar con el modelo para pronosticas, dado que nos arroja una alta autocorrelación de los residuos y su falta de normalidad
Consumimos la funcion para 21 dias
analyze_residuals(yhat_21,test_complete[1:21],21)
## Residuos para la predicción de 21 días:
## Test de Shapiro-Wilk P-Value: 0.00130329398780393
## Rechazar la hipótesis nula: Los residuos no son normales.
## Test de Ljung-Box P-Value:
## Rechazar la hipótesis nula: Los residuos muestran autocorrelación significativa.
## $`Prediction Length`
## [1] 1
##
## $`Shapiro P-Value`
## [1] 0.001303294
##
## $`Ljung-Box P-Value`
## [1] 3.983308e-10
Como era de esperarse, no se puede confiar en una ventana mucho mas grande para pronosticas
Consumimos la funcion para 28 dias
analyze_residuals(yhat_28,test_complete[1:28],28)
## Residuos para la predicción de 28 días:
## Test de Shapiro-Wilk P-Value: 0.00992973149929236
## Rechazar la hipótesis nula: Los residuos no son normales.
## Test de Ljung-Box P-Value:
## Rechazar la hipótesis nula: Los residuos muestran autocorrelación significativa.
## $`Prediction Length`
## [1] 1
##
## $`Shapiro P-Value`
## [1] 0.009929731
##
## $`Ljung-Box P-Value`
## [1] 1.463829e-12
Vemos que entre mas alta sea la ventana, el modelo se aleja de ser consumible para el pronostico
checkresiduals(forecast_7)
##
## Ljung-Box test
##
## data: Residuals from ARIMA(0,1,0)
## Q* = 37.805, df = 10, p-value = 4.104e-05
##
## Model df: 0. Total lags used: 10
Evidenciamos en la grafica del ACF y vemos que muchos rezagos pegan en el en los bordes, lo que nos dice que hay muchos rezagos correlacionados,la prueba de Ljung-Box indican que los residuos son dependientes, ya que nos indica que p-value <= 0.05, es decir, suede rechazar la hipótesis nula asumiendo un 5% de probabilidad de error. Por lo tanto, puede asumir que sus valores muestran dependencia entre sí.
checkresiduals(forecast_14)
##
## Ljung-Box test
##
## data: Residuals from ARIMA(0,1,0)
## Q* = 37.805, df = 10, p-value = 4.104e-05
##
## Model df: 0. Total lags used: 10
checkresiduals(forecast_21)
##
## Ljung-Box test
##
## data: Residuals from ARIMA(0,1,0)
## Q* = 37.805, df = 10, p-value = 4.104e-05
##
## Model df: 0. Total lags used: 10
checkresiduals(forecast_28)
##
## Ljung-Box test
##
## data: Residuals from ARIMA(0,1,0)
## Q* = 37.805, df = 10, p-value = 4.104e-05
##
## Model df: 0. Total lags used: 10
Notamos que sucede lo mismo a medida que aumentamos la ventana
Sacamos los conjuntos de datos de train y test para 28 dias.
n_TGLS <- length(df_Close_TGLS)
n_test <- 28 # Esto se puede cambiar
train_size <- n_TGLS - n_test
train <- df_Close_TGLS[1:train_size]
dates_train <- df_TGLS$date[1:train_size]
test_complete <- df_Close_TGLS[(train_size + 1):(train_size + n_test)]
dates_complete <- df_TGLS$date[(train_size + 1):(train_size + n_test)]
Sacamos los orders optimos con 28 dias y usando el criterio BIC
# Inicializar el mejor HQIC con un valor grande
best_hqic <- Inf
pq_rng <- 0:4
d_rng <- 0:2
suppressWarnings(for (i in pq_rng) {
for (d in d_rng) {
for (j in pq_rng) {
tryCatch({
# print(paste(i, d, j))
tmp_mdl <- try(arima(train, order = c(i, d, j)), silent = TRUE)
if (!inherits(tmp_mdl, "try-error")) {
# Calcular el número de parámetros en el modelo
k <- length(tmp_mdl$coef)
# Calcular AIC
aic <- AIC(tmp_mdl)
# Calcular el número de observaciones
n <- length(train)
# Calcular AICc
tmp_hqic <- aic + 2*k*(k+1)/(n-k-1)
if (tmp_hqic < best_hqic) {
best_hqic <- tmp_hqic
best_order <- c(i, d, j)
best_mdl <- tmp_mdl
}
}
}, error = function(e) {})
}
}
})
cat(sprintf("hqic: %6.5f | order: %s\n", best_hqic, paste(best_order, collapse = ", ")))
## hqic: 5339.88391 | order: 2, 1, 4
best_order1 <- c(best_order)
Procedemos con los mismo pasos
Definimos la funcion arimarolling()
arima_rolling <- function(history, test, best_order,d) {
predictions <- vector()
c<-test$column_name[1:d]
for (t in 1:length(c)) {
model <- Arima(history, order=best_order1)
model_fit <- forecast(model,d)
yhat <- model_fit$mean[t]
predictions <- c(predictions, yhat)
obs <- test[t, ]
#history <- rbind(history, obs)
print(paste("predicted=", yhat, ", expected=", obs))
}
return(predictions)
}
La aplicamos
test_complete_df <- data.frame(column_name = test_complete)
yhat_7 = arima_rolling(history=train, test=test_complete_df,best_order=best_order1,d=7)
## [1] "predicted= 42.7400052900077 , expected= 44.25"
## [1] "predicted= 42.6368543800289 , expected= 44.5299987792969"
## [1] "predicted= 42.7173312260312 , expected= 45.0099983215332"
## [1] "predicted= 42.9605225224937 , expected= 44.8800010681152"
## [1] "predicted= 42.8190971779198 , expected= 44.6500015258789"
## [1] "predicted= 42.6330586825423 , expected= 45"
## [1] "predicted= 42.815130467191 , expected= 45.5800018310547"
yhat_14 = arima_rolling(history=train, test=test_complete_df,best_order=best_order1,d=14)
## [1] "predicted= 42.7400052900077 , expected= 44.25"
## [1] "predicted= 42.6368543800289 , expected= 44.5299987792969"
## [1] "predicted= 42.7173312260312 , expected= 45.0099983215332"
## [1] "predicted= 42.9605225224937 , expected= 44.8800010681152"
## [1] "predicted= 42.8190971779198 , expected= 44.6500015258789"
## [1] "predicted= 42.6330586825423 , expected= 45"
## [1] "predicted= 42.815130467191 , expected= 45.5800018310547"
## [1] "predicted= 42.9371009149202 , expected= 45.4599990844727"
## [1] "predicted= 42.7350693381808 , expected= 45.7999992370605"
## [1] "predicted= 42.6778675642528 , expected= 45.4199981689453"
## [1] "predicted= 42.8805279446935 , expected= 47.5499992370605"
## [1] "predicted= 42.8776364731191 , expected= 50.4300003051758"
## [1] "predicted= 42.6909435786998 , expected= 52.9000015258789"
## [1] "predicted= 42.745032380515 , expected= 52.2599983215332"
yhat_21 = arima_rolling(history=train, test=test_complete_df,best_order=best_order1,d=21)
## [1] "predicted= 42.7400052900077 , expected= 44.25"
## [1] "predicted= 42.6368543800289 , expected= 44.5299987792969"
## [1] "predicted= 42.7173312260312 , expected= 45.0099983215332"
## [1] "predicted= 42.9605225224937 , expected= 44.8800010681152"
## [1] "predicted= 42.8190971779198 , expected= 44.6500015258789"
## [1] "predicted= 42.6330586825423 , expected= 45"
## [1] "predicted= 42.815130467191 , expected= 45.5800018310547"
## [1] "predicted= 42.9371009149202 , expected= 45.4599990844727"
## [1] "predicted= 42.7350693381808 , expected= 45.7999992370605"
## [1] "predicted= 42.6778675642528 , expected= 45.4199981689453"
## [1] "predicted= 42.8805279446935 , expected= 47.5499992370605"
## [1] "predicted= 42.8776364731191 , expected= 50.4300003051758"
## [1] "predicted= 42.6909435786998 , expected= 52.9000015258789"
## [1] "predicted= 42.745032380515 , expected= 52.2599983215332"
## [1] "predicted= 42.9028537673927 , expected= 53.4700012207031"
## [1] "predicted= 42.8093512688063 , expected= 53.5999984741211"
## [1] "predicted= 42.6890942913598 , expected= 52.6800003051758"
## [1] "predicted= 42.8087150201464 , expected= 52.0299987792969"
## [1] "predicted= 42.8870269078383 , expected= 51.5499992370605"
## [1] "predicted= 42.754794519759 , expected= 50.75"
## [1] "predicted= 42.7187608578463 , expected= 52.5800018310547"
yhat_28 = arima_rolling(history=train, test=test_complete_df,best_order=best_order1,d=28)
## [1] "predicted= 42.7400052900077 , expected= 44.25"
## [1] "predicted= 42.6368543800289 , expected= 44.5299987792969"
## [1] "predicted= 42.7173312260312 , expected= 45.0099983215332"
## [1] "predicted= 42.9605225224937 , expected= 44.8800010681152"
## [1] "predicted= 42.8190971779198 , expected= 44.6500015258789"
## [1] "predicted= 42.6330586825423 , expected= 45"
## [1] "predicted= 42.815130467191 , expected= 45.5800018310547"
## [1] "predicted= 42.9371009149202 , expected= 45.4599990844727"
## [1] "predicted= 42.7350693381808 , expected= 45.7999992370605"
## [1] "predicted= 42.6778675642528 , expected= 45.4199981689453"
## [1] "predicted= 42.8805279446935 , expected= 47.5499992370605"
## [1] "predicted= 42.8776364731191 , expected= 50.4300003051758"
## [1] "predicted= 42.6909435786998 , expected= 52.9000015258789"
## [1] "predicted= 42.745032380515 , expected= 52.2599983215332"
## [1] "predicted= 42.9028537673927 , expected= 53.4700012207031"
## [1] "predicted= 42.8093512688063 , expected= 53.5999984741211"
## [1] "predicted= 42.6890942913598 , expected= 52.6800003051758"
## [1] "predicted= 42.8087150201464 , expected= 52.0299987792969"
## [1] "predicted= 42.8870269078383 , expected= 51.5499992370605"
## [1] "predicted= 42.754794519759 , expected= 50.75"
## [1] "predicted= 42.7187608578463 , expected= 52.5800018310547"
## [1] "predicted= 42.8510176958389 , expected= 52.4599990844727"
## [1] "predicted= 42.8479313551733 , expected= 53.939998626709"
## [1] "predicted= 42.7264252452463 , expected= 55.439998626709"
## [1] "predicted= 42.7627423894233 , expected= 58.4099998474121"
## [1] "predicted= 42.8651510654064 , expected= 59"
## [1] "predicted= 42.8033500687186 , expected= 59"
## [1] "predicted= 42.7256272457829 , expected= 57.6699981689453"
Ahora creamos la funcion para sacar el error:
forecast_accuracy <- function(forecast, actual, str_name) {
mape <- mean(abs(forecast - actual)/abs(actual)) # MAPE
mae <- mean(abs(forecast - actual)) # MAE
rmse <- sqrt(mean((forecast - actual)^2)) # RMSE
mse <- mean((forecast - actual)^2) # MSE
r2 <- cor(forecast, actual)^2 # R2
df_acc <- data.frame(MAE = mae,
MSE = mse,
MAPE = mape,
RMSE = rmse,
R2 = r2,
row.names = str_name)
return(df_acc)
}
La aplicamos
x7<-forecast_accuracy(forecast=yhat_7,actual=test_complete[1:7], "week 1")
x14<-forecast_accuracy(forecast=yhat_14,actual=test_complete[1:14], "week 2")
x21<-forecast_accuracy(forecast=yhat_21,actual=test_complete[1:21], "week 3")
x28<-forecast_accuracy(forecast=yhat_28,actual=test_complete[1:28], "week 4")
concat<-rbind(x7,x14,x21,x28)
concat
## MAE MSE MAPE RMSE R2
## week 1 2.082572 4.486277 0.04637260 2.118083 4.137932e-02
## week 2 3.918130 23.448831 0.08074401 4.842399 1.259897e-06
## week 3 5.806820 46.545288 0.11473252 6.822411 6.768314e-03
## week 4 7.795749 83.760292 0.14651869 9.152065 6.870161e-03
Vemos que considerando el criterio de información de Hannan–Quinn (HQIC) los errores cambian ligeramente y los R2 dan muchisimos mas altos, se presenta un considerable sobreajuste
Entrenamos el modelo 1 sola vez y pronosticamos con la ventana de 7 dias
model <- arima(train, order=best_order1)
forecast_7<-forecast(model,7)
forecast_7
## Point Forecast Lo 80 Hi 80 Lo 95 Hi 95
## 2973 42.74001 41.98012 43.49989 41.57786 43.90215
## 2974 42.63685 41.55979 43.71392 40.98963 44.28408
## 2975 42.71733 41.42229 44.01238 40.73673 44.69793
## 2976 42.96052 41.46910 44.45194 40.67960 45.24145
## 2977 42.81910 41.16358 44.47461 40.28721 45.35099
## 2978 42.63306 40.83356 44.43256 39.88096 45.38516
## 2979 42.81513 40.87376 44.75651 39.84605 45.78421
graficamos y vemos que siguen una tendencia uniforme, que gira alrededor de una media, lo que nos podria dar a entender que el criterio HQIC es mejor que el BIC pero peor que el AIC
plot_forecast(forecast_7,
title = "Tecnoglass cierre - Forecast",
Ytitle = "Cierre (USD)",
Xtitle = "Tiempo")
Vemos que es la misma historia a medida que aumetan las ventanas
model <- arima(train, order=best_order1)
forecast_14<-forecast(model,14)
forecast_14
## Point Forecast Lo 80 Hi 80 Lo 95 Hi 95
## 2973 42.74001 41.98012 43.49989 41.57786 43.90215
## 2974 42.63685 41.55979 43.71392 40.98963 44.28408
## 2975 42.71733 41.42229 44.01238 40.73673 44.69793
## 2976 42.96052 41.46910 44.45194 40.67960 45.24145
## 2977 42.81910 41.16358 44.47461 40.28721 45.35099
## 2978 42.63306 40.83356 44.43256 39.88096 45.38516
## 2979 42.81513 40.87376 44.75651 39.84605 45.78421
## 2980 42.93710 40.86146 45.01275 39.76267 46.11153
## 2981 42.73507 40.54092 44.92922 39.37940 46.09074
## 2982 42.67787 40.37101 44.98472 39.14984 46.20590
## 2983 42.88053 40.45992 45.30114 39.17852 46.58253
## 2984 42.87764 40.35030 45.40498 39.01240 46.74287
## 2985 42.69094 40.06604 45.31585 38.67650 46.70539
## 2986 42.74503 40.02317 45.46690 38.58230 46.90777
model <- arima(train, order=best_order1)
forecast_21<-forecast(model,21)
forecast_21
## Point Forecast Lo 80 Hi 80 Lo 95 Hi 95
## 2973 42.74001 41.98012 43.49989 41.57786 43.90215
## 2974 42.63685 41.55979 43.71392 40.98963 44.28408
## 2975 42.71733 41.42229 44.01238 40.73673 44.69793
## 2976 42.96052 41.46910 44.45194 40.67960 45.24145
## 2977 42.81910 41.16358 44.47461 40.28721 45.35099
## 2978 42.63306 40.83356 44.43256 39.88096 45.38516
## 2979 42.81513 40.87376 44.75651 39.84605 45.78421
## 2980 42.93710 40.86146 45.01275 39.76267 46.11153
## 2981 42.73507 40.54092 44.92922 39.37940 46.09074
## 2982 42.67787 40.37101 44.98472 39.14984 46.20590
## 2983 42.88053 40.45992 45.30114 39.17852 46.58253
## 2984 42.87764 40.35030 45.40498 39.01240 46.74287
## 2985 42.69094 40.06604 45.31585 38.67650 46.70539
## 2986 42.74503 40.02317 45.46690 38.58230 46.90777
## 2987 42.90285 40.08392 45.72178 38.59167 47.21404
## 2988 42.80935 39.90005 45.71865 38.35996 47.25874
## 2989 42.68909 39.69418 45.68401 38.10877 47.26942
## 2990 42.80872 39.72703 45.89040 38.09568 47.52175
## 2991 42.88703 39.72009 46.05396 38.04362 47.73043
## 2992 42.75479 39.50820 46.00139 37.78955 47.72004
## 2993 42.71876 39.39425 46.04327 37.63437 47.80315
model <- arima(train, order=best_order1)
forecast_28<-forecast(model,28)
forecast_28
## Point Forecast Lo 80 Hi 80 Lo 95 Hi 95
## 2973 42.74001 41.98012 43.49989 41.57786 43.90215
## 2974 42.63685 41.55979 43.71392 40.98963 44.28408
## 2975 42.71733 41.42229 44.01238 40.73673 44.69793
## 2976 42.96052 41.46910 44.45194 40.67960 45.24145
## 2977 42.81910 41.16358 44.47461 40.28721 45.35099
## 2978 42.63306 40.83356 44.43256 39.88096 45.38516
## 2979 42.81513 40.87376 44.75651 39.84605 45.78421
## 2980 42.93710 40.86146 45.01275 39.76267 46.11153
## 2981 42.73507 40.54092 44.92922 39.37940 46.09074
## 2982 42.67787 40.37101 44.98472 39.14984 46.20590
## 2983 42.88053 40.45992 45.30114 39.17852 46.58253
## 2984 42.87764 40.35030 45.40498 39.01240 46.74287
## 2985 42.69094 40.06604 45.31585 38.67650 46.70539
## 2986 42.74503 40.02317 45.46690 38.58230 46.90777
## 2987 42.90285 40.08392 45.72178 38.59167 47.21404
## 2988 42.80935 39.90005 45.71865 38.35996 47.25874
## 2989 42.68909 39.69418 45.68401 38.10877 47.26942
## 2990 42.80872 39.72703 45.89040 38.09568 47.52175
## 2991 42.88703 39.72009 46.05396 38.04362 47.73043
## 2992 42.75479 39.50820 46.00139 37.78955 47.72004
## 2993 42.71876 39.39425 46.04327 37.63437 47.80315
## 2994 42.85102 39.44747 46.25457 37.64574 48.05629
## 2995 42.84793 39.36807 46.32780 37.52594 48.16993
## 2996 42.72643 39.17420 46.27865 37.29377 48.15908
## 2997 42.76274 39.13817 46.38732 37.21944 48.30605
## 2998 42.86515 39.16795 46.56235 37.21077 48.51953
## 2999 42.80335 39.03668 46.57002 37.04272 48.56398
## 3000 42.72563 38.89176 46.55949 36.86224 48.58902
Evidenciamos que la mayoria oscila entre el numero 42, por lo que al graficarlo nos damos cuenta que sigue una linea recta y no alguna tendencia
plot_forecast(forecast_28,
title = "Tecnoglass cierre - Forecast",
Ytitle = "Cierre (USD)",
Xtitle = "Tiempo")
Definimos la funcion para revisar supuestos
analyze_residuals <- function(predictions_list, actual_data,d) {
results <- list()
residuals_list <- list()
residuals <- actual_data - predictions_list
# Prueba de normalidad (Shapiro-Wilk)
shapiro_test <- shapiro.test(residuals)
shapiro_pvalue <- shapiro_test$p.value
# Prueba de independencia (Ljung-Box)
# El número de lags recomendado por defecto es min(10, length(residuals) / 5)
lb_lags <- min(10, length(residuals) / 5)
lb_test <- Box.test(residuals, lag = lb_lags, type = "Ljung-Box")
# Almacenar resultados
results <- list(
"Prediction Length" = length(predictions_list[[d]]),
"Shapiro P-Value" = shapiro_pvalue,
"Ljung-Box P-Value" = lb_test$p.value
)
# Imprimir conclusiones
cat(paste0("Residuos para la predicción de ", d, " días:\n"))
cat(paste0("Test de Shapiro-Wilk P-Value: ", shapiro_pvalue, "\n"))
if (shapiro_pvalue < 0.05) {
cat("Rechazar la hipótesis nula: Los residuos no son normales.\n")
} else {
cat("No rechazar la hipótesis nula: Los residuos parecen ser normales.\n")
}
cat(paste0("Test de Ljung-Box P-Value: ", lb_test$lb_pvalue[1], "\n"))
if (lb_test$p.value < 0.05) {
cat("Rechazar la hipótesis nula: Los residuos muestran autocorrelación significativa.\n")
} else {
cat("No rechazar la hipótesis nula: Los residuos parecen ser independientes.\n")
}
cat("\n")
return(results)
}
Consumimos la funcion para 7 dias
analyze_residuals(yhat_7,test_complete[1:7],7)
## Residuos para la predicción de 7 días:
## Test de Shapiro-Wilk P-Value: 0.810787041182961
## No rechazar la hipótesis nula: Los residuos parecen ser normales.
## Test de Ljung-Box P-Value:
## No rechazar la hipótesis nula: Los residuos parecen ser independientes.
## $`Prediction Length`
## [1] 1
##
## $`Shapiro P-Value`
## [1] 0.810787
##
## $`Ljung-Box P-Value`
## [1] 0.6827367
Vemos que si se puede confiar en el pronostico usando el metodo de rolling forecast, ya que vemos por las pruebas de normalidad y independencia que se cumplen los supuesto, es decir no se rechazan las hipotesis nulas. Veamos que pasa con las demas ventanas
Consumimos la funcion para 14 dias
analyze_residuals(yhat_14,test_complete[1:14],14)
## Residuos para la predicción de 14 días:
## Test de Shapiro-Wilk P-Value: 0.000894617234839748
## Rechazar la hipótesis nula: Los residuos no son normales.
## Test de Ljung-Box P-Value:
## Rechazar la hipótesis nula: Los residuos muestran autocorrelación significativa.
## $`Prediction Length`
## [1] 1
##
## $`Shapiro P-Value`
## [1] 0.0008946172
##
## $`Ljung-Box P-Value`
## [1] 0.005858441
Evidenciamos que si aumentamos la ventana por 1 semana, ya no se puede contar con el modelo para pronosticas, dado que nos arroja una alta autocorrelación de los residuos y su falta de normalidad
Consumimos la funcion para 21 dias
analyze_residuals(yhat_21,test_complete[1:21],21)
## Residuos para la predicción de 21 días:
## Test de Shapiro-Wilk P-Value: 0.00121712931698376
## Rechazar la hipótesis nula: Los residuos no son normales.
## Test de Ljung-Box P-Value:
## Rechazar la hipótesis nula: Los residuos muestran autocorrelación significativa.
## $`Prediction Length`
## [1] 1
##
## $`Shapiro P-Value`
## [1] 0.001217129
##
## $`Ljung-Box P-Value`
## [1] 3.894185e-10
Como era de esperarse, no se puede confiar en una ventana mucho mas grande para pronosticas
Consumimos la funcion para 28 dias
analyze_residuals(yhat_28,test_complete[1:28],28)
## Residuos para la predicción de 28 días:
## Test de Shapiro-Wilk P-Value: 0.0093674942118775
## Rechazar la hipótesis nula: Los residuos no son normales.
## Test de Ljung-Box P-Value:
## Rechazar la hipótesis nula: Los residuos muestran autocorrelación significativa.
## $`Prediction Length`
## [1] 1
##
## $`Shapiro P-Value`
## [1] 0.009367494
##
## $`Ljung-Box P-Value`
## [1] 1.431189e-12
Vemos que entre mas alta sea la ventana, el modelo se aleja de ser consumible para el pronostico
checkresiduals(forecast_7)
##
## Ljung-Box test
##
## data: Residuals from ARIMA(2,1,4)
## Q* = 5.8603, df = 4, p-value = 0.2098
##
## Model df: 6. Total lags used: 10
Evidenciamos en la grafica del ACF y vemos que muchos rezagos pegan en el en los bordes, lo que nos dice que hay muchos rezagos correlacionados, aunque la prueba de Ljung-Box indican que los residuos son white noise, ya que nos indica que p-value > 0.05, por lo que no tiene suficiente evidencia estadística para rechazar la hipótesis nula. Así que no puede asumir que sus valores son dependientes.
checkresiduals(forecast_14)
##
## Ljung-Box test
##
## data: Residuals from ARIMA(2,1,4)
## Q* = 5.8603, df = 4, p-value = 0.2098
##
## Model df: 6. Total lags used: 10
checkresiduals(forecast_21)
##
## Ljung-Box test
##
## data: Residuals from ARIMA(2,1,4)
## Q* = 5.8603, df = 4, p-value = 0.2098
##
## Model df: 6. Total lags used: 10
checkresiduals(forecast_28)
##
## Ljung-Box test
##
## data: Residuals from ARIMA(2,1,4)
## Q* = 5.8603, df = 4, p-value = 0.2098
##
## Model df: 6. Total lags used: 10
Notamos que sucede lo mismo a medida que aumentamos la ventana
Vemos que al usar los 3 criterios, si solo nos concentramos usando forecast, vemos que para el BIC es el unico best_order tal que nos da p-value <= 0.05, es decir, suede rechazar la hipótesis nula asumiendo un 5% de probabilidad de error. Por lo tanto, puede asumir que sus valores muestran dependencia entre sí.
Ahora, si nos cocentramos en el rolling forecast, vemos que siempre para las ventanas de 7 dias, usese el criterio que se use para los orders optimos (AIC, BIC ó HQIC), evidenciamos que cumple con los supuestos de normalidad e independencia, en cambio si aumentamos la brecha de dias, los supuestos se dejan de cumplir, a diferencia de cuando solo se usa forecaste. Dandonos a entender que por el metodod de rolling forecast se pueden tener modelos mas ‘robustos’ en pequeñas ventana de tiempo