¿Cómo crear muchas estimaciones a la vez?
Hace unos meses en mi trabajo comencé a hacer algunas mejoras en estimaciones de servicios por parte de la empresa, a lo cuál traté de hacer un modelo global que estimara los servicios totales que se iban a requerir en los próximos meses. El forecast era bueno, realmente tenía un nivel de exactitud bastante alto cuando se puso a prueba, sin embargo, el área de ventas me dijo que ellos necesitaban ver la información con mucho más detalle, es decir, ver un forecast por cada producto que venden. En ese momento pensé algo como “Wow, la empresa tiene varios cientos de productos, esto me va a llevar una eternidad de realizar”, por lo que comencé a plantearme algunas soluciones para minimizar el tiempo que invertiría en realizar ese forecast. Después de estar algunas horas pensando, probando y corrigiendo métodos, encontré uno que puede serte útil si te enfrentas a un problema similar. Te lo compartiré a continuación:
Para resolver esto, dividí el problema en varios pasos, pero antes de comenzar a explicarte la solución a ello quiero que te tomes un minuto para pensar lo que podrías hacer para resolverlo por tu cuenta; algo muy importante dentro del mundo de la programación es pensar con lógica, pensar en cada pequeño paso que nos acerque a la meta deseada, los códigos saldrán consecuentemente, en tanto tengas la idea clara en tu cabeza.
Imaginemos la siguiente situación: Supongamos que tenemos el dataset
de muestra sobre vuelos comerciales del paquete
nycflights13
y queremos estimar el número de destinos para
todo el siguiente año respecto a nuestros datos actuales, esto con una
frecuencia mensual, ¿cómo podemos comenzar a plantear este problema?
Bueno, un primer acercamiento a esto es, simplemente, ver la información
que tenemos disponible:
Lo que podemos observar es que tenemos datos de tipo panel, con observaciones para cada origen y destino del vuelo para un cierto periodo de tiempo en 2013. Mirando más de cerca, encontramos que los destinos de los vuelos son los siguientes:
[1] "IAH" "MIA" "BQN" "ATL" "ORD" "FLL" "IAD" "MCO" "PBI" "TPA"
[11] "LAX" "SFO" "DFW" "BOS" "LAS" "MSP" "DTW" "RSW" "SJU" "PHX"
[21] "BWI" "CLT" "BUF" "DEN" "SNA" "MSY" "SLC" "XNA" "MKE" "SEA"
[31] "ROC" "SYR" "SRQ" "RDU" "CMH" "JAX" "CHS" "MEM" "PIT" "SAN"
[41] "DCA" "CLE" "STL" "MYR" "JAC" "MDW" "HNL" "BNA" "AUS" "BTV"
[51] "PHL" "STT" "EGE" "AVL" "PWM" "IND" "SAV" "CAK" "HOU" "LGB"
[61] "DAY" "ALB" "BDL" "MHT" "MSN" "GSO" "CVG" "BUR" "RIC" "GSP"
[71] "GRR" "MCI" "ORF" "SAT" "SDF" "PDX" "SJC" "OMA" "CRW" "OAK"
[81] "SMF" "TUL" "TYS" "OKC" "PVD" "DSM" "PSE" "BHM" "CAE" "HDN"
[91] "BZN" "MTJ" "EYW" "PSP" "ACK" "BGR" "ABQ" "ILM" "MVY" "SBN"
[101] "LEX" "CHO" "TVC" "ANC" "LGA"
Tenemos 105 destinos en total, lo que implica que tendremos que hacer 105 estimaciones, parece algo que nos llevaría horas o hasta días haciéndolo manualmente, pero vamos por pasos. Pensemos por un momento, ¿qué forma debería tener nuestra información para poder crear un pronóstico por cada uno de los destinos, considerando datos mensuales? En primer lugar, nuestros datos deberían ser mensuales pero los tenemos diarios, por lo que podemos comenzar a agruparlos por meses, para poder contar los viajes que hubo para cada mes por destino, creando una variable que contenga la fecha mensual, es decir:
### Creamos el objeto con los datos
data <- nycflights13::flights
### Creamos una variable de fecha y agrupamos por meses
data |>
mutate(
fecha_mensual = make_date(
year = year, month = month, day = 1
)
) |>
group_by(fecha_mensual, dest) |>
summarise(
viajes = n()
)
# A tibble: 1,113 x 3
# Groups: fecha_mensual [12]
fecha_mensual dest viajes
<date> <chr> <int>
1 2013-01-01 ALB 64
2 2013-01-01 ATL 1396
3 2013-01-01 AUS 169
4 2013-01-01 AVL 2
5 2013-01-01 BDL 37
6 2013-01-01 BHM 25
7 2013-01-01 BNA 399
8 2013-01-01 BOS 1245
9 2013-01-01 BQN 93
10 2013-01-01 BTV 223
# … with 1,103 more rows
Ahora, nosotros ya teníamos en el dataset variables con el mes y año por separado, ¿por qué no utilizarlos y crear otra variable nueva de fecha? El problema con ello es que puede llegar a darse el caso en el que tengamos información para el mismo mes de varios años diferentes, por lo que el número de vuelos sería incorrecto, sin embargo, en ese caso también bastaría con agrupar por mes y año, sin embargo, el crear un grupo nuevo supone una tarea algo más pesada y tardada, lo más sencillo es crear una variable nueva con la fecha, considerando a todos los días del mes como 1, para poder crear datos mensuales. Ya que tenemos los datos agrupados, ¿qué podemos hacer para crear un forecast para cada destino?
Haremos uso de un ciclo for i
, con el que vamos a crear
de manera masiva los pronósticos para cada uno de los 105 casos, pero
para ello aún tenemos que hacer otra transformación con nuestra data,
necesitamos crear sublistas que contengan un dataframe con cada caso
diferente, ¿cómo haremos esto? Sencillo, utilizaremos una función del
paquete tidyr
llamada nest()
, de la siguiente
manera:
### Creamos una variable de fecha y agrupamos por meses
mod <-
data |>
mutate(
fecha_mensual = make_date(
year = year, month = month, day = 1
)
) |>
group_by(fecha_mensual, dest) |>
summarise(
viajes = n()
) |>
group_by(
dest
) |>
tidyr::nest()
mod
# A tibble: 105 x 2
# Groups: dest [105]
dest data
<chr> <list>
1 ALB <tibble [12 × 2]>
2 ATL <tibble [12 × 2]>
3 AUS <tibble [12 × 2]>
4 AVL <tibble [10 × 2]>
5 BDL <tibble [12 × 2]>
6 BHM <tibble [12 × 2]>
7 BNA <tibble [12 × 2]>
8 BOS <tibble [12 × 2]>
9 BQN <tibble [12 × 2]>
10 BTV <tibble [12 × 2]>
# … with 95 more rows
Bien, ahora que ya tenemos la data justo como la necesitamos, podemos comenzar a plantear el modelo estadístico, pero, al final tendremos que presentar los datos de alguna manera, ¿cierto? No sería correcto presentar la consola de R con los datos obtenidos, por lo que por ahora, para simplificar la tarea, vamos a exportar estos datos a un archivo de excel, pero eso lo dejaremos para el punto final.
La idea de crear un ciclo es sencilla: Simplemente vamos a repetir una serie de instrucciones un número i de veces. Lo que haremos en este caso será decirle a nuestro ciclo que tome de nuestros datos transformados los datos del grupo \(1,2,3,...,105\), es por ello que creamos las sublistas para cada caso específico, de esa manera tendremos un dataframe con limpio para cada caso que podremos trabajar de manera independiente.
Cada ciclo va a crear un forecast independiente que podríamos guardar como un objeto individual, pero hacer eso con 105 objetos sería algo poco práctico, en lugar de eso podríamos juntar todos los resultados en otro dataframe concentrado, utilizando de identificador el destino de los vuelos. Vamor a crear un vector vacío en donde alojaremos los resultados:
Ahora, vamos a crear un ciclo que primero genere una serie de tiempo para cada grupo, tomando los números de vuelos y su fecha inicial, así como una frecuencia mensual. Para acceder a esa información en las sublistas, lo haremos de la siguiente manera:
Quiero que notes un par de cosas del código anterior: Primero, el
número i es el rango del ciclo que vamos a introducir, después,
estoy utilizando una función de head()
con número de
observaciones igual a 1, lo que me devolverá un valor unico con el
primer valor de la serie para cada caso, ya que no tenemos la seguridad
de que todos los vuelos para cada destino tengan su primera observación
en el mismo mes o año.
En el siguiente paso, vamos a crear el respectivo modelo, el cuál
guardaremos en un objeto y, después, utilizaremos la función
bind_rows()
para introducir los datos en el dataframe vacío
que creamos anteriormente, guardándolo en cada caso para que nuestro
dataframe final se vaya rellenando con cada forecast individual.
Tendremos:
fin <-
fortify(
forecast(h = 12, auto.arima(ts_var, nmodels = 200)
)
) |>
as.data.frame() |>
select(Index, Data, `Point Forecast` ) |>
mutate(
Destino = mod$dest[i]
)
modelo_df <-
modelo_df |>
bind_rows(
fin
)
Por ahora no voy a entrar en detalles con el modelo a utilizar para estimar los vuelos, ya que este es un ejemplo sencillo para poder comprender cómo crear muchos modelos de forma masiva.
Finalmente, nuestro ciclo se verá así:
for (i in 1:nrow(mod)){
ts_var <-
ts(mod$data[[i]]$viajes, start =
c(
year(head(mod$data[[i]]$fecha_mensual,1)),
month(head(mod$data[[i]]$fecha_mensual,1))
),
frequency = 12
)
fin <-
fortify(
forecast(h = 12, auto.arima(ts_var, nmodels = 200)
)
) |>
as.data.frame() |>
select(Index, Data, `Point Forecast` ) |>
mutate(
Destino = mod$dest[i]
)
modelo_df <-
modelo_df |>
bind_rows(
fin
)
}
Todos los resultados se encontrarán en el objeto
modelo_df
, los cuales tendrán el siguiente formato:
Ahora, ya que tenemos los datos que necesitamos, vamos a crear un archivo de excel con dicha información, de la siguiente manera:
### Eliminamos la primera observación, la cual solo tiene NA´s
modelo_df <- modelo_df[-1,]
### Creamos el archivo de excel
writexl::write_xlsx(modelo_df, path = "dirección_donde_guardaras_tu_archivo/modelo_df.xlsx")
[1] Wickham, H., Francois, R., Henry, L., & Müller, K. (2014, June). dplyr. In useR! Conference.
[2] Bunn, D., & Wright, G. (1991). Interaction of judgemental and statistical forecasting methods: issues & analysis. Management science, 37(5), 501-518.
[3] Wheelwright, S., Makridakis, S., & Hyndman, R. J. (1998). Forecasting: methods and applications. John Wiley & Sons.
[4] Contreras, J., Espinola, R., Nogales, F. J., & Conejo, A. J. (2003). ARIMA models to predict next-day electricity prices. IEEE transactions on power systems, 18(3), 1014-1020.
[5] Benvenuto, D., Giovanetti, M., Vassallo, L., Angeletti, S., & Ciccozzi, M. (2020). Application of the ARIMA model on the COVID-2019 epidemic dataset. Data in brief, 29, 105340.
[6] Zhang, G. P. (2003). Time series forecasting using a hybrid ARIMA and neural network model. Neurocomputing, 50, 159-175.
[7] Tang, Y., Horikoshi, M., & Li, W. (2016). ggfortify: unified interface to visualize statistical results of popular R packages. R J., 8(2), 474.