Alura > Cursos de Dados > Cursos de Machine Learning & Deep Learning > Conteúdos de Machine Learning & Deep Learning > Primeiras aulas do curso Séries temporais com Python: modelagem e previsão com ARIMA e SARIMAX

Séries temporais com Python: modelagem e previsão com ARIMA e SARIMAX

Introdução às séries temporais - Apresentação

Apresentando o instrutor e realizando audiodescrição

Olá! Eu sou Ariel Velardo Paternostre da Silva e vou acompanhar vocês neste curso de séries temporais com Python. Antes de começarmos, farei uma breve autodescrição.

Audiodescrição: Sou um homem de pele parda, com cabelo cacheado e barba curta. Estou vestindo uma camiseta azul acinzentada e um relógio no pulso esquerdo. Ao fundo, há uma estante com alguns livros e, ao lado, outra estante com meu diploma, um troféu de conclusão do meu MBA e alguns objetos pessoais.

Apresentando formação e experiências

Falando sobre a minha trajetória, sou formado em Física pela Universidade de São Paulo (USP). Também concluí um MBA pela USP, tenho uma especialização em Engenharia de Machine Learning (aprendizado de máquina) pela FIAP e, atualmente, estou cursando um mestrado em Computação Aplicada no IPT. Minha linha de pesquisa está ligada à computação quântica, com foco em Quantum Machine Learning (aprendizado de máquina quântico).

Ao longo da minha carreira, passei por diversas empresas de diferentes setores e também por alguns bancos. Isso me deu a oportunidade de trabalhar com ciência de dados em inúmeros problemas de negócios bastante diferentes.

Destacando experiência em docência e papel no curso

Antes de atuar diretamente com dados, fui professor de Física e Matemática em escolas públicas e particulares.

Ensinar sempre fez parte da minha trajetória. Agora, também serei a pessoa docente de vocês neste curso.

Contextualizando séries temporais e estrutura do curso

Séries temporais aparecem em muitos problemas reais. Há diversas aplicações: prever vendas, demanda, preços, acessos na plataforma, consumo de energia e outras informações que variam ao longo do tempo.

Durante este curso, vamos estudar esse tema de forma prática. Vamos usar Python e construir um projeto passo a passo. No próximo vídeo, apresentaremos melhor a proposta do curso, o projeto que vamos desenvolver e o caminho que vamos percorrer ao longo das aulas.

Compartilhando recursos e encerrando a introdução

Também deixei no slide meu LinkedIn e meu GitHub. Podem me procurar. Vocês já podem me acompanhar pelo LinkedIn e consultar meus projetos no GitHub. O repositório ainda reúne materiais em vários níveis de organização; nem tudo está no formato final de portfólio, mas pode servir como referência para vocês ou como ponto de partida para os próprios projetos que desejarem desenvolver.

Espero que vocês aproveitem muito este curso. Eu vejo vocês no próximo vídeo.

Introdução às séries temporais - Validando ambiente e organizando a base de dados

Apresentando o plano de trabalho e validações iniciais

No vídeo anterior, nós criamos a estrutura inicial do projeto, configuramos o ambiente virtual, instalamos as bibliotecas e conectamos o projeto ao GitHub. Agora, vamos começar a trabalhar com os arquivos do projeto.

Pessoal, primeiro vamos analisar o arquivo visual_utils.py. Vamos explicar o que precisamos fazer ali, pois ele centralizará o padrão visual dos gráficos. Essa parte é interessante e pode ser utilizada no trabalho de vocês também. É útil definir um padrão visual para as análises, especialmente quando formos apresentar para uma pessoa gestora ou alguém da área de negócios.

Em seguida, vamos executar um notebook (caderno) de validação do ambiente para confirmar se as bibliotecas, a base de dados e a estrutura de pastas estão funcionando conforme o planejado. Também vamos iniciar a preparação da base de bicicletas, organizando os dados no tempo. Essa prática é importante e recomendada em projetos de dados: antes de modelar, validamos, organizamos e, só depois, começamos a análise.

Organizando arquivos e pastas do projeto

Vamos retornar ao VS Code e observar a organização de pastas. Na pasta raiz do repositório, temos a pasta "series-temporais-bikes" e outras pastas criadas posteriormente. Na pasta "data", já existe um arquivo CSV. Vocês provavelmente ainda não têm esse arquivo; vamos indicar onde obtê-lo. Ele estará disponível para vocês, mas, se preferirem consultar a origem, o arquivo está no Kaggle, no conjunto de dados Bike Sharing Demand, que utilizaremos no curso. A página contém bastante informação sobre a base. No momento, há uma competição do Kaggle utilizando essa base. Pedimos que usem a versão de treino. As colunas estão documentadas lá; posteriormente, comentaremos sobre cada uma delas e apresentaremos traduções para falarmos em português ao longo do curso. Se desejarem simular mais de perto o ambiente de uma competição, podem baixar diretamente do Kaggle.

Voltando ao VS Code, onde colocar o arquivo? Vamos mostrar o caminho. O nosso repositório do GitHub, onde armazenamos os projetos, está no disco C, na pasta "GitHub", em seguida na pasta "Alura", depois na pasta "series-temporais-bikes" e então na pasta "data", dentro do projeto "series-temporais-bikes". Colocamos o CSV ali. Apenas alteramos o nome de train.csv para bike.csv.

De volta ao VS Code, podemos ocultar o painel de arquivos quando não precisarmos visualizá-lo o tempo todo. Fizemos uma alteração: havíamos estruturado os notebooks (cadernos) e movido todos para a pasta do projeto, e também copiamos alguns notebooks (cadernos) que já havíamos testado previamente para agilizar. A ideia é não gastar tempo codificando do zero ou pedindo para uma IA adiantar trechos de código aqui; vamos conversar sobre os códigos e seguir com o restante do curso.

Utilizando o módulo de visualização para padronização gráfica

O que importa neste momento é utilizarmos o visual_utils.py. Vamos abrir esse arquivo, que já está pronto. Vamos copiá-lo e colá-lo no projeto. Em seguida, salvamos com "Ctrl+S". Para contextualizar: estamos utilizando a biblioteca Plotly. A ideia é aplicar, dentro do Plotly, algumas modificações para padronizar os gráficos. Definimos as cores e sequências de cores e criamos uma função para aplicação do layout (disposição). O arquivo está bem comentado e ficará acessível para trabalharmos com vários tipos de gráficos de forma padronizada. As funções são simples; quem já fez cursos introdutórios de Python e aprendeu a utilizar Matplotlib ou Seaborn deve compreender facilmente como cada parte funciona. Não vamos nos aprofundar nessa explicação agora. O objetivo é gerar gráficos com padrão visual consistente.

O conteúdo do arquivo visual_utils.py, que centraliza paleta de cores e funções de layout e gráficos com Plotly, é o seguinte:

import plotly.express as px
import plotly.graph_objects as go

CORES = {
    "azul_escuro": "#003f5c",
    "azul_principal": "#2f4b7c",
    "azul_medio": "#665191",
    "azul_claro": "#a05195",
    "rosa": "#d45087",
    "laranja": "#f95d6a",
    "amarelo": "#ff7c43",
    "cinza_escuro": "#333333",
    "cinza_texto": "#555555",
    "cinza_claro": "#e0e0e0",
    "branco": "#ffffff",
    "fundo_claro": "#f7f7f7"
}

SEQUENCIA_CORES = [
    CORES["azul_principal"],
    CORES["azul_medio"],
    CORES["azul_claro"],
    CORES["rosa"],
    CORES["laranja"],
    CORES["amarelo"]
]

def aplicar_layout_padrao(
    fig,
    titulo=None,
    altura=500,
    mostrar_grade=True,
    legenda_horizontal=True
):
    """
    Aplica um layout visual padrão para os gráficos Plotly do projeto.
    """
    fig.update_layout(
        title=titulo,
        title_x=0.02,
        title_font=dict(size=18, color=CORES["azul_escuro"]),
        height=altura,
        plot_bgcolor=CORES["fundo_claro"],
        paper_bgcolor=CORES["branco"],
        font=dict(color=CORES["cinza_texto"]),
        margin=dict(t=80, l=50, r=30, b=50)
    )

    # Configurações dos eixos
    eixo_padrao = dict(
        showgrid=mostrar_grade,
        gridcolor=CORES["cinza_claro"],
        linecolor=CORES["cinza_claro"],
        tickfont=dict(color=CORES["cinza_texto"])
    )

    fig.update_xaxes(**eixo_padrao)
    fig.update_yaxes(**eixo_padrao)

    # Configuração da legenda
    if legenda_horizontal:
        fig.update_layout(
            legend=dict(
                orientation="h",
                yanchor="bottom",
                y=1.02,
                xanchor="right",
                x=1
            )
        )

    return fig

def grafico_linha_padrao(
    df,
    x,
    y,
    titulo=None,
    cor=None,
    label_x=None,
    label_y=None,
    altura=500,
    markers=False
):
    """
    Cria um gráfico de linha com layout padrão do projeto.
    """
    fig = px.line(
        df,
        x=x,
        y=y,
        labels={x: label_x or x, y: label_y or y},
        markers=markers
    )

    fig.update_traces(line=dict(width=3))

    if cor:
        fig.update_traces(line_color=cor)
    else:
        fig.update_traces(line_color=CORES["azul_principal"])

    fig = aplicar_layout_padrao(
        fig,
        titulo=titulo,
        altura=altura
    )

    return fig

def grafico_linha_multiplas_series_padrao(
    df,
    x,
    y,
    color_col,
    titulo=None,
    labels=None,
    altura=500
):
    """
    Cria um gráfico de linha com múltiplas séries usando a paleta do projeto.
    """
    fig = px.line(
        df,
        x=x,
        y=y,
        color=color_col,
        labels=labels,
        color_discrete_sequence=SEQUENCIA_CORES
    )

    fig.update_traces(line=dict(width=3))

    fig = aplicar_layout_padrao(
        fig,
        titulo=titulo,
        altura=altura
    )

    return fig

def grafico_barra_padrao(
    df,
    x,
    y,
    titulo,
    cor=None,
    label_x=None,
    label_y=None,
    altura=500,
    markers=False
):
    """
    Cria um gráfico de barras com layout padrão do projeto.
    """
    fig = px.bar(
        df,
        x=x,
        y=y
    )

    fig.update_traces(
        marker_color=cor or CORES["azul_principal"]
    )

    fig = aplicar_layout_padrao(
        fig,
        titulo=titulo,
        altura=altura
    )

    return fig

Validando o ambiente e configurando o kernel

Com o padrão configurado, vamos abrir o notebook (caderno) de validação do ambiente. Antes, um ponto importante para garantirmos que estamos na mesma página: no terminal, ao realizar o fluxo de versionamento, precisamos navegar corretamente entre as pastas. Quando formos realizar o commit (registro), retornamos para a pasta "Alura" com o comando cd .. e, a partir dela, fazemos o commit (registro) da pasta "series-temporais-bikes". Depois, quando voltarmos a desenvolver no projeto, entramos novamente na pasta "series-temporais-bikes" com cd series-temporais-bikes e ativamos o ambiente virtual. É fundamental ativar o ambiente virtual; caso contrário, algo pode falhar, podem ocorrer erros e o commit (registro) pode não ser concluído corretamente. Vamos apenas minimizar o terminal, sem fechá-lo, pois precisaremos dele depois. Ao longo das aulas, a ideia é realizar commits (registros) com frequência; no início, avisaremos explicitamente quando fazer, e depois seguiremos de forma mais direta.

Agora, vamos para a validação do ambiente. Este notebook (caderno) serve para verificar, primeiro, as bibliotecas. Antes de rodar as células, é necessário selecionar o kernel (núcleo) correto. Vamos selecionar o kernel (núcleo) que criamos anteriormente; ele deve ter um nome semelhante a .venv-alura. No código ao qual vocês têm acesso, em algum lugar estará indicado que pedimos para validar o ambiente .venv-alura; criamos o ambiente virtual com esse nome.

Vamos executar a primeira célula. As bibliotecas foram importadas com sucesso, o que indica que estão instaladas corretamente.

Estamos investigando a raiz do projeto para verificar se está correta. A raiz foi localizada e a leitura dos arquivos esperados está ocorrendo adequadamente. Em seguida, validamos se o módulo de visualização é acessado corretamente. A função foi importada com sucesso, portanto está tudo certo nessa parte.

Vamos conferir as versões das bibliotecas pandas e NumPy que utilizaremos. É importante validar esses detalhes porque funções são adicionadas, descontinuadas ou modificadas conforme as bibliotecas e suas versões evoluem. Mantemos essa checagem para evitar inconsistências.

Confirmando a integridade da base e geração de gráficos

Prosseguimos com calma nesta etapa inicial e, depois, avançaremos para a análise dos dados. Validamos os diretórios e as pastas esperadas. Em seguida, confirmamos o caminho da base: a base foi encontrada. Validamos o arquivo CSV: a base foi carregada com sucesso.

Agora, verificamos as informações da base para avaliar se fazem sentido. Para isso, comparamos com a base original à qual temos acesso (por exemplo, no Kaggle). Realizamos mais algumas validações e conferimos se os gráficos serão gerados como desejamos. O gráfico foi gerado; ajustamos o tamanho para facilitar a leitura.

Um dos pontos mais importantes desta validação é o uso do Plotly. Verificamos se o gráfico foi salvo corretamente no diretório. A confirmação foi positiva; o salvamento ocorreu como esperado no diretório configurado. Concluímos todas as validações com sucesso.

Iniciando a preparação e organização temporal da base

Podemos salvar o trabalho. Agora, vamos iniciar a preparação da base. Essa etapa também é extremamente importante.

O primeiro objetivo aqui é conhecer a base. Antes, precisamos definir o kernel. Em seguida, executamos as importações necessárias, validamos a raiz do projeto novamente, carregamos os utilitários de visualização e carregamos a base de dados.

A base está carregada. Ajustamos o tamanho de exibição para facilitar a visualização. Utilizamos a função info, que no pandas fornece um panorama detalhado: tipos de dados das colunas, nomes das colunas e contagem de não nulos.

Agora, vamos renomear as colunas principais para trabalharmos em português: data_hora, demanda, temperatura e umidade. Executamos e verificamos o resultado com data_hora e demanda; funcionou corretamente. No código a seguir, usamos data_hora (com underline) como nome da coluna de data-hora:

df = df.rename(columns={
    "datetime": "data_hora",
    "count": "demanda",
    "temp": "temperatura",
    "humidity": "umidade",
    "windspeed": "velocidade_vento",
    "season": "estacao",
    "holiday": "feriado",
    "workingday": "dia_util",
    "casual": "usuarios_casuais",
    "registered": "usuarios_registrados",
    "atemp": "sensacao_termica"
})

Em seguida, convertemos a coluna de data e a ordenamos. Como mencionamos no primeiro vídeo, a ordem em que os eventos ocorrem é fundamental para séries temporais. Sem essa ordenação, a série temporal, como naquele primeiro gráfico visto na validação, ficaria incoerente.

df["data_hora"] = pd.to_datetime(df["data_hora"])
df = df.sort_values("data_hora").reset_index(drop=True)

Vamos criar variáveis de calendário a partir de data_hora, separando as informações existentes. Por exemplo, para um registro como 01/01/2011 à meia-noite, extraímos ano, mês, dia, hora e dia da semana, além de mapear o dia da semana para facilitar interpretações. Todas essas variáveis são importantes para a análise. Também criaremos um índice temporal. O dataframe estava indexado numericamente (0, 1, 2, 3, ...), e passamos a indexá-lo pela coluna data_hora, que é única, o que justifica essa forma de indexação.

df["ano"] = df["data_hora"].dt.year
df["mes"] = df["data_hora"].dt.month
df["dia"] = df["data_hora"].dt.day
df["hora"] = df["data_hora"].dt.hour
df["dia_semana"] = df["data_hora"].dt.dayofweek

mapa_dias_semana = {
    0: "segunda-feira",
    1: "terça-feira",
    2: "quarta-feira",
    3: "quinta-feira",
    4: "sexta-feira",
    5: "sábado",
    6: "domingo"
}

df["nome_dia_semana"] = df["dia_semana"].map(mapa_dias_semana)
df_temporal = df.set_index("data_hora")

Verificando qualidade dos dados e realizando primeira visualização

Verificamos também a existência de valores ausentes. Não foram encontrados valores ausentes, o que é positivo. No mundo real, é comum haver problemas de base, como dados faltantes ou inconsistentes, mas, neste caso, não identificamos lacunas.

Fazemos uma primeira visualização rápida para observar como a base está após o tratamento. É praticamente a mesma tabela vista na validação, agora com os dados melhor estruturados. Como já organizamos a dimensão temporal, salvamos a base tratada. Com a base salva, seguimos adiante para a análise propriamente dita no próximo vídeo.

Para essa primeira visualização com padrão visual consistente, utilizamos a função grafico_linha_padrao definida no módulo visual_utils:

_df_grafico = df_temporal.reset_index()

fig = grafico_linha_padrao(
    _df_grafico,
    x="data_hora",
    y="demanda",
    titulo="Demanda de bicicletas ao longo do tempo",
    label_x="Data e hora",
    label_y="Demanda",
    altura=500
)

fig.show()

E, em seguida, salvamos a base tratada:

df_temporal.reset_index().to_csv(
    caminho_saida,
    index=False,
    encoding="utf-8-sig"
)

Salvando alterações e versionando o progresso

Essas etapas podem parecer menos interessantes, mas são extremamente importantes. Não devemos iniciar aplicando modelos sem garantir que estamos no ambiente adequado, com dados coerentes e validações realizadas.

Finalizamos salvando e fechando o arquivo. Podemos usar Ctrl+S para salvar. O indicador amarelo sinaliza que há modificações pendentes de salvamento.

Em seguida, preparamos o controle de versão para comitar as alterações. Navegamos pelo sistema de arquivos com cd (por exemplo, entrando na pasta "Alura") e, se necessário, utilizamos cd .. para voltar. Caso ocorra algum problema ao copiar comandos, ajustamos manualmente.

Executamos git status para inspecionar o que foi modificado e adicionado. Em seguida, preparamos o commit, definindo uma mensagem descritiva, como "add séries temporais", para indicar o que será enviado. Por fim, realizamos git push para enviar as alterações ao repositório remoto. Tudo concluído com sucesso.

Para efetivar essas ações, os comandos usados no terminal foram:

git add series-temporais-bikes
git commit -m "valida ambiente e organiza base inicial de bicicletas"
git push

Encerramos por aqui. Na próxima aula, nós continuamos na parte de análise exploratória.

Introdução às séries temporais - Observando a série temporal antes de modelar

Enfatizando a observação antes de modelar

Agora chegamos a uma parte que consideramos uma das mais importantes em séries temporais: observar a série antes de tentar modelá-la. É muito comum querermos ir direto para o modelo, testar ARIMA, SARIMA, SARIMAX, ajustar parâmetros e comparar métricas. Antes disso, precisamos parar e olhar para os dados.

Quando colocamos uma série temporal em um gráfico, ela começa a mostrar algumas pistas. Passamos a enxergar picos, quedas, períodos mais estáveis, momentos com muita oscilação, algum ciclo e padrões que talvez se repitam. Neste vídeo, a pergunta principal ainda não é qual modelo vamos usar. A pergunta é mais simples: o que essa série está tentando mostrar?

Recapitulando a preparação da base e planejando a análise

No vídeo anterior, organizamos a base: convertemos a data, ordenamos os registros, criamos variáveis de calendário e salvamos uma base preparada. Agora, vamos usar essa base para fazer a primeira leitura mais visual da demanda de bicicleta.

Vamos para o VS Code (Visual Studio Code). Já deixamos aberto o notebook que vamos utilizar, nosso segundo notebook, Análise Exploratória Temporal. A ideia é carregar a base organizada que salvamos no vídeo anterior e começar a olhar para a demanda ao longo do tempo. Ainda não vamos treinar modelos; primeiro, vamos entender o comportamento da série.

Relembrando: é fundamental acessar a pasta raiz "Alura", navegar para "séries temporais Bikes" e ativar a venv (ambiente virtual).

Configurando o notebook e os caminhos do projeto

Vamos começar a olhar o notebook. Primeiro, fazemos algumas validações. Carregamos a base organizada que salvamos no vídeo anterior. Precisamos também selecionar o nosso kernel (núcleo de execução) e executá-lo. Em seguida, rodamos a célula de carregamento.

Para configurar o ambiente do notebook e apontar para os arquivos organizados, utilizamos:

from pathlib import Path
import sys

import pandas as pd
import numpy as np

if Path.cwd().name == 'notebooks':
    raiz_projeto = Path.cwd().parent
else:
    raiz_projeto = Path.cwd()

sys.path.append(str(raiz_projeto / 'src'))

from visual_utils import (
    cores,
    grafico_linha_padrao,
    grafico_barra_padrao
)

caminho_base = raiz_projeto / "outputs" / "tabelas" / "base_bike_organizada.csv"
caminho_saida_serie_diaria = raiz_projeto / "outputs" / "tabelas" / "serie_diaria_bike.csv"

print('Base organizada:')
print(caminho_base)

Observemos que não estamos mais lendo diretamente o arquivo bike.csv. Agora, lemos a base que já passou por uma primeira preparação e foi salva em "outputs". Em "outputs", temos uma tabela chamada base_bike_organizada.csv. Essa é uma prática adequada em projetos de dados: em cada etapa, geramos um arquivo intermediário que ajuda na próxima etapa.

Carregando a base e garantindo os tipos corretos

Há um detalhe importante: mesmo que tenhamos convertido a coluna de data anteriormente, ao salvar e reler o CSV, o pandas pode carregar essa coluna como texto. Portanto, é importante realizar essa conversão novamente. Ao inspecionarmos head(), confirmamos se a base veio correta: temos a coluna data_hora, a coluna demanda e aquelas variáveis de calendário que criamos. Se estiver tudo correto, podemos começar a visualizar.

Na sequência, carregamos a base e garantimos que a coluna de data esteja no formato datetime:

df = pd.read_csv(caminho_base)

df['data_hora'] = pd.to_datetime(df['data_hora'])

print(f"Linhas: {df.shape[0]}")
print(f"Colunas: {df.shape[1]}")

df.head()

Visualizando a demanda horária

Vamos começar olhando a nossa série horária. Cada ponto do gráfico representa a demanda de bicicleta em uma hora específica. É possível aproximar para analisar com mais precisão. Para visualizar essa série:

fig = grafico_linha_padrao(
    df=df,
    x="data_hora",
    y="demanda",
    titulo="Demanda horária de bicicletas",
    xaxis="Data e hora",
    yaxis="Demanda",
    altura=500
)

fig.show()

Esse gráfico é informativo porque mostra muitos detalhes, mas também é bastante ruidoso: a demanda muda significativamente de uma hora para outra. Isso faz sentido, pois a demanda pode variar conforme a hora do dia. De manhã pode haver um comportamento; no almoço, outro; no fim da tarde, outro; e de madrugada, provavelmente muito diferente do período diurno.

Esse gráfico ajuda, mas pode confundir quando queremos enxergar uma tendência mais geral — é como observar de perto uma rua muito movimentada: vemos cada pessoa passando, mas fica difícil entender o fluxo geral da cidade. Propomos a seguinte pergunta: considerando uma apresentação para a área de negócios, esse gráfico horário conta bem a história geral da demanda ou tem detalhes em excesso? Convém avaliar esse ponto.

Agregando por dia e gerando a série diária

Em seguida, agregamos a série por dia para criar a série diária. O objetivo é facilitar a leitura. A lógica é simples: em vez de observar quantas bicicletas foram alugadas em cada hora, somamos o que ocorreu em cada dia. A pergunta muda de “qual foi a demanda em cada hora?” para “qual foi a demanda total em cada dia?”. Essa mudança de granularidade — de uma visão detalhada para uma visão agregada — é muito comum em séries temporais. Às vezes, o dado detalhado é importante, mas a visão agregada ajuda a enxergar melhor o comportamento geral. No notebook, essa agregação é feita assim:

serie_diaria = (
    df.assign(data=df['data_hora'].dt.floor("D"))
    .groupby("data", as_index=False)
    .agg(demanda_diaria=("demanda", "sum"))
    .rename(columns={"data": "data_hora"})
)

print(f"Quantidade de dias observados: {serie_diaria.shape[0]}")
print(f"Dias com demanda igual a zero: {(serie_diaria['demanda_diaria'] == 0).sum()}")

serie_diaria.head()

Agora, a tabela passa a ter uma linha por dia. Em muitos casos, é nesse nível que vamos fazer as previsões. A coluna demanda deixa de representar a demanda por horário e passamos a usar demanda_diaria para representar o total do dia.

Visualizando a demanda diária

Ao visualizar a demanda diária, observamos uma diferença clara em relação à visualização horária. A série fica menos poluída: perdemos o detalhe hora a hora, mas ganhamos uma visão melhor do comportamento geral. Essa é uma troca inevitável. Não existe uma granularidade sempre melhor; depende da pergunta a ser respondida. Se quisermos entender o pico de operação ao longo do dia, por exemplo, a série horária é importante. Para visualizar a série diária:

fig = grafico_linha_padrao(
    df=serie_diaria,
    x="data_hora",
    y="demanda_diaria",
    titulo="Demanda diária de bicicletas",
    xaxis="Data",
    yaxis="Demanda diária",
    altura=500
)

fig.show()

Se quisermos entender a evolução geral da demanda, a série diária pode ser mais útil. Nela, conseguimos enxergar melhor os períodos de alta e baixa e o movimento mais amplo da série. Isso nos prepara para a próxima aula, na qual falaremos de tendência, sazonalidade e ruído.

Comparando os dois gráficos exibidos, qual utilizaríamos para explicar o comportamento geral dessa demanda?

Analisando a demanda média por hora do dia

Vamos realizar uma agregação por hora do dia: uma demanda média por hora. Agrupamos os dados por hora e calculamos a média. Podemos pausar e analisar o código em detalhe em caso de dúvidas. Observando esse gráfico, percebemos o início de uma rotina: em um problema de bicicletas, é possível haver relação com deslocamentos, trabalho, estudo e lazer, com horários de pico e de menor movimento. Se aparece um pico em alguma hora, isso pode significar que a demanda não está distribuída de forma aleatória ao longo do dia; ela segue um comportamento. Essa já é uma informação muito valiosa. No código, fazemos essa agregação e geramos o gráfico de barras:

demanda_por_hora = (
    df.groupby("hora", as_index=False)
    .agg(demanda_media=("demanda", "mean"))
)

fig = grafico_barra_padrao(
    df=demanda_por_hora,
    x="hora",
    y="demanda_media",
    titulo="Demanda média por hora do dia",
    labels={
        "hora": "Hora do dia",
        "demanda_media": "Demanda média"
    },
    altura=450
)

fig.show()

Para uma operação real, conhecer os horários de maior demanda ajuda a posicionar melhor as bicicletas, planejar manutenções e entender quando o sistema precisa estar mais preparado, isto é, quando há mais bicicletas nas ruas. Se fôssemos responsáveis por uma operação de aluguel de bicicletas, como essa informação de horário auxiliaria? Ela pode apoiar diversas decisões operacionais.

Analisando a demanda média por dia da semana

Agora, vamos observar a demanda média por dia da semana. A pergunta passa a ser: a demanda muda conforme o dia da semana? Estamos realizando agregações simples, usando a média, e definindo a ordem dos dias (por meio de um mapeamento) para manter a apresentação organizada. Agrupamos a base por dia da semana e calculamos a demanda média, definindo a ordem para que a visualização seja consistente. Esse gráfico nos ajuda a entender se existe um padrão semanal: a demanda em dias úteis é parecida com a demanda no fim de semana? Parece mais ligada a uma rotina de trabalho ou a uma rotina de lazer? No notebook, aplicamos a seguinte transformação e plotagem:

ordem_dias = [
    "segunda-feira",
    "terça-feira",
    "quarta-feira",
    "quinta-feira",
    "sexta-feira",
    "sábado",
    "domingo"
]

demanda_por_dia_semana = (
    df.groupby("nome_dia_semana", as_index=False)
    .agg(demanda_media=("demanda", "mean"))
    .assign(nome_dia_semana=lambda x: pd.Categorical(x["nome_dia_semana"], categories=ordem_dias, ordered=True))
    .sort_values("nome_dia_semana")
)

fig = grafico_barra_padrao(
    df=demanda_por_dia_semana,
    x="nome_dia_semana",
    y="demanda_media",
    titulo="Demanda média por dia da semana",
    labels={
        "nome_dia_semana": "Dia da semana",
        "demanda_media": "Demanda média"
    },
    altura=450
)

fig.update_xaxes(categoryorder='array', categoryarray=ordem_dias)
fig.show()

Esses procedimentos são importantes porque, em séries temporais, padrões que se repetem no calendário podem indicar algum tipo de sazonalidade.

Analisando a demanda média por mês

Vamos analisar também padrões por mês. A ideia é ampliar a pergunta: existem meses com demanda maior? Observamos que, em certos trechos, a série sobe e atinge picos no meio do ano. Talvez isso se relacione com clima; pode ser que pessoas pedalem mais quando está mais quente. No entanto, isso depende do local e da cultura da população. O gráfico, por si só, não prova nada, mas levanta hipóteses relevantes. Se alguns meses apresentam demanda média maior, há um componente sazonal. Não discutiremos quais meses especificamente, pois, como mencionamos, meses mais frios ou mais quentes podem se comportar de forma diferente, mas não vamos concluir nada a esse respeito porque os dados não são do Brasil e não identificamos o país. Portanto, não inferiremos esse tipo de informação. No dia a dia, ao lidar com o problema de negócio, podemos trazer esse ponto para a discussão e levantar hipóteses, como a de que variáveis climáticas podem ajudar a explicar a demanda. Para inspecionar padrões por mês:

demanda_por_mes = (
    df.groupby("mes", as_index=False)
    .agg(demanda_media=("demanda", "mean"))
)

fig = grafico_barra_padrao(
    df=demanda_por_mes,
    x="mes",
    y="demanda_media",
    titulo="Demanda média por mês",
    labels={
        "mes": "Mês",
        "demanda_media": "Demanda média"
    },
    altura=450
)

fig.show()

Antecipando modelos e salvando a série diária

Mais adiante, testaremos modelos que usarão apenas o passado da série e também modelos que utilizarão variáveis externas. Veremos, em geral, como eles se comportam.

Salvamos essa série diária. Onde a salvamos? Vamos abrir: serie_diaria_bike.csv. Tudo o que vamos fazendo, vamos salvando, porque utilizaremos depois. E por fim, salvamos a série diária com:

serie_diaria.to_csv(
    caminho_saida_serie_diaria,
    index=False,
    encoding='utf-8-sig'
)
print('Série diária salva em:')
print(caminho_saida_serie_diaria)

Resumindo a aula e registrando o trabalho

Neste notebook, não fizemos previsão. Não treinamos um modelo, não escolhemos parâmetros e não comparamos métricas. O foco foi observar a série. Vimos que a demanda horária apresenta bastante variação, que a série diária nos ajuda a enxergar melhor o comportamento geral e que há padrões interessantes por hora, por dia da semana e por mês. Esse é o papel da análise visual: levantar hipóteses e promover a discussão antes de modelar, seguindo exatamente a ideia do slide “ver antes de modelar”.

Agora, vamos registrar o commit. Deixamos o fluxo pronto: voltar uma pasta, realizar o commit, depois retornar para a pasta e ativar a venv. Vamos abrir para efetuar o commit. O commit foi realizado e retornamos ao ponto anterior, com tudo registrado corretamente. O commit ficará disponível para acesso.

Encerrando a aula e apontando próximos passos

Neste vídeo, encerramos a Aula 1, olhando a série temporal pela primeira vez de forma completa. Utilizamos o slide “ver antes de modelar” como guia para aplicar essa ideia no notebook e observamos a demanda horária e diária, entre outros pontos. No próximo vídeo, começaremos a nomear alguns comportamentos que passaremos a enxergar, como tendência, sazonalidade e ruído.

Nos vemos no próximo vídeo.

Sobre o curso Séries temporais com Python: modelagem e previsão com ARIMA e SARIMAX

O curso Séries temporais com Python: modelagem e previsão com ARIMA e SARIMAX possui 390 minutos de vídeos, em um total de 71 atividades. Gostou? Conheça nossos outros cursos de Machine Learning & Deep Learning em Dados, ou leia nossos artigos de Dados.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Machine Learning & Deep Learning acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas