Calculator: \(t\)-testing

Author

Tom Coleman

Summary
Calculators to provide facilit for all types of \(t\)-testing.

\(t\)-statistic and degrees of freedom calculator

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

library(shiny)
library(bslib)

ui <- page_fluid(
  title = "t-statistic Calculator",
  
  # Navigation buttons
  layout_columns(
    col_widths = 12,
    card(
      card_body(
        div(
          style = "text-align: center; margin-bottom: 20px;",
          actionButton("show_one_sample", "One sample t-test", 
                       class = "btn-primary", style = "margin-right: 10px;"),
          actionButton("show_two_sample", "Two-sample t-test", 
                       class = "btn-outline-primary")
        )
      )
    )
  ),
  
  # One-sample t-test panel
  conditionalPanel(
    condition = "input.current_tab == 'one_sample'",
    layout_columns(
      col_widths = c(6, 6),
      card(
        card_header("Sample Data"),
        card_body(
          numericInput("n1", "Sample size (n)", value = 30, min = 1, step = 1),
          numericInput("xbar1", "Sample mean", value = 50, step = 0.01),
          numericInput("s1", "Sample standard deviation", value = 10, min = 0.001, step = 0.01),
          numericInput("mu0", "Population mean (μ₀)", value = 45, step = 0.01),
          hr(),
          helpText("Calculate t-statistic for testing whether sample mean differs from population mean.")
        )
      ),
      card(
        card_header("Results"),
        card_body(
          verbatimTextOutput("one_sample_results")
        )
      )
    )
  ),
  
  # Two-sample t-test panel
  conditionalPanel(
    condition = "input.current_tab == 'two_sample'",
    layout_columns(
      col_widths = c(4, 4, 4),
      card(
        card_header("Sample 1"),
        card_body(
          numericInput("n1_two", "Sample Size (n₁)", value = 25, min = 1, step = 1),
          numericInput("xbar1_two", "Sample Mean (x̄₁)", value = 52, step = 0.01),
          numericInput("s1_two", "Sample Standard Deviation (s₁)", value = 8, min = 0.001, step = 0.01)
        )
      ),
      card(
        card_header("Sample 2"),
        card_body(
          numericInput("n2_two", "Sample Size (n₂)", value = 30, min = 1, step = 1),
          numericInput("xbar2_two", "Sample Mean (x̄₂)", value = 48, step = 0.01),
          numericInput("s2_two", "Sample Standard Deviation (s₂)", value = 10, min = 0.001, step = 0.01)
        )
      ),
      card(
        card_header("Test Settings"),
        card_body(
          radioButtons("test_type", "Test type:",
                       choices = list("Independent Samples (Equal variances)" = "independent",
                                      "Independent Samples (Welch's t-test)" = "welch",
                                      "Paired Samples" = "paired"),
                       selected = "independent"),
          hr(),
          helpText("Choose between pooled t-test, Welch's t-test, or paired samples t-test.")
        )
      )
    ),
    layout_columns(
      col_widths = 12,
      card(
        card_header("Results"),
        card_body(
          verbatimTextOutput("two_sample_results")
        )
      )
    )
  ),
  
  # Hidden input to track current tab
  textInput("current_tab", "", value = "one_sample", width = "0px"),
  tags$style("#current_tab { display: none; }"),
  uiOutput("button_css")
)

server <- function(input, output, session) {
  
  # Switch tab logic
  observeEvent(input$show_one_sample, {
    updateTextInput(session, "current_tab", value = "one_sample")
  })
  
  observeEvent(input$show_two_sample, {
    updateTextInput(session, "current_tab", value = "two_sample")
  })
  
  # Dynamic CSS
  output$button_css <- renderUI({
    if (input$current_tab == "one_sample") {
      tags$style("
        #show_one_sample { 
          background-color: #3F6BB6 !important; 
          border-color: #3F6BB6 !important; 
          color: white !important; 
        }
        #show_two_sample { 
          background-color: transparent !important; 
          border-color: #3F6BB6 !important; 
          color: #3F6BB6 !important; 
        }
      ")
    } else {
      tags$style("
        #show_one_sample { 
          background-color: transparent !important; 
          border-color: #3F6BB6 !important; 
          color: #3F6BB6 !important; 
        }
        #show_two_sample { 
          background-color: #3F6BB6 !important; 
          border-color: #3F6BB6 !important; 
          color: white !important; 
        }
      ")
    }
  })
  
  # One-sample t-test logic
  output$one_sample_results <- renderText({
    n <- input$n1
    xbar <- input$xbar1
    s <- input$s1
    mu0 <- input$mu0
    
    t_stat <- (xbar - mu0) / (s / sqrt(n))
    df <- n - 1
    se <- s / sqrt(n)
    
    paste0(
      "T-statistic: ", round(t_stat, 6), "\n",
      "Degrees of freedom: ", df, "\n",
      "Standard error: ", round(se, 6), "\n\n",
      "Formula: t = (x̄ - μ₀) / (s / √n)\n",
      "x̄ = ", xbar, ", μ₀ = ", mu0, ", s = ", s, ", n = ", n
    )
  })
  
  # Two-sample t-test logic
  output$two_sample_results <- renderText({
    n1 <- input$n1_two
    xbar1 <- input$xbar1_two
    s1 <- input$s1_two
    n2 <- input$n2_two
    xbar2 <- input$xbar2_two
    s2 <- input$s2_two
    test_type <- input$test_type
    
    if (test_type == "independent") {
      # Pooled t-test
      sp <- sqrt(((n1 - 1) * s1^2 + (n2 - 1) * s2^2) / (n1 + n2 - 2))
      se <- sp * sqrt(1/n1 + 1/n2)
      t_stat <- (xbar1 - xbar2) / se
      df <- n1 + n2 - 2
      
      result <- paste0(
        "Independent samples t-test (Equal variances assumed)\n",
        "T-statistic: ", round(t_stat, 6), "\n",
        "Degrees of freedom: ", df, "\n",
        "Pooled standard deviation: ", round(sp, 6), "\n",
        "Standard error: ", round(se, 6), "\n\n",
        "Formula: t = (x̄₁ - x̄₂) / (sp × √(1/n₁ + 1/n₂))"
      )
      
    } else if (test_type == "welch") {
      # Welch's t-test
      se <- sqrt((s1^2 / n1) + (s2^2 / n2))
      t_stat <- (xbar1 - xbar2) / se
      df_num <- (s1^2 / n1 + s2^2 / n2)^2
      df_den <- ((s1^2 / n1)^2 / (n1 - 1)) + ((s2^2 / n2)^2 / (n2 - 1))
      df <- df_num / df_den
      
      result <- paste0(
        "Welch's t-test (Unequal variances)\n",
        "T-statistic: ", round(t_stat, 6), "\n",
        "Degrees of freedom (Welch): ", round(df, 2), "\n",
        "Standard error: ", round(se, 6), "\n\n",
        "Formula: t = (x̄₁ - x̄₂) / √(s₁²/n₁ + s₂²/n₂)\n",
        "df = [(s₁²/n₁ + s₂²/n₂)²] / [ (s₁²/n₁)²/(n₁−1) + (s₂²/n₂)²/(n₂−1) ]"
      )
      
    } else {
      # Paired t-test approximation
      n <- min(n1, n2)
      s_diff <- sqrt(s1^2 + s2^2)
      se <- s_diff / sqrt(n)
      t_stat <- (xbar1 - xbar2) / se
      df <- n - 1
      
      result <- paste0(
        "Paired samples t-test (approximation)\n",
        "T-statistic: ", round(t_stat, 6), "\n",
        "Degrees of freedom: ", df, "\n",
        "Standard error of differences: ", round(se, 6), "\n\n",
        "Formula: t = (x̄₁ - x̄₂) / (s_diff / √n)\n",
        "s_diff ≈ √(s₁² + s₂²) = ", round(s_diff, 6)
      )
    }
    
    return(result)
  })
}

shinyApp(ui, server)

\(p\)-value calculator

Using your \(t\)-statistic and degrees of freedom from the above calculator, use the below calculator to work out your \(p\)-value.

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

library(shiny)
library(bslib)
library(ggplot2)

ui <- page_fluid(
  title = "T-test calculator",
  
  layout_columns(
    col_widths = c(4, 8),
    
    # Left column - Input parameters
    card(
      card_header("Input parameters"),
      card_body(
        numericInput("tscore", "T-statistic", value = 2.0, step = 0.01),
        numericInput("df", "Degrees of freedom", value = 20, min = 1, step = 1),
        radioButtons("test_type", "Test type",
                    choices = list("Two-tailed" = "two",
                                  "One-tailed (upper)" = "upper",
                                  "One-tailed (lower)" = "lower"),
                    selected = "two"),
        hr(),
        helpText("This app calculates p-values for t-tests based on the t-distribution with specified degrees of freedom.")
      )
    ),
    
    # Right column - Graphical representation
    card(
      card_header("Graphical representation"),
      card_body(
        plotOutput("density_plot", height = "300px")
      )
    )
  ),
  
  # Results at the bottom
  card(
    card_header("T-test results"),
    card_body(
      verbatimTextOutput("pvalue_result")
    )
  )
)

server <- function(input, output, session) {
  
  # Calculate p-value based on test type
  p_value <- reactive({
    t <- input$tscore
    df <- input$df
    test <- input$test_type
    
    if (test == "two") {
      p <- 2 * pt(-abs(t), df = df)
      result <- paste0("Two-tailed p-value: ", round(p, 4))
    } else if (test == "upper") {
      p <- pt(t, df = df, lower.tail = FALSE)
      result <- paste0("Upper-tailed p-value: ", round(p, 4))
    } else if (test == "lower") {
      p <- pt(t, df = df, lower.tail = TRUE)
      result <- paste0("Lower-tailed p-value: ", round(p, 4))
    }
    
    return(list(p = p, result = result, test = test))
  })
  
  # Display p-value
  output$pvalue_result <- renderText({
    p_value()$result
  })
  
  # Create density plot
  output$density_plot <- renderPlot({
    t <- input$tscore
    df <- input$df
    test <- p_value()$test
    
    # Generate x values for t-distribution
    x <- seq(-4, 4, length.out = 1000)
    y <- dt(x, df = df)
    df_data <- data.frame(x = x, y = y)
    
    # Base plot
    p <- ggplot(df_data, aes(x = x, y = y)) +
      geom_line() +
      labs(x = "T-statistic", y = "Density", 
           title = paste("T-distribution (df =", df, ")")) +
      theme_minimal() +
      theme(panel.grid.minor = element_blank()) +
      geom_vline(xintercept = 0, linetype = "dashed", alpha = 0.5)
    
    # Add shaded area based on test type, using #3F6BB6 as the color
    if (test == "two") {
      # Two-tailed test: shade both tails
      if (t > 0) {
        p <- p + 
          geom_area(data = subset(df_data, x >= t), aes(x = x, y = y), fill = "#3F6BB6", alpha = 0.5) +
          geom_area(data = subset(df_data, x <= -t), aes(x = x, y = y), fill = "#3F6BB6", alpha = 0.5) +
          geom_vline(xintercept = t, color = "#3F6BB6") +
          geom_vline(xintercept = -t, color = "#3F6BB6")
      } else {
        p <- p + 
          geom_area(data = subset(df_data, x <= t), aes(x = x, y = y), fill = "#3F6BB6", alpha = 0.5) +
          geom_area(data = subset(df_data, x >= -t), aes(x = x, y = y), fill = "#3F6BB6", alpha = 0.5) +
          geom_vline(xintercept = t, color = "#3F6BB6") +
          geom_vline(xintercept = -t, color = "#3F6BB6")
      }
    } else if (test == "upper") {
      # Upper-tailed test: shade area above t
      p <- p + 
        geom_area(data = subset(df_data, x >= t), aes(x = x, y = y), fill = "#3F6BB6", alpha = 0.5) +
        geom_vline(xintercept = t, color = "#3F6BB6")
    } else if (test == "lower") {
      # Lower-tailed test: shade area below t
      p <- p + 
        geom_area(data = subset(df_data, x <= t), aes(x = x, y = y), fill = "#3F6BB6", alpha = 0.5) +
        geom_vline(xintercept = t, color = "#3F6BB6")
    }
    
    return(p)
  })
}

shinyApp(ui, server)

Version history

v1.0: created by tdhc 08/25.

This work is licensed under CC BY-NC-SA 4.0.

Mailing List



Feedback

Your feedback is appreciated and useful. Feel free to leave a comment here,
but please be specific with any issues you encounter so we can help to resolve them
(for example, what page it occured on, what you tried, and so on).