flowchart LR A[nombre] ===> B>Saludo] linkStyle 0 stroke-width:5px, fill: green, stroke:blue;
Objetivos:
En Shiny, la lógica de la función servidor se define usando programación reactiva. El principio fundamenteal de este tipo de programación consiste en especificar un grafo de dependencias, de manera que, cuando algún input cambie, todos sus outputs asociados se actualicen automáticamente.
server
)input
El argumento input contiene la información que manda el navegador web, sobre la data de los inputs en la aplicación, en función de su identificador.
input$IDENTIFICADOR
es el valor del input con identificador IDENTIFICADOR
.
input es como una lista de R, pero no podemos modificar su contenido, dentro de la función servidor.
Para leer a algún valor del argumento input, esta lectura debe realizarse dentro de un contexto reactivo, tales como renderText()
y reactive()
.
library(shiny)
<- fluidPage(
ui numericInput("n", label = "Cantidad de valores", value = 100)
)
<- function(input, output, session) {
server # Incorrecto
# input$n <- 10
# Incorrecto x2
message("The value of input$n is ", input$n)
}
shinyApp(ui, server)
output
Ejemplo:
<- fluidPage(
ui textOutput("saludo")
)
<- function(input, output, session) {
server # Incorrecto
$saludo <- "Hola, buenas."
output
# Incorrecto x2
message("El mensaje es ", output$saludo)
# Correcto
$saludo <- renderText("Hola, buenas.")
output }
¿Qué hace una función de tipo render?
Ejemplo de la actualización automática que nos proporciona la programación reactiva de Shiny:
<- fluidPage(
ui textInput("nombre", "¿Y tú cómo te llamas?"),
textOutput("saludo")
)
<- function(input, output, session) {
server $saludo <- renderText({
outputpaste0("¡Hola ", input$nombre, "!")
}) }
La magia de Shiny consiste en que no necesitas avisar a un output cuándo debe actualizarse, ya que Shiny lo resuelve por ti.
¿Pero cómo funciona el código en server
?
¿A qué nos referimos con recetas y comandos?
Ejemplo del libro:
Las aplicaciones Shiny son extremadamente perezosas, en el sentido que Shiny no ejecutará el código de las secciones output que no sean parte de la aplicación.
Ejemplo:
<- fluidPage(
ui textInput("nombre", "¿Y tú cómo te llamas?"),
textOutput("saludo")
)
<- function(input, output, session) {
server $saluda <- renderText({
outputpaste0("¡Hola ", input$nombre, "!")
}) }
La pereza de Shiny también se manifiesta en que el código en la función servidor no se ejecuta de arriba a abajo, como es la manera convencional en R, sino más bien cuando sea necesario.
Para entender cuándo es necesario que Shiny ejecute código, se trabaja con el gráfico reactivo, una visualización que describe cómo los inputs y outputs están relacionados.
flowchart LR A[nombre] ===> B>Saludo] linkStyle 0 stroke-width:5px, fill: green, stroke:blue;
A las expresiones reactivas las denotaremos con un símbolo especial en el gráfico reactivo. Estas sirven para reducir la repetición de código dentro de la función servidor.
<- fluidPage(
ui textInput("nombre", "¿Y tú cómo te llamas?"),
textOutput("saludo")
)
<- function(input, output, session) {
server <- reactive(paste0("¡Hola ", input$nombre, "!"))
cadena $saludo <- renderText(cadena())
output }
flowchart LR A[nombre] ===> B((cadena)) B((cadena)) ===> C>saludo] linkStyle 0 stroke-width:5px, fill: green, stroke:blue; linkStyle 1 stroke-width:5px, fill: green, stroke:blue;
El siguiente ejemplo parece no debería funcionar, pero la pereza de Shiny permite que la aplicación funcione, ya que el gráfico reactivo de esta app no ha cambiado, por lo que el orden en que se ejecuta el código es el mismo.
<- fluidPage(
ui textInput("nombre", "¿Y tú cómo te llamas?"),
textOutput("saludo")
)
<- function(input, output, session) {
server $saludo <- renderText(cadena())
output<- reactive(paste0("¡Hola ", input$nombre, "!"))
cadena }
Recordemos que las expresiones reactivas dependen de inputs y saben automáticamente cuando actualizarse. Asimismo, podemos usar el valor de expresiones reactivas dentro de un output.
Realizaremos una simulación donde emplearemos la función t.test()
para determinar si la media de dos grupos de datos son iguales.
Pero, supondremos que ambos grupos de datos han sido sorteados de distribuciones normales (gaussianas) con misma desviación estándar.
En caso que el p-valor hallado vía el t.test()
resulte menor que 0.05, entonces afirmaremos que las medias de las distribuciones son diferentes.
library(ggplot2)
<- function(x1, x2, binwidth = 0.1, xlim = c(-3, 3)) {
freqpoly <- data.frame(
df x = c(x1, x2),
g = c(rep("x1", length(x1)), rep("x2", length(x2)))
)
ggplot(df, aes(x, colour = g)) +
geom_freqpoly(binwidth = binwidth, size = 1) +
coord_cartesian(xlim = xlim)
}
<- function(x1, x2) {
t_test <- t.test(x1, x2)
test
sprintf(
"p valor: %0.3f\n[%0.2f, %0.2f]",
$p.value, test$conf.int[1], test$conf.int[2]
test
) }
<- rnorm(100, mean = 0, sd = 1)
x1 <- rnorm(200, mean = 0.1, sd = 1)
x2
freqpoly(x1, x2)
cat(t_test(x1, x2))
En vez de ejecutar las simulaciones cambiando parámetros en el código, y ejecutar de nuevo el código relevante, podemos acelerar este proceso por medio de una aplicación Shiny.
<- fluidPage(
ui fluidRow(
column(4,
"Distribución 1",
numericInput("n1", label = "n", value = 1000, min = 1),
numericInput("mean1", label = "µ", value = 0, step = 0.1)
),column(4,
"Distribución 2",
numericInput("n2", label = "n", value = 1000, min = 1),
numericInput("mean2", label = "µ", value = 0, step = 0.1)
),column(4,
"Polígono de frecuencias",
numericInput("binwidth", label = "Bin width", value = 0.1, step = 0.1),
sliderInput("range", label = "range", value = c(-3, 3), min = -5, max = 5)
)
),fluidRow(
column(9, plotOutput("hist")),
column(3, verbatimTextOutput("ttest"))
)
)
<- function(input, output, session) {
server $hist <- renderPlot({
output<- rnorm(input$n1, input$mean1, 1)
x1 <- rnorm(input$n2, input$mean2, 1)
x2
freqpoly(x1, x2, binwidth = input$binwidth, xlim = input$range)
res = 96)
},
$ttest <- renderText({
output<- rnorm(input$n1, input$mean1, 1)
x1 <- rnorm(input$n2, input$mean2, 1)
x2
t_test(x1, x2)
}) }
Shiny sabe que debe actualiza un output solo cuando los inputs a los que hace referencia cambian de valor.
Sin embargo, Shiny no ejecuta solo parte del código dentro de contexto reactivo, es decir, o ejecuta todo ese bloque de código, o no ejecuta nada.
Ejemplo:
<- rnorm(input$n1, input$mean1, 1)
x1 <- rnorm(input$n2, input$mean2, 1)
x2
t_test(x1, x2)
flowchart LR A[n1] --> B>ttest] C[mean1] --> B>ttest] D[n2] --> B>ttest] E[mean2] --> B>ttest] A[n1] --> G>ttest] C[mean1] --> G>ttest] D[n2] --> G>ttest] E[mean2] --> G>ttest] F[binwidth] --> G>hist] H[range] --> G>hist] linkStyle 4 stroke:red; linkStyle 5 stroke:red; linkStyle 6 stroke:red; linkStyle 7 stroke:red; linkStyle 8 stroke:red; linkStyle 9 stroke:red; linkStyle default stroke-width:2px, fill:none, stroke:blue;
Fallas:
t.test
están evaluando data distinta, debido a su naturaleza aleatoria. Debe tratarse de la misma data en ambos contextos reactivos.(Cambiar Bin width o range manualmente)
<- function(input, output, session) {
server <- reactive(rnorm(input$n1, input$mean1, 1))
x1 <- reactive(rnorm(input$n2, input$mean2, 1))
x2
$hist <- renderPlot({
outputfreqpoly(x1(), x2(), binwidth = input$binwidth, xlim = input$range)
res = 96)
},
$ttest <- renderText({
outputt_test(x1(), x2())
}) }
(Cambiar Bin width o range manualmente)
flowchart LR A[n1] --> X((x1)) B[mean1] --> X((x1)) C[n2] --> Y((x2)) D[mean2] --> Y((x2)) E[binwidth] --> W>hist] F[range] --> W>hist] X((x1)) --> V>ttest] X((x1)) --> W>hist] Y((x2)) --> V>ttest] Y((x2)) --> W>hist] linkStyle default stroke-width:2px, fill:none, stroke:blue;
Se podría intentar reemplazar el uso de expresiones reactivas, en favor de variables o funciones usuales como las empleamos en R. Sin embargo, ambos casos o fallarán o serán ineficientes.
Uso de variables
<- function(input, output, session) {
server <- rnorm(input$n1, input$mean1, 1)
x1 <- rnorm(input$n2, input$mean2, 1)
x2 $hist <- renderPlot({
outputfreqpoly(x1, x2, binwidth = input$binwidth, xlim = input$range)
res = 96)
},
$ttest <- renderText({
outputt_test(x1, x2)
}) }
Errores:
x1 <- ...
y x2 <- ...
solo se ejecutaría una vez, cuando empieza la sesión.Uso de funciones
<- function(input, output, session) {
server <- function() rnorm(input$n1, input$mean1, 1)
x1 <- function() rnorm(input$n2, input$mean2, 1)
x2
$hist <- renderPlot({
outputfreqpoly(x1(), x2(), binwidth = input$binwidth, xlim = input$range)
res = 96)
},
$ttest <- renderText({
outputt_test(x1(), x2())
}) }
Con este método, la app va a funcionar, pero cada input que cambie hará que se ejecute todo el código de la función servidor, una vez más. En realidad, es el mismo problema que tuvimos en la sección El gráfico reactivo.
Recuerden que las expresiones reactivas automáticamente guardan su valor, y solo se actualizan cuando sus inputs cambian de valor.
Todavía no exploraremos las herramientas de esta sección, ya que serán presentadas como parte del capítulo 15, durante la sesión 4.
reactiveTimer()
Código del ejemplo del libro (simulación automática).
On click
Código del ejemplo del libro (simulación pausada).
observeEvent()
No es necesario que, al correr una app Shiny, cambiar el valor de algún input actualice el contenido de la página.
Si requerimos que cierto código (no asociado a algún output) se ejecute cuando cierto input en particular cambie de valor, podemos usar la función observeEvent()
… esta cuenta con dos argumentos:
library(shiny)
<- fluidPage(
ui textInput("nombre", "¿Cuál es tu nombre?"),
textOutput("saludo")
)
<- function(input, output, session) {
server <- reactive(paste0("Hola ", input$nombre))
cadena
$saludo <- renderText(cadena())
outputobserveEvent(input$nombre, {
message("Saludo realizado")
})
}
shinyApp(ui, server)
Similarmente, si requerimos que cierto código (no asociado a algún output) se ejecute cuando uno o más inputs en particular cambie(n) de valor, podemos usar la función observe()
.
Las funciones observe()
y observeEvent()
nos servirán al momento de integrar R con JavaScript.
Pueden ver un ejemplo de aquella interacción, vía el código en esta carpeta.
Este capítulo concluye la visión general de Shiny.