Exemplo de aplicação web com Shiny

  • R
  • Ciência de dados

Shiny é um sistema para desenvolvimento de aplicações web interativas usando o R, consistindo de um pacote do R (shiny) e um servidor web (shiny server). O código de uma aplicação Shiny permite estruturar tanto a interface com o usuário (frontend) quanto o processamento de dados, geração de visualizações e modelagem (backend). Ao rodarmos o código, criamos um servidor que envia páginas web que recebe informações do usuário e processa os dados, utilizando apenas o R.

O pacote shiny do R possui internamente um servidor web básico, geralmente utilizado para aplicações locais, permitindo somente uma aplicação por vez. O shiny server é um programa que é executado somente no Linux que permite o acesso a múltiplas aplicações simultaneamente.

Antes de começarmos a explorar o Shiny, precisamos instalar o pacote shiny no R:

install.packages("shiny")

Vamos usar o shiny para criar um dashboard básico, que funcionará da seguinte forma: O usuário escolhe um número \(n\) entre 1 e 100, e exibimos um histograma com uma amostra aleatória de \(n\) números.

Estrutura básica

O modelo básico de um dashboard no Shiny é

library(shiny)

ui <- fluidPage()

server <- function(input, output) {}

shinyApp(ui = ui, server = server)

Esse modelo se baseia em três componentes:

  • Um objeto (ui) que define a interface do usuário (frontend);
  • Uma função server() (backend); e
  • Uma chamada para a função shinyApp().

Se você executar esse script, uma nova janela/aba se abrirá no navegador e você terá acesso ao aplicativo. Ele estará sendo executado localmente: logo, você ainda não poderá acessá-lo pela internet.

Uma vez compreendida a estrutura de uma aplicação do Shiny, vamos começar a construir a nossa aplicação, começando com a interface com o usuário.

Frontend

Tudo o que será apresentado ao usuário está guardado no objeto ui, que nada mais é do que um código HTML. Por exemplo, se você verificar o conteúdo da variável ui do nosso primeiro exemplo no interpretador do R poderá ver que o seu conteúdo é

<div class="container-fluid"></div>

ou seja, apenas um contâiner vazio do HTML (é por isso que se você executou o script 1 provavelmente ficou desapontado por obter apenas uma tela em branco!).

A função fluidPage() é utilizada pelo Shiny para criar um contâiner que automaticamente ajusta as dimensões da janela do navegador do usuário (atualmente ele usa o Bootstrap para isso). Os elementos da interface do usuário são então colocados dentro dessa função. Por exemplo:

library(shiny)

ui <- fluidPage(
  titlePanel("Título"),

  sidebarLayout(
    sidebarPanel("Painel lateral"),
    mainPanel("Painel principal")
  )
)

server <- function(input, output) {}

shinyApp(ui = ui, server = server)

2019-05-22T1452-shiny_ui_example.png

Você pode checar agora que o conteúdo da variável ui nesse caso é:

<div class="container-fluid">
  <h2>Título</h2>
  <div class="row">
    <div class="col-sm-4">
      <form class="well">Painel lateral</form>
    </div>
    <div class="col-sm-8">Painel principal</div>
  </div>
</div>

Para uma lista completa das funções que definem o layout da aplicação, olhe aqui.

Funções de Entrada (Input functions)

Na prática, o layout da aplicação define apenas como ela é apresentada ao usuário. Para podermos interagir com a aplicação precisamos das entradas (inputs). inputs são componentes que possibilitam a interação do usuário com o aplicativo. Eles recebem um valor escolhido pelo usuário e o envia para o script interno (backend), funcionando como um intermediário entre o programa e o usuário. No nosso exemplo, queremos que usuário escolha um valor dentro de um intervalo já conhecido. Nesse caso podemos usar como método de entrada um slider:

ui <- fluidPage(
  sliderInput(inputId = "classes",
	      label = "Escolha um número",
	      value = 50, min = 1, max = 100)
)
  • O argumento inputId é um identificador único do elemento, que servirá para interligar os dados entre os diferentes componentes da aplicação;
  • label é um texto para explicar ao usuário o a função do elemento;
  • value é o valor padrão mostrada inicialmente na criação da aplicação; e
  • min e max São respectivamente o menor e o maior valor assumido pela variável

Se você executar a aplicação até este ponto, você verá no seu navegador algo parecido com isto:

2019-05-22T1452-shiny_first_template.png

Para uma lista completa das funções que definem o layout da aplicação, olhe aqui.

Agora que já sabemos como enviar objetos para o backend, precisamos saber como receber as suas saídas.

Funções de Saída (Output functions)

As saídas possíveis dentro de uma aplicação Shiny são diversas, como textos, gráficos e tabelas. Para cada tipo de saída possível existe uma função _Output(). No nosso exemplo, a saída deve ser um gráfico (histograma). Para isso o Shiny possui a função plotOutput(), que gera um elemento para acomodar o gráfico. Vamos colocá-lo no painel principal da nossa aplicação:

mainPanel(
      plotOutput(outputId = "histPlot")
    )

As diversão funções que definem os tipos de saída são listadas aqui.

Assim como as funções de entrada, funções de saída recebem um identificador, o outputId. Esse argumento recebe uma string que representa o nome utilizado no backend para se referir a essa saída. Consulte o help() de cada função para saber mais sobre os argumentos adicionais.

Criadas as entradas e as saídas da aplicação, precisamos saber manipulá-los no backend.

Backend

Com a interface do usuário estruturada, precisamos agora implementar a função server(). Nela, colocaremos as instruções para gerar as saídas que nós vemos no frontend a partir dos valores das entradas que o usuário escolher.

A primeira coisa que precisamos fazer é defini-la. A função server() será sempre uma função que recebe dois argumentos: input e output.

Precisamos seguir três regras:

  1. Todos as saídas estão em uma lista chamada output. Assim, como no exemplo do histograma nós chamamos o gráfico de histPlot, para nos referirmos a ele no server side utilizaremos output$histPlot.
  2. As saídas devem ser construídas com funções render_*(). Existe uma função render_() para cada tipo de objeto. Elas estão listadas aqui.
  3. Da mesma forma que as saídas, todos as entradas estão numa lista chamada input. Assim, para acessar o valor escolhido para o número de classes no exemplo do histograma, utilizaremos input$classes.

No nosso exemplo, a função server() fica:

server <- function(input, output) {
    output$histPlot <- renderPlot({
      hist((rnorm(input$classes)))
    })
  }

Assim, sempre que o usuário mudar o número de classes no slider, o aplicativo atualizará o valor de input$classes e irá executar novamente o código dentro da função renderPlot(). Assim, o objeto output$histPlot será atualizado.

Juntando tudo

O código final da nossa aplicação se torna então:

library(shiny)

ui <- fluidPage(
  titlePanel("Minha primeira aplicação Shiny"),

  sidebarLayout(
    sidebarPanel(
      sliderInput(inputId = "classes",
		  label = "Escolha um número",
		  value = 25, min = 1, max = 100)
    ),
    mainPanel(
      plotOutput("histPlot")
    )
  )
)

server <- function(input, output) {
  output$histPlot <- renderPlot({
    hist((rnorm(input$classes)))
  })
}

shinyApp(ui = ui, server = server)

2019-05-22T1452-shiny_final_example.png

Uma vez finalizada a funcionalidade da aplicação, podemos então modificar outros aspectos, como a sua aparência. Isso está fora do objetivo deste texto, e deixarei para textos posteriores.