# Instalación de shiny
install.packages("shiny")
# Instalación de rsconnect
install.packages("rsconnect")
16 shiny - desarrollo de aplicaciones web interactivas
16.1 Resumen
Shiny es un paquete de R que facilita el desarrollo de aplicaciones web interactivas. Las aplicaciones shiny se componen de una interfaz de usuario y de un servidor. En la interfaz de usuario, este puede realizar operaciones como filtros, búsquedas y ordenamientos de datos, entre otras. El servidor se encarga de procesar los datos de acuerdo con los parámetros especificados y de retornar los resultados a la interfaz de usuario.
Además de los bloques de código en R para la interfaz de usuario y el servidor, shiny proporciona bloques para compartir código y datos. También para personalizar la interfaz de usuario.
Las funciones reactivas de shiny se ejecutan cada vez que el usuario cambia los controles de la interfaz, lo que permite a las aplicaciones shiny responder dinámicamente a las entradas del usuario.
16.2 Trabajo previo
16.2.1 Lecturas
Quarto - Dashboards with Shiny for R. (s.f.). Quarto. Recuperado el 1 de marzo de 2024, de https://quarto.org/docs/dashboards/interactivity/shiny-r.html
16.2.2 Instalación y carga de paquetes
# Carga de paquetes
library(shiny)
library(rsconnect)
16.3 Introducción
En una aplicación interactiva, el usuario puede configurar las salidas, usualmente mediante una interfaz que le permite realizar operaciones como filtros, búsquedas y ordenamientos, entre otras. Shiny es un paquete de R que facilita el desarrollo de este tipo de aplicaciones.
Las aplicaciones shiny requieren un servidor, el cual es un proceso que puede alojarse en cualquier computador habilitado para ejecutar código en R (o Python) como, por ejemplo, la estación de trabajo del programador (esta opción se usa principalmente para efectos de desarrollo y pruebas), un servidor ubicado en la red de una organización o un servidor en la nube (ej. shinyapps.io, Posit Connect, Posit Cloud).
Para ejemplos de aplicaciones shiny, puede visitar la siguiente galería.
16.4 Arquitectura
Una aplicación shiny tiene dos componentes principales:
- Interfaz de usuario: despliega controles de entrada y salida (widgets), los cuales eventualente convierte a Lenguaje de Marcado de Hipertexto (HTML).
- Widgets de entrada: campos de texto, listas de selección, botones de radio, etc.
- Widgets de salida: tablas, gráficos, mapas, etc. Muchos de estos controles están incluídos en los paquetes que generan los diferentes tipos de salidas (ej. DT, plotly, leaflet).
- Servidor: es un proceso que recibe las entradas y realiza el procesamiento necesario para generar las salidas y retornar los resultados a la interfaz de usuario.
Para más información sobre la arquitectura de aplicaciones shiny, se recomienda leer The Anatomy of a Shiny Application.
16.5 Ejemplo de aplicación básica
El siguiente documento Quarto contiene una aplicación shiny con un tablero de control interactivo para el conjunto de datos diamonds. Esta aplicación se incluye como ejemplo en:
https://quarto.org/docs/dashboards/interactivity/shiny-r.html.
Puede acceder a la aplicación ejecutable en:
https://mfvargas.shinyapps.io/2024-i-tablero-interactivo-diamantes/.
---
title: "Diamonds Explorer"
author: "Barkamian Analytics"
format: dashboard
server: shiny
---
```{r}
#| context: setup
library(ggplot2)
dataset <- diamonds
```
# {.sidebar}
```{r}
sliderInput('sampleSize', 'Sample Size',
min=1, max=nrow(dataset),
value=min(1000, nrow(dataset)),
step=500, round=0)
br()
checkboxInput('jitter', 'Jitter')
checkboxInput('smooth', 'Smooth')
```
```{r}
selectInput('x', 'X', names(dataset))
selectInput('y', 'Y', names(dataset), names(dataset)[[2]])
selectInput('color', 'Color', c('None', names(dataset)))
```
```{r}
selectInput('facet_row', 'Facet Row',
c(None='.', names(diamonds[sapply(diamonds, is.factor)])))
selectInput('facet_col', 'Facet Column',
c(None='.', names(diamonds[sapply(diamonds, is.factor)])))
```
# Plot
```{r}
plotOutput('plot')
```
# Data
```{r}
tableOutput('data')
```
```{r}
#| context: server
dataset <- reactive({
diamonds[sample(nrow(diamonds), input$sampleSize),]
})
output$plot <- renderPlot({
p <- ggplot(
dataset(),
aes_string(x=input$x, y=input$y)) + geom_point()
if (input$color != 'None')
p <- p + aes_string(color=input$color)
facets <- paste(input$facet_row, '~', input$facet_col)
if (facets != '. ~ .')
p <- p + facet_grid(facets)
if (input$jitter)
p <- p + geom_jitter()
if (input$smooth)
p <- p + geom_smooth()
p
})
output$data <- renderTable({
dataset()
})
```
Es de vital importancia comprender que el último bloque de código (con la opción #| context: server
) se ejecuta en una sesión separada de R. Esto implica que no es posible, en principio, acceder desde ese bloque variables definidas en otros bloques, ni viceversa. Sin embargo, existen varias estrategias para compartir código, como las que se detallan en Sharing Code y también en las secciones siguientes de este documento.
Una forma en la que la interfaz de usuario y el servidor pueden comunicarse, es a través de las listas input
y output
.
input
contiene la lista de widgets de entrada (listas de selección, campos de entrada de texto, botones de radio, etc.). Cada uno de estos widgets tiene uninputId
único. En el ejemplo, “sampleSize” es el inputId del widget tipo sliderInput. Se referencia comoinput$sampleSize
en el bloque del servidor.output
es una lista de componentes que se crean o modifican en el servidor (tablas, gráficos, mapas, etc). y que luego se envían a la interfaz de usuario para su visualización. Para crear o modificar un elemento deoutput
, se utiliza una funciónrender*
, y para mostrarlo en la interfaz de usuario, se utiliza una función*Output
. En el ejemplo,output$plot
es un gráfico que se crea con la funciónrenderPlot()
en función de los valores de los widgets de entrada.
Nótese que hay diferencias importantes entre este documento y otros documentos Quarto:
La opción
server: shiny
: en la sección YAML, la cual le indica a Quarto que debe iniciar un servidor Shiny.La opción
context: server
: en el último bloque de código, la cual indica que ese bloque debe ejecutarse en el servidor.
Existen otros posibles valores de context
para los bloques de código en aplicaciones shiny, los cuales permiten compartir código y datos.
16.6 Bloques para compartir código y datos
Como puede apreciarse en el ejemplo anterior, las aplicaciones shiny pueden contener bloques de código que se ejecutan en tiempo de despliegue (rendering) de la interfaz de usuario y también bloques que se ejecutan en el servidor en respuesta a las acciones del usuario y a los cambios en los valores de entrada. Estos tipos de bloques se identifican respectivamente mediante las opciones context: render
(valor por defecto de context
) y context: server
.
Existen también otros tipos de bloques, los cuales permiten compartir código y datos.
16.6.1 context: setup
El código de este tipo de bloques se ejecuta tanto en el contexto de la interfaz de usuario como del servidor. Puede usarse para operaciones que se ejecutan al inicio de una aplicación, como la carga de paquetes.
```{r}
#| label: inicio
#| context: setup
#| message: false
# Carga de paquetes
library(tidyverse)
library(DT)
library(sf)
```
16.6.2 context: data
Se utiliza para cargar datos que deben compartirse entre todos los bloques de código. Por ejemplo:
```{r}
#| label: lectura-datos
#| context: data
# Lectura de archivo CSV
felidos <-
st_read(
dsn = "felidos.csv",
options = c(
"X_POSSIBLE_NAMES=decimalLongitude",
"Y_POSSIBLE_NAMES=decimalLatitude"
),
quiet = TRUE
)
```
16.7 Configuración de la interfaz de usuario
16.7.1 Barras laterales
Las barras laterales o sidebars se utilizan generalmente para colocar widgets de entrada. Para incluir una barra lateral, se agrega la clase .sidebar
a un encabezado de nivel 2. Por ejemplo:
# {.sidebar}
```{r}
sliderInput('sampleSize', 'Sample Size',
min=1, max=nrow(dataset),
value=min(1000, nrow(dataset)),
step=500, round=0)
br()
checkboxInput('jitter', 'Jitter')
checkboxInput('smooth', 'Smooth')
```
```{r}
selectInput('x', 'X', names(dataset))
selectInput('y', 'Y', names(dataset), names(dataset)[[2]])
selectInput('color', 'Color', c('None', names(dataset)))
```
```{r}
selectInput('facet_row', 'Facet Row',
c(None='.', names(diamonds[sapply(diamonds, is.factor)])))
selectInput('facet_col', 'Facet Column',
c(None='.', names(diamonds[sapply(diamonds, is.factor)])))
```
Puede consultar sobre más recursos para configurar el tablero en https://quarto.org/docs/dashboards/inputs.html.
16.8 Funciones reactivas
Son funciones especiales que se ejecutan cada vez que cambia uno de los datos que utiliza como entrada, como un widget de la interfaz de usuario. En el siguiente bloque de código se define una función reactiva que utiliza el valor especificado por el usuario en una lista de selección para filtrar un conjunto de datos.
```{r}
# Función reactiva para filtrar los registros de presencia de félidos
# de acuerdo con el valor de una lista de selección
filtrar_felidos <- reactive({
# Valor inicial del objeto que va a retornarse
felidos_filtrados <- felidos
if (input$especie != "Todas") {
felidos_filtrados <-
felidos_filtrados |>
filter(species == input$especie)
}
return(felidos_filtrados)
})
```
Las funciones reactivas permiten a las aplicaciones shiny responder dinámicamente a las entradas del usuario.
16.9 Ejemplo: Desarrollo de una aplicación interactiva para el Atlas de Desarrollo Humano Cantonal 2024
A manera de ejemplo, se presenta el desarrollo, paso por paso, de una aplicación interactiva para el Atlas de Desarrollo Humano Cantonal 2024.
En el capítulo anterior de este curso, se desarrolló un tablero de control sobre el mismo conjunto de datos. La aplicación interactiva le permite al usuario visualizar y explorar mayores cantidades de datos que en el tablero de control y también especificar filtros.
Esta aplicación interactiva cuenta con controles (widgets) para filtrar los datos por:
- Provincia.
- Año.
Los datos se presentan en tres salidas:
- Una tabla DT.
- Un gráfico plotly.
- Un mapa tmap.
Para fines didácticos, el desarrollo se dividió en las siguientes secciones:
- Creación del proyecto y del archivo principal de la aplicación.
- Creación de los bloques iniciales de código.
- Creación de la barra lateral con widgets para filtros.
- Creación de los controles de salida.
- Creación de una función reactiva para filtrar los datos.
- Creación de funciones para generar las salidas.
Seguidamente, se enumeran los pasos detallados de cada una de estas secciones.
16.9.1 Creación del proyecto y del archivo principal de la aplicación
1. Cree un proyecto en RStudio con un nombre apropiado de su elección (ej. aplicacion-interactiva-atlas-desarrollo-humano
).
2. Descargue y copie en el directorio de su proyecto los siguientes archivos de datos:
cantones-simplificados.gpkg
(polígonos de cantones de Costa Rica con geometrías simplificadas en formato GeoPackage).indice_de_desarrollo_humano.xlsx
(datos del índice de desarrollo humano, por cantón y por año, en formato Microsoft Excel).
3. En el nuevo proyecto, cree un documento Quarto con un nombre de su elección (ej. aplicacion-interactiva-atlas-desarrollo-humano.qmd
). Puede borrar el contenido que RStudio genera automáticamente en el documento.
16.9.2 Creación de los bloques iniciales de código
4. Agregue un encabezado en el nuevo documento. Asegúrese de incluir las opciones format: dashboard
y server: shiny
. Por ejemplo:
---
title: "Atlas de Desarrollo Humano Cantonal"
format: dashboard
server: shiny
---
5. Agregue un bloque de código en R para cargar las bibliotecas. Asegúrese de incluir la opción context: setup
.
```{r}
#| label: carga-bibliotecas
#| context: setup
#| warning: false
#| message: false
# Carga de bibliotecas
library(tidyverse)
library(readxl)
library(DT)
library(plotly)
library(sf)
library(terra)
library(tmap)
library(shiny)
# Especificar modo interactivo para tmap
tmap_mode("view")
```
Todas las bibliotecas deben haber sido instaladas previamente con la función install.packages()
. En este bloque se incluye también el llamado a la función tmap_mode("view")
para especificar el modo interactivo de tmap.
6. Agregue un bloque de código en R para cargar los datos y realizar cualquier transformación necesaria (filtrados, ordenamientos, uniones, etc.). Asegúrese de incluir la opción context: data
.
```{r}
#| label: carga-datos
#| context: data
# CANTONES
# Cargar datos de cantones
cantones <- st_read("cantones-simplificados.gpkg", quiet = TRUE)
# ÍNDICE DE DESARROLLO HUMANO (IDH)
# Cargar datos del IDH
idh <- read_excel("indice_de_desarrollo_humano.xlsx", sheet = "IDH")
# Crear la columna CÓDIGO_CANTÓN (el mismo nombre que tiene en cantones)
# y renombrar "Cantón" como "CANTÓN", para que quede en mayúscula como las otras
idh <-
idh |>
mutate(
`CÓDIGO_CANTÓN` = substr(Cantón, 1, 3) |> # toma los 3 primeros caracteres
as.integer() # los convierte a entero
)
# UNIÓN DE CANTONES E ÍNDICES
# Crear unión de cantones e IDH
datos_anchos <-
cantones |>
left_join(
idh,
by = "CÓDIGO_CANTÓN" # llave para realizar la unión
)
# "Alargar" el dataframe al poner todas las columnas de años en una sola
datos <- datos_anchos |>
pivot_longer(
cols = `2010`:`2022`, # columnas a “alargar”
names_to = "ANIO", # nueva columna con los nombres (años)
values_to = "IDH" # nueva columna con los valores (IDH)
) |>
mutate(ANIO = as.integer(ANIO)) # opcional: convertir a número
```
16.9.3 Creación de la barra lateral con widgets para filtros
7. Agregue un encabezado Markdown para crear una barra lateral para los widgets de filtros de datos.
# {.sidebar}
8. Agregue un bloque de código en R para agregar una lista de selección (selectInput()
) de provincia.
```{r}
#| label: selectinput-provincia
# Lista ordenada de provincias con un elemento adicional = "Todas"
lista_provincias <- unique(datos$PROVINCIA)
lista_provincias <- sort(lista_provincias)
lista_provincias <- c("Todas", lista_provincias)
# Widget de lista de selección ("selectInput") de provincias
selectInput(
inputId = "provincia", # identificador del widget
label = "Provincia", # etiqueta de la lista (la que ve el usuario)
choices = lista_provincias, # lista de opciones para seleccionar de la lista
selected = "Todas" # opción seleccionada por defecto
)
```
Note el argumento inputId = "provincia"
de la función selectInput()
. Este es el identificador de este widget y luego se utilizará en las funciones de filtrado.
9. Agregue un bloque de código en R para agregar un widget deslizador de selección (sliderInput()
) de año.
```{r}
#| label: slider-anio
# Año mínimo y año máximo
anio_minimo <- min(datos$ANIO)
anio_maximo <- max(datos$ANIO)
# Widget deslizador de selección ("sliderInput") de año
sliderInput(
inputId = "anio",
label = "Año",
min = anio_minimo, # año mínimo permitido
max = anio_maximo, # año máximo permitido
value = round((anio_minimo + anio_maximo) / 2), # año inicial seleccionado
step = 1, # solo incrementos de un año (evita decimales)
round = TRUE, # redondea al entero más cercano
sep = "" # sin separador de miles
)
```
Note el argumento inputId = "anio"
de la función sliderInput()
. Este es el identificador de este widget y luego se utilizará en las funciones de filtrado.
16.9.4 Creación de los controles de salida
10. Agregue un encabezado Markdown para crear una página y un bloque de código en R para crear el control de salida de una tabla DT.
# Tabla
```{r}
#| label: salida-tabla
#| title: "Tabla"
# Tabla
dataTableOutput(
outputId = "tabla" # identificador del widget
)
```
Note el argumento outputId = "tabla"
de la función dataTableOutput()
. Este es el identificador de este control.
11. Agregue un encabezado Markdown para crear una página y un bloque de código en R para crear el control de salida de un gráfico plotly.
# Gráfico
```{r}
#| label: salida-grafico
#| title: "Gráfico"
# Gráfico
plotlyOutput(
outputId = "grafico" # identificador del widget
)
```
Note el argumento outputId = "grafico"
de la función plotlyOutput()
. Este es el identificador de este control.
12. Agregue un encabezado Markdown para crear una página y un bloque de código en R para crear el control de salida de un mapa tmap.
# Mapa
```{r}
#| label: salida-mapa
#| title: "Mapa"
# Mapa
tmapOutput(
outputId = "mapa" # identificador del widget
)
```
Note el argumento outputId = "mapa"
de la función tmapOutput()
. Este es el identificador de este control.
16.9.5 Creación de una función reactiva para filtrar los datos
13. Agregue un bloque de código en R para definir una función reactiva que filtre los datos. Asegúrese de incluir la opción context: server
.
```{r}
#| label: servidor
#| context: server
# Función reactiva para filtrar los datos
# de acuerdo con los filtros especificados por el usuario
filtrar_datos <- reactive({
# Para comenzar, se toman los datos sin filtrar
datos_filtrados <- datos
# Filtro por provincia
if (input$provincia != "Todas") {
datos_filtrados <-
datos_filtrados |>
filter(PROVINCIA == input$provincia)
}
# Filtro por año
datos_filtrados <-
datos_filtrados |>
filter(ANIO == input$anio)
return(datos_filtrados)
})
```
Note el uso de los identificadores de los widgets de entrada: input$provincia
e input$anio
.
16.9.6 Creación de funciones para generar las salidas
14. En el mismo bloque de código de la función reactiva, defina una función para generar la tabla.
output$tabla <- renderDataTable({
# Filtrado del conjunto de datos
datos <- filtrar_datos()
# Definir la tabla
tabla <-
datos |>
st_drop_geometry() |>
dplyr::select(PROVINCIA, CANTÓN, ANIO, IDH) |>
mutate(IDH = round(IDH, 3)) |>
arrange(desc(IDH)) |>
datatable(
rownames = FALSE,
colnames = c("Provincia", "Cantón", "Año", "IDH"),
options = list(
pageLength = 8,
language = list(url = '//cdn.datatables.net/plug-ins/1.10.11/i18n/Spanish.json')
)
)
# Mostrar la tabla
tabla
})
**15. En el mismo bloque de código de la función reactiva, defina una función para generar el gráfico.**
```{r}
output$grafico <- renderPlotly({
# Filtrado del conjunto de datos
datos <- filtrar_datos()
# Definir el gráfico ggplot2
grafico_ggplot2 <-
datos |>
slice_head(n = 15) |>
ggplot(aes(x = reorder(CANTÓN, -IDH), y = IDH)) +
geom_col(
aes(
text = paste0(
"Cantón: ", CANTÓN, "\n",
"IDH: ", round(IDH, 3)
)
),
fill = "#1f77b4"
) +
xlab("Cantón") +
ylab("IDH") +
theme_classic() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) # etiquetas a 45°
# Mostrar el gráfico plotly
ggplotly(grafico_ggplot2, tooltip = "text") |>
config(locale = 'es')
})
```
Note como el valor que retorna la función renderPlotly()
es asignado a output$grafico
, el identificador del control de salida del gráfico.
16. En el mismo bloque de código de la función reactiva, defina una función para generar el mapa.
output$mapa <- renderTmap({
# Filtrado del conjunto de datos
datos <- filtrar_datos()
# Definir y configurar el mapa
mapa <-
tm_basemap(c("Esri.WorldGrayCanvas", "OpenStreetMap", "Esri.WorldImagery")) +
tm_shape(datos, name = "Cantones") +
tm_fill(
fill = "IDH",
fill.scale = tm_scale_intervals(
style = "quantile",
values = "Blues"
),
fill.legend = tm_legend(title = "IDH"),
id = "CANTÓN",
popup.vars = c(
"IDH" = "IDH"
)
) +
tm_borders(col = "black", lwd = 0.5) +
tm_scale_bar(position = c("left", "bottom"))
# Mostrar el mapa
mapa
})
````
Note como el valor que retorna la función renderTmap()
es asignado a output$mapa
, el identificador del control de salida del mapa.
16.10 Ejercicios
Ejecute en su computadora el código del tablero de control sobre diamantes mostrado anteriormente.
- Cree un proyecto en RStudio.
- Cree un documento Quarto.
- Copie en el nuevo documento el código de la aplicación.
- Ejecute el documento con el botón
Run Document
.
Publique el tablero de control en shinyapps.io (puede consultar Quarto - Running Documents y How to Deploy R Shiny App for Free on Shinyapps.io).
- Cree una cuenta en shinyapps.io.
- Obtenga su token de autenticación de shinyapps.io en Accounts - Tokens - Show- Show secret - Copy to clipboard.
- Ejecute la aplicación en su computadora y publíquela en shinyapps.io con el botón Publish. Elija la opción shinyapps.io e ingrese el token cuando se le solicite. Debe seleccionar todos los archivos requeridos para que su aplicación funciones (archivos .qmd, archivos .html, archivos de datos, etc).
Ejecute en su computadora y luego publique en shinyapps.io la aplicación Iris K-Means Clustering.
Estudie el resto de los ejemplos en Quarto - Shiny - Examples.
Analice el siguiente tablero de control sobre datos de mamíferos:
- Código fuente: https://github.com/gf0604-procesamientodatosgeograficos/2025-i-tablero-interactivo-mamiferos
- Aplicación ejecutable: https://mfvargas.shinyapps.io/tablero-interactivo-mamiferos/
- Agregue un gráfico y un mapa al tablero de control.
16.11 Recursos de interés
Quarto - Running Documents. (s. f.). Recuperado 20 de noviembre de 2022, de https://quarto.org/docs/interactive/shiny/running.html
Shiny. (s. f.). Recuperado 20 de noviembre de 2022, de https://shiny.rstudio.com/
Shiny - Gallery. (s. f.). Recuperado 20 de noviembre de 2022, de https://shiny.rstudio.com/gallery/
The Anatomy of a Shiny Application | R-bloggers. (2021). Recuperado 21 de noviembre de 2022, de https://www.r-bloggers.com/2021/04/the-anatomy-of-a-shiny-application/
1littlecoder. (2020). How to Deploy R Shiny App for Free on Shinyapps.io. https://www.youtube.com/watch?v=2QstfyGX4ZU