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.

Setup

install.packages("BSDA")
library(BSDA)

Part 1: One-Sample Z-Test

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

Using BSDA::z.test()

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

The manual equivalent (base R only)

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.

One-tailed variants

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

Critical value method (equivalent to the p-value method)

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).

Part 2: Two-Sample Z-Test

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.

Part 3: The Assumption That Actually Matters

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.

Summary Table

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