× ¿Necesitas ayuda para aprender R? Inscríbete en el Curso de introducción a R de Applied Epi, prueba nuestros Tutoriales gratuitos de R, escribe en nuestro Foro de preguntas y respuestas, o pregunta por nuestra Asistencia técnica para R.

43 Dashboards con Shiny

Los Dashboards (cuadros de mando o tableros de control) suelen ser una buena forma de compartir los resultados de los análisis con otras personas. Elaborar un cuadro de mando con shiny requiere un conocimiento relativamente avanzado del lenguaje R, pero ofrece una personalización y unas posibilidades increíbles.

Se recomienda que alguien que esté aprendiendo a usar Dashboards con shiny tenga buenos conocimientos de transformación y visualización de datos, y se sienta cómodo depurando código y escribiendo funciones. Trabajar con dashboards no es intuitivo cuando se empieza, y es difícil de entender a veces, pero es una gran habilidad para aprender y se hace mucho más fácil con la práctica.

Esta página dará una breve visión general de cómo hacer Dashboards con shiny y sus extensiones. Para un método alternativo de hacer dashboards que es más rápido, más fácil, pero quizás menos personalizable, ver la página sobre flextable (Dashboards with R Markdown).

43.1 Preparación

Cargar paquetes

En este manual destacamos p_load() de pacman, que instala el paquete si es necesario y lo carga para su uso. También puedes cargar los paquetes instalados con library() de R base. Consulta la página Fundamentos de R para obtener más información sobre los paquetes de R.

Comenzamos instalando el paquete R Shiny:

pacman::p_load("shiny")

Importar datos

Si quieres seguir esta página, consulta esta sección del Manual de descarga y datos. Hay enlaces para descargar los scripts de R y los archivos de datos que producen la aplicación final de Shiny.

Si intentas reconstruir la aplicación utilizando estos archivos, ten en cuenta la estructura de carpetas del proyecto R que se crea en el transcurso de la demostración (por ejemplo, carpetas para “data” y para “funcs”).

43.2 Estructura de una app Shiny

Estructuras básicas de archivos

Para entender Shiny, primero tenemos que entender cómo funciona la estructura de archivos de una aplicación. Deberíamos crear un nuevo directorio antes de empezar. Esto puede hacerse más fácil eligiendo Nuevo proyecto en Rstudio, y eligiendo Aplicación Web Shiny. Esto creará la estructura básica de una aplicación shiny para ti.

Al abrir este proyecto, notarás que ya hay un archivo .R llamado app.R. Es esencial que tengamos una de las dos estructuras básicas de archivos:

  1. Un archivo llamado app.R, o
  2. Dos archivos, uno llamado ui.R y el otro server.R

En esta página, utilizaremos el primer enfoque de tener un archivo llamado app.R. Aquí hay un script de ejemplo:

# un ejemplo de app.R

library(shiny)

ui <- fluidPage(

    # Título de la aplicación
    titlePanel("My app"),

    # Barra lateral con un widget de entrada deslizante
    sidebarLayout(
        sidebarPanel(
            sliderInput("input_1")
        ),

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

# Definir la lógica del servidor necesaria para dibujar un histograma
server <- function(input, output) {
     
     plot_1 <- reactive({
          plot_func(param = input_1)
     })
     
    output$my_plot <- renderPlot({
       plot_1()
    })
}


# Ejecutar la aplicación 
shinyApp(ui = ui, server = server)

Si abres este archivo, te darás cuenta de que hay dos objetos definidos: uno llamado ui (interfaz de usuario) y otro llamado server (servidor). Estos objetos deben ser definidos en todas las aplicaciones shiny y son fundamentales para la estructura de la propia aplicación. De hecho, la única diferencia entre las dos estructuras de archivos descritas anteriormente es que en la estructura 1, tanto ui como server están definidos en un solo archivo, mientras que en la estructura 2 están definidos en archivos separados. Nota: también podemos (y deberíamos si tenemos una aplicación más grande) tener otros archivos .R en nuestra estructura que podemos llamar con source() desde nuestra aplicación.

El servidor y la Interfaz de Usuario (ui)

A continuación, tenemos que entender lo que hacen realmente los objetos server y ui. En pocas palabras, se trata de dos objetos que interactúan entre sí cada vez que el usuario interactúa con la app shiny.

El elemento de interfaz de usuario de una aplicación Shiny es, en un nivel básico, el código R que crea una interfaz HTML. Esto significa todo lo que se muestra en la UI de una app. Esto generalmente incluye:

  • “Widgets” - menús desplegables, casillas de verificación, deslizadores, etc. con los que puede interactuar el usuario
  • Gráficos, tablas, etc. - resultados que se generan con el código R
  • Aspectos de la navegación de una aplicación: pestañas, paneles, etc.
  • Texto genérico, hipervínculos, etc.
  • Elementos HTML y CSS (abordados más adelante)

Lo más importante que hay que entender sobre la UI es que recibe entradas del usuario y muestra salidas del servidor. No hay código activo que se ejecute en la UI en ningún momento - todos los cambios que se ven en la UI pasan por el servidor (más o menos). Así que tenemos que hacer nuestros gráficos, descargas, etc en el servidor

El servidor de la app shiny es donde se ejecuta todo el código una vez que la aplicación se inicia. La forma en que esto funciona es un poco confusa. La función del servidor reaccionará efectivamente a la interfaz del usuario con la UI, y ejecutará trozos de código en respuesta. Si las cosas cambian en el servidor, estas serán pasadas de vuelta a la UI, donde pueden verse los cambios. Es importante destacar que el código en el servidor se ejecutará de forma no consecutiva (o es mejor pensarlo así). Básicamente, cada vez que una entrada de la ui afecte a un trozo de código en el servidor, éste se ejecutará automáticamente, y se producirá y mostrará esa salida.

Probablemente todo esto suene muy abstracto por ahora, así que tendremos que sumergirnos en algunos ejemplos para tener una idea clara de cómo funciona realmente.

Antes de empezar a crear una app

Antes de empezar a construir una aplicación, es muy útil saber qué quieres construir. Dado que tu interfaz de usuario estará escrita en código, no puedes visualizar realmente lo que estás construyendo a menos que tengas como objetivo algo específico. Por esta razón, es inmensamente útil mirar muchos ejemplos de aplicaciones Shiny para tener una idea de lo que puedes hacer - ¡incluso mejor si puedes mirar el código fuente detrás de estas aplicaciones! Algunos de los mejores recursos para ello son:

Una vez que tengas una idea de lo que es posible, también es útil hacer un mapa de cómo quieres que sea la tuya; puedes hacerlo en papel o en cualquier software de dibujo (PowerPoint, MS paint, etc.). Es útil empezar con algo sencillo para tu primera aplicación. Tampoco hay que avergonzarse de utilizar el código que encuentres en Internet de una buena aplicación como plantilla para tu trabajo: es mucho más fácil que construir algo desde cero.

43.3 Construir una interfaz de usuario

Cuando construimos nuestra aplicación, es más fácil trabajar en la interfaz de usuario (UI) primero para que podamos ver lo que estamos haciendo, y no arriesgarnos a que la aplicación falle debido a cualquier error del servidor. Como se mencionó anteriormente, a menudo es bueno utilizar una plantilla cuando se trabaja en la interfaz de usuario. Hay una serie de diseños estándar que se pueden utilizar con shiny que están disponibles en el paquete base de shiny, pero vale la pena señalar que también hay una serie de extensiones del paquete como shinydashboard. Utilizaremos un ejemplo del paquete shiny básico para empezar.

Una interfaz de usuario Shiny se define generalmente como una serie de funciones anidadas, en el siguiente orden

  1. Una función que define el diseño general (la más básica es fluidPage(), pero hay más disponibles)
  2. Paneles dentro del diseño como:
  3. Widgets y salidas: pueden conferir entradas al servidor (widgets) o salidas del servidor (salidas)
    • Los widgets suelen tener el estilo de xxxInput(), por ejemplo, selectInput()
    • Las salidas suelen tener el estilo de xxxOutput(), por ejemplo, plotOutput()

Vale la pena repetir que estos datos no se pueden visualizar fácilmente de forma abstracta, por lo que es mejor ver un ejemplo. Consideremos la posibilidad de crear una aplicación básica que visualice nuestros datos de recuento de instalaciones de malaria por distrito. Estos datos tienen muchos parámetros diferentes, por lo que sería estupendo que el usuario final pudiera aplicar algunos filtros para ver los datos por grupo de edad/distrito según su criterio. Podemos utilizar un diseño Shiny muy simple para empezar - el diseño de la barra lateral. Se trata de un diseño en el que los widgets se colocan en una barra lateral a la izquierda, y el gráfico se coloca a la derecha.

Planifiquemos nuestra aplicación: podemos empezar con un selector que nos permita elegir el distrito donde queremos visualizar los datos, y otro que nos permita visualizar el grupo de edad que nos interesa. Con estos filtros pretendemos mostrar una epicurva que refleje estos parámetros. Para ello necesitamos:

  1. Dos menús desplegables que nos permiten elegir el distrito que queremos y el grupo de edad que nos interesa.
  2. Un área donde podemos mostrar nuestra epicurva resultante.

Esto podría ser algo así:

library(shiny)

ui <- fluidPage(

  titlePanel("Malaria facility visualisation app"),

  sidebarLayout(

    sidebarPanel(
         # selector para el distrito
         selectInput(
              inputId = "select_district",
              label = "Select district",
              choices = c(
                   "All",
                   "Spring",
                   "Bolo",
                   "Dingo",
                   "Barnard"
              ),
              selected = "All",
              multiple = TRUE
         ),
         # selector para el grupo de edad
         selectInput(
              inputId = "select_agegroup",
              label = "Select age group",
              choices = c(
                   "All ages" = "malaria_tot",
                   "0-4 yrs" = "malaria_rdt_0-4",
                   "5-14 yrs" = "malaria_rdt_5-14",
                   "15+ yrs" = "malaria_rdt_15"
              ), 
              selected = "All",
              multiple = FALSE
         )

    ),

    mainPanel(
      # La curva epidemiológica va aquí
      plotOutput("malaria_epicurve")
    )
    
  )
)

Cuando se ejecuta app.R con el código de interfaz de usuario anterior (sin código activo en la parte del server de app.R), el diseño aparece con el siguiente aspecto: ten en cuenta que no habrá ningún gráfico si no hay un servidor que lo represente, ¡pero nuestras entradas están funcionando!

Esta es una buena oportunidad para discutir cómo funcionan los widgets - nota que cada widget está aceptando un inputId, una label (etiqueta), y una serie de otras opciones que son específicas para el tipo de widget. Este inputId es extremadamente importante - estos son los IDs que se utilizan para pasar la información de la UI al servidor. Por esta razón, deben ser únicos. Deberías hacer un esfuerzo para denominarlos con algo sensato, y específico a lo que están interactuando en casos de aplicaciones más grandes.

Deberías leer la documentación cuidadosamente para conocer todos los detalles sobre lo que hace cada uno de estos widgets. Los widgets pasarán tipos específicos de datos al servidor dependiendo del tipo de widget, y esto debe entenderse completamente. Por ejemplo, selectInput() pasará un dato de tipo carácter al servidor:

  • Si seleccionamos Spring para el primer widget aquí, pasará el objeto carácter "Spring" al servidor.
  • Si seleccionamos dos elementos del menú desplegable, aparecerán como un vector de caracteres (por ejemplo, c("Primavera", "Bolo")).

Otros widgets pasarán diferentes tipos de objetos al servidor. Por ejemplo:

También vale la pena tener en cuenta el nombre del vector que usaremos para los datos de edad aquí. Para muchos widgets, el uso de un vector para las opciones mostrará los nombres del vector como las opciones de visualización, pero pasará el valor seleccionado del vector al servidor. Por ejemplo, aquí alguien puede seleccionar “15+” en el menú desplegable, y la interfaz de usuario pasará "malaria_rdt_15" al servidor, que resulta ser el nombre de la columna que nos interesa.

Hay un montón de widgets que puedes utilizar para hacer muchas cosas con tu aplicación. Los widgets también permiten cargar archivos en la aplicación y descargar resultados. También hay algunas excelentes extensiones de shiny que te dan acceso a más widgets que el shiny básico - el paquete shinyWidgets es un gran ejemplo de esto. Para ver algunos ejemplos puedes consultar los siguientes enlaces:

43.4 Cargar datos en nuestra app

El siguiente paso en el desarrollo de nuestra aplicación es poner en marcha el servidor. Para ello, sin embargo, tenemos que conseguir algunos datos en nuestra aplicación, y averiguar todos los cálculos que vamos a hacer. Una aplicación Shiny no es fácil de depurar, ya que a menudo no está claro de dónde provienen los errores, por lo que es ideal desarrollar el código todo nuestro procesamiento de datos y visualización antes de empezar a hacer el propio servidor.

Así que dado que queremos hacer una aplicación que muestre epicurvas que cambien en base a la entrada del usuario, deberíamos pensar en qué código necesitaríamos para ejecutar esto en un script normal de R. Necesitaremos:

  1. Cargar nuestros paquetes
  2. Cargar nuestros datos
  3. Transformar nuestros datos
  4. Desarrollar una función para visualizar nuestros datos en función de las entradas del usuario

Esta lista es bastante sencilla, y no debería ser demasiado difícil de hacer. Ahora es importante pensar qué partes de este proceso deben hacerse una sola vez y qué partes deben ejecutarse en respuesta a las entradas del usuario. Esto se debe a que las aplicaciones Shiny generalmente ejecutan algún código antes de ejecutarse, que sólo se realiza una vez. Ayudará al rendimiento de nuestra aplicación si la mayor parte de nuestro código puede ser trasladado a esta sección. Para este ejemplo, sólo necesitamos cargar nuestros datos/paquetes y hacer transformaciones básicas una vez, así que podemos poner ese código fuera del servidor. Esto significa que lo único que necesitaremos en el servidor es el código para visualizar nuestros datos. Vamos a desarrollar todos estos componentes en un script primero. Sin embargo, ya que estamos visualizando nuestros datos con una función, también podemos poner el código de la función fuera del servidor para que nuestra función esté en el entorno cuando la aplicación se ejecute.

Primero vamos a cargar nuestros datos. Ya que estamos trabajando con un nuevo proyecto, y queremos limpiarlo, podemos crear un nuevo directorio llamado data, y añadir nuestros datos de malaria allí. Podemos ejecutar este código de abajo en un script de prueba que eventualmente borraremos cuando limpiemos la estructura de nuestra aplicación.

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

# lectura de datos
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` `malaria_rdt_5-14` malaria_rdt_15 malaria_tot newid
##    <chr>         <date>     <date>         <chr>    <chr>                <int>              <int>          <int>       <int> <int>
##  1 Facility 1    2020-08-11 2020-08-12     North    Spring                  11                 12             23          46     1
##  2 Facility 2    2020-08-11 2020-08-12     North    Bolo                    11                 10              5          26     2
##  3 Facility 3    2020-08-11 2020-08-12     North    Dingo                    8                  5              5          18     3
##  4 Facility 4    2020-08-11 2020-08-12     North    Bolo                    16                 16             17          49     4
##  5 Facility 5    2020-08-11 2020-08-12     North    Bolo                     9                  2              6          17     5
##  6 Facility 6    2020-08-11 2020-08-12     North    Dingo                    3                  1              4           8     6
##  7 Facility 6    2020-08-10 2020-08-12     North    Dingo                    4                  0              3           7     6
##  8 Facility 5    2020-08-10 2020-08-12     North    Bolo                    15                 14             13          42     5
##  9 Facility 5    2020-08-09 2020-08-12     North    Bolo                    11                 11             13          35     5
## 10 Facility 5    2020-08-08 2020-08-12     North    Bolo                    19                 15             15          49     5
## # … with 3,028 more rows

Será más fácil trabajar con estos datos si utilizamos estándares de datos ordenados, por lo que también debemos transformarlos en un formato de datos más largo, donde el grupo de edad es una columna, y los casos son otra columna. Podemos hacer esto fácilmente usando lo que hemos aprendido en la página de Pivotar datos.

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        cases_reported
##    <chr>         <date>     <date>         <chr>    <chr>    <chr>                     <int>
##  1 Facility 1    2020-08-11 2020-08-12     North    Spring   malaria_rdt_0-4              11
##  2 Facility 1    2020-08-11 2020-08-12     North    Spring   malaria_rdt_5-14             12
##  3 Facility 1    2020-08-11 2020-08-12     North    Spring   malaria_rdt_15               23
##  4 Facility 1    2020-08-11 2020-08-12     North    Spring   malaria_tot                  46
##  5 Facility 2    2020-08-11 2020-08-12     North    Bolo     malaria_rdt_0-4              11
##  6 Facility 2    2020-08-11 2020-08-12     North    Bolo     malaria_rdt_5-14             10
##  7 Facility 2    2020-08-11 2020-08-12     North    Bolo     malaria_rdt_15                5
##  8 Facility 2    2020-08-11 2020-08-12     North    Bolo     malaria_tot                  26
##  9 Facility 3    2020-08-11 2020-08-12     North    Dingo    malaria_rdt_0-4               8
## 10 Facility 3    2020-08-11 2020-08-12     North    Dingo    malaria_rdt_5-14              5
## # … with 12,142 more rows

Y con esto hemos terminado de preparar nuestros datos! Esto tacha los puntos 1, 2 y 3 de nuestra lista de cosas a desarrollar para nuestro “script de prueba de R”. La última tarea, y la más difícil, será construir una función para producir una epicurva basada en parámetros definidos por el usuario. Como se mencionó anteriormente, se recomienda encarecidamente que cualquier persona que aprenda shimy primero mire la sección sobre la programación funcional (Escribir funciones) para entender cómo funciona esto!

Al definir nuestra función, puede ser difícil pensar en los parámetros que queremos incluir. Para la programación funcional con shiny, cada parámetro relevante tendrá generalmente un widget asociado a él, así que pensar en esto suele ser bastante fácil. Por ejemplo, en nuestra aplicación actual, queremos ser capaces de filtrar por distrito, y tener un widget para ello, por lo que podemos añadir un parámetro de distrito para reflejar esto. No tenemos ninguna funcionalidad de la aplicación para filtrar por centro (por ahora), así que no necesitamos añadir esto como parámetro. Empecemos haciendo una función con tres parámetros:

  1. Los datos básicos
  2. El distrito de elección
  3. El grupo de edad elegido
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"
    
  }
  
  # si no quedan datos, devuelve NULL
  if (nrow(data) == 0) {
    
    return(NULL)
  }
  
  data <- data %>%
    filter(age_group == agegroup)
  
  
  # si no quedan datos, devuelve 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
    )
  
  
  
}

No entraremos en grandes detalles sobre esta función, ya que su funcionamiento es relativamente sencillo. Una cosa a tener en cuenta, sin embargo, es que debemos gestionar los errores devolviendo NULL cuando de otro modo daría un error. Esto se debe a que cuando un servidor Shiny produce un objeto NULL en lugar de un objeto gráfico, ¡no se mostrará nada en la interfaz de usuario! Esto es importante, ya que de lo contrario los errores a menudo harán que la aplicación deje de funcionar.

Otra cosa a tener en cuenta es el uso del operador %in% cuando se evalúa la entrada del district. Como se mencionó anteriormente, esto podría llegar como un vector de caracteres con múltiples valores, por lo que el uso de %in% es más flexible que, por ejemplo, ==.

Vamos a probar nuestra función!

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

Con nuestra función ya trabajando, ahora tenemos que entender cómo va a encajar todo esto en nuestra aplicación Shiny. Hemos mencionado el concepto de código de inicio antes, pero vamos a ver cómo podemos incorporar esto en la estructura de nuestra aplicación. Hay dos maneras de hacerlo.

  1. Escribe este código en tu archivo app.R al principio del script (por encima de la interfaz de usuario), o
  2. Crea un nuevo archivo en el directorio de tu aplicación llamado global.R, y pon el código de inicio en él.

Vale la pena señalar en este punto que generalmente es más fácil, especialmente con aplicaciones más grandes, utilizar la segunda estructura de archivos, ya que permite separar su estructura de una manera sencilla. Vamos a desarrollar completamente este script global.R ahora. Esto es lo que podría parecer:

# script global.R

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

# lectura de datos
malaria_data <- rio::import(here::here("data", "malaria_facility_count_data.rds")) %>% 
  as_tibble()

# limpiar datos y pivotar largo
malaria_data <- malaria_data %>%
  select(-newid) %>%
  pivot_longer(cols = starts_with("malaria_"), names_to = "age_group", values_to = "cases_reported")


# define la función de gráficos
plot_epicurve <- function(data, district = "All", agegroup = "malaria_tot") {
  
  # crear el título del gráfico
  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"
    
  }
  
  # si no quedan datos, devuelve NULL
  if (nrow(data) == 0) {
    
    return(NULL)
  }
  
  # filtra el grupo de edad
  data <- data %>%
    filter(age_group == agegroup)
  
  
  # si no quedan datos, devuelve 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! Una gran característica es qe shiny entenderá para qué sirven los archivos llamados app.R, server.R, ui.R y global.R, por lo que no es necesario conectarlos entre sí mediante ningún código. Así que sólo con tener este código en global.R en el directorio adecuado se ejecutará antes de que iniciemos nuestra app!

También debemos tener en cuenta que mejoraría la organización de nuestra aplicación si movemos la función de dibujar a su propio archivo - esto será especialmente útil a medida que las aplicaciones se hacen más grandes. Para hacer esto, podríamos hacer otro directorio llamado funcs, y poner esta función en un archivo llamado plot_epicurve.R. Podríamos entonces leer esta función a través del siguiente comando en global.R

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

Ten en cuenta que siempre debes especificar local = TRUE en las aplicaciones shiny, ya que afectará a la obtención de recursos cuando/si la aplicación se publica en un servidor.

43.5 Desarrollar un servidor de app

Ahora que tenemos la mayor parte de nuestro código, sólo tenemos que desarrollar nuestro servidor. Esta es la pieza final de nuestra aplicación, y es probablemente la más difícil de entender. El servidor es una gran función de R, pero es útil pensar en él como una serie de funciones más pequeñas, o tareas que la aplicación puede realizar. Es importante entender que estas funciones no se ejecutan en un orden lineal. Hay un orden en ellas, pero no es necesario entenderlo del todo cuando se empieza con Shiny. A un nivel muy básico, estas tareas o funciones se activarán cuando haya un cambio en las entradas del usuario que las afecte, a menos que el desarrollador las haya configurado para que se comporten de forma diferente. De nuevo, todo esto es bastante abstracto, pero vamos a repasar primero los tres tipos básicos de objetos shiny

  1. Fuentes reactivas - este es otro término para las entradas del usuario. El servidor shiny tiene acceso a las salidas de la UI a través de los widgets que hemos programado. Cada vez que los valores de estos se cambian, esto se pasa al servidor.

  2. Conductores reactivos - estos son objetos que existen sólo dentro del servidor Shiny. En realidad no los necesitamos para aplicaciones simples, pero producen objetos que sólo pueden ser vistos dentro del servidor, y utilizados en otras operaciones. Generalmente dependen de fuentes reactivas.

  3. Puntos finales: son las salidas que se pasan del servidor a la interfaz de usuario. En nuestro ejemplo, esto sería la epicurva que estamos produciendo.

Con esto en mente vamos a construir nuestro servidor paso a paso. Vamos a mostrar nuestro código de interfaz de usuario de nuevo aquí sólo para referencia:

ui <- fluidPage(

  titlePanel("Malaria facility visualisation app"),

  sidebarLayout(

    sidebarPanel(
         # selector para el distrito
         selectInput(
              inputId = "select_district",
              label = "Select district",
              choices = c(
                   "All",
                   "Spring",
                   "Bolo",
                   "Dingo",
                   "Barnard"
              ),
              selected = "All",
              multiple = TRUE
         ),
         # selector para el grupo de edad
         selectInput(
              inputId = "select_agegroup",
              label = "Select age group",
              choices = c(
                   "All ages" = "malaria_tot",
                   "0-4 yrs" = "malaria_rdt_0-4",
                   "5-14 yrs" = "malaria_rdt_5-14",
                   "15+ yrs" = "malaria_rdt_15"
              ), 
              selected = "All",
              multiple = FALSE
         )

    ),

    mainPanel(
      # La curva epidemiológica va aquí
      plotOutput("malaria_epicurve")
    )
    
  )
)

De este código UI tenemos:

  • Dos entradas:
    • Selector de distrito (con un inputId de select_district)
    • Selector de grupo de edad (con un inputId de select_agegroup)
  • Una salida:
    • La epicurva (con un outputId de malaria_epicurve)

Como hemos dicho anteriormente, estos nombres únicos que hemos asignado a nuestras entradas y salidas son cruciales. Deben ser únicos y se utilizan para pasar información entre la ui y el servidor. En nuestro servidor, accedemos a nuestras entradas a través de la sintaxis input$inputID y a las salidas y las pasamos a la ui a través de la sintaxis output$output_name ¡Veamos un ejemplo, porque de nuevo esto es difícil de entender de otra manera!

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

El servidor para una aplicación simple como esta es en realidad bastante sencillo. Te darás cuenta de que el servidor es una función con tres parámetros - input, output, and session - esto no es tan importante para entender por ahora, pero es importante seguir esta configuración. En nuestro servidor sólo tenemos una tarea - esta procesa un gráfico basado en la función que hicimos antes, y las entradas del servidor. Fíjate en que los nombres de los objetos de entrada y salida se corresponden exactamente con los de la interfaz de usuario.

Para entender los fundamentos de cómo el servidor reacciona a las entradas del usuario, debes tener en cuenta que la salida sabrá (a través del paquete subyacente) cuando las entradas cambian, y volver a ejecutar esta función para crear un gráfico cada vez que cambian. Ten en cuenta que aquí también utilizamos la función renderPlot() - esta es de una familia de funciones específicas del tipo que pasan esos objetos a una salida ui. Hay una serie de funciones que se comportan de manera similar, pero hay que asegurarse de que la función utilizada coincide con el tipo de objeto que se está pasando a la ui. Por ejemplo:

  • renderText() - enviar texto a la ui
  • renderDataTable - envía una tabla interactiva a la ui.

Recuerda que estos también necesitan coincidir con la función de salida utilizada en la ui - así que renderPlot() se empareja con plotOutput(), y renderText() se empareja con textOutput().

Así que finalmente hemos hecho una aplicación que funciona! Podemos ejecutarla clicando el botón Ejecutar aplicación en la parte superior derecha de la ventana de script en Rstudio. Debes tener en cuenta que puedes elegir ejecutar tu aplicación en tu navegador por defecto (en lugar de Rstudio), lo que reflejará con mayor precisión el aspecto que tendrá la aplicación para otros usuarios.

¡Es divertido observar que en la consola R, la aplicación está “escuchando”. Hablando de reactividad!

43.6 Añadir más funcionalidad

En este punto tenemos finalmente una aplicación en funcionamiento, pero tenemos muy poca funcionalidad. Tampoco hemos rascado la superficie de lo que shiny puede hacer, ¡así que hay mucho más que aprender! Vamos a seguir construyendo nuestra aplicación actual añadiendo algunas características adicionales. Algunas cosas que podría ser bueno añadir:

  1. Algunos textos explicativos
  2. Un botón de descarga para nuestra gráfica - esto proporcionaría al usuario una versión de alta calidad de la imagen que está generando en la aplicación
  3. Un selector de instalaciones específicas
  4. Otra página del panel de control: podría mostrar una tabla con nuestros datos.

Esto es mucho para agregar, pero podemos usarlo para aprender en el camino un montón de diferentes características de Shiny. Hay mucho que aprender sobre Shiny (puede ser muy avanzado, pero es de esperar que una vez que los usuarios tienen una mejor idea de cómo usarlo pueden llegar a ser más cómodo usando fuentes de aprendizaje externas también).

Añadir texto estático

Vamos a hablar primero de la adición de texto estático a nuestra aplicación Shiny. Añadir texto a nuestra aplicación es extremadamente fácil, una vez que se tiene un conocimiento básico de la misma. Dado que el texto estático no cambia en la aplicación shiny (si quieres que cambie, puedes utilizar las funciones de procesado de texto en el servidor), todo el texto estático de shiny se añade generalmente en la interfaz de usuario de la aplicación. No vamos a entrar en detalles, pero puedes añadir un número de elementos diferentes a su ui (e incluso personalizados) mediante la interfaz de R con HTML y css.

HTML y css son lenguajes que intervienen explícitamente en el diseño de la interfaz de usuario. No es necesario entenderlos demasiado bien, pero HTML crea objetos en la interfaz de usuario (como un cuadro de texto, o una tabla), y css se utiliza generalmente para cambiar el estilo y la estética de esos objetos. Shiny tiene acceso a una gran variedad de etiquetas HTML - éstas están presentes para los objetos que se comportan de una manera específica, como los encabezados, los párrafos de texto, los saltos de línea, las tablas, etc. Podemos utilizar algunos de estos ejemplos así:

  • h1() - esta es una etiqueta de encabezado, que hará que el texto adjunto sea automáticamente más grande, y cambiará los valores predeterminados en cuanto a la fuente, el color, etc. (dependiendo del tema general de tu aplicación). Puedes acceder a subtítulos cada vez más pequeños con h2() hasta h6() también. El uso es así:

    • h1("mi cabecera - sección 1")
  • p() - esta es una etiqueta de párrafo, que hará que el texto encerrado sea similar al texto de un cuerpo de texto. Este texto se envolverá automáticamente, y será de un tamaño relativamente pequeño (los pies de página podrían ser más pequeños, por ejemplo). Piensa en ello como el cuerpo de texto de un documento de Word. El uso es así:

    • p("Este es un cuerpo de texto más grande donde explico la función de mi aplicación")
  • tags$b() ytags$i() - se utilizan para poner tags$b() en negrita (bold) y tags$i() en cursiva el texto que se incluya entre los paréntesis.

  • tags$ul(), tags$ol() y tags$li() - son etiquetas utilizadas para crear listas. Todas ellas se utilizan dentro de la sintaxis siguiente, y permiten al usuario crear una lista ordenada (tags$ol(); es decir, numerada) o desordenada (tags$ul(), es decir, con viñetas). tags$li() se utiliza para marcar los elementos de la lista, independientemente del tipo de lista que se utilice. p. ej:

tags$ol(
  
  tags$li("Item 1"),
  
  tags$li("Item 2"),
  
  tags$li("Item 3")
  
)
  • br() y hr() - estas etiquetas crean saltos de línea y líneas horizontales (con un salto de línea) respectivamente. Utilízalas para separar las secciones de tu aplicación y el texto. No es necesario pasar ningún elemento a estas etiquetas (los paréntesis pueden permanecer vacíos).

  • div() - esta es una etiqueta genérica que puede contener cualquier cosa, y puede tener cualquier nombre. Una vez que avances en el diseño de la interfaz de usuario, puedes utilizarlas para compartimentar tu interfaz de usuario, dar estilos específicos a determinadas secciones y crear interacciones entre el servidor y los elementos de la interfaz de usuario. No vamos a entrar en detalles, pero vale la pena conocerlos.

Ten en cuenta que se puede acceder a cada uno de estos objetos a través de tags$... o para algunos, sólo la función. Estos son efectivamente sinónimos, pero puede ayudar a utilizar el estilo tags$... si prefieres ser más explícito y no sobrescribir las funciones accidentalmente. Esta no es en absoluto una lista exhaustiva de etiquetas disponibles. Hay una lista completa de todas las etiquetas disponibles en shiny aquí e incluso se pueden utilizar más insertando HTML directamente en su ui!

Si te sientes seguro, también puedes añadir cualquier elemento de estilo css a tus etiquetas HTML con el argumento style en cualquiera de ellas. No vamos a entrar en detalles sobre cómo funciona esto, pero un consejo para probar los cambios estéticos en una interfaz de usuario es utilizar el modo de inspector de HTML en Chrome (de tu aplicación Shiny que está ejecutando en el navegador), y editar el estilo de los objetos tu mismo!

Vamos a añadir algo de texto a nuestra aplicación

ui <- fluidPage(

  titlePanel("Malaria facility visualisation app"),

  sidebarLayout(

    sidebarPanel(
         h4("Options"),
         # selector para el distrito
         selectInput(
              inputId = "select_district",
              label = "Select district",
              choices = c(
                   "All",
                   "Spring",
                   "Bolo",
                   "Dingo",
                   "Barnard"
              ),
              selected = "All",
              multiple = TRUE
         ),
         # selector para el grupo de edad
         selectInput(
              inputId = "select_agegroup",
              label = "Select age group",
              choices = c(
                   "All ages" = "malaria_tot",
                   "0-4 yrs" = "malaria_rdt_0-4",
                   "5-14 yrs" = "malaria_rdt_5-14",
                   "15+ yrs" = "malaria_rdt_15"
              ), 
              selected = "All",
              multiple = FALSE
         ),
    ),

    mainPanel(
      # La curva epidemiológica va aquí
      plotOutput("malaria_epicurve"),
      br(),
      hr(),
      p("Welcome to the malaria facility visualisation app! To use this app, manipulate the widgets on the side to change the epidemic curve according to your preferences! To download a high quality image of the plot you've created, you can also download it with the download button. To see the raw data, use the raw data tab for an interactive form of the table. The data dictionary is as follows:"),
    tags$ul(
      tags$li(tags$b("location_name"), " - the facility that the data were collected at"),
      tags$li(tags$b("data_date"), " - the date the data were collected at"),
      tags$li(tags$b("submitted_daate"), " - the date the data were submitted at"),
      tags$li(tags$b("Province"), " - the province the data were collected at (all 'North' for this dataset)"),
      tags$li(tags$b("District"), " - the district the data were collected at"),
      tags$li(tags$b("age_group"), " - the age group the data were collected for (0-5, 5-14, 15+, and all ages)"),
      tags$li(tags$b("cases_reported"), " - the number of cases reported for the facility/age group on the given date")
    )
    
  )
)
)

Añadir un enlace

Para añadir un enlace a una página web, utiliza tags$a() con el enlace y el texto a mostrar como se muestra a continuación. Para tener como un párrafo independiente, escríbelo dentro de p(). Para tener sólo algunas palabras de una frase enlazada, divide la frase en partes y utiliza tags$a() para la parte hipervinculada. Para que el enlace se abra en una nueva ventana del navegador, añade target = "_blank" como argumento.

tags$a(href = "www.epiRhandbook.com", "Visit our website!")

Añadir un botón de descarga

Pasemos a la segunda de las tres características. Un botón de descarga es una cosa bastante común para añadir a una aplicación y es bastante fácil de hacer. Tenemos que añadir otro Widget a nuestra ui, y tenemos que añadir otra salida a nuestro servidor para adjuntarlo. También podemos introducir conductores reactivos en este ejemplo!

Vamos a actualizar nuestra interfaz de usuario primero - esto es fácil ya que Shiny viene con un widget llamado downloadButton() - vamos a darle un inputId y una label (etiqueta).

ui <- fluidPage(

  titlePanel("Malaria facility visualisation app"),

  sidebarLayout(

    sidebarPanel(
         # selector para el distrito
         selectInput(
              inputId = "select_district",
              label = "Select district",
              choices = c(
                   "All",
                   "Spring",
                   "Bolo",
                   "Dingo",
                   "Barnard"
              ),
              selected = "All",
              multiple = FALSE
         ),
         # selector para el grupo de edad
         selectInput(
              inputId = "select_agegroup",
              label = "Select age group",
              choices = c(
                   "All ages" = "malaria_tot",
                   "0-4 yrs" = "malaria_rdt_0-4",
                   "5-14 yrs" = "malaria_rdt_5-14",
                   "15+ yrs" = "malaria_rdt_15"
              ), 
              selected = "All",
              multiple = FALSE
         ),
         # línea horizontal
         hr(),
         downloadButton(
           outputId = "download_epicurve",
           label = "Download plot"
         )

    ),

    mainPanel(
      # La curva epidemiológica va aquí
      plotOutput("malaria_epicurve"),
      br(),
      hr(),
      p("Welcome to the malaria facility visualisation app! To use this app, manipulate the widgets on the side to change the epidemic curve according to your preferences! To download a high quality image of the plot you've created, you can also download it with the download button. To see the raw data, use the raw data tab for an interactive form of the table. The data dictionary is as follows:"),
      tags$ul(
        tags$li(tags$b("location_name"), " - the facility that the data were collected at"),
        tags$li(tags$b("data_date"), " - the date the data were collected at"),
        tags$li(tags$b("submitted_daate"), " - the date the data were submitted at"),
        tags$li(tags$b("Province"), " - the province the data were collected at (all 'North' for this dataset)"),
        tags$li(tags$b("District"), " - the district the data were collected at"),
        tags$li(tags$b("age_group"), " - the age group the data were collected for (0-5, 5-14, 15+, and all ages)"),
        tags$li(tags$b("cases_reported"), " - the number of cases reported for the facility/age group on the given date")
      )
      
    )
    
  )
)

Observa que también hemos añadido una etiqueta hr() - esto añade una línea horizontal que separa nuestros widgets de control de nuestros widgets de descarga. Esta es otra de las etiquetas HTML que hemos discutido anteriormente.

Ahora que tenemos nuestra ui lista, necesitamos añadir el componente del servidor. Las descargas se realizan en el servidor con la función downloadHandler(). De manera similar a nuestra trama, necesitamos adjuntarla a una salida que tenga el mismo inputId que el botón de descarga. Esta función toma dos argumentos - filename y content - ambos son funciones. Como podrás adivinar, filename se utiliza para especificar el nombre del archivo descargado, y content se utiliza para especificar lo que debe ser descargado. content contiene una función que usarías para guardar los datos localmente - así que si estuvieras descargando un archivo csv podrías usar rio::export(). Como estamos descargando un gráfico, usaremos ggplot2::ggsave(). Veamos cómo programaríamos esto (aún no lo añadiremos al servidor).

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)
    }
    
  )
  
}

Observa que la función content siempre toma un argumento file, que ponemos donde se especifica el nombre del archivo de salida. También puedes notar que estamos repitiendo código aquí - estamos usando nuestra función plot_epicurve() dos veces en este servidor, una para la descarga y otra para la imagen mostrada en la aplicación. Aunque esto no afecta masivamente al rendimiento, significa que el código para generar este gráfico tendrá que ejecutarse cuando el usuario cambie los widgets que especifican el distrito y el grupo de edad, y de nuevo cuando quiera descargar el gráfico. En aplicaciones más grandes, decisiones subóptimas como ésta ralentizarán cada vez más las cosas, así que es bueno aprender a hacer nuestra aplicación más eficiente en este sentido. Lo que tendría más sentido es si tuviéramos una forma de ejecutar el código de la epicurva cuando los distritos/grupos de edad cambien, y dejar que eso sea utilizado por las funciones renderPlot() y downloadHandler(). Aquí es donde entran los conductores reactivos!

Los conductores reactivos son objetos que se crean en el servidor shiny de forma reactiva, pero no se emiten - sólo pueden ser utilizados por otras partes del servidor. Hay varios tipos de conductores reactivos, pero vamos a repasar los dos básicos.

  1. reactive() - este es el conductor reactivo más básico - reaccionará siempre que cualquier entrada utilizada dentro de él cambie (por nuestros widgets de distrito/grupo de edad)
  2. eventReactive() - este conductor rectivo funciona igual que reactive(), excepto que el usuario puede especificar qué entradas hacen que se vuelva a ejecutar. Esto es útil si tu conductor reactivo tarda mucho en procesar, pero esto se explicará más adelante.

Veamos los dos ejemplos:

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


# ¡Sólo se ejecuta cuando cambia el selector de distrito!
malaria_plot_er <- eventReactive(input$select_district, {
  
  plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup)
  
})

Cuando usamos la configuración de eventReactive(), podemos especificar qué entradas hacen que se ejecute este trozo de código - esto no nos es muy útil por el momento, así que podemos dejarlo por ahora. Ten en cuenta que puedes incluir múltiples entradas con c()

Veamos cómo podemos integrar esto en el código de nuestro 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)
    }
    
  )
  
}

Puedes ver que sólo estamos llamando a la salida del reactivo que hemos definido en nuestras funciones de descarga y representación gráfica. Una cosa que hay que tener en cuenta y que suele confundir a la gente es que hay que utilizar las salidas de los reactivos como si fueran funciones, por lo que hay que añadir paréntesis vacíos al final de los mismos (es decir, malaria_plot() es correcto, y malaria_plot no lo es). Ahora que hemos añadido esta solución nuestra aplicación es un poco más ordenada, más rápida y más fácil de cambiar ya que todo el código que ejecuta la función epicurve está en un solo lugar.

Añadir un selector de instalaciones

Pasemos a nuestra siguiente función: un selector para instalaciones específicas. Implementaremos otro parámetro en nuestra función para poder pasarlo como argumento desde nuestro código. Vamos a ver cómo hacer esto primero - sólo funciona con los mismos principios que los otros parámetros que hemos establecido. Actualicemos y probemos nuestra función.

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"
    
  }
  
  # si no quedan datos, devuelve NULL
  if (nrow(data) == 0) {
    
    return(NULL)
  }
  
  data <- data %>%
    filter(age_group == agegroup)
  
  
  # si no quedan datos, devuelve 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 <- "all facilities"
    
  }
  
  # si no quedan datos, devuelve 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 a probarlo:

plot_epicurve(malaria_data, district = "Spring", agegroup = "malaria_rdt_0-4", facility = "Facility 1")

Con todas las instalaciones en nuestros datos, no está muy claro qué instalaciones corresponden a qué distritos, y el usuario final tampoco lo sabrá. Esto puede hacer que el uso de la aplicación sea poco intuitivo. Por esta razón, debemos hacer que las opciones de instalaciones en la interfaz de usuario cambien dinámicamente a medida que el usuario cambia de distrito, de modo que una filtra a la otra. Dado que tenemos tantas variables que estamos utilizando en las opciones, también podríamos querer generar algunas de nuestras opciones para la ui en nuestro archivo global.R a partir de los datos. Por ejemplo, podemos añadir este trozo de código a global.R después de haber leído nuestros datos:

all_districts <- c("All", unique(malaria_data$District))

# Dataframe de los nombres de las localidades por distrito
facility_list <- malaria_data %>%
  group_by(location_name, District) %>%
  summarise() %>% 
  ungroup()

Vamos a verlos:

all_districts
## [1] "All"     "Spring"  "Bolo"    "Dingo"   "Barnard"
facility_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    
## # … with 55 more rows

Podemos pasar estas nuevas variables a la ui sin ningún problema, ya que son visibles globalmente tanto por el servidor como por la ui. Actualicemos nuestra UI:

ui <- fluidPage(

  titlePanel("Malaria facility visualisation app"),

  sidebarLayout(

    sidebarPanel(
         # selector para el distrito
         selectInput(
              inputId = "select_district",
              label = "Select district",
              choices = all_districts,
              selected = "All",
              multiple = FALSE
         ),
         # selector para el grupo de edad
         selectInput(
              inputId = "select_agegroup",
              label = "Select age group",
              choices = c(
                   "All ages" = "malaria_tot",
                   "0-4 yrs" = "malaria_rdt_0-4",
                   "5-14 yrs" = "malaria_rdt_5-14",
                   "15+ yrs" = "malaria_rdt_15"
              ), 
              selected = "All",
              multiple = FALSE
         ),
         # selector para los centros
         selectInput(
           inputId = "select_facility",
           label = "Select Facility",
           choices = c("All", facility_list$location_name),
           selected = "All"
         ),
         
         # línea horizontal
         hr(),
         downloadButton(
           outputId = "download_epicurve",
           label = "Download plot"
         )

    ),

    mainPanel(
      # La curva epidemiológica va aquí
      plotOutput("malaria_epicurve"),
      br(),
      hr(),
      p("Welcome to the malaria facility visualisation app! To use this app, manipulate the widgets on the side to change the epidemic curve according to your preferences! To download a high quality image of the plot you've created, you can also download it with the download button. To see the raw data, use the raw data tab for an interactive form of the table. The data dictionary is as follows:"),
      tags$ul(
        tags$li(tags$b("location_name"), " - the facility that the data were collected at"),
        tags$li(tags$b("data_date"), " - the date the data were collected at"),
        tags$li(tags$b("submitted_daate"), " - the date the data were submitted at"),
        tags$li(tags$b("Province"), " - the province the data were collected at (all 'North' for this dataset)"),
        tags$li(tags$b("District"), " - the district the data were collected at"),
        tags$li(tags$b("age_group"), " - the age group the data were collected for (0-5, 5-14, 15+, and all ages)"),
        tags$li(tags$b("cases_reported"), " - the number of cases reported for the facility/age group on the given date")
      )
      
    )
    
  )
)

Fíjate en que ahora pasamos variables para nuestras elecciones en lugar de codificarlas en la interfaz de usuario. Esto también puede hacer que nuestro código sea más compacto. Por último, tendremos que actualizar el servidor. Será fácil actualizar nuestra función para incorporar nuestra nueva entrada (sólo tenemos que pasarla como argumento a nuestro nuevo parámetro), pero debemos recordar que también queremos que la ui se actualice dinámicamente cuando el usuario cambie el distrito seleccionado. Es importante entender aquí que podemos cambiar los parámetros y el comportamiento de los widgets mientras la aplicación se está ejecutando, pero esto debe hacerse en el servidor. Tenemos que entender una nueva forma de salida al servidor para aprender a hacer esto.

Las funciones que necesitamos para entender cómo hacer esto se conocen como funciones de observador, y son similares a las funciones reactivas en cuanto a su comportamiento. Sin embargo, tienen una diferencia clave:

  • Las funciones reactivas no afectan directamente a las salidas, y producen objetos que pueden verse en otros lugares del servidor

  • Las funciones de los observadores pueden afectar a las salidas del servidor, pero lo hacen a través de los efectos secundarios de otras funciones. (También pueden hacer otras cosas, pero esta es su función principal en la práctica)

Al igual que las funciones reactivas, hay dos tipos de funciones de observador, y se dividen por la misma lógica que divide las funciones reactivas:

  1. observe() - esta función se ejecuta cada vez que cambian las entradas utilizadas dentro de ella
  2. observeEvent() - esta función se ejecuta cuando cambia una entrada especificada por el usuario

También necesitamos entender las funciones proporcionadas por Shiny que actualizan los widgets. Estas son bastante sencillas de ejecutar - primero toman el objeto session de la función del servidor (esto no necesita ser entendido por ahora), y luego el inputId de la función a ser cambiada. Luego pasamos las nuevas versiones de todos los parámetros que ya son tomados por selectInput() - estos serán actualizados automáticamente en el widget.

Veamos un ejemplo aislado de cómo podríamos utilizar esto en nuestro servidor. Cuando el usuario cambia de distrito, queremos filtrar nuestra lista de instalaciones por distrito, y actualizar las opciones para que sólo reflejen las que están disponibles en ese distrito (y una opción para todas las instalaciones)

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)
  
})

Y ya está, podemos añadirlo a nuestro servidor, y ese comportamiento ya funcionará. Este es el aspecto que debería tener nuestro nuevo 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)
    }
    
  )
  
  
  
}

Añadir otra pestaña con una tabla

Ahora pasaremos al último componente que queremos añadir a nuestra aplicación. Querremos separar nuestra ui en dos pestañas, una de las cuales tendrá una tabla interactiva donde el usuario podrá ver los datos con los que está haciendo la curva epidémica. Para ello, podemos utilizar los elementos de ui empaquetados que vienen con shiny relevantes para las pestañas. En un nivel básico, podemos encerrar la mayor parte de nuestro panel principal en esta estructura general:

# ... el resto de la interfaz de usuario

mainPanel(
  
  tabsetPanel(
    type = "tabs",
    tabPanel(
      "Epidemic Curves",
      ...
    ),
    tabPanel(
      "Data",
      ...
    )
  )
)

Apliquemos esto a nuestra ui. También vamos a querer utilizar el paquete DT aquí - este es un gran paquete para hacer tablas interactivas a partir de datos preexistentes. Podemos ver que se utiliza para DT::datatableOutput() en este ejemplo.

ui <- fluidPage(
     
     titlePanel("Malaria facility visualisation app"),
     
     sidebarLayout(
          
          sidebarPanel(
               # selector para el distrito
               selectInput(
                    inputId = "select_district",
                    label = "Select district",
                    choices = all_districts,
                    selected = "All",
                    multiple = FALSE
               ),
               # selector para el grupo de edad
               selectInput(
                    inputId = "select_agegroup",
                    label = "Select age group",
                    choices = c(
                         "All ages" = "malaria_tot",
                         "0-4 yrs" = "malaria_rdt_0-4",
                         "5-14 yrs" = "malaria_rdt_5-14",
                         "15+ yrs" = "malaria_rdt_15"
                    ), 
                    selected = "All",
                    multiple = FALSE
               ),
               # selector para los centros
               selectInput(
                    inputId = "select_facility",
                    label = "Select Facility",
                    choices = c("All", facility_list$location_name),
                    selected = "All"
               ),
               
               # línea horizontal 
               hr(),
               downloadButton(
                    outputId = "download_epicurve",
                    label = "Download plot"
               )
               
          ),
          
          mainPanel(
               tabsetPanel(
                    type = "tabs",
                    tabPanel(
                         "Epidemic Curves",
                         plotOutput("malaria_epicurve")
                    ),
                    tabPanel(
                         "Data",
                         DT::dataTableOutput("raw_data")
                    )
               ),
               br(),
               hr(),
               p("Welcome to the malaria facility visualisation app! To use this app, manipulate the widgets on the side to change the epidemic curve according to your preferences! To download a high quality image of the plot you've created, you can also download it with the download button. To see the raw data, use the raw data tab for an interactive form of the table. The data dictionary is as follows:"),
               tags$ul(
                    tags$li(tags$b("location_name"), " - the facility that the data were collected at"),
                    tags$li(tags$b("data_date"), " - the date the data were collected at"),
                    tags$li(tags$b("submitted_daate"), " - the date the data were submitted at"),
                    tags$li(tags$b("Province"), " - the province the data were collected at (all 'North' for this dataset)"),
                    tags$li(tags$b("District"), " - the district the data were collected at"),
                    tags$li(tags$b("age_group"), " - the age group the data were collected for (0-5, 5-14, 15+, and all ages)"),
                    tags$li(tags$b("cases_reported"), " - the number of cases reported for the facility/age group on the given date")
               )
               
               
          )
     )
)

Ahora nuestra aplicación está organizada en pestañas! Hagamos también las modificaciones necesarias en el servidor. Dado que no necesitamos manipular nuestro conjunto de datos antes de procesarlo, esto es muy sencillo: ¡sólo tenemos que procesar los datos malaria_data a través de DT::renderDT() en la interfaz de usuario!

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)
    }
    
  )
  
  # render data table to ui
  output$raw_data <- DT::renderDT(
    malaria_data
  )
  
  
}

43.7 Compartir apps Shiny

Ahora que has desarrollado tu aplicación, probablemente quieras compartirla con los demás, ¡al fin y al cabo esta es la principal ventaja de shiny! Podemos hacerlo compartiendo el código directamente, o podemos publicarlo en un servidor. Si compartimos el código, otros podrán ver lo que has hecho y construir sobre él, pero esto anulará una de las principales ventajas de shiny: puede eliminar la necesidad de que los usuarios finales mantengan una instalación de R. Por esta razón, si estás compartiendo tu aplicación con usuarios que no se sienten cómodos con R, es mucho más fácil compartir una aplicación que ha sido publicada en un servidor.

Si prefieres compartir el código, puedes hacer un archivo .zip de la aplicación, o mejor aún, publicar tu aplicación en github y añadir colaboradores. Puedes consultar la sección de github para más información aquí.

Sin embargo, si vamos a publicar la aplicación en línea, tenemos que hacer un poco más de trabajo. En última instancia, queremos que se pueda acceder a tu aplicación a través de una URL web para que otros puedan acceder a ella de forma rápida y sencilla. Desafortunadamente, para publicar tu aplicación en un servidor, necesitas tener acceso a un servidor donde publicarla. Hay varias opciones de alojamiento en este sentido:

  • shinyapps.io: es el lugar más sencillo para publicar aplicaciones shiny, ya que es el que menos trabajo de configuración necesita, y tiene algunas licencias gratuitas, pero limitadas.

  • RStudio Connect: es una versión mucho más potente de un servidor de R, que puede realizar muchas operaciones, incluida la publicación de aplicaciones Shinys. Sin embargo, es más difícil de usar y menos recomendable para los usuarios noveles.

Para los propósitos de este documento, utilizaremos shinyapps.io, ya que es más fácil para los usuarios noveles. Puedes hacer una cuenta gratuita aquí para empezar - también hay diferentes planes de precios para las licecias de los servidores si es necesario. Cuantos más usuarios esperes tener, más caro tendrá que ser tu plan de precios, así que tenlo en cuenta. Si quieres crear algo para un pequeño grupo de personas, una licencia gratuita puede ser perfectamente adecuada, pero una aplicación de cara al público puede necesitar más licencias.

Primero debemos asegurarnos de que nuestra aplicación es adecuada para publicar en un servidor. En tu aplicación, debes reiniciar tu sesión de R, y asegurarte de que se ejecuta sin ejecutar ningún código extra. Esto es importante, ya que una aplicación que requiere la carga de paquetes, o la lectura de datos no definidos en el código de tu aplicación no se ejecutará en un servidor. También ten en cuenta que no puedes tener rutas de archivo explícitas en tu aplicación - éstas serán inválidas en la configuración del servidor - el uso del paquete here resuelve muy bien este problema. Por último, si estás leyendo datos de una fuente que requiere autenticación de usuario, como los servidores de tu organización, esto no funcionará generalmente en un servidor. Tendrás que ponerte en contacto con tu departamento de TI para averiguar cómo poner en la lista blanca el Shiny servidor.

registro de la cuenta

Una vez que tengas tu cuenta, puedes navegar a la página de tokens en Accounts. Aquí querrás añadir un nuevo token, que se utilizará para desplegar tu aplicación.

A partir de aquí, debes tener en cuenta que la url de tu cuenta reflejará el nombre de tu app - así que si tu app se llama mi_app, la url se añadirá como xxx.io/mi_app/. Elige bien el nombre de tu aplicación. Ahora que está todo listo, clica en desplegar - si tiene éxito esto ejecutará tu aplicación en la url web elegida.

¿algo sobre la creación de aplicaciones en documentos?

43.8 Más información

Hasta ahora, hemos cubierto muchos aspectos de shiny, y apenas hemos arañado la superficie de lo que ofrece shiny. Aunque esta guía sirve de introducción, hay mucho más que aprender para entender completamente shiny. Deberías empezar a crear aplicaciones y añadir gradualmente más y más funcionalidad