43  Painéis com Shiny

Os painéis costumam ser uma ótima maneira de compartilhar resultados de análises com outras pessoas. Produzir um painel com shiny requer um conhecimento relativamente avançado da linguagem R, mas oferece uma personalização e possibilidades incríveis.

<! – Uma das maiores desvantagens do R é sua usabilidade para pessoas que são novas ou não têm experiência com linguagens de programação. Embora essas habilidades sejam muito valiosas, a maioria das pessoas descobrirá que isso representa uma barreira para o compartilhamento de análises, especialmente em ambientes multidisciplinares. Requer algum trabalho para manter uma instalação R, e nem todos se sentirão confortáveis executando um código compartilhado, mesmo que seja bem documentado e fácil de ler. Isso é especialmente verdadeiro quando os usuários precisam alterar os parâmetros do código! –>

Recomenda-se que alguém que esteja aprendendo painéis com shiny tenha bom conhecimento de transformação e visualização de dados e se sinta confortável para depurar código e escrever funções. Trabalhar com painéis não é intuitivo quando você está começando e às vezes é difícil de entender, mas é uma ótima habilidade de aprender e fica muito mais fácil com a prática!

Esta página fornecerá uma breve visão geral de como fazer painéis com shiny e suas extensões. Para um método alternativo de tornar os painéis mais rápidos, fáceis, mas talvez menos personalizáveis, consulte a página flextable (Painéis com R Markdown).

43.1 Preparação

Carregar pacotes

Neste manual, enfatizamos p_load() de pacman, que instala o pacote se necessário e o carrega para uso. Você também pode carregar pacotes instalados com library() do R base. Veja a página em Introdução ao R para mais informações sobre pacotes R.

Começamos instalando o pacote R shiny:

pacman::p_load("shiny")

Importar dados

Se você gostaria de acompanhar esta página, consulte esta seção do Baixar manual e dados. Existem links para baixar os scripts R e arquivos de dados que produzem o aplicativo Shiny final.

Se você tentar reconstruir o aplicativo usando esses arquivos, esteja ciente da estrutura de pastas do projeto R que é criada durante a demonstração (por exemplo, pastas para “dados” e para “funções”).

43.2 A estrutura de um aplicativo shiny

Estruturas de arquivo básicas

Para entender shiny, primeiro precisamos entender como funciona a estrutura de arquivos de um aplicativo! Devemos fazer um novo diretório antes de começar. Na verdade, isso pode ser facilitado escolhendo Novo projeto em Rstudio e escolhendo Shiny Web Application. Isso criará a estrutura básica de um aplicativo shiny para você.

Ao abrir este projeto, você notará que já existe um arquivo .R chamado app.R. É essencial que tenhamos uma das duas estruturas básicas de arquivo:

  1. Um arquivo chamado app.R, ou
  2. Dois arquivos, um denominado ui.R e o outro server.R

Nesta página, usaremos a primeira abordagem de ter um arquivo chamado app.R. Aqui está um script de exemplo:

# um exemplo de app.R

library(shiny)

ui <- fluidPage(

    # Título do aplicativo
    titlePanel("Meu aplicativo"),

    # Barra lateral com um widget de entrada deslizante
    sidebarLayout(
        sidebarPanel(
            sliderInput("input_1")
        ),

        # Mostrar um gráfico 
        mainPanel(
           plotOutput("my_plot")
        )
    )
)

# Defina a lógica do servidor necessária para desenhar um histograma
server <- function(input, output) {
     
     plot_1 <- reactive({
          plot_func(param = input_1)
     })
     
    output$my_plot <- renderPlot({
       plot_1()
    })
}


# Execute o aplicativo 
shinyApp(ui = ui, server = server)

Se você abrir este arquivo, você notará que dois objetos são definidos - um chamado ui e outro chamado servidor. Esses objetos devem ser definidos em todos os aplicativos shiny e são centrais para a estrutura do próprio aplicativo! Na verdade, a única diferença entre as duas estruturas de arquivo descritas acima é que na estrutura 1, ui e server são definidos em um arquivo, enquanto na estrutura 2 eles são definidos em arquivos separados. Nota: nós também podemos (e devemos se tiver um aplicativo maior) ter outros arquivos .R em nossa estrutura que podemos source() em nosso aplicativo.

O servidor e a ui

Em seguida, precisamos entender o que os objetos server e ui realmente do. Simplificando, esses são dois objetos que estão interagindo entre si sempre que o usuário interage com o aplicativo shiny.

O elemento UI de um aplicativo shiny é, em um nível básico, o código R que cria uma interface HTML. Isso significa tudo o que é exibido na IU de um aplicativo. Isso geralmente inclui:

  • “Widgets” - menus suspensos, caixas de seleção, controles deslizantes etc. com os quais o usuário pode interagir
  • Plots, tabelas, etc - saídas que são geradas com o código R
  • Aspectos de navegação de um aplicativo - guias, painéis, etc.
  • Texto genérico, hiperlinks, etc.
  • Elementos HTML e CSS (abordado posteriormente)

O mais importante a entender sobre a IU é que ela recebe entradas do usuário e exibe saídas do servidor. Não há código ativo em execução na interface do usuário a qualquer momento - todas as alterações vistas na interface do usuário são passadas pelo servidor (mais ou menos). Portanto, temos que fazer nossos gráficos, downloads, etc no servidor

O servidor do aplicativo shiny é onde todo o código está sendo executado assim que o aplicativo é inicializado. A maneira como isso funciona é um pouco confusa. A função de servidor irá efetivamente reagir à interface do usuário com a IU e executar pedaços de código em resposta. Se as coisas mudarem no servidor, elas serão repassadas para a interface do usuário, onde as mudanças podem ser vistas. É importante ressaltar que o código no servidor será executado não consecutivamente (ou é melhor pensar dessa forma). Basicamente, sempre que uma entrada da interface do usuário afetar um pedaço de código no servidor, ela será executada automaticamente e essa saída será produzida e exibida.

Isso tudo provavelmente parece muito abstrato por agora, então teremos que mergulhar em alguns exemplos para ter uma ideia clara de como isso realmente funciona.

Antes de começar a construir um aplicativo

Antes de começar a construir um aplicativo, é extremamente útil saber o que você deseja construir. Uma vez que sua IU será escrita em código, você não pode realmente visualizar o que está construindo, a menos que esteja visando algo específico. Por esse motivo, é extremamente útil olhar muitos exemplos de aplicativos shiny para ter uma ideia do que você pode fazer - ainda melhor se você puder olhar o código-fonte por trás desses aplicativos! Alguns ótimos recursos para isso são:

Depois de ter uma ideia do que é possível, também é útil mapear como você quer que o seu fique - você pode fazer isso no papel ou em qualquer software de desenho (PowerPoint, MS Paint etc.). É útil começar de forma simples para seu primeiro aplicativo! Também não há vergonha em usar o código que você encontra online de um bom aplicativo como modelo para o seu trabalho - é muito mais fácil do que construir algo do zero!

43.3 Construindo uma IU

Ao construir nosso aplicativo, é mais fácil trabalhar na IU primeiro, para que possamos ver o que estamos fazendo e não correr o risco de o aplicativo falhar devido a erros do servidor. Como mencionado anteriormente, geralmente é bom usar um modelo ao trabalhar na IU. Existem vários layouts padrão que podem ser usados com o shiny que estão disponíveis no pacote base shiny, mas é importante notar que também há várias extensões de pacote como shinydashboard. Usaremos um exemplo da base shiny para começar.

Uma IU shiny é geralmente definida como uma série de funções aninhadas, na seguinte ordem

  1. Uma função que define o layout geral (a mais básica é fluidPage(), mas há mais disponíveis)
  2. Painéis dentro do layout, como:
    • uma barra lateral (sidebarPanel())
    • um painel “principal” (mainPanel())
    • uma guia (tabPanel())
    • uma “coluna” genérica (coluna())
  3. Ferramentas (Widgets) e saídas - podem conferir entradas para o servidor (widgets) ou saídas do servidor (saídas)
    • Os widgets geralmente são estilizados como xxxInput(), por exemplo selectInput ()
    • As saídas são geralmente estilizadas como xxxOutput(), por exemplo plotOutput()

Vale a pena reafirmar que eles não podem ser visualizados facilmente de forma abstrata, então é melhor dar uma olhada em um exemplo! Vamos considerar a criação de um aplicativo básico que visualiza os dados de contagem de nossas instalações para malária por distrito. Esses dados têm muitos parâmetros diferentes, então seria ótimo se o usuário final pudesse aplicar alguns filtros para ver os dados por faixa etária / distrito como achar melhor! Podemos usar um layout shiny muito simples para começar - o layout da barra lateral. Este é um layout onde os widgets são colocados em uma barra lateral à esquerda e o gráfico é colocado à direita.

Vamos planejar nosso aplicativo - podemos começar com um seletor que nos permite escolher o bairro onde queremos visualizar os dados, e outro para nos permitir visualizar a faixa etária que nos interessa. Nosso objetivo é usar esses filtros para mostrar uma epicurva que reflete esses parâmetros. Então, para isso, precisamos:

  1. Dois menus suspensos que nos permitem escolher o distrito que desejamos e a faixa etária em que estamos interessados.
  2. Uma área onde podemos mostrar nossa epicurva resultante.

Isso pode ser parecido com isto:

library(shiny)

ui <- fluidPage(

  titlePanel ("Aplicativo de visualização de instalação para malária"),

  sidebarLayout(

    sidebarPanel(
         # seletor para distrito
         selectInput(
              inputId = "select_district",
              label = "Selecionar distrito",
              escolhas = c(
                   "Tudo",
                   "Primavera",
                   "Bolo",
                   "Dingo",
                   "Barnard"
              ),
              selecionado = "Tudo",
              múltiplo = TRUE
         ),
         # seletor para faixa etária
         selectInput(
              inputId = "select_agegroup",
              label = "Selecionar faixa etária",
              escolhas = c(
                   "Todas as idades" = "malaria_tot",
                   "0-4 yrs" = "malaria_rdt_0-4",
                   "5-14 yrs" = "malaria_rdt_5-14",
                   "15+ yrs" = "malaria_rdt_15"
              ),
              selecionado = "Tudo",
              múltiplo = FALSE
         )

    ),

    mainPanel(
      # epicurva vai aqui
      plotOutput("malaria_epicurve")
    )
    
  )
)

Quando app.R é executado com o código de IU acima (sem nenhum código ativo na parte server de app.R), o layout aparece assim - observe que não haverá um gráfico se não houver servidor para renderizá-lo, mas nossas entradas estão funcionando!

Esta é uma boa oportunidade para discutir como os widgets funcionam - observe que cada widget está aceitando um inputId, umlabel e uma série de outras opções que são específicas para o tipo de widget. Este inputId é extremamente importante - esses são os IDs usados para passar informações da IU para o servidor. Por esse motivo, eles devem ser exclusivos. Você deve fazer um esforço para nomeá-los de algo sensato e específico para o que estão interagindo em casos de aplicativos maiores.

Você deve ler a documentação cuidadosamente para obter detalhes completos sobre o que cada um desses widgets faz. Os widgets passarão tipos específicos de dados para o servidor, dependendo do tipo de widget, e isso precisa ser totalmente compreendido. Por exemplo, selectInput() passará um tipo de caractere para o servidor:

  • Se selecionarmos Spring para o primeiro widget aqui, ele passará o objeto de caractere "Spring" para o servidor.
  • Se selecionarmos dois itens no menu suspenso, eles aparecerão como um vetor de caracteres (por exemplo, c(" Spring "," Bolo ")).

Outros widgets irão passar diferentes tipos de objetos para o servidor! Por exemplo:

  • numericInput() passará um objeto de tipo numérico para o servidor
  • checkboxInput() passará um objeto de tipo lógico para o servidor (TRUE ouFALSE)

Também vale a pena notar o vetor nomeado que usamos para os dados de idade aqui. Para muitos widgets, usar um vetor nomeado como as escolhas exibirá os nomes do vetor como as opções de exibição, mas passará o valor selecionado do vetor para o servidor. Ou seja, aqui, alguém pode selecionar “15+” no menu suspenso e a IU passará "malaria_rdt_15" para o servidor - que por acaso é o nome da coluna em que estamos interessados!

Existem muitos widgets que você pode usar para fazer muitas coisas com seu aplicativo. Os widgets também permitem que você carregue arquivos em seu aplicativo e baixe saídas. Existem também algumas extensões shiny excelentes que fornecem acesso a mais widgets do que o básico shiny - o pacote shinyWidgets é um ótimo exemplo disso. Para ver alguns exemplos, você pode ver os seguintes links:

43.4 Carregando dados em nosso aplicativo

A próxima etapa no desenvolvimento de nosso aplicativo é colocar o servidor em funcionamento. Para fazer isso, no entanto, precisamos colocar alguns dados em nosso aplicativo e descobrir todos os cálculos que faremos. Um aplicativo shiny não é simples de depurar, já que geralmente não fica claro de onde vêm os erros, então é ideal para fazer com que todo o nosso processamento de dados e código de visualização funcionem antes de começarmos a fazer o próprio servidor.

Portanto, como queremos fazer um aplicativo que mostra curvas epi que mudam com base na entrada do usuário, devemos pensar sobre o código de que precisaríamos para executá-lo em um script R normal. Precisamos:

  1. Carregue nossos pacotes
  2. Carregue nossos dados
  3. Transforme nossos dados
  4. Desenvolva uma função para visualizar nossos dados com base nas entradas do usuário

Esta lista é bastante direta e não deve ser muito difícil de fazer. Agora é importante pensar sobre quais partes desse processo precisam ser feitas apenas uma vez e quais partes precisam ser executadas em resposta às entradas do usuário. Isso ocorre porque os aplicativos shiny geralmente executam algum código antes de serem executados, o que é executado apenas uma vez. Isso ajudará no desempenho de nosso aplicativo se o máximo de nosso código puder ser movido para esta seção. Para este exemplo, só precisamos carregar nossos dados / pacotes e fazer as transformações básicas uma vez, para que possamos colocar esse código fora do servidor. Isso significa que a única coisa de que precisaremos no servidor é o código para visualizar nossos dados. Vamos desenvolver todos esses componentes em um script primeiro. No entanto, como estamos visualizando nossos dados com uma função, também podemos colocar o código para a função fora do servidor para que nossa função esteja no ambiente quando o aplicativo for executado!

Primeiro, vamos carregar nossos dados. Como estamos trabalhando com um novo projeto e queremos torná-lo limpo, podemos criar um novo diretório chamado data e adicionar nossos dados de malária nele. Podemos executar o código abaixo em um script de teste que excluiremos ao limpar a estrutura de nosso aplicativo.

pacman::p_load("tidyverse", "lubridate")

# dados lidos
malaria_data <- rio::import(here::here("data", "malaria_facility_count_data.rds")) %>% 
  as_tibble()

print(malaria_data)
# A tibble: 3,038 × 10
   location_name data_date  submitted_date Province District `malaria_rdt_0-4`
   <chr>         <date>     <date>         <chr>    <chr>                <int>
 1 Facility 1    2020-08-11 2020-08-12     North    Spring                  11
 2 Facility 2    2020-08-11 2020-08-12     North    Bolo                    11
 3 Facility 3    2020-08-11 2020-08-12     North    Dingo                    8
 4 Facility 4    2020-08-11 2020-08-12     North    Bolo                    16
 5 Facility 5    2020-08-11 2020-08-12     North    Bolo                     9
 6 Facility 6    2020-08-11 2020-08-12     North    Dingo                    3
 7 Facility 6    2020-08-10 2020-08-12     North    Dingo                    4
 8 Facility 5    2020-08-10 2020-08-12     North    Bolo                    15
 9 Facility 5    2020-08-09 2020-08-12     North    Bolo                    11
10 Facility 5    2020-08-08 2020-08-12     North    Bolo                    19
# ℹ 3,028 more rows
# ℹ 4 more variables: `malaria_rdt_5-14` <int>, malaria_rdt_15 <int>,
#   malaria_tot <int>, newid <int>

Será mais fácil trabalhar com esses dados se usarmos padrões de dados organizados, então também devemos transformar em um formato de dados mais longo, em que a faixa etária é uma coluna e os casos é outra coluna. Podemos fazer isso facilmente usando o que aprendemos na página Pivoteando dados.

malaria_data <- malaria_data%>%
  select (-newid)%>%
  pivot_longer(cols = starts_with("malaria_"), names_to = "age_group", values_to = "cases_reported")

print(malaria_data)
# A tibble: 12,152 × 7
   location_name data_date  submitted_date Province District age_group       
   <chr>         <date>     <date>         <chr>    <chr>    <chr>           
 1 Facility 1    2020-08-11 2020-08-12     North    Spring   malaria_rdt_0-4 
 2 Facility 1    2020-08-11 2020-08-12     North    Spring   malaria_rdt_5-14
 3 Facility 1    2020-08-11 2020-08-12     North    Spring   malaria_rdt_15  
 4 Facility 1    2020-08-11 2020-08-12     North    Spring   malaria_tot     
 5 Facility 2    2020-08-11 2020-08-12     North    Bolo     malaria_rdt_0-4 
 6 Facility 2    2020-08-11 2020-08-12     North    Bolo     malaria_rdt_5-14
 7 Facility 2    2020-08-11 2020-08-12     North    Bolo     malaria_rdt_15  
 8 Facility 2    2020-08-11 2020-08-12     North    Bolo     malaria_tot     
 9 Facility 3    2020-08-11 2020-08-12     North    Dingo    malaria_rdt_0-4 
10 Facility 3    2020-08-11 2020-08-12     North    Dingo    malaria_rdt_5-14
# ℹ 12,142 more rows
# ℹ 1 more variable: cases_reported <int>

E com isso terminamos de preparar nossos dados! Isso cruza os itens 1, 2 e 3 de nossa lista de coisas a desenvolver para nosso “script de teste R”. A última e mais difícil tarefa será construir uma função para produzir uma epicurva baseada em parâmetros definidos pelo usuário. Como mencionado anteriormente, é altamente recomendado que qualquer pessoa que esteja aprendendo algo shiny primeiro dê uma olhada na seção sobre programação funcional (Escrevendo funções) para entender como isso funciona!

Ao definir nossa função, pode ser difícil pensar sobre quais parâmetros queremos incluir. Para programação funcional com shiny, cada parâmetro relevante geralmente terá um widget associado a ele, então pensar sobre isso geralmente é bem fácil! Por exemplo, em nosso aplicativo atual, queremos ser capazes de filtrar por distrito e ter um widget para isso, para que possamos adicionar um parâmetro de distrito para refletir isso. Nós não temos nenhuma funcionalidade de aplicativo para filtrar por instalação (por enquanto), portanto, não precisamos adicionar isso como um parâmetro. Vamos começar criando uma função com três parâmetros:

  1. O conjunto de dados principal
  2. O distrito de escolha
  3. A faixa etária de escolha
plot_epicurve <- function(data, district = "All", agegroup = "malaria_tot") {
  
  if (!("All" %in% district)) {
    data <- data %>%
      filter(District %in% district)
    
    plot_title_district <- stringr::str_glue("{paste0(district, collapse = ', ')} districts")
    
  } else {
    
    plot_title_district <- "all districts"
    
  }
  
  # se não houver dados restantes, retorna NULL
  if (nrow(data) == 0) {
    
    return(NULL)
  }
  
  data <- data %>%
    filter(age_group == agegroup)
  
  
  # se não houver dados restantes, retorna NULL
  if (nrow(data) == 0) {
    
    return(NULL)
  }
  
  if (agegroup == "malaria_tot") {
      agegroup_title <- "All ages"
  } else {
    agegroup_title <- stringr::str_glue("{str_remove(agegroup, 'malaria_rdt')} years")
  }
  
  
  ggplot(data, aes(x = data_date, y = cases_reported)) +
    geom_col(width = 1, fill = "darkred") +
    theme_minimal () +
    labs(
      x = "date",
      y = "number of cases",
      title = stringr::str_glue("Malaria cases - {plot_title_district}"),
      subtitle = agegroup_title
    )
  
  
  
}

Não entraremos em muitos detalhes sobre essa função, pois é relativamente simples em como funciona. Uma coisa a se notar, entretanto, é que tratamos os erros retornando NULL quando, de outra forma, resultaria em um erro. Isso ocorre porque quando um servidor shiny produz um objeto NULL em vez de um objeto de gráfico, nada será mostrado na interface do usuário! Isso é importante, pois, caso contrário, os erros geralmente farão com que seu aplicativo pare de funcionar.

Outra coisa a se notar é o uso do operador %in% ao avaliar a entrada district. Como mencionado acima, isso pode chegar como um vetor de caracteres com vários valores, então usar %in% é mais flexível do que dizer, ==.

Vamos testar nossa função!

plot_epicurve(malaria_data, district = "Bolo", agegroup = "malaria_rdt_0-4")

Com nossa função funcionando, agora temos que entender como tudo isso vai se encaixar em nosso aplicativo shiny. Mencionamos o conceito de código de inicialização antes, mas vamos ver como podemos realmente incorporar isso à estrutura de nosso aplicativo. Podemos fazer isso de duas maneiras!

  1. Coloque este código em seu arquivo app.R no início do script (acima da IU), ou
  2. Crie um novo arquivo no diretório do seu aplicativo chamado global.R e coloque o código de inicialização neste arquivo.

É importante notar neste ponto que geralmente é mais fácil, especialmente com aplicativos maiores, usar a segunda estrutura de arquivo, pois permite separar sua estrutura de arquivo de uma forma simples. Vamos desenvolver totalmente um script global.R agora. Pode ser assim:

# global.R script

pacman::p_load("tidyverse", "lubridate", "shiny")

# dados lidos
malaria_data <- rio::import(here::here("data", "malaria_facility_count_data.rds")) %>% 
  as_tibble()

# limpe os dados e gire por mais tempo
malaria_data <- malaria_data%>%
  select (-newid)%>%
  pivot_longer(cols = starts_with("malaria_"), names_to = "age_group", values_to = "cases_reported")


# define a função de criação de um gráfico
plot_epicurve <- function(data, district = "All", agegroup = "malaria_tot") {
  
  # criar título do enredo
  if (!("All" %in% district)) {            
    data <- data %>%
      filter(District %in% district)
    
    plot_title_district <- stringr::str_glue("{paste0(district, collapse = ', ')} districts")
    
  } else {
    
    plot_title_district <- "all districts"
    
  }
  
  # se não houver dados restantes, retorna NULL
  if (nrow(data) == 0) {
    
    return(NULL)
  }
  
  # filtro para faixa etária
  data <- data %>%
    filter(age_group == agegroup)
  
  
  # se não houver dados restantes, retorna NULL
  if (nrow(data) == 0) {
    
    return(NULL)
  }
  
  if (agegroup == "malaria_tot") {
      agegroup_title <- "All ages"
  } else {
    agegroup_title <- stringr::str_glue("{str_remove(agegroup, 'malaria_rdt')} years")
  }
  
  
  ggplot(data, aes(x = data_date, y = cases_reported)) +
    geom_col(width = 1, fill = "darkred") +
    theme_minimal () +
    labs(
      x = "date",
      y = "number of cases",
      title = stringr::str_glue("Malaria cases - {plot_title_district}"),
      subtitle = agegroup_title
    )
  
  
  
}

Fácil! Um grande recurso do shiny é que ele entenderá para que servem os arquivos chamados app.R, server.R, ui.R e global.R, portanto, não há necessidade de conectá-los entre si por meio de qualquer código. Portanto, apenas por ter esse código em global.R no diretório, ele será executado antes de iniciarmos nosso aplicativo!

Devemos também observar que melhoraria a organização de nosso aplicativo se movêssemos a função de plotagem para seu próprio arquivo - isso será especialmente útil à medida que os aplicativos se tornam maiores. Para fazer isso, poderíamos criar outro diretório chamado funcs e colocar essa função em um arquivo chamado plot_epicurve.R. Poderíamos então ler essa função por meio do seguinte comando em global.R

source(here("funcs", "plot_epicurve.R"), local = TRUE)

Observe que você deve sempre especificar local = TRUE em aplicativos shiny, uma vez que afetará o fornecimento quando / se o aplicativo for publicado em um servidor.

43.5 Desenvolvendo um servidor de aplicativos

Agora que temos a maior parte do nosso código, só precisamos desenvolver nosso servidor. Esta é a parte final de nosso aplicativo e provavelmente a mais difícil de entender. O servidor é uma grande função R, mas é útil pensar nele como uma série de funções menores ou tarefas que o aplicativo pode executar. É importante entender que essas funções não são executadas em uma ordem linear. Há uma ordem para eles, mas não é totalmente necessário entender quando se começa com o shiny. Em um nível muito básico, essas tarefas ou funções serão ativadas quando houver uma mudança nas entradas do usuário que as afete, a menos que o desenvolvedor as tenha configurado para que se comportem de maneira diferente. Novamente, tudo isso é bastante abstrato, mas vamos primeiro examinar os três tipos básicos de objetos shiny

  1. Fontes reativas - este é outro termo para entradas do usuário. O servidor shiny tem acesso às saídas da IU por meio dos widgets que programamos. Cada vez que os valores para estes são alterados, isso é passado para o servidor.

  2. Condutores reativos - são objetos que existem apenas dentro do servidor shiny. Na verdade, não precisamos deles para aplicativos simples, mas eles produzem objetos que só podem ser vistos dentro do servidor e usados em outras operações. Eles geralmente dependem de fontes reativas.

  3. Resultados (Endpoints) - são saídas que são passadas do servidor para a IU. Em nosso exemplo, essa seria a curva epi que estamos produzindo.

Com isso em mente vamos construir nosso servidor passo a passo. Mostraremos nosso código de IU novamente aqui apenas para referência:

ui <- fluidPage(

  titlePanel ("Aplicativo de visualização de instalação para malária"),

  sidebarLayout(

    sidebarPanel(
         # seletor para distrito
         selectInput(
              inputId = "select_district",
              label = "Selecionar distrito",
              escolhas = c(
                   "Tudo",
                   "Primavera",
                   "Bolo",
                   "Dingo",
                   "Barnard"
              ),
              selecionado = "Tudo",
              múltiplo = TRUE
         ),
         # seletor para faixa etária
         selectInput(
              inputId = "select_agegroup",
              label = "Selecionar faixa etária",
              escolhas = c(
                   "Todas as idades" = "malaria_tot",
                   "0-4 yrs" = "malaria_rdt_0-4",
                   "5-14 yrs" = "malaria_rdt_5-14",
                   "15+ yrs" = "malaria_rdt_15"
              ),
              selecionado = "Tudo",
              múltiplo = FALSE
         )

    ),

    mainPanel(
      # epicurva vai aqui
      plotOutput("malaria_epicurve")
    )
    
  )
)

A partir desta IU de código, temos:

  • Duas entradas:
    • Seletor de distrito (com um inputId de select_district)
    • Seletor de faixa etária (com um inputId de select_agegroup)
  • Uma saída:
    • A epicurva (com outputId de malaria_epicurve)

Conforme declarado anteriormente, esses nomes exclusivos que atribuímos às nossas entradas e saídas são cruciais. Eles devem ser exclusivos e são usados para passar informações entre a interface do usuário e o servidor. Em nosso servidor, acessamos nossas entradas através da sintaxe input $ inputID e saídas e passadas para a interface do usuário através da sintaxeoutput $ output_name Vamos dar uma olhada em um exemplo, porque novamente é difícil entender de outra forma!

server <- function(input, output, session) {
  
  output$malaria_epicurve <- renderPlot(
    plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup)
  )
  
}

O servidor para um aplicativo simples como esse é bastante direto! Você notará que o servidor é uma função com três parâmetros - input, output e session - isso não é tão importante entender agora, mas é importante seguir esta configuração! Em nosso servidor, temos apenas uma tarefa - isso renderiza um gráfico com base em nossa função que criamos anteriormente e nas entradas do servidor. Observe como os nomes dos objetos de entrada e saída correspondem exatamente aos da interface do usuário.

Para entender os fundamentos de como o servidor reage às entradas do usuário, você deve observar que a saída saberá (por meio do pacote subjacente) quando as entradas mudam e execute novamente esta função para criar um gráfico sempre que mudam. Observe que também usamos a função renderPlot() aqui - esta é uma de uma família de funções específicas de classe que passam esses objetos para uma saída da interface do usuário. Existem várias funções que se comportam de maneira semelhante, mas você precisa garantir que a função usada corresponda à classe de objeto que você está passando para a interface do usuário! Por exemplo:

  • renderText() - envia texto para a interface do usuário
  • renderDataTable - envia uma tabela interativa para a interface do usuário.

Lembre-se de que eles também precisam corresponder à função de saída usada na ui - então renderPlot() é pareado com plotOutput(), e renderText() é pareado com textOutput().

Então, finalmente criamos um aplicativo funcional! Podemos executá-lo pressionando o botão Executar aplicativo no canto superior direito da janela do script no Rstudio. Você deve observar que pode optar por executar seu aplicativo em seu navegador padrão (em vez de Rstudio), que refletirá com mais precisão a aparência do aplicativo para outros usuários.

É divertido notar que no console R, o aplicativo está “ouvindo”! É a tal da reatividade!

<! – A FAZER: ADICIONE ALGO AO BAIXAR UM ARQUIVO ZIP DO APLICATIVO? –>

43.6 Adicionando mais funcionalidades

Neste ponto, finalmente temos um aplicativo em execução, mas temos muito poucas funcionalidades. Até agora só vimos a “ponta do iceberg” do que shiny pode fazer, então há muito mais para aprender! Vamos continuar a construir nosso aplicativo existente, adicionando alguns recursos extras. Algumas coisas que podem ser interessantes de adicionar são:

  1. Algum texto explicativo
  2. Um botão de download para nosso gráfico - isso forneceria ao usuário uma versão de alta qualidade da imagem que está gerando no aplicativo
  3. Um seletor para instalações específicas
  4. Outra página do painel - isso pode mostrar uma tabela de nossos dados.

Isso é muito a acrescentar, mas podemos usá-lo para aprender sobre um monte de recursos shiny diferentes no caminho. Há muito o que aprender sobre shiny (ele pode ficar muito avançado, mas esperamos que, assim que os usuários tiverem uma ideia melhor de como usá-lo, eles possam se sentir mais confortáveis usando fontes externas de aprendizagem também).

Adicionando texto estático

Vamos primeiro discutir a adição de texto estático ao nosso aplicativo shiny. Adicionar texto ao nosso aplicativo é extremamente fácil, uma vez que você tenha um domínio básico sobre ele. Visto que o texto estático não muda no aplicativo shiny (se você gostaria que ele mudasse, você pode usar funções de renderização de texto no servidor!), todo o texto estático shiny é geralmente adicionado na interface do usuário do aplicativo. Não examinaremos isso em grandes detalhes, mas você pode adicionar vários elementos diferentes à sua interface do usuário (e até mesmo alguns personalizados) fazendo a interface de R com HTML e css.

HTML e css são linguagens que estão explicitamente envolvidas no design da interface do usuário. Não precisamos entender isso muito bem, mas HTML cria objetos na IU (como uma caixa de texto ou uma tabela) e css geralmente é usado para alterar o estilo e a estética desses objetos. O Shiny tem acesso a uma grande variedade de HTML tags - presentes para objetos que se comportam de uma maneira específica, como cabeçalhos, parágrafos de texto, quebras de linha, tabelas, etc. Podemos usar alguns desses exemplos como este:

  • h1() - esta é uma tag de header, que tornará o texto fechado automaticamente maior, e mudará os padrões conforme eles pertencem à face da fonte, cor etc. (dependendo do tema geral do seu aplicativo). Você pode acessar_menos e menor_subtítulo com h2() até h6() também. O uso se parece com:

    • h1(" meu cabeçalho - seção 1")
  • p() - esta é uma tag de parágrafo, que tornará o texto fechado semelhante ao texto em um corpo de texto. Este texto será quebrado automaticamente e terá um tamanho relativamente pequeno (os rodapés podem ser menores, por exemplo). Pense nisso como o corpo do texto de um documento do Word. O uso se parece com:

    • p(" Este é um corpo de texto maior onde estou explicando a função do meu aplicativo")
  • tags$b() e tags$i() - são usadas para criar tags $b() em negrito e tags$i() em itálico com qualquer texto que esteja incluído!

  • tags$ul(), tags$ol() e tags$li() - são tags usadas na criação de * listas *. Todos eles são usados na sintaxe abaixo e permitem ao usuário criar uma lista ordenada (tags$ol(); ou seja, numerada) ou lista não ordenada (tags$ul(), ou seja, marcadores). tags$li() é usado para denotar itens na lista, independentemente do tipo de lista usado. Por exemplo:

tags$ol(
  
  tags$li("Item 1"),
  
  tags$li("Item 2"),
  
  tags$li("Item 3")
  
)
  • br() e hr() - essas tags criam quebras de linha e linhas horizontais (com uma quebra de linha) respectivamente. Use-os para separar as seções de seu aplicativo e texto! Não há necessidade de passar nenhum item para essas tags (os parênteses podem permanecer vazios).

  • div() - esta é uma tag genérica que pode conter qualquer coisa, e pode ser nomeada qualquer coisa. Depois de progredir com o design da interface do usuário, você pode usá-los para compartimentar sua interface do usuário, fornecer estilos específicos a seções específicas e criar interações entre o servidor e os elementos da interface do usuário. Não entraremos em detalhes, mas vale a pena estar ciente deles!

Note que cada um desses objetos pode ser acessado através de tags$... ou para alguns, apenas a função. Estes são efetivamente sinônimos, mas pode ajudar usar o estilo tags$... se você preferir ser mais explícito e não sobrescrever as funções acidentalmente. Esta também não é, de forma alguma, uma lista exaustiva de tags disponíveis. Há uma lista completa de todas as tags disponíveis em shiny aqui e ainda mais podem ser usadas inserindo HTML diretamente em sua interface do usuário!

Se você se sentir confiante, também pode adicionar quaisquer elementos de estilo css às suas tags HTML com o argumento style em qualquer um deles. Não entraremos em detalhes sobre como isso funciona, mas uma dica para testar mudanças estéticas em uma IU é usar o modo inspetor HTML em cromo (do seu aplicativo shiny que você está executando no navegador) e editar o estilo dos objetos você mesmo!

Vamos adicionar algum texto ao nosso aplicativo

ui <- fluidPage(

  titlePanel ("Aplicativo de visualização de instalação para malária"),

  sidebarLayout(

    sidebarPanel(
         h4 ("Opções"),
         # seletor para distrito
         selectInput(
              inputId = "select_district",
              label = "Selecionar distrito",
              escolhas = c(
                   "Tudo",
                   "Primavera",
                   "Bolo",
                   "Dingo",
                   "Barnard"
              ),
              selecionado = "Tudo",
              múltiplo = TRUE
         ),
         # seletor para faixa etária
         selectInput(
              inputId = "select_agegroup",
              label = "Selecionar faixa etária",
              escolhas = c(
                   "Todas as idades" = "malaria_tot",
                   "0-4 yrs" = "malaria_rdt_0-4",
                   "5-14 yrs" = "malaria_rdt_5-14",
                   "15+ yrs" = "malaria_rdt_15"
              ),
              selecionado = "Tudo",
              múltiplo = FALSE
         ),
    ),

    mainPanel(
      # epicurva vai aqui
      plotOutput("malaria_epicurve"),
      br(),
      hr(),
      p("Bem-vindo ao aplicativo de visualização de instalações para malária! Para usar este aplicativo, manipule os widgets ao lado para alterar a curva epidêmica de acordo com suas preferências! Para baixar uma imagem de alta qualidade do plot que você criou, você também pode baixá-la com o botão de download. Para ver os dados brutos, use a guia de dados brutos para uma forma interativa da tabela. O dicionário de dados é o seguinte:"),
    tags$ul(
      tags$li(tags$b("location_name"), "- a instalação onde os dados foram coletados"),
      tags$li(tags$b("data_date"), "- a data em que os dados foram coletados"),
      tags$li(tags$b("submitted_daate"), "- a data em que os dados foram enviados"),
      tags$li(tags$b("Província"), "- a província onde os dados foram coletados (todo 'Norte' para este conjunto de dados)"),
      tags$li(tags$b("Distrito"), "- o distrito onde os dados foram coletados"),
      tags$li(tags$b("age_group"), "- a faixa etária para a qual os dados foram coletados (0-5, 5-14, 15+ e todas as idades)"),
      tags$li(tags$b("cases_reported"), "- o número de casos relatados para o estabelecimento / faixa etária na data fornecida")
    )
    
  )
)
)

Adicionando um botão de download

Vamos passar para o segundo dos três recursos. Um botão de download é algo bastante comum para adicionar a um aplicativo e é bastante fácil de fazer. Precisamos adicionar outro widget à nossa interface do usuário e precisamos adicionar outra saída ao nosso servidor para anexar a ele. Também podemos introduzir condutores reativos neste exemplo!

Vamos atualizar nossa interface do usuário primeiro - isso é fácil, pois o shiny vem com um widget chamado downloadButton() - vamos dar a ele um inputId e um rótulo.

ui <- fluidPage(

  titlePanel ("Aplicativo de visualização de instalação para malária"),

  sidebarLayout(

    sidebarPanel(
         # seletor para distrito
         selectInput(
              inputId = "select_district",
              label = "Selecionar distrito",
              escolhas = c(
                   "Tudo",
                   "Primavera",
                   "Bolo",
                   "Dingo",
                   "Barnard"
              ),
              selecionado = "Tudo",
              múltiplo = FALSE
         ),
         # seletor para faixa etária
         selectInput(
              inputId = "select_agegroup",
              label = "Selecionar faixa etária",
              escolhas = c(
                   "Todas as idades" = "malaria_tot",
                   "0-4 yrs" = "malaria_rdt_0-4",
                   "5-14 yrs" = "malaria_rdt_5-14",
                   "15+ yrs" = "malaria_rdt_15"
              ),
              selecionado = "Tudo",
              múltiplo = FALSE
         ),
         # linha horizontal
         hr(),
         downloadButton(
           outputId = "download_epicurve",
           label = "Baixar plot"
         )

    ),

    mainPanel(
      # epicurva vai aqui
      plotOutput("malaria_epicurve"),
      br(),
      hr(),
      p("Bem-vindo ao aplicativo de visualização de instalações para malária! Para usar este aplicativo, manipule os widgets ao lado para alterar a curva epidêmica de acordo com suas preferências! Para baixar uma imagem de alta qualidade do plot que você criou, você também pode baixá-la com o botão de download. Para ver os dados brutos, use a guia de dados brutos para uma forma interativa da tabela. O dicionário de dados é o seguinte:"),
      tags$ul(
        tags$li(tags$b("location_name"), "- a instalação onde os dados foram coletados"),
        tags$li(tags$b("data_date"), "- a data em que os dados foram coletados"),
        tags$li(tags$b("submitted_daate"), "- a data em que os dados foram enviados"),
        tags$li(tags$b("Província"), "- a província onde os dados foram coletados (todo 'Norte' para este conjunto de dados)"),
        tags$li(tags$b("Distrito"), "- o distrito onde os dados foram coletados"),
        tags$li(tags$b("age_group"), "- a faixa etária para a qual os dados foram coletados (0-5, 5-14, 15+ e todas as idades)"),
        tags$li(tags$b("cases_reported"), "- o número de casos relatados para o estabelecimento / faixa etária na data fornecida")
      )
      
    )
    
  )
)

Observe que também adicionamos uma tag hr() - isso adiciona uma linha horizontal separando nossos widgets de controle de nossos widgets de download. Esta é outra das tags HTML que discutimos anteriormente.

Agora que temos nossa interface do usuário pronta, precisamos adicionar o componente do servidor. Os downloads são feitos no servidor com a função downloadHandler(). Semelhante ao nosso gráfico, precisamos anexá-lo a uma saída que tenha o mesmo inputId do botão de download. Esta função leva dois argumentos - nome do arquivo e conteúdo - ambos são funções. Como você pode adivinhar, filename é usado para especificar o nome do arquivo baixado, e content é usado para especificar o que deve ser baixado. content contém uma função que você usaria para salvar dados localmente - então se você estivesse baixando um arquivo csv você poderia usar rio :: export(). Já que estamos baixando um gráfico, usaremos ggplot2 :: ggsave(). Vamos ver como o programamos (não o adicionaremos ao servidor ainda).

server <- function(input, output, session) {
  
  output$malaria_epicurve <- renderPlot(
    plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup)
  )
  
  output $ download_epicurve <- downloadHandler (
    filename = function() {
      stringr::str_glue("malaria_epicurve_{input$select_district}.png")
    },
    
    content = function(file) {
      ggsave(file, 
             plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup),
             width = 8, height = 5, dpi = 300)
    }
    
  )
  
}

Observe que a função content sempre recebe um argumento file, que colocamos onde o nome do arquivo de saída é especificado. Você também pode notar que estamos repetindo código aqui - estamos usando nossa função plot_epicurve() duas vezes neste servidor, uma para o download e uma vez para a imagem exibida no aplicativo. Embora isso não afete maciçamente o desempenho, isso significa que o código para gerar este gráfico terá que ser executado quando o usuário alterar os widgets especificando o distrito e a faixa etária, e novamente quando você quiser fazer o download do gráfico. Em aplicativos maiores, decisões subótimas como esta irão desacelerar as coisas cada vez mais, então é bom aprender como tornar nosso aplicativo mais eficiente neste sentido. O que faria mais sentido é se tivéssemos uma maneira de executar o código epicurva quando os distritos / grupos de idade são alterados, e permitir que isso seja usado pelas funções renderPlot() e downloadHandler(). É aqui que entram os condutores reativos!

Condutores reativos são objetos que são criados no servidor shiny de uma forma reativa, mas não são emitidos - eles podem apenas ser usados por outras partes do servidor. Existem vários tipos diferentes de condutores reativos, mas examinaremos os dois básicos.

1.reactive()- este é o condutor reativo mais básico - ele reagirá sempre que qualquer entrada usada dentro dele mudar (então nossos widgets de distrito / faixa etária)
2. eventReactive() - este condutor reativo funciona da mesma forma que reactive(), exceto que o usuário pode especificar quais entradas fazem com que ele seja executado novamente. Isso é útil se o seu condutor reativo demorar muito para processar, mas isso será explicado mais tarde.

Vejamos os dois exemplos:

malaria_plot_r <- reactive({
  
  plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup)
  
})


# só funciona quando o seletor de distrito muda!
malaria_plot_er <- eventReactive(input$select_district, {
  
  plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup)
  
})

Quando usamos a configuração eventReactive(), podemos especificar quais entradas fazem com que esse pedaço de código seja executado - isso não é muito útil para nós no momento, então podemos deixar por enquanto. Observe que você pode incluir várias entradas com c()

Vamos ver como podemos integrar isso em nosso código de servidor:

server <- function(input, output, session) {
  
  malaria_plot <- reactive({
    plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup)
  })
  
  
  
  output$malaria_epicurve <- renderPlot(
    malaria_plot()
  )
  
  output $ download_epicurve <- downloadHandler (
    
    filename = function() {
      stringr::str_glue("malaria_epicurve_{input$select_district}.png")
    },
    
    content = function(file) {
      ggsave(file, 
             malaria_plot(),
             width = 8, height = 5, dpi = 300)
    }
    
  )
  
}

Você pode ver que estamos apenas chamando a saída de nosso reativo, que definimos em ambas as funções de download e de renderização de gráficos. Uma coisa a notar, e que muitas vezes confunde as pessoas, é que você precisa usar os resultados dos reativos como se fossem funções - então você deve adicionar parênteses vazios no final deles (ou seja, malaria_plot() está correto, e malaria_plot não está). Agora que adicionamos essa solução, nosso aplicativo está um pouco mais arrumado, mais rápido e mais fácil de alterar, pois todo o nosso código que executa a função da epicurva está em um só lugar.

Adicionando um seletor de instalação

Vamos passar para o nosso próximo recurso - um seletor para instalações específicas. Implementaremos outro parâmetro em nossa função para que possamos informar isso como um argumento de nosso código. Vamos ver como fazer isso primeiro - ele apenas opera com os mesmos princípios que os outros parâmetros que já configuramos. Vamos atualizar e testar nossa função.

plot_epicurve <- function(data, district = "All", agegroup = "malaria_tot", facility = "All") {
  
  if (!("All" %in% district)) {
    data <- data %>%
      filter(District %in% district)
    
    plot_title_district <- stringr::str_glue("{paste0(district, collapse = ', ')} districts")
    
  } else {
    
    plot_title_district <- "all districts"
    
  }
  
  # se não houver dados restantes, retorna NULL
  if (nrow(data) == 0) {
    
    return(NULL)
  }
  
  data <- data %>%
    filter(age_group == agegroup)
  
  
  # se não houver dados restantes, retorna NULL
  if (nrow(data) == 0) {
    
    return(NULL)
  }
  
  if (agegroup == "malaria_tot") {
      agegroup_title <- "All ages"
  } else {
    agegroup_title <- stringr::str_glue("{str_remove(agegroup, 'malaria_rdt')} years")
  }
  
    if(! ("All" %in% facility)) {
    data <- data %>%
      filter(location_name == facility)
    
    plot_title_facility <- facility
    
  } else {
    
    plot_title_facility <- "todas as instalações"
    
  }
  
  # se não houver dados restantes, retorna NULL
  if (nrow(data) == 0) {
    
    return(NULL)
  }

  
  
  ggplot(data, aes(x = data_date, y = cases_reported)) +
    geom_col(width = 1, fill = "darkred") +
    theme_minimal () +
    labs(
      x = "date",
      y = "number of cases",
      title = stringr::str_glue("Malaria cases - {plot_title_district}; {plot_title_facility}"),
      subtitle = agegroup_title
    )
  
  
  
}

Vamos testar:

Com todas as instalações em nossos dados, não está muito claro quais instalações correspondem a quais distritos - e o usuário final também não saberá. Isso pode tornar o uso do aplicativo pouco intuitivo. Por esse motivo, devemos fazer com que as opções de instalação na IU mudem dinamicamente à medida que o usuário muda o distrito - para que uma filtre a outra! Uma vez que temos tantas variáveis que estamos usando nas opções, também podemos querer gerar algumas de nossas opções para a interface do usuário em nosso arquivo global.R _ a partir dos dados_. Por exemplo, podemos adicionar este fragmento de código a global.R depois de lermos nossos dados em:

`summarise()` has grouped output by 'location_name'. You can override using the
`.groups` argument.

Vamos dar uma olhada neles:

all_districts
[1] "All"     "Spring"  "Bolo"    "Dingo"   "Barnard"
installation_list
# A tibble: 65 × 2
   location_name District
   <chr>         <chr>   
 1 Facility 1    Spring  
 2 Facility 10   Bolo    
 3 Facility 11   Spring  
 4 Facility 12   Dingo   
 5 Facility 13   Bolo    
 6 Facility 14   Dingo   
 7 Facility 15   Barnard 
 8 Facility 16   Barnard 
 9 Facility 17   Barnard 
10 Facility 18   Bolo    
# ℹ 55 more rows

Podemos passar essas novas variáveis para a interface do usuário sem nenhum problema, pois elas são visíveis globalmente pelo servidor e pela interface do usuário! Vamos atualizar nossa IU:

ui <- fluidPage(

  titlePanel ("Aplicativo de visualização de instalação para malária"),

  sidebarLayout(

    sidebarPanel(
         # seletor para distrito
         selectInput(
              inputId = "select_district",
              label = "Selecionar distrito",
              choices = all_districts,
              selecionado = "Tudo",
              múltiplo = FALSE
         ),
         # seletor para faixa etária
         selectInput(
              inputId = "select_agegroup",
              label = "Selecionar faixa etária",
              escolhas = c(
                   "Todas as idades" = "malaria_tot",
                   "0-4 yrs" = "malaria_rdt_0-4",
                   "5-14 yrs" = "malaria_rdt_5-14",
                   "15+ yrs" = "malaria_rdt_15"
              ),
              selecionado = "Tudo",
              múltiplo = FALSE
         ),
         # seletor para instalação
         selectInput(
           inputId = "select_facility",
           label = "Selecionar Instalação",
           choices = c("All", facility_list$location_name),
           selecionado = "All"
         ),
         
         # linha horizontal
         hr(),
         downloadButton(
           outputId = "download_epicurve",
           label = "Baixar plot"
         )

    ),

    mainPanel(
      # epicurva vai aqui
      plotOutput("malaria_epicurve"),
      br(),
      hr(),
      p("Bem-vindo ao aplicativo de visualização de instalações para malária! Para usar este aplicativo, manipule os widgets ao lado para alterar a curva epidêmica de acordo com suas preferências! Para baixar uma imagem de alta qualidade do gráfico que você criou, você também pode baixá-la com o botão de download. Para ver os dados brutos, use a guia de dados brutos para uma forma interativa da tabela. O dicionário de dados é o seguinte:"),
      tags$ul(
        tags$li(tags$b("location_name"), "- a instalação onde os dados foram coletados"),
        tags$li(tags$b("data_date"), "- a data em que os dados foram coletados"),
        tags$li(tags$b("submitted_daate"), "- a data em que os dados foram enviados"),
        tags$li(tags$b("Província"), "- a província onde os dados foram coletados (todo 'Norte' para este conjunto de dados)"),
        tags$li(tags$b("Distrito"), "- o distrito onde os dados foram coletados"),
        tags$li(tags$b("age_group"), "- a faixa etária para a qual os dados foram coletados (0-5, 5-14, 15+ e todas as idades)"),
        tags$li(tags$b("cases_reported"), "- o número de casos relatados para o estabelecimento / faixa etária na data fornecida")
      )
      
    )
    
  )
)

Observe como agora estamos informando variáveis para nossas escolhas em vez de codificá-las permanentemente na interface do usuário! Isso pode tornar nosso código mais compacto também! Por último, teremos que atualizar o servidor. Será fácil atualizar nossa função para incorporar nossa nova entrada (só temos que passá-la como um argumento para nosso novo parâmetro), mas devemos lembrar que também queremos que a interface do usuário seja atualizada dinamicamente quando o usuário muda o distrito selecionado. É importante entender aqui que podemos alterar os parâmetros e o comportamento dos widgets enquanto o aplicativo está em execução, mas isso precisa ser feito no servidor. Precisamos entender uma nova forma de saída para o servidor para aprender como fazer isso.

As funções que precisamos entender como fazer isso são conhecidas como funções de observador e são semelhantes às funções reativas no modo como se comportam. No entanto, eles têm uma diferença fundamental:

  • As funções reativas não afetam diretamente as saídas e produzem objetos que podem ser vistos em outros locais no servidor
  • As funções do observador podem afetar as saídas do servidor, mas fazem isso por meio de efeitos colaterais de outras funções. (Eles também podem fazer outras coisas, mas esta é sua função principal na prática)

Semelhante às funções reativas, existem dois tipos de funções de observador, e eles são divididos pela mesma lógica que divide as funções reativas:

  1. observe() - esta função é executada sempre que qualquer entrada usada dentro dela muda
  2. observeEvent() - esta função é executada quando uma entrada especificada pelo usuário muda

Também precisamos entender as funções fornecidas de forma shiny que atualizam widgets. Eles são bastante simples de executar - eles primeiro pegam o objeto sessão da função do servidor (isso não precisa ser entendido por enquanto), e então o inputId da função a ser alterado. Em seguida, passamos novas versões de todos os parâmetros que já são obtidos por selectInput() - eles serão atualizados automaticamente no widget.

Vejamos um exemplo isolado de como poderíamos usar isso em nosso servidor. Quando o usuário muda o distrito, queremos filtrar nossa tabela de instalações por distrito e atualizar as opções para refletir apenas aquelas que estão disponíveis naquele distrito (e uma opção para todas as instalações)

observe({
  
  if (input$select_district == "All") {
    new_choices <- facility_list$location_name
  } else {
    new_choices <- facility_list%>%
      filter(District == input$select_district) %>%
      pull(location_name)
  }
  
  new_choices <- c("All", new_choices)
  
  updateSelectInput(session, inputId = "select_facility",
                    choices = new_choices)
  
})

E é isso! podemos adicioná-lo ao nosso servidor, e esse comportamento agora funcionará. Esta é a aparência de nosso novo servidor:

server <- function(input, output, session) {
  
  malaria_plot <- reactive({
    plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup, facility = input$select_facility)
  })
  
  
  
  observe({
    
    if (input$select_district == "All") {
      new_choices <- facility_list$location_name
    } else {
      new_choices <- facility_list%>%
        filter(District == input$select_district) %>%
        pull(location_name)
    }
    
    new_choices <- c("All", new_choices)
    
    updateSelectInput(session, inputId = "select_facility",
                      choices = new_choices)
    
  })
  
  
  output$malaria_epicurve <- renderPlot(
    malaria_plot()
  )
  
  output $ download_epicurve <- downloadHandler (
    
    filename = function() {
      stringr::str_glue("malaria_epicurve_{input$select_district}.png")
    },
    
    content = function(file) {
      ggsave(file, 
             malaria_plot(),
             width = 8, height = 5, dpi = 300)
    }
    
  )
  
  
  
}

Adicionando outra guia com uma tabela

Agora vamos passar para o último componente que queremos adicionar ao nosso aplicativo. Queremos separar nossa interface do usuário em duas guias, uma das quais terá uma tabela interativa onde o usuário pode ver os dados com os quais está fazendo a curva epidêmica. Para fazer isso, podemos usar os elementos da interface do usuário, que já vêm no pacote shiny, que relevantes para a elaboração de guias. Em um nível básico, podemos incluir a maior parte de nosso painel principal nesta estrutura geral:

# ... o resto da interface do usuário

mainPanel(
  
  tabsetPanel(
    type = "tabs",
    tabPanel(
      "Curvas epidêmicas",
      ...
    ),
    tabPanel(
      "Dados",
      ...
    )
  )
)

Vamos aplicar isso à nossa interface do usuário. Também queremos usar o pacote DT aqui - este é um ótimo pacote para fazer tabelas interativas a partir de dados pré-existentes. Podemos ver que está sendo usado para DT :: datatableOutput() neste exemplo.

ui <- fluidPage(
     
     titlePanel ("Aplicativo de visualização de instalação para malária"),
     
     sidebarLayout(
          
          sidebarPanel(
               # seletor para distrito
               selectInput(
                    inputId = "select_district",
                    label = "Selecionar distrito",
                    choices = all_districts,
                    selecionado = "Tudo",
                    múltiplo = FALSE
               ),
               # seletor para faixa etária
               selectInput(
                    inputId = "select_agegroup",
                    label = "Selecionar faixa etária",
                    escolhas = c(
                         "Todas as idades" = "malaria_tot",
                         "0-4 yrs" = "malaria_rdt_0-4",
                         "5-14 yrs" = "malaria_rdt_5-14",
                         "15+ yrs" = "malaria_rdt_15"
                    ),
                    selecionado = "Tudo",
                    múltiplo = FALSE
               ),
               # seletor para instalação
               selectInput(
                    inputId = "select_facility",
                    label = "Selecionar Instalação",
                    choices = c("All", facility_list$location_name),
                    selecionado = "All"
               ),
               
               # linha horizontal
               hr(),
               downloadButton(
                    outputId = "download_epicurve",
                    label = "Baixar plot"
               )
               
          ),
          
          mainPanel(
               tabsetPanel(
                    type = "tabs",
                    tabPanel(
                         "Curvas epidêmicas",
                         plotOutput("malaria_epicurve")
                    ),
                    tabPanel(
                         "Dados",
                         DT::dataTableOutput("raw_data")
                    )
               ),
               br(),
               hr(),
               p("Bem-vindo ao aplicativo de visualização de instalações para malária! Para usar este aplicativo, manipule os widgets ao lado para alterar a curva epidêmica de acordo com suas preferências! Para baixar uma imagem de alta qualidade do gráfico que você criou, você também pode baixá-la com o botão de download. Para ver os dados brutos, use a guia de dados brutos para uma forma interativa da tabela. O dicionário de dados é o seguinte:"),
               tags$ul(
                    tags$li(tags$b("location_name"), "- a instalação onde os dados foram coletados"),
                    tags$li(tags$b("data_date"), "- a data em que os dados foram coletados"),
                    tags$li(tags$b("submitted_daate"), "- a data em que os dados foram enviados"),
                    tags$li(tags$b("Província"), "- a província onde os dados foram coletados (todo 'Norte' para este conjunto de dados)"),
                    tags$li(tags$b("Distrito"), "- o distrito onde os dados foram coletados"),
                    tags$li(tags$b("age_group"), "- a faixa etária para a qual os dados foram coletados (0-5, 5-14, 15+ e todas as idades)"),
                    tags$li(tags$b("cases_reported"), "- o número de casos relatados para o estabelecimento / faixa etária na data fornecida")
               )
               
               
          )
     )
)

Agora nosso aplicativo está organizado em guias! Vamos fazer as edições necessárias no servidor também. Como não precisamos manipular nosso conjunto de dados antes de renderizá-lo, isso é na verdade muito simples - apenas renderizamos o conjunto de dados malaria_data via DT :: renderDT() para a interface do usuário!

server <- function(input, output, session) {
  
  malaria_plot <- reactive({
    plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup, facility = input$select_facility)
  })
  
  
  
  observe({
    
    if (input$select_district == "All") {
      new_choices <- facility_list$location_name
    } else {
      new_choices <- facility_list%>%
        filter(District == input$select_district) %>%
        pull(location_name)
    }
    
    new_choices <- c("All", new_choices)
    
    updateSelectInput(session, inputId = "select_facility",
                      choices = new_choices)
    
  })
  
  
  output$malaria_epicurve <- renderPlot(
    malaria_plot()
  )
  
  output $ download_epicurve <- downloadHandler (
    
    filename = function() {
      stringr::str_glue("malaria_epicurve_{input$select_district}.png")
    },
    
    content = function(file) {
      ggsave(file, 
             malaria_plot(),
             width = 8, height = 5, dpi = 300)
    }
    
  )
  
  # renderiza a tabela de dados para ui
  output$raw_data <- DT::renderDT(
    malaria_data
  )
  
  
}

43.7 Compartilhando aplicativos shiny

Agora que você desenvolveu seu aplicativo, provavelmente deseja compartilhá-lo com outras pessoas - afinal, essa é a principal vantagem do shiny! Podemos fazer isso compartilhando o código diretamente ou podemos publicá-lo em um servidor. Se compartilharmos o código, outros poderão ver o que você fez e construir sobre ele, mas isso negará uma das principais vantagens do shiny - pode eliminar a necessidade dos usuários finais de manter uma instalação R . Por esse motivo, se você estiver compartilhando seu aplicativo com usuários que não se sentem confortáveis com R, é muito mais fácil compartilhar um aplicativo que foi publicado em um servidor.

Se você preferir compartilhar o código, pode fazer um arquivo .zip do aplicativo, ou melhor ainda, publicar seu aplicativo no github e adicionar colaboradores. Você pode consultar a seção no github para obter mais informações aqui.

No entanto, se estamos publicando o aplicativo online, precisamos trabalhar um pouco mais. Em última análise, queremos que seu aplicativo possa ser acessado por meio de um URL da web para que outras pessoas possam ter acesso rápido e fácil a ele. Infelizmente, para publicar seu aplicativo em um servidor, você precisa ter acesso a um servidor para publicá-lo! Existem várias opções de hospedagem quando se trata disso:

  • shinyapps.io: este é o lugar mais fácil para publicar aplicativos shiny, pois tem a menor quantidade de trabalho de configuração necessária e tem algumas licenças gratuitas, mas limitadas.

  • RStudio Connect: esta é uma versão muito mais poderosa de um servidor R, que pode realizar muitas operações, incluindo a publicação de aplicativos shiny. No entanto, é mais difícil de usar e menos recomendado para usuários iniciantes.

Para os fins deste documento, usaremos shinyapps.io, pois é mais fácil para usuários iniciantes. Você pode criar uma conta gratuita aqui para começar - também existem diferentes planos de preços para licenças de servidor, se necessário. Quanto mais usuários você espera ter, mais caro seu plano de preços terá que ser, portanto, mantenha isso em consideração. Se você está procurando criar algo para um pequeno grupo de indivíduos usar, uma licença gratuita pode ser perfeitamente adequada, mas um aplicativo voltado ao público pode precisar de mais licenças.

Primeiro, devemos nos certificar de que nosso aplicativo é adequado para publicação em um servidor. Em seu aplicativo, você deve reiniciar sua sessão R e garantir que ela seja executada sem executar nenhum código extra. Isso é importante, pois um aplicativo que requer carregamento de pacote ou a leitura de dados não definida no código do aplicativo não será executado em um servidor. Observe também que você não pode ter nenhum caminho de arquivo explícito em seu aplicativo - eles serão inválidos na configuração do servidor - usar o pacote here resolve este problema muito bem. Finalmente, se você estiver lendo dados de uma fonte que requer autenticação do usuário, como os servidores da sua organização, isso geralmente não funcionará em um servidor. Você precisará entrar em contato com seu departamento de TI para descobrir como colocar o servidor shiny na lista de permissões aqui.

inscrevendo-se em uma conta

Depois de ter sua conta, você pode navegar até a página de tokens em Contas. Aqui você desejará adicionar um novo token - ele será usado para implantar seu aplicativo.

A partir daqui, você deve observar que o url da sua conta refletirá o nome do seu aplicativo - portanto, se o seu aplicativo se chamar my_app, o url será anexado como xxx.io/my_app/. Escolha o nome do seu aplicativo com sabedoria! Agora que você está pronto, clique em implantar - se for bem-sucedido, seu aplicativo será executado no url da web que você escolheu!

algo sobre como fazer aplicativos em documentos?

43.8 Leitura adicional

Até agora, cobrimos muitos aspectos do shiny e mal arranhamos a superfície do que é oferecido ele. Embora este guia sirva como uma introdução, há muito mais coisas para aprender para entender completamente o shiny. Você deve começar a fazer aplicativos e adicionar gradualmente mais e mais funcionalidades

43.9 Pacotes de extensão recomendados

O que segue representa uma seleção de extensões shiny de alta qualidade que podem ajudá-lo a obter muito mais brilho (trocadilho com o nome shiny que em inglês significa “brilhante”). Em nenhuma ordem particular:

  • BrightWidgets - este pacote oferece muitos, muitos mais widgets que podem ser usados em seu aplicativo. Execute shinyWidgets::shinyWidgetsGallery() para ver uma seleção de widgets disponíveis com este pacote. Veja exemplos aqui

  • Brightjs - este é um excelente pacote que dá ao usuário a habilidade de estender muito a utilidade do shiny por meio de uma série de javascript. Os aplicativos deste pacote variam de muito simples a altamente avançado, mas você pode querer usá-lo primeiro para manipular a interface do usuário de maneiras simples, como ocultar / mostrar elementos ou habilitar / desabilitar botões. Saiba mais aqui

  • Brightdashboard - este pacote expande enormemente a interface do usuário disponível que pode ser usada em shiny, permitindo especificamente que o usuário crie um painel complexo com uma variedade de layouts complexos. Veja mais aqui

  • BrightdashboardPlus - obtenha ainda mais recursos da estrutura do Brightdashboard! Veja mais aqui

  • Brightthemes - mude o tema CSS padrão para seu aplicativo shiny com uma ampla gama de modelos predefinidos! Veja mais aqui

Existem também vários pacotes que podem ser usados para criar saídas interativas compatíveis com o shiny.

  • DT é semi-incorporado em base shiny, mas fornece um grande conjunto de funções para criar tabelas interativas.

  • plotly é um pacote para a criação de gráficos interativos que o usuário pode manipular no aplicativo. Você também pode converter seu gráfico para versões interativas via plotly::ggplotly()! Como alternativas, dygraphs e highcharter também são excelentes.

43.10 Recursos recomendados