Inputs y outputs básicos

r
shiny
El binding implícito por inputId/outputId. Inputs frecuentes (slider, select, numeric, date, text, checkbox, file), outputs frecuentes (plot, table, text, verbatim), y la pareja render()/Output().

El binding implícito por inputId/outputId

Shiny conecta UI y server por el id que asignas a cada componente. No hay configuración explícita, basta con que el id coincida.

ui <- fluidPage(
  sliderInput(inputId = "n", label = "Muestras:",   min = 10, max = 500, value = 100),
  plotOutput(outputId = "grafico")
)

server <- function(input, output, session) {
  output$grafico <- renderPlot({
    hist(rnorm(input$n))
  })
}

Tres conexiones implícitas:

  1. inputId = "n" crea un input al que el server accede con input$n.
  2. outputId = "grafico" crea un output al que el server escribe en output$grafico.
  3. Cuando input$n cambia, Shiny recalcula automáticamente todo lo que depende de él, incluido output$grafico.

El parámetro se llama inputId (no id) para inputs, y outputId para outputs. En la práctica casi siempre se pasa posicionalmente sin nombrarlo, porque es el primer argumento:

sliderInput("n", "Muestras:", min = 10, max = 500, value = 100)
plotOutput("grafico")

Es la convención dominante en código Shiny.

Reglas de los inputId

  • Únicos dentro de la app. Dos inputs con el mismo id colisionan silenciosamente, el segundo no se registra.
  • Sin espacios ni caracteres especiales. Usa nombres válidos en R: letras, numeros_con_guion_bajo, no "mi input".
  • Descriptivos. n_muestras es mejor que n cuando crezca la app.
  • Camelo snake consistentes. Yo prefiero snake_case (alineado con R idiomático), pero camelCase también es común. Elige uno y mantenlo.

Inputs frecuentes

Input Cuándo Argumentos clave
sliderInput() Numérico continuo en rango min, max, value, step
numericInput() Numérico libre value, min, max, step
selectInput() Elegir entre opciones discretas choices, selected, multiple
radioButtons() Opciones excluyentes con botones choices, selected
checkboxInput() Booleano único value
checkboxGroupInput() Múltiples checkboxes choices, selected
textInput() Texto libre corto value, placeholder
textAreaInput() Texto libre largo value, rows, cols
dateInput() Fecha única value, min, max, format
dateRangeInput() Rango de fechas start, end, min, max
fileInput() Subida de archivo accept, multiple
actionButton() Botón que dispara una acción label, icon

Ejemplos:

# Slider numérico
sliderInput("edad", "Edad:", min = 18, max = 100, value = 35)

# Selector con múltiples opciones
selectInput("region", "Región:",
            choices = c("Norte", "Sur", "Este", "Oeste"),
            selected = "Norte",
            multiple = TRUE)

# Subir un CSV
fileInput("csv", "Sube un CSV:", accept = ".csv")

# Botón que dispara una acción
actionButton("recalcular", "Recalcular", icon = icon("refresh"))

actionButton: el caso especial

actionButton no almacena un “valor” en el sentido habitual. Lo que cambia es un contador de clicks:

input$boton   # 0 al inicio, 1 tras el primer click, 2 tras el segundo, etc.

Esto es porque el patrón típico es “haz algo cuando se pulse”, no “lee el valor del botón”. Se usa con observeEvent() o eventReactive(), lo veremos en el tutorial de reactividad.

Outputs frecuentes

Output Renderer Para
plotOutput("id") renderPlot({ ... }) Gráficos base R o ggplot2
tableOutput("id") renderTable({ ... }) Tablas estáticas (poco interactiva)
dataTableOutput("id") renderDataTable({ ... }) Tabla interactiva (DT internamente)
textOutput("id") renderText({ ... }) Texto inline (no preserva formato)
verbatimTextOutput("id") renderPrint({ ... }) Texto con formato (como en la consola R)
uiOutput("id") renderUI({ ... }) UI dinámica (HTML generado en server)
imageOutput("id") renderImage({ ... }) Imágenes (más raro que plot)

La regla: el *Output() en ui se empareja con su render*() en server.

ui <- fluidPage(
  plotOutput("grafico"),
  tableOutput("tabla"),
  textOutput("mensaje")
)

server <- function(input, output, session) {
  output$grafico <- renderPlot({ hist(rnorm(100)) })
  output$tabla   <- renderTable({ head(mtcars) })
  output$mensaje <- renderText({ paste("Hora:", Sys.time()) })
}

Confundir las parejas es un error común, renderPlot() con tableOutput() no funciona y el error es críptico.

renderText vs renderPrint

Sutil pero importante:

# renderText: convierte el resultado a string con as.character()
output$msj1 <- renderText({
  paste("Media:", mean(input$x))
})
# Muestra: "Media: 5.42"

# renderPrint: usa print() como en la consola
output$msj2 <- renderPrint({
  summary(input$x)
})
# Muestra el output multi-línea de summary() con formato

renderText() para mensajes cortos inline. renderPrint() cuando quieres preservar el output como saldría en la consola, útil para mostrar resúmenes de modelos, str(), etc. Va emparejado con verbatimTextOutput().

renderUI: UI dinámica desde el server

A veces necesitas que la UI cambie en función de inputs:

ui <- fluidPage(
  selectInput("tipo", "Tipo:", c("numérico", "categórico")),
  uiOutput("control_dinamico")
)

server <- function(input, output, session) {
  output$control_dinamico <- renderUI({
    if (input$tipo == "numérico") {
      sliderInput("valor", "Valor:", min = 0, max = 100, value = 50)
    } else {
      selectInput("valor", "Categoría:", c("A", "B", "C"))
    }
  })
}

renderUI() genera HTML desde el server y lo inyecta donde está el uiOutput(). Útil para formularios condicionales, “si seleccionas X, aparece el campo Y”.

Truco: el input dinámico (input$valor en el ejemplo) está disponible en server, pero solo después de que la UI lo haya creado. Si el server intenta leer input$valor antes de que exista, devuelve NULL. Hay que manejarlo con req() o validate() (tutorial 8).

Patrón completo de ejemplo

Una app con tres inputs y dos outputs:

library(shiny)

ui <- fluidPage(
  titlePanel("Distribución aleatoria"),

  sidebarLayout(
    sidebarPanel(
      sliderInput("n", "Tamaño de muestra:",
                  min = 10, max = 1000, value = 100, step = 10),
      selectInput("dist", "Distribución:",
                  choices = c("Normal" = "norm",
                              "Uniforme" = "unif",
                              "Exponencial" = "exp")),
      numericInput("seed", "Semilla (para reproducibilidad):", value = 42)
    ),

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

server <- function(input, output, session) {
  output$grafico <- renderPlot({
    set.seed(input$seed)
    x <- switch(input$dist,
                norm = rnorm(input$n),
                unif = runif(input$n),
                exp  = rexp(input$n))
    hist(x, main = paste("Distribución:", input$dist), col = "steelblue")
  })

  output$resumen <- renderPrint({
    set.seed(input$seed)
    x <- switch(input$dist,
                norm = rnorm(input$n),
                unif = runif(input$n),
                exp  = rexp(input$n))
    summary(x)
  })
}

shinyApp(ui, server)

Tres inputs (slider, select, numeric) + dos outputs (plot, verbatim). Al cambiar cualquier input, ambos outputs se recalculan.

Problema: estamos calculando x dos veces, una para el plot y otra para el resumen. En el siguiente tutorial veremos reactive(), que resuelve esto compartiendo el cálculo.

Trampas habituales

  • IDs duplicados. Dos componentes con el mismo inputId colisionan. Shiny no avisa explícitamente. El comportamiento es impredecible. Si tu app “casi funciona pero no”, busca duplicados primero.
  • Olvidar emparejar *Output() con render*(). Si output$x es un plot pero el ui declara tableOutput("x"), falla con error de tipos. Lee qué emparejas.
  • renderText({ ... }) con resultado multi-línea. Convierte el resultado a un string aplanado. Si quieres formato consola, usa renderPrint() + verbatimTextOutput().
  • Acceder a input$x en el body de server (fuera de un reactive/render). Solo se puede leer input$x dentro de un contexto reactivo. Fuera, da error sobre “reactive context”. Lo veremos a fondo en reactividad.

En la siguiente entrega

Has aprendido inputs y outputs. La siguiente pieza es el corazón conceptual de Shiny: el sistema reactivo. reactive(), observe(), observeEvent(), eventReactive() y cuándo cada uno. Es lo que separa una app que funciona de una app que escala. Lo siguiente.