Skip to content

11-01: One-Sample t-Test

The z-test of 10-02 needs σ. You almost never have it. Replace σ with the sample standard deviation s, and the test statistic no longer follows the normal distribution — it follows Student's t.


The Test Statistic

             x̄ − μ₀
t  =  ─────────────────           df  =  n − 1
           s / √n

Identical in shape to the z-statistic, with s in place of σ — and a different reference distribution as a consequence.

Conditions

1.  Random sample
2.  σ UNKNOWN (estimated by s)
3.  Population approximately normal, OR n ≥ 30

The t-test is fairly robust to mild non-normality, especially for n ≥ 15. It is not robust to outliers, because both and s are pulled by them — check a boxplot first (04-02).


The t-Distribution

Property t z
Shape Bell, symmetric about 0 Bell, symmetric about 0
Tails Heavier Lighter
Parameters df = n − 1 none
Critical value at α = 0.05 (two-tail) 2.776 (df=4), 2.145 (df=14), 2.042 (df=30) 1.960
Limit → z as df → ∞

The extra uncertainty from estimating σ is exactly what the fatter tails encode: you need a larger test statistic to reject with a small sample.


Worked Example

A coffee machine is supposed to dispense 250 ml per cup. A technician samples 16 cups and finds x̄ = 246.8 ml with s = 5.4 ml. Is the machine off target? Test at α = 0.05.

STEP 1   H₀: μ = 250        H₁: μ ≠ 250        (two-tailed)     α = 0.05

STEP 2   Random sample ✓   σ unknown ✓   volume is approximately normal ✓
         df = 16 − 1 = 15

STEP 3   SE = s/√n = 5.4/√16 = 5.4/4 = 1.35

                246.8 − 250      −3.2
         t  =  ─────────────  =  ──────  =  −2.370
                    1.35          1.35

STEP 4   Critical values:  ±t(0.025, 15) = ±2.131
         |−2.370| > 2.131            →  REJECT H₀

         p-value:  2 × P(T₁₅ < −2.370) = 2(0.01581) = 0.0316  ≤ 0.05
                                        →  REJECT H₀

STEP 5   There is sufficient evidence at the 5% level to conclude that the
         mean dispensed volume differs from 250 ml.

Confidence interval (equivalent view).

246.8 ± 2.131 × 1.35 = 246.8 ± 2.877 = (243.92, 249.68)

250 is not in the interval — the same rejection, with the added information that the machine is under-filling by roughly 0.3 to 6.1 ml.

Effect size.

Cohen's d = (246.8 − 250) / 5.4 = −0.59      →  medium effect

One-Tailed Version

If the technician only cared about under-filling:

H₀: μ ≥ 250        H₁: μ < 250

Critical value:  t(0.05, 15) = −1.753
−2.370 < −1.753                            →  REJECT H₀

p-value:  P(T₁₅ < −2.370) = 0.0158  ≤ 0.05  →  REJECT H₀

Half the two-tailed p-value, and a smaller critical value — a one-tailed test is more powerful when the direction is justified in advance.


t-Test vs. z-Test — Which One

                    ┌─────────────────────────────┐
                    │  Is σ (population SD) known?│
                    └──────────┬──────────────────┘
                     YES       │        NO
              ┌────────────────┘        └───────────────┐
              ▼                                         ▼
        Use the Z-TEST                            Use the T-TEST
    z = (x̄−μ₀)/(σ/√n)                       t = (x̄−μ₀)/(s/√n), df = n−1

In coursework σ is sometimes supplied to make the z-test possible. In practice it almost never is — when in doubt, use t. With n ≥ 30 the two give nearly identical answers anyway.


Reporting the Result

The standard format in scientific writing:

t(15) = −2.37, p = .032, d = −0.59, 95% CI [243.92, 249.68]
   │        │        │        │            │
   df    statistic p-value  effect     interval estimate

Report all four. The p-value alone tells the reader almost nothing about how big the effect is.


Excel

' ══ FROM SUMMARY STATISTICS ═════════════════════════════════════════
' B1 = x̄ = 246.8, B2 = μ₀ = 250, B3 = s = 5.4, B4 = n = 16, B5 = α = 0.05
=B3/SQRT(B4)                        ' standard error         -> 1.35
=B4-1                               ' degrees of freedom     -> 15
=(B1-B2)/(B3/SQRT(B4))              ' t statistic            -> -2.37037

=T.INV.2T(B5, B4-1)                 ' two-tailed critical t  -> 2.13145
=T.INV(B5, B4-1)                    ' left-tailed critical t -> -1.75305
=T.INV(1-B5, B4-1)                  ' right-tailed critical  -> 1.75305

=T.DIST.2T(ABS(B8), B4-1)           ' two-tailed p-value     -> 0.03163
=T.DIST(B8, B4-1, TRUE)             ' left-tailed p-value    -> 0.01581
=T.DIST.RT(B8, B4-1)                ' right-tailed p-value   -> 0.98419

=IF(B12<=B5, "Reject H0", "Fail to reject H0")

' Confidence interval
=B1-T.INV.2T(B5,B4-1)*B3/SQRT(B4)   ' lower -> 243.923
=B1+T.INV.2T(B5,B4-1)*B3/SQRT(B4)   ' upper -> 249.677
=CONFIDENCE.T(B5, B3, B4)           ' margin of error E -> 2.877

' Effect size
=(B1-B2)/B3                         ' Cohen's d -> -0.5926

' ══ FROM RAW DATA ═══════════════════════════════════════════════════
=AVERAGE(A2:A17)
=STDEV.S(A2:A17)
=(AVERAGE(A2:A17)-250)/(STDEV.S(A2:A17)/SQRT(COUNT(A2:A17)))    ' t
=T.DIST.2T(ABS(F5), COUNT(A2:A17)-1)                            ' p-value

' Excel has no one-sample t-test dialog. The standard workaround:
' put a column of 250s next to your data and run
'   Data ▸ Data Analysis ▸ t-Test: Paired Two Sample for Means
' The result is exactly the one-sample t-test.
=T.TEST(A2:A17, C2:C17, 2, 1)       ' tails=2, type=1 (paired) -> 0.03163

Tip

T.TEST(array1, array2, tails, type)type is 1 = paired, 2 = two-sample equal variance, 3 = two-sample unequal variance (Welch). The paired trick above turns it into a one-sample test.


R

# ══ FROM SUMMARY STATISTICS ════════════════════════════════════════
t_test_summary <- function(xbar, mu0, s, n,
                           alternative = c("two.sided", "less", "greater"),
                           conf = 0.95) {
  alternative <- match.arg(alternative)
  se <- s / sqrt(n); df <- n - 1
  t  <- (xbar - mu0) / se
  p  <- switch(alternative,
               less      = pt(t, df),
               greater   = pt(t, df, lower.tail = FALSE),
               two.sided = 2 * pt(abs(t), df, lower.tail = FALSE))
  tc <- qt(1 - (1 - conf)/2, df)
  list(se = se, df = df, t = t, p.value = p, d = (xbar - mu0) / s,
       conf.int = c(xbar - tc * se, xbar + tc * se))
}

t_test_summary(246.8, 250, 5.4, 16)
# $se 1.35  $df 15  $t -2.370370  $p.value 0.031627  $d -0.5926
# $conf.int 243.9226 249.6774

qt(0.975, 15)     # two-tailed critical t -> 2.131450
qt(0.05, 15)      # left-tailed  critical -> -1.753050

# ══ FROM RAW DATA — t.test() ═══════════════════════════════════════
cups <- c(244, 250, 243, 249, 252, 241, 247, 245,
          255, 240, 248, 246, 251, 243, 249, 246)

t.test(cups, mu = 250)                          # two-sided (default)
t.test(cups, mu = 250, alternative = "less")    # one-sided
t.test(cups, mu = 250, conf.level = 0.99)

out <- t.test(cups, mu = 250)
out$statistic; out$parameter; out$p.value; out$conf.int; out$estimate

# ══ CHECKING THE ASSUMPTIONS ═══════════════════════════════════════
par(mfrow = c(1, 3))
hist(cups, breaks = 8, col = "#8A5FBF", border = "white", main = "Histogram")
boxplot(cups, col = "#0FA3A3", main = "Boxplot")         # outlier check
qqnorm(cups, pch = 19); qqline(cups, col = "#0FA3A3", lwd = 2)
par(mfrow = c(1, 1))

shapiro.test(cups)          # H0: data is normal — large p is reassuring

# ══ EFFECT SIZE ════════════════════════════════════════════════════
(mean(cups) - 250) / sd(cups)                   # Cohen's d, by hand
# library(effectsize);  cohens_d(cups, mu = 250)

# ══ NONPARAMETRIC ALTERNATIVE (skewed data / outliers) ═════════════
wilcox.test(cups, mu = 250)                     # Wilcoxon signed-rank test

# ══ SEEING WHY t HAS FATTER TAILS ══════════════════════════════════
curve(dnorm(x), from = -4, to = 4, col = "#5B2A86", lwd = 2, ylab = "density")
curve(dt(x, df = 5),  add = TRUE, col = "#0FA3A3", lwd = 2)
curve(dt(x, df = 30), add = TRUE, col = "#0B7A7A", lwd = 2, lty = 2)
legend("topright", c("z", "t, df=5", "t, df=30"), lwd = 2,
       col = c("#5B2A86", "#0FA3A3", "#0B7A7A"))

Python

import numpy as np
from scipy import stats

# ══ FROM SUMMARY STATISTICS ════════════════════════════════════════
def t_test_summary(xbar, mu0, s, n, alternative="two-sided", conf=0.95):
    se, df = s / np.sqrt(n), n - 1
    t = (xbar - mu0) / se
    if alternative == "less":
        p = stats.t.cdf(t, df)
    elif alternative == "greater":
        p = stats.t.sf(t, df)
    else:
        p = 2 * stats.t.sf(abs(t), df)
    tc = stats.t.ppf(1 - (1 - conf) / 2, df)
    return {"se": se, "df": df, "t": t, "p_value": p,
            "d": (xbar - mu0) / s,
            "conf_int": (xbar - tc * se, xbar + tc * se)}

t_test_summary(246.8, 250, 5.4, 16)
# {'se': 1.35, 'df': 15, 't': -2.37037, 'p_value': 0.031627,
#  'd': -0.59259, 'conf_int': (243.9226, 249.6774)}

stats.t.ppf(0.975, 15)     # 2.131450
stats.t.ppf(0.05, 15)      # -1.753050

# ══ FROM RAW DATA ══════════════════════════════════════════════════
cups = np.array([244, 250, 243, 249, 252, 241, 247, 245,
                 255, 240, 248, 246, 251, 243, 249, 246])

res = stats.ttest_1samp(cups, popmean=250)
res.statistic, res.pvalue, res.df
res.confidence_interval(confidence_level=0.95)

stats.ttest_1samp(cups, 250, alternative="less")     # one-sided

# statsmodels gives a fuller report
# from statsmodels.stats.weightstats import DescrStatsW
# d = DescrStatsW(cups); d.ttest_mean(250); d.tconfint_mean()

# ══ CHECKING THE ASSUMPTIONS ═══════════════════════════════════════
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 3, figsize=(12, 3))
axes[0].hist(cups, bins=8, color="#8A5FBF", edgecolor="white")
axes[1].boxplot(cups)
stats.probplot(cups, dist="norm", plot=axes[2])
plt.tight_layout()

stats.shapiro(cups)

# ══ EFFECT SIZE ════════════════════════════════════════════════════
(cups.mean() - 250) / cups.std(ddof=1)

# ══ NONPARAMETRIC ALTERNATIVE ══════════════════════════════════════
stats.wilcoxon(cups - 250)

Quick Reference

Task Excel R Python
t statistic (x̄-μ₀)/(s/SQRT(n)) (xbar-mu0)/(s/sqrt(n)) same
Critical t (two-tail) T.INV.2T(α, df) qt(1-α/2, df) t.ppf(1-α/2, df)
Critical t (left) T.INV(α, df) qt(α, df) t.ppf(α, df)
p-value (two-tail) T.DIST.2T(ABS(t), df) 2*pt(abs(t),df,lower.tail=FALSE) 2*t.sf(abs(t), df)
p-value (left) T.DIST(t, df, TRUE) pt(t, df) t.cdf(t, df)
p-value (right) T.DIST.RT(t, df) pt(t,df,lower.tail=FALSE) t.sf(t, df)
Full test, raw data T.TEST(x, const, 2, 1) t.test(x, mu = μ₀) ttest_1samp(x, μ₀)
Confidence interval CONFIDENCE.T(α, s, n) t.test(x)$conf.int res.confidence_interval()
Normality check SKEW, Q-Q scatter shapiro.test(x), qqnorm stats.shapiro, probplot
Nonparametric backup wilcox.test(x, mu=μ₀) stats.wilcoxon(x-μ₀)

Common Mistakes

  • Using df = n instead of n − 1.
  • Using T.INV when T.INV.2T is needed. At df = 15, T.INV(0.05,15) = −1.753 (one tail) but T.INV.2T(0.05,15) = 2.131 (two tails).
  • Running a t-test on data with a clear outlier without investigating it — the test is not robust to outliers.
  • Using a z-test with s in place of σ and calling it a z-test. If you estimated the SD, the reference distribution is t.
  • Reporting only the p-value. Always add df, the effect size, and the CI.
  • Forgetting that T.TEST's type argument (1/2/3) has nothing to do with the number of tails — that is the separate tails argument.

Exercises: 11-01: Exercises — One-Sample t-Test


⬅️ Previous: 10-02: One-Sample Z-Tests ➡️ Next: 11-02: Two-Sample t-Test