Deploy de modelos com Python + Shinylive + GitHub gastando ZERO reais

Colocar modelos em produção pode ser um grande desafio. Lidar com custos monetários, infraestrutura operacional e complexidades de códigos e ferramentas pode acabar matando potenciais projetos.

Uma solução que elimina todos estes obstáculos é a recém lançada Shinylive. Esta ferramenta permite transformar um app Shiny em uma estrutura compacta de arquivos que pode ser hospedada com se fosse um site estático, mas com a possibilidade de rodar códigos de Python no próprio navegador. Em outras palavras, é possível colocar um modelo em produção sem a necessidade de um servidor (computador) por trás.

Aprenda a coletar, processar e analisar dados na formação de Do Zero à Análise de Dados com Python.

As vantagens de utilizar o Shinylive para deploy de modelos são:

  • Custo monetário igual a zero
  • Simples e rápido de utilizar
  • Permite rodar Python no navegador
  • Elimina necessidade de configurar servidores

Dessa forma, basta ter um app Shiny funcional com um modelo de previsão embutido. A parte chata, burocrática e complicada de colocar este modelo em produção fica sob responsabilidade do Shinylive, que converte o app em uma estrutura de arquivos “deployable”, restando apenas hospedá-los em algum local (como o GitHub Pages).

Algumas desvantagens de utilizar o Shinylive para de deploy de modelos são:

  • Não há gerenciamento nativo de logs e registros de execução da aplicação
  • Tempo de inicialização da aplicação costuma ser demorado
  • Realizar requisições de APIs externas na aplicação não é tão simples

Dito isso, vamos exemplificar como colocar um modelo de previsão para o preço do petróleo Brent em produção. Utilizaremos o Python para desenvolver o modelo, o Shiny para criar uma interface interativa com parâmetros do modelo, o Shinylive para fazer o deploy e o GitHub Pages para hospedar os arquivos e gerar um link de acesso.

Passo 01: Shiny App com modelo de exemplo

Crie um modelo de previsão e coloque o mesmo em um app Shiny, como abaixo:

Para obter o código e o tutorial deste exercício faça parte do Clube AM e receba toda semana os códigos em R/Python, vídeos, tutoriais e suporte completo para dúvidas.
app.py
# Bibliotecas
from shiny import reactive
from shiny.express import input, render, ui
import pandas as pd
import numpy as np
import statsmodels.formula.api as smf
import statsmodels.api as sm
from statsmodels.tsa.api import VAR
import plotnine as p9
from pathlib import Path


# Dados
# url_base = "http://www.ipeadata.gov.br/api/odata4/ValoresSerie(SERCODIGO="
# url_p = f"{url_base}'EIA366_PBRENT366')"
# url_c = f"{url_base}'GM366_EREURO366')"
# 
# dados_petroleo = pd.DataFrame.from_records(pd.read_json(url_p).value.values)
# dados_cambio = pd.DataFrame.from_records(pd.read_json(url_c).value.values)
# 
# petroleo = pd.DataFrame(
#   data = {"brent": dados_petroleo.VALVALOR.astype(float).apply(np.log).values},
#   index = pd.to_datetime(dados_petroleo.VALDATA, utc = True).rename("data")
#   )
# cambio = pd.DataFrame(
#   data = {"eurusd": dados_cambio.VALVALOR.astype(float).values},
#   index = pd.to_datetime(dados_cambio.VALDATA, utc = True).rename("data")
#   )
# 
# dados = petroleo.join(cambio, how = "outer")
# dados.insert(0, "data", pd.to_datetime(dados.index.date))
# dados.to_csv("app/dados.csv", index= False)

def ler_csv():
    infile = Path(__file__).parent / "dados.csv"
    df_dados = pd.read_csv(
      infile, 
      converters = {"data": lambda x: pd.to_datetime(x)}
      )
    return df_dados
  
dados = ler_csv()
dados.index = dados.data

# Inputs
ui.page_opts(title = ui.strong("Preço do Petróleo"), fillable = True)

with ui.sidebar(width = 300):
    ui.input_date(
        id = "periodo",
        label = "Filtrar data inicial:",
        value = (dados.index.max() - pd.offsets.BusinessDay(n = 90)).date(),
        min = dados.index.min().date(),
        max = dados.index.max().date(),
        language = "pt-BR"
        )
    ui.input_select(
        id = "modelo",
        label = "Modelo de previsão:",
        choices = ["Regressão linear", "SARIMAX", "VAR"],
        selected = "VAR"
        )
    ui.input_numeric(
        id = "horizonte", 
        label = "Horizonte de previsão:",
        value = 7,
        min = 1,
        max = 30,
        step = 1
        )
    ui.input_numeric(
        id = "intervalo", 
        label = "Intervalo de confiança:",
        value = 80,
        min = 1,
        max = 100,
        step = 1
        )
    ui.markdown("[Análise Macro](https://analisemacro.com.br/)")


# Outputs
with ui.navset_card_underline(title = "Previsão"):
    with ui.nav_panel("Gráfico"):
        @render.plot
        def grafico():

          df_previsao = (
            previsao()
            .assign(
              brent = lambda x: x["Previsão"],
              data = lambda x: pd.to_datetime(x["Período"])
              )
            )
          
          data_ini = input.periodo().strftime("%Y-%m-%d")
          df_filtrado = (
            dados
            .query("data >= @data_ini")
            .assign(brent = lambda x: np.exp(x.brent))
            )
            
          df_grafico = pd.concat([df_filtrado, df_previsao])

          from datetime import date

          def weekinmonth(dates):
              firstday_in_month = dates - pd.to_timedelta(dates.day - 1, unit='d')
              return (dates.day-1 + firstday_in_month.weekday()) // 7 + 1
          
          def custom_date_format2(breaks):
              res = []
              for x in breaks:
                  # First day of the year
                  if x.month == 1 and weekinmonth(x) == 1:
                      fmt = "%Y"
                  # Every other month
                  elif x.month != 1 and weekinmonth(x) == 1:
                      fmt = "%b"
                  else:
                      fmt = "%d"

                  res.append(date.strftime(x, fmt))

              return res
            
          return (
            p9.ggplot(df_grafico) +
            p9.aes(x = "data", y = "Previsão") +
            p9.geom_line(mapping = p9.aes(y = "brent"), size = 1.5) +
            p9.geom_line(size = 2, color = "blue") +
            p9.geom_ribbon(
              mapping = p9.aes(ymin = "Intervalo Inferior", ymax = "Intervalo Superior"),
              alpha = 0.25,
              fill = "blue"
              ) +
            p9.scale_x_date(
                date_breaks = "1 week" if df_grafico.shape[0] < 120 else "1 month", 
                labels = custom_date_format2, 
                date_minor_breaks = "1 week"
                ) +
            p9.theme_gray(base_size = 12) +
            p9.theme(axis_text_x = p9.element_text(angle = 90)) +
            p9.labs(y = "US$ / barril Brent", x = "")
          )
          

    with ui.nav_panel("Tabela"):
        @render.data_frame
        def tabela():
            return render.DataGrid(previsao(), editable = False, width = "100%")


# Servidor
@reactive.calc
def previsao():
  
    modelo = input.modelo()
    h = input.horizonte()
    ic = 1 - input.intervalo()/100

    df_treino = dados.dropna(subset = "brent").query("data > '2014-01-01'")
    df_ex = dados.query("data > @df_treino.data.max()")
    
    index_cenario = pd.date_range(
        start = df_ex.index.max() + pd.DateOffset(days = 1), 
        end = df_ex.index.max() + pd.DateOffset(days = h - df_ex.shape[0]), 
        freq = "D"
        )
    df_cenario = pd.DataFrame(
      data = {
        "eurusd": pd.Series(
          [df_ex.eurusd.iloc[-1]] * (h - df_ex.shape[0])
          ).values,
        "data": pd.to_datetime(index_cenario.date),
        "brent": np.nan
        },
      index = index_cenario
      )
    df_previsao = pd.concat([df_ex, df_cenario])
    
    
    if modelo == "Regressão linear":
        mod = smf.ols(formula = "brent ~ eurusd", data = df_treino).fit()
        prev = mod.get_prediction(df_previsao)
        df_prev = pd.DataFrame(
          data = {
            "Período": pd.to_datetime(df_previsao.index.date).astype(str),
            "Intervalo Inferior": np.exp(prev.conf_int(alpha = ic)[:,0]),
            "Previsão": np.exp(prev.predicted),
            "Intervalo Superior": np.exp(prev.conf_int(alpha = ic)[:,1]),
          }
        )
    elif modelo == "SARIMAX":
        mod = sm.tsa.statespace.SARIMAX(
          endog = df_treino.dropna()["brent"],
          exog = df_treino.dropna()["eurusd"],
          order = (1, 0, 0),
          seasonal_order = (0, 0, 1, 12),
          trend = "ct"
          ).fit()
        prev = mod.get_forecast(
          steps = df_previsao.shape[0],
          exog = df_previsao.eurusd
          )
        df_prev = pd.DataFrame(
          data = {
            "Período": pd.to_datetime(df_previsao.index.date).astype(str),
            "Intervalo Inferior": np.exp(prev.conf_int(alpha = ic)["lower brent"]),
            "Previsão": np.exp(prev.predicted_mean.values),
            "Intervalo Superior": np.exp(prev.conf_int(alpha = ic)["upper brent"]),
          }
        )
    else:
        mod = VAR(df_treino.drop(labels = "data", axis = "columns").dropna()).fit(
          maxlags = 15,
          trend = "ct"
          )
        prev = mod.forecast_interval(
          y = df_treino.drop(labels = "data", axis = "columns").dropna().values[-mod.k_ar:],
          steps = df_previsao.shape[0],
          alpha = ic
          )
        df_prev = pd.DataFrame(
          data = {
            "Período": pd.to_datetime(df_previsao.index.date).astype(str),
            "Intervalo Inferior": np.exp(prev[1][:,0]),
            "Previsão": np.exp(prev[0][:,0]),
            "Intervalo Superior": np.exp(prev[2][:,0]),
          }
        )
      
      
    return df_prev

Além do arquivo app.py da dashboard Shiny, ainda há o arquivo dados.csv neste projeto.

Passo 02: repositório no GitHub

  1. Acesse github.com
  2. Faça o cadastro/login se necessário
  3. Clique no botão “New” para criar um repositório
  4. Digite um nome no campo “Repository name*”
  5. Marque a opção “Add a README file”
  6. Clique no botão “Create repository”

Passo 03: ambiente de desenvolvimento

  1. Clique no botão “Code” no repositório GitHub criado
  2. Clique em “Codespaces”
  3. Clique em “Create codespace on main”
  4. Selecione os arquivos app.py e dados.csv do seu computador, clique e arraste os mesmos para o painel “EXPLORER” do VS Code web
  5. Instale a versão “0.3.0.9000” ou superior do Shinylive, rodando o comando no painel “TERMINAL” (Ctrl+' para exibir)
    pip install git+https://github.com/posit-dev/py-shinylive.git

Passo 04: deploy com Shinylive + GitHub Pages

  1. Rode o comando abaixo no painel “TERMINAL”
    shinylive export . docs
  2. Clique no painel “Source Control”
  3. Clique no botão “+” (“Stage All Changes”) ao lado de “Changes”
  4. Digite uma mensagem como “Deploy do meu modelo” no campo textual acima do botão “Commit”
  5. Clique no botão “Commit”
  6. Clique no botão “Sync Changes”
  7. Clique no botão “OK”
  8. Navegue até a página inicial do repositório GitHub criado anteriormente
  9. Clique no botão “Settings”
  10. Clique no menu “Pages” na seção “Code and automation”
  11. Clique no botão “None” na seção “Branch”
  12. Clique na opção “main”
  13. Clique no botão “/(root)” na seção “Branch”
  14. Clique na opção “/docs”
  15. Clique no botão “Save”

Ao final de todos estes passos o modelo estará em produção (deploy) e pode ser acessado pelo link:

  • https://USUARIO.github.io/REPOSITORIO/

Onde “USUARIO” é o nome de usuário da sua conta cadastrada no GitHub e REPOSITORIO é o nome do repositório criado anteriormente.

Conclusão

Colocar modelos em produção pode ser um grande desafio. Lidar com custos monetários, infraestrutura operacional e complexidades de códigos e ferramentas pode acabar matando potenciais projetos. Uma solução que elimina todos estes obstáculos é a recém lançada Shinylive. Neste artigo mostramos um exemplo com um modelo de previsão para o preço do petróleo Brent.

Quer aprender mais?

Clique aqui para fazer seu cadastro no Boletim AM e baixar o código que produziu este exercício, além de receber novos exercícios com exemplos reais de análise de dados envolvendo as áreas de Data Science, Econometria, Machine Learning, Macroeconomia Aplicada, Finanças Quantitativas e Políticas Públicas diretamente em seu e-mail.

Compartilhe esse artigo

Facebook
Twitter
LinkedIn
WhatsApp
Telegram
Email
Print

Comente o que achou desse artigo

Outros artigos relacionados

Resultado PNADc Trimestral - 3° Trimestre/2024

A Análise Macro apresenta os resultados da PNADc Trimestral do 3º trimestre de 2024, com gráficos elaborados em Python para coleta, tratamento e visualização de dados. Todo o conteúdo, disponível exclusivamente no Clube AM, foi desenvolvido com base nos métodos ensinados nos cursos da Análise Macro, permitindo aos assinantes acesso aos códigos e replicação das análises.

Análise exploratória para modelagem preditiva no Python

Antes de desenvolver bons modelos preditivos é necessário organizar e conhecer muito bem os dados. Neste artigo, damos algumas dicas de recursos, como gráficos, análises e estatísticas, que podem ser usados para melhorar o entendimento sobre os dados usando Python.

Como usar modelos do Sklearn para previsão? Uma introdução ao Skforecast

Prever séries temporais é uma tarefa frequente em diversas áreas, porém exige conhecimento e ferramentas específicas. Os modelos de machine learning do Sklearn são populadores, porém são difíceis de aplicar em estruturas temporais de dados. Neste sentido, introduzimos a biblioteca Skforecast, que integra os modelos do Sklearn e a previsão de séries temporais de forma simples.

Boletim AM

Receba diretamente em seu e-mail gratuitamente nossas promoções especiais e conteúdos exclusivos sobre Análise de Dados!

Boletim AM

Receba diretamente em seu e-mail gratuitamente nossas promoções especiais e conteúdos exclusivos sobre Análise de Dados!

como podemos ajudar?

Preencha os seus dados abaixo e fale conosco no WhatsApp

Boletim AM

Preencha o formulário abaixo para receber nossos boletins semanais diretamente em seu e-mail.