Inputs y outputs básicos
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:
inputId = "n"crea un input al que el server accede coninput$n.outputId = "grafico"crea un output al que el server escribe enoutput$grafico.- Cuando
input$ncambia, Shiny recalcula automáticamente todo lo que depende de él, incluidooutput$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_muestrases mejor quencuando crezca la app. - Camelo snake consistentes. Yo prefiero
snake_case(alineado con R idiomático), perocamelCasetambié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"))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 formatorenderText() 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
inputIdcolisionan. Shiny no avisa explícitamente. El comportamiento es impredecible. Si tu app “casi funciona pero no”, busca duplicados primero. - Olvidar emparejar
*Output()conrender*(). Sioutput$xes un plot pero el ui declaratableOutput("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, usarenderPrint()+verbatimTextOutput().- Acceder a
input$xen el body de server (fuera de unreactive/render). Solo se puede leerinput$xdentro 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.