Originally published in longer form at Z-Test in R: Complete Guide with One-Sample & Two-Sample Examples— this version is a condensed, code-first walkthrough for reproducing the results directly.
install.packages("BSDA")
library(BSDA)
The one-sample Z-test evaluates whether a sample mean differs significantly from a hypothesized population mean, under the condition that the population standard deviation (sigma) is known in advance — not estimated from the sample itself.
xbar <- mean(mtcars$mpg)
mu <- 21 # hypothesized population mean
sigma <- 6 # assumed known population sd
n <- length(mtcars$mpg)
xbar
#> [1] 20.09062
z.test(x = mtcars$mpg, mu = 21, sigma.x = 6)
#> One-sample z-Test
#>
#> data: mtcars$mpg
#> z = -0.8574, p-value = 0.3912
#> alternative hypothesis: true mean is not equal to 21
#> 95 percent confidence interval:
#> 18.01175 22.16947
#> sample estimates:
#> mean of x
#> 20.09062
Z <- (xbar - mu) / (sigma / sqrt(n))
Z
#> [1] -0.8574411
p_value <- 2 * (1 - pnorm(abs(Z)))
p_value
#> [1] 0.3912
Both approaches agree exactly, because z.test() isn’t
doing anything beyond this formula plus a normal-distribution
lookup.
p_left <- pnorm(Z) # H1: mu < 21
p_right <- 1 - pnorm(Z) # H1: mu > 21
p_two <- 2 * (1 - pnorm(abs(Z))) # H1: mu != 21
c(left = p_left, right = p_right, two = p_two)
#> left right two
#> 0.1955802 0.8044198 0.3911603
z_crit_two <- qnorm(0.975) # ±1.96 for alpha = 0.05, two-tailed
z_crit_right <- qnorm(0.95) # +1.645 for alpha = 0.05, right-tailed
c(two_tailed = z_crit_two, right_tailed = z_crit_right)
#> two_tailed right_tailed
#> 1.959964 1.644854
Since Z = -0.857 falls inside [-1.96, 1.96], we fail to reject H0 — matching the p-value conclusion (0.3912 > 0.05).
Comparing two independent groups — automatic vs. manual transmission
cars in mtcars — again assuming both population standard
deviations are known (6 for each group, for illustration):
auto <- mtcars$mpg[mtcars$am == 0]
manual <- mtcars$mpg[mtcars$am == 1]
z.test(x = auto, y = manual, sigma.x = 6, sigma.y = 6, mu = 0)
#> Two-sample z-Test
#>
#> data: auto and manual
#> z = -3.3547, p-value = 0.0007942
#> alternative hypothesis: true difference in means is not equal to 0
#> 95 percent confidence interval:
#> -11.477848 -3.012072
#> sample estimates:
#> mean of x mean of y
#> 17.14737 24.39231
Unlike the one-sample example, this result is significant — p < 0.001, and the confidence interval on the difference excludes zero.
Every example above assumes sigma is known. In real research data, it
almost never is — sigma is typically calculated from the same sample
you’re analyzing, which makes it an estimate, not a known
constant. Once sigma is estimated rather than known, the correct test is
t.test(), not z.test():
# If sigma is NOT actually known — use t.test() instead:
t.test(mtcars$mpg, mu = 21)
t.test(auto, manual)
The syntax difference is trivial. The assumption difference is not — a t-test’s wider critical values correctly account for the added uncertainty of an estimated standard deviation, which a Z-test formula has no mechanism to capture.
| Scenario | Function |
|---|---|
| Sigma known, one sample | z.test(x, mu, sigma.x) |
| Sigma known, two samples | z.test(x, y, sigma.x, sigma.y, mu = 0) |
| Sigma estimated, one sample | t.test(x, mu) |
| Sigma estimated, two samples | t.test(x, y) |
Full write-up with the decision framework for choosing between Z and t, and the reasoning behind each step, is here: Z-Test in R