Como criar amostras temporais para treino/teste em Python para dados
Aprende a criar amostras temporais para treino/teste em Python para dados — uma tarefa essencial para modelação de séries temporais que evita data leakage e garante validação realista. A técnica é útil para prever vendas, métricas web ou qualquer série em que a ordem cronológica seja importante.
Pré-requisitos
- Python 3.8+ instalado
- Pacotes: pandas, scikit-learn, numpy (instalar com pip se necessário)
- Noções básicas de pandas e de treino/teste em machine learning
Passo 1: Porquê fazer a divisão temporal em vez de shuffle
Em dados temporais, embaralhar (shuffle) pode causar data leakage: informações do futuro entram no treino e inflacionam a métrica. A divisão temporal preserva a ordem cronológica — treino com dados mais antigos e teste com dados mais recentes.
Passo 2: Estrutura mínima de dados
Vamos começar com um DataFrame simples que tem uma coluna de datas e uma variável alvo. Garantimos que a coluna de datas está em datetime e ordenamos por ela.
import pandas as pd
import numpy as np
# Exemplo mínimo
np.random.seed(42)
dates = pd.date_range('2022-01-01', periods=100, freq='D')
value = np.random.randn(100).cumsum() + 50
df = pd.DataFrame({'date': dates, 'value': value})
# Converter e ordenar
df['date'] = pd.to_datetime(df['date'])
df = df.sort_values('date').reset_index(drop=True)
print(df.head())
Passo 3: Divisão simples treino/teste temporais (holdout)
Uma abordagem comum é usar uma percentagem final como teste (por exemplo, últimos 20%). Isto é simples e evita mistura temporal.
# Proporção de teste
test_size = 0.2
n = len(df)
split = int(n * (1 - test_size))
train = df.iloc[:split].reset_index(drop=True)
test = df.iloc[split:].reset_index(drop=True)
print('Train:', train['date'].min(), '->', train['date'].max())
print('Test:', test['date'].min(), '->', test['date'].max())
Passo 4: Validação temporal com rolling windows
Para avaliar a estabilidade do modelo, usamos cross-validation temporal (rolling/expanding window). Aqui criamos splits sequenciais em que cada fold treina em dados até um ponto t e testa num bloco futuro.
from sklearn.model_selection import TimeSeriesSplit
# Exemplo com TimeSeriesSplit (k folds)
X = df[['value']].copy() # features fictícias
y = df['value']
tscv = TimeSeriesSplit(n_splits=5)
for i, (train_idx, test_idx) in enumerate(tscv.split(X)):
print(f'Fold {i+1}: train {train_idx[0]}-{train_idx[-1]}, test {test_idx[0]}-{test_idx[-1]}')
Passo 5: Janela com avanço (walk-forward) e blocos de teste fixos
Se precisares de prever um horizonte fixo (p.ex. 7 dias), cria um gerador que devolve janelas em que o teste tem comprimento fixo e o treino expande ou desliza.
def rolling_windows(df, train_size, test_size, step=1):
n = len(df)
start = 0
while True:
train_end = start + train_size
test_end = train_end + test_size
if test_end > n:
break
yield df.iloc[start:train_end].reset_index(drop=True), df.iloc[train_end:test_end].reset_index(drop=True)
start += step
# Exemplo
for i, (tr, te) in enumerate(rolling_windows(df, train_size=60, test_size=7, step=7)):
print(f'Window {i+1}: train {tr.date.min().date()}->{tr.date.max().date()}, test {te.date.min().date()}->{te.date.max().date()}')
Passo 6: Evitar leaks com características baseadas no futuro
Ao criar características (lags, rolling means) garante que só usas informações até t-1 para prever t. Usa shift() em pandas para criar lags e evita funções que agreguem valores do futuro.
# Exemplo de lags e média móvel sem data leakage
df['lag_1'] = df['value'].shift(1)
df['rolling_7'] = df['value'].shift(1).rolling(window=7, min_periods=1).mean()
# Remover primeiras linhas com NaN resultantes
df_feat = df.dropna().reset_index(drop=True)
print(df_feat.head())
Verificar o resultado
Confirma que as datas do treino são sempre anteriores às do teste e que as características do treino não contêm valores do futuro. Verifica o tamanho dos conjuntos e imprime uma amostra das primeiras linhas depois de criar lags/rolling.
# Verificações rápidas
assert train['date'].max() < test['date'].min(), 'Data leakage: sobreposição temporal!'
print('Tamanhos: ', len(train), len(test))
print(df_feat[['date','value','lag_1','rolling_7']].head())
Conclusão
Dividir séries temporais corretamente é crítico para evitar data leakage e obter métricas realistas. Usa holdout temporal para testes simples, TimeSeriesSplit para validação mais robusta e rolling windows para avaliar a estabilidade ao longo do tempo. Próximo passo: treinar um modelo simples (ex.: RandomForest, XGBoost) usando os lags e validar com os splits temporais. Dica: visualiza sempre as datas e as janelas para garantir que não houve sobreposição.