Pipeline de Business Intelligence e Machine Learning para vendas de e-commerce, com foco em validação rigorosa de previsão e no relato honesto do resultado.
Projeto pessoal de portfólio. Começou a partir de um mini-projeto do curso SCTEC/SENAI e hoje é independente da avaliação acadêmica.
A versão original do curso está somente na tag
v0.1-senai(git show v0.1-senai:README.md).Sobre os dados: O dashboard e a EDA usam o Online Retail II (UCI, dados reais de e-commerce UK, 2009-2011, licença CC BY 4.0). A validação principal de ML usa o Olist (Kaggle, dados reais de e-commerce brasileiro). O dataset sintético (
generate.py) continua disponível para testes, mas não é o padrão do dashboard.
Nota sobre o Olist: O dataset Olist original não está versionado neste repositório (ignorado pelo
.gitignorepara evitar inflar o repo com ~45MB). O resultado da validação temporal (incluindo a Dobra 3 com MAPE 39,92%) está preservado emoutputs/validate_v6_ratio_results.csv. Para reproduzir, baixe o Olist do Kaggle, coloque emdata/raw/olist/e rodepython3 src/data/process_olist.pyseguido depython3 src/models/validate_v6.py.
Origem: este projeto começou como o mini-projeto avaliativo do curso SCTEC/SENAI (Módulo 1, Semana 08), entregue em Python puro (biblioteca padrão), preservado em
salesinsight-py. A evolução para o portfólio pessoal (Pandas, Streamlit, LightGBM e Olist) foi feita depois da entrega e não faz parte da avaliação acadêmica.
O dataset Online Retail II não traz taxonomia nativa de categorias. Para permitir análise mercadológica, implementamos uma função de categorização em src/data/process_online_retail.py baseada em palavras-chave da coluna Description, com a seguinte ordem de prioridade:
- Natal:
CHRISTMAS,XMAS,ADVENT,SANTA,REINDEER - Bolsas:
BAG,TOTE,PURSE,BACKPACK,LUNCH BAG,SHOPPER - Cozinha/Mesa:
MUG,CUP,TEA,BOWL,PLATE,JUG,CAKESTAND,CUTLERY,KITCHEN - Iluminação:
LIGHT,LAMP,CANDLE,HOLDER,LANTERN,T-LIGHT - Papelaria:
CARD,PAPER,WRAP,NOTEBOOK,PEN,PENCIL,ENVELOPE - Armazenamento:
BOX,TIN,STORAGE,JAR,BASKET,CONTAINER - Joias:
JEWEL,NECKLACE,BRACELET,RING,EARRING,BROOCH - Brinquedos:
TOY,GAME,PLAY,DOLL,TEDDY,PUZZLE - Decoração:
HEART,HANGING,FRAME,SIGN,ORNAMENT,DOORSTOP,WALL ART - Outros: termos residuais.
Honestidade Radical: essa taxonomia é derivada, não oficial. Está documentada para que o leitor saiba exatamente como as categorias foram construídas.
Pergunta: o LightGBM supera baselines simples na previsão de receita semanal?
Resposta neste setup: não. O naive de 1 passo (último valor observado) foi igual ou melhor em todas as dobras.
Dados: Brazilian E-Commerce Public Dataset by Olist (Kaggle), 83 semanas úteis (2017-01-23 a 2018-08-20). Foram removidas 6 semanas truncadas (menos de 30% da mediana de pedidos). Os CSVs brutos não estão no Git; veja os termos de uso na página do dataset.
Validação: janela expansiva, 5 dobras, 12 semanas de teste (treino: 15, 27, 39, 51, 63 semanas). Avaliação one-step com lags observados (cada semana usa o valor real da semana anterior — comparável ao Naive1; não é forecast multi-step no escuro).
Features: lag_1…lag_4, roll_mean_4, roll_mean_8, roll_std_4 (sempre com shift(1) antes do rolling). Sem mes_num / semana_do_ano.
Hiperparâmetros (fixos): 200 árvores, learning_rate 0,08, 15 folhas, max_depth 4, min_child_samples 3, random_state 42.
| Modelo | MAPE médio | WAPE médio |
|---|---|---|
| LightGBM, alvo nível (v5c) | 26,46% | 27,00% |
| LightGBM, alvo razão y_t / y_(t-1) (v6) | 25,07% | 24,94% |
| Naive de 12 passos | 21,30% | 22,01% |
| Determinístico | 17,37% | 17,48% |
| Naive de 1 passo | 16,91% | 16,93% |
Vitórias do LightGBM contra o melhor baseline de cada dobra: 0/5 (nível e razão).
| Dobra | Treino (semanas) | LightGBM | Naive 12 | Naive 1 | Determinístico | Semanas com fator 1,0 |
|---|---|---|---|---|---|---|
| 1 | 15 | 18,40 | 24,83 | 15,63 | 15,63 | 12/12 |
| 2 | 27 | 17,69 | 26,97 | 17,68 | 18,34 | 11/12 |
| 3 | 39 | 39,92 | 20,84 | 17,62 | 14,00 | 9/12 |
| 4 | 51 | 20,02 | 13,46 | 15,96 | 16,38 | 0/12 |
| 5 | 63 | 23,37 | 20,42 | 17,66 | 22,51 | 0/12 |
- O LightGBM (nível) não supera o naive de 1 passo em nenhuma dobra. Na dobra 2 há empate técnico (~17,69% vs 17,68%); nas demais fica atrás. Mediana do MAPE: 20,02% vs 17,62%.
- O baseline determinístico não melhora o naive de forma consistente.
- O MAPE 39,92% na dobra 3 é uma anomalia documentada (a dobra mais alta, mas o Naive1 teve o melhor desempenho nela — o modelo superajustou um período de baixa variância).
- Frase honesta para portfólio/entrevista: "Com ~80 semanas e só lags/rolling, a persistência de 1 passo já captura quase todo o sinal previsível; o LightGBM não encontrou estrutura extra e aumentou o erro — por isso o baseline venceu em 5/5 dobras."
- O naive é forte demais neste problema. Em vendas semanais curtas, a correlação com a semana anterior é alta. O erro do naive já fica em torno de 17% MAPE.
- Poucos dados (fome de amostra). Com 15–39 semanas no início do teste, árvores não encontram padrões robustos e tendem ao overfitting.
- Features quase iguais ao naive.
lag_1é a feature mais forte; as outras lags/rolling são correlacionadas. O boosting ou se comporta parecido com o naive, ou aprende padrões que só existiam no treino. - Regime que muda (2017). A série cresceu muito. O que valia no começo da série pode não valer no fim. O naive se adapta na hora (usa o nível atual); o LightGBM aprende regras no passado.
- Sem sinal externo permitido. Bloqueamos calendário (
mes_num,semana_do_ano) para evitar vazamento. Sem feriados, promoções, preço, o que sobra é a própria série.
- Naive de 1 passo: previsão em t = valor observado em t−1.
- Naive de 12 passos: último valor do treino repetido nas 12 semanas de teste (não se atualiza; mantido por transparência histórica).
- Determinístico: lag_1 × fator médio mensal (razões y_t/y_(t−1) no treino); fator = 1,0 se o mês não aparece no treino (32/60 semanas de teste).
Artefatos: outputs/validate_v5c_corrigida_results.csv, outputs/validate_v6_ratio_results.csv.
| Versão | Dados | Alvo | Features | MAPE LightGBM |
|---|---|---|---|---|
| v1 | Sintético (46 meses) | Nível | 12 (com ano/trimestre) | 9,99% |
| v2 | Sintético | Nível | 10 | 10,53% |
| v3 | Sintético | Razão | ver CSV em outputs/ |
— |
| v4 | Olist | Nível | 9 (com calendário) | 26,42% |
| v5a | Olist | Nível | 8 | 26,43% |
| v5b / v5c | Olist | Nível | 7 (só lags/rolling) | 26,46% |
| v6 | Olist | Razão | 7 | 25,07% |
A v5c = mesmo modelo da v5b + Naive1 + contagem de fallback do fator sazonal.
- Dashboard e EDA usam dados sintéticos.
- Série Olist curta (~1,6 ano) e com crescimento rápido em 2017.
- Dobras com treinos sobrepostos (não independentes).
- Fatores sazonais mensais com poucas observações.
- Um dataset, granularidade semanal apenas.
- Sem CI/testes automatizados neste momento.
Ambiente de referência: Python 3.14 (WSL2), pandas 3.0.6, numpy 2.5.4, lightgbm 4.7.0, streamlit 1.66.0.
git clone https://github.com/deegpnini/ARC.BI.Sales.Insight.git
cd ARC.BI.Sales.Insight
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt- Baixe o dataset público Brazilian E-Commerce Public Dataset by Olist no Kaggle.
- Coloque os CSVs em
data/raw/. - Rode o ETL para gerar
data/processed/:
python src/data/process_olist.pyO resultado documentado no topo deste README (LightGBM perdeu para Naive1 em 5/5 dobras) é gerado por:
python src/models/validate_v6.pySaída esperada: outputs/validate_v6_ratio_results.csv com colunas mape_model, mape_naive1, wape_model, wape_naive1, fold. As 5 dobras com MAPE do modelo acima do Naive1 confirmam a derrota do ML.
streamlit run src/visualization/dashboard.pyO dashboard usa dados sintéticos (não Olist) — isso está avisado no próprio app.
Dashboard interativo com filtros de período, região e categoria. Screenshot gerado automaticamente via src/visualization/capture_screenshot.py (Playwright).
python src/visualization/explore.pyGera os 7 PNGs em outputs/ (sazonalidade, top produtos, regiões, correlação, segmentação, evolução, forecast).