Mais que Tendências: Como o Prophet Torna a Previsão de Séries Temporais uma Vantagem Competitiva
📌 Aviso aos Leitores: Este artigo mergulha em detalhes técnicos da previsão de séries temporais com o Prophet – uma ferramenta poderosa para quem trabalha com dados. Mas, não se engane: os conceitos aqui vão muito além do universo técnico.
Para gestores, executivos e líderes que buscam vantagem competitiva, acompanhar até o final será uma oportunidade de enxergar o potencial estratégico que a análise preditiva traz para decisões mais ágeis e inteligentes. Este é um convite para descobrir como transformar dados em insights que realmente antecipam o futuro do negócio.
Nota: Este post foi escrito e publicado originalmente em 2017, e agora estou trazendo ele de volta com alguns pequenos ajustes na comunicação para destacar a relevância contínua das previsões temporais no apoio a decisões estratégicas.
Para um estudo de caso sobre previsão de índices de criminalidade em uma determinada região para os próximos seis meses, optei pela abordagem de Previsão de Série Temporal (Time-Series Forecast), utilizando a biblioteca Prophet, desenvolvida pelo Facebook. Essa ferramenta se mostrou uma solução acessível e eficiente para gerar previsões precisas e alinhadas com o objetivo do estudo.
Neste post, replico a análise usando o mesmo conjunto de dados de crimes, conduzindo o leitor por cada etapa: desde a Análise Exploratória dos Dados (ou EDA), até a aplicação do método de previsão temporal com o Prophet, apresentando o resultado final. Compartilho aqui cada passo do processo de maneira clara e prática.
Sinalizo aqui que o objetivo deste artigo não é estressar todas as possibilidades disponíveis para otimização do resultado. Para isso, recomendo ler a documentação da biblioteca para que possa explorar as diversas opções de otimização. Além disso é preciso levar em consideração as particularidades de cada cenário, como, por exemplo, sazonalidade, feriados etc.
Instalação da biblioteca Prophet:
pip install fbprophet
Bem simples! 😎
Importação das bibliotecas necessárias:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# Necessário para apresentação correta dos gráficos no Jupyter Notebook
%matplotlib inline
# Configuração do estilo dos gráficos que serão criados
plt.style.use('fivethirtyeight')
Leitura do arquivo de dados:
df = pd.read_csv('./Crimes_2011_to_present.csv', index_col=False, error_bad_lines=False, engine='python')
Explicando os parâmetros:
index_col=False: configurado para o Pandas não utilizar o ID do dataset como índice.
error_bad_lines=False: utilizado para caso a leitura do dataset encontre alguma linha com problema ele simplesmente ignorar.
engine=’python’: utilizado apenas por é hábito. No caso de números decimais contidos no dataset ele já importa com o número de casas decimais corretos.
Remoção de colunas que não serão utilizadas:
df.drop(['Case.Number', 'IUCR', 'X.Coordinate', 'Y.Coordinate', 'Block', 'Updated.On', 'Year', 'FBI.Code', 'Beat', 'Ward', 'Community.Area', 'Location'], inplace=True, axis=1)
Para facilitar a vida no futuro eu converti a coluna Date para o tipo correto e a configurei para ser utilizada como índice do dataset:
df['Date'] = pd.to_datetime(df['Date'])
df.index = pd.DatetimeIndex(df['Date'])
Agora os demais tratamentos de dados para realizar as análises:
# Criei duas novas colunas de Ano e Mês
df['Year'] = pd.DatetimeIndex(df['Date']).year
df['Month'] = pd.DatetimeIndex(df['Date']).month
# ---
# Converti o datatype de duas colunas para booleano
df['Arrest'] = df['Arrest'].astype('bool')
df['Domestic'] = df['Domestic'].astype('bool')
# ---
# NaN se não converter
df['Latitude'] = pd.to_numeric(df['Latitude'], errors='coerce')
df['Longitude'] = pd.to_numeric(df['Longitude'], errors='coerce')
df['District'] = pd.to_numeric(df['District'], errors='coerce')
# ---
# Remove os NaN
df = df[df['Latitude'].notnull()]
df = df[df['Longitude'].notnull()]
df = df[df['District'].notnull()]
# ---
# Seleciona as top 20 entradas
loc_to_change = list(df['Location.Description'].value_counts()[20:].index)
desc_to_change = list(df['Description'].value_counts()[20:].index)
# ---
# Função auxiliar para converter para 'OTHER' o que não estiver nos top 20
def parse_location_names(location):
if location in loc_to_change:
return 'OTHER'
else:
return location
df['Location.Description'] = df['Location.Description'].map(parse_location_names)
# ---
# Converter para 'OTHER' o que não estiver nos top 20
df.loc[df['Description'].dropna().isin(desc_to_change), df.columns == 'Description'] = 'OTHER'
# ---
# Converte features para o tipo categórico
df['Primary.Type'] = pd.Categorical(df['Primary.Type'])
df['Description'] = pd.Categorical(df['Description'])
df['Location.Description'] = pd.Categorical(df['Location.Description'])
Com isso nós terminamos a leitura e preparação dos dados e estamos prontos para a próxima etapa! 😎
Preparar dados é como tentar organizar um guarda-roupa bagunçado: você encontra coisas que nem sabia que tinha e outras que prefere fingir que nunca viu! - Desconhecido
📊 Exploração e Visualização dos dados
A exploração e visualização dos dados têm como principal objetivo revelar padrões, tendências e insights ocultos que, de outra forma, seriam difíceis de perceber.
Ao transformar dados brutos em gráficos e análises visuais, é possível identificar sazonalidades, anomalias e correlações relevantes que apoiam a tomada de decisões estratégicas. Esse processo não só facilita a compreensão do comportamento histórico dos dados, mas também prepara o terreno para previsões mais precisas, possibilitando uma gestão proativa e fundamentada em informações reais.
💡 Em contextos como segurança pública, por exemplo, essas visualizações permitem entender melhor a dinâmica dos crimes e alocar recursos de forma mais eficaz.
1. Frequência de crimes por mês ao longo dos anos
Vamos criar um gráfico que apresenta a quantidade de crimes registrados por mês entre 2011 e 2015.
O objetivo é identificar padrões e sazonalidades que podem surgir nos dados históricos. Ao observar as flutuações mensais, é possível entender melhor se há períodos específicos de aumento ou diminuição de incidentes, o que fornece uma base importante para a previsão de índices futuros.
Esse tipo de análise inicial é essencial para capturar tendências e fornecer contexto sobre o comportamento do fenômeno ao longo do tempo, permitindo que a previsão seja mais precisa e fundamentada.
plt.figure(figsize=(11, 5))
df.resample('M').size().plot(legend=False)
plt.title('Number of crimes per month (2011 - 2015)')
plt.xlabel('Months')
plt.ylabel('Number of crimes')
plt.show()
2. Média móvel acumulada dos crimes ao longo dos anos
O gráfico abaixo exibe a soma acumulada de crimes em uma janela móvel de 365 dias ao longo do período de 2011 a 2015.
O objetivo é observar a evolução das tendências anuais nos índices de criminalidade, suavizando flutuações diárias e sazonais para destacar mudanças de longo prazo.
Com essa visão, é possível detectar padrões consistentes de aumento ou diminuição na atividade criminal ao longo dos anos, o que oferece insights valiosos sobre a direção geral do fenômeno.
Essa análise ajuda a prever potenciais variações anuais, fornecendo uma base mais sólida para as projeções futuras.
plt.figure(figsize=(11, 5))
df.resample('D').size().rolling(365).sum().plot(legend=False)
plt.title('Rolling sum of all crimes from 2011 - 2015')
plt.ylabel('Number of crimes')
plt.xlabel('Days')
plt.show()
3. Média móvel acumulada dos crimes ao longo dos anos, por tipo
O conjunto de gráficos abaixo apresenta a soma acumulada anual (com uma janela móvel de 365 dias) para cada tipo específico de crime registrado ao longo do período analisado.
A visualização individual de cada categoria de crime permite observar padrões, tendências e sazonalidades específicas por tipo de delito.
Essa abordagem ajuda a identificar quais tipos de crimes estão em aumento ou queda ao longo do tempo, oferecendo uma visão detalhada e crucial para decisões estratégicas de segurança e alocação de recursos.
A análise por categoria ajuda a entender melhor o comportamento de cada tipo de crime para a criação de políticas preventivas mais eficazes e direcionadas.
crimes_count = df.pivot_table('ID', aggfunc=np.size, columns='Primary.Type', index=df.index.date, fill_value=0)
crimes_count.index = pd.DatetimeIndex(crimes_count.index)
plo = crimes_count.rolling(365).sum().plot(figsize=(12, 30), subplots=True, layout=(-1, 3), sharex=False, sharey=False)
4. Frequência de crimes por dia da semana
Este gráfico exibe a quantidade total de crimes registrados para cada dia da semana.
O objetivo é identificar se existem dias específicos com maior ou menor incidência de crimes, fornecendo uma visão sobre a distribuição semanal dos incidentes.
Compreender essas variações ajuda a direcionar recursos e planejar ações de prevenção em dias de maior risco.
Essa análise é valiosa para a tomada de decisões operacionais, permitindo que estratégias de segurança sejam ajustadas com base em padrões semanais e atendam de forma mais eficaz às demandas de cada dia.
days = ['Monday','Tuesday','Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
df.groupby([df.index.dayofweek]).size().plot(kind = 'barh')
plt.title('Number of crimes by day of week')
plt.xlabel('Number of Crimes')
plt.ylabel('Day of Week')
plt.yticks(np.arange(7), days)
plt.show()
5. Frequência de crimes por mês
Este gráfico mostra a quantidade total de crimes registrada em cada mês do ano.
O objetivo é identificar padrões sazonais, destacando se existem meses específicos com maior ou menor incidência de crimes.
Com essa visão mensal, é possível observar tendências sazonais, o que permite um planejamento mais estratégico para ações preventivas e alocação de recursos ao longo do ano.
Esse tipo de análise é especialmente útil para prever e preparar respostas para períodos de maior atividade criminal, auxiliando na formulação de políticas de segurança mais eficazes e baseadas em dados.
Recomendados pelo LinkedIn
df.groupby([df.index.month]).size().plot(kind = 'barh')
plt.title('Number of crimes by month')
plt.xlabel('Number of Crimes')
plt.ylabel('Month')
plt.show()
6. Frequência de crimes por tipo
Este gráfico mostra a quantidade total de crimes, organizada por tipo de crime.
O objetivo é identificar quais tipos de crime são mais comuns e quais são menos frequentes dentro do período analisado.
Essa visão permite priorizar recursos e atenção para os tipos de crime mais prevalentes, ajudando a direcionar políticas de segurança e estratégias de prevenção de maneira mais eficaz.
Com esse entendimento, gestores podem focar em ações específicas para os crimes de maior incidência, otimizando esforços de segurança pública e planejamento preventivo.
plt.figure(figsize=(8, 10))
df.groupby([df['Primary.Type']]).size().sort_values(ascending=True).plot(kind = 'barh')
plt.title('Number of crimes by Type')
plt.xlabel('Number of crimes')
plt.ylabel('Primery Type')
plt.show()
7. Frequência de crimes por tipo e dia da semana
Neste gráfico de mapa de calor, visualizamos a frequência de diferentes tipos de crimes por dia da semana, com uma escala padronizada para destacar variações significativas. A análise utiliza agrupamento hierárquico para organizar os dados, facilitando a identificação de padrões e relações entre tipos de crimes e os dias em que eles ocorrem com maior frequência.
O objetivo desta visualização é detectar concentrações e anomalias específicas de cada tipo de crime ao longo da semana, possibilitando uma visão detalhada das interações entre a natureza dos incidentes e os dias em que são mais recorrentes. Isso permite decisões estratégicas, como ajustar recursos para dias de alta incidência para tipos de crimes específicos
def scale_df(df,axis=0):
return (df - df.mean(axis=axis)) / df.std(axis=axis)
def plot_hmap(df, ix=None, cmap='bwr'):
if ix is None:
ix = np.arange(df.shape[0])
plt.imshow(df.iloc[ix,:], cmap=cmap)
plt.colorbar(fraction=0.03)
plt.yticks(np.arange(df.shape[0]), df.index[ix])
plt.xticks(np.arange(df.shape[1]))
plt.grid(False)
plt.show()
def scale_and_plot(df, ix = None):
df_marginal_scaled = scale_df(df.T).T
if ix is None:
ix = AC(4).fit(df_marginal_scaled).labels_.argsort()
cap = np.min([np.max(df_marginal_scaled.as_matrix()), np.abs(np.min(df_marginal_scaled.as_matrix()))])
df_marginal_scaled = np.clip(df_marginal_scaled, -1*cap, cap)
plot_hmap(df_marginal_scaled, ix=ix)
def normalize(df):
result = df.copy()
for feature_name in df.columns:
max_value = df[feature_name].max()
min_value = df[feature_name].min()
result[feature_name] = (df[feature_name] - min_value) / (max_value - min_value)
return result
dayofweek_by_location = df.pivot_table(values='ID', index='Location.Description', columns=df.index.dayofweek, aggfunc=np.size).fillna(0)
dayofweek_by_type = df.pivot_table(values='ID', index='Primary.Type', columns=df.index.dayofweek, aggfunc=np.size).fillna(0)
location_by_type = df.pivot_table(values='ID', index='Location.Description', columns='Primary.Type', aggfunc=np.size).fillna(0)
from sklearn.cluster import AgglomerativeClustering as AC
plt.figure(figsize=(17,17))
scale_and_plot(dayofweek_by_type)
8. Frequência de crimes por tipo e localidade
Este mapa de calor apresenta a frequência de ocorrência de crimes em diferentes localizações, categorizada por tipo de crime.
O objetivo é visualizar rapidamente onde certos tipos de crimes ocorrem com maior ou menor frequência, ajudando a identificar padrões geográficos para cada categoria de incidente.
Essa análise é fundamental para otimizar a alocação de recursos em áreas específicas e desenvolver estratégias de prevenção focadas em localidades de maior risco.
Ao destacar locais com maior incidência por tipo de crime, esta análise oferece insights que permitem uma abordagem mais direcionada e eficiente na gestão de segurança e políticas públicas.
df2 = normalize(location_by_type)
ix = AC(3).fit(df2.T).labels_.argsort()
plt.figure(figsize=(17,13))
plt.imshow(df2.T.iloc[ix,:], cmap='Reds')
plt.colorbar(fraction=0.03)
plt.xticks(np.arange(df2.shape[0]), df2.index, rotation='vertical')
plt.yticks(np.arange(df2.shape[1]), df2.columns)
plt.title('Location frequency for each crime')
plt.grid(False)
plt.show()
Agora que nós já conseguimos identificar alguns padrões através dos dados, vamos partir para o tópico principal que é a projeção do volume de crimes utilizando a biblioteca Prophet.
Criar visualizações de dados é como fazer arte moderna: você sabe que fez certo quando alguém para, olha e pergunta, 'O que exatamente isso quer dizer?' - Desconhecido
🔮 Time-Series Forecasting com Prophet
O objetivo das técnicas de forecasting é antecipar eventos futuros com base em dados históricos, fornecendo previsões que auxiliam na tomada de decisões estratégicas. Essas técnicas permitem que empresas e gestores transformem dados passados em insights acionáveis para o futuro, identificando tendências, sazonalidades e possíveis variações.
Ao projetar cenários, o forecasting permite uma gestão mais proativa e preparada para responder a desafios e oportunidades, seja em planejamento de recursos, otimização de operações ou definição de estratégias de mercado.
Em resumo, trata-se de um recurso poderoso para reduzir incertezas e guiar decisões com mais segurança e precisão.
***
Neste próximo gráfico nós visualizamos a quantidade de crimes ao longo do tempo para o Distrito 1.0, preparando o terreno para a aplicação de técnicas de previsão. Esse primeiro passo é importante para entender o comportamento histórico dos crimes no distrito escolhido, permitindo observar tendências e padrões sazonais específicos que podem influenciar o forecast.
A visualização da série temporal para este distrito facilita a interpretação dos dados iniciais e fornece uma base sólida para a etapa de modelagem preditiva, que será realizada em seguida com a biblioteca Prophet. Essa análise inicial garante que estamos lidando com um conjunto de dados limpo e relevante para a previsão.
from fbprophet import Prophet
fc_df = df[['District']]
fc_df.reset_index(inplace=True)
fc_df = df.groupby(['Date', 'District']).size()
fc_df = fc_df.reset_index()
fc_df.columns = ['ds', 'District', 'y']
# Foi escolhido o distrito 1.0 para realizar o forecast
# =====================================================
aux = fc_df[fc_df['District']==1.0]
aux = aux[['ds', 'y']]
ax = aux.set_index('ds').plot(figsize=(16, 8))
ax.set_ylabel('Number of Crimes')
ax.set_xlabel('Date')
plt.show()
***
Neste próximo passo aplicamos o modelo de previsão Prophet aos dados históricos de crimes no Distrito 1.0.
O modelo é ajustado com os dados disponíveis, e em seguida, são geradas novas datas futuras para os próximos seis meses, permitindo a criação de uma projeção. O objetivo é prever o número de crimes esperados para esses meses, fornecendo estimativas centrais (yhat) e intervalos de confiança (yhat_lower e yhat_upper) que indicam a faixa provável dos valores futuros.
Essa previsão oferece uma visão antecipada sobre possíveis variações nos índices de criminalidade no distrito, auxiliando na alocação de recursos e no planejamento de estratégias preventivas com base em dados projetados.
my_model = Prophet()
my_model.fit(aux)
future_dates = my_model.make_future_dataframe(periods=6, freq='MS')
forecast = my_model.predict(future_dates)
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail()
***
O comando abaixo gera uma visualização detalhada dos componentes individuais da previsão, permitindo uma análise mais profunda dos fatores que influenciam a série temporal.
Com ele, observamos a decomposição dos dados em tendências gerais, padrões sazonais (como variações anuais, mensais ou semanais), e outros fatores que o modelo Prophet identificou como relevantes para o forecast.
Essa visualização é essencial para entender os principais impulsionadores das previsões, fornecendo insights valiosos sobre os elementos que contribuem para o comportamento futuro dos dados.
Para gestores e analistas, isso permite uma interpretação mais transparente das previsões e ajuda a validar a aplicabilidade dos resultados no contexto de decisão estratégica.
my_model.plot_components(forecast)
***
Neste próximo passo é realizada a validação cruzada do modelo para avaliar a precisão das previsões de crimes em um horizonte de 365 dias.
A validação cruzada é uma técnica que permite medir a capacidade preditiva do modelo ao testar sua performance em diferentes partes do conjunto de dados. Ao dividir os dados em intervalos e comparar as previsões com os valores reais é possível identificar o grau de precisão e ajustar o modelo conforme necessário.
💡 Esse processo é fundamental para garantir que as previsões sejam confiáveis e possam ser utilizadas com segurança.
from fbprophet.diagnostics import cross_validation
df_cv = cross_validation(my_model, horizon = '365 days')
df_cv.head()
df_cv[['y', 'yhat']].plot(figsize=(16, 6))
◎ Conclusão
Com base nos resultados do forecast visualizados no gráfico, é possível observar que o modelo Prophet foi capaz de acompanhar as variações gerais na quantidade de crimes ao longo do tempo. A linha vermelha, que representa as previsões do modelo (yhat), segue de perto os padrões da série histórica real (y), capturando as flutuações e tendências sazonais do distrito selecionado. Essa proximidade entre os dados reais e as previsões indica que o modelo é eficaz para esse tipo de análise e fornece uma base confiável para antecipar cenários futuros.
Ao entender esses padrões, gestores e tomadores de decisão podem se preparar melhor para variações sazonais, alocar recursos de forma mais eficiente e planejar estratégias preventivas com maior precisão. O uso de análise preditiva não só permite uma gestão mais proativa, mas também transforma dados históricos em uma ferramenta poderosa para a tomada de decisões estratégicas.
Essa aplicação do Prophet demonstra como técnicas de forecasting podem ser aliadas valiosas na construção de uma abordagem orientada por dados. Em um mundo cada vez mais complexo e dinâmico, antecipar o comportamento futuro com precisão é um diferencial competitivo que pode fazer a diferença. Espero que esta análise inspire novas aplicações de previsões temporais e reforce a importância de métodos preditivos para apoiar a inovação e a eficiência em diversos setores.
Para todos que buscam entender e aproveitar o poder dos dados — seja na estratégia ou na prática — continuaremos trazendo conteúdos que aprofundam o impacto dos dados em decisões e inovação. Na próxima publicação, vamos explorar novos caminhos para transformar dados em valor real, oferecendo insights valiosos para quem deseja se aprofundar ainda mais neste universo.
Dados | BI | PowerBI | SQL | Python | ETL | DAX | AWS | GCP
2 mMuito rico e didático!! Parabéns