Mean & SD Explorer

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 950

library(shiny)
library(bslib)

ui <- page_sidebar(
  title = "Mean & SD Explorer",

  sidebar = sidebar(
    numericInput(
      "target_mean",
      "Target Mean:",
      value = 50,
      step = 1
    ),
    numericInput(
      "target_sd",
      "Target SD:",
      value = 10,
      min = 0.1,
      step = 1
    ),
    radioButtons(
      "n",
      "Sample Size:",
      choices = c("5" = 5, "10" = 10),
      selected = 5
    ),
    actionButton("generate", "Generate New Data", class = "btn-primary"),
    hr(),
    p("This app generates random numbers with approximately your specified mean and standard deviation."),
    p("Click the button to see different samples - notice how the actual values vary around the target!")
  ),

  card(
    card_header("Generated Data"),
    tableOutput("data_table")
  ),

  layout_columns(
    card(
      card_header("Summary Statistics"),
      tableOutput("stats_table")
    ),
    card(
      card_header("Visualisation"),
      plotOutput("dot_plot", height = "200px")
    )
  )
)

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

  # Generate data when button is clicked
  data <- reactiveVal(NULL)

  observeEvent(input$generate, {
    n <- as.integer(input$n)
    values <- rnorm(n, mean = input$target_mean, sd = input$target_sd)
    # Round to 1 decimal place for readability
    values <- round(values, 1)
    data(values)
  }, ignoreNULL = FALSE)

  # Also generate on input changes
  observeEvent(c(input$target_mean, input$target_sd, input$n), {
    n <- as.integer(input$n)
    values <- rnorm(n, mean = input$target_mean, sd = input$target_sd)
    values <- round(values, 1)
    data(values)
  })

  output$data_table <- renderTable({
    req(data())
    df <- data.frame(
      Observation = paste0("Value ", seq_along(data())),
      Value = data()
    )
    df
  }, striped = TRUE, hover = TRUE, width = "100%")

  output$stats_table <- renderTable({
    req(data())
    actual_mean <- mean(data())
    actual_sd <- sd(data())

    data.frame(
      Statistic = c("Target Mean", "Actual Mean", "Target SD", "Actual SD"),
      Value = c(
        round(input$target_mean, 2),
        round(actual_mean, 2),
        round(input$target_sd, 2),
        round(actual_sd, 2)
      )
    )
  }, striped = TRUE, hover = TRUE, width = "100%")

  output$dot_plot <- renderPlot({
    req(data())
    values <- data()
    actual_mean <- mean(values)

    # Set up plot range
    plot_range <- range(c(values, input$target_mean - input$target_sd * 2,
                          input$target_mean + input$target_sd * 2))

    par(mar = c(4, 2, 2, 2))

    # Create dot plot
    plot(values, rep(1, length(values)),
         xlim = plot_range,
         ylim = c(0.5, 1.5),
         pch = 19, cex = 2, col = "#0d6efd",
         xlab = "Value", ylab = "",
         yaxt = "n", main = "")

    # Add mean line
    abline(v = actual_mean, col = "#dc3545", lwd = 2, lty = 2)

    # Add target mean line
    abline(v = input$target_mean, col = "#198754", lwd = 2)

    # Legend
    legend("topright",
           legend = c("Data points", "Actual mean", "Target mean"),
           col = c("#0d6efd", "#dc3545", "#198754"),
           pch = c(19, NA, NA),
           lty = c(NA, 2, 1),
           lwd = c(NA, 2, 2),
           cex = 0.8,
           bg = "white")
  })
}

shinyApp(ui, server)