CrunchAcademy · K-12

Math for Data Science · MDS-6

Statistics & Inference

Turn data into defensible conclusions — estimate, test, and quantify uncertainty with confidence intervals, the bootstrap, A/B tests, regression, and Bayes, all in code.

Statistical inference is how data scientists move from a sample to a claim about the world while honestly reporting uncertainty. This course builds the toolkit end to end — descriptive summaries and sampling, estimation and maximum likelihood, confidence intervals and the bootstrap, hypothesis testing, experiment design and A/B testing, regression and GLMs, and a first look at Bayesian inference. Every idea is paired with a hands-on analysis in Python (SciPy/statsmodels) or R.

PrereqsMDS-5 Probability
Code inPython (SciPy/statsmodels)R
7Units
42Lessons
126Worked examples

Course Outline

MDS-6 — units

Every lesson is self-contained: a full explanation, worked step-by-step examples (in Python and R/Julia), common mistakes, a try-it-yourself, and an interactive quiz. Jump to a unit:

Weeks 1-2 Unit 1: Descriptive Statistics & Sampling
distinguish-parameter-statisticcompute-center-spreadinterpret-quantiles-robustvisualize-distributionsevaluate-sampling-designexplain-sampling-distribution
Lecture
Populations, samples, and parameters vs. statistics

Inference starts by separating what we want to know (the population) from what we actually observe (the sample).

A population is the complete set of units you care about; a parameter is a fixed, usually unknown number describing it (e.g., the true mean income mu). Because we rarely measure everyone, we take a sample and compute a statistic (e.g., the sample mean x-bar) to estimate the parameter. The whole of statistical inference is using a known statistic to make a defensible, uncertainty-aware claim about an unknown parameter. Notation matters: Greek letters (mu, sigma, p) denote parameters, Latin letters (x-bar, s, p-hat) denote statistics. A statistic is random because it changes from sample to sample; a parameter is not. Confusing the two is the root cause of most beginner errors, because every confidence interval and hypothesis test is fundamentally a statement that connects a random statistic to a fixed parameter.

Worked Example 1

Problem. A city has 50,000 households. You survey 200 of them and find an average of 1.8 cars per household. Identify the population, sample, parameter, and statistic.

  1. Population: all 50,000 households in the city.
  2. Sample: the 200 surveyed households.
  3. Parameter: mu, the true mean cars per household across all 50,000 (unknown).
  4. Statistic: x-bar = 1.8, computed from the sample, used to estimate mu.

Answer. Population = 50,000 households; sample = 200 surveyed; parameter = mu (unknown true mean); statistic = x-bar = 1.8 cars.

Worked Example 2

Problem. From a sample, p-hat = 0.42 favor a policy. A report says 'the population proportion is 0.42.' What is wrong, and how should it be phrased?

  1. p-hat = 0.42 is a statistic, an estimate, not the parameter p.
  2. The true population proportion p is unknown; 0.42 is our best single guess.
  3. Correct phrasing: 'We estimate the population proportion at 0.42,' ideally with a margin of error.
  4. Python check that p-hat is just a sample mean of 0/1 data: import numpy as np; x = np.array([1,0,1,1,0]); print(x.mean()) -> 0.6.

Answer. The report conflates a statistic with the parameter; p-hat = 0.42 estimates the unknown p and should be reported with uncertainty.

Worked Example 3

Problem. Two analysts each draw a different sample of size 30 from the same population and get x-bar = 51.2 and x-bar = 48.7. Does this mean one is wrong?

  1. No. x-bar is a random statistic; different samples give different values even when mu is fixed.
  2. This variability is exactly sampling variability, quantified later by the standard error.
  3. Both estimate the same mu; neither is 'wrong,' they bracket the true value.
  4. Simulate: import numpy as np; rng=np.random.default_rng(0); pop=rng.normal(50,10,100000); print(pop[rng.integers(0,100000,30)].mean(), pop[rng.integers(0,100000,30)].mean()) -> two different means near 50.

Answer. Both are valid estimates of the same fixed mu; differing values reflect normal sampling variability, not error.

Common mistakes
  • Saying 'the parameter is 0.42' when 0.42 is a sample statistic — the parameter stays unknown; the statistic only estimates it.
  • Treating a parameter as random — the parameter is fixed; it is the statistic that varies from sample to sample.
  • Assuming a single sample 'is' the population — a sample is a partial, possibly biased window onto the population.
✎ Try it yourself

Problem. A factory produces millions of bolts. An inspector measures 150 bolts and finds a mean length of 20.04 mm with the goal of judging the true mean length. Label the population, sample, parameter, and statistic, and state which value is known.

Solution. Population = all bolts produced. Sample = the 150 measured bolts. Parameter = mu, the true mean length of all bolts (unknown). Statistic = x-bar = 20.04 mm (known, computed from the sample). The known value is the statistic x-bar = 20.04 mm; it is our estimate of the unknown parameter mu.

Measures of center, spread, and shape

Summaries compress a dataset into a few numbers describing where it sits, how spread out it is, and whether it is symmetric.

Center locates a typical value: the mean (sum divided by n) is the balance point but is pulled by outliers; the median (middle rank) and mode (most frequent) resist them. Spread measures variability: variance is the mean squared deviation from the mean, and standard deviation (its square root) is in original units. Shape describes asymmetry via skewness: right-skewed data have a long upper tail and mean > median; left-skewed have mean < median. In data science these summaries drive feature understanding, outlier detection, and the choice of model. A key subtlety is the divisor in variance: dividing by n - 1 (Bessel's correction) gives an unbiased estimate of the population variance from a sample, whereas dividing by n underestimates it. Always match center to spread: report mean with standard deviation, or median with IQR.

Worked Example 1

Problem. For the data 2, 4, 4, 6, 9, compute the mean, median, and mode.

  1. Mean = (2+4+4+6+9)/5 = 25/5 = 5.
  2. Sorted data: 2,4,4,6,9; the middle (3rd) value is the median = 4.
  3. Mode = most frequent value = 4 (appears twice).
  4. Python: import numpy as np; from scipy import stats; d=[2,4,4,6,9]; print(np.mean(d), np.median(d), stats.mode(d).mode) -> 5.0 4.0 4.

Answer. Mean = 5, median = 4, mode = 4.

Worked Example 2

Problem. Compute the sample standard deviation of 2, 4, 4, 6, 9.

  1. Mean = 5 (from Example 1).
  2. Squared deviations: (2-5)^2=9, (4-5)^2=1, (4-5)^2=1, (6-5)^2=1, (9-5)^2=16; sum = 28.
  3. Sample variance s^2 = 28 / (5-1) = 28/4 = 7 (divide by n-1).
  4. s = sqrt(7) = 2.646. Python: import numpy as np; print(np.std([2,4,4,6,9], ddof=1)) -> 2.6458.

Answer. Sample variance = 7, sample standard deviation = 2.646 (using n - 1).

Worked Example 3

Problem. Incomes (in thousands) are 30, 32, 35, 38, 200. Compare mean vs. median and describe the skew.

  1. Mean = (30+32+35+38+200)/5 = 335/5 = 67.
  2. Median = middle of sorted 30,32,35,38,200 = 35.
  3. Mean (67) far exceeds median (35), pulled up by the 200 outlier.
  4. Mean > median indicates right (positive) skew. Python: from scipy.stats import skew; print(skew([30,32,35,38,200])) -> ~1.49 (positive).

Answer. Mean = 67, median = 35; mean > median signals strong right skew driven by the outlier, so the median better represents a typical income.

Common mistakes
  • Dividing by n instead of n - 1 for a sample variance — use n - 1 (ddof=1) so the estimate is unbiased for the population variance.
  • Reporting only the mean for skewed data — pair the median with the IQR when outliers or skew are present.
  • Confusing variance and standard deviation — variance is in squared units; take the square root to return to the data's units.
✎ Try it yourself

Problem. For the values 10, 12, 12, 13, 18, compute the mean, median, sample standard deviation, and state the likely skew direction.

Solution. Mean = (10+12+12+13+18)/5 = 65/5 = 13. Median = middle of sorted = 12. Deviations from 13: -3,-1,-1,0,5; squares 9,1,1,0,25 sum to 36; s^2 = 36/(5-1) = 9; s = 3. Mean (13) > median (12), so mild right skew. In Python: np.mean, np.median, np.std(d, ddof=1) confirm 13, 12, 3.

Distributions, quantiles, and robust summaries

Quantiles slice a distribution by rank, giving robust summaries that ignore extreme values.

A quantile (or percentile) is the value below which a given fraction of data falls: the 0.25 quantile (Q1, 25th percentile) has a quarter of the data below it, and the median is the 0.5 quantile. The five-number summary (min, Q1, median, Q3, max) and the interquartile range IQR = Q3 - Q1 describe a distribution without assuming a shape. These are robust: a single huge outlier barely moves a quantile, unlike the mean. In data science, quantiles power percentile-based SLAs (e.g., p95 latency), winsorizing, and outlier rules. A common outlier fence flags points beyond Q1 - 1.5*IQR or Q3 + 1.5*IQR. Because quantile estimation depends on an interpolation convention, different tools can report slightly different values, so know which method your library uses.

Worked Example 1

Problem. For 1, 3, 5, 7, 9, 11, 13, find Q1, the median (Q2), Q3, and the IQR.

  1. Median (Q2) = middle of 7 values = 4th = 7.
  2. Lower half (below median): 1,3,5 -> Q1 = median = 3.
  3. Upper half: 9,11,13 -> Q3 = 11.
  4. IQR = Q3 - Q1 = 11 - 3 = 8. Python: import numpy as np; print(np.percentile([1,3,5,7,9,11,13],[25,50,75])) -> [4. 7. 10.] with linear interpolation.

Answer. Q1 = 3, median = 7, Q3 = 11, IQR = 8 (by the simple split method; library defaults may interpolate to Q1=4, Q3=10).

Worked Example 2

Problem. Latency data has p95 = 420 ms. Explain what that means for an SLA.

  1. p95 = the 95th percentile = the value below which 95% of requests fall.
  2. So 95% of requests complete in 420 ms or less; only 5% are slower.
  3. An SLA 'p95 < 500 ms' is met since 420 < 500.
  4. Python: import numpy as np; print(np.percentile(latencies, 95)) reports the p95 directly.

Answer. p95 = 420 ms means 95% of requests are at or under 420 ms; the SLA target of 500 ms is satisfied.

Worked Example 3

Problem. Using the 1.5*IQR rule, identify outliers in 4, 5, 6, 7, 8, 9, 40.

  1. With Q1 = 5 and Q3 = 9 (simple split of the six non-median values gives lower 4,5,6 -> Q1=5; upper 8,9,40 -> Q3=9), IQR = 9 - 5 = 4.
  2. Upper fence = Q3 + 1.5*IQR = 9 + 6 = 15.
  3. Lower fence = Q1 - 1.5*IQR = 5 - 6 = -1.
  4. 40 > 15, so 40 is flagged as an outlier; all others lie within [-1, 15].

Answer. 40 is the only outlier, since it exceeds the upper fence of 15.

Common mistakes
  • Assuming all software computes quantiles identically — interpolation conventions differ, so Q1/Q3 can vary slightly between tools.
  • Using mean-based summaries for heavy-tailed data — quantiles and IQR are far more robust to extreme values.
  • Treating any point beyond the IQR fence as an 'error' — the 1.5*IQR rule flags candidates, not guaranteed mistakes; investigate before removing.
✎ Try it yourself

Problem. For 2, 4, 6, 8, 10, 12, 100, compute Q1, Q3, the IQR, and use the 1.5*IQR rule to check whether 100 is an outlier.

Solution. Median = 8 (4th of 7). Lower half 2,4,6 -> Q1 = 4; upper half 10,12,100 -> Q3 = 12. IQR = 12 - 4 = 8. Upper fence = 12 + 1.5*8 = 24. Since 100 > 24, 100 is an outlier. (np.percentile may interpolate to Q1=5, Q3=11, IQR=6, fence=20; 100 is still an outlier.)

Visualizing data: histograms, boxplots, and ECDFs

Three core plots reveal a distribution's shape, spread, outliers, and exact percentiles.

A histogram bins values and shows counts as bars, revealing modality and skew; the bin width is a tuning choice that can hide or invent structure. A boxplot draws the five-number summary as a box (Q1 to Q3) with a median line and whiskers, marking 1.5*IQR outliers as points; it compresses spread and outliers into one compact glyph, excellent for comparing groups side by side. An empirical cumulative distribution function (ECDF) plots, for each x, the fraction of data <= x as a rising step function from 0 to 1; unlike a histogram it uses no binning, so it shows every data point and lets you read any percentile directly off the curve. Together these let a data scientist judge shape, detect outliers, and compare distributions before modeling.

Worked Example 1

Problem. Sketch what the ECDF value is at x = 5 for the data 1, 3, 5, 7, 9.

  1. ECDF(x) = fraction of observations <= x.
  2. Values <= 5 are 1, 3, 5 -> that is 3 of 5.
  3. ECDF(5) = 3/5 = 0.6.
  4. Python: import numpy as np; d=np.array([1,3,5,7,9]); print((d<=5).mean()) -> 0.6.

Answer. ECDF(5) = 0.6 — 60% of the data is at or below 5.

Worked Example 2

Problem. A boxplot has Q1 = 20, median = 25, Q3 = 40 with a point plotted at 75. Is 75 an outlier and why is it shown separately?

  1. IQR = Q3 - Q1 = 40 - 20 = 20.
  2. Upper whisker fence = Q3 + 1.5*IQR = 40 + 30 = 70.
  3. 75 > 70, so it lies beyond the fence and is drawn as an outlier point.
  4. Boxplots cap whiskers at the fence and plot beyond-fence values individually.

Answer. Yes, 75 exceeds the upper fence of 70, so the boxplot displays it as an individual outlier point.

Worked Example 3

Problem. A histogram of response times has a tall bar near 0 and a long thin tail to the right. Name the shape and which plot best reads the median.

  1. A long right tail with most mass on the left indicates right (positive) skew.
  2. Histograms show shape but reading an exact median is hard.
  3. An ECDF lets you find the median: locate where the curve crosses 0.5 and read the x-value.
  4. Python: import numpy as np; x=np.sort(times); F=np.arange(1,len(x)+1)/len(x); median=x[np.searchsorted(F,0.5)].

Answer. The distribution is right-skewed; the ECDF best pinpoints the median by reading the x where the curve reaches 0.5.

Common mistakes
  • Choosing too few or too many histogram bins — too few hides structure, too many adds noise; try several widths.
  • Reading a boxplot as showing the full data shape — it hides multimodality; pair it with a histogram or ECDF.
  • Forgetting an ECDF is a step function that runs from 0 to 1 — its height is a cumulative fraction, not a count or density.
✎ Try it yourself

Problem. For the data 2, 2, 4, 6, 6, 6, 9, give the ECDF value at x = 6 and describe what a histogram with bin width 2 starting at 1 would show as the tallest bar.

Solution. ECDF(6): values <= 6 are 2,2,4,6,6,6 = 6 of 7, so ECDF(6) = 6/7 ~ 0.857. Bins [1,3),[3,5),[5,7),[7,9),[9,11): counts are 2 (the two 2s), 1 (the 4), 3 (the three 6s), 0, 1 (the 9). The tallest bar is the [5,7) bin with 3 observations, reflecting the mode at 6.

Sampling designs and sources of bias

How you select a sample determines whether your estimates are trustworthy or systematically wrong.

A sampling design is the rule for choosing units. Simple random sampling gives every unit an equal chance and is the gold standard; stratified sampling draws within homogeneous subgroups to improve precision; cluster sampling samples whole groups for convenience. Bias is a systematic error that pushes estimates away from the truth regardless of sample size. Selection bias arises when the sampling method over- or under-represents parts of the population (e.g., convenience or volunteer samples); nonresponse bias arises when non-responders differ from responders; survivorship bias arises when only 'surviving' units are observable. Crucially, bias is not cured by a bigger sample — a large biased sample is confidently wrong. For data scientists, recognizing how data was collected is often more important than the analysis applied to it.

Worked Example 1

Problem. An app surveys only users who opened it this week to estimate satisfaction of all registered users. Name the bias.

  1. The sample frame excludes inactive/churned users.
  2. Active users likely differ systematically (more satisfied) than inactive ones.
  3. This systematically over-represents satisfied users.
  4. This is selection bias (a form of coverage/undercoverage bias).

Answer. Selection bias: sampling only active users excludes dissatisfied or churned users, inflating the satisfaction estimate.

Worked Example 2

Problem. A population is 70% region A and 30% region B. To estimate mean spending precisely, design a stratified sample of size 100.

  1. Stratify by region to ensure both are represented proportionally.
  2. Proportional allocation: A gets 0.70*100 = 70 units; B gets 0.30*100 = 30 units.
  3. Sample randomly within each stratum, then combine.
  4. Python sketch: import numpy as np; sA = np.random.choice(regionA, 70, replace=False); sB = np.random.choice(regionB, 30, replace=False).

Answer. Draw 70 random units from region A and 30 from region B (proportional stratified sampling), reducing variance versus a simple random sample.

Worked Example 3

Problem. A WWII analysis examined returning planes' bullet holes to decide where to add armor and proposed reinforcing the most-hit areas. What bias is at play?

  1. Only planes that survived were available to inspect.
  2. Holes appear where a plane can be hit and still return.
  3. Areas with no holes on survivors are likely fatal when hit, since those planes did not return.
  4. This is survivorship bias; armor should go where survivors show NO holes.

Answer. Survivorship bias: the sample omits downed planes, so armor belongs on the unhit areas of survivors, not the hit areas.

Common mistakes
  • Believing a larger sample fixes bias — bias is systematic; more data just makes a biased estimate more precisely wrong.
  • Equating 'random-looking' convenience samples with random samples — convenience and volunteer samples carry selection bias.
  • Ignoring nonresponse — if non-responders differ from responders, the observed responses are not representative.
✎ Try it yourself

Problem. A radio station asks listeners to call in to vote on a city policy, and 5,000 people respond. Why might this estimate of citywide support be biased, and what design would be better?

Solution. Call-in voting is a self-selected (volunteer) sample, so it suffers selection and nonresponse bias: only listeners with strong opinions and access to the station respond, who may differ systematically from the general population. The large n = 5,000 does not fix this. A better design is a simple random or stratified random sample of all residents (e.g., random-digit dialing across the city with follow-ups to reduce nonresponse), giving every resident a known, roughly equal chance of inclusion.

Sampling distributions and the standard error

A statistic computed from random samples has its own distribution; its spread is the standard error.

If you could draw every possible sample of size n and compute a statistic (say x-bar) for each, the distribution of those values is the sampling distribution. Its standard deviation is the standard error (SE), which measures how much the statistic bounces around the parameter from sample to sample. For the sample mean, SE = sigma / sqrt(n), so precision improves with the square root of sample size: quadrupling n halves the SE. The Central Limit Theorem says that for large n the sampling distribution of the mean is approximately normal regardless of the population's shape, which justifies normal-based confidence intervals and tests. When sigma is unknown we estimate SE with s / sqrt(n). The SE is the bridge from a single sample to a statement of uncertainty, making it the backbone of all the inference that follows.

Worked Example 1

Problem. A population has sigma = 12. For a sample of n = 36, find the standard error of the mean.

  1. SE = sigma / sqrt(n).
  2. sqrt(36) = 6.
  3. SE = 12 / 6 = 2.
  4. Python: import numpy as np; print(12/np.sqrt(36)) -> 2.0.

Answer. The standard error of the mean is 2.

Worked Example 2

Problem. With sigma = 12, how large must n be to cut the standard error to 1?

  1. Set SE = sigma / sqrt(n) = 1 -> sqrt(n) = sigma = 12.
  2. n = 12^2 = 144.
  3. Check: 12 / sqrt(144) = 12/12 = 1.
  4. Note quadrupling n (from 36 to 144) halved the SE from 2 to 1.

Answer. n = 144 yields SE = 1; halving the SE requires quadrupling the sample size.

Worked Example 3

Problem. A sample of n = 50 has s = 8 (sigma unknown). Estimate the SE of the mean and explain why CLT matters here.

  1. With sigma unknown, estimate SE = s / sqrt(n) = 8 / sqrt(50).
  2. sqrt(50) ~ 7.071, so SE ~ 8 / 7.071 ~ 1.131.
  3. By the CLT, with n = 50 the sampling distribution of x-bar is approximately normal even if the data are skewed.
  4. Python: import numpy as np; print(8/np.sqrt(50)) -> 1.1314.

Answer. Estimated SE ~ 1.131; the CLT lets us treat x-bar as approximately normal at n = 50, enabling normal/t-based inference.

Common mistakes
  • Confusing the standard deviation with the standard error — sigma describes spread of individuals; SE = sigma/sqrt(n) describes spread of the statistic.
  • Thinking SE shrinks like 1/n — it shrinks like 1/sqrt(n), so you must quadruple n to halve it.
  • Assuming the CLT makes the data normal — it makes the sampling distribution of the mean approximately normal, not the raw data.
✎ Try it yourself

Problem. A population has sigma = 20. (a) Find the SE of the mean for n = 100. (b) What n gives an SE of 1? (c) If only s = 18 is known with n = 81, estimate the SE.

Solution. (a) SE = 20/sqrt(100) = 20/10 = 2. (b) Set 20/sqrt(n) = 1 -> sqrt(n) = 20 -> n = 400. (c) With sigma unknown, SE ~ s/sqrt(n) = 18/sqrt(81) = 18/9 = 2. In Python: 20/np.sqrt(100)=2.0, (20)**2=400, 18/np.sqrt(81)=2.0.

Key terms
  • Population vs. sample — the full set of units of interest versus the subset we actually observe and measure.
  • Parameter vs. statistic — a fixed (usually unknown) population quantity versus a value computed from the sample to estimate it.
  • Mean, median, mode — three measures of center; the median and mode resist outliers more than the mean.
  • Variance and standard deviation — measures of spread around the mean; the standard deviation is in the original units.
  • Quantile (percentile) — the value below which a given fraction of the data falls; the median is the 50th percentile.
  • Interquartile range (IQR) — the spread of the middle 50% of data, Q3 minus Q1, a robust measure of variability.
  • Sampling distribution — the distribution of a statistic across all possible samples of a given size.
  • Standard error — the standard deviation of a statistic's sampling distribution; it shrinks as sample size grows.
Assignment · Profile a real dataset and its sampling behavior

Load a public dataset (e.g., a CSV of city temperatures or housing prices) in Python or R and compute mean, median, standard deviation, IQR, and skewness. Produce a histogram, boxplot, and ECDF and describe the distribution's shape and any outliers. Then draw 1000 random samples of size n=30 from the data, record each sample mean, and plot the resulting sampling distribution alongside its standard error.

Deliverable · A notebook or script with the summary table, the three distribution plots, and the sampling-distribution plot, plus a paragraph interpreting the shape and the observed standard error.

Quiz · 5 questions
  1. 1. Which statement correctly distinguishes a parameter from a statistic?

  2. 2. A dataset of incomes has a few very large values. Which measure of center is most resistant to those outliers?

  3. 3. As the sample size n increases, the standard error of the sample mean:

  4. 4. A convenience sample of volunteers is used to estimate average exercise time in a city. The main concern is:

  5. 5. The interquartile range (IQR) measures:

You'll be able to

I can summarize a dataset with appropriate measures of center, spread, and shape and choose robust alternatives when needed.

I can evaluate a sampling design for bias and explain how the sampling distribution of a statistic gives rise to standard error.

I can produce and read histograms, boxplots, and ECDFs to describe a distribution.

Weeks 3-4 Unit 2: Estimation & Maximum Likelihood
define-estimator-propertiesdecompose-mseapply-method-of-momentsbuild-likelihood-functionderive-mlecompute-mle-numerically
Lecture
Point estimation: bias, variance, and consistency

An estimator is judged by whether it is centered on the truth, how much it varies, and whether it improves with data.

A point estimator theta-hat is a function of the sample used to guess a parameter theta. Three properties characterize it. Bias = E[theta-hat] - theta measures systematic error: an unbiased estimator is centered on the truth on average. Variance = Var(theta-hat) measures how much it scatters across samples. Consistency means theta-hat converges in probability to theta as n grows, so with enough data it lands on the truth. These can trade off: a slightly biased estimator can have much smaller variance and thus be preferable. For data scientists this framework explains why we choose s^2 with n - 1 (unbiased for variance) and why the sample mean is both unbiased and consistent for mu. Crucially, bias and variance are properties of the estimator's sampling distribution, not of any one estimate.

Worked Example 1

Problem. Show the sample mean x-bar is an unbiased estimator of the population mean mu.

  1. x-bar = (1/n) * sum(X_i), with each E[X_i] = mu.
  2. E[x-bar] = (1/n) * sum(E[X_i]) = (1/n) * (n*mu).
  3. = mu, so bias = E[x-bar] - mu = 0.
  4. Simulate: import numpy as np; rng=np.random.default_rng(0); print(np.mean([rng.normal(5,2,10).mean() for _ in range(100000)])) -> ~5.0.

Answer. E[x-bar] = mu, so the sample mean is unbiased for the population mean.

Worked Example 2

Problem. Why does dividing the sum of squared deviations by n (instead of n - 1) give a biased variance estimator?

  1. The deviations are taken from x-bar, not the true mu, so they are slightly too small.
  2. E[ (1/n) sum (X_i - x-bar)^2 ] = ((n-1)/n) * sigma^2, which is below sigma^2.
  3. Dividing by n - 1 instead corrects this: E[s^2] = sigma^2 (unbiased).
  4. Simulate: import numpy as np; rng=np.random.default_rng(1); samp=[rng.normal(0,1,5) for _ in range(200000)]; print(np.mean([x.var(ddof=0) for x in samp]), np.mean([x.var(ddof=1) for x in samp])) -> ~0.8 vs ~1.0.

Answer. Dividing by n underestimates sigma^2 by a factor (n-1)/n; using n - 1 removes the bias.

Worked Example 3

Problem. Estimator A has bias 0 and variance 10; estimator B has bias 1 and variance 2. Which has smaller MSE, and is the sample mean consistent for mu?

  1. MSE = variance + bias^2.
  2. MSE_A = 10 + 0^2 = 10; MSE_B = 2 + 1^2 = 3.
  3. B has the smaller MSE despite being biased — a bias-variance tradeoff.
  4. The sample mean is consistent: Var(x-bar) = sigma^2/n -> 0 as n -> infinity, so x-bar -> mu.

Answer. Estimator B (MSE = 3) beats A (MSE = 10); and yes, x-bar is consistent because its variance vanishes as n grows.

Common mistakes
  • Equating unbiased with 'best' — a biased estimator with much lower variance can have smaller MSE and be preferable.
  • Confusing consistency with unbiasedness — consistency is about large-n convergence; an estimator can be biased yet consistent.
  • Treating bias/variance as properties of one estimate — they describe the estimator's behavior over many samples.
✎ Try it yourself

Problem. Estimator P of theta has bias = 2 and variance = 4; estimator Q has bias = 0 and variance = 9. Compute each MSE and say which you would prefer, and state whether either being biased rules it out.

Solution. MSE = variance + bias^2. MSE_P = 4 + 2^2 = 8. MSE_Q = 9 + 0^2 = 9. P has the smaller MSE (8 < 9), so prefer P even though it is biased; bias alone does not disqualify an estimator, because lower variance can more than compensate.

Mean squared error and the bias-variance tradeoff

MSE bundles bias and variance into one number, exposing the central tradeoff of estimation and modeling.

Mean squared error is MSE(theta-hat) = E[(theta-hat - theta)^2], the expected squared distance from the truth. It decomposes exactly as MSE = Var(theta-hat) + Bias(theta-hat)^2. This identity is the bias-variance tradeoff: reducing one often raises the other, and the best estimator (or model) minimizes their sum, not either alone. In machine learning the same decomposition governs over- and under-fitting: a flexible model has low bias but high variance (overfits), while a rigid model has high bias but low variance (underfits). Regularization deliberately adds bias to cut variance and lower total error. Understanding MSE lets a data scientist reason about why a more complex model can predict worse on new data even though it fits the training data better.

Worked Example 1

Problem. An estimator has bias = 3 and variance = 16. Compute its MSE.

  1. MSE = Variance + Bias^2.
  2. = 16 + 3^2 = 16 + 9.
  3. = 25.
  4. Python: var=16; bias=3; print(var + bias**2) -> 25.

Answer. MSE = 25.

Worked Example 2

Problem. Consider estimating mu with the shrinkage estimator c * x-bar for constant c in [0,1], where Var(x-bar) = sigma^2/n. Express its bias and variance.

  1. E[c*x-bar] = c*mu, so bias = c*mu - mu = (c - 1)*mu.
  2. Var(c*x-bar) = c^2 * Var(x-bar) = c^2 * sigma^2/n.
  3. MSE = c^2 * sigma^2/n + (c-1)^2 * mu^2.
  4. Shrinking c below 1 lowers variance (c^2 factor) but introduces bias — the tradeoff in formula form.

Answer. Bias = (c - 1)*mu, Variance = c^2 * sigma^2/n, MSE = c^2 sigma^2/n + (c-1)^2 mu^2; c < 1 trades added bias for reduced variance.

Worked Example 3

Problem. A flexible model has training MSE 0.5 but test MSE 8; a simpler model has training MSE 3 and test MSE 4. Interpret via bias-variance.

  1. The flexible model fits training data well (low bias) but generalizes poorly: large test error implies high variance — overfitting.
  2. The simpler model has higher training error (more bias) but lower test error: lower variance.
  3. Total expected error on new data is what matters: 4 < 8.
  4. Prefer the simpler model; its bias-variance sum (test MSE) is smaller.

Answer. The flexible model overfits (low bias, high variance); the simpler model wins on test MSE (4 vs 8), illustrating the bias-variance tradeoff.

Common mistakes
  • Forgetting to square the bias in MSE — it is variance + bias^2, not variance + bias.
  • Judging a model by training error alone — low training error can hide high variance that shows up as large test error.
  • Believing more flexibility always helps — added flexibility lowers bias but can raise variance and total MSE.
✎ Try it yourself

Problem. Estimator R has variance 5 and bias 4; estimator S has variance 18 and bias 1. Compute both MSEs and explain which generalizes better.

Solution. MSE_R = 5 + 4^2 = 5 + 16 = 21. MSE_S = 18 + 1^2 = 18 + 1 = 19. S has the lower MSE (19 < 21), so it has smaller expected squared error and generalizes better, even though R has lower variance — its large bias dominates.

The method of moments

Match sample moments to their theoretical formulas and solve for the parameters — a quick, intuitive estimator.

The method of moments (MoM) estimates parameters by equating sample moments to their model-implied counterparts and solving. The k-th sample moment is (1/n) sum X_i^k; the first is the sample mean, the second relates to variance. If a distribution has p parameters, you write p moment equations and solve the system. MoM is computationally cheap and needs no optimization, making it a fast starting point or initializer for MLE. It is consistent but generally not as efficient (higher variance) as the MLE, and it can produce out-of-range estimates. In data science MoM is handy for quick distribution fits, sanity checks, and providing good starting values for numerical likelihood maximization.

Worked Example 1

Problem. Data are exponential with rate lambda; the mean is 1/lambda. Given x-bar = 4, find the MoM estimate of lambda.

  1. Theoretical first moment: E[X] = 1/lambda.
  2. Set sample mean equal: x-bar = 1/lambda -> 4 = 1/lambda.
  3. Solve: lambda-hat = 1/x-bar = 1/4 = 0.25.
  4. Python: import numpy as np; print(1/np.mean(data)) gives the MoM rate.

Answer. lambda-hat = 1/x-bar = 0.25.

Worked Example 2

Problem. For a Poisson(lambda), E[X] = lambda. A sample has counts with mean 3.2. Give the MoM estimate and note its relation to the MLE.

  1. Theoretical first moment: E[X] = lambda.
  2. Set x-bar = lambda -> lambda-hat = 3.2.
  3. For Poisson the MLE is also x-bar, so MoM and MLE coincide here.
  4. Python: import numpy as np; print(np.mean(counts)) -> 3.2.

Answer. lambda-hat = 3.2; for the Poisson, MoM and MLE give the same estimate, x-bar.

Worked Example 3

Problem. A Uniform(0, theta) has E[X] = theta/2. With x-bar = 5, find the MoM estimate, and note a flaw if the sample max exceeds it.

  1. E[X] = theta/2; set x-bar = theta/2 -> theta-hat = 2*x-bar = 10.
  2. But if an observed value, say 11, exceeds theta-hat = 10, the estimate is impossible (data outside the assumed range).
  3. MoM ignores the support constraint; the MLE here is the sample maximum, which never has this flaw.
  4. Python: import numpy as np; print(2*np.mean(data), np.max(data)) lets you compare and catch the conflict.

Answer. theta-hat = 2*x-bar = 10, but MoM can yield an estimate below the observed maximum, an impossibility the MLE (sample max) avoids.

Common mistakes
  • Forgetting to use enough moment equations — p parameters need p moment conditions to solve uniquely.
  • Trusting MoM blindly — it can produce impossible (out-of-support) estimates that respect no constraints.
  • Assuming MoM equals MLE in general — they coincide only sometimes (e.g., Poisson); usually MLE is more efficient.
✎ Try it yourself

Problem. A Geometric distribution (number of trials to first success) has mean 1/p. A sample of waiting times has mean x-bar = 5. Find the MoM estimate of p, and state whether it lies in a valid range.

Solution. The first moment is E[X] = 1/p. Setting x-bar = 1/p gives 5 = 1/p, so p-hat = 1/x-bar = 1/5 = 0.2. Since 0 < 0.2 < 1, the estimate is a valid probability. In Python: 1/np.mean(data) = 0.2.

The likelihood function and log-likelihood

The likelihood reinterprets the data's joint density as a function of the parameter, scoring how well each parameter explains the data.

For i.i.d. data x_1,...,x_n from a model with density f(x; theta), the likelihood is L(theta) = product f(x_i; theta) — the same joint density, but viewed as a function of theta with the data fixed. It answers: how plausible is each theta given what we observed? Because products of many small numbers underflow and are awkward to differentiate, we take the log-likelihood ell(theta) = sum log f(x_i; theta), turning the product into a sum. The log is monotonic, so it has the same maximizer as L. The log-likelihood is the central object linking estimation, hypothesis testing (likelihood-ratio tests), and Bayesian inference (where it multiplies the prior). Mastering how to write down a log-likelihood for a model is the key skill that unlocks MLE.

Worked Example 1

Problem. Write the log-likelihood for n i.i.d. Bernoulli(p) observations with s successes.

  1. Each pmf: f(x_i; p) = p^{x_i} (1-p)^{1-x_i}.
  2. Likelihood: L(p) = product = p^{sum x_i} (1-p)^{n - sum x_i} = p^s (1-p)^{n-s}.
  3. Log-likelihood: ell(p) = s*log(p) + (n - s)*log(1 - p).
  4. Python: import numpy as np; ell = lambda p: s*np.log(p)+(n-s)*np.log(1-p).

Answer. ell(p) = s*log(p) + (n - s)*log(1 - p).

Worked Example 2

Problem. For n i.i.d. Poisson(lambda) counts with sum x_i = T, write the log-likelihood (dropping constants).

  1. pmf: f(x_i; lambda) = e^{-lambda} lambda^{x_i} / x_i!.
  2. L(lambda) = product = e^{-n*lambda} lambda^{T} / product(x_i!).
  3. Take logs: ell(lambda) = -n*lambda + T*log(lambda) - sum log(x_i!).
  4. The last term is constant in lambda, so for maximizing: ell(lambda) ~ -n*lambda + T*log(lambda).

Answer. ell(lambda) = -n*lambda + T*log(lambda) - const, where T = sum x_i.

Worked Example 3

Problem. Two values of p give L(0.4) = 0.012 and L(0.6) = 0.030 for some Bernoulli data. Which p is more likely, and why compare log-likelihoods instead?

  1. Higher likelihood means the parameter better explains the data: 0.030 > 0.012, so p = 0.6 is more likely.
  2. log(0.012) ~ -4.42 and log(0.030) ~ -3.51; the larger log-likelihood (-3.51) flags the same winner.
  3. Logs are used because multiplying many densities underflows toward 0, while summing logs is numerically stable.
  4. Python: import numpy as np; print(np.log(0.012), np.log(0.030)) -> -4.4228 -3.5066.

Answer. p = 0.6 is more likely (higher L and higher log-L); logs are preferred for numerical stability and easier differentiation.

Common mistakes
  • Treating the likelihood as a probability distribution over theta — it is not normalized to integrate to 1 over theta.
  • Multiplying raw densities for large n — this underflows; sum log-densities instead.
  • Forgetting that constants independent of theta can be dropped — they do not affect where the maximum occurs.
✎ Try it yourself

Problem. For n i.i.d. Exponential(lambda) observations with density f(x;lambda)=lambda*e^{-lambda x}, write the log-likelihood in terms of n and S = sum x_i.

Solution. L(lambda) = product lambda*e^{-lambda x_i} = lambda^n * e^{-lambda * sum x_i} = lambda^n e^{-lambda S}. Taking the log: ell(lambda) = n*log(lambda) - lambda*S. In Python: ell = lambda lam: n*np.log(lam) - lam*S.

Maximum likelihood estimation (MLE)

The MLE is the parameter that maximizes the (log-)likelihood, found by setting its derivative to zero.

Maximum likelihood estimation chooses theta-hat to maximize the log-likelihood ell(theta) — the value under which the observed data are most probable. Analytically, you differentiate ell with respect to theta, set the score equation ell'(theta) = 0, solve, and confirm it is a maximum (second derivative negative). MLEs have excellent large-sample properties: they are consistent, asymptotically unbiased, asymptotically normal, and efficient (achieving the smallest possible variance, the Cramer-Rao bound). The curvature of the log-likelihood at the maximum (the observed information) yields standard errors. These properties are why MLE underlies logistic regression, GLMs, and most modern statistical models. The recipe is always the same: write ell, differentiate, solve the score equation, verify a maximum.

Worked Example 1

Problem. Derive the MLE of p for Bernoulli data with s successes in n trials.

  1. ell(p) = s*log(p) + (n - s)*log(1 - p).
  2. Differentiate: ell'(p) = s/p - (n - s)/(1 - p).
  3. Set to 0: s/p = (n - s)/(1 - p) -> s(1 - p) = (n - s)p -> s = n*p.
  4. Solve: p-hat = s/n (the sample proportion).

Answer. p-hat = s/n, the sample proportion of successes.

Worked Example 2

Problem. Derive the MLE of lambda for n i.i.d. Exponential(lambda) data with S = sum x_i.

  1. ell(lambda) = n*log(lambda) - lambda*S.
  2. Differentiate: ell'(lambda) = n/lambda - S.
  3. Set to 0: n/lambda = S -> lambda-hat = n/S = 1/x-bar.
  4. Second derivative -n/lambda^2 < 0 confirms a maximum. Python: import numpy as np; print(1/np.mean(data)).

Answer. lambda-hat = n/S = 1/x-bar, the reciprocal of the sample mean.

Worked Example 3

Problem. Derive the MLE of mu for n i.i.d. Normal(mu, sigma^2) with sigma^2 known.

  1. ell(mu) = -(1/(2 sigma^2)) * sum (x_i - mu)^2 + const.
  2. Differentiate w.r.t. mu: ell'(mu) = (1/sigma^2) * sum (x_i - mu).
  3. Set to 0: sum (x_i - mu) = 0 -> sum x_i = n*mu.
  4. Solve: mu-hat = (1/n) sum x_i = x-bar.

Answer. mu-hat = x-bar; the MLE of a normal mean is the sample mean.

Common mistakes
  • Maximizing the likelihood without checking the second-order condition — confirm the critical point is a maximum, not a minimum or saddle.
  • Forgetting parameter constraints — e.g., p must lie in [0,1] and lambda > 0; check the solution is valid.
  • Assuming the MLE is always unbiased — it is only asymptotically unbiased; e.g., the MLE of sigma^2 divides by n, which is biased in small samples.
✎ Try it yourself

Problem. Derive the MLE of lambda for n i.i.d. Poisson(lambda) counts with T = sum x_i.

Solution. From ell(lambda) = -n*lambda + T*log(lambda) - const, differentiate: ell'(lambda) = -n + T/lambda. Set to 0: T/lambda = n -> lambda-hat = T/n = x-bar. The second derivative -T/lambda^2 < 0 confirms a maximum. So the Poisson MLE is the sample mean: lambda-hat = x-bar. In Python: np.mean(counts).

MLE in practice: optimization in code

When no closed form exists, maximize the log-likelihood numerically with an optimizer.

Many models (logistic regression, custom mixtures) have no closed-form MLE, so we maximize ell(theta) numerically. The standard trick is to minimize the negative log-likelihood (NLL), since optimizers minimize by default. Tools like scipy.optimize.minimize or R's optim use gradient-based methods (e.g., BFGS) that iterate from a starting guess toward the optimum. Good practice includes: providing sensible starting values (method-of-moments estimates work well), optimizing on an unconstrained scale (e.g., optimize log(lambda) to keep lambda > 0), and checking convergence flags. The Hessian (second derivatives) at the optimum estimates the information matrix, whose inverse gives the parameter covariance and hence standard errors. This numerical approach is how virtually all real statistical and ML models are actually fit.

Worked Example 1

Problem. Write Python to numerically find the MLE rate of exponential data and confirm it matches 1/x-bar.

  1. Define NLL: import numpy as np; from scipy.optimize import minimize
  2. nll = lambda lam, x: -(len(x)*np.log(lam[0]) - lam[0]*x.sum())
  3. res = minimize(nll, x0=[1.0], args=(data,), method='Nelder-Mead')
  4. Compare: print(res.x[0], 1/np.mean(data)) -> the two values match to optimizer tolerance.

Answer. The optimizer's lambda-hat equals 1/x-bar, confirming the closed-form MLE numerically.

Worked Example 2

Problem. Why optimize log(lambda) instead of lambda directly, and how do you recover lambda?

  1. lambda must be positive; an unconstrained optimizer might step to lambda <= 0, breaking log(lambda).
  2. Reparameterize: let theta = log(lambda), so lambda = exp(theta) is always positive for any real theta.
  3. Optimize the NLL over theta unconstrained, then set lambda-hat = exp(theta-hat).
  4. Python: nll = lambda th, x: -(len(x)*th[0] - np.exp(th[0])*x.sum()); recover np.exp(res.x[0]).

Answer. Optimizing on the log scale keeps lambda > 0; recover lambda-hat = exp(theta-hat).

Worked Example 3

Problem. After fitting by MLE, how do you get a standard error for the estimate using the Hessian?

  1. At the optimum, the Hessian H of the NLL approximates the observed information matrix.
  2. The parameter covariance is the inverse Hessian: Cov ~ H^{-1}.
  3. The standard error is the square root of the relevant diagonal entry: SE = sqrt((H^{-1})_ii).
  4. Python: import numpy as np; res = minimize(nll, x0, args=(data,)); cov = res.hess_inv; se = np.sqrt(np.diag(cov)) (for BFGS, hess_inv is provided).

Answer. Invert the Hessian of the NLL at the optimum; the square roots of its diagonal are the parameter standard errors.

Common mistakes
  • Maximizing the log-likelihood directly with a minimizer — minimize the negative log-likelihood instead.
  • Ignoring parameter bounds — optimize on a transformed (unconstrained) scale or pass bounds so estimates stay valid.
  • Trusting results without checking convergence — always inspect the optimizer's success flag and try multiple starting points.
✎ Try it yourself

Problem. Outline Python code to find the MLE of the Poisson rate by numerical optimization and verify it equals x-bar.

Solution. import numpy as np; from scipy.optimize import minimize. Define the negative log-likelihood (dropping the constant log factorial term): nll = lambda lam, x: -(-len(x)*lam[0] + x.sum()*np.log(lam[0])). Then res = minimize(nll, x0=[1.0], args=(counts,), method='Nelder-Mead'). Finally print(res.x[0], np.mean(counts)); the optimizer's estimate equals the sample mean x-bar, matching the closed-form Poisson MLE.

Key terms
  • Estimator — a rule or formula that maps sample data to an estimate of a parameter.
  • Bias — the difference between an estimator's expected value and the true parameter; an unbiased estimator has zero bias.
  • Consistency — the property that an estimator converges to the true parameter as the sample size grows.
  • Mean squared error (MSE) — the expected squared error of an estimator, equal to variance plus bias squared.
  • Method of moments — estimating parameters by setting sample moments equal to theoretical moments and solving.
  • Likelihood function — the joint probability (or density) of the observed data viewed as a function of the parameters.
  • Log-likelihood — the natural logarithm of the likelihood; maximized in place of the likelihood for numerical convenience.
  • Maximum likelihood estimate (MLE) — the parameter value that makes the observed data most probable under the model.
Assignment · Fit a distribution two ways

Simulate (or load) data you believe is exponentially or Poisson distributed. Derive the MLE of the rate parameter by hand, then implement it in Python or R and compare it to the method-of-moments estimate. Finally, use a numerical optimizer (scipy.optimize or R's optim) to maximize the log-likelihood directly and confirm it matches your closed-form MLE.

Deliverable · A short report showing the hand-derived MLE, the method-of-moments estimate, and the optimizer result side by side, with a note on whether the estimates agree and why.

Quiz · 5 questions
  1. 1. An estimator is unbiased if:

  2. 2. Mean squared error of an estimator decomposes into:

  3. 3. Why is the log-likelihood maximized instead of the likelihood itself?

  4. 4. For n i.i.d. observations from a Bernoulli(p) distribution, the MLE of p is:

  5. 5. The method of moments estimates parameters by:

You'll be able to

I can describe an estimator's bias, variance, and consistency and use mean squared error to compare estimators.

I can write down a likelihood function for a model and find the maximum likelihood estimate analytically or numerically.

I can apply the method of moments as an alternative estimation strategy.

Weeks 5-6 Unit 3: Confidence Intervals & the Bootstrap
interpret-confidence-intervalconstruct-mean-intervalconstruct-proportion-intervalplan-sample-sizeapply-bootstrap-resamplingbuild-bootstrap-interval
Lecture
What a confidence interval really means

A confidence interval is a procedure with a long-run coverage guarantee, not a probability about one fixed interval.

A confidence interval (CI) is a range computed from data designed so that, if the sampling and computation were repeated many times, a stated proportion (e.g., 95%) of the resulting intervals would contain the true parameter. The confidence level describes the reliability of the method, not the probability that the one interval you computed contains the parameter — once computed, that interval either does or does not contain the fixed parameter, so the probability is 0 or 1. The correct frequentist statement is about the procedure across repetitions. This distinction matters in data science because stakeholders routinely misread '95% CI' as 'a 95% chance the truth is in this range,' which is a Bayesian-style claim that requires a prior. Coverage is the property we actually want: the realized capture rate should match the nominal level.

Worked Example 1

Problem. A 95% CI for a mean is [12.1, 15.3]. Is the statement 'there is a 95% probability mu is between 12.1 and 15.3' correct?

  1. mu is a fixed (unknown) constant; the interval [12.1, 15.3] is also now fixed.
  2. A fixed interval either contains the fixed mu or it does not — probability 0 or 1.
  3. The 95% refers to the long-run procedure, not this single realized interval.
  4. Correct phrasing: 'the procedure that produced this interval captures mu 95% of the time across repeated samples.'

Answer. No; the 95% describes the method's long-run coverage, not the probability that this specific interval contains mu.

Worked Example 2

Problem. You simulate 1000 datasets and build a 95% CI for the known true mean each time. Roughly how many should contain the truth, and what does that demonstrate?

  1. Each interval independently has a 95% chance of capturing mu by construction.
  2. Expected number capturing mu = 0.95 * 1000 = 950.
  3. Random variation means you might see, say, 948 or 953.
  4. Python: import numpy as np; rng=np.random.default_rng(0); cover=0
    for _ in range(1000):
    x=rng.normal(5,2,30); m=x.mean(); se=x.std(ddof=1)/np.sqrt(30)
    if m-1.96*se<=5<=m+1.96*se: cover+=1
    print(cover) -> a number near 950.

Answer. About 950 of 1000 intervals should contain the true mean, demonstrating the 95% coverage of the procedure.

Worked Example 3

Problem. A team reports 'we are 95% confident the click rate is between 4.8% and 5.6%.' Rewrite this so it is technically accurate but still readable.

  1. Avoid implying a probability about the fixed parameter in this one interval.
  2. Frame it as a property of the estimation method.
  3. Acceptable practitioner phrasing keeps 'confident' but anchors it to repetition.
  4. Example: 'Our method yields intervals that capture the true click rate 95% of the time; this run gives 4.8% to 5.6%.'

Answer. 'Using a procedure that captures the true rate in 95% of repeated samples, this sample's interval is 4.8% to 5.6%' — accurate and clear.

Common mistakes
  • Saying 'there is a 95% probability the parameter is in this interval' — the parameter is fixed; 95% is the long-run coverage of the method.
  • Claiming 95% of the data lies in the CI — a CI bounds a parameter (e.g., the mean), not 95% of individual observations.
  • Thinking a wider interval is 'worse' — width reflects honest uncertainty; a too-narrow interval with poor coverage is the real problem.
✎ Try it yourself

Problem. An analyst computes a 90% CI of [0.30, 0.38] for a proportion and writes 'p has a 90% chance of being in [0.30, 0.38].' State why this is technically wrong and give a correct one-sentence interpretation.

Solution. It is wrong because p is a fixed unknown constant and the computed interval is also fixed, so p is either in it or not (probability 0 or 1); the 90% is a property of the procedure, not this interval. Correct interpretation: 'If we repeated the sampling and interval construction many times, about 90% of the intervals produced would contain the true proportion p, and this particular interval is [0.30, 0.38].'

Intervals for a mean using the normal and t distributions

Build a mean interval as estimate plus-or-minus a critical value times the standard error, using t when sigma is unknown.

A confidence interval for a mean is x-bar +/- (critical value) * SE. When the population sigma is known (rare), the critical value is a z from the normal (1.96 for 95%) and SE = sigma/sqrt(n). When sigma is unknown (the usual case), we estimate it with s and use the t-distribution with n - 1 degrees of freedom, whose heavier tails widen the interval to account for estimating sigma. As n grows the t critical value approaches the z value, so the two methods converge. The interval requires either approximately normal data or a large enough n for the CLT to make x-bar approximately normal. This is the workhorse interval of applied statistics: reporting a mean estimate with a t-based interval is the default way to quantify uncertainty about a population average.

Worked Example 1

Problem. n = 25, x-bar = 50, s = 10. Build a 95% t-interval for the mean.

  1. SE = s/sqrt(n) = 10/sqrt(25) = 10/5 = 2.
  2. df = n - 1 = 24; t* for 95% ~ 2.064. Python: from scipy import stats; stats.t.ppf(0.975, 24) -> 2.0639.
  3. Margin = 2.064 * 2 = 4.128.
  4. Interval = 50 +/- 4.128 = [45.87, 54.13].

Answer. 95% CI = [45.87, 54.13].

Worked Example 2

Problem. With sigma known = 10, n = 25, x-bar = 50, build the 95% z-interval and compare to the t-interval above.

  1. SE = sigma/sqrt(n) = 10/5 = 2.
  2. z* for 95% = 1.96; margin = 1.96 * 2 = 3.92.
  3. Interval = 50 +/- 3.92 = [46.08, 53.92].
  4. It is narrower than the t-interval [45.87, 54.13] because z (1.96) < t (2.064): knowing sigma removes uncertainty.

Answer. z-interval = [46.08, 53.92], slightly narrower than the t-interval because the known sigma needs no extra-uncertainty correction.

Worked Example 3

Problem. A sample of n = 100 has x-bar = 8.4, s = 3. Build a 99% confidence interval for the mean.

  1. SE = s/sqrt(n) = 3/sqrt(100) = 3/10 = 0.3.
  2. df = 99; t* for 99% (two-sided) ~ 2.626. Python: stats.t.ppf(0.995, 99) -> 2.6264.
  3. Margin = 2.626 * 0.3 = 0.788.
  4. Interval = 8.4 +/- 0.788 = [7.61, 9.19].

Answer. 99% CI = [7.61, 9.19].

Common mistakes
  • Using z (1.96) when sigma is unknown and n is small — use the t-distribution with n - 1 df to widen for the extra uncertainty.
  • Dividing s by n instead of sqrt(n) for the SE — the standard error is s/sqrt(n).
  • Applying the interval to non-normal data with tiny n — without normality or a large n, the CLT may not justify the interval.
✎ Try it yourself

Problem. A sample of n = 16 measurements has x-bar = 100 and s = 8 (sigma unknown). Construct a 95% confidence interval for the mean.

Solution. SE = s/sqrt(n) = 8/sqrt(16) = 8/4 = 2. df = 16 - 1 = 15; t* for 95% two-sided = 2.131 (scipy: stats.t.ppf(0.975, 15) -> 2.1314). Margin = 2.131 * 2 = 4.262. Interval = 100 +/- 4.262 = [95.74, 104.26].

Intervals for a proportion

A proportion interval is p-hat plus-or-minus z times the standard error of a proportion.

For a binary outcome, the sample proportion p-hat = s/n estimates the population proportion p. Its standard error is SE = sqrt(p-hat*(1 - p-hat)/n), and the classic (Wald) 95% interval is p-hat +/- 1.96*SE. This relies on a normal approximation that works well when both n*p-hat and n*(1 - p-hat) are at least about 10; for small samples or proportions near 0 or 1 it behaves poorly (intervals can fall outside [0,1] or undercover). Better alternatives are the Wilson score interval and the Agresti-Coull adjustment, which add pseudo-counts for more reliable coverage. Proportion intervals are everywhere in data science: conversion rates, click-through rates, defect rates, and poll results all need them.

Worked Example 1

Problem. Out of n = 400 visitors, 80 converted. Build a 95% Wald interval for the conversion rate.

  1. p-hat = 80/400 = 0.20.
  2. SE = sqrt(0.20*0.80/400) = sqrt(0.16/400) = sqrt(0.0004) = 0.02.
  3. Margin = 1.96 * 0.02 = 0.0392.
  4. Interval = 0.20 +/- 0.0392 = [0.161, 0.239].

Answer. 95% CI ~ [0.161, 0.239], i.e., about 16.1% to 23.9%.

Worked Example 2

Problem. Check the normal-approximation condition for the previous example.

  1. Need n*p-hat >= ~10 and n*(1 - p-hat) >= ~10.
  2. n*p-hat = 400*0.20 = 80 >= 10. OK.
  3. n*(1 - p-hat) = 400*0.80 = 320 >= 10. OK.
  4. Both far exceed 10, so the Wald interval is reasonable here.

Answer. Both success and failure counts (80 and 320) exceed 10, so the normal approximation is valid.

Worked Example 3

Problem. Only 1 of 20 trials succeeded. Why is the Wald interval unreliable, and what should you use?

  1. p-hat = 1/20 = 0.05; n*p-hat = 1, well below 10 — the normal approximation fails.
  2. Wald: SE = sqrt(0.05*0.95/20) = sqrt(0.002375) = 0.0487; interval 0.05 +/- 1.96*0.0487 = [-0.045, 0.145], which goes below 0 (impossible).
  3. Use a Wilson score interval, which stays within [0,1] and covers better.
  4. Python: from statsmodels.stats.proportion import proportion_confint; print(proportion_confint(1, 20, method='wilson')) -> about (0.009, 0.236).

Answer. The Wald interval gives an impossible negative bound; use the Wilson interval (about [0.009, 0.236]) for small-sample reliability.

Common mistakes
  • Using the Wald interval for tiny n or extreme p-hat — it can exit [0,1] and undercover; use Wilson or Agresti-Coull.
  • Forgetting the *(1 - p-hat) term in the SE — the proportion SE is sqrt(p-hat*(1 - p-hat)/n), not sqrt(p-hat/n).
  • Reporting a proportion CI without checking n*p-hat and n*(1 - p-hat) >= ~10 — the normal approximation may not hold.
✎ Try it yourself

Problem. A poll of n = 1000 finds 540 in favor. Build a 95% Wald confidence interval for the true proportion and verify the normal-approximation condition.

Solution. p-hat = 540/1000 = 0.54. SE = sqrt(0.54*0.46/1000) = sqrt(0.2484/1000) = sqrt(0.0002484) = 0.01576. Margin = 1.96 * 0.01576 = 0.0309. Interval = 0.54 +/- 0.0309 = [0.509, 0.571]. Condition: n*p-hat = 540 and n*(1 - p-hat) = 460, both well above 10, so the approximation is valid. In statsmodels: proportion_confint(540, 1000, method='normal').

Margin of error, confidence level, and sample size

The margin of error links confidence, variability, and sample size; solving for n lets you plan a study.

The margin of error (ME) is the half-width of a confidence interval: ME = critical value * SE. Three levers control it. Higher confidence raises the critical value (z grows from 1.645 at 90% to 1.96 at 95% to 2.576 at 99%), widening the interval. Greater variability (larger sigma or p near 0.5) widens it. Larger sample size shrinks SE like 1/sqrt(n), narrowing it. Inverting the formula lets you plan: to achieve a target ME, solve for n. For a mean, n = (z*sigma/ME)^2; for a proportion, n = z^2 * p*(1 - p)/ME^2, using p = 0.5 for the most conservative (largest) n when p is unknown. This is the core calculation behind survey design and experiment sizing.

Worked Example 1

Problem. Estimate a mean within ME = 1 at 95% confidence, with sigma = 5. Find the required n.

  1. n = (z*sigma/ME)^2 with z = 1.96.
  2. z*sigma/ME = 1.96*5/1 = 9.8.
  3. n = 9.8^2 = 96.04.
  4. Round up: n = 97 (always round up to guarantee the margin).

Answer. n = 97 (rounding 96.04 up).

Worked Example 2

Problem. Estimate a proportion within ME = 0.03 at 95% with no prior guess for p. Find the conservative n.

  1. Use the most conservative p = 0.5 (maximizes p*(1 - p) = 0.25).
  2. n = z^2 * p*(1 - p)/ME^2 = 1.96^2 * 0.25 / 0.03^2.
  3. = 3.8416 * 0.25 / 0.0009 = 0.9604 / 0.0009 = 1067.1.
  4. Round up: n = 1068.

Answer. n = 1068 (the familiar '~1067 for +/-3%' poll size).

Worked Example 3

Problem. For a fixed sample, how does the margin of error change going from 95% to 99% confidence? Use z = 1.96 vs 2.576.

  1. ME is proportional to the critical value, with SE held fixed.
  2. Ratio = 2.576 / 1.96 = 1.314.
  3. So the 99% margin is about 31.4% wider than the 95% margin.
  4. Higher confidence buys reliability at the cost of a wider, less precise interval.

Answer. The margin grows by a factor of ~1.31 (about 31% wider) when moving from 95% to 99% confidence.

Common mistakes
  • Rounding the required n down — always round up so the achieved margin is at most the target.
  • Using p = your hoped-for value instead of 0.5 when p is unknown — p = 0.5 gives the safe, largest n.
  • Expecting the margin to halve when you double n — it shrinks like 1/sqrt(n), so you must quadruple n to halve the margin.
✎ Try it yourself

Problem. You want to estimate a population mean within a margin of error of 2 at 99% confidence, and a pilot suggests sigma = 12. How large must the sample be?

Solution. Use n = (z*sigma/ME)^2 with z = 2.576 for 99%. z*sigma/ME = 2.576*12/2 = 30.912/2 = 15.456. n = 15.456^2 = 238.9. Round up to n = 239. In Python: import math; math.ceil((2.576*12/2)**2) -> 239.

The bootstrap: resampling from your data

The bootstrap treats your sample as the population and resamples it to approximate a statistic's sampling distribution.

The bootstrap is a resampling method that estimates the sampling distribution of almost any statistic without formulas or distributional assumptions. The idea: your observed sample is the best available stand-in for the population, so repeatedly draw new samples of the same size n from it with replacement, compute the statistic on each resample, and collect those values. The spread of this bootstrap distribution estimates the statistic's standard error, and its quantiles can form confidence intervals. The bootstrap shines for statistics with no neat formula (the median, a correlation, a ratio, a trimmed mean) and for small or non-normal data. Its main assumptions are that the sample is representative and observations are independent. In data science it is a go-to tool for honest uncertainty when classical theory is hard or unavailable.

Worked Example 1

Problem. Describe one bootstrap resample of the data [3, 7, 7, 10] and why duplicates appear.

  1. Sampling with replacement of size n = 4 means each draw is from the full set independently.
  2. A possible resample: [7, 3, 7, 7] — values can repeat and some originals can be missing.
  3. Duplicates are expected because each of the 4 draws can land on any element.
  4. Compute the statistic (e.g., mean) on this resample: (7+3+7+7)/4 = 6.0.

Answer. One resample might be [7, 3, 7, 7] with mean 6.0; with-replacement sampling naturally produces repeats and omissions.

Worked Example 2

Problem. Write Python to bootstrap the standard error of the median for a dataset.

  1. import numpy as np; rng = np.random.default_rng(0)
  2. boot = [np.median(rng.choice(data, size=len(data), replace=True)) for _ in range(10000)]
  3. se_median = np.std(boot, ddof=1)
  4. print(se_median) gives the bootstrap standard error of the median, which has no simple closed form.

Answer. The standard deviation of 10,000 bootstrap medians estimates the SE of the median directly from data.

Worked Example 3

Problem. Roughly what fraction of the original observations appear in a typical large-n bootstrap resample?

  1. Probability a given observation is NOT picked in one draw = 1 - 1/n.
  2. Across n draws: (1 - 1/n)^n.
  3. As n grows this tends to e^{-1} ~ 0.368.
  4. So about 1 - 0.368 = 0.632, i.e., ~63.2% of distinct originals appear in each resample.

Answer. About 63.2% of the distinct original observations appear in a typical resample (the rest are omitted; some appear multiple times).

Common mistakes
  • Resampling without replacement — that just reshuffles the data and destroys the bootstrap; you must sample with replacement.
  • Using too few resamples — hundreds give noisy estimates; use thousands (e.g., 10,000) for stable intervals.
  • Bootstrapping a biased or dependent sample — the bootstrap inherits any bias and assumes independence; it cannot fix bad data.
✎ Try it yourself

Problem. Outline a bootstrap to estimate the standard error of the mean for a sample of 200 values, and state how many resamples you would use.

Solution. Treat the 200 values as the population. For B = 10,000 iterations, draw a resample of size 200 with replacement and record its mean. The standard deviation of those 10,000 bootstrap means estimates the SE of the mean. In Python: import numpy as np; rng=np.random.default_rng(0); boot=[rng.choice(data,200,replace=True).mean() for _ in range(10000)]; se=np.std(boot,ddof=1). Using B = 10,000 keeps Monte Carlo noise small.

Bootstrap confidence intervals (percentile and beyond)

Read interval bounds directly off the bootstrap distribution's quantiles, with refinements when needed.

Once you have a bootstrap distribution of a statistic, the percentile method forms a (1 - alpha) interval by taking its alpha/2 and 1 - alpha/2 quantiles — for 95%, the 2.5th and 97.5th percentiles. It is simple and assumption-light, but it can have biased coverage when the statistic's distribution is skewed or the estimator is biased. Refinements address this: the basic (pivotal) bootstrap reflects the percentiles around the observed estimate, and the bias-corrected and accelerated (BCa) interval adjusts for both bias and skewness, generally giving the most accurate coverage. A normal-approximation bootstrap interval uses estimate +/- z * (bootstrap SE). Data scientists default to percentile or BCa intervals for statistics where classical formulas are unavailable or unreliable.

Worked Example 1

Problem. From 10,000 bootstrap means, the 2.5th percentile is 48.2 and the 97.5th is 53.6. Give the 95% percentile interval.

  1. The percentile method uses the alpha/2 and 1 - alpha/2 quantiles directly.
  2. For 95%, those are the 2.5th and 97.5th percentiles.
  3. Lower = 48.2, Upper = 53.6.
  4. Python: import numpy as np; print(np.percentile(boot, [2.5, 97.5])) -> [48.2, 53.6].

Answer. 95% percentile bootstrap CI = [48.2, 53.6].

Worked Example 2

Problem. The observed mean is 50 with bootstrap SE 1.4. Give a normal-approximation bootstrap 95% interval and compare to the percentile one above.

  1. Normal-approx interval = estimate +/- 1.96 * bootstrap SE.
  2. = 50 +/- 1.96 * 1.4 = 50 +/- 2.744.
  3. = [47.26, 52.74].
  4. It is symmetric about 50, whereas the percentile interval [48.2, 53.6] is shifted up, hinting at right skew the normal method misses.

Answer. Normal-approx CI = [47.26, 52.74]; its forced symmetry differs from the skewed percentile interval, which better reflects the bootstrap shape.

Worked Example 3

Problem. When the bootstrap distribution is skewed and the estimator biased, which interval is preferred and why?

  1. The percentile method assumes the bootstrap distribution is roughly centered and symmetric for good coverage.
  2. BCa (bias-corrected and accelerated) adjusts endpoints for bias (z0) and for skewness (acceleration a).
  3. It generally restores the nominal coverage better than percentile or normal methods.
  4. Python: from scipy.stats import bootstrap; res = bootstrap((data,), np.mean, method='BCa'); print(res.confidence_interval).

Answer. Use the BCa interval; it corrects for bias and skewness and gives more accurate coverage than the plain percentile method.

Common mistakes
  • Assuming the percentile interval is always accurate — under bias or skew its coverage can be off; consider BCa.
  • Confusing the percentile interval with mean +/- 2 SD — read the actual 2.5th/97.5th quantiles of the bootstrap distribution.
  • Using too few bootstrap replicates for tail quantiles — extreme percentiles are noisy; use many thousands of resamples.
✎ Try it yourself

Problem. You bootstrap a correlation coefficient 5000 times; the 2.5th percentile of the bootstrap values is 0.31 and the 97.5th is 0.67. State the 95% percentile CI and note one situation where you would switch to BCa.

Solution. The 95% percentile bootstrap CI is just the [2.5th, 97.5th] percentiles: [0.31, 0.67]. In Python: np.percentile(boot_corrs, [2.5, 97.5]) -> [0.31, 0.67]. You would switch to BCa if the bootstrap distribution of the correlation is noticeably skewed (common for correlations near +/-1) or the estimator is biased, because BCa adjusts the endpoints for bias and skewness to restore proper coverage.

Key terms
  • Confidence interval — a range computed from data that would contain the true parameter in a stated percentage of repeated samples.
  • Confidence level — the long-run proportion of intervals (e.g., 95%) constructed this way that capture the true parameter.
  • Margin of error — the half-width of a confidence interval, driven by variability, confidence level, and sample size.
  • t-distribution — a bell curve with heavier tails than the normal, used for a mean when the population standard deviation is unknown.
  • Critical value — the multiplier (z or t) that sets how many standard errors wide the interval is for a given confidence level.
  • Bootstrap — resampling the observed data with replacement to approximate a statistic's sampling distribution.
  • Percentile bootstrap interval — a confidence interval read off the quantiles of the bootstrap distribution.
  • Coverage — the actual probability that an interval method captures the true parameter, ideally matching the stated level.
Assignment · Classical vs. bootstrap intervals

Take a sample of continuous data (real or simulated) and compute a 95% confidence interval for the mean using the t-distribution. Then write a bootstrap routine in Python or R that resamples the data thousands of times and builds a percentile confidence interval for the same mean. Compare the two intervals and discuss when the bootstrap is preferable.

Deliverable · A script that prints both intervals plus a histogram of the bootstrap distribution with the interval bounds marked, and a short comparison of the two approaches.

Quiz · 5 questions
  1. 1. Which interpretation of a 95% confidence interval is correct?

  2. 2. When constructing a confidence interval for a mean with an unknown population standard deviation, you use:

  3. 3. Holding everything else fixed, increasing the confidence level from 90% to 99% will:

  4. 4. The bootstrap approximates a statistic's sampling distribution by:

  5. 5. A percentile bootstrap 95% interval is formed by:

You'll be able to

I can construct and correctly interpret a confidence interval for a mean or proportion.

I can use the bootstrap to estimate the sampling distribution of a statistic and build a confidence interval without strong assumptions.

I can reason about how confidence level, variability, and sample size affect the margin of error.

Weeks 7-8 Unit 4: Hypothesis Testing (t / χ² / ANOVA)
formulate-hypothesesinterpret-pvalue-errorsapply-t-testapply-chi-squaredapply-anovacorrect-multiple-comparisons
Lecture
Null and alternative hypotheses, and test logic

Testing frames a question as a default 'no effect' claim that the data must argue against.

A hypothesis test starts with a null hypothesis H0 stating no effect or no difference (e.g., mu = 0, or two means equal), and an alternative H1 stating the effect we suspect. The logic is proof by contradiction under uncertainty: we assume H0 is true, compute a test statistic measuring how far the data deviate from H0 in standard-error units, and ask how surprising that deviation would be if H0 held. If the data are sufficiently unlikely under H0, we reject H0 in favor of H1; otherwise we fail to reject (we never 'accept' H0). The alternative can be two-sided (different) or one-sided (greater or less). Framing the right H0/H1 — and choosing one- vs. two-sided before seeing data — is the foundation that keeps every downstream test honest.

Worked Example 1

Problem. A coin is suspected of being unfair. State H0 and H1 and whether the test is one- or two-sided.

  1. Parameter: p = probability of heads.
  2. H0: p = 0.5 (fair coin, the no-effect default).
  3. H1: p != 0.5 (unfair in either direction).
  4. Because 'unfair' allows both more and fewer heads, the test is two-sided.

Answer. H0: p = 0.5; H1: p != 0.5; two-sided test.

Worked Example 2

Problem. A new drug is claimed to lower blood pressure versus placebo. Write directional hypotheses for the mean difference d = mu_drug - mu_placebo.

  1. The claim is a decrease, a directional effect.
  2. H0: d = 0 (or d >= 0) — the drug does not lower pressure.
  3. H1: d < 0 — the drug lowers pressure.
  4. This is a one-sided (left-tailed) test; the direction must be fixed before seeing the data.

Answer. H0: d = 0; H1: d < 0; a one-sided (left-tailed) test.

Worked Example 3

Problem. A test yields a result that does not reach significance. Is it correct to conclude 'H0 is true'?

  1. Failing to reject H0 means the data are consistent with H0, not that H0 is proven.
  2. Absence of evidence (perhaps due to low power or small n) is not evidence of absence.
  3. Correct wording: 'we fail to reject H0; there is insufficient evidence of an effect.'
  4. A non-significant result could still hide a real effect the study was too small to detect.

Answer. No; we 'fail to reject H0' — the data simply do not provide enough evidence against it, which is not proof H0 is true.

Common mistakes
  • Saying we 'accept H0' — we only ever fail to reject it; tests cannot prove the null.
  • Choosing one- vs. two-sided after seeing the data — the direction must be set in advance or the error rate is inflated.
  • Putting the effect of interest in H0 — the null is the no-effect default; the suspected effect goes in H1.
✎ Try it yourself

Problem. A company believes a redesign changed (in either direction) the average time users spend on a page, formerly mu = 120 seconds. State the hypotheses and the test sidedness.

Solution. Let mu be the new mean time on page. H0: mu = 120 (no change from the old average). H1: mu != 120 (the redesign changed the time, up or down). Because the claim is 'changed in either direction,' this is a two-sided test. We would assume H0, compute a t-statistic from the new sample, and reject H0 only if the data are sufficiently unlikely under mu = 120.

p-values, significance levels, and the two error types

A p-value measures surprise under the null; alpha sets the threshold and equals the Type I error rate.

The p-value is the probability, computed assuming H0 is true, of observing a test statistic at least as extreme as the one obtained. A small p-value means the data would be surprising under H0, casting doubt on it. The significance level alpha (commonly 0.05) is a pre-chosen threshold: reject H0 if p <= alpha. Two errors are possible. A Type I error rejects a true null (false positive); its probability is exactly alpha. A Type II error fails to reject a false null (false negative); its probability is beta, and power = 1 - beta. There is a tradeoff: lowering alpha reduces false positives but raises beta (lowers power) unless n increases. A p-value is not the probability that H0 is true and not a measure of effect size — always report an effect estimate alongside it.

Worked Example 1

Problem. A two-sided test gives a z-statistic of 2.1. Find the p-value and decide at alpha = 0.05.

  1. Two-sided p = 2 * P(Z >= 2.1).
  2. P(Z >= 2.1) = 0.0179 (standard normal upper tail).
  3. p = 2 * 0.0179 = 0.0358.
  4. Since 0.0358 <= 0.05, reject H0. Python: from scipy import stats; print(2*(1-stats.norm.cdf(2.1))) -> 0.0357.

Answer. p ~ 0.036 <= 0.05, so reject H0.

Worked Example 2

Problem. Interpret p = 0.20 correctly and state which error you risk if you fail to reject.

  1. p = 0.20 means: if H0 were true, there is a 20% chance of data at least this extreme.
  2. 0.20 > 0.05, so we fail to reject H0.
  3. If H0 is actually false, failing to reject is a Type II error (false negative).
  4. p = 0.20 is NOT 'a 20% chance H0 is true.'

Answer. p = 0.20 is the chance of equally/more extreme data under H0; failing to reject risks a Type II error if H0 is really false.

Worked Example 3

Problem. Explain how lowering alpha from 0.05 to 0.01 affects the two error types, holding n fixed.

  1. alpha is the Type I error rate, so lowering it to 0.01 reduces false positives.
  2. A stricter threshold makes rejection harder, so true effects are missed more often.
  3. Thus the Type II error rate beta rises and power = 1 - beta falls.
  4. To regain power at the lower alpha, you must increase the sample size n.

Answer. Lower alpha cuts Type I errors but raises Type II errors (less power), unless you increase n to compensate.

Common mistakes
  • Reading p as 'the probability the null is true' — it is the probability of the data (or more extreme) given the null.
  • Treating p = 0.049 and p = 0.051 as fundamentally different — the 0.05 cutoff is a convention, not a law of nature.
  • Reporting significance without an effect size — a tiny, unimportant effect can be 'significant' with a huge sample.
✎ Try it yourself

Problem. A one-sided (upper-tail) test produces a z-statistic of 1.5. Compute the p-value and decide at alpha = 0.05, then name the error you could be committing.

Solution. One-sided upper-tail p = P(Z >= 1.5) = 1 - 0.9332 = 0.0668 (scipy: 1 - stats.norm.cdf(1.5) = 0.0668). Since 0.0668 > 0.05, we fail to reject H0. If the true effect actually exists (H0 false), failing to reject it is a Type II error (a false negative).

One- and two-sample t-tests

t-tests compare means using t = (estimate - null) / standard error, judged against the t-distribution.

A t-test assesses claims about means when sigma is unknown. The one-sample t-test checks whether a mean equals a hypothesized value: t = (x-bar - mu0)/(s/sqrt(n)) with n - 1 degrees of freedom. The paired t-test applies the one-sample test to within-pair differences (e.g., before/after on the same subjects). The two-sample (independent) t-test compares two group means; Welch's version, which does not assume equal variances, is the safe default and uses t = (x1-bar - x2-bar)/sqrt(s1^2/n1 + s2^2/n2). You compare the t-statistic to a t-distribution to get a p-value. Assumptions are approximately normal data (or large n via the CLT) and independence; for paired designs, the pairs must be matched. t-tests are the most common tool for comparing averages in experiments and A/B analyses.

Worked Example 1

Problem. n = 16, x-bar = 52, s = 8, test H0: mu = 50 vs H1: mu != 50.

  1. SE = s/sqrt(n) = 8/sqrt(16) = 8/4 = 2.
  2. t = (x-bar - mu0)/SE = (52 - 50)/2 = 1.0.
  3. df = 15; two-sided p = 2*P(T15 >= 1.0). scipy: 2*(1-stats.t.cdf(1.0,15)) -> 0.333.
  4. p = 0.333 > 0.05, so fail to reject H0.

Answer. t = 1.0 on 15 df, p ~ 0.33; fail to reject H0 — no evidence mu differs from 50.

Worked Example 2

Problem. Paired before/after differences have mean d-bar = 3, s_d = 5, n = 25. Test whether the change is nonzero.

  1. Use a one-sample t on the differences: H0: mu_d = 0.
  2. SE = s_d/sqrt(n) = 5/sqrt(25) = 5/5 = 1.
  3. t = d-bar/SE = 3/1 = 3.0, df = 24.
  4. Two-sided p = 2*P(T24 >= 3.0). scipy: 2*(1-stats.t.cdf(3.0,24)) -> 0.0063, so reject H0.

Answer. t = 3.0 on 24 df, p ~ 0.006; reject H0 — there is a significant mean change.

Worked Example 3

Problem. Two groups: group 1 (n1=30, x1-bar=10.0, s1=2.0), group 2 (n2=30, x2-bar=11.5, s2=2.5). Run a Welch two-sample test.

  1. SE = sqrt(s1^2/n1 + s2^2/n2) = sqrt(4/30 + 6.25/30) = sqrt(0.1333 + 0.2083) = sqrt(0.3417) = 0.5846.
  2. t = (x1-bar - x2-bar)/SE = (10.0 - 11.5)/0.5846 = -2.566.
  3. Welch df ~ 55 (Satterthwaite); two-sided p ~ 0.013. scipy: stats.ttest_ind(g1, g2, equal_var=False).
  4. p ~ 0.013 < 0.05, so reject H0: the group means differ.

Answer. Welch t ~ -2.57, p ~ 0.013; reject H0 — group 2's mean is significantly higher.

Common mistakes
  • Using an independent two-sample t-test on paired data — paired designs need the paired (one-sample-on-differences) test, which is more powerful.
  • Assuming equal variances by default — prefer Welch's t-test (equal_var=False) unless variances are known to match.
  • Applying a t-test to clearly non-normal, small samples — check normality or use a nonparametric alternative.
✎ Try it yourself

Problem. A sample of n = 9 has x-bar = 104 and s = 6. Test H0: mu = 100 against H1: mu != 100 at alpha = 0.05.

Solution. SE = s/sqrt(n) = 6/sqrt(9) = 6/3 = 2. t = (104 - 100)/2 = 2.0 on df = 8. Two-sided p = 2*P(T8 >= 2.0); scipy: 2*(1-stats.t.cdf(2.0, 8)) = 0.0805. Since 0.0805 > 0.05, we fail to reject H0 — there is not enough evidence that mu differs from 100 (note the critical t for 8 df is 2.306, and 2.0 < 2.306).

The chi-squared test for independence and goodness of fit

The chi-squared test compares observed counts to counts expected under a hypothesis about categories.

The chi-squared (chi^2) test works with categorical counts. The goodness-of-fit test checks whether one categorical variable's observed frequencies match a hypothesized distribution; the test of independence checks whether two categorical variables in a contingency table are associated. Both use the statistic chi^2 = sum (O - E)^2 / E, where O are observed counts and E are counts expected under H0. For independence, E for a cell = (row total * column total)/grand total. The statistic follows a chi-squared distribution; degrees of freedom are (categories - 1) for goodness of fit and (rows - 1)*(cols - 1) for independence. A large chi^2 (small p) means observed counts deviate from expectation more than chance allows. A rule of thumb requires expected counts of at least about 5 per cell for the approximation to hold.

Worked Example 1

Problem. A die is rolled 60 times with counts 8,12,9,11,10,10. Test goodness of fit to a fair die.

  1. Under fairness, each face expected E = 60/6 = 10.
  2. chi^2 = sum (O - E)^2/E = (4+4+1+1+0+0)/10 = 10/10 = 1.0. (deviations: -2,2,-1,1,0,0; squares 4,4,1,1,0,0.)
  3. df = 6 - 1 = 5; p = P(chi^2_5 >= 1.0). scipy: 1 - stats.chi2.cdf(1.0, 5) -> 0.962.
  4. p ~ 0.96 >> 0.05, so fail to reject — counts are consistent with a fair die.

Answer. chi^2 = 1.0 on 5 df, p ~ 0.96; no evidence the die is unfair.

Worked Example 2

Problem. Compute the expected count for a contingency cell whose row total is 80, column total is 50, and grand total is 200.

  1. E = (row total * column total)/grand total.
  2. = (80 * 50)/200.
  3. = 4000/200 = 20.
  4. This E is what the cell count would be on average if the two variables were independent.

Answer. Expected count E = 20.

Worked Example 3

Problem. A 2x2 table of (treatment vs control) by (success vs failure) is [[30,20],[20,30]]. Outline the independence test and its df.

  1. Row totals 50, 50; column totals 50, 50; grand total 100.
  2. Each E = 50*50/100 = 25 for all four cells.
  3. chi^2 = sum (O - E)^2/E = (25+25+25+25)/25 = 100/25 = 4.0. (each |O-E| = 5, square 25.)
  4. df = (2-1)*(2-1) = 1; p = P(chi^2_1 >= 4.0) ~ 0.0455. scipy: stats.chi2_contingency([[30,20],[20,30]]) (note it applies Yates' correction by default).

Answer. chi^2 = 4.0 on 1 df, p ~ 0.046; borderline-significant association (use chi2_contingency, which by default applies Yates' continuity correction for 2x2 tables).

Common mistakes
  • Running chi-squared on small expected counts — if any E < ~5, use Fisher's exact test or combine categories.
  • Dividing by O instead of E — the statistic is sum (O - E)^2/E, normalized by expected, not observed.
  • Forgetting Yates' correction for 2x2 tables — many tools apply it by default, which lowers chi^2 slightly; know your tool's behavior.
✎ Try it yourself

Problem. A survey expects responses split 50/30/20 across three options, and out of 100 people observes 40/35/25. Run a goodness-of-fit test.

Solution. Expected counts for n = 100: 50, 30, 20. chi^2 = sum (O-E)^2/E = (40-50)^2/50 + (35-30)^2/30 + (25-20)^2/20 = 100/50 + 25/30 + 25/20 = 2.0 + 0.833 + 1.25 = 4.083. df = 3 - 1 = 2; p = P(chi^2_2 >= 4.083); scipy: 1 - stats.chi2.cdf(4.083, 2) = 0.130. Since 0.130 > 0.05, fail to reject — observed counts are consistent with the 50/30/20 split.

ANOVA: comparing several group means

ANOVA uses an F-test to ask whether three or more group means differ, comparing between- to within-group variation.

Analysis of variance (ANOVA) tests H0 that several group means are all equal against the alternative that at least one differs. Rather than running many pairwise t-tests (which inflates the error rate), one-way ANOVA partitions total variation into between-group (variation of group means around the grand mean) and within-group (variation inside each group). The F-statistic is the ratio of mean squares: F = MSB/MSW, where MSB = SSB/(k - 1) and MSW = SSW/(N - k), with k groups and N total observations. Under H0, F follows an F-distribution with (k - 1, N - k) degrees of freedom, centered near 1; a large F (small p) signals that between-group differences exceed what within-group noise explains. ANOVA assumes independent, roughly normal groups with similar variances. A significant F says 'some means differ' but not which — follow up with post-hoc comparisons.

Worked Example 1

Problem. Three groups give SSB = 60 (k = 3) and SSW = 90 (N = 30). Compute the F-statistic.

  1. MSB = SSB/(k - 1) = 60/(3 - 1) = 60/2 = 30.
  2. MSW = SSW/(N - k) = 90/(30 - 3) = 90/27 = 3.333.
  3. F = MSB/MSW = 30/3.333 = 9.0.
  4. df = (2, 27); p = P(F_{2,27} >= 9.0). scipy: 1 - stats.f.cdf(9.0, 2, 27) -> 0.001.

Answer. F = 9.0 on (2, 27) df, p ~ 0.001; reject H0 — at least one group mean differs.

Worked Example 2

Problem. Why does a large F lead to rejecting equal means? Interpret F near 1 vs F much greater than 1.

  1. F = between-group variance / within-group variance.
  2. If all group means are equal, between-group variation is just noise, so F is around 1.
  3. If means truly differ, MSB grows while MSW stays put, pushing F well above 1.
  4. Thus a large F (small tail probability) is evidence against equal means.

Answer. F near 1 is consistent with equal means; F much greater than 1 means between-group spread exceeds within-group noise, so we reject H0.

Worked Example 3

Problem. Outline running a one-way ANOVA in Python on three samples and what a significant result does not tell you.

  1. from scipy import stats; F, p = stats.f_oneway(group1, group2, group3).
  2. If p < alpha, reject H0: not all group means are equal.
  3. ANOVA does NOT say which pair of groups differs.
  4. Follow up with a post-hoc test (e.g., Tukey HSD: statsmodels.stats.multicomp.pairwise_tukeyhsd) to locate the differences.

Answer. stats.f_oneway gives F and p; a significant result flags that some means differ but requires a post-hoc test (e.g., Tukey HSD) to identify which.

Common mistakes
  • Running many pairwise t-tests instead of ANOVA — that inflates the overall Type I error; ANOVA tests all means jointly.
  • Concluding which groups differ from the F-test alone — a significant ANOVA needs post-hoc comparisons to localize differences.
  • Ignoring the equal-variance and normality assumptions — badly unequal variances call for Welch's ANOVA or a transformation.
✎ Try it yourself

Problem. Four groups (k = 4) with N = 40 observations give SSB = 120 and SSW = 144. Compute MSB, MSW, and the F-statistic, then state the df.

Solution. MSB = SSB/(k - 1) = 120/(4 - 1) = 120/3 = 40. MSW = SSW/(N - k) = 144/(40 - 4) = 144/36 = 4. F = MSB/MSW = 40/4 = 10.0. Degrees of freedom are (k - 1, N - k) = (3, 36). The p-value is P(F_{3,36} >= 10.0); scipy: 1 - stats.f.cdf(10.0, 3, 36) ~ 0.00006, so we strongly reject H0 that all four means are equal.

Multiple comparisons and the danger of p-hacking

Running many tests inflates false positives; corrections and pre-registration keep conclusions honest.

Each test at level alpha has an alpha chance of a false positive. Run many and the family-wise error rate (FWER), the chance of at least one false positive, balloons: with m independent true nulls it is 1 - (1 - alpha)^m. p-hacking — trying many analyses, subgroups, or metrics and reporting only the significant ones — exploits this and produces irreproducible findings. Corrections restore control. The Bonferroni method tests each hypothesis at alpha/m, controlling FWER but conservatively. The Benjamini-Hochberg procedure controls the false discovery rate (FDR), the expected proportion of false positives among rejections, offering more power for large m (common in genomics or many-metric experiments). The cure is also procedural: pre-register hypotheses and the primary metric before looking at data so the analysis is not contorted to find significance.

Worked Example 1

Problem. You run 10 independent tests at alpha = 0.05 with all nulls true. What is the chance of at least one false positive?

  1. P(no false positive in one test) = 1 - 0.05 = 0.95.
  2. P(none across 10 independent) = 0.95^10.
  3. 0.95^10 = 0.5987.
  4. P(at least one) = 1 - 0.5987 = 0.4013. Python: 1 - 0.95**10 -> 0.401.

Answer. About 40% — there is a 0.40 chance of at least one false positive across 10 tests.

Worked Example 2

Problem. Apply a Bonferroni correction for 10 tests at family alpha = 0.05. What is the per-test threshold, and which of p = 0.004, 0.02, 0.06 survive?

  1. Bonferroni per-test alpha = 0.05/10 = 0.005.
  2. Compare each p to 0.005.
  3. 0.004 <= 0.005 -> significant; 0.02 > 0.005 -> not; 0.06 > 0.005 -> not.
  4. Only p = 0.004 remains significant after correction.

Answer. Per-test threshold = 0.005; only p = 0.004 stays significant.

Worked Example 3

Problem. Explain why reporting only the 'best' of 20 metrics tested is p-hacking, and one fix.

  1. Testing 20 metrics gives 20 chances to cross alpha by luck; expected false positives ~ 20*0.05 = 1.
  2. Cherry-picking the significant one ignores the 19 silent tests, so the reported p is misleadingly small.
  3. This selective reporting inflates the true false-positive rate far above 5%.
  4. Fix: pre-register a single primary metric, or apply a multiple-comparison correction (Bonferroni or Benjamini-Hochberg) across all 20.

Answer. It is p-hacking because selecting the best of 20 tests hides the multiplicity; fix it by pre-registering the primary metric or correcting across all tests.

Common mistakes
  • Reporting only significant results from many analyses — selective reporting (p-hacking) makes the stated p-value meaningless.
  • Forgetting to correct when running many tests — uncorrected multiplicity inflates false positives well above alpha.
  • Treating Bonferroni as the only option — it can be overly conservative; Benjamini-Hochberg (FDR control) offers more power for many tests.
✎ Try it yourself

Problem. An experiment tests 5 independent metrics at alpha = 0.05, all nulls true. (a) Find the family-wise false-positive probability. (b) Give the Bonferroni per-test threshold. (c) Decide which of p = 0.008, 0.03, 0.04 are significant after Bonferroni.

Solution. (a) P(at least one false positive) = 1 - (1 - 0.05)^5 = 1 - 0.95^5 = 1 - 0.7738 = 0.2262, about 23%. (b) Bonferroni threshold = 0.05/5 = 0.01. (c) Compare each p to 0.01: 0.008 <= 0.01 is significant; 0.03 and 0.04 exceed 0.01 and are not. Only p = 0.008 survives the correction.

Key terms
  • Null hypothesis — the default claim of no effect or no difference that a test attempts to disprove.
  • Alternative hypothesis — the claim of an effect or difference that we accept if the null is rejected.
  • p-value — the probability, assuming the null is true, of observing data at least as extreme as what we saw.
  • Significance level (alpha) — the threshold (e.g., 0.05) for rejecting the null, equal to the tolerated Type I error rate.
  • Type I error — rejecting a true null hypothesis (a false positive).
  • Type II error — failing to reject a false null hypothesis (a false negative).
  • t-test — a test comparing means using a t-statistic, for one sample, two samples, or paired data.
  • Chi-squared test — a test of categorical data for goodness of fit or independence in a contingency table.
  • ANOVA — analysis of variance, an F-test comparing the means of three or more groups simultaneously.
Assignment · Choose the right test and report it honestly

You are given three mini-datasets: two groups of continuous measurements, a contingency table of categorical counts, and several groups for a one-way comparison. Using Python (SciPy/statsmodels) or R, pick and run the correct test for each — a two-sample t-test, a chi-squared test, and an ANOVA — and report the test statistic, p-value, and a plain-language conclusion. Where you run several tests, apply a multiple-comparison correction and explain its effect.

Deliverable · A report with the chosen test for each dataset, the statistics and p-values, the corrected results, and a sentence each on what the conclusion does and does not justify.

Quiz · 5 questions
  1. 1. A p-value of 0.03 means:

  2. 2. A Type I error occurs when you:

  3. 3. To test whether two categorical variables in a contingency table are independent, you use:

  4. 4. ANOVA is most appropriate when you want to:

  5. 5. Running 20 independent tests at alpha = 0.05 with all nulls true, you would expect roughly how many false positives?

You'll be able to

I can state null and alternative hypotheses and interpret a p-value and significance level correctly.

I can choose and run the appropriate test (t-test, chi-squared, or ANOVA) for a given question and report the result responsibly.

I can explain Type I and Type II errors and why multiple comparisons inflate false positives.

Weeks 9-10 Unit 5: Experiment Design & A/B Testing
design-randomized-experimentspecify-ab-testcompute-powercalculate-sample-sizeanalyze-ab-resultsdiagnose-experiment-pitfalls
Lecture
Randomization, control, and causal inference basics

Random assignment is what turns a correlation into a credible causal claim.

Observational data can show that two things move together, but correlation alone cannot establish causation because of confounders — variables that influence both the treatment and the outcome. A randomized controlled experiment breaks this by randomly assigning units to a treatment group and a control group. Randomization balances all confounders (known and unknown) across groups in expectation, so the two groups are comparable except for the treatment. Any systematic difference in outcomes can then be attributed to the treatment, estimating the average treatment effect (ATE) = mean(outcome | treated) - mean(outcome | control). The control group provides the counterfactual baseline of what would have happened without the change. This logic — randomize, compare, attribute — is the gold standard for causal claims and the backbone of A/B testing in data science.

Worked Example 1

Problem. An observational study finds users of a feature have higher retention. Why can't we conclude the feature causes retention?

  1. Users self-select into the feature; they are not randomly assigned.
  2. A confounder (e.g., overall engagement) may drive both feature use and retention.
  3. So the higher retention could be due to the confounder, not the feature.
  4. Only random assignment would balance such confounders and license a causal claim.

Answer. Self-selection means confounders (like baseline engagement) are not balanced, so the association is not necessarily causal.

Worked Example 2

Problem. A randomized test gives treated mean conversion 0.062 and control mean 0.050. Estimate the average treatment effect.

  1. ATE = mean(treated outcome) - mean(control outcome).
  2. = 0.062 - 0.050.
  3. = 0.012, an absolute lift of 1.2 percentage points.
  4. Relative lift = 0.012/0.050 = 0.24, a 24% increase.

Answer. Estimated ATE = +0.012 (1.2 percentage points absolute, a 24% relative lift).

Worked Example 3

Problem. How does randomization handle an unknown confounder that an analyst never even measures?

  1. Adjustment methods (like regression) can only control for confounders you measure.
  2. Randomization assigns treatment independently of every variable, measured or not.
  3. So in expectation the unknown confounder is balanced across groups.
  4. This is the unique strength of experiments over observational adjustment.

Answer. Randomization balances even unmeasured confounders in expectation, which observational adjustment cannot guarantee.

Common mistakes
  • Concluding causation from observational correlation — without randomization, confounders can fully explain the association.
  • Believing you can statistically adjust away all confounding — you can only adjust for variables you measured; randomization handles the rest.
  • Dropping the control group — without a counterfactual baseline, you cannot isolate the treatment's effect from time trends.
✎ Try it yourself

Problem. An online store randomly shows half its visitors a new checkout flow. Treated visitors convert at 8.0% and control at 7.0%. (a) Estimate the absolute and relative treatment effect. (b) Explain in one sentence why randomization makes this causal.

Solution. (a) Absolute ATE = 8.0% - 7.0% = 1.0 percentage point; relative lift = 1.0/7.0 = 0.143, about a 14.3% increase. (b) Because visitors were randomly assigned, the treatment and control groups are comparable in expectation on all other factors (device, intent, traffic source, etc.), so the 1.0-point difference can be attributed to the new checkout flow rather than to a confounder.

Designing an A/B test: metric, hypothesis, and unit

A sound A/B test fixes a single primary metric, clear hypotheses, and a consistent randomization unit before launch.

Designing an A/B test means specifying three things up front. The metric: one primary success metric (e.g., conversion rate, revenue per user) chosen before launch, ideally with a few guardrail metrics to catch harm. The hypothesis: H0 of no difference between control (A) and treatment (B), with a directional or two-sided H1 and a pre-stated minimum detectable effect worth acting on. The randomization unit: what gets assigned to A or B — usually the user (not the page view), so a person sees a consistent experience and units are independent. Mismatched units (e.g., randomizing by session while measuring per-user metrics) break independence and bias inference. Defining these before collecting data prevents p-hacking and ensures the test answers a single, decision-relevant question.

Worked Example 1

Problem. A team wants to test a new homepage. Why randomize by user rather than by page view?

  1. A user may generate many page views; randomizing views means one user sees both A and B.
  2. That contaminates the comparison and violates independence (views from one user are correlated).
  3. Randomizing by user gives each person one consistent variant and independent units.
  4. Analysis then aggregates the metric per user, matching the unit of randomization.

Answer. Randomize by user so each person sees one consistent variant and units are independent, avoiding correlated within-user observations.

Worked Example 2

Problem. State a complete primary hypothesis for testing whether variant B raises conversion above a 5% baseline.

  1. Primary metric: conversion rate (purchasers / visitors), measured per user.
  2. H0: p_B = p_A (no difference); baseline p_A = 0.05.
  3. H1: p_B != p_A (two-sided) or p_B > p_A if only an increase matters.
  4. Pre-state a minimum detectable effect, e.g., a lift to 0.06 (1 point absolute) as the smallest worthwhile effect.

Answer. Metric = per-user conversion; H0: p_B = p_A; H1: p_B != p_A; MDE = +1 point (0.05 -> 0.06), all fixed before launch.

Worked Example 3

Problem. Why include a guardrail metric like page-load time alongside the primary conversion metric?

  1. Optimizing one metric can silently harm another (e.g., a flashy variant slows load time).
  2. A guardrail metric is monitored to ensure the change does no unacceptable damage.
  3. If conversion rises but load time degrades badly, the change may be rejected.
  4. Guardrails prevent shipping a 'win' that hurts the broader experience.

Answer. Guardrail metrics catch unintended harm, so you do not ship a primary-metric win that degrades performance or another key outcome.

Common mistakes
  • Tracking many metrics and picking a winner after the fact — choose one primary metric in advance to avoid p-hacking.
  • Randomizing at a different level than you analyze — the randomization unit and the metric's unit must match for valid inference.
  • Skipping a minimum detectable effect — without an MDE you cannot size the test or judge practical significance.
✎ Try it yourself

Problem. You want to test whether a new onboarding email increases 7-day activation. Specify the primary metric, the randomization unit, H0/H1, and one guardrail metric.

Solution. Primary metric: 7-day activation rate (activated users / enrolled users), measured per user. Randomization unit: the user, randomly assigned to receive the new email (B) or the old one (A), so each person gets one consistent experience. Hypotheses: H0: activation_B = activation_A; H1: activation_B > activation_A (one-sided, since we only care about an increase) with a pre-set MDE such as +2 percentage points. Guardrail metric: unsubscribe rate, monitored to ensure the new email does not drive users away even if activation rises.

Statistical power and minimum detectable effect

Power is the probability of detecting a real effect; the MDE is the smallest effect a test can reliably catch.

Statistical power is the probability that a test correctly rejects H0 when a true effect of a given size exists; it equals 1 - beta, where beta is the Type II error rate. Power rises with larger true effect size, larger sample size, larger alpha, and smaller variance. Studies are typically designed for 80% power (beta = 0.20). The minimum detectable effect (MDE) is the smallest effect the test is powered to find at the chosen alpha and power: smaller MDEs demand much larger samples. There is a direct link via the standardized effect size (Cohen's d for means, or a standardized proportion difference). Computing power before launch prevents underpowered tests that waste resources and miss real effects, and it forces an honest conversation about what effect size is worth detecting.

Worked Example 1

Problem. A two-sample mean test has standardized effect d = 0.5, n = 64 per group, alpha = 0.05 two-sided. Roughly estimate the power.

  1. The noncentrality is d*sqrt(n/2) = 0.5*sqrt(64/2) = 0.5*sqrt(32) = 0.5*5.657 = 2.83.
  2. Critical z for two-sided 0.05 is 1.96.
  3. Power ~ P(Z > 1.96 - 2.83) = P(Z > -0.87) = 0.808.
  4. Python: from statsmodels.stats.power import TTestIndPower; print(TTestIndPower().power(effect_size=0.5, nobs1=64, alpha=0.05)) -> ~0.80.

Answer. Power is about 0.80 — roughly the conventional 80% target.

Worked Example 2

Problem. Holding alpha and effect size fixed, what happens to power as you double the sample size, and why?

  1. Larger n shrinks the standard error of the estimate (SE ~ 1/sqrt(n)).
  2. A smaller SE makes the same true effect more standard errors away from H0.
  3. The noncentrality grows, pushing more of the test statistic's distribution past the critical value.
  4. Therefore power increases (toward 1) as n grows.

Answer. Power increases because a larger n reduces the standard error, making a fixed true effect easier to detect.

Worked Example 3

Problem. Explain the tradeoff between MDE and sample size at fixed power and alpha.

  1. For means, required n per group ~ 2*(z_alpha/2 + z_beta)^2 / d^2, where d is the standardized MDE.
  2. Halving the MDE (d) divides d^2 by 4.
  3. So required n multiplies by 4 to keep the same power.
  4. Detecting smaller effects is expensive: precision costs quadratically in sample size.

Answer. Smaller MDEs require far larger samples — n scales like 1/MDE^2, so halving the MDE roughly quadruples the needed n.

Common mistakes
  • Confusing power with the p-value or with 1 - alpha — power is 1 - beta, the chance of detecting a true effect.
  • Running an underpowered test and reading a null result as 'no effect' — low power means real effects are often missed.
  • Forgetting that smaller detectable effects cost quadratically more data — n grows like 1/MDE^2.
✎ Try it yourself

Problem. A study is powered at 80% to detect a standardized effect of d = 0.4. If the team now wants to detect d = 0.2 at the same 80% power and alpha, by what factor must the per-group sample size change?

Solution. Required n per group scales like 1/d^2 (since n ~ 2*(z_alpha/2 + z_beta)^2 / d^2). Going from d = 0.4 to d = 0.2 halves d, so 1/d^2 changes by a factor of (0.4/0.2)^2 = 2^2 = 4. The per-group sample size must increase about fourfold. In statsmodels: TTestIndPower().solve_power(effect_size=0.2, power=0.8, alpha=0.05) returns roughly 4x the n for d = 0.4.

Sample size and duration calculations

Translate the MDE, baseline, power, and alpha into the visitors and days a test needs.

Before launching, you compute how many units the test needs and how long that takes. For comparing two proportions, a common formula for n per group is n = (z_{alpha/2} + z_beta)^2 * (p1*(1 - p1) + p2*(1 - p2)) / (p2 - p1)^2, where p1 is the baseline and p2 = p1 + MDE. The z-values come from the chosen alpha (e.g., 1.96 for two-sided 0.05) and power (e.g., 0.84 for 80%). Duration follows from traffic: days = (total units needed)/(daily eligible units), rounded up and ideally spanning full weeks to cover weekday/weekend cycles. Tools like statsmodels' NormalIndPower or proportion effect-size helpers automate this. Planning duration prevents two failure modes: stopping too early (underpowered) and running needlessly long (wasting traffic and delaying decisions).

Worked Example 1

Problem. Baseline conversion p1 = 0.10, MDE to p2 = 0.12, alpha = 0.05 two-sided, power = 0.80. Estimate n per group.

  1. z_{alpha/2} = 1.96, z_beta = 0.84; (1.96 + 0.84)^2 = 2.8^2 = 7.84.
  2. Variance term = p1(1 - p1) + p2(1 - p2) = 0.10*0.90 + 0.12*0.88 = 0.09 + 0.1056 = 0.1956.
  3. Denominator = (p2 - p1)^2 = 0.02^2 = 0.0004.
  4. n = 7.84 * 0.1956 / 0.0004 = 1.5335 / 0.0004 = 3834 per group (round up).

Answer. About 3,834 users per group (roughly 7,700 total).

Worked Example 2

Problem. If each group needs 3,834 users and the site gets 2,000 eligible users/day split 50/50, how long must the test run?

  1. Each variant gets 2,000/2 = 1,000 users/day.
  2. Days = users needed per group / daily per group = 3,834 / 1,000 = 3.834 days.
  3. Round up and extend to full weeks for day-of-week effects.
  4. Run at least 7 days (one full week) to be safe, rather than stopping at ~4 days.

Answer. Mathematically ~4 days, but run a full 7 days to cover weekly cycles.

Worked Example 3

Problem. Sketch statsmodels code to get the per-group sample size for detecting a lift from 0.10 to 0.12.

  1. from statsmodels.stats.proportion import proportion_effectsize
  2. es = proportion_effectsize(0.12, 0.10) # standardized effect (Cohen's h)
  3. from statsmodels.stats.power import NormalIndPower
  4. n = NormalIndPower().solve_power(effect_size=es, alpha=0.05, power=0.80, alternative='two-sided') # ~3,800 per group

Answer. proportion_effectsize then NormalIndPower().solve_power returns about 3,800 users per group, matching the formula.

Common mistakes
  • Rounding the required sample size down — round up so the test actually reaches the target power.
  • Ignoring day-of-week effects — run full weeks; a few weekday-only days can bias the metric.
  • Plugging in the hoped-for effect rather than the smallest worthwhile one — using too large an MDE underpowers the test for the effect you care about.
✎ Try it yourself

Problem. Baseline conversion p1 = 0.05, you want to detect a lift to p2 = 0.06 at alpha = 0.05 (two-sided) and 80% power. Estimate the per-group sample size.

Solution. (z_{alpha/2} + z_beta)^2 = (1.96 + 0.84)^2 = 2.8^2 = 7.84. Variance term = p1(1-p1) + p2(1-p2) = 0.05*0.95 + 0.06*0.94 = 0.0475 + 0.0564 = 0.1039. Denominator = (0.06 - 0.05)^2 = 0.01^2 = 0.0001. n = 7.84 * 0.1039 / 0.0001 = 0.8146 / 0.0001 = 8146 per group (round up). So roughly 8,150 users per variant, about 16,300 total.

Running and analyzing an A/B test

Run to the planned sample, then compare the variants with the appropriate two-sample test and report effect plus uncertainty.

Once the test reaches its pre-computed sample size, analyze it with the test that matches the metric. For a conversion (proportion) metric, use a two-proportion z-test (or chi-squared on the 2x2 table). For a continuous metric like revenue per user, use a two-sample (Welch) t-test. Report not just the p-value but the estimated effect and its confidence interval, since a tiny but significant lift may not be worth shipping. Also check for a sample-ratio mismatch (the actual split deviating from the intended 50/50), which signals a logging or assignment bug that can invalidate results. Good analysis pairs a clear decision rule (ship if significant and the lift exceeds the MDE, respecting guardrails) with honest reporting of uncertainty. Avoid re-deciding based on data you peeked at mid-run.

Worked Example 1

Problem. Control: 500/10000 convert; Treatment: 560/10000 convert. Run a two-proportion z-test.

  1. p1 = 500/10000 = 0.050; p2 = 560/10000 = 0.056; pooled p = 1060/20000 = 0.053.
  2. SE = sqrt(p(1-p)(1/n1 + 1/n2)) = sqrt(0.053*0.947*(2/10000)) = sqrt(0.053*0.947*0.0002) = sqrt(1.0038e-5) = 0.003169.
  3. z = (p2 - p1)/SE = 0.006/0.003169 = 1.893; two-sided p = 2*(1 - Phi(1.893)) ~ 0.0583.
  4. Python: from statsmodels.stats.proportion import proportions_ztest; print(proportions_ztest([560,500],[10000,10000])) -> z~1.89, p~0.058.

Answer. z ~ 1.89, p ~ 0.058; not significant at 0.05 — the 0.6-point lift is not yet distinguishable from chance.

Worked Example 2

Problem. The lift above is +0.006 with SE 0.003169. Give a 95% CI for the difference and interpret.

  1. CI = (p2 - p1) +/- 1.96 * SE_diff. (Using the unpooled SE for the CI ~ 0.00317.)
  2. Margin = 1.96 * 0.003169 = 0.00621.
  3. CI = 0.006 +/- 0.00621 = [-0.0002, 0.0122].
  4. The interval includes 0, consistent with the non-significant p-value: the true lift could be slightly negative to about +1.2 points.

Answer. 95% CI for the lift ~ [-0.0002, 0.0122]; it includes 0, so the effect is not statistically distinguishable from no change.

Worked Example 3

Problem. You intended a 50/50 split but observe 10,000 vs 11,200 users. Why investigate before trusting the result?

  1. Expected split is 50/50; observed is 10000/21200 = 47.2% vs 52.8%.
  2. A chi-squared check against 50/50 on 21,200 users flags whether the deviation is implausible by chance.
  3. chi^2 = (10000-10600)^2/10600 + (11200-10600)^2/10600 = 33.96 + 33.96 = 67.9 on 1 df -> p ~ 0, a clear mismatch.
  4. Such a sample-ratio mismatch signals an assignment/logging bug that can bias the metric; fix it before drawing conclusions.

Answer. The split deviates far more than chance (chi^2 ~ 68, p ~ 0), a sample-ratio mismatch indicating a bug; results are untrustworthy until resolved.

Common mistakes
  • Reporting only the p-value — also give the effect estimate and its confidence interval to judge practical importance.
  • Ignoring a sample-ratio mismatch — an off-target split usually means a bug that biases the analysis.
  • Using a t-test for a proportion metric — for conversion use a two-proportion z-test or chi-squared, not a means t-test.
✎ Try it yourself

Problem. Control converts 300/5000 and treatment 360/5000. Compute both proportions, the pooled proportion, and the two-proportion z-statistic, then state the decision at alpha = 0.05.

Solution. p1 = 300/5000 = 0.060; p2 = 360/5000 = 0.072; pooled p = 660/10000 = 0.066. SE = sqrt(0.066*0.934*(1/5000 + 1/5000)) = sqrt(0.066*0.934*0.0004) = sqrt(2.4658e-5) = 0.004966. z = (0.072 - 0.060)/0.004966 = 0.012/0.004966 = 2.416. Two-sided p = 2*(1 - Phi(2.416)) ~ 0.0157. Since 0.0157 < 0.05, reject H0 — treatment significantly increases conversion (statsmodels: proportions_ztest([360,300],[5000,5000]) gives z~2.42, p~0.016).

Pitfalls: peeking, novelty effects, and Simpson's paradox

Common traps quietly invalidate A/B tests; recognizing them protects your conclusions.

Several pitfalls undermine experiments. Peeking is repeatedly checking results and stopping as soon as significance appears; each look is another chance to cross the threshold by luck, so naive peeking can inflate the Type I error rate from 5% to well over 20%. The fix is to fix the sample size in advance or use sequential methods with adjusted boundaries (e.g., alpha-spending or always-valid p-values). Novelty (and primacy) effects mean a new variant temporarily changes behavior simply because it is new, so short tests can overstate or understate the durable effect; running long enough lets behavior settle. Simpson's paradox is when an effect within every subgroup reverses after pooling, caused by a confounder unevenly distributed across groups; segmenting and using proper weighting or stratification reveals the true picture. Awareness of these traps separates trustworthy experimentation from misleading dashboards.

Worked Example 1

Problem. An analyst checks an A/B test 5 times and stops at the first p < 0.05. Why does this inflate false positives?

  1. Under H0, each independent look has a ~5% chance of crossing the line.
  2. With 5 looks, P(at least one false significance) ~ 1 - (1 - 0.05)^5 (a rough bound for independent looks).
  3. = 1 - 0.7738 = 0.2262, so roughly 23% rather than 5%.
  4. Real sequential looks are correlated, but the inflation is still large; use a sequential testing correction or a fixed n.

Answer. Multiple peeks give multiple chances to cross 0.05 by luck, inflating the false-positive rate to roughly 20%+ instead of 5%.

Worked Example 2

Problem. A new feature shows a huge lift in week 1 that fades to near zero by week 4. What is happening and how do you handle it?

  1. Week-1 spike is likely a novelty effect: users react to newness, not lasting value.
  2. As novelty wears off, behavior reverts toward the true durable effect.
  3. Deciding from week 1 would overstate the impact.
  4. Run the test long enough (multiple weeks) and base the decision on the stabilized, later-period effect.

Answer. It is a novelty effect; run the experiment long enough for behavior to stabilize and judge by the durable effect, not the early spike.

Worked Example 3

Problem. Variant B beats A within both mobile and desktop, yet A wins overall. Explain this Simpson's paradox.

  1. Suppose B was shown disproportionately to mobile users, who convert lower overall.
  2. Within mobile, B > A; within desktop, B > A — B is better in each segment.
  3. But B's overall average is dragged down by its heavier mobile mix (a confounder).
  4. Pooling without accounting for the segment imbalance reverses the conclusion; correct by stratifying or weighting segments equally.

Answer. An uneven segment mix (a confounder) reverses the pooled result; stratify or reweight by segment to recover B's true within-group advantage.

Common mistakes
  • Stopping a test the moment it looks significant — unplanned peeking inflates false positives; fix n in advance or use sequential corrections.
  • Deciding from the first few days — novelty/primacy effects distort early behavior; let the metric stabilize.
  • Trusting only the pooled result — always segment to check for Simpson's paradox driven by an uneven confounder.
✎ Try it yourself

Problem. An A/B test is monitored daily for 10 days and the team will stop at the first day with p < 0.05, with the null actually true. Give a rough upper bound on the false-positive probability treating the looks as independent, and name one proper fix.

Solution. Treating the 10 daily looks as independent, P(at least one false positive) = 1 - (1 - 0.05)^10 = 1 - 0.95^10 = 1 - 0.5987 = 0.4013, roughly a 40% false-positive rate instead of 5%. (Real sequential looks are positively correlated, so the true inflation is somewhat lower but still far above 5%.) A proper fix is to pre-commit to a fixed sample size and analyze once, or use a sequential testing method with alpha-spending boundaries (or always-valid/Bayesian sequential tests) that control the error rate across repeated looks.

Key terms
  • Randomized controlled experiment — a study that randomly assigns units to treatment or control to support causal claims.
  • A/B test — an online randomized experiment comparing a control variant (A) against a treatment variant (B) on a metric.
  • Treatment and control groups — the units exposed to the change versus those kept on the baseline experience.
  • Statistical power — the probability of detecting a true effect of a given size; one minus the Type II error rate.
  • Minimum detectable effect (MDE) — the smallest effect a test is designed to reliably detect at a chosen power.
  • Sample size calculation — determining how many units are needed to achieve target power for a given MDE and alpha.
  • Peeking — repeatedly checking results and stopping early, which inflates the false-positive rate.
  • Simpson's paradox — a trend that appears in groups but reverses when the groups are combined, often due to confounding.
Assignment · Power, sample size, and a simulated A/B test

Pick a conversion-rate scenario (e.g., baseline 5%, hoped-for lift to 6%). Use Python (statsmodels) or R to compute the power for a fixed sample size and the sample size needed for 80% power at alpha = 0.05. Then simulate the experiment: generate treatment and control outcomes, run the appropriate test, and report whether you would have detected the effect. Repeat the simulation many times to estimate empirical power and compare it to the formula.

Deliverable · A notebook showing the power and sample-size calculations, the single-run test result, and the empirical power from repeated simulations, with a note on how peeking would distort the conclusion.

Quiz · 5 questions
  1. 1. Why does randomly assigning units to treatment and control support causal claims?

  2. 2. Statistical power is:

  3. 3. Holding the effect size and alpha fixed, increasing the sample size will generally:

  4. 4. Repeatedly checking an A/B test and stopping as soon as it looks significant (peeking) primarily causes:

  5. 5. Simpson's paradox refers to a situation where:

You'll be able to

I can design a randomized A/B test with a clear metric, hypothesis, and randomization unit.

I can compute the power and required sample size for a target effect, and analyze the result correctly.

I can identify common experiment pitfalls such as peeking, novelty effects, and Simpson's paradox.

Weeks 11-12 Unit 6: Regression & GLMs
fit-simple-regressioninterpret-multiple-regressioninfer-regression-coefficientsdiagnose-regression-fitfit-logistic-regressionexplain-glm-framework
Lecture
Simple linear regression and least squares

Fit a straight line to two variables by minimizing squared vertical distances, yielding a slope and intercept.

Simple linear regression models a continuous response y as y = beta0 + beta1*x + error, a straight line plus random noise. Ordinary least squares (OLS) picks the intercept beta0 and slope beta1 that minimize the sum of squared residuals, sum (y_i - y-hat_i)^2. The closed-form solutions are beta1-hat = Sxy/Sxx = (sum (x_i - x-bar)(y_i - y-bar)) / (sum (x_i - x-bar)^2) and beta0-hat = y-bar - beta1-hat*x-bar, so the fitted line always passes through (x-bar, y-bar). The slope is the estimated change in y per one-unit increase in x. Squaring residuals penalizes large misses and gives a unique, differentiable solution. This is the foundational model of statistics and machine learning: interpretable, fast, and the base case for everything that follows.

Worked Example 1

Problem. Fit a line to x = [1,2,3,4,5], y = [2,4,5,4,5]. Find the slope and intercept.

  1. x-bar = 3, y-bar = 4. Deviations x: -2,-1,0,1,2; y: -2,0,1,0,1.
  2. Sxy = (-2)(-2)+(-1)(0)+(0)(1)+(1)(0)+(2)(1) = 4+0+0+0+2 = 6.
  3. Sxx = 4+1+0+1+4 = 10; beta1-hat = Sxy/Sxx = 6/10 = 0.6.
  4. beta0-hat = y-bar - beta1-hat*x-bar = 4 - 0.6*3 = 4 - 1.8 = 2.2. Python: import numpy as np; np.polyfit([1,2,3,4,5],[2,4,5,4,5],1) -> [0.6, 2.2].

Answer. y-hat = 2.2 + 0.6*x (slope 0.6, intercept 2.2).

Worked Example 2

Problem. Using y-hat = 2.2 + 0.6x, predict y at x = 4 and compute that residual.

  1. Predicted: y-hat = 2.2 + 0.6*4 = 2.2 + 2.4 = 4.6.
  2. Observed y at x = 4 is 4.
  3. Residual = observed - predicted = 4 - 4.6 = -0.6.
  4. The model slightly over-predicts this point by 0.6.

Answer. Predicted y = 4.6; residual = 4 - 4.6 = -0.6.

Worked Example 3

Problem. Interpret a fitted housing model price-hat = 50 + 0.2*sqft (price in $thousands). What does the slope mean and what is a 1500 sqft prediction?

  1. Slope 0.2 means each additional square foot adds 0.2 thousand dollars ($200) to predicted price.
  2. So 100 more sqft adds about $20,000.
  3. Prediction at sqft = 1500: 50 + 0.2*1500 = 50 + 300 = 350 (thousand).
  4. That is a predicted price of $350,000.

Answer. Each extra sqft adds ~$200 to predicted price; a 1,500 sqft home is predicted at $350,000.

Common mistakes
  • Confusing the slope with correlation — the slope carries units (y per x); correlation is unitless and bounded in [-1, 1].
  • Minimizing absolute or horizontal distances by default — OLS minimizes squared vertical residuals, giving its unique formulas.
  • Extrapolating far beyond the observed x-range — the linear fit need not hold outside the data you fitted.
✎ Try it yourself

Problem. Fit a line by least squares to x = [0,1,2,3], y = [1,3,2,5]. Find the slope, intercept, and predict y at x = 5.

Solution. x-bar = 1.5, y-bar = 2.75. x deviations: -1.5,-0.5,0.5,1.5; y deviations: -1.75,0.25,-0.75,2.25. Sxy = (-1.5)(-1.75)+(-0.5)(0.25)+(0.5)(-0.75)+(1.5)(2.25) = 2.625 - 0.125 - 0.375 + 3.375 = 5.5. Sxx = 2.25+0.25+0.25+2.25 = 5. beta1-hat = 5.5/5 = 1.1. beta0-hat = 2.75 - 1.1*1.5 = 2.75 - 1.65 = 1.1. So y-hat = 1.1 + 1.1*x; at x = 5, y-hat = 1.1 + 5.5 = 6.6. (np.polyfit confirms slope 1.1, intercept 1.1.)

Multiple regression and interpreting coefficients

With several predictors, each coefficient is a partial effect holding the others constant.

Multiple linear regression extends the model to several predictors: y = beta0 + beta1*x1 + ... + betap*xp + error. OLS still minimizes the sum of squared residuals, now solving for a vector of coefficients (in matrix form beta-hat = (X'X)^{-1} X'y). The key interpretive shift: each beta_j is a partial effect — the expected change in y for a one-unit increase in x_j while holding all other predictors fixed. This 'controlling for' interpretation is what lets regression disentangle overlapping influences, but it weakens when predictors are highly correlated (multicollinearity), which inflates coefficient variances and makes individual effects hard to separate. Categorical predictors enter via dummy (indicator) variables interpreted relative to a reference level. Multiple regression is the everyday tool for prediction and for adjusted, ceteris paribus association in data science.

Worked Example 1

Problem. A model is salary-hat = 30 + 2*years + 5*degree (degree is 1 if graduate, else 0; salary in $thousands). Interpret each coefficient.

  1. Intercept 30: predicted salary for years = 0 and no graduate degree is $30k.
  2. Coefficient 2 on years: each additional year adds $2k, holding degree fixed.
  3. Coefficient 5 on degree: a graduate degree adds $5k versus no degree, holding years fixed.
  4. Predict 5 years, graduate: 30 + 2*5 + 5*1 = 30 + 10 + 5 = 45 -> $45k.

Answer. Base $30k; +$2k per year (holding degree); +$5k for a graduate degree (holding years); predicted $45k for 5 years with a degree.

Worked Example 2

Problem. Why might a predictor's coefficient change sign when a second correlated predictor is added?

  1. In simple regression the coefficient absorbs the effect of omitted correlated variables (omitted-variable bias).
  2. Adding the correlated predictor lets the model attribute shared variation correctly.
  3. The original coefficient now reflects the partial effect, holding the new variable fixed.
  4. This can flip its sign — a classic confounding/multicollinearity phenomenon.

Answer. Because the simple-regression coefficient was biased by an omitted correlated variable; controlling for it reveals the true partial effect, which can change sign.

Worked Example 3

Problem. Sketch statsmodels code to fit and read coefficients for y on x1 and x2.

  1. import statsmodels.api as sm; import pandas as pd
  2. X = sm.add_constant(df[['x1','x2']]) # adds the intercept column
  3. model = sm.OLS(df['y'], X).fit()
  4. print(model.params) # intercept and partial slopes; model.summary() shows full output

Answer. sm.add_constant then sm.OLS(...).fit(); model.params gives the intercept and each predictor's partial slope.

Common mistakes
  • Reading a coefficient as a total/marginal effect — it is the partial effect holding the other predictors fixed.
  • Ignoring multicollinearity — highly correlated predictors inflate standard errors and make individual coefficients unstable.
  • Forgetting to add the intercept (constant) — omitting it forces the line through the origin and biases the fit (use sm.add_constant).
✎ Try it yourself

Problem. A model predicts monthly spend (dollars): spend-hat = 20 + 0.5*age + 15*member, where member = 1 for members, 0 otherwise. Interpret the 15 coefficient and predict spend for a 40-year-old member.

Solution. The coefficient 15 on member means that, holding age fixed, members are predicted to spend $15 more per month than non-members. Prediction for a 40-year-old member: spend-hat = 20 + 0.5*40 + 15*1 = 20 + 20 + 15 = 55, so $55 per month. (A 40-year-old non-member would be predicted at 20 + 20 + 0 = $40.)

Inference for regression: standard errors and tests

Each coefficient comes with a standard error, t-statistic, p-value, and confidence interval for inference.

Regression estimates are statistics, so they carry uncertainty. Each coefficient beta_j-hat has a standard error SE(beta_j-hat) reflecting its sampling variability. To test H0: beta_j = 0 (the predictor has no linear effect), compute t = beta_j-hat / SE(beta_j-hat), compared to a t-distribution with n - p - 1 degrees of freedom (n observations, p predictors plus intercept). A small p-value indicates the predictor is significantly associated with y. A confidence interval is beta_j-hat +/- t* * SE(beta_j-hat). The overall F-test asks whether the model as a whole explains more than the intercept-only model. Valid inference rests on the regression assumptions (linearity, independent errors, constant variance, approximately normal errors); violations distort the standard errors. Reporting coefficients with their intervals — not just point estimates — is essential honest practice.

Worked Example 1

Problem. A coefficient is beta-hat = 0.80 with SE = 0.20, n = 50, p = 3 predictors. Test H0: beta = 0.

  1. t = beta-hat/SE = 0.80/0.20 = 4.0.
  2. df = n - p - 1 = 50 - 3 - 1 = 46.
  3. Two-sided p = 2*P(T46 >= 4.0). scipy: 2*(1 - stats.t.cdf(4.0, 46)) -> 0.00023.
  4. p ~ 0.0002 < 0.05, so reject H0 — the predictor is significant.

Answer. t = 4.0 on 46 df, p ~ 0.0002; the coefficient is significantly different from 0.

Worked Example 2

Problem. For that coefficient (beta-hat = 0.80, SE = 0.20, df = 46), give an approximate 95% confidence interval.

  1. CI = beta-hat +/- t* * SE; t* for 95% with 46 df ~ 2.013.
  2. Margin = 2.013 * 0.20 = 0.4026.
  3. CI = 0.80 +/- 0.4026 = [0.397, 1.203].
  4. It excludes 0, consistent with the significant test.

Answer. 95% CI ~ [0.397, 1.203]; it excludes 0, agreeing with the significant t-test.

Worked Example 3

Problem. What does the model's overall F-test assess, and how does it differ from the per-coefficient t-tests?

  1. The F-test compares the fitted model to an intercept-only model.
  2. H0: all slope coefficients are simultaneously 0 (the predictors jointly explain nothing).
  3. A small F p-value means at least one predictor matters as a set.
  4. t-tests examine each coefficient individually; the F-test is a joint test, useful when predictors are correlated and individually borderline.

Answer. The F-test jointly tests whether all slopes are zero (overall model usefulness); t-tests assess each coefficient one at a time.

Common mistakes
  • Reading a coefficient's significance without its standard error or interval — a large estimate with a huge SE is not reliable.
  • Trusting p-values when assumptions are violated — heteroscedasticity or dependence distorts SEs; use robust SEs if needed.
  • Confusing the overall F-test with individual t-tests — F is a joint test; a model can be F-significant yet have no individually significant predictor under multicollinearity.
✎ Try it yourself

Problem. A regression coefficient is beta-hat = -1.5 with SE = 0.5 from a model with n = 24 and p = 2 predictors. Test H0: beta = 0 at alpha = 0.05 and give the t-statistic and df.

Solution. t = beta-hat/SE = -1.5/0.5 = -3.0. df = n - p - 1 = 24 - 2 - 1 = 21. Two-sided p = 2*P(T21 >= 3.0); scipy: 2*(1 - stats.t.cdf(3.0, 21)) = 0.0069. Since 0.0069 < 0.05 (and |t| = 3.0 exceeds the critical t ~ 2.080 for 21 df), reject H0 — the predictor has a significant negative association with the response.

Diagnostics: residuals, assumptions, and outliers

Residual plots reveal whether the regression assumptions hold and flag influential points.

OLS inference relies on four assumptions, all checkable through residuals (observed minus fitted). Linearity: a residuals-vs-fitted plot should show no pattern; curvature signals a missing nonlinear term. Constant variance (homoscedasticity): the residual spread should be roughly uniform across fitted values; a funnel shape indicates heteroscedasticity. Normality of errors: a Q-Q plot of residuals should be roughly straight, important for small-sample inference. Independence: residuals should not be autocorrelated (relevant for time series). Beyond assumptions, watch for influential points using leverage (extreme x) and Cook's distance (impact on the fitted coefficients); a high-leverage outlier can single-handedly tilt the line. Diagnostics turn regression from a black box into a model you can trust or fix — transform variables, add terms, or use robust methods as the plots dictate.

Worked Example 1

Problem. A residuals-vs-fitted plot shows a clear U-shaped (parabolic) pattern. What is violated and what is a fix?

  1. Residuals should scatter randomly around 0 with no shape.
  2. A U-shape means the model systematically under- then over-predicts: the linearity assumption fails.
  3. The true relationship is likely curved.
  4. Fix by adding a quadratic term (x^2) or transforming x or y.

Answer. The linearity assumption is violated; add a polynomial term (e.g., x^2) or transform a variable to capture the curvature.

Worked Example 2

Problem. Residual spread is small for low fitted values and large for high ones (a funnel). Name the issue and a remedy.

  1. Increasing spread with fitted value indicates non-constant error variance.
  2. This is heteroscedasticity, which biases the usual standard errors.
  3. Point estimates stay unbiased, but t-tests and CIs are unreliable.
  4. Remedy: log-transform y, use weighted least squares, or report heteroscedasticity-robust (HC) standard errors.

Answer. Heteroscedasticity (non-constant variance); fix via a log/variance-stabilizing transform, weighted least squares, or robust standard errors.

Worked Example 3

Problem. One data point has very high leverage and a large Cook's distance. Why does it matter and how do you respond?

  1. High leverage means its x-value is far from the others, giving it outsized pull on the fit.
  2. A large Cook's distance means removing it would substantially change the coefficients.
  3. Such a point can dominate the entire regression line.
  4. Investigate it (data error? legitimate extreme?); refit with and without it, and report sensitivity rather than silently deleting.

Answer. It is an influential point that can dominate the fit; investigate it, refit with and without it, and report how the conclusions change.

Common mistakes
  • Judging fit by R-squared alone — a high R-squared can hide curvature, heteroscedasticity, or an influential point that diagnostics would reveal.
  • Deleting outliers automatically — investigate first; an outlier may be the most informative or a data-entry error.
  • Ignoring heteroscedasticity — it leaves estimates unbiased but invalidates the default standard errors and p-values.
✎ Try it yourself

Problem. After fitting a regression you make a Q-Q plot of the residuals and it bends sharply up at both ends (heavy tails). Which assumption is in question, when does it matter most, and what is one response?

Solution. An S-shaped or both-ends-bending Q-Q plot indicates the residuals are not normally distributed (here, heavier tails than normal). The normality assumption matters most for inference (t-tests, confidence intervals) in small samples; with large n the CLT makes coefficient estimates approximately normal regardless. One response is to transform the response (e.g., log y) to tame the tails, add a missing predictor, or use robust/bootstrap standard errors that do not rely on normal errors. In Python: import statsmodels.api as sm; sm.qqplot(model.resid, line='45').

Logistic regression for binary outcomes

Model the log-odds of a yes/no outcome as a linear function of predictors, fit by maximum likelihood.

When the response is binary (0/1), linear regression is inappropriate because it can predict probabilities outside [0,1]. Logistic regression instead models the log-odds (logit) of the positive class as linear: log(p/(1 - p)) = beta0 + beta1*x1 + ... + betap*xp. Solving for p gives the S-shaped logistic (sigmoid) curve p = 1/(1 + e^{-(linear predictor)}), always between 0 and 1. Coefficients are interpreted on the odds scale: a one-unit increase in x_j multiplies the odds by e^{beta_j} (the odds ratio). So e^{beta_j} > 1 raises the odds, < 1 lowers them. There is no closed-form solution, so coefficients are estimated by maximum likelihood via iterative optimization. Logistic regression is the standard interpretable classifier in data science for problems like churn, default, and click prediction.

Worked Example 1

Problem. A logistic model has logit(p) = -2 + 0.5*x. Find the predicted probability at x = 4.

  1. Linear predictor = -2 + 0.5*4 = -2 + 2 = 0.
  2. p = 1/(1 + e^{-0}) = 1/(1 + 1) = 0.5.
  3. So at x = 4 the predicted probability is exactly 0.5.
  4. Python: import numpy as np; print(1/(1+np.exp(-(-2 + 0.5*4)))) -> 0.5.

Answer. Predicted probability p = 0.5 at x = 4.

Worked Example 2

Problem. Interpret a coefficient beta = 0.7 on a predictor as an odds ratio.

  1. The odds ratio is e^{beta}.
  2. e^{0.7} = 2.014.
  3. So a one-unit increase in the predictor about doubles the odds of the positive outcome (holding others fixed).
  4. Python: import numpy as np; print(np.exp(0.7)) -> 2.0138.

Answer. Odds ratio = e^{0.7} ~ 2.01; each one-unit increase roughly doubles the odds of the positive outcome.

Worked Example 3

Problem. At x = 6 with logit(p) = -2 + 0.5*x, find the predicted probability and the odds.

  1. Linear predictor = -2 + 0.5*6 = -2 + 3 = 1.
  2. p = 1/(1 + e^{-1}) = 1/(1 + 0.3679) = 1/1.3679 = 0.731.
  3. Odds = p/(1 - p) = 0.731/0.269 = 2.718 (= e^1, as expected since logit = 1).
  4. Python: import numpy as np; p = 1/(1+np.exp(-1)); print(p, p/(1-p)) -> 0.7311 2.7183.

Answer. p ~ 0.731 and odds ~ 2.72 (e^1) at x = 6.

Common mistakes
  • Interpreting logistic coefficients as changes in probability — they are changes in log-odds; exponentiate to get odds ratios.
  • Using linear regression for a binary outcome — it can predict probabilities below 0 or above 1 and violates the error assumptions.
  • Confusing odds with probability — odds = p/(1 - p) can exceed 1, while probability is bounded in [0, 1].
✎ Try it yourself

Problem. A logistic model is logit(p) = -1 + 0.8*x. (a) Find the predicted probability at x = 1. (b) Interpret the coefficient 0.8 as an odds ratio.

Solution. (a) Linear predictor at x = 1 is -1 + 0.8*1 = -0.2. p = 1/(1 + e^{0.2}) = 1/(1 + 1.2214) = 1/2.2214 = 0.450. So the predicted probability is about 0.45. (b) The odds ratio is e^{0.8} = 2.2255, meaning each one-unit increase in x multiplies the odds of the positive outcome by about 2.23 (a 123% increase in odds), holding other predictors fixed. In Python: 1/(1+np.exp(0.2)) = 0.4502; np.exp(0.8) = 2.2255.

Generalized linear models and link functions

GLMs unify regression by pairing a response distribution with a link function connecting predictors to the mean.

A generalized linear model (GLM) generalizes linear regression through three components: a random component (the response's distribution from the exponential family, e.g., normal, Bernoulli, Poisson), a systematic component (the linear predictor eta = beta0 + sum beta_j*x_j), and a link function g connecting them via g(mean) = eta. The link maps the constrained mean onto the unbounded linear scale: the identity link recovers ordinary linear regression (normal response), the logit link gives logistic regression (Bernoulli response), and the log link gives Poisson regression for count data (ensuring positive means). This framework lets one fitting machinery (iteratively reweighted least squares maximizing the likelihood) handle continuous, binary, and count outcomes with consistent inference. Recognizing which distribution and link suit a problem is a core modeling skill that subsumes the earlier regressions as special cases.

Worked Example 1

Problem. Name the response distribution and link for (a) ordinary linear regression and (b) logistic regression.

  1. GLM = distribution + linear predictor + link.
  2. (a) Linear regression: Normal response with the identity link, g(mu) = mu.
  3. (b) Logistic regression: Bernoulli/Binomial response with the logit link, g(p) = log(p/(1 - p)).
  4. Both are GLMs sharing the same fitting machinery.

Answer. (a) Normal distribution, identity link; (b) Bernoulli distribution, logit link.

Worked Example 2

Problem. For count data (e.g., number of website visits), which GLM fits, and why a log link?

  1. Counts are non-negative integers, suited to the Poisson distribution.
  2. The mean count mu must be positive.
  3. The log link g(mu) = log(mu) maps positive mu onto the whole real line for the linear predictor.
  4. So log(mu) = beta0 + sum beta_j*x_j, and mu = e^{linear predictor} > 0 always.

Answer. Poisson regression with a log link; the log link keeps the predicted mean count positive.

Worked Example 3

Problem. In a Poisson (log-link) model, log(mu) = 0.5 + 0.3*x. Interpret the 0.3 coefficient and find mu at x = 2.

  1. On the log scale, a one-unit increase in x adds 0.3 to log(mu).
  2. On the count scale, it multiplies the mean by e^{0.3} = 1.350 (a 35% increase).
  3. At x = 2: log(mu) = 0.5 + 0.3*2 = 0.5 + 0.6 = 1.1.
  4. mu = e^{1.1} = 3.004. Python: import numpy as np; print(np.exp(0.3), np.exp(1.1)) -> 1.3499 3.0042.

Answer. Each unit of x multiplies the expected count by e^{0.3} ~ 1.35; at x = 2, mu = e^{1.1} ~ 3.0.

Common mistakes
  • Forgetting a GLM needs all three parts — a distribution, a linear predictor, AND a link function.
  • Using an identity link for bounded responses — probabilities and counts need logit/log links to keep predictions in range.
  • Interpreting log-link coefficients additively on the response scale — exponentiate them; they act multiplicatively on the mean.
✎ Try it yourself

Problem. You model the number of support tickets per customer (a count) with predictors. (a) State the appropriate GLM distribution and link. (b) If the fitted model is log(mu) = 0.2 + 0.5*plan, where plan is 1 for premium and 0 otherwise, interpret the plan coefficient and predict the mean tickets for a premium customer.

Solution. (a) Counts call for Poisson regression with a log link. (b) The coefficient 0.5 on plan means premium customers' expected ticket count is multiplied by e^{0.5} = 1.6487, about a 65% increase versus non-premium, holding other predictors fixed. Prediction for a premium customer: log(mu) = 0.2 + 0.5*1 = 0.7, so mu = e^{0.7} = 2.0138, about 2.0 tickets. (A non-premium customer: mu = e^{0.2} = 1.221.) In Python: np.exp(0.5) = 1.6487; np.exp(0.7) = 2.0138.

Key terms
  • Linear regression — modeling a continuous response as a linear combination of predictors plus error.
  • Least squares — the method that fits a line or plane by minimizing the sum of squared residuals.
  • Coefficient (slope) — the estimated change in the response per one-unit change in a predictor, holding others fixed.
  • Residual — the difference between an observed value and the model's fitted value.
  • R-squared — the proportion of variance in the response explained by the model.
  • Logistic regression — a GLM that models the log-odds of a binary outcome as a linear function of predictors.
  • Link function — the function connecting the linear predictor to the mean of the response in a GLM (e.g., logit, log).
  • Generalized linear model (GLM) — a family extending linear regression to non-normal responses via a link and a distribution.
Assignment · From linear to logistic

Using a dataset with a continuous outcome, fit a multiple linear regression in Python (statsmodels) or R, interpret the coefficients and R-squared, and run residual diagnostics to check the assumptions. Then take a binary outcome from the same domain and fit a logistic regression, interpreting one coefficient as an odds ratio. Compare what each model framework is doing.

Deliverable · A report with the linear model summary and diagnostic plots, the logistic model summary with an odds-ratio interpretation, and a paragraph contrasting the two models.

Quiz · 5 questions
  1. 1. Ordinary least squares fits a regression line by:

  2. 2. In a multiple regression, a coefficient represents:

  3. 3. A residual plot showing a clear curved pattern most likely indicates:

  4. 4. Logistic regression models which quantity as a linear function of the predictors?

  5. 5. A generalized linear model is defined by three components: a response distribution, a linear predictor, and a:

You'll be able to

I can fit and interpret simple and multiple linear regression models and test their coefficients.

I can check regression assumptions with residual diagnostics and identify influential points.

I can fit a logistic regression and explain how GLMs extend linear models through link functions.

Weeks 13-14 Unit 7: Bayesian Inference Intro
contrast-bayesian-frequentistapply-bayes-rule-inferenceuse-conjugate-priorsummarize-posteriorapproximate-posterior-computationallyapply-bayesian-ab-test
Lecture
Bayesian vs. frequentist thinking

The two schools differ in what 'probability' means and whether a parameter can have a distribution.

Frequentist inference treats a parameter theta as a fixed unknown constant; probability is the long-run frequency of events over repeated sampling, so a confidence interval is a statement about the procedure, not the parameter. Bayesian inference treats theta as a random variable with a probability distribution that encodes our belief, updated by data via Bayes' rule. The Bayesian workflow is: choose a prior p(theta), observe data with likelihood p(data | theta), and obtain the posterior p(theta | data) proportional to likelihood times prior. The big payoff is interpretation: a Bayesian 95% credible interval lets you say directly 'there is a 95% probability theta lies here,' a statement a frequentist CI cannot make. For data scientists, the Bayesian view shines when you have prior knowledge, small samples, or want probability statements about hypotheses themselves.

Worked Example 1

Problem. A frequentist reports a 95% CI of [0.30, 0.38] for a conversion rate; a Bayesian reports a 95% credible interval of [0.30, 0.38]. How do their interpretations differ?

  1. Frequentist: the rate p is a fixed constant, so the 95% describes the long-run capture rate of the interval-building procedure, not this interval.
  2. Bayesian: p has a posterior distribution, so the interval carries the direct claim P(0.30 <= p <= 0.38 | data) = 0.95.
  3. Same numbers, different meaning: one is about the method across repetitions, the other about this parameter given the data.
  4. The Bayesian statement requires a prior; the frequentist statement does not.

Answer. Numerically identical here, but the Bayesian interval makes a direct probability statement about p, while the frequentist interval describes the procedure's long-run coverage.

Worked Example 2

Problem. You flip a coin 3 times and get 3 heads. Contrast the frequentist MLE of p(heads) with a Bayesian estimate using a uniform prior.

  1. Frequentist MLE = s/n = 3/3 = 1.0, claiming the coin always lands heads — implausible from 3 flips.
  2. Bayesian with a uniform Beta(1,1) prior: posterior is Beta(1+3, 1+0) = Beta(4,1).
  3. Posterior mean = a/(a+b) = 4/5 = 0.8, pulled away from 1.0 toward the prior.
  4. Python: from scipy.stats import beta; print(beta(4,1).mean()) -> 0.8.

Answer. The frequentist MLE is an overconfident 1.0; the Bayesian posterior mean of 0.8 (Beta(4,1)) regularizes the estimate using the prior, more sensible for tiny n.

Worked Example 3

Problem. Why can a Bayesian directly answer 'what is the probability the new variant beats the old one?' while a frequentist test cannot?

  1. A frequentist p-value is P(data at least this extreme | H0 true), not P(hypothesis | data).
  2. Frequentist parameters are fixed, so 'probability the variant is better' is undefined in that framework.
  3. A Bayesian has full posteriors for each variant's rate, so P(p_B > p_A | data) is a well-defined integral.
  4. Python: draws_A = beta(aA,bA).rvs(100000); draws_B = beta(aB,bB).rvs(100000); print((draws_B > draws_A).mean()) estimates that probability.

Answer. Because Bayesian parameters have distributions, P(p_B > p_A | data) is a direct, computable quantity; the frequentist framework treats parameters as fixed and only bounds error rates.

Common mistakes
  • Reading a frequentist CI as a Bayesian credible interval — only the credible interval supports 'P(theta in interval) = 0.95'.
  • Thinking Bayesian methods are 'subjective' and therefore unscientific — the prior is an explicit, auditable assumption that data can override.
  • Believing the two always disagree — with a flat prior and large n they often give nearly identical numbers but different interpretations.
✎ Try it yourself

Problem. Explain, in one or two sentences each, (a) what probability means to a frequentist vs. a Bayesian, and (b) which framework lets you say 'there is a 90% probability the parameter is between 2 and 5.'

Solution. (a) To a frequentist, probability is the long-run relative frequency of an event over many repetitions, and a parameter is a fixed unknown constant; to a Bayesian, probability is a degree of belief, and the parameter itself has a probability distribution updated by data. (b) Only the Bayesian framework can make the direct statement 'there is a 90% probability the parameter is between 2 and 5,' because it treats the parameter as a random variable with a posterior distribution; the frequentist can only say 90% of such intervals would capture the parameter over repeated sampling.

Priors, likelihoods, and posteriors

Bayes' rule combines what you believed before (prior) with what the data say (likelihood) into an updated belief (posterior).

Bayesian inference runs on Bayes' rule: p(theta | data) = p(data | theta) * p(theta) / p(data). In words, posterior proportional to likelihood times prior, since the denominator p(data) (the evidence) is just a normalizing constant that does not depend on theta. The prior p(theta) encodes belief before data — it can be informative (a sharp guess) or weak/flat (let the data speak). The likelihood p(data | theta) is the same object from MLE: how probable the observed data are for each theta. Multiplying them and renormalizing yields the posterior, the full updated distribution over theta. The shape of the posterior is a compromise: with little data the prior dominates, and as data accumulate the likelihood takes over and the posterior concentrates around the data-driven value. This update can be done repeatedly, yesterday's posterior becoming today's prior.

Worked Example 1

Problem. State Bayes' rule for a parameter theta and explain why we usually write 'posterior proportional to likelihood times prior.'

  1. Bayes' rule: p(theta | data) = p(data | theta) * p(theta) / p(data).
  2. The denominator p(data) = integral over theta of p(data | theta) p(theta) d-theta does not depend on theta.
  3. So as a function of theta, p(theta | data) is proportional to p(data | theta) * p(theta).
  4. We recover the constant at the end by requiring the posterior to integrate to 1.

Answer. p(theta | data) proportional to p(data | theta) * p(theta); the omitted denominator is a theta-independent normalizing constant fixed by making the posterior integrate to 1.

Worked Example 2

Problem. A disease has prevalence 1% (prior). A test is 99% sensitive and 95% specific. Given a positive test, find the posterior probability of disease.

  1. Prior: P(D) = 0.01, P(not D) = 0.99. Likelihoods: P(+ | D) = 0.99, P(+ | not D) = 1 - 0.95 = 0.05.
  2. Evidence: P(+) = 0.99*0.01 + 0.05*0.99 = 0.0099 + 0.0495 = 0.0594.
  3. Posterior: P(D | +) = (0.99*0.01)/0.0594 = 0.0099/0.0594 = 0.1667.
  4. Python: print((0.99*0.01)/(0.99*0.01 + 0.05*0.99)) -> 0.1667.

Answer. P(disease | positive) ~ 0.167; despite a positive test, the low prior keeps the posterior near 17%, illustrating how strongly the prior matters with rare conditions.

Worked Example 3

Problem. Show how the same final posterior arises whether you update with two data batches sequentially or all at once.

  1. Sequential: posterior1 proportional to L(batch1 | theta) * prior(theta); then posterior2 proportional to L(batch2 | theta) * posterior1(theta).
  2. Substitute: posterior2 proportional to L(batch2 | theta) * L(batch1 | theta) * prior(theta).
  3. All-at-once: posterior proportional to L(batch1, batch2 | theta) * prior(theta) = L(batch1)*L(batch2)*prior for independent data.
  4. The two expressions are identical, so updating order and batching do not change the result.

Answer. Because independent likelihoods multiply, sequential updating (yesterday's posterior becomes today's prior) gives exactly the same posterior as updating on all data at once.

Common mistakes
  • Forgetting to renormalize — 'likelihood times prior' gives an unnormalized posterior; divide by the evidence so it integrates to 1.
  • Confusing the likelihood with a probability of theta — p(data | theta) is a function of the data for fixed theta, not a distribution over theta.
  • Ignoring how much the prior matters when data are scarce or the event is rare — see the base-rate effect in the disease example.
✎ Try it yourself

Problem. Spam filtering: 20% of emails are spam (prior). The word 'free' appears in 60% of spam emails and 5% of non-spam. An email contains 'free.' What is the posterior probability it is spam?

Solution. Prior: P(spam) = 0.20, P(ham) = 0.80. Likelihoods: P('free' | spam) = 0.60, P('free' | ham) = 0.05. Evidence P('free') = 0.60*0.20 + 0.05*0.80 = 0.12 + 0.04 = 0.16. Posterior P(spam | 'free') = (0.60*0.20)/0.16 = 0.12/0.16 = 0.75. In Python: (0.6*0.2)/(0.6*0.2 + 0.05*0.8) = 0.75. So there is a 75% posterior probability the email is spam.

Conjugate priors: the Beta-Binomial model

When the prior and likelihood pair so the posterior stays in the prior's family, updating becomes simple arithmetic.

A conjugate prior is chosen so the posterior belongs to the same distributional family as the prior, making Bayesian updating analytic with no integration. The flagship example is the Beta-Binomial model for a proportion p. The Beta(a, b) distribution lives on [0, 1] and is the natural prior for a probability; a and b act like prior pseudo-counts of successes and failures. With binomial data of s successes in n trials, the likelihood is proportional to p^s (1 - p)^{n - s}, and multiplying by the Beta(a, b) prior proportional to p^{a-1}(1 - p)^{b-1} gives a posterior proportional to p^{a+s-1}(1 - p)^{b+n-s-1} — exactly Beta(a + s, b + n - s). So you just add successes to a and failures to b. Beta(1,1) is the uniform (flat) prior. Conjugacy makes the Beta-Binomial the textbook entry point to Bayesian estimation of rates.

Worked Example 1

Problem. With a uniform Beta(1,1) prior, you observe 8 successes in 10 trials. Find the posterior distribution.

  1. Posterior = Beta(a + s, b + n - s).
  2. a = 1, b = 1, s = 8, n - s = 10 - 8 = 2.
  3. Posterior = Beta(1 + 8, 1 + 2) = Beta(9, 3).
  4. Python: from scipy.stats import beta; post = beta(9, 3).

Answer. Posterior = Beta(9, 3).

Worked Example 2

Problem. Compute the posterior mean for Beta(9, 3) and compare it to the MLE p-hat = 8/10.

  1. Posterior mean of Beta(a, b) = a/(a + b) = 9/(9 + 3) = 9/12 = 0.75.
  2. MLE = s/n = 8/10 = 0.80.
  3. The posterior mean (0.75) is pulled slightly below the MLE toward the prior mean 0.5.
  4. Python: from scipy.stats import beta; print(beta(9,3).mean()) -> 0.75.

Answer. Posterior mean = 0.75, shrunk from the MLE of 0.80 toward the prior mean 0.5 because of the prior's two pseudo-observations.

Worked Example 3

Problem. Use an informative prior Beta(20, 20) (belief centered at 0.5) with the same data (8 of 10). How does the posterior compare to the flat-prior case?

  1. Posterior = Beta(20 + 8, 20 + 2) = Beta(28, 22).
  2. Posterior mean = 28/(28 + 22) = 28/50 = 0.56.
  3. The strong prior (40 pseudo-counts) dominates only 10 data points, so the mean stays near 0.5.
  4. Compared with the flat-prior mean 0.75, the informative prior pulls the estimate much closer to 0.5; more data would be needed to override it.

Answer. Posterior = Beta(28, 22), mean ~ 0.56 — far closer to the prior's 0.5 than the flat-prior result of 0.75, showing a strong prior resists a small dataset.

Common mistakes
  • Subtracting instead of adding counts — the update is Beta(a + s, b + n - s); you add successes to a and failures to b.
  • Forgetting that a and b are pseudo-counts — a strong prior (large a + b) can overwhelm a small sample, so size your prior deliberately.
  • Assuming conjugacy holds for any prior — only specific prior-likelihood pairs (e.g., Beta-Binomial, Gamma-Poisson, Normal-Normal) are conjugate.
✎ Try it yourself

Problem. Starting from a Beta(2, 2) prior, you run an A/B variant and see 30 conversions out of 50 visitors. Give the posterior distribution and its mean.

Solution. Posterior = Beta(a + s, b + n - s) = Beta(2 + 30, 2 + (50 - 30)) = Beta(32, 22). Posterior mean = a/(a + b) = 32/(32 + 22) = 32/54 = 0.5926, about 0.593. In Python: from scipy.stats import beta; beta(32, 22).mean() -> 0.5926. The raw conversion rate 30/50 = 0.60 is pulled slightly toward the prior mean 0.5.

Summarizing the posterior and credible intervals

The posterior is a full distribution; we report point summaries and a credible interval that carries a direct probability.

A posterior distribution holds everything we know about theta, but we usually summarize it. Common point estimates are the posterior mean (minimizes squared-error loss), the posterior median (robust, minimizes absolute-error loss), and the posterior mode or MAP (the peak). To express uncertainty we report a credible interval: a (1 - alpha) credible interval is any region containing (1 - alpha) of the posterior probability. The equal-tailed interval uses the alpha/2 and 1 - alpha/2 posterior quantiles; the highest posterior density (HPD) interval is the shortest interval with the required mass and is preferable for skewed posteriors. Unlike a confidence interval, a 95% credible interval supports the direct statement 'P(theta in interval | data) = 0.95.' For the Beta posterior these summaries are one-line computations in SciPy or R.

Worked Example 1

Problem. For a Beta(9, 3) posterior, compute the posterior mean and an equal-tailed 95% credible interval.

  1. Posterior mean = a/(a + b) = 9/12 = 0.75.
  2. Equal-tailed 95% interval uses the 2.5th and 97.5th posterior percentiles.
  3. Python: from scipy.stats import beta; print(beta(9,3).ppf([0.025, 0.975])).
  4. This returns approximately [0.482, 0.940].

Answer. Posterior mean = 0.75; 95% equal-tailed credible interval ~ [0.482, 0.940], i.e., P(0.482 <= p <= 0.940 | data) = 0.95.

Worked Example 2

Problem. State the direct interpretation of the credible interval [0.482, 0.940] and contrast it with a confidence interval.

  1. Credible: given the data and prior, there is a 95% posterior probability that p lies in [0.482, 0.940].
  2. This is a probability statement about the parameter itself.
  3. A frequentist CI instead says 95% of such intervals would capture the fixed p over repeated sampling.
  4. The credible interpretation is what most people intuitively (but wrongly) read into a CI.

Answer. The credible interval says P(p in [0.482, 0.940] | data) = 0.95 directly about p, whereas a confidence interval makes a long-run statement about the procedure, not this interval.

Worked Example 3

Problem. When does the highest posterior density (HPD) interval differ noticeably from the equal-tailed interval, and how do you compute it?

  1. For a symmetric posterior the HPD and equal-tailed intervals coincide.
  2. For a skewed posterior (e.g., Beta with very different a and b) the HPD is shorter and shifted toward the mode.
  3. HPD is the shortest interval containing the target mass, so it excludes low-density tails.
  4. Python: from arviz import hdi; import numpy as np; samples = beta(9,3).rvs(200000); print(hdi(samples, hdi_prob=0.95)).

Answer. They diverge for skewed posteriors, where the HPD is the shortest 95% interval (closer to the mode); compute it from posterior samples with a tool like ArviZ's hdi.

Common mistakes
  • Reporting only a point estimate — the posterior's spread (via a credible interval) is the whole advantage of being Bayesian.
  • Confusing equal-tailed and HPD intervals for skewed posteriors — they differ; HPD is the shortest interval with the stated mass.
  • Calling a credible interval a confidence interval — only the credible interval supports a direct probability claim about the parameter.
✎ Try it yourself

Problem. You have a Beta(32, 22) posterior for a conversion rate. Report the posterior mean and outline how to get a 90% equal-tailed credible interval in Python.

Solution. Posterior mean = a/(a + b) = 32/(32 + 22) = 32/54 = 0.5926. For a 90% equal-tailed credible interval, take the 5th and 95th posterior percentiles: from scipy.stats import beta; lo, hi = beta(32, 22).ppf([0.05, 0.95]), which returns approximately [0.483, 0.700]. Interpretation: given the data and prior, there is a 90% posterior probability the true conversion rate lies between about 0.483 and 0.700.

A first taste of computation: grid approximation and sampling

When the posterior has no closed form, approximate it on a grid or by drawing samples from it.

Conjugate models are convenient but rare; most real posteriors lack a closed form. Two beginner-friendly approximations help. Grid approximation discretizes the parameter space into a fine grid, evaluates the unnormalized posterior (likelihood times prior) at each grid point, and normalizes by dividing by the sum — giving a discrete approximation you can use for means, intervals, and plots. It is exact in the limit of a fine grid but scales badly beyond one or two parameters (the curse of dimensionality). Sampling methods instead draw many values from the posterior; the collection of samples represents the distribution, and any summary becomes a simple average or quantile of the draws. For conjugate models you can sample directly; for general models, Markov chain Monte Carlo (MCMC) tools like PyMC or Stan draw correlated samples. Both turn integration into counting.

Worked Example 1

Problem. Outline a grid approximation for a Beta(1,1) prior with 7 successes in 10 trials, and how to get the posterior mean.

  1. Grid: import numpy as np; p = np.linspace(0, 1, 1001).
  2. Unnormalized posterior (prior is flat): post = p**7 * (1 - p)**3.
  3. Normalize: post = post / post.sum().
  4. Posterior mean = np.sum(p * post); this is ~0.667, matching Beta(8,4)'s mean 8/12 = 0.667.

Answer. Evaluate p^7(1-p)^3 on a fine grid, normalize by its sum, and the weighted average sum(p*post) ~ 0.667 reproduces the analytic Beta(8,4) mean.

Worked Example 2

Problem. Estimate the posterior mean and a 95% credible interval of a Beta(9,3) posterior by drawing samples.

  1. from scipy.stats import beta; import numpy as np; draws = beta(9, 3).rvs(100000).
  2. Posterior mean ~ draws.mean() ~ 0.75 (matches the analytic 9/12).
  3. 95% interval ~ np.percentile(draws, [2.5, 97.5]) ~ [0.48, 0.94].
  4. More draws reduce Monte Carlo error; 100k gives stable two-decimal summaries.

Answer. Sampling 100k Beta(9,3) draws gives mean ~ 0.75 and 95% interval ~ [0.48, 0.94], matching the analytic results to Monte Carlo precision.

Worked Example 3

Problem. Why does grid approximation become impractical for models with many parameters?

  1. A grid with g points per parameter needs g^d points for d parameters.
  2. With g = 100 and d = 5, that is 100^5 = 10^10 evaluations — infeasible.
  3. This exponential blow-up is the curse of dimensionality.
  4. Sampling methods like MCMC scale far better because they explore high-probability regions instead of the entire grid.

Answer. Because a grid needs g^d points, cost explodes exponentially with the number of parameters (curse of dimensionality); MCMC sampling is used instead for high-dimensional posteriors.

Common mistakes
  • Forgetting to normalize the grid posterior — divide the evaluated values by their sum (or integral) before computing summaries.
  • Using too coarse a grid — a sparse grid misses sharp posterior features; refine until summaries stabilize.
  • Using too few posterior samples — small sample counts make means and especially tail quantiles noisy; draw tens of thousands.
✎ Try it yourself

Problem. Using a flat Beta(1,1) prior and data of 3 successes in 4 trials, outline a grid approximation in Python and state which Beta posterior it should reproduce.

Solution. import numpy as np; p = np.linspace(0, 1, 1001); post = p**3 * (1 - p)**1; post = post / post.sum(). The posterior mean is np.sum(p * post) and a credible interval comes from the cumulative sum of post. Analytically the posterior is Beta(1 + 3, 1 + 1) = Beta(4, 2) with mean 4/6 = 0.667; the grid approximation's mean should match ~0.667, confirming the method.

Bayesian A/B testing in practice

Model each variant's rate with a Beta posterior and answer the business question directly: P(B beats A).

Bayesian A/B testing reframes the comparison in terms most stakeholders actually want. Model each variant's conversion rate with a Beta posterior: with a Beta(a, b) prior and s successes in n trials, variant A's posterior is Beta(a + s_A, b + n_A - s_A), and likewise for B. The key quantity is P(p_B > p_A | data), computed by drawing many samples from each posterior and counting the fraction where B's draw exceeds A's. You can also report the posterior of the difference p_B - p_A or the expected uplift, and the expected loss of each choice for a decision-theoretic stop rule. Unlike fixed-horizon frequentist tests, Bayesian methods make optional stopping less hazardous (no p-value to inflate), though priors and decision thresholds should still be set in advance. The output — 'a 97% probability B is better, with an expected uplift of 1.2 points' — is directly actionable.

Worked Example 1

Problem. Variant A: 90 conversions in 1000. Variant B: 110 in 1000. With flat Beta(1,1) priors, write the two posteriors.

  1. Posterior = Beta(1 + s, 1 + n - s).
  2. A: s = 90, n - s = 910 -> Beta(91, 911).
  3. B: s = 110, n - s = 890 -> Beta(111, 891).
  4. Posterior means: A = 91/1002 ~ 0.0908; B = 111/1002 ~ 0.1108.

Answer. p_A ~ Beta(91, 911) (mean ~ 0.091) and p_B ~ Beta(111, 891) (mean ~ 0.111).

Worked Example 2

Problem. Estimate P(p_B > p_A) by Monte Carlo for the posteriors above.

  1. import numpy as np; from scipy.stats import beta
  2. a = beta(91, 911).rvs(200000); b = beta(111, 891).rvs(200000).
  3. prob = (b > a).mean().
  4. This returns about 0.93, i.e., roughly a 93% posterior probability that B's rate exceeds A's.

Answer. P(p_B > p_A | data) ~ 0.93 from 200k posterior draws — strong but not overwhelming evidence that B is better.

Worked Example 3

Problem. Beyond P(B > A), report the posterior of the uplift p_B - p_A and the expected loss of wrongly shipping B.

  1. diff = b - a (sample-wise from the draws above); posterior mean uplift = diff.mean() ~ 0.020 (about 2 percentage points).
  2. 95% credible interval for the uplift: np.percentile(diff, [2.5, 97.5]) ~ [-0.006, 0.046].
  3. Expected loss of choosing B = E[max(p_A - p_B, 0)] = np.maximum(a - b, 0).mean(), a small number here.
  4. Decision rule: ship B if P(B > A) exceeds a preset threshold (e.g., 0.95) or expected loss is below a tolerance.

Answer. Posterior mean uplift ~ 0.020 with 95% credible interval ~ [-0.006, 0.046] (it includes 0), and a small expected loss; at a 0.95 threshold you would keep testing rather than ship B yet.

Common mistakes
  • Computing P(p_B > p_A) from point estimates alone — you must integrate over both full posteriors (e.g., by sampling), not compare two means.
  • Assuming Bayesian methods license unlimited peeking with no thought — set priors and decision thresholds in advance even though there is no p-value to inflate.
  • Reporting only P(B > A) — also report the uplift's credible interval and expected loss, since a high probability of a tiny gain may not be worth shipping.
✎ Try it yourself

Problem. Variant A converts 40 of 500 and variant B converts 55 of 500. Using flat Beta(1,1) priors, give both posteriors and outline how to estimate P(p_B > p_A) and the expected uplift in Python.

Solution. Posteriors: A = Beta(1 + 40, 1 + 460) = Beta(41, 461) (mean ~ 0.0817); B = Beta(1 + 55, 1 + 445) = Beta(56, 446) (mean ~ 0.1116). In Python: import numpy as np; from scipy.stats import beta; a = beta(41, 461).rvs(200000); b = beta(56, 446).rvs(200000); print((b > a).mean()) gives P(p_B > p_A) ~ 0.97, and print((b - a).mean()) gives the expected uplift ~ 0.030 (about 3 percentage points). With ~97% probability B beats A and a ~3-point uplift, shipping B is well supported.

Key terms
  • Prior distribution — the distribution encoding beliefs about a parameter before seeing the data.
  • Likelihood — the probability of the observed data given a parameter value, the same object used in MLE.
  • Posterior distribution — the updated belief about the parameter after combining prior and likelihood via Bayes' rule.
  • Bayes' rule — posterior is proportional to likelihood times prior, the engine of Bayesian updating.
  • Conjugate prior — a prior whose form is preserved in the posterior for a given likelihood (e.g., Beta with Binomial).
  • Beta-Binomial model — using a Beta prior for a proportion with binomial data, yielding a Beta posterior.
  • Credible interval — an interval that contains the parameter with a stated posterior probability.
  • Grid approximation — evaluating the posterior on a fine grid of parameter values to approximate it numerically.
Assignment · Bayesian estimate of a conversion rate

Suppose you observe a number of successes out of trials for a conversion rate. Choose a Beta prior (try a uniform Beta(1,1) and a more informed prior) and derive the Beta posterior analytically. In Python or R, plot the prior, likelihood, and posterior, report the posterior mean and a 95% credible interval, and then confirm your result with a grid approximation. Finally, compare your Bayesian conclusion to a frequentist confidence interval for the same data.

Deliverable · A notebook plotting prior/likelihood/posterior for both priors, the posterior summaries and credible interval, the grid-approximation check, and a short comparison to the frequentist interval.

Quiz · 5 questions
  1. 1. Bayes' rule for inference states that the posterior is proportional to:

  2. 2. A conjugate prior is one that:

  3. 3. With a Beta(a, b) prior and observing s successes in n trials (binomial), the posterior is:

  4. 4. How does a 95% credible interval differ from a 95% confidence interval?

  5. 5. As you collect more data, the posterior distribution typically:

You'll be able to

I can explain the Bayesian view of probability and combine a prior with a likelihood to obtain a posterior.

I can use a conjugate Beta-Binomial model and summarize the posterior with a credible interval.

I can approximate a posterior by grid or sampling and apply Bayesian reasoning to an A/B test.

Where this leads

Course milestones

Data profiling checkpoint: summarize a real dataset and demonstrate the sampling distribution and standard error of the mean by simulation (Unit 1).
Estimation lab: derive and compute an MLE three ways — by hand, by method of moments, and by numerical optimization — and reconcile the results (Units 2-3).
Inference toolkit: choose, run, and correctly report t-tests, chi-squared tests, and ANOVA, including a multiple-comparison correction (Unit 4).
A/B test design project: compute power and sample size for a target effect, simulate the experiment, and estimate empirical power (Unit 5).
Capstone — modeling and Bayesian comparison: fit a regression or logistic model and analyze a conversion rate both frequentist and Bayesian, contrasting the conclusions (Units 6-7).

Free, forever. Math you can actually use.

Part of the open-source Crunch Academy tech-education path. Pair it with the data-science and ML tracks when you're ready.

All Math for Data Science courses Crunch Academy home