Este problema foi retirado de uma competição no site Kaggle.com, proposta a sete anos atrás. O Kaggle é um site do Grupo Google especializado em competições sobre análise e predição de dados, inclusive com premiações em dinheiro para a melhor predição (é necessario criar uma conta para entrar).
Descrição do projeto
A Rossmann é uma CIA farmacêutica que opera mais de 3.000 drogarias em 7 países europeus. Atualmente, os gerentes de loja da Rossmann têm a tarefa de prever suas vendas diárias com até seis semanas de antecedência.
As vendas da loja são influenciadas por muitos fatores, incluindo promoções, concorrentes, feriados escolares e estaduais, sazonalidade e localidade.
Com milhares de gerentes individuais prevendo vendas com base em suas circunstâncias únicas, a precisão dos resultados pode ser bastante variada.

Descrição do conjunto de dados
Os dados são os históricos de vendas de 1.115 lojas Rossmann.
A tarefa é prever a coluna “Vendas” para o conjunto de teste.
Observe que algumas lojas no conjunto de dados foram temporariamente fechadas para reforma
Predições de vendas das lojas Rossmann
Contexto
Em uma das reuniões mensais o CFO pediu uma previsão de vendas das próximas seis semanas de cada loja.
Desafio
A necessidade desta informação está vinculada ao orçamento para efetuar reformas nas lojas.
O cenário atual das previsões nas lojas são:
- A predição de vendas atual apresenta muitas divergências.
- O processo de Predição de Vendas é baseado em experiências passadas.
- Toda a Previsão de vendas é feita manualmente pelas 1.115 lojas.
- A visualização das vendas é limitada ao computador.
Solução
Usar Machine Learning para realizar a previsão de vendas de todas as lojas individualmente.
Visualização das predições de vendas em um navegador da web, acessado por um Smartphone.
Descrição dos Dados
print( 'Number of Rows: {}'. format( df1.shape[0]))
print( 'Number of Cols: {}'. format( df1.shape[1]))
Number of Rows: 1.017.209 (Número de linhas)
Number of Cols: 18 (Número de colunas)
Estátistica Descritiva
# dividir os atributos em numéricos e categórigos (objets)
num_attributes = df1.select_dtypes( include = ['int64', 'float64'])
cat_attributes = df1.select_dtypes( exclude = ['int', 'float','datetime64[ns]'])
# Central Tendency - mean, median
ct1 = pd.DataFrame(num_attributes.apply(np.mean)).T # ct1 = central tendency 1
ct2 = pd.DataFrame(num_attributes.apply(np.median)).T
# Dispersion - std, min, max, range, skew, kurtosis
d1 = pd.DataFrame(num_attributes.apply( np.std )).T # d1 = dispersion tendency 1
d2 = pd.DataFrame(num_attributes.apply( min )).T
d3 = pd.DataFrame(num_attributes.apply( max )).T
d4 = pd.DataFrame(num_attributes.apply( lambda x: x.max() - x.min() )).T
d5 = pd.DataFrame(num_attributes.apply( lambda x: x.skew() )).T
d6 = pd.DataFrame(num_attributes.apply( lambda x: x.kurtosis() )).T
# concatenate
m = pd.concat([d2, d3, d4, ct1, ct2, d1, d5, d6 ]).T.reset_index() # m = metricas
m.columns = ['atributes','min','max','range','mean','median','std','skew','kurtosis']
m

Podemos observar na linha ‘customers’ (clientes), que chegamos a ter um número máximo de 7.388 clientes por dia em uma loja e uma média de 633.14.
Estas features (colunas) podem revelar informações relevantes, para começar a analisa-las vamos elaborar algumas hipóteses.
Para podermos explorar os dados de maneira mais orientada vamos criar um
Mapa Mental de Hipóteses
Mapa criado no site coggle.it

Esse mapa descreve no centro o fenômeno que eu quero modelar e as ramificações são os impactos sobre as vendas.
Ele serve para tirarmos algumas hipóteses sobre as vendas.
Hipóteses da Analise Exploratória de Dados
1 – Lojas com maior sortimento deveriam vender mais.
2 – Lojas com competidores mais próximos deveriam vender menos.
3 – Lojas com competidores a mais tempo deveriam vender mais.
4 – Lojas com promoções ativas por mais tempo deveriam vender mais.
5 – Lojas com mais dias de promoção deveriam vender mais.
6 – Lojas com mais promoções deveriam vender mais.
7 – Lojas abertas durante o feriado de Natal deveriam vender mais.
8 – Lojas deveriam vender mais ao longo dos anos.
9 – Lojas deveriam vender mais no segundo semestre do ano.
10 – Lojas deveriam vender mais depois do dia 10 de cada mês.
11 – Lojas deveriam vender menos aos finais de semana.
12 -Lojas deveriam vender menos durante os feriados escolares.
Analise Exploratória dos Dados
Response Variable
sns.displot(df1['sales']);

Volume de vendas por dia, temos uma distribuição muito próxima da normal, o que vai facilitar a aprendizagem do algoritmo.
Numerical Variable
df4 = df1[(df1['sales'] > 0)]
# dividir os atributos em numéricos e categórigos (objets)
num_attributes = df4.select_dtypes( include = ['int64', 'float64'])
cat_attributes = df4.select_dtypes( exclude = ['int', 'float','datetime64[ns]'])
num_attributes.hist(bins=5);

Olhando o primeiro gráfico podemos ver que os concorrentes ficam bem próximo. No segundo temos um pico em meses específicos de abertura de lojas concorrentes.
Categorical Variable
# state_holiday (Primeiro grafico)
plt.subplot(1, 2, 1)
a = df4[df4['state_holiday'] != 'regular_day']
sns.countplot(a['state_holiday'])
plt.subplot(1, 2, 2)
sns.kdeplot( df4[df4['state_holiday']== 'public_holiday']['sales'], label='public_holiday', shade=True)
sns.kdeplot( df4[df4['state_holiday']== 'easter_holiday']['sales'], label='easter_holiday', shade=True)
sns.kdeplot( df4[df4['state_holiday']== 'christmas']['sales'], label='christmas', shade=True);
# store_type (Segundo grafico)
plt.subplot( 3, 2, 3 )
sns.countplot( df4['store_type'] )
plt.subplot( 3, 2, 4 )
sns.kdeplot( df4[df4['store_type'] == 'a']['sales'], label='a', shade=True )
sns.kdeplot( df4[df4['store_type'] == 'b']['sales'], label='b', shade=True )
sns.kdeplot( df4[df4['store_type'] == 'c']['sales'], label='c', shade=True )
sns.kdeplot( df4[df4['store_type'] == 'd']['sales'], label='d', shade=True )
#assortment (Terceiro grafico)
plt.subplot( 3, 2, 5 )
sns.countplot( df4['assortment'] )
plt.subplot( 3, 2, 6 )
sns.kdeplot( df4[df4['assortment'] == 'extended']['sales'], label='extended', shade=True )
sns.kdeplot( df4[df4['assortment'] == 'basic']['sales'], label='basic', shade=True )
sns.kdeplot( df4[df4['assortment'] == 'extra']['sales'], label='extra', shade=True )

O primeiro gráfico mostra as vendas em 3 feriados, públicos, pascoa e natal;
O segundo as diferencia em quatro modelos, lojas A, B, C, D;
E o terceiro classifica a quantidade de produtos diferentes (sortimento).
Validação das Hipóteses
1 – Lojas com maior sortimento deveriam vender mais.
FALSA – Lojas com maior sortimento vendem menos.
aux1 =df4[['assortment','sales']].groupby('assortment').sum().reset_index()
sns.barplot(x='assortment', y='sales', data= aux1);
aux2 =df4[['year_week','assortment','sales']].groupby(['year_week','assortment']).sum().reset_index()
aux2.pivot(index='year_week', columns='assortment', values='sales').plot()
aux3 =aux2[aux2['assortment']=='extra']
aux3.pivot(index='year_week', columns='assortment', values='sales').plot();


Isso é bom saber pois não é necessário grande sortimento para aumentar as vendas, produtos mais específicos vendem mais.
2 – Lojas com competidores mais próximos deveriam vender menos
FALSA – Lojas com competidores mais próximos vendem mais.
aux1 = df4[['competition_distance','sales']].groupby('competition_distance').sum().reset_index()
bins = list(np.arange(0,20000,1000))
aux1['competition_distance_binned'] = pd.cut(aux1['competition_distance'],bins=bins)
aux2 = aux1[['competition_distance_binned','sales']].groupby('competition_distance_binned').sum().reset_index()
sns.barplot(x='competition_distance_binned', y='sales', data=aux2);
aux1 = df4[['competition_distance','sales']].groupby('competition_distance').sum().reset_index()
plt.subplot(1, 2, 1)
sns.scatterplot( x ='competition_distance', y='sales' , data=aux1);
plt.subplot(1, 2, 2)
sns.heatmap (aux1.corr(method = 'pearson'), annot=True);
plt.xticks(rotation=45); # para rotacionar os numeros da linha x

O primeiro gráfico mostra a distância entre as lojas concorrentes, da para reparar que estão bem próximos.
O segundo mostra as lojas agrupadas de 10 em 10kms.
E o terceiro mostra a correlação entre as vendas e a distancia dos competidores (Quanto maior a distância menos vendas). Não é uma correlaçao forte mais nos mostra uma tendência.
7 – Lojas abertas durante o feriado de Natal deveriam vender mais.
FALSA Lojas abertas durante o feriado de Natal vendem menos.
aux = df4[df4['state_holiday'] != 'regular_day']
plt.subplot(1,2,1)
aux1 = aux[['state_holiday', 'sales']].groupby('state_holiday').sum().reset_index()
sns.barplot( x= 'state_holiday', y= 'sales', data=aux1);
plt.subplot(1,2,2)
aux2 = aux[['year', 'state_holiday', 'sales']].groupby(['year','state_holiday']).sum().reset_index()
sns.barplot( x= 'year', y = 'sales', hue = 'state_holiday', data=aux2);
# hue = tipos de barra,a cada state_holiday tenho uma barra

Olhando as vendas nos feriados, podemos ver claramente a não validação dessa hipótese, é só olhar a coluna ‘christmas’ (azul) e comparar com as outras ( Páscoa (marrom) e feriados públicos (verde) ).
10 – Lojas deveriam vender mais depois do dia 10 de cada mês.
VERDADEIRA.
aux1 = df4[['day','sales']].groupby('day').sum().reset_index()
plt.subplot(2,2,1)
sns.barplot(x='day', y='sales', data=aux1 );
plt.subplot(2,2,2)
sns.regplot(x ='day', y='sales', x_ci=int, data=aux1 );
plt.xticks(rotation = 45);
plt.subplot(2,2,3)
sns.heatmap(aux1.corr(method='pearson'), annot=True);
aux1['before_after'] = aux1['day'].apply(lambda x: 'before_10_days' if x <= 10 else 'after_10_days')
aux2 = aux1[['before_after', 'sales']].groupby('before_after').sum().reset_index()
plt.subplot(2,2,4)
sns.barplot( x='before_after', y='sales', data=aux2);

O primeiro gráfico mostra as vendas por dia e gráfico ao lado mostra a tendência no decréscimo destas vendas.
Nos gráficos abaixo estão a correlação de vendas pelos dias do mês, que também possuem uma correlação negativa próxima de zero significando que quanto mais próximo do final do mês as vendas caem e finalmente o último gráfico deixa bem claro isto.
Resumo das Hipóteses
tab = [['Hipoteses', 'Conclusão', 'Relevância'],
['H1', 'Falsa','Baixa'],
['H2', 'Falsa','Média'],
['H3', 'Falsa','Média'],
['H4', 'Falsa','Baixa'],
['H5', '-','-'],
['H7', 'Falsa','Baixa'],
['H8', 'Falsa','Média'],
['H9', 'Falsa','Alta'],
['H10', 'Falsa','Alta'],
['H11', 'Verdadeira','Alta'],
['H12', 'Verdadeira','Alta'],
['H13', 'Verdadeira','Baixa'],
]
print ( tabulate (tab, headers = 'firstrow'))
Hipoteses Conclusão Relevância ----------- ----------- ------------ H1 Falsa Baixa H2 Falsa Média H3 Falsa Média H4 Falsa Baixa H5 - - H7 Falsa Baixa H8 Falsa Média H9 Falsa Alta H10 Falsa Alta H11 Verdadeira Alta H12 Verdadeira Alta H13 Verdadeira Baixa
Relevância significa importância da hipótese para o aprendizado do algoritmos de Machine Learn.
Analise Multivariada
É dividida em duas partes, atributos Numéricos e atributos Variáveis.
Atributos Numéricos
correlation = num_attributes.corr(method = 'pearson')
sns.heatmap(correlation, annot = True);

Atributos Categóricos
sns.heatmap(d, annot = True);

Machine Learning Modelling
Compare Model’s Performance

Folram utilizados 4 modelos de ML descritos acima em Model Name.
O modelo escolhido foi o XGBoost Regressor, principalmente por sua rapidez de processamento.
Os resultados do Random Foreste foram um pouco melhores, porém levaria um longo tempo para serem processados.
Conclusão & Demonstração
Tradução e interpretação do erro
Business Performance
# model
model_xgb_tuned = xgb.XGBRegressor(objective = 'reg:squarederror',
n_estimators = param_tuned['n_estimator'],
eta = param_tuned['eta'],
max_depth=param_tuned['max_depth'],
subsample=param_tuned['subsample'],
colsample_bytree= param_tuned['colsample_bytree'],
min_child_weight= param_tuned['min_child_weight'] ).fit (x_train, y_train)
# prediction
yhat_xgb_tuned = model_xgb_tuned.predict( x_test )
# performance
xgb_result_tuned = ml_error ('XGBoost Regressor',(y_test) , (yhat_xgb_tuned))
xgb_result_tuned
df1.sort_values( 'MAPE', ascending=False ).head()

Traduzindo a tabela acima, com base nas quatro primeiras colunas, podemos dizer:
A loja 1041 ira vender um valor de $ 250.928,16 nas próximas seis semanas, com a probabilidade de, na melhor das hipóteses, vender $ 251,655,37 ou na piror vender $ 250.200,94.
A loja 113 ira vender um valor de $ 278.431,03 nas próximas seis semanas, com a probabilidade de, na melhor das hipóteses, vender $ 280.144,54 ou na piror vender $ 276.717,52, e assim por diante.

Aqui temos um gráfico de erros em relação as lojas, temos alguns outlines, nestes pontos temos que olhar de perto a predição, pois são pontos problemáticos de predição, é aconselhável fazer um modelo diferente para sua previsão.
Total Performance
df93 = df92[['predictions', 'worst_scenario', 'best_scenario']].apply(lambda x: np.sum(x), axis=0).reset_index().rename( columns ={'index':'scenario', 0: 'values'})
df93['values'] = df93['values'].map('R$ {:,.2f}'.format)
df93

A tabela acima mostra a previsão total de vendas contando todas as lojas.
Machine Learning Performance
df9['error'] = df9 ['sales'] - df9 ['predictions']
df9['error_rate'] = df9['predictions'] / df9['sales']
plt.rcParams['figure.figsize'] = (20,12)
plt.subplot(2,2,1)
sns.lineplot ( x = 'date', y = 'sales', data=df9, label = 'SALES')
sns.lineplot ( x = 'date', y = 'predictions', data=df9, label = 'PREDICTIONS')
plt.subplot(2,2,2)
sns.lineplot ( x = 'date', y = 'error_rate', data=df9)
plt.axhline(1, linestyle = '--')
plt.subplot(2,2,3)
sns.distplot (df9['error'])
plt.subplot(2,2,4)
sns.scatterplot (df9['predictions'], df9['error']);

O primeito gráfico mostra a relação das vendas com as predições, da para ver que estão bem próximas.
O segundo ao lado mostra uma linha de erro, os valores acima mostram uma super avaliação e abaixo da linha uma sub avaliação.
O da segunda linha mostra uma variação do erro próximo da normal, e o gráfico ao lado mostra uma distribuição dos resultados das predições da ML, sempre o ideal é que os pontos fiquem dentro de um tubo imaginário, o que está acontecendo.
Próximos Passos
- Workshop do Modelo para os Business Users.
- Coletar Feedbacks sobre a usabilidade.
- Aumentar em 10% a acurácia do modelo.
Agradeço sua atenção!
Fique a vontade para deixar seu comentário via WhatsApp.