Standard Proportions
Executive Summary

Executive Summary

Did the yes rate really differ across Checkout Version?

Observations
219
Rate
New Checkout 19.1% vs Old Checkout 12.8%
Difference (pp)
6.25
P-value
0.207
Verdict
No significant difference
Test Used
two-sample proportion z-test
The data does not show a statistically significant difference in yes rates between the two Checkout Version groups: New Checkout runs at 19.1% vs Old Checkout at 12.8% (two-sample proportion z-test p = 0.207). The Newcombe 95% interval on the difference runs -3.57 to 15.99 percentage points. No significant difference is NOT proof the rates are equal — it means the data cannot distinguish the observed gap of 6.2 percentage points from sampling noise. The Newcombe 95% interval on the difference (-3.57 to 15.99 percentage points) shows the range of true differences still compatible with this data.
Suggested Interpretation

The short answer

No. The new checkout's 19.1% conversion rate is 6.25 percentage points higher than the old checkout's 12.8%, but the p-value of 0.207 indicates this difference could easily arise from random sampling variation. The data do not establish a genuine difference.

The detail

New Checkout: 19.1%; Old Checkout: 12.8%; difference: 6.25 percentage points (p = 0.207, two-sample proportion z-test). The Newcombe 95% confidence interval on the difference spans -3.57 to 15.99 percentage points—wide enough to include zero, consistent with no true difference. The continuity-corrected chi-square cross-check yielded p = 0.281, in agreement. With 219 observations across two groups, the data lack sufficient power to distinguish this observed gap from chance.

What this can't tell you

Non-significance is not proof of equality. The true difference could plausibly range from -3.57 to 15.99 percentage points. A larger sample might detect a genuine effect of this magnitude, or the true effect might be smaller than the observed gap.

Overview

Analysis Overview

Comparison of yes rates in Converted between the Checkout Version groups New Checkout and Old Checkout (219 observations).

N Observations219
N Groups2
Difference Pp6.25
Suggested Interpretation

The short answer

The analysis compares conversion rates between two checkout versions using confidence intervals and a formal statistical test. The New Checkout shows a 6.25 percentage-point higher rate than the Old Checkout, but this difference falls within the range that sampling variation alone could produce.

The detail

This comparison uses Wilson 95% confidence intervals for each group's rate, a Newcombe hybrid interval for the difference, and a two-sample proportion z-test to determine whether the gap is statistically significant. The smallest cell count in the 2×2 contingency table is 14, well above the minimum of 5 required for the z-test to be valid. 'Yes' in the Converted field was treated as success. The observed difference of 6.25 percentage points carries a Newcombe 95% interval of -3.57 to 15.99 percentage points, spanning both negative and positive values.

What this can't tell you

Statistical non-significance does not prove the rates are equal—it means the data cannot rule out sampling noise as the explanation for the observed gap. The Newcombe interval shows which true differences remain plausible given this sample size and observed rates.

Data Preparation

Data Quality

Row cleaning, group filtering, and how the binary outcome was read.

Initial Rows219
Final Rows219
Rows Removed0
Groups Dropped0
Suggested Interpretation

The short answer

All 219 rows were usable; none were dropped for missing values. Both Converted and Checkout Version columns were complete. The outcome was read as binary: 'yes' counted as success, 'no' as non-success. The data contained exactly two Checkout Version groups meeting the minimum 3-row threshold.

The detail

Initial and final row counts were both 219; rows_removed = 0; groups_dropped = 0. New Checkout comprised n=110 observations; Old Checkout comprised n=109. The binary outcome 'Converted' was standardized: 'yes' is a recognized affirmative value (like 1/TRUE/yes), so it was treated as the success level. No rows were filtered for missing values in either column.

What this can't tell you

This report does not address missing data patterns or whether missingness correlates with group assignment—the data supplied contained no missing values.

Visualization

Rates with Confidence Intervals

Per-group yes rates with Wilson 95% confidence intervals.

Suggested Interpretation

The short answer

The New Checkout rate of 19.1% sits visibly above the Old Checkout rate of 12.8%, but their confidence intervals overlap substantially. This overlap is consistent with the formal test result: no statistically significant difference.

The detail

New Checkout: 19.1% yes (Wilson 95% CI: 12.8% to 27.4%). Old Checkout: 12.8% yes (Wilson 95% CI: 7.8% to 20.4%). The intervals overlap from 12.8% to 20.4%, meaning both rates are compatible with a common underlying true rate within that range. However, overlapping intervals do not themselves determine statistical significance—the formal verdict comes from the two-sample proportion z-test (p = 0.207), which accounts for both the gap and the within-group variability.

What this can't tell you

Visual overlap of confidence intervals is a rough guide only. The formal test is required to distinguish a true difference from sampling noise, and that test shows no significant difference here.

Data Table

Statistical Tests

z-test and chi-square cross-check on the yes rate difference.

TestStatisticP ValueInterpretation
Two-sample proportion z-test1.2610.207Tests whether the yes rate differs between New Checkout and Old Checkout. The observed gap is within the range chance alone could produce.
Chi-square test (continuity-corrected)1.160.281Cross-check on the same 2x2 table with the Yates continuity correction, which is slightly more conservative. The continuity-corrected chi-square cross-check agrees with the z-test verdict (p = 0.281 vs p = 0.207).
Suggested Interpretation

The short answer

Both the z-test (p = 0.207) and its continuity-corrected chi-square cross-check (p = 0.281) agree: no statistically significant difference between the two checkout versions. The z-test was valid because all cells of the 2×2 table were well above 5 (smallest: 14 conversions in Old Checkout).

The detail

Two-sample proportion z-test: z = 1.261, p = 0.207. Chi-square test (continuity-corrected): χ² = 1.16, p = 0.281. Both tests assess the same null hypothesis (yes rate is equal across groups) and both yield p > 0.05. The z-test requires every cell ≥ 5; the smallest cell here was 14, so the normal approximation holds and Fisher's exact test was not invoked. The z-test carries the verdict.

What this can't tell you

These tests confirm that the observed 6.25 percentage point gap is not statistically significant, but they do not prove the true rates are identical.

Data Table

Group Counts & Rates

n, successes, failures, and Wilson 95% CI per Checkout Version group.

GroupNSuccessesFailuresRate PCTCI Low PCTCI High PCT
New Checkout110218919.0912.8427.43
Old Checkout109149512.847.8120.41
Suggested Interpretation

The short answer

New Checkout recorded 21 conversions out of 110 attempts (19.1%); Old Checkout recorded 14 out of 109 (12.8%). The absolute difference is 6.25 percentage points. The two groups are nearly equal in size, so any imbalance is not a confound.

The detail

New Checkout: n = 110, successes = 21, failures = 89, rate = 19.09%, Wilson 95% CI [12.84%, 27.43%]. Old Checkout: n = 109, successes = 14, failures = 95, rate = 12.84%, Wilson 95% CI [7.81%, 20.41%]. Absolute difference = 6.25 percentage points. In relative terms, Old Checkout's rate is 32.7% lower than New Checkout's. Both groups exceed the minimum 3-row threshold by a wide margin.

What this can't tell you

These counts describe what was observed but do not establish whether the difference is real or due to chance—that verdict comes from the hypothesis test.

Data Table

Methods & Disclosure

How the rates, intervals, and verdict were computed.

ItemDetail
MethodTwo-sample comparison of proportions: z-test on the pooled standard error, continuity-corrected chi-square cross-check, Fisher's exact fallback for small cells
Success level'yes' in Converted counts as a success; 'no' as a non-success. 'yes' is a recognized affirmative value (like 1/TRUE/yes), so it was treated as the success level.
ComparisonDifference in yes rate: New Checkout minus Old Checkout = 6.25 percentage points
Per-group intervalsWilson score 95% confidence intervals (better small-sample and near-0/1 behavior than the naive Wald interval)
Difference intervalNewcombe hybrid 95% CI on the difference, built from the two Wilson intervals: -3.57 to 15.99 percentage points
Cell-count guardThe z-test requires every cell of the 2x2 table (successes and failures in each group) to be at least 5; the smallest cell here is 14, so the z-test is valid and used.
Test used for the verdicttwo-sample proportion z-test
Significance levelalpha = 0.05, two-sided
AssumptionsIndependent observations; each row is one trial; the two groups are independent samples
Suggested Interpretation

The short answer

Each group's rate is reported with a Wilson 95% confidence interval, chosen for stable behavior in small samples. The difference between rates carries a Newcombe hybrid interval. The verdict comes from a two-sample proportion z-test, with a continuity-corrected chi-square as a cross-check; the smallest cell count is 14, so both tests are valid.

The detail

Success was defined as 'yes' in Converted. Rates use Wilson score intervals rather than the naive Wald interval because Wilson intervals behave sensibly near 0% and 100% and in small samples. The difference interval (Newcombe hybrid) is -3.57 to 15.99 percentage points. The z-test uses a pooled standard error; the continuity-corrected chi-square applies the Yates correction. Alpha = 0.05, two-sided. Assumptions: independent observations; each row one trial; two independent samples. The observed gap of 6.2 percentage points falls within the range sampling noise alone could produce.

What this can't tell you

Non-significance does not prove equality. The Newcombe interval shows the range of true differences compatible with the data; a larger sample would narrow this range and might reveal a genuine effect if one exists.

Methodology

Methodology

Statistical methodology and diagnostics for Proportion Tests & Confidence Intervals

Statistical Method

Proportion Tests & Confidence Intervals

Standard-library analysis: did the conversion rate really differ between two groups? Map a binary outcome (0/1, yes/no, TRUE/FALSE, converted/not) and a two-level group column and get each group's rate with a Wilson 95% confidence interval, the two-sample proportion z-test with a continuity-corrected chi-square cross-check, the absolute difference with a Newcombe hybrid confidence interval, the relative change as computed, and an automatic Fisher's exact fallback — clearly disclosed — whenever any cell count is too small for the z-test.

Data
N = 219 observations
Assumptions
  • The outcome column is binary — exactly two distinct values (0/1, yes/no, TRUE/FALSE, converted/not)
  • The group column has exactly two usable levels; each row is one independent trial
  • The two groups are independent samples (not paired or repeated measurements of the same units)
Limitations
  • Exactly two groups are compared; use a many-group categorical test for 3+ groups
  • When the outcome levels are not standard yes/no tokens, the rarer level is assumed to be the event of interest and the report says so — check that assumption
  • Statistical significance is not practical importance: a tiny rate gap becomes significant with enough rows, and a real gap can be missed with too few
  • Paired or clustered designs (same users in both groups, repeated visits) violate the independence assumption and need different methods
Software & Citation
MCP Analytics · mcpanalytics.ai
Code Appendix

Analysis Code

Complete R source code for this analysis

Proportion Tests & Confidence Intervals — Did the Rate Really Differ?

Compares a binary outcome (converted / clicked / churned / passed) between exactly two groups: per-group proportions with Wilson 95% confidence intervals, a two-sample proportion z-test with a continuity-corrected chi-square cross-check, the absolute difference with a Newcombe hybrid confidence interval, and Fisher's exact test as an automatic fallback when any cell count is too small for the z-test.

Why This Method?

Rates from finite samples wobble. The z-test answers whether the observed gap between two proportions is bigger than chance alone would produce, and the Wilson/Newcombe intervals show how large or small the true rates and their difference could plausibly be — which is the decision-relevant part. Small cell counts silently break the normal approximation, so the tool checks the cells and switches to Fisher's exact test when needed.

What This Analysis Covers

  • Per-group conversion rates with Wilson 95% CIs (bar chart with error bars)
  • Two-sample proportion z-test + continuity-corrected chi-square cross-check
  • Absolute difference with a Newcombe hybrid 95% CI, relative change as computed
  • Fisher's exact fallback when any cell count is below 5, clearly disclosed

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/n_na_rows  $ row accounting
  #   outcome_h / group_h   $ humanized user names for the mapped columns
  #   l1 / l2               $ the two group levels (factor order)
  #   success_label / failure_label  $ outcome levels (original casing)
  #   success_flagged / success_note $ how the success level was chosen
  #   n1/n2, x1/x2, p1/p2   $ per-group n, successes, proportions
  #   w1 / w2               $ Wilson 95% CIs per group (low/high)
  #   d / d_low / d_high    $ absolute difference (l1 - l2) + Newcombe 95% CI
  #   rel_change / rel_ok   $ relative change of l2 vs l1 (only when p1 > 0)
  #   min_cell / z_valid    $ smallest cell count; z-test validity guard
  #   z_stat/p_z, chi_stat/chi_df/p_chi, p_fisher/fisher_or  $ the tests
  #   p_primary / test_used / significant / verdict_label    $ the verdict
  #   dropped_groups_df     $ groups dropped for n < 3
  #   rates_df / test_df / group_summary_df / methods_df  $ card datasets
  #   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("Proportion testing needs both &#x27;%s' (the binary outcome) and '%s' (the two groups) mapped.",
                 outcome_h, group_h))
  }

Step 2: Drop rows with a missing outcome or group; keep accounting

o_raw <- as.character(df$outcome)
  g_raw <- as.character(df$group)
  o_norm <- tolower(trimws(o_raw))
  g_clean <- trimws(g_raw)
  keep <- !is.na(o_raw) & o_norm != "" & o_norm != "na" &
          !is.na(g_raw) & g_clean != ""
  n_na_rows <- sum(!keep)
  o_raw  <- o_raw[keep];  o_norm <- o_norm[keep]
  g_clean <- g_clean[keep]
  if (length(o_norm) == 0) {
    stop(sprintf("No rows with usable values in both &#x27;%s' and '%s' remained after cleaning.",
                 outcome_h, group_h))
  }

Step 3: The outcome must be binary — detect the success level robustly

lev_norm <- sort(unique(o_norm))
  if (length(lev_norm) != 2) {
    stop(sprintf("The outcome column &#x27;%s' must be binary (exactly 2 distinct values such as 0/1, yes/no, TRUE/FALSE); it has %d distinct value(s): %s. Pick a two-valued column.",
                 outcome_h, length(lev_norm),
                 paste(head(lev_norm, 6), collapse = ", ")))
  }

Original-casing display labels (first occurrence per normalized level)

disp <- sapply(lev_norm, function(l) trimws(o_raw[match(l, o_norm)]))
  positive_tokens <- c("1", "true", "t", "yes", "y", "converted", "success",
                       "succeeded", "won", "pass", "passed", "clicked",
                       "purchased", "retained")
  negative_tokens <- c("0", "false", "f", "no", "n", "not converted",
                       "not_converted", "non-converted", "failure", "failed",
                       "lost", "fail", "none")
  in_pos <- lev_norm %in% positive_tokens
  in_neg <- lev_norm %in% negative_tokens
  success_flagged <- FALSE
  if (sum(in_pos) == 1) {
    success_norm <- lev_norm[in_pos]
    success_note <- sprintf("&#x27;%s' is a recognized affirmative value (like 1/TRUE/yes), so it was treated as the success level.",
                            disp[in_pos])
  } else if (sum(in_neg) == 1) {
    success_norm <- lev_norm[!in_neg]
    success_note <- sprintf("&#x27;%s' is a recognized negative value (like 0/FALSE/no), so the other level, '%s', was treated as the success level.",
                            disp[in_neg], disp[!in_neg])
  } else {

Ambiguous levels: prefer the RARER level as the event of interest, and flag it

cnt <- table(o_norm)[lev_norm]
    success_norm <- lev_norm[which.min(as.integer(cnt))]
    success_flagged <- TRUE
    success_note <- sprintf("Neither &#x27;%s' nor '%s' is a standard yes/no token, so the RARER level, '%s', was assumed to be the event of interest (the success level). If the other level is the one you care about, note that every rate below is simply 100%% minus the reported one.",
                            disp[1], disp[2], disp[lev_norm == success_norm])
  }
  success_label <- unname(disp[lev_norm == success_norm])
  failure_label <- unname(disp[lev_norm != success_norm])
  y <- as.integer(o_norm == success_norm)

Step 4: Clean the groups — drop n<3 (reported), require exactly 2 levels

tab <- table(g_clean)
  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_clean %in% small)
    g_clean <- g_clean[sel]
    y <- y[sel]
  }
  gf <- factor(g_clean)
  k  <- nlevels(gf)
  if (k < 2) {
    stop(sprintf("Comparing proportions needs exactly 2 groups in &#x27;%s' with 3 or more rows each; only %d usable group(s) remained after cleaning. Check that '%s' really splits the data into two groups.",
                 group_h, k, group_h))
  }
  if (k > 2) {
    stop(sprintf("This tool compares exactly two groups, but &#x27;%s' has %d usable levels (%s). Filter the data to two groups, or use a many-group categorical test instead.",
                 group_h, k, paste(levels(gf), collapse = ", ")))
  }
  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 &#x27;%s' rates across '%s'.",
                 final_rows, outcome_h, group_h))
  }
  if (sum(y) == 0 || sum(y) == final_rows) {
    stop(sprintf("Every usable row has the same &#x27;%s' outcome ('%s') — with no variation, no rate difference can be tested.",
                 outcome_h, if (sum(y) == 0) failure_label else success_label))
  }

Step 5: Per-group proportions + Wilson 95% CIs

l1 <- levels(gf)[1]; l2 <- levels(gf)[2]
  y1 <- y[gf == l1]; y2 <- y[gf == l2]
  n1 <- length(y1); n2 <- length(y2)
  x1 <- sum(y1); x2 <- sum(y2)
  p1 <- x1 / n1; p2 <- x2 / n2
  w1 <- wilson_ci(x1, n1)
  w2 <- wilson_ci(x2, n2)

Step 6: Absolute difference (l1 - l2) with the Newcombe hybrid 95% CI

d <- p1 - p2
  d_low  <- d - sqrt((p1 - w1["low"])^2  + (w2["high"] - p2)^2)
  d_high <- d + sqrt((w1["high"] - p1)^2 + (p2 - w2["low"])^2)
  d_low <- unname(max(-1, d_low)); d_high <- unname(min(1, d_high))

Relative change of l2 vs the l1 baseline — stated only when computable

rel_ok <- p1 > 0
  rel_change <- if (rel_ok) (p2 - p1) / p1 else NA_real_

Step 7: Cell-count guard, z-test, chi-square cross-check, Fisher fallback

cells <- c(x1, n1 - x1, x2, n2 - x2)
  min_cell <- min(cells)
  z_valid <- min_cell >= 5

  p_pool <- (x1 + x2) / (n1 + n2)
  se0 <- sqrt(p_pool * (1 - p_pool) * (1 / n1 + 1 / n2))
  z_stat <- if (se0 > 0) d / se0 else NA_real_
  p_z <- if (!is.na(z_stat)) 2 * stats::pnorm(-abs(z_stat)) else NA_real_

  ct <- tryCatch(
    suppressWarnings(stats::prop.test(c(x1, x2), c(n1, n2), correct = TRUE)),
    error = function(e) NULL)
  chi_stat <- if (!is.null(ct)) unname(ct$statistic) else NA_real_
  chi_df   <- if (!is.null(ct)) unname(ct$parameter) else NA_real_
  p_chi    <- if (!is.null(ct)) ct$p.value else NA_real_

  p_fisher <- NA_real_; fisher_or <- NA_real_
  if (!z_valid) {
    ft <- stats::fisher.test(matrix(c(x1, n1 - x1, x2, n2 - x2),
                                    nrow = 2, byrow = TRUE))
    p_fisher <- ft$p.value
    fisher_or <- unname(ft$estimate)
  }

  if (z_valid) {
    p_primary <- p_z
    test_used <- "two-sample proportion z-test"
  } else {
    p_primary <- p_fisher
    test_used <- "Fisher&#x27;s exact test"
  }
  significant <- !is.na(p_primary) && p_primary < 0.05
  verdict_label <- if (significant) "Significant difference" else "No significant difference"

Step 8: Computed honesty notes (magnitude vs detectability)

abs_pp <- abs(d) * 100
  magnitude_word <- if (abs_pp < 1) "small" else if (abs_pp < 5) "modest" else "sizeable"
  practical_note <- if (significant) {
    paste0(
      "Statistical significance measures detectability, not importance: the observed gap is ",
      fmt_num(abs_pp, 1), " percentage points",
      if (rel_ok) paste0(" (a relative change of ", fmt_pct(abs(rel_change)),
                         if (rel_change >= 0) " higher" else " lower",
                         " for ", l2, " vs ", l1, ")") else "",
      " — a ", magnitude_word, " gap in absolute terms. Whether a ",
      fmt_num(abs_pp, 1), "-point shift in ", outcome_h,
      " matters is a business judgment about stakes and volume, not a statistical one."
    )
  } else {
    paste0(
      "No significant difference is NOT proof the rates are equal — it means the data ",
      "cannot distinguish the observed gap of ", fmt_num(abs_pp, 1),
      " percentage points from sampling noise. The Newcombe 95% interval on the difference(",
      fmt_num(d_low * 100, 2), " to ", fmt_num(d_high * 100, 2),
      " percentage points) shows the range of true differences still compatible with this data."
    )
  }

  cross_check_note <- if (!is.na(p_z) && !is.na(p_chi)) {
    agree <- (p_z < 0.05) == (p_chi < 0.05)
    if (agree) {
      paste0("The continuity-corrected chi-square cross-check agrees with the z-test verdict(p = ",
             fmt_p(p_chi), " vs p = ", fmt_p(p_z), ").")
    } else {
      paste0("Caution: the continuity-corrected chi-square(p = ", fmt_p(p_chi),
             ") and the z-test(p = ", fmt_p(p_z),
             ") land on opposite sides of the 0.05 line — the evidence is borderline and should not be over-read.")
    }
  } else ""

Step 10: Metrics + JSON answer

metrics <- list(
    `Observations` = final_rows,
    `Rate` = paste0(l1, " ", fmt_pct(p1), " vs ", l2, " ", fmt_pct(p2)),
    `Difference(pp)` = round(d * 100, 2),
    `P-value` = fmt_p(p_primary),
    `Verdict` = verdict_label,
    `Test Used` = test_used
  )

  json_output <- list(
    answer = paste0(
      "Comparison of ", success_label, " rates in ", outcome_h, " between the two ",
      group_h, " groups(", format(final_rows, big.mark = ","), " rows): ",
      l1, " ", fmt_pct(p1), " (Wilson 95% CI ", fmt_pct(w1["low"]), " to ",
      fmt_pct(w1["high"]), ", n=", format(n1, big.mark = ","), ") vs ",
      l2, " ", fmt_pct(p2), " (", fmt_pct(w2["low"]), " to ", fmt_pct(w2["high"]),
      ", n=", format(n2, big.mark = ","), "). Absolute difference ",
      fmt_num(d * 100, 2), " percentage points(Newcombe 95% CI ",
      fmt_num(d_low * 100, 2), " to ", fmt_num(d_high * 100, 2),
      " percentage points). Verdict by the ", test_used, ": ",
      if (significant) "the rates differ beyond chance" else
        "no statistically significant difference was detected",
      " (p = ", fmt_p(p_primary), ").",
      if (success_flagged) paste0(" Note: ", success_note) else ""
    ),
    cards = lapply(
      c("tldr", "overview", "preprocessing", "rates_chart",
        "test_table", "group_summary", "methods"),
      function(cid) list(id = cid, metrics = metrics)
    )
  )

  list(
    initial_rows = initial_rows, final_rows = final_rows,
    rows_removed = rows_removed, n_na_rows = n_na_rows,
    outcome_h = outcome_h, group_h = group_h,
    l1 = l1, l2 = l2,
    success_label = success_label, failure_label = failure_label,
    success_flagged = success_flagged, success_note = success_note,
    n1 = n1, n2 = n2, x1 = x1, x2 = x2, p1 = p1, p2 = p2,
    w1 = w1, w2 = w2,
    d = d, d_low = d_low, d_high = d_high,
    rel_change = rel_change, rel_ok = rel_ok,
    min_cell = min_cell, z_valid = z_valid,
    z_stat = z_stat, p_z = p_z,
    chi_stat = chi_stat, chi_df = chi_df, p_chi = p_chi,
    p_fisher = p_fisher, fisher_or = fisher_or,
    p_primary = p_primary, test_used = test_used,
    significant = significant, verdict_label = verdict_label,
    practical_note = practical_note, cross_check_note = cross_check_note,
    dropped_groups_df = dropped_groups_df,
    rates_df = rates_df, test_df = test_df,
    group_summary_df = group_summary_df, methods_df = methods_df,
    metrics = metrics, json_output = json_output
  )
}
Your data has more stories to tell. Run any analysis on your own data — 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