Las proyecciones de ventas o estado de resultados en las empresas son siempre un tema complicado, donde generalmente todas las áreas de las empresas participan para hacer las proyecciones a través de supuestos de negocio. Por supuesto, todas estas proyecciones con el COVIH-19 han tenido que sufrir bastante cambios y reestructuración ya que nadie pensaba que iba a ver una pandemia en el mundo.
Generalmente, estas proyecciones son hechas por medio supuestos que no necesariamente son propios de los datos, sino que estimaciones de los equipos en base a su experiencia, lo que podría generar bastantes diferencias con la realidad. Una forma de solucionar este tipo de problemas es poder hacer estas proyecciones en base al comportamiento de los datos, más que usar supuestos para la proyección.
En este articulo mostrare tres distintas maneras de poder hacer proyección con Python, usando la librería de Prophet, que es una forma tradicional de hacer proyección, y una manera un poco más distinta, que es usar un modelo tradicional de Machine Learning.
import pandas as pd
from fbprophet import Prophet
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import datetime
from sklearn.model_selection import RandomizedSearchCV
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from sklearn.model_selection import ParameterGrid
import random
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
En esta primera parte, les mostrare un poco los datos y su estructura. Los datos son un dataset que muestra el nivel de ventas de artículos electrónicos en las sucursales de una empresa desde enero del 2019 hasta diciembre del 2020, representando cada registro el total de ventas por día. Los primeros cinco registros del dataset son:
df = pd.read_csv('Datos.csv', sep =';')
df.head()
Fecha | CantidadVentas | |
---|---|---|
0 | 2019-01-01 | 4 |
1 | 2019-01-02 | 103 |
2 | 2019-01-03 | 103 |
3 | 2019-01-04 | 80 |
4 | 2019-01-05 | 29 |
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 816 entries, 0 to 815 Data columns (total 2 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Fecha 816 non-null object 1 CantidadVentas 816 non-null int64 dtypes: int64(1), object(1) memory usage: 12.9+ KB
def funcion_fecha(x):
año = x[0:4]
mes = x[5:7]
dia = x[8:10]
return datetime.datetime(int(año), int(mes), int(dia))
df['Fecha'] = df['Fecha'].apply(lambda x : funcion_fecha(x))
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 816 entries, 0 to 815 Data columns (total 2 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Fecha 816 non-null datetime64[ns] 1 CantidadVentas 816 non-null int64 dtypes: datetime64[ns](1), int64(1) memory usage: 12.9 KB
Si gratificamos todos los datos del dataset, se obtiene lo siguiente:
plt.figure(figsize = (10,6))
df_datos = df.loc[(df['Fecha'] >='2019-01-01') & (df['Fecha'] <= '2020-12-31')]
plt.plot( df_datos['Fecha'], df_datos['CantidadVentas'])
plt.title('Cantidad de Ventas diarias de artículos electrónicos en el periodo de estudio', fontsize = 17)
plt.xlabel('Periodo')
plt.ylabel('Cantidad de Ventas')
sns.despine(left=True, bottom=True)
Como se puede apreciar, desde enero del 2019 hasta marzo del 2020, hubo un crecimiento sostenido de las ventas, donde se produjo una fuerte disminución a partir de abril del 2020 con el Covih-19. Junto con esto, a partir de septiembre del 2020, las ventas se empezaron a recuperar, posiblemente por las medidas de desconfinamiento de la población.
Junto con esto, me parece interesante realizar un análisis mensual de los datos. Si agrupamos los datos por mes, se obtiene lo siguiente:
def periodos(df):
df['Mes'] = df['Fecha'].apply(lambda x: x.month)
df['año'] = df['Fecha'].apply(lambda x: x.year)
df['dia'] = df['Fecha'].apply(lambda x: x.day)
return df
df = periodos(df)
df.head()
Fecha | CantidadVentas | Mes | año | dia | |
---|---|---|---|---|---|
0 | 2019-01-01 | 4 | 1 | 2019 | 1 |
1 | 2019-01-02 | 103 | 1 | 2019 | 2 |
2 | 2019-01-03 | 103 | 1 | 2019 | 3 |
3 | 2019-01-04 | 80 | 1 | 2019 | 4 |
4 | 2019-01-05 | 29 | 1 | 2019 | 5 |
df['Mes'] = df['Mes'].apply(lambda x: '0' + str(x) if len(str(x)) == 1 else str(x) )
df['año'] = df['año'].apply(lambda x: str(x) )
df['Periodo'] = df['año'] + df['Mes']
df_datos = df.loc[(df['Fecha'] >='2019-01-01') & (df['Fecha'] <= '2020-12-31')]
df_periodo = df_datos.groupby('Periodo').sum()['CantidadVentas'].reset_index()
plt.figure(figsize = (10,6))
plt.plot( df_periodo['Periodo'], df_periodo['CantidadVentas'], '-o')
plt.xticks(rotation=60)
plt.title('Cantidad de Ventas mensuales de artículos electrónicos en la empresa', fontsize = 17)
plt.xlabel('Mes del Año')
plt.ylabel('Cantidad de Ventas')
sns.despine(left=True, bottom=True)
INFO:matplotlib.category:Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting. INFO:matplotlib.category:Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting.
df_periodo
Periodo | CantidadVentas | |
---|---|---|
0 | 201901 | 2712 |
1 | 201902 | 2547 |
2 | 201903 | 2402 |
3 | 201904 | 2747 |
4 | 201905 | 3636 |
5 | 201906 | 4954 |
6 | 201907 | 6001 |
7 | 201908 | 6633 |
8 | 201909 | 6493 |
9 | 201910 | 8329 |
10 | 201911 | 8034 |
11 | 201912 | 8872 |
12 | 202001 | 9579 |
13 | 202002 | 8060 |
14 | 202003 | 7274 |
15 | 202004 | 3351 |
16 | 202005 | 3936 |
17 | 202006 | 4455 |
18 | 202007 | 5392 |
19 | 202008 | 6743 |
20 | 202009 | 7498 |
21 | 202010 | 8589 |
22 | 202011 | 9238 |
23 | 202012 | 7956 |
24 | 202101 | 6911 |
25 | 202102 | 6122 |
26 | 202103 | 6810 |
df_mes = df.loc[df['Periodo'] =='202012']
plt.figure(figsize = (10,6))
plt.plot( df_mes['Fecha'], df_mes['CantidadVentas'], '-o')
plt.xticks(rotation=60)
plt.title('Cantidad de Ventas por Dia en Diciembre del 2020', fontsize = 17)
plt.xlabel('Dia de Diciembre')
plt.ylabel('Cantidad de Ventas')
sns.despine(left=True, bottom=True)
Como se esperaba, las ventas mensuales siguen el mismo comportamiento que las ventas diarias, llegando a un peak en enero del 2020, con un total de 9.579 ventas. Junto con esto, la pandemia tuvo un efecto negativo en el negocio, llegando a tener solo 3.351 ventas en marzo del 2020.
trabajaremos con Prophet y un modelo de Machine Learning, llamado Random Forest. Los modelos a trabajar serán los siguientes:
Prophet Normal
Prophet Optimizado a través de una grilla de parámetros.
Random Forest Optimizado, a través de una grilla de parámetros.
Como solo tengo los datos hasta el 10 de Enero del 2021, intentare buscar el modelo que mejor pueda predecir la proyección de las ventas para la semana del 04 hasta el 10 de enero del 2021. No obstante, estos modelos pueden ser ejecutado para predecir el tiempo que se necesite, desde una semana, un mes o un año. Por supuesto, si predecimos más tiempo, se puede producir más errores con la realidad, ya que pueden haber factores externos que afecten el comportamiento de la proyección, como una pandemia, que no estaban modelado en los datos previos.
Para la ejecución del modelo de Prophet sin optimizar, solo tenemos que importar la librería de Prophet, y ejecutar el código establecido en la documentación de Prophet.
Los resultados del modelo de Prophet sin optimizar son:
df = pd.read_csv('Datos.csv', sep =';')
df['Fecha'] = df['Fecha'].apply(lambda x : funcion_fecha(x))
df.tail()
Fecha | CantidadVentas | |
---|---|---|
811 | 2021-03-23 | 331 |
812 | 2021-03-24 | 325 |
813 | 2021-03-25 | 343 |
814 | 2021-03-26 | 259 |
815 | 2021-03-27 | 42 |
df_prophet = df.loc[(df['Fecha'] >='2019-01-01') & (df['Fecha'] <= '2021-01-03')]
df_prophet = df_prophet.rename(columns= {'Fecha': 'ds' , 'CantidadVentas': 'y' } )
modelo = Prophet()
modelo.fit(df_prophet)
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
<fbprophet.forecaster.Prophet at 0x7f56ff107730>
future = modelo.make_future_dataframe(periods=7)
forecast = modelo.predict(future)
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']]
forecast.tail()
ds | trend | yhat_lower | yhat_upper | trend_lower | trend_upper | additive_terms | additive_terms_lower | additive_terms_upper | weekly | weekly_lower | weekly_upper | yearly | yearly_lower | yearly_upper | multiplicative_terms | multiplicative_terms_lower | multiplicative_terms_upper | yhat | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
735 | 2021-01-06 | 219.604161 | 224.163679 | 382.773830 | 219.604161 | 219.604161 | 82.052113 | 82.052113 | 82.052113 | 57.260934 | 57.260934 | 57.260934 | 24.791179 | 24.791179 | 24.791179 | 0.0 | 0.0 | 0.0 | 301.656274 |
736 | 2021-01-07 | 219.590574 | 222.245339 | 379.418233 | 219.590574 | 219.590574 | 81.658745 | 81.658745 | 81.658745 | 52.181519 | 52.181519 | 52.181519 | 29.477226 | 29.477226 | 29.477226 | 0.0 | 0.0 | 0.0 | 301.249319 |
737 | 2021-01-08 | 219.576987 | 191.855541 | 350.014987 | 219.576987 | 219.576997 | 46.772114 | 46.772114 | 46.772114 | 12.300568 | 12.300568 | 12.300568 | 34.471545 | 34.471545 | 34.471545 | 0.0 | 0.0 | 0.0 | 266.349101 |
738 | 2021-01-09 | 219.563400 | 63.465001 | 220.449039 | 219.563400 | 219.570847 | -79.539006 | -79.539006 | -79.539006 | -119.230515 | -119.230515 | -119.230515 | 39.691509 | 39.691509 | 39.691509 | 0.0 | 0.0 | 0.0 | 140.024395 |
739 | 2021-01-10 | 219.549814 | 47.281240 | 210.426761 | 219.548788 | 219.572626 | -87.625847 | -87.625847 | -87.625847 | -132.673976 | -132.673976 | -132.673976 | 45.048129 | 45.048129 | 45.048129 | 0.0 | 0.0 | 0.0 | 131.923967 |
forescast_comparacion = forecast[['ds','yhat', 'yhat_lower', 'yhat_upper']]
forescast_comparacion = forescast_comparacion.loc[(forescast_comparacion['ds'] >= '2021-01-04') & (forescast_comparacion['ds'] <= '2021-01-10')]
forescast_comparacion.head()
ds | yhat | yhat_lower | yhat_upper | |
---|---|---|---|---|
733 | 2021-01-04 | 307.374034 | 224.813776 | 391.208980 |
734 | 2021-01-05 | 299.158590 | 220.091757 | 379.623970 |
735 | 2021-01-06 | 301.656274 | 224.163679 | 382.773830 |
736 | 2021-01-07 | 301.249319 | 222.245339 | 379.418233 |
737 | 2021-01-08 | 266.349101 | 191.855541 | 350.014987 |
plt.figure(figsize=(10,6))
plt.plot(forescast_comparacion['ds'], forescast_comparacion['yhat'], '-o', label ='ForestCast')
plt.plot(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['Fecha'], df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], '-o', label ='Real')
plt.title('Cantidad de Ventas de artículos electrónicos en la semana de validación',fontsize = 17)
plt.xlabel('Semana del 04 hasta 10 de enero')
plt.ylabel('Cantidad de Ventas')
plt.legend()
sns.despine(left=True, bottom=True)
print('DataFrame de Validacion:')
print('\n')
print("R2 (Explicacion de la Varianza):", round(r2_score(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion['yhat']), 2))
print("Porcentaje Error Absoluto Medio (Σ(|y-pred|/y)/n):", round(np.mean(np.abs((np.array(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'])-forescast_comparacion['yhat'])/forescast_comparacion['yhat'])), 2))
print("Error Absoluto Medio (Σ|y-pred|/n):", "{:,.0f}".format(mean_absolute_error(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion['yhat'])))
print("Error Cuadratico Medio (sqrt(Σ(y-pred)^2/n)):", "{:,.0f}".format(np.sqrt(mean_squared_error(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion['yhat']))))
DataFrame de Validacion: R2 (Explicacion de la Varianza): 0.88 Porcentaje Error Absoluto Medio (Σ(|y-pred|/y)/n): 0.09 Error Absoluto Medio (Σ|y-pred|/n): 19 Error Cuadratico Medio (sqrt(Σ(y-pred)^2/n)): 27
Antes de hablar sobre el resultado de las métricas, es importante aclarar que significan, enfocándose principalmente en dos:
R2 (Error Cuadrado): Porcentaje de variación en la variable respuesta que es explicada por el modelo. Si el r2 es mayor, el modelo mejor se ajustado a los datos.
Error Cuadrático Medio: permite medir el error entre dos conjuntos de datos (real y predicción). A menor error, más el modelo se ajusta a los datos.
Luego de esta explicación, se desprende que el modelo de Prophet hubiese precedido bastante bien la semana del 04 hasta el 10 de enero, llegando a lograr un 0.88 y 27 en el R2 y error cuadrático medio respectivamente.
Luego de haber ejecutado el modelo de Prophet sin optimización, y con sus parámetros por default, se generó otro modelo, el cual por medio de una iteración de sus parámetros, se encontró una solución más optima
df_prophet_optimazado = df.copy()
df_prophet_optimazado = df_prophet_optimazado.loc[(df_prophet_optimazado['Fecha'] >='2019-01-01') & (df_prophet_optimazado['Fecha'] <= '2021-01-03')]
df_prophet_optimazado = df_prophet_optimazado.rename(columns= {'Fecha': 'ds' , 'CantidadVentas': 'y' } )
df_prophet_optimazado.tail()
ds | y | |
---|---|---|
728 | 2020-12-30 | 297 |
729 | 2020-12-31 | 182 |
730 | 2021-01-01 | 30 |
731 | 2021-01-02 | 58 |
732 | 2021-01-03 | 67 |
params_grid = {'seasonality_mode':('multiplicative','additive'),
'changepoint_prior_scale':[0.1,0.3,0.5],
'holidays_prior_scale':[0.1,0.3,0.5],
'n_changepoints' : [100,150,200]}
grid = ParameterGrid(params_grid)
cnt = 0
for p in grid:
cnt = cnt+1
print('Total Possible Models',cnt)
def mean_absolute_percentage_error(y_true, y_pred):
y_true, y_pred = np.array(y_true), np.array(y_pred)
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
Total Possible Models 54
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 816 entries, 0 to 815 Data columns (total 2 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Fecha 816 non-null datetime64[ns] 1 CantidadVentas 816 non-null int64 dtypes: datetime64[ns](1), int64(1) memory usage: 12.9 KB
strt='2021-01-04'
end='2021-01-10'
model_parameters = pd.DataFrame(columns = ['MAPE','Parameters'])
for p in grid:
test = pd.DataFrame()
print(p)
random.seed(0)
train_model =Prophet(changepoint_prior_scale = p['changepoint_prior_scale'],
holidays_prior_scale = p['holidays_prior_scale'],
n_changepoints = p['n_changepoints'],
seasonality_mode = p['seasonality_mode'],
weekly_seasonality=True,
daily_seasonality = True,
interval_width=0.95)
train_model.fit(df_prophet_optimazado)
train_forecast = train_model.make_future_dataframe(periods=7, freq='D',include_history = False)
train_forecast = train_model.predict(train_forecast)
test=train_forecast[['ds','yhat']]
Actual = df[(df['Fecha']>=strt) & (df['Fecha']<=end)]
MAPE = mean_absolute_percentage_error(Actual['CantidadVentas'],abs(test['yhat']))
print('Mean Absolute Percentage Error(MAPE)------------------------------------',MAPE)
model_parameters = model_parameters.append({'MAPE':MAPE,'Parameters':p},ignore_index=True)
{'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.1, 'n_changepoints': 100, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 20.649390118416783 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.1, 'n_changepoints': 100, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 16.20860432797343 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.1, 'n_changepoints': 150, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 20.5984260409069 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.1, 'n_changepoints': 150, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 15.004819213958612 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.1, 'n_changepoints': 200, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 20.58413522072185 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.1, 'n_changepoints': 200, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 15.452243617742864 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.3, 'n_changepoints': 100, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 20.649390118416783 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.3, 'n_changepoints': 100, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 16.20860432797343 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.3, 'n_changepoints': 150, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 20.5984260409069 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.3, 'n_changepoints': 150, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 15.004819213958612 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.3, 'n_changepoints': 200, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 20.58413522072185 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.3, 'n_changepoints': 200, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 15.452243617742864 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.5, 'n_changepoints': 100, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 20.649390118416783 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.5, 'n_changepoints': 100, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 16.20860432797343 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.5, 'n_changepoints': 150, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 20.5984260409069 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.5, 'n_changepoints': 150, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 15.004819213958612 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.5, 'n_changepoints': 200, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 20.58413522072185 {'changepoint_prior_scale': 0.1, 'holidays_prior_scale': 0.5, 'n_changepoints': 200, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 15.452243617742864 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.1, 'n_changepoints': 100, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 19.159631935281077 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.1, 'n_changepoints': 100, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 8.558696162247017 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.1, 'n_changepoints': 150, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 19.138179642313816 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.1, 'n_changepoints': 150, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 8.57978419795937 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.1, 'n_changepoints': 200, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 18.914098651449223 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.1, 'n_changepoints': 200, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 7.96358154228917 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.3, 'n_changepoints': 100, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 19.159631935281077 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.3, 'n_changepoints': 100, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 8.558696162247017 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.3, 'n_changepoints': 150, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 19.138179642313816 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.3, 'n_changepoints': 150, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 8.57978419795937 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.3, 'n_changepoints': 200, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 18.914098651449223 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.3, 'n_changepoints': 200, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 7.96358154228917 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.5, 'n_changepoints': 100, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 19.159631935281077 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.5, 'n_changepoints': 100, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 8.558696162247017 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.5, 'n_changepoints': 150, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 19.138179642313816 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.5, 'n_changepoints': 150, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 8.57978419795937 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.5, 'n_changepoints': 200, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 18.914098651449223 {'changepoint_prior_scale': 0.3, 'holidays_prior_scale': 0.5, 'n_changepoints': 200, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 7.96358154228917 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.1, 'n_changepoints': 100, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 20.085566582253314 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.1, 'n_changepoints': 100, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 7.966501367314198 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.1, 'n_changepoints': 150, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 19.30344434129816 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.1, 'n_changepoints': 150, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 8.120288462223046 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.1, 'n_changepoints': 200, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 20.383215247900583 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.1, 'n_changepoints': 200, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 7.655080153829756 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.3, 'n_changepoints': 100, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 20.085566582253314 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.3, 'n_changepoints': 100, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 7.966501367314198 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.3, 'n_changepoints': 150, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 19.30344434129816 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.3, 'n_changepoints': 150, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 8.120288462223046 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.3, 'n_changepoints': 200, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 20.383215247900583 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.3, 'n_changepoints': 200, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 7.655080153829756 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.5, 'n_changepoints': 100, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 20.085566582253314 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.5, 'n_changepoints': 100, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 7.966501367314198 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.5, 'n_changepoints': 150, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 19.30344434129816 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.5, 'n_changepoints': 150, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 8.120288462223046 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.5, 'n_changepoints': 200, 'seasonality_mode': 'multiplicative'} Mean Absolute Percentage Error(MAPE)------------------------------------ 20.383215247900583 {'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.5, 'n_changepoints': 200, 'seasonality_mode': 'additive'} Mean Absolute Percentage Error(MAPE)------------------------------------ 7.655080153829756
parameters = model_parameters.sort_values(by=['MAPE'])
parameters = parameters.reset_index(drop=True)
parameters.head()
MAPE | Parameters | |
---|---|---|
0 | 7.655080 | {'changepoint_prior_scale': 0.5, 'holidays_pri... |
1 | 7.655080 | {'changepoint_prior_scale': 0.5, 'holidays_pri... |
2 | 7.655080 | {'changepoint_prior_scale': 0.5, 'holidays_pri... |
3 | 7.963582 | {'changepoint_prior_scale': 0.3, 'holidays_pri... |
4 | 7.963582 | {'changepoint_prior_scale': 0.3, 'holidays_pri... |
print('Mejor Parametros:')
parameters['Parameters'][0]
Mejor Parametros:
{'changepoint_prior_scale': 0.5, 'holidays_prior_scale': 0.5, 'n_changepoints': 200, 'seasonality_mode': 'additive'}
final_model = Prophet(changepoint_prior_scale= 0.5,
holidays_prior_scale = 0.5,
n_changepoints = 200,
seasonality_mode = 'additive',
weekly_seasonality=True,
daily_seasonality = True,
interval_width=0.95)
final_model.fit(df_prophet_optimazado)
<fbprophet.forecaster.Prophet at 0x7f56ffa182b0>
future = final_model.make_future_dataframe(periods=7)
forecast = final_model.predict(future)
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']]
forecast.head()
ds | trend | yhat_lower | yhat_upper | trend_lower | trend_upper | additive_terms | additive_terms_lower | additive_terms_upper | daily | ... | weekly | weekly_lower | weekly_upper | yearly | yearly_lower | yearly_upper | multiplicative_terms | multiplicative_terms_lower | multiplicative_terms_upper | yhat | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2019-01-01 | 4.829644 | -25.574548 | 202.756720 | 4.829644 | 4.829644 | 86.331705 | 86.331705 | 86.331705 | 3.96706 | ... | 58.959214 | 58.959214 | 58.959214 | 23.405431 | 23.405431 | 23.405431 | 0.0 | 0.0 | 0.0 | 91.161349 |
1 | 2019-01-02 | 5.677582 | -27.165866 | 201.519198 | 5.677582 | 5.677582 | 85.675415 | 85.675415 | 85.675415 | 3.96706 | ... | 57.202781 | 57.202781 | 57.202781 | 24.505575 | 24.505575 | 24.505575 | 0.0 | 0.0 | 0.0 | 91.352998 |
2 | 2019-01-03 | 6.525521 | -12.959319 | 200.621697 | 6.525521 | 6.525521 | 82.317474 | 82.317474 | 82.317474 | 3.96706 | ... | 52.156365 | 52.156365 | 52.156365 | 26.194048 | 26.194048 | 26.194048 | 0.0 | 0.0 | 0.0 | 88.842994 |
3 | 2019-01-04 | 7.373459 | -60.168144 | 171.144548 | 7.373459 | 7.373459 | 44.722544 | 44.722544 | 44.722544 | 3.96706 | ... | 12.307548 | 12.307548 | 12.307548 | 28.447936 | 28.447936 | 28.447936 | 0.0 | 0.0 | 0.0 | 52.096004 |
4 | 2019-01-05 | 8.221398 | -190.019743 | 39.925955 | 8.221398 | 8.221398 | -83.988352 | -83.988352 | -83.988352 | 3.96706 | ... | -119.188328 | -119.188328 | -119.188328 | 31.232916 | 31.232916 | 31.232916 | 0.0 | 0.0 | 0.0 | -75.766954 |
5 rows × 22 columns
forescast_comparacion = forecast[['ds','yhat', 'yhat_lower', 'yhat_upper']]
forescast_comparacion = forescast_comparacion.loc[(forescast_comparacion['ds'] >= '2021-01-04') & (forescast_comparacion['ds'] <= '2021-01-10')]
forescast_comparacion.head()
ds | yhat | yhat_lower | yhat_upper | |
---|---|---|---|---|
733 | 2021-01-04 | 283.642995 | 163.239586 | 396.570873 |
734 | 2021-01-05 | 274.422070 | 154.964926 | 391.335878 |
735 | 2021-01-06 | 275.932697 | 165.897669 | 384.437902 |
736 | 2021-01-07 | 274.550721 | 163.463928 | 392.420016 |
737 | 2021-01-08 | 238.692909 | 110.931544 | 355.089320 |
plt.figure(figsize=(10,6))
plt.plot(forescast_comparacion['ds'], forescast_comparacion['yhat'], '-o', label ='ForestCast')
plt.plot(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['Fecha'], df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], '-o', label ='Real')
plt.title('Cantidad de Ventas de artículos electrónicos en la semana de validación',fontsize = 17)
plt.xlabel('Semana 04 al 10 de Enero')
plt.ylabel('Cantidad de Ventas')
sns.despine(left=True, bottom=True)
plt.legend()
<matplotlib.legend.Legend at 0x7f56fefe0b50>
print('DataFrame de Validacion:')
print('\n')
print("R2 (Explicacion de la Varianza):", round(r2_score(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion['yhat']), 2))
print("Porcentaje Error Absoluto Medio (Σ(|y-pred|/y)/n):", round(np.mean(np.abs((np.array(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'])-forescast_comparacion['yhat'])/forescast_comparacion['yhat'])), 2))
print("Error Absoluto Medio (Σ|y-pred|/n):", "{:,.0f}".format(mean_absolute_error(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion['yhat'])))
print("Error Cuadratico Medio (sqrt(Σ(y-pred)^2/n)):", "{:,.0f}".format(np.sqrt(mean_squared_error(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion['yhat']))))
DataFrame de Validacion: R2 (Explicacion de la Varianza): 0.92 Porcentaje Error Absoluto Medio (Σ(|y-pred|/y)/n): 0.08 Error Absoluto Medio (Σ|y-pred|/n): 18 Error Cuadratico Medio (sqrt(Σ(y-pred)^2/n)): 22
El modelo optimizado obtiene aun mejores métricas, llegando a un 0.92 y 22 en el R2 y Error cuadrático medio. Junto con esto, también se puede apreciar en el mismo gráfico, que la predicción se comporta bastante similar a los valores reales, llevando a concluir que la optimización del modelo si permite obtener mejores resultados.
Modelar el problema de una forma un poco distinta. Se que el dataset solo contiene dos variables, que es la fecha y la cantidad, no obstante, se podría construir un modelo de machine learning que considere estas variables para poder realizar la predicción.
Como todo modelo de ML necesita datos numéricos para poder ser construido, tuve que transformar la variable fecha a números que pudieran explicar de algunas maneras la fecha, tales como: el año, mes, día y día de la semana del registro. Esto se puede observar a continuación:
def periodos(df):
df['Mes'] = df['Fecha'].apply(lambda x: x.month)
df['año'] = df['Fecha'].apply(lambda x: x.year)
df['dia'] = df['Fecha'].apply(lambda x: x.day)
df['DiaSemana'] = df['Fecha'].apply(lambda x: x.weekday())
df = df.drop(columns ='Fecha')
return df
df_random = periodos(df)
df_random.head()
CantidadVentas | Mes | año | dia | DiaSemana | |
---|---|---|---|---|---|
0 | 4 | 1 | 2019 | 1 | 1 |
1 | 103 | 1 | 2019 | 2 | 2 |
2 | 103 | 1 | 2019 | 3 | 3 |
3 | 80 | 1 | 2019 | 4 | 4 |
4 | 29 | 1 | 2019 | 5 | 5 |
La columna "Fecha" del dataset fue remplazada por el mes, año, día y día de la semana, lo que permite construir un modelo en base a estas columnas. Con esta transformación, el vector objetivo del modelo será la columna "CantidadVentas" y las demás columnas, serán las features. Junto con esto, el modelo de RandomForest también fue optimizado, usando una grilla de parámetros para poder encontrar la mejor solución que se adapte a los datos.
#HyperParametrosModelo
# Randomized Search CV
# Number of trees in random forest
n_estimators = [int(x) for x in np.linspace(start = 100, stop = 1200, num = 12)]
# Number of features to consider at every split
max_features = ['auto', 'sqrt']
# Maximum number of levels in tree
max_depth = [int(x) for x in np.linspace(5, 30, num = 6)]
# max_depth.append(None)
# Minimum number of samples required to split a node
min_samples_split = [2, 5, 10, 15, 100]
# Minimum number of samples required at each leaf node
min_samples_leaf = [1, 2, 5, 10]
# Method of selecting samples for training each tree
# bootstrap = [True, False]
# Create the random grid
random_grid = {'n_estimators': n_estimators,
'max_features': max_features,
'max_depth': max_depth,
'min_samples_split': min_samples_split,
'min_samples_leaf': min_samples_leaf}
print(random_grid)
{'n_estimators': [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200], 'max_features': ['auto', 'sqrt'], 'max_depth': [5, 10, 15, 20, 25, 30], 'min_samples_split': [2, 5, 10, 15, 100], 'min_samples_leaf': [1, 2, 5, 10]}
df_random_train = df.loc[(df['Fecha'] >= '2019-01-01') & (df['Fecha'] <= '2021-01-03')]
df_random_val = df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]
df_random_train = periodos(df_random_train)
df_random_val = periodos(df_random_val)
features = df_random_train[['Mes','año','dia','DiaSemana']]
labels = df_random_train[['CantidadVentas']]
feature_list = list(features.columns)
train_features, test_features, train_labels, test_labels = train_test_split(features, labels, test_size = 0.10, random_state = 42)
rf = RandomForestRegressor()
# Random search of parameters, using 3 fold cross validation,
# search across 100 different combinations, and use all available cores
rf = RandomizedSearchCV(estimator = rf, param_distributions = random_grid, n_iter = 100, cv = 3, verbose=2, random_state=42, n_jobs = -1)
# Fit the random search model
rf.fit(train_features, train_labels)
Fitting 3 folds for each of 100 candidates, totalling 300 fits
<ipython-input-166-9f767321d20b>:3: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df['Mes'] = df['Fecha'].apply(lambda x: x.month) <ipython-input-166-9f767321d20b>:4: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df['año'] = df['Fecha'].apply(lambda x: x.year) <ipython-input-166-9f767321d20b>:5: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df['dia'] = df['Fecha'].apply(lambda x: x.day) <ipython-input-166-9f767321d20b>:6: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df['DiaSemana'] = df['Fecha'].apply(lambda x: x.weekday()) [Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers. [Parallel(n_jobs=-1)]: Done 25 tasks | elapsed: 4.3s [Parallel(n_jobs=-1)]: Done 146 tasks | elapsed: 26.0s [Parallel(n_jobs=-1)]: Done 300 out of 300 | elapsed: 57.8s finished /home/foco/anaconda3/lib/python3.8/site-packages/sklearn/model_selection/_search.py:765: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples,), for example using ravel(). self.best_estimator_.fit(X, y, **fit_params)
RandomizedSearchCV(cv=3, estimator=RandomForestRegressor(), n_iter=100, n_jobs=-1, param_distributions={'max_depth': [5, 10, 15, 20, 25, 30], 'max_features': ['auto', 'sqrt'], 'min_samples_leaf': [1, 2, 5, 10], 'min_samples_split': [2, 5, 10, 15, 100], 'n_estimators': [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200]}, random_state=42, verbose=2)
predicted = rf.predict(test_features)
print('Test')
print("R2 (explained variance):", round(r2_score(test_labels, predicted), 2))
print("Mean Absolute Perc Error (Σ(|y-pred|/y)/n):", round(np.mean(np.abs((np.array(test_labels)-predicted)/predicted)), 2))
print("Mean Absolute Error (Σ|y-pred|/n):", "{:,.0f}".format(mean_absolute_error(test_labels, predicted)))
print("Root Mean Squared Error (sqrt(Σ(y-pred)^2/n)):", "{:,.0f}".format(np.sqrt(mean_squared_error(test_labels, predicted))))
predicted = rf.predict(train_features)
print('Train')
print("R2 (explained variance):", round(r2_score(train_labels, predicted), 2))
print("Mean Absolute Perc Error (Σ(|y-pred|/y)/n):", round(np.mean(np.abs((np.array(train_labels)-predicted)/predicted)), 2))
print("Mean Absolute Error (Σ|y-pred|/n):", "{:,.0f}".format(mean_absolute_error(train_labels, predicted)))
print("Root Mean Squared Error (sqrt(Σ(y-pred)^2/n)):", "{:,.0f}".format(np.sqrt(mean_squared_error(train_labels, predicted))))
predicted = rf.predict(df_random_val[['Mes','año','dia','DiaSemana']])
print('Validacion')
print('\n')
print("R2 (Explicacion de la Varianza):", round(r2_score(df_random_val['CantidadVentas'], predicted), 2))
print("Porcentaje Error Absoluto Medio (Σ(|y-pred|/y)/n):", round(np.mean(np.abs((np.array(df_random_val['CantidadVentas'])-predicted)/predicted)), 2))
print("Error Absoluto Medio (Σ|y-pred|/n):", "{:,.0f}".format(mean_absolute_error(df_random_val['CantidadVentas'], predicted)))
print("Error Cuadratico Medio (sqrt(Σ(y-pred)^2/n)):", "{:,.0f}".format(np.sqrt(mean_squared_error(df_random_val['CantidadVentas'], predicted))))
Test R2 (explained variance): 0.85 Mean Absolute Perc Error (Σ(|y-pred|/y)/n): 1.12 Mean Absolute Error (Σ|y-pred|/n): 31 Root Mean Squared Error (sqrt(Σ(y-pred)^2/n)): 48 Train R2 (explained variance): 0.98 Mean Absolute Perc Error (Σ(|y-pred|/y)/n): 1.15 Mean Absolute Error (Σ|y-pred|/n): 12 Root Mean Squared Error (sqrt(Σ(y-pred)^2/n)): 19 Validacion R2 (Explicacion de la Varianza): 0.62 Porcentaje Error Absoluto Medio (Σ(|y-pred|/y)/n): 0.24 Error Absoluto Medio (Σ|y-pred|/n): 41 Error Cuadratico Medio (sqrt(Σ(y-pred)^2/n)): 47
print('DataFrame de Validacion:')
print('\n')
print("R2 (Explicacion de la Varianza):", round(r2_score(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion['yhat']), 2))
print("Porcentaje Error Absoluto Medio (Σ(|y-pred|/y)/n):", round(np.mean(np.abs((np.array(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'])-forescast_comparacion['yhat'])/forescast_comparacion['yhat'])), 2))
print("Error Absoluto Medio (Σ|y-pred|/n):", "{:,.0f}".format(mean_absolute_error(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion['yhat'])))
print("Error Cuadratico Medio (sqrt(Σ(y-pred)^2/n)):", "{:,.0f}".format(np.sqrt(mean_squared_error(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion['yhat']))))
predicted = rf.predict(df_random_val[['Mes','año','dia','DiaSemana']])
plt.figure(figsize=(10,6))
plt.plot(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['Fecha'], predicted , '-o', label ='ForestCast')
plt.plot(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['Fecha'], df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], '-o', label ='Real')
plt.title('Cantidad de Ventas de artículos electrónicos en la semana de validación',fontsize = 17)
plt.xlabel('Dia del Año')
plt.ylabel('Cantidad de Ventas')
sns.despine(left=True, bottom=True)
plt.legend()
<matplotlib.legend.Legend at 0x7f56feedc7f0>
El modelo de RandomForest optimizado entrego peores métricas de rendimiento que los modelos anteriores, tanto como el R2 y el error cuadrático medio, Junto con esto, el grafico también muestra que hay una diferencia importante entre el valor predico y real para la semana de evaluación.
De todos modos, esto no quiere decir que estos modelos no se puedan usar para este tipo de problemas, siempre va a depender de los datos, por lo que, en el futuro no se debería descartar esta alternativa para comparar las predicciones.
Ya hemos visto todos los modelos por separado, pero me parece que la mejor forma de poder comparar los modelos seria mostrar todos los resultados en un mismo gráfico, de la siguiente manera:
Prophet Optimizado
final_model = Prophet(changepoint_prior_scale= 0.5,
holidays_prior_scale = 0.5,
n_changepoints = 200,
seasonality_mode = 'additive',
weekly_seasonality=True,
daily_seasonality = True,
interval_width=0.95)
final_model.fit(df_prophet_optimazado)
future = final_model.make_future_dataframe(periods=7)
forecast = final_model.predict(future)
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']]
forescast_comparacion_prophet_op = forecast[['ds','yhat', 'yhat_lower', 'yhat_upper']]
forescast_comparacion_prophet_op = forescast_comparacion_prophet_op.loc[(forescast_comparacion_prophet_op['ds'] >= '2021-01-04') & (forescast_comparacion_prophet_op['ds'] <= '2021-01-10')]
Prophet Normal
df_prophet = df.loc[(df['Fecha'] >='2019-01-01') & (df['Fecha'] <= '2021-01-03')]
df_prophet = df_prophet.rename(columns= {'Fecha': 'ds' , 'CantidadVentas': 'y' } )
modelo = Prophet()
modelo.fit(df_prophet)
future = modelo.make_future_dataframe(periods=7)
forecast = modelo.predict(future)
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']]
forescast_comparacion_prophet = forecast[['ds','yhat', 'yhat_lower', 'yhat_upper']]
forescast_comparacion_prophet = forescast_comparacion_prophet.loc[(forescast_comparacion_prophet['ds'] >= '2021-01-04') & (forescast_comparacion_prophet['ds'] <= '2021-01-10')]
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
Random¨Forest
predicted = rf.predict(df_random_val[['Mes','año','dia','DiaSemana']])
plt.figure(figsize=(10,6))
#RandomoFores
plt.plot(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['Fecha'], predicted , '-o', label ='ForestCast RandomForest')
#ProphetOptimizado
plt.plot(forescast_comparacion_prophet_op['ds'], forescast_comparacion_prophet_op['yhat'], '-o', label ='ForestCast Prophet Optimizado')
#ProphetOptimizado
plt.plot(forescast_comparacion_prophet['ds'], forescast_comparacion_prophet['yhat'], '-o', label ='ForestCast Prophet')
#DatosReales
plt.plot(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['Fecha'], df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], '-o', label ='Real')
plt.title('Cantidad de Ventas de artículos electrónicos en la semana de validación',fontsize = 17)
plt.xlabel('Dia del Año')
plt.ylabel('Cantidad de Ventas')
sns.despine(left=True, bottom=True)
plt.legend()
<matplotlib.legend.Legend at 0x7f56ff89f3d0>
print("R2 (Explicacion de la Varianza):", round(r2_score(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion['yhat']), 2))
print("Porcentaje Error Absoluto Medio (Σ(|y-pred|/y)/n):", round(np.mean(np.abs((np.array(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'])-forescast_comparacion['yhat'])/forescast_comparacion['yhat'])), 2))
print("Error Absoluto Medio (Σ|y-pred|/n):", "{:,.0f}".format(mean_absolute_error(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion['yhat'])))
print("Error Cuadratico Medio (sqrt(Σ(y-pred)^2/n)):", "{:,.0f}".format(np.sqrt(mean_squared_error(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion['yhat']))))
predicted = rf.predict(df_random_val[['Mes','año','dia','DiaSemana']])
print('RandomForest:')
print("R2 (Explicacion de la Varianza):",round(r2_score(df_random_val['CantidadVentas'], predicted), 2))
print("Porcentaje Error Absoluto Medio (Σ(|y-pred|/y)/n):", round(np.mean(np.abs((np.array(df_random_val['CantidadVentas'])-predicted)/predicted)), 2))
print("Mean Absolute Error (Σ|y-pred|/n):", "{:,.0f}".format(mean_absolute_error(df_random_val['CantidadVentas'], predicted)))
print("Error Cuadratico Medio (sqrt(Σ(y-pred)^2/n)):", "{:,.0f}".format(np.sqrt(mean_squared_error(df_random_val['CantidadVentas'], predicted))))
print('\n')
print('Prophet Optimizado:')
print('\n')
print("R2 (Explicacion de la Varianza):", round(r2_score(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion_prophet_op['yhat']), 2))
print("Porcentaje Error Absoluto Medio (Σ(|y-pred|/y)/n):", round(np.mean(np.abs((np.array(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'])-forescast_comparacion_prophet_op['yhat'])/forescast_comparacion_prophet_op['yhat'])), 2))
print("Error Absoluto Medio (Σ|y-pred|/n):", "{:,.0f}".format(mean_absolute_error(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion_prophet_op['yhat'])))
print("Error Cuadratico Medio (sqrt(Σ(y-pred)^2/n)):", "{:,.0f}".format(np.sqrt(mean_squared_error(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion_prophet_op['yhat']))))
print('\n')
print('Prophet Normal:')
print('\n')
print("R2 (Explicacion de la Varianza):",round(r2_score(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion_prophet['yhat']), 2))
print("Porcentaje Error Absoluto Medio (Σ(|y-pred|/y)/n):", round(np.mean(np.abs((np.array(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'])-forescast_comparacion_prophet['yhat'])/forescast_comparacion_prophet['yhat'])), 2))
print("Error Absoluto Medio (Σ|y-pred|/n):", "{:,.0f}".format(mean_absolute_error(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion_prophet['yhat'])))
print("Error Cuadratico Medio (sqrt(Σ(y-pred)^2/n)):", "{:,.0f}".format(np.sqrt(mean_squared_error(df.loc[(df['Fecha'] >= '2021-01-04') & (df['Fecha'] <= '2021-01-10')]['CantidadVentas'], forescast_comparacion_prophet['yhat']))))
RandomForest: R2 (Explicacion de la Varianza): 0.62 Porcentaje Error Absoluto Medio (Σ(|y-pred|/y)/n): 0.24 Mean Absolute Error (Σ|y-pred|/n): 41 Error Cuadratico Medio (sqrt(Σ(y-pred)^2/n)): 47 Prophet Optimizado: R2 (Explicacion de la Varianza): 0.92 Porcentaje Error Absoluto Medio (Σ(|y-pred|/y)/n): 0.08 Error Absoluto Medio (Σ|y-pred|/n): 18 Error Cuadratico Medio (sqrt(Σ(y-pred)^2/n)): 22 Prophet Normal: R2 (Explicacion de la Varianza): 0.88 Porcentaje Error Absoluto Medio (Σ(|y-pred|/y)/n): 0.09 Error Absoluto Medio (Σ|y-pred|/n): 19 Error Cuadratico Medio (sqrt(Σ(y-pred)^2/n)): 27
Como se observa en el gráfico y metricas, el modelo que más se acerca a la realidad es el modelo de Prophet Optimizado, donde su r2 y error cuadrático medio es 0,92 y 22 respectivamente. El segundo modelo sería el Prophet por default, seguido por el RandomForest que no obtuvo buenas métricas.
Las principales conclusiones del articulo son:
El modelo de Prophet Optimizado es el mejor modelo que se adapta a los datos, logrando buenas métricas y resultados aparecidos a la realidad. Aunque el RandomForest no entrego buenos resultados, es interesante pensar que se pueden construir modelos de ML clásicos, para predecir valores en series de tiempo. Para la predicción solo se utilizó una semana de enero, siendo un caso puntual, ya que se podría trabajar en predecir todo el mes, lo que podría a llevar a mayores diferencias con la realidad.