Shiny en R: crear aplicaciones web interactivas de datos sin saber JavaScript

Shiny es el paquete de R que permite crear aplicaciones web interactivas escribiendo solo R, sin tocar JavaScript. Un analista que sabe hacer gráficos y modelos en R puede convertir ese análisis en una app con controles deslizantes, selectores, tablas actualizables y gráficos reactivos sin aprender frontend. Eso es lo que lo hace tan popular en entornos científicos y empresariales donde el equipo de datos no tiene perfil web.

Shiny 1.8, publicado en 2024, introdujo mejoras en la gestión de sesiones y en el sistema de módulos. La estructura básica de una aplicación Shiny no ha cambiado desde los inicios: hay una parte de interfaz (ui) y una parte de lógica (server).

Estructura básica de una app Shiny

library(shiny)

# UI: define lo que ve el usuario
ui <- fluidPage(
  titlePanel("Explorador de datos de iris"),

  sidebarLayout(
    sidebarPanel(
      selectInput(
        inputId  = "especie",
        label    = "Selecciona especie:",
        choices  = c("Todas", unique(iris$Species)),
        selected = "Todas"
      ),
      selectInput(
        inputId  = "variable_x",
        label    = "Eje X:",
        choices  = names(iris)[1:4],
        selected = "Sepal.Length"
      ),
      selectInput(
        inputId  = "variable_y",
        label    = "Eje Y:",
        choices  = names(iris)[1:4],
        selected = "Petal.Length"
      ),
      sliderInput(
        inputId = "tamanyo_punto",
        label   = "Tamaño de punto:",
        min = 1, max = 5, value = 2, step = 0.5
      )
    ),

    mainPanel(
      plotOutput("grafico"),
      tableOutput("resumen")
    )
  )
)

# Server: define la lógica reactiva
server <- function(input, output, session) {

  # Datos filtrados reactivos
  datos_filtrados <- reactive({
    if (input$especie == "Todas") {
      iris
    } else {
      iris[iris$Species == input$especie, ]
    }
  })

  # Gráfico reactivo
  output$grafico <- renderPlot({
    df <- datos_filtrados()
    plot(
      df[[input$variable_x]],
      df[[input$variable_y]],
      col   = as.integer(df$Species) + 1,
      pch   = 16,
      cex   = input$tamanyo_punto,
      xlab  = input$variable_x,
      ylab  = input$variable_y,
      main  = paste("Iris:", input$variable_x, "vs", input$variable_y)
    )
    legend("topright", legend = levels(df$Species),
           col = 2:4, pch = 16)
  })

  # Tabla reactiva
  output$resumen <- renderTable({
    df <- datos_filtrados()
    aggregate(. ~ Species, data = df[, 1:5], FUN = mean)
  })
}

# Lanzar la app
shinyApp(ui = ui, server = server)

El modelo reactivo de Shiny

Lo que hace especial a Shiny es su sistema de reactividad. Cuando el usuario mueve un control, Shiny sabe qué cálculos dependen de ese control y los recalcula solo a ellos. No hay que gestionar callbacks manualmente ni conectar eventos: el grafo de dependencias se construye automáticamente.

server <- function(input, output, session) {

  # reactive(): un valor que se recalcula cuando cambian sus dependencias
  datos <- reactive({
    df <- read.csv(input$archivo$datapath)
    df[df$valor > input$umbral, ]
  })

  # reactiveVal(): una variable mutable que dispara reactividad al cambiar
  contador <- reactiveVal(0)

  observeEvent(input$boton_click, {
    contador(contador() + 1)
  })

  # observe(): corre un bloque sin producir output cuando cambian sus inputs
  observe({
    cat("El umbral cambió a:", input$umbral, "n")
  })

  # eventReactive(): como reactive() pero solo se dispara con un evento concreto
  resultado_modelo <- eventReactive(input$boton_ajustar, {
    lm(y ~ x1 + x2, data = datos())
  })

  output$coeficientes <- renderPrint({
    summary(resultado_modelo())
  })
}

Módulos: reutilización en apps grandes

Cuando una app crece, los módulos permiten encapsular partes de la UI y el server como unidades reutilizables. Un módulo es simplemente un par de funciones con un namespace aislado:

# Definir un módulo de gráfico
grafico_UI <- function(id) {
  ns <- NS(id)
  tagList(
    selectInput(ns("variable"), "Variable:", choices = names(iris)[1:4]),
    plotOutput(ns("hist"))
  )
}

grafico_server <- function(id, datos) {
  moduleServer(id, function(input, output, session) {
    output$hist <- renderPlot({
      hist(datos()[[input$variable]], main = input$variable, col = "steelblue")
    })
  })
}

# Usar el módulo en la app principal
ui <- fluidPage(
  grafico_UI("modulo1"),
  grafico_UI("modulo2")
)

server <- function(input, output, session) {
  datos_reactivos <- reactive(iris)
  grafico_server("modulo1", datos_reactivos)
  grafico_server("modulo2", datos_reactivos)
}

Despliegue: shinyapps.io y Shiny Server

Posit ofrece shinyapps.io para desplegar apps en la nube con un solo comando desde RStudio:

library(rsconnect)
deployApp()

Para servidores propios existe Shiny Server (versión open source gratuita) y Posit Connect (versión enterprise). La versión open source permite alojar cualquier número de apps sin límites de tiempo de sesión, aunque sin las características de autenticación y escalado de la versión de pago.

Una alternativa más ligera que Shiny para dashboards estáticos o semestáticos es Quarto con Observable JS. Para análisis de datos complejos que alimentan la app, vale la pena ver el artículo sobre tidyverse con dplyr y ggplot2. Si te preguntas cómo combinar R con Python en el mismo flujo de trabajo, el artículo sobre reticulate para usar Python desde R cubre esa integración.

Imagen: Pexels / weCare Media

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP