Contents
Deep Breakdown
What it tells me (Situation Awareness): A confidence interval gives a range of plausible values for the population mean . The Z-interval uses the standard Normal quantile and is valid when (1) is known, or (2) is large enough that the sample by CLT. In DA/DS this appears in A/B test result reporting, uplift estimates, and everywhere a "margin of error" is communicated to stakeholders.
Flowchart
Real-world use
Where it is used
Reporting A/B test results: "the mean revenue per user in the treatment group is $12.50, with a 95% CI of [$11.80, $13.20]." Stakeholders use the CI to assess practical significance, not just statistical significance.
Problem it solves
A point estimate has no inherent uncertainty attached to it. The CI quantifies that uncertainty, telling you how wide the range of plausible population means is given the sample you observed.
How it solves the problem
By the CLT, approximately. The Z critical value captures the central probability mass of the standard Normal. Multiplying by the SEM scales this back to the original units.
Example in a business
An A/B test with users per group measures mean order value. Treatment group: , EUR. SEM . 95% CI: . Since the CI excludes the control mean of 43.50 EUR, the result is statistically significant.
The formula, piece by piece
| Symbol | Meaning |
|---|---|
| Sample mean | |
| Standard Normal quantile at tail probability | |
| Significance level (e.g. for 95% CI) | |
| Population standard deviation | |
| Square root of sample size | |
| Standard Error of the Mean (SEM) |
Common Z critical values: (95% CI), (99% CI). Full formula:
The formula in words
"The confidence interval is the sample mean plus or minus the critical Z value times the standard error." The gives a symmetric interval around the sample mean. The wider the interval, the less precise the estimate; the wider, the more confident you are it contains .
Critical Concepts
- Correct interpretation: "If we repeated the sampling procedure many times, % of the resulting CIs would contain the true ." NOT "there is a 95% probability that is in THIS interval."
- Z vs t: use Z when is known or ; use the t-distribution otherwise.
- Width trade-off: wider CI = more confidence; narrower CI = more precision. Can only reduce width by increasing .
- Margin of error: the half-width is called the margin of error.
Step-by-step calculation (tiny example)
, , , confidence = 95%.
Step 1: Compute SEM
Step 2: Get Z critical value 95% CI: , so
Step 3: Compute margin of error
Step 4: Form interval
Code examples
Beginner example (Python)
import numpy as np
from scipy import stats
sample_data = [48, 52, 55, 47, 50, 53, 49, 51, 46, 54]
n = len(sample_data)
x_bar = np.mean(sample_data)
sigma = 5.0 # assume known population std
sem = sigma / np.sqrt(n)
z_critical = stats.norm.ppf(0.975) # 1.96 for 95% CI
margin_error = z_critical * sem
ci_lower = x_bar - margin_error
ci_upper = x_bar + margin_error
print(f"Sample mean: {x_bar:.2f}")
print(f"SEM: {sem:.4f}")
print(f"Z_0.025: {z_critical:.4f}")
print(f"95% CI: [{ci_lower:.4f}, {ci_upper:.4f}]")Beginner example (R)
sample_data <- c(48, 52, 55, 47, 50, 53, 49, 51, 46, 54)
n <- length(sample_data)
x_bar <- mean(sample_data)
sigma <- 5.0
sem <- sigma / sqrt(n)
z_critical <- qnorm(0.975) # 1.96
margin_error <- z_critical * sem
ci_lower <- x_bar - margin_error
ci_upper <- x_bar + margin_error
cat("95% Z-interval: [", round(ci_lower, 4), ",", round(ci_upper, 4), "]\n")Professional example (Python)
import numpy as np
import pandas as pd
from scipy import stats
from dataclasses import dataclass
@dataclass
class ConfidenceIntervalResult:
mean: float
sem: float
z_critical: float
lower: float
upper: float
confidence_level: float
def contains(self, value: float) -> bool:
return self.lower <= value <= self.upper
def width(self) -> float:
return self.upper - self.lower
def z_confidence_interval(
data: np.ndarray,
sigma: float | None = None,
confidence_level: float = 0.95,
) -> ConfidenceIntervalResult:
# Compute Z-based confidence interval for the population mean.
# If sigma is None, uses sample std (large-sample approximation).
n = len(data)
x_bar = float(np.mean(data))
std_used = sigma if sigma is not None else float(np.std(data, ddof=1))
sem = std_used / np.sqrt(n)
alpha = 1.0 - confidence_level
z_crit = float(stats.norm.ppf(1 - alpha / 2))
margin = z_crit * sem
return ConfidenceIntervalResult(
mean=round(x_bar, 4),
sem=round(sem, 4),
z_critical=round(z_crit, 4),
lower=round(x_bar - margin, 4),
upper=round(x_bar + margin, 4),
confidence_level=confidence_level,
)
def ab_test_ci_report(
control_data: np.ndarray,
treatment_data: np.ndarray,
confidence_level: float = 0.95,
) -> pd.DataFrame:
# Compute CIs for control and treatment means and the lift.
control_ci = z_confidence_interval(control_data, confidence_level=confidence_level)
treatment_ci = z_confidence_interval(treatment_data, confidence_level=confidence_level)
lift = treatment_ci.mean - control_ci.mean
rows = [
{"group": "control", "mean": control_ci.mean, "ci_lower": control_ci.lower, "ci_upper": control_ci.upper},
{"group": "treatment", "mean": treatment_ci.mean, "ci_lower": treatment_ci.lower, "ci_upper": treatment_ci.upper},
{"group": "lift", "mean": round(lift, 4), "ci_lower": None, "ci_upper": None},
]
return pd.DataFrame(rows)
if __name__ == "__main__":
rng = np.random.default_rng(seed=42)
control = rng.normal(loc=43.0, scale=12.0, size=500)
treatment = rng.normal(loc=45.5, scale=12.0, size=500)
report = ab_test_ci_report(control, treatment, confidence_level=0.95)
print("A/B Test 95% Confidence Intervals:")
print(report.to_string(index=False))Source: domain knowledge