Episódio 3 — de 1.017.209 registros diários a 1.115 lojas com suas variáveis

A clusterização trabalha com uma linha por loja. A base chega com um milhão de linhas por dia de loja. Entre uma coisa e outra há seis armadilhas — e a mais perigosa não aparece como nulo, nem como zero. As linhas simplesmente não existem.

As armadilhas da base diária

  • Dias fechados zeram a média. 17% das linhas são de loja fechada. A média sobre todos os dias é exatamente a média em dia aberto × a proporção de dias abertos — ou seja, mede funcionamento, não capacidade de venda. Só por abrir aos domingos, uma loja sobe em média 108 posições no ranking.
  • Um buraco sistemático. 180 lojas não têm nenhuma linha de 01/07/2014 a 31/12/2014 — os mesmos 184 dias em todas. Não há nulo para o .isnull() encontrar.
  • Soma distorce, média não. No ranking por faturamento total, 98 das 180 lojas incompletas caem no pior quartil, quando o esperado seria 45 (p ≈ 10⁻²⁰). Pela média em dia aberto, caem exatamente 45 (p = 0,54).
  • Registro inconsistente. 54 linhas com loja aberta e faturamento zero, em 41 lojas.
  • Domingo é raro. Só 33 lojas abrem aos domingos — e 17 delas são de um mesmo tipo do cadastro.
  • Distância com cauda longa. A distância ao concorrente vai de 20 a 75.860, com skew de 2,93. As 5% lojas mais isoladas concentram 59,6% da variância.

Cada defeito virou uma regra de engenharia. As métricas de desempenho são calculadas só sobre dias de operação (aberta = 1 e faturamento > 0): 844.338 linhas das 1.017.209, com as 1.115 lojas presentes. E por causa do buraco, só entram médias, proporções e taxas — nunca somas.

O buraco distorce as features temporais?

Não dava para assumir. O teste: nas lojas completas, recalcular a feature removendo o 2º semestre de 2014 — exatamente o que as 180 incompletas sofrem — e medir quanto ela muda.

FeatureVariação mediana sem o períodoSpearman entre versõesConclusão
Volatilidade−0,2%0,994Não distorce: calculada no histórico inteiro
Sazonalidade+8,3%0,921Distorce: precisa de tratamento

A sazonalidade passou a ser calculada mês a mês dividida pela média do próprio ano da loja, o que neutraliza o semestre ausente.

Como a loja vende, não só quanto

Se o modelo recebesse só faturamento, os grupos seriam "lojas grandes, médias e pequenas" — e isso a rede já sabia. O valor está em capturar o comportamento. Nove candidatas foram construídas, em cinco blocos:

BlocoFeatureComo é calculada
Portevenda_media_diaFaturamento médio por dia de operação
Porteclientes_medios_diaClientes médios por dia de operação
Porteticket_medioVenda média ÷ clientes médios
Promoçãoefeito_promoVenda média com promoção ÷ sem promoção − 1
RitmovolatilidadeCoeficiente de variação da venda diária
RitmosazonalidadeCada mês ÷ média do próprio ano; desvio entre os 12 meses
Formatoprop_domingos_abertosDomingos abertos ÷ domingos registrados
Formatopico_sabadoVenda média de sábado ÷ venda média de segunda a sexta
Concorrênciadistancia_concorrenteCadastro; 3 nulos imputados pela mediana (2.325) e marcados

Uma observação sobre promoção: a coluna promo é o calendário da rede — cerca de 38% dos dias, igual para todas as lojas. Medir "% de dias em promoção" não diferencia ninguém. O que diferencia é a resposta à promoção: na média, a venda sobe 41,4% nos dias promocionais, numa faixa que vai de −6,9% a +144,9%.

Nove candidatas, seis no modelo

Matriz de correlação de Pearson entre as dez candidatas a feature, após log1p
Correlação entre as candidatas (após log1p). Os pares fortes decidiram quem sai.
  • Clientes saiu: venda = clientes × ticket é uma identidade exata. Manter os três contaria o porte duas vezes.
  • Pico de sábado saiu: correlação de −0,81 com o efeito da promoção, porque a promoção nunca ocorre no fim de semana — os dias "sem promoção" incluem os sábados.
  • Domingos abertos saiu: 97,0% das lojas estão em zero. Log1p, binarização, Yeo-Johnson e quantil deixam o skew entre 5,56 e 5,97. Nenhuma transformação cria variação onde há um valor só.
  • Efeito da promoção e volatilidade ficaram, juntos, mesmo com correlação de 0,81. Promoção interessa ao Trade; oscilação, ao Supply. O preço, assumido e registrado, é dar mais peso a essa dimensão.
Feature do modeloDimensãoSkew originalSkew após log1p
venda_media_diaPorte1,750,30
ticket_medioPadrão de compra0,28−0,30
efeito_promoResposta à promoção0,680,22
volatilidadeOscilação diária0,720,55
sazonalidadeVariação ao longo do ano2,201,86
distancia_concorrenteContexto competitivo2,93−0,35

As seis passam por log1p e depois por padronização (StandardScaler), para que uma feature em reais não engula uma feature em percentual. Matriz final: 1.115 × 6, sem nulos.

⚠️

Repare na sazonalidade: o skew continua alto depois do log1p (1,86). Na primeira versão do pipeline eu "corrigi" isso com log puro e zerei a assimetria. Foi o erro mais caro do projeto — a cauda era um grupo real de lojas. A conta está na parte 9.

🏪

Parte 3 de 9 da série 1.115 lojas, 1 régua injusta. O mapa completo da série reúne todas as partes, os cinco grupos e os números finais. O código, os notebooks e a documentação estão no repositório no GitHub ↗.