Modelo de Pronósticos para Ventas¶

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.

Importar Librerias¶

In [ ]:
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

Carga Datos¶

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:

In [ ]:
df = pd.read_csv('Datos.csv', sep =';')
df.head()
Out[ ]:
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
In [ ]:
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
In [ ]:
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))
In [ ]:
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

Visualizacion de Datos¶

Si gratificamos todos los datos del dataset, se obtiene lo siguiente:

In [ ]:
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:

In [ ]:
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)
In [ ]:
df.head()
Out[ ]:
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
In [ ]:
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.
In [ ]:
df_periodo
Out[ ]:
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
In [ ]:
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.

Modelo de ML¶

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.

Prophet Normal¶

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:

In [ ]:
df = pd.read_csv('Datos.csv', sep =';')
df['Fecha'] = df['Fecha'].apply(lambda x : funcion_fecha(x))
df.tail()
Out[ ]:
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
In [ ]:
df_prophet = df.loc[(df['Fecha'] >='2019-01-01') & (df['Fecha'] <= '2021-01-03')]
df_prophet = df_prophet.rename(columns= {'Fecha': 'ds' , 'CantidadVentas': 'y' } )
In [ ]:
modelo = Prophet()
modelo.fit(df_prophet)
INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
Out[ ]:
<fbprophet.forecaster.Prophet at 0x7f56ff107730>
In [ ]:
future = modelo.make_future_dataframe(periods=7)
forecast = modelo.predict(future)
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']]
forecast.tail()
Out[ ]:
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
In [ ]:
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()
Out[ ]:
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
In [ ]:
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)
In [ ]:
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.

Prophet Optimizado¶

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

In [ ]:
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()
Out[ ]:
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
In [ ]:
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
In [ ]:
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
In [ ]:
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
In [ ]:
parameters = model_parameters.sort_values(by=['MAPE'])
parameters = parameters.reset_index(drop=True)
parameters.head()
Out[ ]:
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...
In [ ]:
print('Mejor Parametros:')
parameters['Parameters'][0]
Mejor Parametros:
Out[ ]:
{'changepoint_prior_scale': 0.5,
 'holidays_prior_scale': 0.5,
 'n_changepoints': 200,
 'seasonality_mode': 'additive'}
In [ ]:
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)
Out[ ]:
<fbprophet.forecaster.Prophet at 0x7f56ffa182b0>
In [ ]:
future = final_model.make_future_dataframe(periods=7)
forecast = final_model.predict(future)
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']]
forecast.head()
Out[ ]:
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

In [ ]:
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()
Out[ ]:
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
In [ ]:
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()
Out[ ]:
<matplotlib.legend.Legend at 0x7f56fefe0b50>
In [ ]:
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.

Random Forest Optimizado¶

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:

In [ ]:
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
In [ ]:
df_random = periodos(df)
df_random.head()
Out[ ]:
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.

In [ ]:
#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]}
In [ ]:
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)
Out[ ]:
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)
In [ ]:
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
In [ ]:
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']))))
In [ ]:
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()
Out[ ]:
<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.

COMPARACION DE LOS TRES MODELO GRAFICO¶

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

In [ ]:
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

In [ ]:
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

In [ ]:
predicted = rf.predict(df_random_val[['Mes','año','dia','DiaSemana']])
In [ ]:
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()
Out[ ]:
<matplotlib.legend.Legend at 0x7f56ff89f3d0>
In [ ]:
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']))))
In [ ]:
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.

Conclusiones¶

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.