Standard Paired Comparison
Executive Summary

Executive Summary

Did Post Score change from Pre Score?

Pairs Analysed
80
Mean Difference
5.05
Cohen's dz
1.848
Headline Test
Paired t-test
Headline p-value
< 0.001
Pairs Increased
98.75%
Post Score genuinely increased from Pre Score (Paired t-test, p < 0.001). The mean change (Post Score minus Pre Score) is 5.05 (95% CI 4.44 to 5.66), a large effect (Cohen's dz = 1.85). Across the pairs, 98.75% increased and 1.25% decreased. The differences are close enough to normal that the paired t-test is the headline.
Interpretation

Yes, Post Score increased genuinely from Pre Score (Paired t-test, p < 0.001). The mean change is 5.05 units (95% CI 4.44 to 5.66), a large effect with Cohen's dz = 1.848. Of the 80 pairs, 98.75% increased and 1.25% decreased. The differences are close enough to normal that the paired t-test is the safe headline. The answer to "did it change?" is unambiguous: a real, large, and consistent increase.

Overview

Analysis Overview

Paired comparison of Pre Score and Post Score across 80 matched pairs.

N Pairs80
Mean Difference5.05
Cohens Dz1.848
Interpretation

Post Score increased substantially from Pre Score across 80 matched pairs. The paired design isolates within-subject change by canceling stable person-to-person variation—a key advantage here because the two measurements correlate strongly at r = 0.96, removing much of the noise that would cloud an independent-groups comparison. The mean change of 5.05 units is large relative to typical variation, and the standardized effect size (Cohen's dz = 1.848) confirms a substantial shift. By working on paired differences rather than raw scores, the test gains power to detect real change without being confounded by baseline differences between subjects.

Data Preparation

Data Quality

Pair completeness and the normality check on the differences.

Initial Rows80
Final Rows80
Rows Removed0
Interpretation

All 80 rows loaded formed complete pairs, with 0 rows removed. Data quality was high: both measurements were coerced to numeric form before pairing. The Shapiro-Wilk normality test on the paired differences yielded p = 0.168, indicating no strong departure from normality. This result supports the use of the parametric paired t-test as the primary headline, since the differences are approximately normally distributed and the paired t-test is robust under this condition.

Visualization

Before vs After

Every pair's Pre Score plotted against its Post Score.

Interpretation

98.75% of the 80 pairs sit above the no-change diagonal, showing that the typical movement is upward. The high correlation (r = 0.96) between Pre Score and Post Score demonstrates tight tracking within each subject—individuals who started low tended to stay low, and those who started high stayed high, but nearly all moved up. This strong alignment along the diagonal is why pairing is effective: it removes the large stable differences between subjects and focuses the test on the common upward shift.

Visualization

Distribution of Changes

How much each pair changed between Pre Score and Post Score.

Interpretation

Per-pair changes are centered at 5.05 with a standard deviation of 2.73. The distribution sits predominantly to the right of zero, with only one pair showing a decrease (difference = −0.05). The bulk of changes range from roughly 1 to 8 units, with a few larger jumps (e.g., 12.01, 11.96, 11.06). This concentration of positive changes away from zero, combined with the tight standard deviation relative to the mean, confirms the shift is both real and reasonably consistent across pairs rather than driven by a few outliers.

Data Table

Statistical Tests

Paired t-test, Wilcoxon signed-rank, effect size, and the normality check.

TestStatisticP ValueEffect Or CIInterpretation
Paired t-test16.53< 0.001mean difference 5.05 (95% CI 4.44 to 5.66)Tests whether the mean change in Post Score differs from zero: highly significant (p < 0.001).
Wilcoxon signed-rank3239< 0.001median difference 5.17Rank-based, no normality assumption: highly significant (p < 0.001).
Cohen's dz1.848n/alarge effectStandardized paired effect size: dz = 1.85.
Shapiro-Wilk (differences)0.168normality plausibleNo strong evidence against normal differences — the paired t-test is reliable.
Interpretation

The paired t-test (statistic = 16.53, p < 0.001) and Wilcoxon signed-rank test (statistic = 3239, p < 0.001) agree, strengthening confidence in the verdict. The paired t-test estimates mean difference of 5.05 (95% CI 4.44 to 5.66); the Wilcoxon median difference is 5.17. Cohen's dz = 1.848 marks a large standardized effect. The Shapiro-Wilk test (p = 0.168) shows no strong evidence against normality of the differences, making the paired t-test the recommended headline. Parametric and rank-based tests converge, a robust confirmation that Post Score genuinely increased.

Methodology

Methodology

Statistical methodology and diagnostics for Paired Comparison — Before vs After

Statistical Method

Paired Comparison — Before vs After

Standard-library analysis: did a paired or repeated measurement actually change? Map a before column and an after column measured on the same subject — before vs after, treatment vs control on the same unit, two raters on the same items — and get the paired t-test with a confidence interval on the mean change, a Wilcoxon signed-rank cross-check, Cohen's dz effect size, a normality check that picks the safer headline, and the share of pairs that moved up or down.

Data
N = 80 observations
Key Results
Pairs Analysed 80
Mean Difference 5.0500
Cohen'S Dz 1.8480
Headline Test Paired t-test
Headline P-Value < 0.001
Pairs Increased 98.75%
Assumptions
  • Each row is one subject measured twice: a before value and an after value on the same unit
  • Both measurements are numeric or cleanly convertible
  • The paired differences are approximately normal for the paired t-test — the Wilcoxon signed-rank test is reported as a rank-based cross-check
Limitations
  • Rows missing either the before or the after value are dropped as incomplete pairs
  • At least 6 complete pairs are required to run the comparison
  • A significant change says the measurement moved, not why — it does not rule out other causes acting between the two measurements
  • Very few pairs have little power; a non-significant result is not proof of no change
Software & Citation
MCP Analytics · mcpanalytics.ai
Code Appendix

Analysis Code

Complete R source code for this analysis

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 &#x27;%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 &#x27;%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 <- 0

Step 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.05

Step 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.05

Step 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&#x27;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) <- NULL

Step 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&#x27;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&#x27;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
  )
}
Your data has more stories to tell. Run any analysis on your own data — 60+ validated R modules, interactive reports, AI insights, and PDF export. 500 free credits on signup.
Try Free — No Signup Sign Up Free

Report an Issue

Tell us what's wrong. You'll get a free re-run of this analysis so you can try again with different parameters. If the re-run still doesn't meet your expectations, we'll refund your credits.

Want to run this analysis on your own data? Upload CSV — Free Analysis See Pricing