Pandas: Séries Temporais Avançadas
Última atualização: 2026-08-26
Na Lição 16, você aprendeu sobre tipos de datetime. Esta lição aprofunda-se na análise prática de séries temporais — resample transforma dados diários em resumos mensais, shift/diff cria características de defasagem, e as três funções de janela (rolling/expanding/ewm) deixam tendências e volatilidade claras como cristal. Esta é a base fundamental para análise quantitativa e engenharia de características de ML.
1. What You Will Learn
- ❶ resample — resampling
- ❷ shift / diff / pct_change
- ❸ rolling — moving window
- ❹ expanding — cumulative window
- ❺ ewm — exponential weighting
2. Relatório Mensal da Cafeteria da Alice
(1) O Problema: Dados Diários São Muito Granulares — Necessidade de Agregação Mensal
Alice tem 90 dias de dados diários de vendas, e seu chefe quer um relatório mensal:
import pandas as pd
import numpy as np
np.random.seed(42)
df = pd.DataFrame({
'sales': np.random.poisson(50, 90) * 10,
'customers': np.random.poisson(30, 90)
}, index=pd.date_range('2024-01-01', periods=90))
# Dados diários → precisa de resumo mensal
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeramente dependendo da sua versão do pandas.
(2) A Solução: Agregação em Uma Linha com resample
▶ Exemplo
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeramente dependendo da sua versão do pandas.
: agregação mensal com resample (Dificuldade ⭐)
import pandas as pd
import numpy as np
np.random.seed(42)
df = pd.DataFrame({
'sales': np.random.poisson(50, 90) * 10,
'customers': np.random.poisson(30, 90)
}, index=pd.date_range('2024-01-01', periods=90))
# Reamostre dados diários → mensais
monthly = df.resample('M').agg({
'sales': 'sum',
'customers': 'mean'
})
print(monthly)
# sales customers
# 2024-01-31 14680 29.55
# 2024-02-29 13520 31.10
# 2024-03-31 15430 30.45
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeramente dependendo da sua versão do pandas.
3. resample — Reamostragem
(1) Subamostragem e Superamostragem
▶ Exemplo
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem o pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
: reamostragem subamostragem/superamostragem (Dificuldade ⭐⭐)
import pandas as pd
import numpy as np
np.random.seed(42)
df = pd.DataFrame({
'price': np.random.uniform(100, 200, 60).round(2)
}, index=pd.date_range('2024-01-01', periods=60))
# Subamostragem: diário → semanal
weekly = df.resample('W').agg({
'price': ['first', 'max', 'min', 'last'] # OHLC
})
print("OHLC Semanal:")
print(weekly.head())
# Subamostragem: diário → trimestral
quarterly = df.resample('Q').mean()
# Superamostragem: diário → horário (precisa de método de preenchimento)
hourly = df.resample('h').ffill() # preenchimento progressivo
# Aviso: cria muitas linhas!
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem o pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
(2) resample vs groupby
| Recurso | resample | groupby |
|---|---|---|
| Base de agrupamento | Frequência temporal | Qualquer coluna |
| Funciona com | DatetimeIndex | Qualquer DataFrame |
| Superamostragem | ✅ Preenche valores faltantes | ❌ |
| Aliases de frequência | D/W/M/Q/H | — |
| Essência | groupby ao longo da dimensão temporal | Agrupamento de uso geral |
4. shift / diff / pct_change
(1) Defasagem e Diferença
▶ Exemplo
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeramente dependendo da sua versão do pandas.
: shift/diff/pct_change (Dificuldade ⭐⭐)
import pandas as pd
import numpy as np
np.random.seed(42)
df = pd.DataFrame({
'sales': np.random.poisson(50, 10) * 10
}, index=pd.date_range('2024-01-01', periods=10))
# shift: move os dados para frente/para trás
df['sales_prev'] = df['sales'].shift(1) # vendas de ontem
df['sales_next'] = df['sales'].shift(-1) # vendas de amanhã
# diff: diferença com o valor anterior
df['sales_diff'] = df['sales'].diff() # vendas - vendas_anteriores
# pct_change: variação percentual
df['sales_pct'] = df['sales'].pct_change() * 100 # % de variação
print(df.round(1))
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeramente dependendo da sua versão do pandas.
df['lag_1'] = df['value'].shift(1) usa o valor de ontem para prever hoje. diff extrai mudanças de tendência, e pct_change extrai taxas de crescimento.
5. rolling — Janela Deslizante
(1) Média Móvel
▶ Exemplo
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
: média móvel rolling (Dificuldade ⭐⭐)
import pandas as pd
import numpy as np
np.random.seed(42)
df = pd.DataFrame({
'temperature': np.round(np.random.normal(20, 5, 30), 1)
}, index=pd.date_range('2024-01-01', periods=30))
# 7-day moving average
df['ma_7'] = df['temperature'].rolling(window=7).mean()
# With min_periods (allow partial windows)
df['ma_7_flex'] = df['temperature'].rolling(window=7, min_periods=3).mean()
# Multiple aggregations
# Pandas 2.x: rolling.agg() returns a multi-column DataFrame and cannot be assigned to a single column directly; use join instead
rolling_agg = df['temperature'].rolling(7).agg(['mean', 'std', 'min', 'max'])
rolling_agg.columns = [f'temp_{stat}' for stat in ['mean', 'std', 'min', 'max']]
df = df.join(rolling_agg)
# Centered rolling (include future values — for smoothing, not forecasting)
df['ma_7_center'] = df['temperature'].rolling(7, center=True).mean()
print(df[['temperature', 'ma_7', 'ma_7_center']].head(10))
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
(2) Janelas Baseadas em Tempo
▶ Exemplo
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
: janela rolling baseada em tempo (Dificuldade ⭐⭐)
import pandas as pd
import numpy as np
# Irregular time series — row-based window is wrong
df = pd.DataFrame({
'value': [10, 20, 15, 30, 25, 40, 35, 50, 45, 60]
}, index=pd.to_datetime([
'2024-01-01', '2024-01-02', '2024-01-05', # gap on 03-04
'2024-01-06', '2024-01-07', '2024-01-15', # gap on 08-14
'2024-01-16', '2024-01-20', '2024-01-21', '2024-01-25'
]))
# Row-based window (wrong for irregular data)
print("Row-based 3:")
print(df['value'].rolling(3).mean())
# Time-based window (correct — 7 calendar days)
print("\nTime-based 7D:")
print(df['value'].rolling('7D').mean())
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Por favor, instale localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
6. expanding — Janela Cumulativa
▶ Exemplo
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
: estatísticas cumulativas com expanding (Dificuldade ⭐)
import pandas as pd
import numpy as np
np.random.seed(42)
df = pd.DataFrame({
'sales': np.random.poisson(50, 10) * 10
})
# Expanding: todos os valores até a posição atual
df['cumsum'] = df['sales'].expanding().sum()
df['cummean'] = df['sales'].expanding().mean()
df['cummax'] = df['sales'].expanding().max()
# Com min_periods
df['cummean_3'] = df['sales'].expanding(min_periods=3).mean()
print(df)
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem pandas pré-instalado. Instale-o localmente (`pip install pandas`) e acompanhe. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
(1) Comparação rolling vs expanding vs ewm
| Característica | rolling | expanding | ewm |
|---|---|---|---|
| Janela | Tamanho fixo | Crescimento contínuo | Infinita (pesos decaem) |
| Pesos | Iguais | Iguais | Valores recentes pesam mais |
| Caso de uso | Tendências locais | Estatísticas cumulativas | Tendências suavizadas |
| Parâmetros | window | min_periods | span/com/halflife |
7. ewm — Suavização Exponencial
▶ Exemplo
> **Output:** Run in a local Python environment (pandas 2.x). The Piston server does not have pandas pre-installed. Please install it locally (`pip install pandas`) and follow along. Actual values may vary slightly depending on your pandas version.
: suavização exponencial ewm (Dificuldade ⭐⭐)
import pandas as pd
import numpy as np
np.random.seed(42)
df = pd.DataFrame({
'price': np.cumsum(np.random.randn(30)) + 100
}, index=pd.date_range('2024-01-01', periods=30))
# Exponentially weighted moving average
df['ewm_5'] = df['price'].ewm(span=5).mean()
df['ewm_10'] = df['price'].ewm(span=10).mean()
df['ewm_20'] = df['price'].ewm(span=20).mean()
# Compare with simple moving average
df['sma_5'] = df['price'].rolling(5).mean()
print(df[['price', 'sma_5', 'ewm_5']].head(10).round(2))
# ewm parameters (only specify one):
# span=5 → similar to 5-period SMA but recent-heavy
# com=4 → center of mass (com = span-1)
# halflife=3 → weight halves every 3 periods
# alpha=0.3 → smoothing factor (0<α≤1)
> **Output:** Run in a local Python environment (pandas 2.x). The Piston server does not have pandas pre-installed. Please install it locally (`pip install pandas`) and follow along. Actual values may vary slightly depending on your pandas version.
8. Exemplo Completo: Análise Diária de Gráfico de Candlestick de Ações
(5) ▶ Janela Deslizante Deslizando ao Longo do Eixo Temporal
graph TB
A[Time Series Data] --> B[rolling Window]
B --> C[Window slides along the time axis]
C --> D[Window 1: t1 to tN]
C --> E[Window 2: t2 to t(N+1)]
C --> F[Window 3: t3 to t(N+2)]
D --> G[mean / std / max / min]
E --> G
F --> G
G --> H[Smoothed trend / Volatility]
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem o pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e siga o código. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
▶ Exemplo
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem o pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e siga o código. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
: pipeline completo de análise de séries temporais (Dificuldade ⭐⭐⭐)
import pandas as pd
import numpy as np
# ============================================
# Exemplo abrangente: dados diários de ações
# resample → rolling → diff → ewm
# ============================================
# 1. Gerar dados diários de ações (60 dias úteis)
np.random.seed(42)
dates = pd.bdate_range('2024-01-01', periods=60)
price = 100 + np.cumsum(np.random.randn(60) * 2)
df = pd.DataFrame({
'open': price + np.random.randn(60) * 0.5,
'high': price + np.abs(np.random.randn(60)) * 1.5,
'low': price - np.abs(np.random.randn(60)) * 1.5,
'close': price + np.random.randn(60) * 0.5,
'volume': np.random.randint(100000, 500000, 60)
}, index=dates)
# 2. Resample: diário → semanal
weekly = df.resample('W-FRI').agg({
'open': 'first', 'high': 'max', 'low': 'min',
'close': 'last', 'volume': 'sum'
})
print("=== OHLCV Semanal ===")
print(weekly.head(4).round(2))
# 3. Rolling: média móvel de 20 dias
df['ma_20'] = df['close'].rolling(20).mean()
df['vol_20'] = df['close'].rolling(20).std()
# 4. Retornos diários
df['returns'] = df['close'].pct_change()
df['log_returns'] = np.log(df['close'] / df['close'].shift(1))
# 5. EWM: suavização exponencial
df['ewm_12'] = df['close'].ewm(span=12).mean()
df['ewm_26'] = df['close'].ewm(span=26).mean()
df['macd'] = df['ewm_12'] - df['ewm_26']
# 6. Resumo
print(f"\n=== Estatísticas ===")
print(f"Período: {df.index[0].date()} a {df.index[-1].date()}")
print(f"Retorno total: {((df['close'].iloc[-1] / df['close'].iloc[0] - 1) * 100):.1f}%")
print(f"Volatilidade (20d): {df['vol_20'].iloc[-1]:.2f}")
print(f"Máximo drawdown: {((df['close'] / df['close'].cummax() - 1).min() * 100):.1f}%")
> **Saída:** Execute em um ambiente Python local (pandas 2.x). O servidor Piston não tem o pandas pré-instalado. Por favor, instale-o localmente (`pip install pandas`) e siga o código. Os valores reais podem variar ligeiramente dependendo da sua versão do pandas.
❓ Perguntas Frequentes
s.diff() = s - s.shift(1). shift é usado para criar features de lag (ML), enquanto diff extrai a quantidade de mudança (análise de tendência). pct_change calcula a taxa de mudança: s.pct_change() = (s - s.shift(1)) / s.shift(1).s.expanding().sum() é equivalente a s.cumsum(). expanding é mais flexível, mas um pouco mais lento; para uso diário, cumsum é mais conciso.📖 Resumo
- reamostra agregações por frequência temporal — é um "groupby ao longo da dimensão temporal"
- desloca os dados para criar características de atraso; diff/pct_change calculam diferenças e taxas de variação
- rolling calcula sobre uma janela móvel de tamanho fixo (média/desvio padrão/extremos móveis)
- rolling baseado no tempo ('7D') é ideal para séries temporais irregulares
- expanding é uma janela cumulativa (do início até a posição atual); ewm aplica ponderação exponencial (valores recentes com maior peso)
- Escolha: tendências locais → rolling, estatísticas cumulativas → expanding, suavização → ewm
📝 Exercícios
- Básico (Dificuldade ⭐): Crie um DataFrame de vendas diárias de 30 dias. Use
resample('W')para agregar em dados semanais erolling(7)para calcular uma média móvel de 7 dias. - Intermediário (Dificuldade ⭐⭐): Crie 60 dias de preços de fechamento de ações. Calcule:
shift(1)lag →diff()diferença diária →pct_change()retornos →rolling(20)volatilidade →ewm(span=12)suavização. - Desafio (Dificuldade ⭐⭐⭐): Simule 90 dias de vendas diárias. Complete: resumo mensal/trimestral com
resample→ comparação de média móvel de 7/14/30 dias comrolling→ média cumulativa expansiva comexpanding→ suavização comewm→ cálculo do maximum drawdown.
← Previous Lesson: Date and Time · Next Lesson: MultiIndex →