Um pouco sobre shiny

Cristiano de Carvalho Santos

O que é shiny?

O Shiny é um sistema para desenvolvimento de aplicações web usando o R, um pacote do R (shiny) e um servidor web (shiny server). O Shiny é exatamente isso e nada mais, portanto Shiny não é uma página web.

Como criar um exemplo que utilize o shiny?

Exemplo que aparece ao criar um arquivo do R Markdown

inputPanel(
  # configurando o objeto n_breaks
  selectInput("n_breaks", label = "Number of bins:",
              choices = c(10, 20, 35, 50), selected = 20),
  
  # configurando o objeto bw_adjust
  sliderInput("bw_adjust", label = "Bandwidth adjustment:",
              min = 0.2, max = 2, value = 1, step = 0.2)
)

renderPlot({
  hist(faithful$eruptions, probability = TRUE, breaks = as.numeric(input$n_breaks),
       xlab = "Duration (minutes)", main = "Geyser eruption duration")
  
  dens <- density(faithful$eruptions, adjust = input$bw_adjust)
  lines(dens, col = "blue")
})

inputPanel

Cria o painel para que o usuário possa interagir. Para isso, precisamos adicionar os inputs.

inputs

Na prática, inputs são widgets que possibilitam a interação do usuário com o app. Eles recebem um valor escolhido pelo usuário e o envia para o server side. Segue uma lista dos principais inputs utilizados num Shiny app:

renderPlot

Os outputs devem ser construídos com funções render_(). Existe uma função render_() para cada tipo de objeto.

As principais são:

Outro exemplo (exemplo ao criar R markdown document)

shinyAppDir(
  system.file("examples/06_tabsets", package = "shiny"),
  options = list(
    width = "100%", height = 550
  )
)

O aplicativo que está sendo carregado esta em {C://Program Files//R//R-3.5.1//library//shiny//examples//06_tabsets}.

Mas é possível fazer a mesma coisa em um documento ou apresentação sem ter que fazer um aplicativo.

sidebarLayout(

    # Sidebar panel for inputs ----
    sidebarPanel(

      # Input: Select the random distribution type ----
      radioButtons("dist", "Distribution type:",
                   c("Normal" = "norm",
                     "Uniform" = "unif",
                     "Log-normal" = "lnorm",
                     "Exponential" = "exp")),

      # br() element to introduce extra vertical spacing ----
      br(),

      # Input: Slider for the number of observations to generate ----
      sliderInput("n",
                  "Number of observations:",
                  value = 500,
                  min = 1,
                  max = 1000)

    ),

    # Main panel for displaying outputs ----
    mainPanel(

      # Output: Tabset w/ plot, summary, and table ----
      tabsetPanel(type = "tabs",
                  tabPanel("Plot", plotOutput("plot")),
                  tabPanel("Summary", verbatimTextOutput("summary")),
                  tabPanel("Table", tableOutput("table"))
      )

    )
)



d <- reactive({
    dist <- switch(input$dist,
                   norm = rnorm,
                   unif = runif,
                   lnorm = rlnorm,
                   exp = rexp,
                   rnorm)

    dist(input$n)
  })


  output$plot <- renderPlot({
    dist <- input$dist
    n <- input$n

    hist(d(),
         main = paste("r", dist, "(", n, ")", sep = ""),
         col = "#75AADB", border = "white")
  })



  # Generate an HTML table view of the data ----
  output$table <- renderTable({
    d()
  })
  
  # Generate a summary of the data ----
  output$summary <- renderPrint({
    summary(d())
  })

Rodando os exemplos internos

runExample("01_hello")      # a histogram
runExample("02_text")       # tables and data frames
runExample("03_reactivity") # a reactive expression
runExample("04_mpg")        # global variables
runExample("05_sliders")    # slider bars
runExample("06_tabsets")    # tabbed panels
runExample("07_widgets")    # help text and submit buttons
runExample("08_html")       # Shiny app built from HTML
runExample("09_upload")     # file upload wizard
runExample("10_download")   # file download wizard
runExample("11_timer")      # an automated timer

Estrutura de um aplicativo

library(shiny)

# Define a interface do usuário para o app que gera um histograma.
ui <- fluidPage(

  # Título do app.
  titlePanel("Meu primeiro shiny app!"),

  # Barra lateral com as definições do input e do output.
  sidebarLayout(

    # Barra lateral para os inputs.
    sidebarPanel(

      # Input: número de classes do histograma.
      sliderInput(inputId = "classes",
                  label = "Número de classes:",
                  min = 1,
                  max = 30,
                  value = 10)

    ),

    # Painel principal para mostrar os outputs.
    mainPanel(

      # Output: Histograma
      plotOutput(outputId = "distPlot")

    )
  )
)


# Define o código necessário para a construção de um histograma.
server <- function(input, output) {

  # Função que gera o histograma e devolve para o user side.
  # Essa função é reativa. Isso significa que o histograma
  # vai mudar sempre que o valor do número de classes mudar.
  output$distPlot <- renderPlot({

    x    <- mtcars$mpg
    bins <- seq(min(x), max(x), length.out = input$classes + 1)

    hist(x, breaks = bins, col = "#75AADB", border = "white",
         xlab = "Milhas por galão",
         main = "Histograma do número de milhas rodadas por galão de combustível.")

  })

}

shinyApp(ui = ui, server = server)

User side

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

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

Tudo o que será apresentado ao usuário está guardado no objeto ui, que nada mais é do que um código HTML.

A função fluidPage() utilizada como exemplo acima é utilizada pelo Shiny para criar um display que automaticamente ajusta as dimensões da janela do navegador do usuário. Os elementos da interface do usuário são então colocados dentro dessa função.



As funções titlePanel() e sidebarLayout() são os argumentos da função fluidPage(). A primeira gera o título “Título”, enquanto a segunda estrutura um layout com barra lateral para o app.

A função sidebarLayout() recebe dois argumentos:

ui <- fluidPage(

    titlePanel("Meu primeiro shiny app!"),

  sidebarLayout(
    sidebarPanel(
      sliderInput(inputId = "classes",
                  label = "Número de classes:",
                  min = 1,
                  max = 30,
                  value = 10)
    ),

    mainPanel(
      plotOutput(outputId = "distPlot")
    )
  )
)

Repare que a função sliderInput() recebe alguns argumentos.

Cada input terá argumentos específicos da própria função. Assim, se você nunca usou um determinado input, procure no help() da função quais são os argumentos que ela recebe.

outputs

No exemplo do histograma, o input do nosso app era o número de classes e o output era o próprio histograma. Veja que no objeto ui temos o seguinte código:

mainPanel(
      plotOutput(outputId = "distPlot")
    )

Isso quer dizer que vamos receber um output do tipo “plot” (gráfico) do servidor e colocá-lo dentro do mainPanel().

Da mesma forma que há uma função para cada tipo de input, há uma função para cada tipo de output:

Assim como as funções de input, funções de output recebem um argumento de identificação, o outputId=. Esse argumento recebe uma string que representa o nome utilizado no server side para se referir a esse output. Consulte o help() de cada função para saber mais sobre os argumentos adicionais.

Criados os inputs e outputs do app, agora precisamos manipulá-los no server side.

Server side

Com a interface do usuário estruturada, precisamos agora implementar a função server(). Nela, colocaremos as instruções para gerar os outputs que nós vemos no user side a partir dos valores dos inputs 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.

server <- function(input, output) {
  
  # Código
  
}

A partir daí, precisamos seguir três regras:

  1. Todos os outputs estão numa lista chamada output. Assim, como no exemplo do histograma nós chamamos o gráfico de hist, para nos referirmos a ele no server side utilizaremos output$hist.
  2. Os outputs devem ser construídos com funções render_(). Existe uma função render_() para cada tipo de objeto. 3. Da mesma forma que os outputs, todos os inputs 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.
server <- function(input, output) {

  output$distPlot <- renderPlot({

    x    <- mtcars$mpg
    bins <- seq(min(x), max(x), length.out = input$classes + 1)

    hist(x, breaks = bins, col = "#75AADB", border = "white",
         xlab = "Milhas por galão",
         main = "Histograma do número de milhas rodadas por galão de combustível.")

  })

}

Repare nas {} dentro da função renderPlot().

Sempre que você usar um input dentro de uma função render_(), o seu output se tornará reativo ao valor do input. Isso significa que, sempre que o usuário mudar o valor do input, o Shiny atualizará automaticamente o valor dentro da lista e também todas as funções render_() que dependam dele.

library(shiny)

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      numericInput(inputId = "num",
                   label = "Número  de observações",
                   value = 100)
    ),
    mainPanel(plotOutput(outputId = "hist"))
  )
)

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

shinyApp(ui = ui, server = server)

Reatividade

O fluxo de reatividade será sempre conduzido por valores e funções reativas. Os objetos dentro da lista input são os principais objetos reativos e as funções render_() são as principais funções reativas.

Um fluxo básico seria o seguinte:

  1. O usuário altera o valor do input x.
  2. O valor reativo input$x é invalidado.
  3. Toda função reativa que depender de input$x é notificada.
  4. Essas funções verificam qual é o novo valor de input$x e atualizam suas saídas.


O Shiny disponibiliza funções para manipular a reatividade, alterando o fluxo básico apresentado acima.

Em http://material.curso-r.com/shiny/ encontramos: