Performing Summary Statistics and Column-Wise T-Tests in R with Tidyverse
The Challenge with Base R Summary Operations
When analyzing differences across two groups in a dataset, developers often need to compute group-wise summary statistics (like mean and standard deviation) and perform hypothesis testing (such as a two-sample t-test) across multiple continuous variables. Doing this with base R functions like aggregate() or manual matrix conversions can lead to verbose, brittle, and unreadable code.
The Modern Solution: Tidyverse and Broom
By leveraging modern R tools—specifically dplyr, tidyr, and broom—you can compute all summary statistics and column-wise t-tests cleanly in just a few lines of code.
Step 1: Compute Group Summary Statistics Cleanly
Instead of running multiple aggregate() calls and stitching vectors together with cbind(), use summarise(across(...)) in dplyr:
library(dplyr)
# Filter dataset for groups of interest (4 and 8 cylinders)
data_sub <- mtcars %>%
filter(cyl %in% c(4, 8))
# Calculate Mean and SD across all numeric variables grouped by cyl
summary_stats <- data_sub %>%
group_by(cyl) %>%
summarise(across(everything(), list(mean = mean, sd = sd)))
print(summary_stats)Step 2: Run Column-Wise T-Tests with pivot_longer() and broom::tidy()
To run t-tests across every numeric variable efficiently without manual loop indexing, reshape the dataset into long format, group by variable name, and extract clean test metrics with broom::tidy():
library(tidyr)
library(purrr)
library(broom)
# Reshape and perform t-test for each variable
t_test_results <- data_sub %>%
pivot_longer(cols = -cyl, names_to = "variable", values_to = "value") %>%
group_by(variable) %>%
summarise(
test = list(t.test(value ~ cyl)),
.groups = "drop"
) %>%
mutate(tidied = map(test, tidy)) %>%
unnest(tidied) %>%
select(variable, p.value, statistic, conf.low, conf.high)
print(t_test_results)Step 3: Combine Everything into a Publication-Ready Table
If your goal is to produce a single aggregated data frame containing group means, standard deviations, and the calculated p-value for each feature, combine the two workflows:
# Convert summary statistics to long layout
stats_reshaped <- summary_stats %>%
pivot_longer(-cyl, names_to = c("variable", "stat"), names_sep = "_") %>%
pivot_wider(names_from = c(cyl, stat), values_from = value, names_prefix = "cyl_")
# Join p-values from the t-tests
final_summary <- stats_reshaped %>%
left_join(t_test_results %>% select(variable, p.value), by = "variable")
print(final_summary)Alternative Approach: Using rstatix
For an even more streamlined experience, the rstatix package provides pipe-friendly statistical wrappers designed for tidy data frames:
library(rstatix)
t_tests_rstatix <- data_sub %>%
pivot_longer(cols = -cyl, names_to = "variable", values_to = "value") %>%
group_by(variable) %>%
t_test(value ~ cyl)
print(t_tests_rstatix)Summary
Using dplyr::across() together with tidyr::pivot_longer() and broom::tidy() replaces long base R scripts with a readable data pipeline. This pattern scales easily to any number of numerical columns and complex grouped data workflows in R.