Confidence Intervals in R: A Complete Guide

R Published Updated 4 mins read By Leon Wei
Confidence Intervals in R: A Complete Guide cover image

Quick summary

Summarize this blog with AI

Introduction

A confidence interval describes uncertainty about a specified population parameter. Start by naming that parameter: a mean, a difference of means, or a proportion. The examples below separate these targets and then contrast a frequentist interval with a Bayesian credible interval.

Tested with R 4.5.1. Run the code blocks in order. Install the named packages before starting; inputs are supplied below.

What 95% confidence means

If we repeatedly sampled under the model assumptions and constructed intervals the same way, about 95% would contain the fixed parameter. An interval from one observed sample either contains it or does not. The confidence level is not a posterior probability for that particular interval, nor a statement that it contains 95% of observations. Selection bias, dependence and measurement errors are not repaired by a narrow interval.

A t interval for one population mean

These six fictional independent measurements have sample mean 12. For a normal population with unknown variance, the t interval uses the sample standard deviation and n − 1 degrees of freedom. With nonnormal data it is an approximation whose adequacy depends on sample size and distribution. The manual calculation below agrees with t.test().

x <- c(8, 10, 11, 13, 14, 16)
 fit <- t.test(x, conf.level = 0.95)
 ci <- as.numeric(fit$conf.int)
 manual <- mean(x) + qt(c(0.025, 0.975), length(x) - 1) * sd(x) / sqrt(length(x))
 print(round(c(mean = mean(x), lower = ci[1], upper = ci[2]), 3))
 stopifnot(isTRUE(all.equal(ci, manual)))

The mean is 12; the interval endpoints are approximately 8.958 and 15.042. This estimates the population mean, not the range of a future individual measurement.

A difference of means has a direction

For independent groups, the default two-sample t.test uses Welch’s unequal-variance method. Supplying new scores first makes the parameter new minus traditional. A paired design instead requires matched observations and paired = TRUE; do not choose pairing merely because vector lengths agree.

traditional <- c(88, 92, 75, 85, 78)
 new <- c(95, 89, 88, 94, 90)
 difference <- t.test(new, traditional)
 print(round(c(estimate = mean(new) - mean(traditional),
               lower = difference$conf.int[1], upper = difference$conf.int[2]), 3))
 reverse <- t.test(traditional, new)
 stopifnot(isTRUE(all.equal(as.numeric(reverse$conf.int),
                           -rev(as.numeric(difference$conf.int)))))

The observed mean difference is 7.6 score points, with a 95% interval of approximately [−0.988, 16.188]. Report its interval and units; the observational comparison alone does not establish a teaching-method effect.

A proportion and a difference of proportions

The following score interval estimates one campaign’s success probability. The two-group call estimates new minus old, not two separate success intervals. We explicitly disable continuity correction so the calculation is reproducible. These approximations assume independent binomial trials; clustered customers or repeated visits need a design-aware analysis.

single <- prop.test(150, 1000, correct = FALSE)
 comparison <- prop.test(c(150, 120), c(1000, 1000), correct = FALSE)
 print(round(rbind(single_probability = single$conf.int,
                   new_minus_old = comparison$conf.int), 4))
 stopifnot(all(single$conf.int >= 0 & single$conf.int <= 1),
           comparison$conf.int[1] > 0, comparison$conf.int[2] > 0)
 # Exact binomial interval remains defined at the boundary.
 zero_success <- binom.test(0, 10)$conf.int
 stopifnot(zero_success[1] == 0, zero_success[2] > 0)

The estimated difference is 0.03, or 3 percentage points. Its interval is approximately [0.0001, 0.0599] and barely excludes zero. That does not establish a causal improvement or guarantee a practically worthwhile effect.

Make missingness and invalid input explicit

A wrapper can enforce a data contract; it does not create a distribution-free interval. This version rejects missing/nonfinite values and requires at least two nonconstant observations. If you choose to omit missing rows, report how many and why that subset answers your question.

mean_interval <- function(x, level = 0.95) {
   if (!is.numeric(x) || !is.null(dim(x)) || length(x) < 2L ||
       any(!is.finite(x)) || !is.numeric(level) || length(level) != 1L ||
       !is.finite(level) || level <= 0 || level >= 1)
     stop("Need a finite numeric vector and a level strictly between 0 and 1")
   if (!is.finite(sd(x)) || sd(x) == 0) stop("Need positive finite sample SD")
   as.numeric(t.test(x, conf.level = level)$conf.int)
 }
 fails <- function(expr) inherits(tryCatch(expr, error = identity), "error")
 stopifnot(isTRUE(all.equal(mean_interval(x), ci)),
           fails(mean_interval(numeric())), fails(mean_interval(c(1, NA))),
           fails(mean_interval(c(2, 2))), fails(mean_interval(c(1, Inf))),
           fails(mean_interval(matrix(1:4, 2))), fails(mean_interval(x, 1)))

A separate Bayesian example: a binomial probability

For the same 150 successes in 1,000 trials, choose a Beta(1,1) prior explicitly. Multiplying its density by the binomial likelihood gives a Beta(151,851) posterior. Its central 95% credible interval does have 95% posterior probability under this specified prior and likelihood. qbeta() supplies the quantiles; this is not a Bayesian bootstrap.

posterior <- qbeta(c(0.025, 0.975), shape1 = 151, shape2 = 851)
 print(round(posterior, 4))
 stopifnot(abs(diff(pbeta(posterior, 151, 851)) - 0.95) < 1e-12)

The interval is approximately [0.1292, 0.1735]. Prior sensitivity matters especially with little data. A Bayesian interval and a frequentist interval answer different probability questions even when their endpoints are close.

Choose a method that estimates your target

A percentile bootstrap can approximate uncertainty by resampling independent observations, but small, biased or dependent samples need more care; resampling does not fix the sampling design. A Wilcoxon signed-rank interval concerns a pseudomedian and relies on symmetry for its usual location interpretation, as the R documentation explains. It is not a universal distribution-free mean interval. For survey estimates, retain sampling weights and design; for a future month’s sales, use an appropriate forecasting or prediction interval rather than a confidence interval for a constant historical mean.

Primary references

Interview Prep

Begin Your SQL, Python, and R Journey

Master 230 interview-style coding questions and build the data skills needed for analyst, scientist, and engineering roles.

Related Articles

All Articles
Comments in R: A Beginner's Guide cover image
r May 2, 2024

Comments in R: A Beginner's Guide

Unlock the secrets of effective commenting in R programming with this comprehensive guide designed for beginners. Learn best practices and tips …