Executive Summary
Does math score differ by parental level of education?
Math scores genuinely differ by parental level of education. The Kruskal-Wallis rank-based test yielded p = 0.0001, confirming the pattern is stronger than chance. The largest gap is 7.608 points between master's degree (mean 69.746) and high school (mean 62.138), with a 95% confidence interval of 1.266 to 13.95 and adjusted p = 0.00837. Overall, 6 of 15 pairwise comparisons were significant. The effect size (eta-squared = 0.032) is small, meaning parental education explains about 3% of the variation in math scores, though the difference is real and detectable.
Analysis Overview
Comparison of math score across 6 parental level of education groups (1,000 observations).
This analysis compares math scores across 6 parental education levels using a full statistical toolkit: parametric tests (one-way ANOVA and Welch ANOVA) test whether group means differ, while the Kruskal-Wallis rank-based test makes no normality assumption. Eta-squared quantifies the practical size of any difference. Because the Shapiro-Wilk normality check rejected normality (p = 0.000432), the rank-based result is the safer headline. All 1,000 observations were complete and all 6 groups met minimum size requirements, so no rows or groups were dropped.
Data Quality
Row and group cleaning applied before testing.
All 1,000 rows loaded were retained; no missing math score values required removal. All 6 parental education groups (associate's degree, bachelor's degree, high school, master's degree, some college, some high school) had at least 3 observations and none were lumped into an "Other" category. The final comparison uses the full dataset with no data quality exclusions, ensuring the results reflect the complete sample.
Outcome by Group
math score distribution within each parental level of education group.
Master's degree shows the highest median (73) and high school the lowest (63). The boxes overlap substantially, reflecting within-group variation that is large relative to the between-group differences. Group spreads are broadly comparable across all six parental education levels, indicating no severe heterogeneity. The visual separation between boxes is modest but consistent with the significant pairwise differences detected in the formal tests—the differences exist but are modest relative to the scatter within each group.
Group Statistics
n, mean, spread, median and 95% CI of math score per parental level of education group.
| Group | N | Mean | SD | Median | CI Low | CI High |
|---|---|---|---|---|---|---|
| associate's degree | 222 | 67.88 | 15.11 | 67 | 65.88 | 69.88 |
| bachelor's degree | 118 | 69.39 | 14.94 | 68 | 66.67 | 72.11 |
| high school | 196 | 62.14 | 14.54 | 63 | 60.09 | 64.19 |
| master's degree | 59 | 69.75 | 15.15 | 73 | 65.8 | 73.69 |
| some college | 226 | 67.13 | 14.31 | 67.5 | 65.25 | 69 |
| some high school | 179 | 63.5 | 15.93 | 65 | 61.15 | 65.85 |
Master's degree has the highest mean (69.746, 95% CI 65.797 to 73.695) among 59 students, while high school has the lowest (62.138, 95% CI 60.09 to 64.186) among 196 students. Bachelor's degree (n=118, mean 69.39) and some college (n=226, mean 67.128) occupy the middle ground. Some high school (n=179, mean 63.497) and associate's degree (n=222, mean 67.883) round out the comparison. Means and medians align closely within each group, suggesting symmetric distributions. The non-overlapping confidence intervals between master's degree and high school signal a genuine difference.
Statistical Tests
The full test battery: parametric, nonparametric, and the normality check.
| Test | Statistic | P Value | Effect Size | Interpretation |
|---|---|---|---|---|
| One-way ANOVA | 6.522 | 5.59e-06 | 0.032 | Mean math score differs across parental level of education groups: highly significant (p < 0.001); eta-squared = 0.032 (small effect). |
| Welch ANOVA (unequal variances) | 6.494 | 8.79e-06 | 0.032 | Variance-robust cross-check: highly significant (p < 0.001). |
| Kruskal-Wallis | 26.51 | 0.0001 | — | Rank-based (no normality assumption): highly significant (p < 0.001). |
| Shapiro-Wilk normality (residuals) | — | 0.0004 | — | Normality clearly violated — favor the rank-based result. |
All tests agree the difference is significant. The one-way ANOVA (F = 6.522, p = 5.59e-06) and Welch ANOVA (F = 6.494, p = 8.79e-06) both show highly significant results with eta-squared = 0.032. The rank-based Kruskal-Wallis test (H = 26.506, p = 0.0001) confirms the pattern without assuming normality. The Shapiro-Wilk normality test on residuals (p = 0.0004) clearly rejects the normality assumption, making the Kruskal-Wallis result the safer headline. The agreement across three independent tests strengthens confidence in the finding.
Which Groups Differ
Pairwise differences in mean math score between parental level of education groups.
| Comparison | Difference | CI Low | CI High | Adj P | Significant |
|---|---|---|---|---|---|
| master's degree-high school | 7.608 | 1.266 | 13.95 | 0.0084 | yes |
| high school-bachelor's degree | -7.252 | -12.23 | -2.276 | 0.0005 | yes |
| some high school-master's degree | -6.249 | -12.66 | 0.163 | 0.0611 | no |
| some high school-bachelor's degree | -5.893 | -10.96 | -0.828 | 0.0119 | yes |
| high school-associate's degree | -5.745 | -9.931 | -1.559 | 0.0013 | yes |
| some college-high school | 4.991 | 0.822 | 9.159 | 0.0086 | yes |
| some high school-associate's degree | -4.386 | -8.676 | -0.095 | 0.0418 | yes |
| some high school-some college | -3.631 | -7.904 | 0.642 | 0.148 | no |
| some college-master's degree | -2.617 | -8.861 | 3.627 | 0.838 | no |
| some college-bachelor's degree | -2.262 | -7.112 | 2.589 | 0.768 | no |
| master's degree-associate's degree | 1.863 | -4.393 | 8.118 | 0.958 | no |
| bachelor's degree-associate's degree | 1.507 | -3.359 | 6.373 | 0.95 | no |
| some high school-high school | 1.359 | -3.056 | 5.775 | 0.951 | no |
| some college-associate's degree | -0.755 | -4.79 | 3.281 | 0.995 | no |
| master's degree-bachelor's degree | 0.356 | -6.454 | 7.166 | 1 | no |
Six pairs differ significantly after adjusting for multiple comparisons. The master's degree–high school gap (7.608, 95% CI 1.266 to 13.95, adj. p = 0.0084) is the largest. High school also differs significantly from bachelor's degree (−7.252, 95% CI −12.228 to −2.276, adj. p = 0.0005) and associate's degree (−5.745, 95% CI −9.931 to −1.559, adj. p = 0.0013). Some college exceeds high school by 4.991 (95% CI 0.822 to 9.159, adj. p = 0.0086). Some high school differs from bachelor's degree (−5.893, adj. p = 0.0119) and associate's degree (−4.386, adj. p = 0.0418). The remaining 9 pairs show no significant differences, with confidence intervals spanning zero.
Group Comparison — t-test, ANOVA & Nonparametric
Does a numeric outcome differ between groups? One deck runs the whole standard toolkit: Welch and Student t-tests (2 groups) or one-way and Welch ANOVA (3+ groups), Mann-Whitney / Kruskal-Wallis nonparametric cross-checks, effect sizes (Cohen's d, eta-squared), and pairwise differences with confidence intervals.
Why This Method?
Real questions rarely announce which test they need. Running the parametric and nonparametric versions together, with a normality check on the residuals, lets the report itself say which result is the safest headline — instead of leaving the user to guess between four calculators.
What This Analysis Covers
- Group distributions side by side (boxplot) and per-group statistics
- The full test battery with statistics, p-values, and effect sizes
- Exactly which group pairs differ (Tukey HSD / Welch CI)
Standard Library
Platform standard-library module (LAT-1441): runs on ANY dataset via the semantic mapping {outcome, group}. All narrative is derived from the user's own column names and computed values.
suppressPackageStartupMessages(library(DT))
suppressPackageStartupMessages(library(htmlwidgets))
suppressPackageStartupMessages(library(arrow))
suppressPackageStartupMessages(library(knitr))
suppressPackageStartupMessages(library(rmarkdown))
suppressPackageStartupMessages(library(dplyr))
suppressPackageStartupMessages(library(tidyr))
suppressPackageStartupMessages(library(ggplot2))
suppressPackageStartupMessages(library(stringr))
suppressPackageStartupMessages(library(lubridate))
suppressPackageStartupMessages(library(broom))
suppressPackageStartupMessages(library(Matrix))
suppressPackageStartupMessages(library(cluster))
suppressPackageStartupMessages(library(data.table))Core Analysis Pipeline
compute_shared <- function(df, params, col_map = list()) {
# === SHARED EXPORTS ===
# initial_rows/final_rows/rows_removed $ row accounting
# outcome_h / group_h $ humanized user names for the two mapped columns
# k / group_levels $ number of groups after cleaning + their names
# n_na_outcome $ rows dropped for missing/non-numeric outcome
# dropped_groups_df $ data.frame(group, n) — groups dropped (n < 3)
# lumped_levels $ character — levels folded into "Other"
# group_summary_df $ group, n, mean, sd, median, ci_low, ci_high
# test_results_df $ test, statistic, p_value, effect_size, interpretation
# pairwise_df $ comparison, difference, ci_low, ci_high, adj_p, significant
# boxplot_df $ group_name, outcome_value (<= 2000 sampled rows)
# shapiro_p / out_frac / nonparam_preferred $ assumption diagnostics
# primary_test / primary_p / primary_sig $ the headline result
# effect_value / effect_text $ headline effect size
# top_sig_pair $ 1-row slice of pairwise_df or NULL
# metrics / json_output
# === /SHARED EXPORTS ===Step 1: Resolve mapped columns (humanized for all prose)
initial_rows <- nrow(df)
outcome_h <- humanize_semantic("outcome", col_map)
group_h <- humanize_semantic("group", col_map)
if (!("outcome" %in% names(df)) || !("group" %in% names(df))) {
stop(sprintf("Group comparison needs both '%s' (the numeric outcome) and '%s' (the groups) mapped.",
outcome_h, group_h))
}Step 2: Coerce the outcome to numeric (95% rule); drop NA-outcome rows
v <- df$outcome
if (!is.numeric(v)) {
ch <- as.character(v)
non_blank <- !is.na(ch) & trimws(ch) != ""
conv <- suppressWarnings(as.numeric(ch))
if (sum(non_blank) == 0 ||
sum(!is.na(conv[non_blank])) < 0.95 * sum(non_blank)) {
stop(sprintf("The outcome column '%s' does not look numeric — fewer than 95%% of its values parse as numbers. Pick a numeric column to compare.",
outcome_h))
}
v <- conv
}
df$outcome <- v
g <- as.character(df$group)
g[is.na(g) | trimws(g) == ""] <- "Missing"
keep <- !is.na(df$outcome)
n_na_outcome <- sum(!keep)
df <- df[keep, , drop = FALSE]
g <- g[keep]
if (nrow(df) == 0) {
stop(sprintf("No rows with a usable numeric value in '%s' remained after cleaning.", outcome_h))
}Step 3: Clean the groups — drop n<3 (reported), lump beyond 8 levels
tab <- table(g)
small <- names(tab)[tab < 3]
dropped_groups_df <- data.frame(group = character(0), n = integer(0),
stringsAsFactors = FALSE)
if (length(small) > 0) {
dropped_groups_df <- data.frame(group = small, n = as.integer(tab[small]),
stringsAsFactors = FALSE)
sel <- !(g %in% small)
df <- df[sel, , drop = FALSE]
g <- g[sel]
}
lumped_levels <- character(0)
tab <- sort(table(g), decreasing = TRUE)
if (length(tab) > 8) {
keep_lv <- names(tab)[1:8]
lumped_levels <- setdiff(names(tab), keep_lv)
g[g %in% lumped_levels] <- "Other"
}Re-check after lumping ("Other" itself could be tiny)
tab <- table(g)
small2 <- names(tab)[tab < 3]
if (length(small2) > 0) {
dropped_groups_df <- rbind(dropped_groups_df,
data.frame(group = small2, n = as.integer(tab[small2]),
stringsAsFactors = FALSE))
sel <- !(g %in% small2)
df <- df[sel, , drop = FALSE]
g <- g[sel]
}
gf <- factor(g)
k <- nlevels(gf)
if (k < 2) {
stop(sprintf("Group comparison needs at least 2 groups in '%s' with 3 or more rows each; only %d usable group(s) remained after cleaning. Check that '%s' really splits the data into groups.",
group_h, k, group_h))
}
y <- df$outcome
final_rows <- length(y)
rows_removed <- initial_rows - final_rows
if (final_rows < 10) {
stop(sprintf("Only %d usable rows remained — at least 10 are needed to compare groups.", final_rows))
}
if (isTRUE(stats::var(y) == 0)) {
stop(sprintf("The outcome '%s' has no variation at all (every value is identical) — there is nothing to compare.", outcome_h))
}
group_levels <- levels(gf)Step 4: Per-group summary statistics with 95% CIs
group_summary_df <- do.call(rbind, lapply(group_levels, function(l) {
x <- y[gf == l]
n <- length(x)
m <- mean(x)
s <- stats::sd(x)
half <- if (!is.na(s) && s > 0 && n > 1) stats::qt(0.975, n - 1) * s / sqrt(n) else 0
data.frame(group = l, n = n,
mean = round(m, 3),
sd = round(if (is.na(s)) 0 else s, 3),
median = round(stats::median(x), 3),
ci_low = round(m - half, 3),
ci_high = round(m + half, 3),
stringsAsFactors = FALSE)
}))
rownames(group_summary_df) <- NULLStep 5: Assumption diagnostics — normality of residuals + outliers
dat <- data.frame(y = y, gf = gf)
fit <- stats::aov(y ~ gf, data = dat)
res <- stats::residuals(fit)
shapiro_p <- NA_real_
if (final_rows >= 3 && final_rows <= 5000) {
shapiro_p <- tryCatch(stats::shapiro.test(res)$p.value,
error = function(e) NA_real_)
}
iqr <- stats::IQR(res)
qs <- stats::quantile(res, c(0.25, 0.75), names = FALSE)
out_frac <- if (is.na(iqr) || iqr == 0) 0 else
mean(res < qs[1] - 3 * iqr | res > qs[2] + 3 * iqr)
heavy_outliers <- out_frac > 0.01
nonparam_preferred <- (!is.na(shapiro_p) && shapiro_p < 0.01) || heavy_outliers
sig_phrase <- function(p) {
if (is.na(p)) "could not be computed"
else if (p < 0.001) "highly significant(p < 0.001)"
else if (p < 0.05) sprintf("significant(p = %.3g)", p)
else sprintf("not significant(p = %.3g)", p)
}
d_word <- function(d) {
a <- abs(d)
if (is.na(a)) "unknown"
else if (a < 0.2) "negligible" else if (a < 0.5) "small"
else if (a < 0.8) "medium" else "large"
}
eta_word <- function(e) {
if (is.na(e)) "unknown"
else if (e < 0.01) "negligible" else if (e < 0.06) "small"
else if (e < 0.14) "medium" else "large"
}Step 6: The test battery
test_rows <- list()
add_test <- function(test, statistic, p_value, effect_size, interpretation) {
test_rows[[length(test_rows) + 1]] <<- data.frame(
test = test,
statistic = round(statistic, 3),
p_value = signif(p_value, 3),
effect_size = if (is.na(effect_size)) NA_real_ else round(effect_size, 3),
interpretation = interpretation,
stringsAsFactors = FALSE
)
}
pairwise_df <- NULL
effect_value <- NA_real_
effect_text <- ""
primary_test <- NA_character_
primary_p <- NA_real_
if (k == 2) {Two groups: Welch (primary), Student, Mann-Whitney, Cohen's d
l1 <- group_levels[1]; l2 <- group_levels[2]
x1 <- y[gf == l1]; x2 <- y[gf == l2]
n1 <- length(x1); n2 <- length(x2)
s1 <- stats::sd(x1); s2 <- stats::sd(x2)
sp <- sqrt(((n1 - 1) * s1^2 + (n2 - 1) * s2^2) / (n1 + n2 - 2))
cohens_d <- if (!is.na(sp) && sp > 0) (mean(x1) - mean(x2)) / sp else NA_real_
higher <- if (mean(x1) >= mean(x2)) l1 else l2
welch <- tryCatch(stats::t.test(x1, x2), error = function(e) NULL)
student <- tryCatch(stats::t.test(x1, x2, var.equal = TRUE),
error = function(e) NULL)
mw <- tryCatch(suppressWarnings(stats::wilcox.test(x1, x2)),
error = function(e) NULL)
rank_biserial <- if (!is.null(mw)) 2 * unname(mw$statistic) / (n1 * n2) - 1 else NA_real_
if (!is.null(welch)) {
add_test("Welch t-test(unequal variances)", unname(welch$statistic),
welch$p.value, cohens_d,
sprintf("Difference in mean %s is %s; %s effect(Cohen's d), %s mean is higher.",
outcome_h, sig_phrase(welch$p.value), d_word(cohens_d), higher))
}
if (!is.null(student)) {
add_test("Student t-test(equal variances)", unname(student$statistic),
student$p.value, cohens_d,
sprintf("Classic t-test cross-check: %s.", sig_phrase(student$p.value)))
}
if (!is.null(mw)) {
add_test("Mann-Whitney U", unname(mw$statistic), mw$p.value, rank_biserial,
sprintf("Rank-based(no normality assumption): %s; rank-biserial r = %s.",
sig_phrase(mw$p.value),
ifelse(is.na(rank_biserial), "NA", round(rank_biserial, 3))))
}
if (!is.na(shapiro_p)) {
add_test("Shapiro-Wilk normality(residuals)", NA_real_, shapiro_p, NA_real_,
if (shapiro_p < 0.01)
"Normality clearly violated — favor the rank-based result."
else "No strong evidence against normality.")
}Pairwise table = the single Welch comparison row (ALWAYS has data)
dmean <- mean(x1) - mean(x2)
if (!is.null(welch)) {
ci <- as.numeric(welch$conf.int)
pw_p <- welch$p.value
} else if (!is.null(student)) {
ci <- as.numeric(student$conf.int)
pw_p <- student$p.value
} else {
ci <- c(dmean, dmean)
pw_p <- NA_real_
}
pairwise_df <- data.frame(
comparison = paste0(l1, " - ", l2),
difference = round(dmean, 3),
ci_low = round(ci[1], 3),
ci_high = round(ci[2], 3),
adj_p = signif(pw_p, 3),
significant = ifelse(is.na(pw_p), "", ifelse(pw_p < 0.05, "yes", "no")),
stringsAsFactors = FALSE
)
effect_value <- cohens_d
effect_text <- sprintf("Cohen's d = %s (%s effect)",
ifelse(is.na(cohens_d), "NA", round(cohens_d, 2)),
d_word(cohens_d))
if (nonparam_preferred && !is.null(mw)) {
primary_test <- "Mann-Whitney U"; primary_p <- mw$p.value
} else if (!is.null(welch)) {
primary_test <- "Welch t-test"; primary_p <- welch$p.value
} else if (!is.null(student)) {
primary_test <- "Student t-test"; primary_p <- student$p.value
} else if (!is.null(mw)) {
primary_test <- "Mann-Whitney U"; primary_p <- mw$p.value
} else {
stop(sprintf("No comparison test could be computed for '%s' between the two %s groups (the data may be essentially constant).",
outcome_h, group_h))
}
} else {3+ groups: ANOVA, Welch ANOVA, Kruskal-Wallis, eta², Tukey HSD
a_tab <- summary(fit)[[1]]
ss_b <- a_tab[["Sum Sq"]][1]
ss_w <- a_tab[["Sum Sq"]][2]
eta2 <- if (!is.na(ss_b) && !is.na(ss_w) && (ss_b + ss_w) > 0)
ss_b / (ss_b + ss_w) else NA_real_
f_stat <- a_tab[["F value"]][1]
anova_p <- a_tab[["Pr(>F)"]][1]
welch_a <- tryCatch(stats::oneway.test(y ~ gf, data = dat),
error = function(e) NULL)
kw <- tryCatch(stats::kruskal.test(y ~ gf, data = dat),
error = function(e) NULL)
if (!is.na(anova_p)) {
add_test("One-way ANOVA", f_stat, anova_p, eta2,
sprintf("Mean %s differs across %s groups: %s; eta-squared = %s(%s effect).",
outcome_h, group_h, sig_phrase(anova_p),
ifelse(is.na(eta2), "NA", round(eta2, 3)), eta_word(eta2)))
}
if (!is.null(welch_a)) {
add_test("Welch ANOVA(unequal variances)", unname(welch_a$statistic),
welch_a$p.value, eta2,
sprintf("Variance-robust cross-check: %s.", sig_phrase(welch_a$p.value)))
}
if (!is.null(kw)) {
add_test("Kruskal-Wallis", unname(kw$statistic), kw$p.value, NA_real_,
sprintf("Rank-based(no normality assumption): %s.", sig_phrase(kw$p.value)))
}
if (!is.na(shapiro_p)) {
add_test("Shapiro-Wilk normality(residuals)", NA_real_, shapiro_p, NA_real_,
if (shapiro_p < 0.01)
"Normality clearly violated — favor the rank-based result."
else "No strong evidence against normality.")
}
tk <- tryCatch(stats::TukeyHSD(fit)$gf, error = function(e) NULL)
if (!is.null(tk) && nrow(tk) > 0) {
pairwise_df <- data.frame(
comparison = rownames(tk),
difference = round(as.numeric(tk[, "diff"]), 3),
ci_low = round(as.numeric(tk[, "lwr"]), 3),
ci_high = round(as.numeric(tk[, "upr"]), 3),
adj_p = signif(as.numeric(tk[, "p adj"]), 3),
stringsAsFactors = FALSE
)
pairwise_df$significant <- ifelse(is.na(pairwise_df$adj_p), "",
ifelse(pairwise_df$adj_p < 0.05, "yes", "no"))
ok <- !is.na(pairwise_df$difference)
pairwise_df <- pairwise_df[ok, , drop = FALSE]
pairwise_df <- pairwise_df[order(-abs(pairwise_df$difference)), , drop = FALSE]
pairwise_df <- head(pairwise_df, 15)
rownames(pairwise_df) <- NULL
} else {
pairwise_df <- data.frame(comparison = character(0), difference = numeric(0),
ci_low = numeric(0), ci_high = numeric(0),
adj_p = numeric(0), significant = character(0),
stringsAsFactors = FALSE)
}
effect_value <- eta2
effect_text <- sprintf("eta-squared = %s(%s effect — share of variation in %s explained by %s)",
ifelse(is.na(eta2), "NA", round(eta2, 3)),
eta_word(eta2), outcome_h, group_h)
if (nonparam_preferred && !is.null(kw)) {
primary_test <- "Kruskal-Wallis"; primary_p <- kw$p.value
} else if (!is.null(welch_a) && !is.na(welch_a$p.value)) {
primary_test <- "Welch ANOVA"; primary_p <- welch_a$p.value
} else if (!is.na(anova_p)) {
primary_test <- "One-way ANOVA"; primary_p <- anova_p
} else if (!is.null(kw)) {
primary_test <- "Kruskal-Wallis"; primary_p <- kw$p.value
} else {
stop(sprintf("No comparison test could be computed for '%s' across the %s groups (the data may be essentially constant).",
outcome_h, group_h))
}
}
test_results_df <- do.call(rbind, test_rows)
rownames(test_results_df) <- NULL
primary_sig <- !is.na(primary_p) && primary_p < 0.05Top significant pairwise difference (NA-safe — never which.max over NAs)
top_sig_pair <- NULL
if (nrow(pairwise_df) > 0) {
sig_rows <- pairwise_df[!is.na(pairwise_df$difference) &
pairwise_df$significant == "yes", , drop = FALSE]
if (nrow(sig_rows) > 0) top_sig_pair <- sig_rows[1, ] # already |diff|-sorted
}
n_sig_pairs <- sum(pairwise_df$significant == "yes", na.rm = TRUE)Step 7: Boxplot dataset — <= 2000 sampled rows
set.seed(42)
sidx <- if (final_rows > 2000) sample(final_rows, 2000) else seq_len(final_rows)
boxplot_df <- data.frame(
group_name = as.character(gf[sidx]),
outcome_value = y[sidx],
stringsAsFactors = FALSE
)
metrics <- list(
`Observations` = final_rows,
`Groups Compared` = k,
`Primary Test` = primary_test,
`Primary p-value` = signif(primary_p, 3),
`Effect Size` = if (is.na(effect_value)) NA_real_ else round(effect_value, 3),
`Significant Pairs` = as.integer(n_sig_pairs)
)
json_output <- list(
answer = paste0(
"Compared ", outcome_h, " across ", k, " ", group_h, " groups(",
format(final_rows, big.mark = ","), " rows): the ", primary_test,
" finds the difference ", sig_phrase(primary_p), "; ", effect_text, ". ",
if (!is.null(top_sig_pair)) paste0(
"Largest significant gap: ", top_sig_pair$comparison, " (difference = ",
top_sig_pair$difference, ", adjusted p = ", top_sig_pair$adj_p, "); ")
else "No individual pair reaches significance; ",
n_sig_pairs, " of ", nrow(pairwise_df), " pairwise comparison(s) significant at p<0.05."
),
cards = lapply(
c("tldr", "overview", "preprocessing", "group_boxplot",
"group_summary", "test_results", "pairwise_differences"),
function(cid) list(id = cid, metrics = metrics)
)
)
list(
initial_rows = initial_rows, final_rows = final_rows,
rows_removed = rows_removed, n_na_outcome = n_na_outcome,
outcome_h = outcome_h, group_h = group_h,
k = k, group_levels = group_levels,
dropped_groups_df = dropped_groups_df, lumped_levels = lumped_levels,
group_summary_df = group_summary_df,
test_results_df = test_results_df,
pairwise_df = pairwise_df,
boxplot_df = boxplot_df,
shapiro_p = shapiro_p, out_frac = out_frac,
nonparam_preferred = nonparam_preferred,
primary_test = primary_test, primary_p = primary_p,
primary_sig = primary_sig,
effect_value = effect_value, effect_text = effect_text,
top_sig_pair = top_sig_pair, n_sig_pairs = n_sig_pairs,
metrics = metrics, json_output = json_output
)
}