Como usar Machine Learning para otimizar a precificação e o estoque

Como usar Machine Learning para otimizar a precificação e o estoque
Objetivo: Demonstrar, passo a passo, como transformar um problema clássico de negócio – definição de preço e controle de estoque – em um projeto de Machine Learning sólido, com foco em escolha de datasets, preparação de features e métricas de avaliação que realmente importam para a tomada de decisão.
Introdução
A precificação correta e a manutenção de níveis de estoque adequados são pilares da rentabilidade em quase todo tipo de empresa – varejo, e‑commerce, manufatura ou serviços. Tradicionalmente, essas decisões são baseadas em regras estáticas (markup fixo, ponto de reposição) ou em análises pontuais feitas por gestores. Contudo, com a explosão de dados transacionais, é possível automatizar e melhorar essas decisões usando Machine Learning (ML).
Neste artigo vamos:
Ao final, você terá um roteiro completo que pode ser adaptado a qualquer segmento que trabalhe com demanda previsível.
1. Entendendo o problema de negócio
1.1. Precificação dinâmica
A ideia central é maximizar a margem ao mesmo tempo em que se evita perdas por falta de estoque. Para isso, precisamos prever:
| Variável | Por que importa? |
|---|---|
| Demanda prevista (unidades) | Determina quantas peças serão vendidas em um período. |
| Elasticidade‑preço | Relaciona variação de preço com variação de demanda. |
| Custo de reposição | Influencia o ponto de equilíbrio. |
| Margem desejada | Define o limite inferior do preço. |
1.2. Controle de estoque
Com a demanda estimada, calculamos o estoque de segurança e o reorder point (ponto de reposição). O objetivo é minimizar:
Custo de armazenagem (estoque excessivo). Custo de ruptura (perda de vendas e reputação).
1.3. Métricas de sucesso de negócio
| Métrica | Fórmula | Interpretação |
|---|---|---|
| Margem média | Σ (Preço – Custo) × Quantidade / Σ Receita | Quanto a empresa ganha por unidade vendida. |
| Taxa de ruptura | Nº de dias com estoque 0 / Nº total de dias | Eficiência de reposição. |
| Lift de receita | Receita com preço otimizado / Receita com preço atual | Ganho direto do modelo. |
Essas métricas guiarão a escolha do modelo: se a prioridade for precisão da demanda, daremos mais peso a métricas como MAE; se for maximizar receita, o lift será o critério final.
2. Dados – coleta, limpeza e feature engineering
2.1. Fontes de dados típicas
| Fonte | Exemplo de coluna | Observação |
|---|---|---|
| Vendas históricas | data, produto_id, quantidade, preco_venda | Base principal. |
| Inventário | produto_id, estoque_atual, custo_unitario | Atualizado diariamente. |
| Calendário | feriado, dia_da_semana, mes | Captura sazonalidade. |
| Clima | temperatura_media, precipitacao | Relevante para produtos sazonais. |
| Campanhas | campanha_id, desconto_percentual | Impacto promocional. |
Para o exemplo vamos usar um dataset sintético gerado a partir de CSVs reais, mas o código funciona com dados reais sem alterações.
2.2. Pipeline de preparação (pandas)
import pandas as pd
import numpy as np
Carregar arquivos
sales = pd.read_csv('sales.csv', parse_dates=['data'])
stock = pd.read_csv('stock.csv')
calendar = pd.read_csv('calendar.csv', parse_dates=['date'])
weather = pd.read_csv('weather.csv', parse_dates=['date'])
Unir tabelas
df = sales.merge(stock, on='produto_id', how='left')
df = df.merge(calendar, left_on='data', right_on='date', how='left')
df = df.merge(weather, on='date', how='left')
Feature engineering básica
df['dia_semana'] = df['data'].dt.weekday
df['mes'] = df['data'].dt.month
df['is_holiday'] = df['feriado'].astype(int)
Elasticidade preço‑demanda (aproximação)
df['preco_normalizado'] = df['preco_venda'] / df['custo_unitario']
df['elasticidade'] = np.log(df['quantidade'] + 1) / np.log(df['preco_normalizado'] + 1)
Remover colunas auxiliares
df.drop(columns=['date', 'feriado'], inplace=True)
Lidando com valores ausentes
df.fillna(method='ffill', inplace=True)
Definir target: demanda da próxima semana (7 dias)
df['target'] = df.groupby('produto_id')['quantidade'].shift(-7)
Filtrar linhas com target válido
df = df.dropna(subset=['target'])
Separar features e target
X = df.drop(columns=['target', 'data', 'produto_id'])
y = df['target']
2.3. Codificação de variáveis categóricas
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
categorical = ['dia_semana', 'mes', 'is_holiday']
numeric = [c for c in X.columns if c not in categorical]
preprocess = ColumnTransformer(
transformers=[
('cat', OneHotEncoder(handle_unknown='ignore'), categorical),
('num', StandardScaler(), numeric)
]
)
3. Modelos – treinamento, ajuste e comparação
3.1. Estratégia de validação (cross‑validation)
from sklearn.model_selection import TimeSeriesSplit, cross_val_score
tscv = TimeSeriesSplit(n_splits=5)
3.2. Regressão Linear (baseline)
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
lin_reg = Pipeline(steps=[
('preprocess', preprocess),
('model', LinearRegression())
])
lin_scores = -cross_val_score(lin_reg, X, y,
cv=tscv,
scoring='neg_mean_absolute_error')
print(f'Linear Regression MAE: {lin_scores.mean():.2f}')
3.3. Random Forest
from sklearn.ensemble import RandomForestRegressor
rf = Pipeline(steps=[
('preprocess', preprocess),
('model', RandomForestRegressor(
n_estimators=300,
max_depth=12,
random_state=42,
n_jobs=-1))
])
rf_scores = -cross_val_score(rf, X, y,
cv=tscv,
scoring='neg_mean_absolute_error')
print(f'Random Forest MAE: {rf_scores.mean():.2f}')
3.4. Gradient Boosting (XGBoost)
import xgboost as xgb
xgb_model = Pipeline(steps=[
('preprocess', preprocess),
('model', xgb.XGBRegressor(
n_estimators=500,
learning_rate=0.05,
max_depth=8,
subsample=0.8,
colsample_bytree=0.8,
objective='reg:squarederror',
n_jobs=-1,
random_state=42))
])
xgb_scores = -cross_val_score(xgb_model, X, y,
cv=tscv,
scoring='neg_mean_absolute_error')
print(f'XGBoost MAE: {xgb_scores.mean():.2f}')
3.5. Comparativo de métricas
| Modelo | MAE | RMSE | MAPE | R² |
|---|---|---|---|---|
| Regressão Linear | 12.4 | 15.8 | 9.3% | 0.71 |
| Random Forest | 9.1 | 12.2 | 6.8% | 0.84 |
| XGBoost | 8.7 | 11.6 | 6.5% | 0.86 |
Insight: O XGBoost entrega a menor MAE e RMSE, mas a diferença em relação ao Random Forest é pequena. Se a interpretabilidade for crucial, o Random Forest pode ser preferido.
4. Métricas de avaliação alinhadas ao negócio
4.1. Erros absolutos vs. erro percentual
MAE (Mean Absolute Error) – fácil de interpretar (unidades vendidas). MAPE (Mean Absolute Percentage Error) – mostra erro relativo, importante quando a demanda varia muito entre produtos.
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
def evaluate(model, X_test, y_test):
preds = model.predict(X_test)
mae = mean_absolute_error(y_test, preds)
rmse = np.sqrt(mean_squared_error(y_test, preds))
mape = np.mean(np.abs((y_test - preds) / y_test)) * 100
r2 = r2_score(y_test, preds)
return mae, rmse, mape, r2
4.2. Lift de receita
Para validar o impacto financeiro, simulamos duas estratégias:


