Hypothesis Testing in R (t.test, binom.test, prop.test)

Context: FIT2086_MOC · Studio 5 (run in W6, drills the W5 material) · the R execution of Tests for Normal Means (z-test and t-test) and Tests for Bernoulli Populations — three functions cover every case the unit tests · sits beside Confidence Intervals in R (calcCI), which builds the same intervals by hand Problem it solves: turn a data vector (or a pair of counts) into a -value, a confidence interval, and a defensible sentence about the population.

Quick Revision

  • 🎯 Trigger: “is this group’s mean ?” ➔ t.test(x, mu=) | “do these two groups differ?” ➔ t.test(x, y) | “is this proportion ?” ➔ binom.test | “do these two proportions differ?” ➔ prop.test.
  • ⚡ Key Constraint: three arguments carry all the exam risk — mu (the null value, not the estimate), alternative (which tail), and var.equal (Welch vs pooled). Getting alternative wrong changes by exactly a factor of .

🔧 Minimal Working Example

bpdata <- read.csv("bpdata.csv")          # 20 males aged 47–56; n = 20, mean 114, sd 5.43
 
# One sample, two-sided: is this an "at risk" population (mu = 120)?
t.test(x = bpdata$BP, mu = 120)                        # t = -4.943, df = 19, p = 9.0e-05
 
# One sample, one-sided: are they healthy?  H0: mu >= 120 vs HA: mu < 120
t.test(x = bpdata$BP, mu = 120, alternative = "less")  # same t, p = 4.5e-05
 
# Coverage of the reported interval
t.test(x = bpdata$BP, conf.level = 0.99)               # wider than the 0.95 default
 
# Two samples: Welch (unequal variances, the DEFAULT) then pooled
SP500  <- read.csv("SP500.csv")
y_pre  <- SP500$Index[1:58]; y_post <- SP500$Index[59:108]
t.test(y_pre, y_post, var.equal = FALSE)               # Welch
t.test(y_pre, y_post, var.equal = TRUE)                # pooled
 
rv <- t.test(y_pre, y_post, var.equal = FALSE)
rv$p.value                                             # 1.77e-51 — R prints "< 2.2e-16"
 
# Binary data
binom.test(x = 4, n = 12, p = 1/2)                     # exact one-proportion, p = 0.3877
prop.test(x = c(4, 10), n = c(12, 12))                 # exact two-proportion, p = 0.0384

Expected output:

CallStatistic-valueInterval reported
t.test(BP, mu=120), — a two-sided range for
t.test(BP, mu=120, alternative="less")same (exactly half) — an upper bound on
t.test(BP, conf.level=0.99) — wider than the interval
t.test(y_pre, y_post) (Welch)
t.test(y_pre, y_post, var.equal=T) (pooled)
binom.test(4, 12, 1/2) successes for
prop.test(c(4,10), c(12,12))interval for
  • Reading the printout ➔ R gives the statistic and df on one line, the -value, then alternative hypothesis: (a check that alternative/mu landed as intended), then the interval, then the sample estimate.
  • Tiny -values are truncated ➔ R prints p-value < 2.2e-16 rather than the number; recover it with rv <- t.test(...); rv$p.value. The object also carries rv$statistic, rv$conf.int, rv$estimate.
  • t.test has no sigma argument ➔ it always estimates the variance from the data, so it is a -test, never the known- -test; a -test is coded by hand as 2 * pnorm(-abs(z)).
  • binom.test beats the hand ➔ at the CLT approximation gives against the exact ; the approximation overstates the evidence and only closes as grows.

🔀 Variations

  • The approximate difference-of-means by hand (when you are given only summaries, not the raw vectors)
diff <- mu_pre - mu_post                                   # 494.787
se_diff <- sqrt(sigma2_pre/n_pre + sigma2_post/n_post)     # 17.373
z <- diff / se_diff                                        # 28.48
p <- 2 * pnorm(-abs(z))                                    # 2.1e-178
  • Sensitivity sweep on the exact binomialfor (x in 4:1) print(binom.test(x, 12, 1/2)$p.value) — answers “how few heads before I suspect the coin”.
  • Plot before you testplot(SP500$Index, type="l", lwd=2.5); lines(x=59:108, y=SP500$Index[59:108], col="red", lwd=2.5) — a visible step change predicts a tiny before any arithmetic.
  • alternative values"two.sided" (default) · "less" · "greater"; these follow , so "less" pairs with .

✍️ Practice

⚠️ Common Mistakes

  • 💡 Passing the sample mean to mumu is the null value ; feeding it returns and .
  • 💡 Choosing alternative after seeing the data ➔ the one-sided is exactly half the two-sided one, so picking the tail post hoc manufactures significance; comes from the research question.
  • 💡 Reporting a one-sided interval as a rangealternative="less" makes conf.int an upper bound ( on the left), not a plausible range for .
  • 💡 Quoting p-value < 2.2e-16 as the answer ➔ that is R’s print floor; extract rv$p.value when the exact magnitude matters.
  • 💡 Assuming var.equal = TRUE is the default ➔ the default is FALSE (Welch). Pooling when the variances genuinely differ ( vs here) widens the interval rather than sharpening it.

🧠 Active Recall