Executive Summary
Did extra sleep drug2 change from extra sleep drug1?
Drug2 delivers genuinely more sleep than drug1: patients gained 1.58 extra hours on average (95% CI 0.70 to 2.46), a large effect (Cohen's dz = 1.28). The Wilcoxon signed-rank test confirms this difference is real at p = 0.004. Across the 10 pairs, 90% increased and 0% decreased, so the direction is consistent. The rank-based test is the recommended verdict because the paired differences fail the normality check.
Analysis Overview
Paired comparison of extra sleep drug1 and extra sleep drug2 across 10 matched pairs.
This analysis compares how much extra sleep patients gain from drug2 versus drug1 by measuring the change within each of the 10 matched pairs. Because both drugs were measured on the same subjects, the analysis works on the within-pair difference, which cancels out stable person-to-person variation and makes the test more powerful. The two measurements correlate at r = 0.80, so pairing removes a substantial share of individual variation and sharpens detection of a real drug effect.
Data Quality
Pair completeness and the normality check on the differences.
All 10 rows loaded formed complete pairs, so no data was dropped. A Shapiro-Wilk test on the paired differences rejects normality (p = 0.033), meaning the differences do not follow a normal distribution. This failure of the normality check makes the rank-based Wilcoxon signed-rank test the safer headline, even though the parametric t-test also reached significance.
Before vs After
Every pair's extra sleep drug1 plotted against its extra sleep drug2.
Nine of the 10 pairs sit above the no-change diagonal, showing a tight upward pattern. The two measurements track closely within each subject (r = 0.80), confirming that pairing was effective. One pair sits exactly on the diagonal (no change), while the other nine all shifted upward, with the largest gain at 4.6 hours. This consistent upward movement supports a real drug effect.
Distribution of Changes
How much each pair changed between extra sleep drug1 and extra sleep drug2.
The per-pair changes center at 1.58 with a standard deviation of 1.23, and the bulk of the distribution sits to the right of zero. Most changes cluster between 0.8 and 2.4 hours, with one outlier at 4.6 hours. The concentration to the right of zero confirms a real shift toward more sleep under drug2, though the spread shows variability in how much individual patients benefit.
Statistical Tests
Paired t-test, Wilcoxon signed-rank, effect size, and the normality check.
| Test | Statistic | P Value | Effect Or CI | Interpretation |
|---|---|---|---|---|
| Paired t-test | 4.062 | 0.003 | mean difference 1.58 (95% CI 0.70 to 2.46) | Tests whether the mean change in extra sleep drug2 differs from zero: significant (p = 0.003). |
| Wilcoxon signed-rank | 54 | 0.004 | median difference 1.30 | Rank-based, no normality assumption: significant (p = 0.004). |
| Cohen's dz | 1.285 | n/a | large effect | Standardized paired effect size: dz = 1.28. |
| Shapiro-Wilk (differences) | — | 0.033 | normality rejected | The paired differences are not normally distributed — favor the Wilcoxon result. |
Both the paired t-test (p = 0.003, mean difference 1.58, 95% CI 0.70 to 2.46) and the Wilcoxon signed-rank test (p = 0.004, median difference 1.30) reach significance and agree on direction. Cohen's dz = 1.285 marks a large standardized effect. The Shapiro-Wilk test rejects normality of the differences (p = 0.033), so the Wilcoxon signed-rank result is the headline. The agreement between both tests strengthens confidence that the difference is real and not an artifact of distributional assumptions.
Paired Comparison — Before vs After
Did a paired or repeated measurement change? Each row is one subject measured twice — before vs after, treatment vs control on the same unit, two raters on the same items. The analysis works on the within-pair change (after minus before) and runs the whole standard paired toolkit at once: a paired t-test with a confidence interval on the mean change, a Wilcoxon signed-rank cross-check, Cohen's dz effect size, and a normality check on the differences that decides which result is the safer headline.
Why This Method?
When the same unit is measured twice, a two-sample test wastes power on the stable person-to-person differences. Working on each subject's own change cancels that variation out, so a paired test detects a real shift with far fewer observations. Running the parametric and rank-based versions together, with a normality check on the differences, lets the report itself say which result to trust.
What This Analysis Covers
- Before against after per pair (scatter with a no-change diagonal)
- The distribution of per-pair changes (histogram)
- The paired t-test, Wilcoxon signed-rank, effect size, and normality check
Standard Library
Platform standard-library module (LAT-1441): runs on ANY dataset via the semantic mapping {before, after}. 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
# n / n_dropped $ complete pairs used + incomplete rows dropped
# before_h / after_h $ humanized user names for the two mapped columns
# mean_d / sd_d / median_d / ci_low / ci_high $ change statistics
# paired_r $ within-subject correlation of before vs after
# t_stat / t_p $ paired t-test statistic + p
# w_V / w_p $ Wilcoxon signed-rank statistic + p
# dz $ Cohen's dz (mean(d)/sd(d))
# shapiro_p / nonparam_preferred $ normality of differences + headline choice
# pct_inc/pct_dec/pct_unch $ share of pairs up/down/flat
# dir_word $ direction language consistent with sign of mean_d
# primary_test/primary_p/primary_sig $ the headline result
# test_results_df $ test, statistic, p_value, effect_or_ci, interpretation
# paired_points_df $ before_value, after_value (<= 1000 sampled)
# difference_dist_df $ difference (<= 2000 sampled)
# metrics / json_output
# === /SHARED EXPORTS ===Step 1: Resolve mapped columns (humanized for all prose)
initial_rows <- nrow(df)
before_h <- humanize_semantic("before", col_map)
after_h <- humanize_semantic("after", col_map)
if (!("before" %in% names(df)) || !("after" %in% names(df))) {
stop(sprintf("Paired comparison needs both '%s' (the before measurement) and '%s' (the after measurement) mapped.",
before_h, after_h))
}Step 2: Coerce both measurements to numeric (95% rule)
coerce_num <- function(v, label_h) {
if (is.numeric(v)) return(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 column '%s' does not look numeric — fewer than 95%% of its values parse as numbers. Pick a numeric column.",
label_h))
}
conv
}
b <- coerce_num(df$before, before_h)
a <- coerce_num(df$after, after_h)Step 3: Keep row-wise complete pairs; drop incomplete rows (reported)
keep <- !is.na(b) & !is.na(a)
n_dropped <- sum(!keep)
b <- b[keep]; a <- a[keep]
n <- length(b)
final_rows <- n
rows_removed <- initial_rows - final_rows
if (n < 6) {
stop(sprintf("Only %d complete %s / %s pairs remained after dropping incomplete rows — at least 6 are needed for a paired comparison.",
n, before_h, after_h))
}Step 4: The within-pair change and its summary statistics
d <- a - b
mean_d <- mean(d)
sd_d <- stats::sd(d)
median_d <- stats::median(d)
paired_r <- suppressWarnings(tryCatch(stats::cor(b, a), error = function(e) NA_real_))
if (is.na(sd_d)) sd_d <- 0Step 5: Paired t-test (mean change + 95% CI)
tt <- tryCatch(stats::t.test(a, b, paired = TRUE), error = function(e) NULL)
if (!is.null(tt)) {
t_stat <- unname(tt$statistic)
t_p <- tt$p.value
ci <- as.numeric(tt$conf.int)
ci_low <- ci[1]; ci_high <- ci[2]
} else {
t_stat <- NA_real_; t_p <- NA_real_; ci_low <- mean_d; ci_high <- mean_d
}Step 6: Wilcoxon signed-rank (rank-based, no normality assumption)
ww <- tryCatch(suppressWarnings(stats::wilcox.test(a, b, paired = TRUE)),
error = function(e) NULL)
w_V <- if (!is.null(ww)) unname(ww$statistic) else NA_real_
w_p <- if (!is.null(ww)) ww$p.value else NA_real_Step 7: Cohen's dz + normality of the differences
dz <- if (!is.na(sd_d) && sd_d > 0) mean_d / sd_d else NA_real_
shapiro_p <- NA_real_
if (n >= 3 && n <= 5000) {
shapiro_p <- tryCatch(stats::shapiro.test(d)$p.value, error = function(e) NA_real_)
}
nonparam_preferred <- !is.na(shapiro_p) && shapiro_p < 0.05Step 8: Direction language + share of pairs moving each way
n_inc <- sum(d > 0); n_dec <- sum(d < 0); n_unch <- sum(d == 0)
pct_inc <- 100 * n_inc / n
pct_dec <- 100 * n_dec / n
pct_unch <- 100 * n_unch / n
dir_word <- if (mean_d > 0) "increased" else if (mean_d < 0) "decreased" else "stayed flat"
primary_test <- if (nonparam_preferred) "Wilcoxon signed-rank" else "Paired t-test"
primary_p <- if (nonparam_preferred) w_p else t_p
primary_sig <- !is.na(primary_p) && primary_p < 0.05Step 9: Test-results table (paired t, Wilcoxon, Cohen's dz, normality)
tr_rows <- list()
add_test <- function(test, statistic, p_value, effect_or_ci, interpretation) {
tr_rows[[length(tr_rows) + 1]] <<- data.frame(
test = test,
statistic = if (is.na(statistic)) NA_real_ else round(statistic, 3),
p_value = fmt_p(p_value),
effect_or_ci = effect_or_ci,
interpretation = interpretation,
stringsAsFactors = FALSE
)
}
add_test("Paired t-test", t_stat, t_p,
sprintf("mean difference %s(95%% CI %s to %s)", r2(mean_d), r2(ci_low), r2(ci_high)),
sprintf("Tests whether the mean change in %s differs from zero: %s.", after_h, sig_phrase(t_p)))
add_test("Wilcoxon signed-rank", w_V, w_p,
sprintf("median difference %s", r2(median_d)),
sprintf("Rank-based, no normality assumption: %s.", sig_phrase(w_p)))
add_test("Cohen's dz", dz, NA_real_,
sprintf("%s effect", dz_word(dz)),
sprintf("Standardized paired effect size: dz = %s.", r2(dz)))
if (!is.na(shapiro_p)) {
add_test("Shapiro-Wilk(differences)", NA_real_, shapiro_p,
if (shapiro_p < 0.05) "normality rejected" else "normality plausible",
if (shapiro_p < 0.05)
"The paired differences are not normally distributed — favor the Wilcoxon result."
else
"No strong evidence against normal differences — the paired t-test is reliable.")
}
test_results_df <- do.call(rbind, tr_rows)
rownames(test_results_df) <- NULLStep 10: Chart datasets — sampled with a fixed seed
set.seed(42)
sidx <- if (n > 1000) sample(n, 1000) else seq_len(n)
paired_points_df <- data.frame(before_value = b[sidx], after_value = a[sidx],
stringsAsFactors = FALSE)
paired_points_df <- paired_points_df[order(paired_points_df$before_value), , drop = FALSE]
rownames(paired_points_df) <- NULL
set.seed(42)
didx <- if (n > 2000) sample(n, 2000) else seq_len(n)
difference_dist_df <- data.frame(difference = round(d[didx], 4), stringsAsFactors = FALSE)
metrics <- list(
`Pairs Analysed` = n,
`Mean Difference` = round(mean_d, 3),
`Cohen's dz` = if (is.na(dz)) NA_real_ else round(dz, 3),
`Headline Test` = primary_test,
`Headline p-value` = fmt_p(primary_p),
`Pairs Increased` = paste0(r2(pct_inc), "%")
)
json_output <- list(
answer = paste0(
"Paired comparison of ", after_h, " versus ", before_h, " across ",
format(n, big.mark = ","), " complete pairs: the mean change(", after_h,
" minus ", before_h, ") is ", r2(mean_d), " (95% CI ", r2(ci_low), " to ",
r2(ci_high), "). The ", primary_test, " finds this change ", sig_phrase(primary_p),
" (Cohen's dz = ", r2(dz), ", ", dz_word(dz), " effect); ",
r2(pct_inc), "% of pairs increased and ", r2(pct_dec), "% decreased."
),
cards = lapply(
c("tldr", "overview", "preprocessing", "paired_change",
"difference_distribution", "test_results"),
function(cid) list(id = cid, metrics = metrics)
)
)
list(
initial_rows = initial_rows, final_rows = final_rows,
rows_removed = rows_removed, n = n, n_dropped = n_dropped,
before_h = before_h, after_h = after_h,
mean_d = mean_d, sd_d = sd_d, median_d = median_d,
ci_low = ci_low, ci_high = ci_high, paired_r = paired_r,
t_stat = t_stat, t_p = t_p, w_V = w_V, w_p = w_p, dz = dz,
shapiro_p = shapiro_p, nonparam_preferred = nonparam_preferred,
pct_inc = pct_inc, pct_dec = pct_dec, pct_unch = pct_unch,
dir_word = dir_word,
primary_test = primary_test, primary_p = primary_p, primary_sig = primary_sig,
test_results_df = test_results_df,
paired_points_df = paired_points_df, difference_dist_df = difference_dist_df,
metrics = metrics, json_output = json_output
)
}