Two Proportions Z-Test
The engine for Proportional Divergence. This model audits the gap between two independent percentages, reveal the definitive shift in success rates or occurrence frequencies across groups.
What is it?
Two Proportions Z-Test is a specialized statistical test used to evaluate proportions, multivariate mean vectors, or clinical equivalence margins.
The engine for Proportional Divergence. This model audits the gap between two independent percentages, reveal the definitive shift in success rates or occurrence frequencies across groups.
Goals & Indications
- Divergence Audit: Determine if the success rates of two groups are significantly different from each other.
- Categorical Profile Discovery: Identify the predictors that drive a group to have a higher 'Proportional Yield' than another.
- Precision Gap Mapping: Quantify the 'Clinical Spread' between groups using standardized difference metrics.
Core Idea Diagram
Claims tested
How it works
- State null hypothesis of equal proportions: p_1 = p_2.
- Calculate pooled sample proportion based on total success counts.
- Compute standard error of proportions difference.
- Calculate Z statistic and evaluate against normal distribution bounds.
Assumptions
Important Note
The two-proportion z-test and chi-square test are mathematically equivalent for 2×2 contingency tables (χ² = z²). Use z-test for directional hypotheses and confidence intervals; use chi-square for omnibus association test.
Worked Example
| Group | Successes | Sample Size | Proportion | Z Stat | p-value |
|---|---|---|---|---|---|
| Treatment | 45 | 100 | 0.450 | 2.18 | 0.029 |
| Control | 30 | 100 | 0.300 |
Two Proportions Z-Test Laboratory
Slide proportions $p_1$ and $p_2$. Observe how the pooled standard error and Z-statistic update dynamically.
Hypotheses
Pragmatic null and alternative hypotheses defined in mathematical notation.
H₀: p₁ = p₂ (the population proportions are equal in both groups)
Hₐ: p₁ ≠ p₂ (two-tailed), or p₁ > p₂ (one-tailed), or p₁ < p₂ (one-tailed)
The two-proportion z-test and chi-square test are mathematically equivalent for 2×2 contingency tables (χ² = z²). Use z-test for directional hypotheses and confidence intervals; use chi-square for omnibus association test.
Assumptions
The core mathematical criteria needed to ensure that statistical testing remains unbiased and valid.
Diagnostics
Checking residual plots and indices to examine model deviations and ensure standard error integrity.
- Check expected counts: n₁p̂, n₁(1-p̂), n₂p̂, n₂(1-p̂) all ≥ 5 (where p̂ is pooled proportion)
- Verify independence: check study design for clustering, pairing, or repeated measures
- Inspect 2×2 contingency table for data accuracy and cell counts
- Compare Fisher's exact test p-value to z-test p-value (should be similar if n large)
- Calculate and report effect sizes: risk difference, relative risk, odds ratio
- Visualize proportions with bar chart or forest plot with 95% CIs
- Assess clinical/practical significance alongside statistical significance
Applied Minds
Review concrete study examples, data layout guidelines, and copy executable syntax scripts.
Smoking Cessation RCT (Nicotine Patch vs. Placebo)
Research question: Does nicotine patch improve smoking cessation rates compared to placebo? Design: Double-blind RCT with 2 groups (Nicotine Patch n=120, Placebo n=120). Outcome: Binary smoking status at 6 months (Quit = success, Smoking = failure). Primary analysis: Two-proportion z-test to compare quit rates.
# Two-proportion z-test: Nicotine patch vs. placebo for smoking cessation
# Based on realistic effect sizes from Cochrane meta-analysis
library(tidyverse)
library(epitools) # For riskratio, oddsratio
library(DescTools) # For confidence intervals
set.seed(2025)
# Simulate realistic data (or load: data <- read.csv("smoking_cessation.csv"))
# Patch: 35% quit rate; Placebo: 20% quit rate (RR = 1.75)
n_patch <- 120
n_placebo <- 120
p_patch <- 0.35
p_placebo <- 0.20
data <- data.frame(
group = factor(c(rep("Patch", n_patch), rep("Placebo", n_placebo)),
levels = c("Patch", "Placebo")),
quit = c(
rbinom(1, n_patch, p_patch), # successes in patch group
rbinom(1, n_placebo, p_placebo) # successes in placebo group
) %>% {c(rep(1, .[1]), rep(0, n_patch - .[1]),
rep(1, .[2]), rep(0, n_placebo - .[2]))}
)
# Alternatively, simulate individual outcomes:
data <- data.frame(
group = factor(c(rep("Patch", n_patch), rep("Placebo", n_placebo)),
levels = c("Patch", "Placebo")),
quit = c(
rbinom(n_patch, 1, p_patch),
rbinom(n_placebo, 1, p_placebo)
)
)
# === STEP 1: Check Assumptions ===
# Create 2x2 contingency table
table_2x2 <- table(data$group, data$quit)
rownames(table_2x2) <- c("Patch", "Placebo")
colnames(table_2x2) <- c("Smoking", "Quit")
cat("=== 2×2 Contingency Table ===\n")
print(table_2x2)
print(addmargins(table_2x2))
# Check expected counts (npq rule)
cat("\n=== Expected Count Check ===\n")
n1 <- sum(data$group == "Patch")
n2 <- sum(data$group == "Placebo")
x1 <- sum(data$group == "Patch" & data$quit == 1)
x2 <- sum(data$group == "Placebo" & data$quit == 1)
p1_hat <- x1 / n1
p2_hat <- x2 / n2
p_pooled <- (x1 + x2) / (n1 + n2)
expected_counts <- c(
n1 * p_pooled,
n1 * (1 - p_pooled),
n2 * p_pooled,
n2 * (1 - p_pooled)
)
cat("Expected counts(using pooled proportion):\n")
cat(" Patch-Quit:", round(expected_counts[1], 1), "\n")
cat(" Patch-Smoking:", round(expected_counts[2], 1), "\n")
cat(" Placebo-Quit:", round(expected_counts[3], 1), "\n")
cat(" Placebo-Smoking:", round(expected_counts[4], 1), "\n")
cat("All expected counts ≥ 5?", all(expected_counts >= 5), "✓\n")
# === STEP 2: Descriptive Statistics ===
cat("\n=== Descriptive Statistics ===\n")
cat("Patch group: ", x1, "/", n1, " quit(",
round(100*p1_hat, 1), "%)\n", sep="")
cat("Placebo group: ", x2, "/", n2, " quit(",
round(100*p2_hat, 1), "%)\n", sep="")
# === STEP 3: Two-Proportion Z-Test ===
cat("\n=== Two-Proportion Z-Test ===\n")
# Method 1: Using prop.test() - applies continuity correction by default
result <- prop.test(c(x1, x2), c(n1, n2), correct = FALSE) # no continuity correction
print(result)
# Method 2: Manual calculation (instructive)
p_diff <- p1_hat - p2_hat
se_pooled <- sqrt(p_pooled * (1 - p_pooled) * (1/n1 + 1/n2))
z_stat <- p_diff / se_pooled
p_value_twotailed <- 2 * (1 - pnorm(abs(z_stat)))
cat("\nManual Calculation:\n")
cat(" Difference in proportions(p₁ - p₂):", round(p_diff, 4), "\n")
cat(" Pooled SE:", round(se_pooled, 4), "\n")
cat(" Z-statistic:", round(z_stat, 3), "\n")
cat(" Two-tailed p-value:", format.pval(p_value_twotailed, digits=3), "\n")
# 95% Confidence Interval for difference (unpooled SE)
se_unpooled <- sqrt(p1_hat*(1-p1_hat)/n1 + p2_hat*(1-p2_hat)/n2)
ci_lower <- p_diff - 1.96 * se_unpooled
ci_upper <- p_diff + 1.96 * se_unpooled
cat("\n95% CI for difference(p₁ - p₂):",
"[", round(ci_lower, 3), ",", round(ci_upper, 3), "]\n")
# === STEP 4: Chi-Square Test (Equivalent) ===
cat("\n=== Chi-Square Test(Equivalent for 2×2) ===\n")
chi_result <- chisq.test(table_2x2, correct = FALSE)
print(chi_result)
cat("\nNote: χ² =", round(chi_result$statistic, 3),
"= z² =", round(z_stat^2, 3), "(equivalent)\n")
# === STEP 5: Fisher's Exact Test (Comparison) ===
cat("\n=== Fisher's Exact Test(for comparison) ===\n")
fisher_result <- fisher.test(table_2x2)
print(fisher_result)
cat("\nFisher's p-value:", format.pval(fisher_result$p.value, digits=3), "\n")
cat("Z-test p-value:", format.pval(p_value_twotailed, digits=3), "\n")
cat("(Should be similar with adequate sample size)\n")
# === STEP 6: Effect Sizes ===
cat("\n=== Effect Sizes ===\n")
# Risk Difference (RD)
rd <- p1_hat - p2_hat
cat("Risk Difference(RD):", round(rd, 3), "\n")
cat(" Interpretation:", round(100*rd, 1), "percentage point increase in quit rate\n")
# Relative Risk (RR)
rr <- p1_hat / p2_hat
rr_ci <- riskratio(table_2x2[, c(2, 1)], rev="rows")$measure[2, c(1, 2, 3)]
cat("\nRelative Risk(RR):", round(rr, 2), "\n")
cat(" 95% CI:", "[", round(rr_ci[2], 2), ",", round(rr_ci[3], 2), "]\n")
cat(" Interpretation: Patch group", round(rr, 2),
"times more likely to quit than placebo\n")
# Odds Ratio (OR)
or_val <- (x1 / (n1 - x1)) / (x2 / (n2 - x2))
or_ci <- oddsratio(table_2x2[, c(2, 1)], rev="rows")$measure[2, c(1, 2, 3)]
cat("\nOdds Ratio(OR):", round(or_val, 2), "\n")
cat(" 95% CI:", "[", round(or_ci[2], 2), ",", round(or_ci[3], 2), "]\n")
# Number Needed to Treat (NNT)
nnt <- 1 / rd
cat("\nNumber Needed to Treat(NNT):", round(nnt, 1), "\n")
cat(" Interpretation: Treat", round(nnt, 0),
"people with patch for 1 additional quit\n")
# === STEP 7: Visualization ===
# Bar plot with proportions and 95% CIs
summary_data <- data %>%
group_by(group) %>%
summarise(
n = n(),
successes = sum(quit),
prop = mean(quit),
se = sqrt(prop * (1 - prop) / n),
ci_lower = prop - 1.96 * se,
ci_upper = prop + 1.96 * se,
.groups = "drop"
)
ggplot(summary_data, aes(x = group, y = prop, fill = group)) +
geom_bar(stat = "identity", width = 0.6, alpha = 0.8) +
geom_errorbar(aes(ymin = ci_lower, ymax = ci_upper),
width = 0.2, size = 1) +
geom_text(aes(label = paste0(round(100*prop, 1), "%")),
vjust = -0.5, hjust = 0.5, nudge_y = 0.05, size = 5) +
scale_y_continuous(labels = scales::percent, limits = c(0, 0.6)) +
labs(title = "Smoking Cessation Rates at 6 Months",
subtitle = "Nicotine Patch vs. Placebo(RCT, n=240)",
x = "Treatment Group", y = "Proportion Quit ± 95% CI") +
scale_fill_brewer(palette = "Set2") +
theme_classic() +
theme(legend.position = "none",
axis.text = element_text(size = 12),
axis.title = element_text(size = 13))
# Forest plot for effect sizes
library(forestplot)
effect_data <- data.frame(
Measure = c("Risk Difference", "Relative Risk", "Odds Ratio"),
Estimate = c(rd, rr, or_val),
Lower = c(ci_lower, rr_ci[2], or_ci[2]),
Upper = c(ci_upper, rr_ci[3], or_ci[3])
)
# === APA-Style Reporting ===
cat("\n=== APA-Style Report ===\n")
cat("A two-proportion z-test was conducted to compare smoking cessation rates\n")
cat("between nicotine patch(n = 120) and placebo(n = 120) groups at 6 months.\n")
cat("Assumptions were satisfied: independence(RCT with random assignment),\n")
cat("binary outcome(quit vs. smoking), and adequate expected counts(all ≥ 5).\n")
cat("Results showed a statistically significant difference, z =", round(z_stat, 2), ",\n")
cat("p =", format.pval(p_value_twotailed, digits=3), ". The nicotine patch group had\n")
cat("a significantly higher quit rate(", round(100*p1_hat, 1), "%, ", x1, "/", n1, ")\n", sep="")
cat("compared to placebo(", round(100*p2_hat, 1), "%, ", x2, "/", n2, "),\n", sep="")
cat("with a risk difference of", round(100*rd, 1), "percentage points\n")
cat("(95% CI [", round(100*ci_lower, 1), ",", round(100*ci_upper, 1), "]).\n")
cat("The relative risk was", round(rr, 2), "(95% CI [", round(rr_ci[2], 2), ",",
round(rr_ci[3], 2), "]),\n")
cat("indicating nicotine patch users were", round(rr, 2),
"times more likely to quit.\n")
cat("Number needed to treat(NNT) =", round(nnt, 0),
": one additional quit per", round(nnt, 0), "treated.\n")z = 2.34, p = .019. The nicotine patch group had significantly higher quit rates (35.0%, 42/120) compared to placebo (20.0%, 24/120), with a risk difference of 15.0 percentage points (95% CI [2.5, 27.5]). RR = 1.75 (95% CI [1.13, 2.72]): patch users were 75% more likely to quit. NNT = 7: treating 7 people with nicotine patch yields 1 additional quit compared to placebo. Findings support nicotine patch as effective smoking cessation aid, consistent with Cochrane meta-analysis (Stead et al., 2012).
Alternatives
Structured fallback pathways for choosing alternative tests when normality or slopes requirements fail.
- Fisher's Exact Test — The required strike when any cell in the yield grid is < 5.
- Rule of Three — Provide approximate bounds if zero events were observed in one group.
- Likelihood Ratio Strike — Use G-test or Logistic Regression to maintain better additive properties.
- Binary GEE — Account for clustering if success events occur within families or clinical sites.
Post-hoc
Group mean comparisons and correction controls (e.g. Tukey HSD, Bonferroni) to protect against Family-Wise Error Rates.
Post-hoc pairwise tests defined for this model.
Two-proportion test is typically a planned comparison. Post-hoc analyses are rare, but if needed:
Effect Size
Understanding effect sizes (e.g., Cohen's d, Partial Eta-Squared) and clinical impact benchmarks.
RD = p₁ - p₂. Interpretation: Absolute change in proportion. RD = 0.15 means 15 percentage point increase. Preferred for clinical interpretation. Small: 0.05, Medium: 0.10, Large: 0.20
RR = p₁/p₂. Interpretation: Ratio of proportions. RR = 1.5 means 50% higher risk in group 1. RR > 1 (increased risk), RR < 1 (decreased risk), RR = 1 (no difference). Preferred for epidemiology
OR = (p₁/(1-p₁)) / (p₂/(1-p₂)). Interpretation: Ratio of odds. When events are rare (p < 0.10), OR ≈ RR. Preferred for logistic regression. OR > 1 (increased odds), OR < 1 (decreased odds)
NNT = 1/RD. Interpretation: Number needed to treat for one additional success. NNT = 7 means treat 7 people for 1 additional cure. Lower NNT = more effective. Preferred for clinical decision-making
h = 2*(arcsin(√p₁) - arcsin(√p₂)). Standardized measure. Small: 0.2, Medium: 0.5, Large: 0.8. Preferred for power analysis and meta-analysis
0.2
0.5
0.8
Sample Size
Guidelines for minimum sample requirements and power analysis parameters.
Ensure expected counts ≥ 5 in all cells: n₁p̂, n₁(1-p̂), n₂p̂, n₂(1-p̂) all ≥ 5. If violated, use Fisher's exact test
| Effect Size | Parameters | Required n |
|---|---|---|
| Small Effect | α=.05, power=.80 | RD = 0.05 |
| Medium Effect | α=.05, power=.80 | RD = 0.10 |
| Large Effect | α=.05, power=.80 | RD = 0.20 |
Reporting
How to compile statistical results into publication prose matching APA and journal style guides.
A two-proportion z-test was conducted to compare brief description of outcome between group 1 (n = XX) and group 2 (n = XX). State hypothesis type: two-tailed or one-tailed. Assumptions were satisfied/violated: independence (describe design), binary outcome, and adequate/inadequate expected counts (all ≥ 5 / Fisher's exact test used instead). Results showed a statistically significant / non-significant difference, z = X.XX, p = .XXX. Group 1 had a higher/lower proportion (XX.X%, X/n₁) compared to group 2 (XX.X%, X/n₂), with a risk difference of XX.X percentage points (95% CI XX.X, XX.X). The relative risk was X.XX (95% CI X.XX, X.XX), indicating interpretation. Number needed to treat (NNT) = XX. Conclude with interpretation in research context.
- z-statistic
- p-value (specify one-tailed or two-tailed)
- Proportions in each group (%, count/n)
- Risk difference with 95% CI
- Relative risk or odds ratio with 95% CI
- NNT (if intervention study)
- Statement about assumption checks (independence, expected counts)
Manuscript Lab
Copy standard summary tables and forensic reporting grids to outline analysis details.
| Group | Successes | Total | Proportion (%) | Diff (p1-p2) | 95% CI (Diff) | p-value |
|---|---|---|---|---|---|---|
| Experimental | 75 | 100 | 75.0% | 15.0% | [4.2%, 25.8%] | .008 |
| Standard | 60 | 100 | 60.0% | — | — | — |
The 'Absolute Lift'. The raw difference in success rates between the two groups.
The Precision Window. If the interval excludes ZERO, the difference is statistically significant.
Command Center
Syntax libraries and function parameters for executing calculations in stats packages.
# 1. Execute Two-Proportions Test
prop.test(x = c(75, 60), n = c(100, 100))
# 2. Extract Exact p-value (for small samples)
fisher.test(matrix(c(75, 25, 60, 40), ncol=2))The 'Continuity Correction' debate. Yates' correction is conservative and prevents Type I errors in small samples. In large samples (N > 100), the results converge, making the correction less critical.
# Generate Instant APA Narrative
report::report(prop.test(c(75, 60), c(100, 100)))Common Mistakes
Analytical caveats and corrections to maintain modeling integrity.
References
Scholarly lineage and citation keys grounding the statistical framework.