📈 Financial Analytics: Previsão com ARIMA e Prophet

Autores: Paloma Vaissman Uribe & Julio Trecenti
Instituição: Insper, PADS | Ano: 2026

Abrir no Colab


Este notebook acompanha os slides da Aula 2:

Série de referência: AirPassengers, passageiros mensais de aviação (1949-1960)

⚙️ Instalação

Código
import subprocess, sysfor pkg in ['statsmodels', 'statsforecast', 'prophet']:    subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-q', pkg])print("✅ Pacotes instalados!")
Código
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.arima_process import ArmaProcess
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.tsa.stattools import adfuller
from statsmodels.stats.diagnostic import acorr_ljungbox
import warnings, logging
warnings.filterwarnings('ignore')
logging.getLogger('prophet').setLevel(logging.WARNING)
logging.getLogger('cmdstanpy').disabled = True

plt.style.use('default')
plt.rcParams.update({
    'figure.facecolor':'#FFFFFF','axes.facecolor':'#F9F9F9',
    'axes.edgecolor':'#444444','axes.labelcolor':'#1C1C1C',
    'xtick.color':'#444444','ytick.color':'#444444',
    'text.color':'#1C1C1C','grid.color': '#DDDDDD',
    'grid.linestyle':'--','grid.alpha':0.5,'figure.dpi':110,
})
RED,BLUE,GREEN,GRAY,YELLOW,PURPLE = '#E4002B','#1a6fc4','#1a9b57','#555555','#cc8800','#cc44ff'
print("✅ Imports OK!")

🔴 Parte 1: Modelagem ARIMA

1 · Revisão: AR, MA e ARMA

Processo ACF PACF
AR(p) Decaimento exponencial Corte em \(p\)
MA(q) Corte em \(q\) Decaimento exponencial
ARMA(p,q) Decaimento Decaimento

ARIMA(p,d,q): aplica \(d\) diferenças antes do ARMA: \[\underbrace{(1-B)^d}_{\text{diferenciação}} y_t \sim \text{ARMA}(p,q)\]

2 · Simulando ARIMA(1,1,0)

Código
np.random.seed(123)# AR(1) com phi=0.5, integrado uma vez → ARIMA(1,1,0)arma_p = ArmaProcess(ar=[1, -0.5], ma=[1])diff_y = arma_p.generate_sample(nsample=500)y  = np.cumsum(diff_y)   # série I(1)dy = np.diff(y)           # 1ª diferença → AR(1)fig, axes = plt.subplots(2, 3, figsize=(14, 7))axes[0,0].plot(y, color=RED, lw=1.2); axes[0,0].set_title('Série y (não estacionária)'); axes[0,0].grid(True)plot_acf( y,  ax=axes[0,1], lags=40, zero=False, title='ACF: Nível')plot_pacf(y,  ax=axes[0,2], lags=40, zero=False, title='PACF: Nível')axes[1,0].plot(dy, color=BLUE, lw=1.2); axes[1,0].axhline(0,color='#888',ls='--',lw=0.8)axes[1,0].set_title('1ª Diferença Δy'); axes[1,0].grid(True)plot_acf( dy, ax=axes[1,1], lags=40, zero=False, title='ACF: 1ª Diferença')plot_pacf(dy, ax=axes[1,2], lags=40, zero=False, title='PACF: 1ª Diferença (corte lag 1 → AR(1) ✓)')plt.suptitle('Simulação ARIMA(1,1,0)', fontsize=13, y=1.01)plt.tight_layout(); plt.show()

3 · Simulando IMA(1,1) = ARIMA(0,1,1)

Código
np.random.seed(123)# MA(1) com theta=0.9, integrado → IMA(1,1)arma_p2  = ArmaProcess(ar=[1], ma=[1, 0.9])diff_ima = arma_p2.generate_sample(nsample=500)dates    = pd.date_range('2020-01-01', periods=500, freq='D')ima1     = pd.Series(np.cumsum(diff_ima), index=dates)dima     = ima1.diff().dropna()fig, axes = plt.subplots(2, 2, figsize=(13, 6))axes[0,0].plot(ima1, color=RED, lw=1.2); axes[0,0].set_title('IMA(1,1): Série'); axes[0,0].grid(True)plot_acf( ima1, ax=axes[0,1], lags=30, zero=False, title='ACF: Nível (decaimento lento)')axes[1,0].plot(dima, color=BLUE, lw=1.2); axes[1,0].axhline(0,color='#888',ls='--',lw=0.8)axes[1,0].set_title('1ª Diferença'); axes[1,0].grid(True)plot_acf( dima, ax=axes[1,1], lags=30, zero=False, title='ACF: 1ª Diferença (corte lag 1 → MA(1) ✓)')plt.suptitle('Simulação IMA(1,1) = ARIMA(0,1,1)', fontsize=13, y=1.01)plt.tight_layout(); plt.show()

4 · Box-Jenkins: Estimação, Diagnóstico e Previsão

Código
# Etapa 1: ADFres_adf = adfuller(ima1, regression='c')print(f"ADF p-value: {res_adf[1]:.4f}",      "→ NÃO estacionária" if res_adf[1] > 0.05 else "→ estacionária")# Etapa 2: Estimação ARIMA(0,1,1)model   = ARIMA(endog=ima1, order=(0, 1, 1))results = model.fit()print("\n", results.summary())
Código
# Etapa 3: Diagnóstico dos resíduosres = results.residfig, axes = plt.subplots(1, 3, figsize=(14, 4))axes[0].plot(res.values, color=BLUE, lw=0.9, alpha=0.85)axes[0].axhline(0, color='#888', ls='--', lw=0.8)axes[0].set_title('Resíduos no tempo'); axes[0].grid(True)axes[1].hist(res, bins=30, color=BLUE, alpha=0.7, edgecolor='#333')axes[1].set_title('Distribuição dos resíduos'); axes[1].grid(True, axis='y')plot_acf(res, ax=axes[2], lags=24, zero=False, title='ACF dos resíduos (deve estar no IC)')plt.tight_layout(); plt.show()lb = acorr_ljungbox(res, lags=[24], model_df=2, return_df=True)print(f"Ljung-Box p-value: {lb['lb_pvalue'].values[0]:.4f}",      "✅ ruído branco" if lb['lb_pvalue'].values[0] > 0.05 else "⚠️ revisar!")
Código
# Etapa 4: Previsão com ICforecast_arima = results.get_forecast(steps=30)fc_mean = forecast_arima.predicted_meanfc_ci   = forecast_arima.conf_int(alpha=0.05)fig, ax = plt.subplots(figsize=(12, 4))ax.plot(ima1.iloc[-80:], color=RED, lw=1.5, label='Histórico')ax.plot(fc_mean, color=GREEN, lw=2, ls='--', label='Previsão ARIMA(0,1,1)')ax.fill_between(fc_ci.index, fc_ci.iloc[:,0], fc_ci.iloc[:,1],                alpha=0.2, color=GREEN, label='IC 95%')ax.axvline(ima1.index[-1], color=YELLOW, lw=1, ls=':', alpha=0.8)ax.set_title('Previsão: ARIMA(0,1,1) com IC 95%')ax.legend(fontsize=9); ax.grid(True)plt.tight_layout(); plt.show()

5 · AutoARIMA: Seleção Automática

Código
from statsforecast import StatsForecastfrom statsforecast.models import AutoARIMAsf = StatsForecast(models=[AutoARIMA(season_length=1)], freq='D', n_jobs=1)df_nixtla = pd.DataFrame({'unique_id':'ima1','ds':ima1.index,'y':ima1.values})sf.fit(df=df_nixtla)# Inspecionar modelo selecionadomodelo_info = sf.fitted_[0, 0].model_arma = modelo_info['arma']   # [p, q, P, Q, s, d, D]p, q, P, Q, s, d, D = armaprint(f"Modelo selecionado: ARIMA({p},{d},{q})({P},{D},{Q})[{s}]")print(f"AIC  : {modelo_info['aic']:.3f}")print(f"BIC  : {modelo_info['bic']:.3f}")print(f"sigma: {modelo_info['sigma2']**0.5:.4f}")

🔴 Parte 2: Sazonalidade: SARIMA, Prophet e ARIMAX

6 · ARIMAX: Variáveis Exógenas

O ARIMAX adiciona regressores externos ao ARIMA:

\[y_t = \beta x_t + \phi_1 y_{t-1} + \cdots + \varepsilon_t - \theta_1\varepsilon_{t-1} - \cdots\]

Se \(y_t\) e \(x_t\) são integradas, diferencie ambas para evitar regressão espúria.

7 · AirPassengers: Dados e Decomposição

Código
# Dados reais do AirPassengers (1949-1960), hardcoded para garantir reprodutibilidadeap_values = [    112,118,132,129,121,135,148,148,136,119,104,118,    115,126,141,135,125,149,170,170,158,133,114,140,    145,150,178,163,172,178,199,199,184,162,146,166,    171,180,193,181,183,218,230,242,209,191,172,194,    196,196,236,235,229,243,264,272,237,211,180,201,    204,188,235,227,234,264,302,293,259,229,203,229,    242,233,267,269,270,315,364,347,312,274,237,278,    284,277,317,313,318,374,413,405,355,306,271,306,    315,301,356,348,355,422,465,467,404,347,305,336,    340,318,362,348,363,435,491,505,404,359,310,337,    360,342,406,396,420,472,548,559,463,407,362,405,    417,391,419,461,472,535,622,606,508,461,390,432,]dates_ap = pd.date_range('1949-01-01', periods=144, freq='MS')AP       = pd.Series(ap_values, index=dates_ap, name='Passengers')AP_train = AP[:'1959-12-01']   # 132 obs: jan/1949-dez/1959AP_test  = AP['1960-01-01':]   # 12 obs: jan/1960-dez/1960real     = AP_test.values       # valores reais de 1960print(f"Treino: {len(AP_train)} obs | Teste: {len(AP_test)} obs")print(f"Real 1960: {real}")AP.plot(figsize=(12, 4), color=RED, title='AirPassengers: série completa', grid=True)plt.ylabel('Passageiros (mil)'); plt.show()
Código
# Decomposição multiplicativadecomp = seasonal_decompose(AP, model='multiplicative', period=12)fig, axes = plt.subplots(4, 1, figsize=(12, 9), sharex=True)for ax, (comp, color, label) in zip(axes, [    (decomp.observed,  RED,   'Série Original'),    (decomp.trend,     BLUE,  'Tendência'),    (decomp.seasonal,  GREEN, 'Sazonalidade'),    (decomp.resid,     GRAY,  'Resíduo'),]):    ax.plot(comp, color=color, lw=1.3)    ax.set_title(label, pad=2); ax.grid(True)plt.suptitle('Decomposição Multiplicativa: AirPassengers', fontsize=13, y=1.01)plt.tight_layout(); plt.show()print("💡 Amplitude sazonal cresce com tendência → modelo MULTIPLICATIVO")

8 · Aditivo vs Multiplicativo: Transformação Log

Código
AP_log = np.log(AP)   # log transforma multiplicativo → aditivofig, axes = plt.subplots(1, 2, figsize=(13, 4))axes[0].plot(AP.index, AP.values, color=RED, lw=1.4)axes[0].set_title('Escala original: amplitude cresce'); axes[0].grid(True)axes[1].plot(AP_log.index, AP_log.values, color=BLUE, lw=1.4)axes[1].set_title('Log: amplitude estabilizada'); axes[1].grid(True)plt.suptitle('AirPassengers: Original vs Log', fontsize=12, y=1.02)plt.tight_layout(); plt.show()

9 · SARIMA: Identificação, Seleção e Previsão

Código
# Trabalhar com log do treinoAP_log_train = np.log(AP_train)AP_diff      = AP_log_train.diff().dropna()    # d=1AP_diff_seas = AP_diff.diff(12).dropna()       # D=1fig, axes = plt.subplots(2, 2, figsize=(13, 7))plot_acf( AP_diff,      ax=axes[0,0], lags=36, zero=False, title='ACF: 1ª Diff (log)')plot_pacf(AP_diff,      ax=axes[0,1], lags=36, zero=False, title='PACF: 1ª Diff (log)')plot_acf( AP_diff_seas, ax=axes[1,0], lags=36, zero=False, title='ACF: Diff + Diff Sazonal')plot_pacf(AP_diff_seas, ax=axes[1,1], lags=36, zero=False, title='PACF: Diff + Diff Sazonal')for ax in axes.flat:    ax.axvline(12, color=YELLOW, lw=0.8, ls=':', alpha=0.7)    ax.axvline(24, color=YELLOW, lw=0.8, ls=':', alpha=0.5)plt.suptitle('AirPassengers: Identificação SARIMA', fontsize=13, y=1.01)plt.tight_layout(); plt.show()
Código
# Seleção por AICcfrom itertools import productresultados_aic = []for p, q in product([0, 1, 2], repeat=2):    try:        m = ARIMA(AP_log_train, order=(p,1,q), seasonal_order=(0,1,0,12)).fit()        resultados_aic.append({'Modelo': f'SARIMA({p},1,{q})(0,1,0)₁₂',                               'AIC': round(m.aic,2), 'AICc': round(m.aicc,2), 'BIC': round(m.bic,2)})    except: passdf_aic = pd.DataFrame(resultados_aic).sort_values('AICc')print(df_aic.head(5).to_string(index=False))melhor_modelo = df_aic.iloc[0]['Modelo']print(f"\n✅ Melhor modelo: {melhor_modelo}")
Código
# Estimação e previsão SARIMA(1,1,0)(0,1,0)₁₂fit_sarima = ARIMA(AP_log_train, order=(1,1,0), seasonal_order=(0,1,0,12)).fit()print(fit_sarima.summary())# Diagnósticolb_s = acorr_ljungbox(fit_sarima.resid, lags=[24], model_df=2, return_df=True)print(f"\nLjung-Box p-value: {lb_s['lb_pvalue'].values[0]:.4f}",      "✅" if lb_s['lb_pvalue'].values[0] > 0.05 else "⚠️ revisar!")
Código
# Previsão na escala original (exp do log)fc_log_s = fit_sarima.get_forecast(12).predicted_meanfc_orig  = np.exp(fc_log_s.values)            # array de 12 valoresci_log_s = fit_sarima.get_forecast(12).conf_int()ci_orig  = np.exp(ci_log_s.values)print(f"Previsão SARIMA 1960: {fc_orig.round(1)}")print(f"Real          1960 : {real}")print(f"MAE SARIMA: {np.abs(fc_orig - real).mean():.2f}")

10 · Prophet (Sazonalidade Multiplicativa)

Código
from prophet import Prophet# Preparar dadosdf_prophet = pd.DataFrame({'ds': AP.index, 'y': AP.values})# Ajustar apenas no treino (jan/1949-dez/1959)m_prophet = Prophet(seasonality_mode='multiplicative')m_prophet.fit(df_prophet[df_prophet['ds'] < '1960-01-01'])# Previsão para 12 meses após o treinofuture   = m_prophet.make_future_dataframe(periods=12, freq='MS')forecast = m_prophet.predict(future)# Componentesfig = m_prophet.plot_components(forecast)plt.show()# Extrair apenas as 12 previsões de 1960fc_prophet = forecast[forecast['ds'] >= '1960-01-01']['yhat'].valuesprint(f"Previsão Prophet 1960 ({len(fc_prophet)} vals): {fc_prophet.round(1)}")print(f"MAE Prophet (mult.): {np.abs(fc_prophet - real).mean():.2f}")

11 · ARIMAX: Dummies Sazonais

Código
# Criar dummies mensais (janeiro = referência) + tendência linearmeses     = pd.get_dummies(AP.index.month, prefix='mes', drop_first=True)meses.index = AP.indextrend_var = pd.Series(range(1, len(AP)+1), index=AP.index, name='trend')X         = pd.concat([meses, trend_var], axis=1).astype(float)X_train = X[:'1959']X_test  = X['1960':]# Ajustar ARIMAX no treinofit_arimax = ARIMA(AP_train, order=(1,1,0), exog=X_train).fit()fc_arimax  = fit_arimax.get_forecast(12, exog=X_test).predicted_mean.valuesprint(f"Previsão ARIMAX 1960: {fc_arimax.round(1)}")print(f"MAE ARIMAX: {np.abs(fc_arimax - real).mean():.2f}")# Diagnósticolb_ax = acorr_ljungbox(fit_arimax.resid, lags=[24], model_df=2, return_df=True)print(f"Ljung-Box p-value: {lb_ax['lb_pvalue'].values[0]:.4f}",      "✅" if lb_ax['lb_pvalue'].values[0] > 0.05 else "⚠️")

11 · Prophet com Regressores Exógenos (add_regressor())

Código
# Usar df_prophet já criado na célula do Prophet# Adicionar dummies de mês como regressoresdf_proph2 = df_prophet.copy()df_proph2['mes'] = df_proph2['ds'].dt.monthregressores = [f'mes_{mes}' for mes in range(2, 13)]for mes in range(2, 13):    df_proph2[f'mes_{mes}'] = (df_proph2['mes'] == mes).astype(int)# Ajustar apenas no treinom_reg = Prophet(seasonality_mode='multiplicative')for reg in regressores:    m_reg.add_regressor(reg)m_reg.fit(df_proph2[df_proph2['ds'] < '1960-01-01'])# Previsão: dummies devem estar no dataframe futurofuture_reg = m_reg.make_future_dataframe(periods=12, freq='MS')future_reg['mes'] = future_reg['ds'].dt.monthfor reg in regressores:    future_reg[reg] = (future_reg['mes'] == int(reg.split('_')[1])).astype(int)forecast_reg   = m_reg.predict(future_reg)fc_prophet_reg = forecast_reg[forecast_reg['ds'] >= '1960-01-01']['yhat'].valuesprint(f"Previsão Prophet+Dummies 1960: {fc_prophet_reg.round(1)}")print(f"MAE Prophet+Dummies: {np.abs(fc_prophet_reg - real).mean():.2f}")

12 · Comparação Final: 4 Modelos

Código
# Verificação de consistência antes da comparaçãoprint("Valores reais 1960:", real)print()print(f"fc_orig shape:       {fc_orig.shape}        (SARIMA)")print(f"fc_prophet shape:    {fc_prophet.shape}        (Prophet mult.)")print(f"fc_prophet_reg shape:{fc_prophet_reg.shape}        (Prophet+Dummies)")print(f"fc_arimax shape:     {fc_arimax.shape}        (ARIMAX)")
Código
# Comparação: MAE e MAPE ordenados do melhor ao piorresultados_finais = {    'SARIMA':            fc_orig,    'Prophet (mult.)':   fc_prophet,    'Prophet + Dummies': fc_prophet_reg,    'ARIMAX':            fc_arimax,}print(f"{'Modelo':25s}  {'MAE':>8}  {'MAPE':>8}")print('-' * 48)for modelo, fc in sorted(resultados_finais.items(),                          key=lambda x: np.abs(x[1] - real).mean()):    mae_val  = np.abs(fc - real).mean()    mape_val = np.abs((fc - real) / real * 100).mean()    print(f"{modelo:25s}  {mae_val:8.1f}  {mape_val:7.1f}%")
Código
# Visualização: 3 painéis: previsões, MAE, MAPEcores_mod = [GREEN, BLUE, PURPLE, YELLOW]fig, axes = plt.subplots(1, 3, figsize=(16, 5))# Painel 1: previsões no tempoaxes[0].plot(AP.index, AP.values, color=GRAY, lw=1.5, label='Histórico')axes[0].plot(AP_test.index, real, color='white', lw=2.5, ls=':', label='Real 1960', zorder=6)for (nome, fc), cor in zip(resultados_finais.items(), cores_mod):    axes[0].plot(AP_test.index, fc, color=cor, lw=2, ls='--', label=nome)axes[0].axvline(AP_train.index[-1], color='#666', lw=1, ls=':', alpha=0.7)axes[0].set_title('Previsões: 1960'); axes[0].set_ylabel('Passageiros')axes[0].legend(fontsize=7.5, loc='upper left'); axes[0].grid(True)# Painel 2: MAEnomes_mod = list(resultados_finais.keys())vals_mae  = [np.abs(fc - real).mean() for fc in resultados_finais.values()]bars = axes[1].bar(nomes_mod, vals_mae, color=cores_mod, alpha=0.85, edgecolor='#333', width=0.55)axes[1].set_title('MAE: Erro Médio Absoluto'); axes[1].set_ylabel('MAE (passageiros)')axes[1].grid(True, axis='y'); axes[1].tick_params(axis='x', labelsize=8)for bar, val in zip(bars, vals_mae):    axes[1].text(bar.get_x()+bar.get_width()/2, bar.get_height()+0.3,                 f'{val:.1f}', ha='center', va='bottom', fontsize=10, fontweight='bold')axes[1].set_ylim(0, max(vals_mae)*1.22)# Painel 3: MAPEvals_mape = [np.abs((fc - real)/real*100).mean() for fc in resultados_finais.values()]bars2 = axes[2].bar(nomes_mod, vals_mape, color=cores_mod, alpha=0.85, edgecolor='#333', width=0.55)axes[2].set_title('MAPE: Erro Percentual'); axes[2].set_ylabel('MAPE (%)')axes[2].grid(True, axis='y'); axes[2].tick_params(axis='x', labelsize=8)for bar, val in zip(bars2, vals_mape):    axes[2].text(bar.get_x()+bar.get_width()/2, bar.get_height()+0.05,                 f'{val:.1f}%', ha='center', va='bottom', fontsize=10, fontweight='bold')axes[2].set_ylim(0, max(vals_mape)*1.22)plt.suptitle('AirPassengers: SARIMA · Prophet · Prophet+Dummies · ARIMAX',             fontsize=12, y=1.02)plt.tight_layout(); plt.show()
Código
# ── Salvar gráfico para usar nos slides ──────────────────────────────────────# Execute esta célula após rodar todas as anteriores# O gráfico gerado terá os valores reais do seu ambientefig, axes = plt.subplots(1, 3, figsize=(16, 5))cores_mod = [GREEN, BLUE, PURPLE, YELLOW]nomes_mod = list(resultados_finais.keys())vals_mae  = [np.abs(fc - real).mean()           for fc in resultados_finais.values()]vals_mape = [np.abs((fc-real)/real*100).mean()  for fc in resultados_finais.values()]axes[0].plot(AP.index, AP.values, color=GRAY, lw=1.5, label='Histórico')axes[0].plot(AP_test.index, real, color='white', lw=2.5, ls=':', label='Real 1960', zorder=6)for (nome, fc), cor in zip(resultados_finais.items(), cores_mod):    axes[0].plot(AP_test.index, fc, color=cor, lw=2, ls='--', label=nome)axes[0].axvline(AP_train.index[-1], color='#666', lw=1, ls=':', alpha=0.7)axes[0].set_title('Previsões: 1960'); axes[0].set_ylabel('Passageiros')axes[0].legend(fontsize=7.5, loc='upper left'); axes[0].grid(True)bars = axes[1].bar(nomes_mod, vals_mae, color=cores_mod, alpha=0.85, edgecolor='#333', width=0.55)axes[1].set_title('MAE: Erro Médio Absoluto'); axes[1].set_ylabel('MAE (passageiros)')axes[1].grid(True, axis='y'); axes[1].tick_params(axis='x', labelsize=8)for bar, val in zip(bars, vals_mae):    axes[1].text(bar.get_x()+bar.get_width()/2, bar.get_height()+0.3,                 f'{val:.1f}', ha='center', va='bottom', fontsize=10, fontweight='bold')axes[1].set_ylim(0, max(vals_mae)*1.22)bars2 = axes[2].bar(nomes_mod, vals_mape, color=cores_mod, alpha=0.85, edgecolor='#333', width=0.55)axes[2].set_title('MAPE: Erro Percentual'); axes[2].set_ylabel('MAPE (%)')axes[2].grid(True, axis='y'); axes[2].tick_params(axis='x', labelsize=8)for bar, val in zip(bars2, vals_mape):    axes[2].text(bar.get_x()+bar.get_width()/2, bar.get_height()+0.05,                 f'{val:.1f}%', ha='center', va='bottom', fontsize=10, fontweight='bold')axes[2].set_ylim(0, max(vals_mape)*1.22)plt.suptitle('AirPassengers: SARIMA · Prophet · Prophet+Dummies · ARIMAX',             fontsize=12, y=1.02)plt.tight_layout()plt.savefig('a2_g8_comparacao.png', dpi=130, bbox_inches='tight',            facecolor='#1C1C1C')plt.show()print("✅ Gráfico salvo como 'a2_g8_comparacao.png'")print("   Substitua o arquivo de mesmo nome na pasta dos slides para atualizar!")

🏋️ Exercício Proposto

Substitua o AirPassengers pelo IBC-Br ou Desemprego e refaça o pipeline:

  1. Carregue via API do BCB
  2. Teste ADF: é estacionária?
  3. Identifique SARIMA via ACF/PACF e seleção por AICc
  4. Compare com AutoARIMA (StatsForecast)
  5. Ajuste Prophet multiplicativo e ARIMAX com dummies
  6. Separe os últimos 12 meses como teste
  7. Calcule MAE e MAPE para os 4 modelos: qual venceu?
Código
import requestsdef bcb_serie(codigo, start='2003-01-01'):    """Carrega série do BCB via API REST, sem dependências extras."""    di  = pd.Timestamp(start).strftime('%d/%m/%Y')    url = (f"https://api.bcb.gov.br/dados/serie/bcdata.sgs.{codigo}/dados"           f"?formato=json&dataInicial={di}")    df  = pd.DataFrame(requests.get(url, timeout=30).json())    df['data']  = pd.to_datetime(df['data'], dayfirst=True)    df['valor'] = pd.to_numeric(df['valor'], errors='coerce')    return df.set_index('data').rename(columns={'valor': f'bcb_{codigo}'}).dropna()# Carregar IBC-Br ou desemprego:# ibc    = bcb_serie(24363)   # IBC-Br mensal# desemp = bcb_serie(24369)   # Taxa de desemprego mensal# serie       = ibc['bcb_24363']# serie_train = serie.iloc[:-12]# serie_test  = serie.iloc[-12:]print("✅ Função bcb_serie() pronta. Descomente as linhas acima para carregar.")

📖 Referências

De volta ao topo