Observe how the sample mean stabilizes as the sample size increases.
Learning objective
The Law of Large Numbers explains why averages calculated from increasingly large random samples become reliable estimates of the population mean.
If X1, X2, … are i.i.d. with finite mean μ, then X̄n = (1/n) Σ Xi → μ in probability as n → ∞.
How to explore
Select a distribution.
Change the sample size.
Generate several trajectories.
Compare the final error.
What does choosing n actually mean?
If you choose n = 20, the lab generates one sequence of 20 observations: X1, X2, …, X20. It then recalculates the cumulative mean every time a new observation is added.
Step 1X̄1 = X1
Step 2X̄2 = (X1+X2)/2
Step 3X̄3 = (X1+X2+X3)/3
Continue to nX̄n = ΣXi/n
The important idea: the graph shows X̄1, X̄2, …, X̄20. These are not 20 unrelated samples. They are nested prefixes of the same sequence: {X1} ⊂ {X1,X2} ⊂ … ⊂ {X1,…,X20}. Therefore, the slider selects the maximum sample size displayed.
Running Sample Mean
Running sample mean X̄ₙTheoretical mean μ
Theoretical mean μ—
Final sample mean—
Absolute error—
Sample size—
What to notice: for small n, the sample mean can fluctuate strongly. As n grows, the trajectory usually settles near μ. Convergence does not mean that every trajectory is monotonic.
Discussion question: Which distribution appears to require more observations before the running mean becomes stable?
Experiment settings
Distribution
—
Mean E[X]—
Variance Var(X)—
—
Animated Example — Rolling a Fair Die
Each roll is an independent observation Xi. The running mean uses every result obtained so far and should progressively approach the theoretical mean 3.5.
⚀
Fair die distribution P(X = x) = 1/6, x ∈ {1,2,3,4,5,6} E[X] = 3.5 Var(X) = 35/12 ≈ 2.9167
Last roll—
Number of rolls0
Running mean—
|X̄ₙ − 3.5|—
Running mean after each roll
Running meanTheoretical mean 3.5
Early averages can move sharply because each roll has a large influence. Later, one additional roll has much less effect on the cumulative mean.
// The original interactive controller is executed by the Observable JS runtime.(function(){const $=id=>document.getElementById(id);functioninfo(type){if(type==='bernoulli')return{name:'Bernoulli distribution',equation:'P(X = x) = pˣ(1 − p)¹⁻ˣ, x ∈ {0,1}, p = 0.30',support:'Support: {0, 1}',mu:.3,variance:.3*.7,meanText:'p = 0.3000',varianceText:'p(1 − p) = 0.2100',sample:()=>Math.random()<.3?1:0};if(type==='uniform')return{name:'Continuous uniform distribution',equation:'f(x) = 1/(b − a) = 1, 0 ≤ x ≤ 1',support:'Support: [0, 1]; f(x) = 0 outside this interval',mu:.5,variance:1/12,meanText:'(a + b)/2 = 0.5000',varianceText:'(b − a)²/12 = 0.0833',sample:()=>Math.random()};return{name:'Exponential distribution',equation:'f(x) = λe<sup>−λx</sup> = e<sup>−x</sup>, x ≥ 0, λ = 1',support:'Support: [0, ∞); f(x) = 0 for x < 0',mu:1,variance:1,meanText:'1/λ = 1.0000',varianceText:'1/λ² = 1.0000',sample:()=>-Math.log(1-Math.random())};}functionupdateDistributionCard(){const d=info($('dist').value);$('distName').textContent=d.name;$('distEquation').innerHTML=d.equation;$('distMean').textContent=d.meanText;$('distVariance').textContent=d.varianceText;$('distSupport').textContent=d.support}functionsetup(c){const x=c.getContext('2d');x.clearRect(0,0,c.width,c.height);x.fillStyle='#ffffff';x.fillRect(0,0,c.width,c.height);return x}functionlabel(ctx,t,x,y,align='left'){ctx.fillStyle='#5f6b7f';ctx.font='13px system-ui';ctx.textAlign=align;ctx.fillText(t,x,y)}let values=[],timer=null;functiongenerate(){if(timer){clearInterval(timer);timer=null}updateDistributionCard();const n=+$('n').value,d=info($('dist').value);values=[];let total=0;for(let i=1;i<=n;i++){total+=d.sample();values.push(total/i)}draw(n)}functiondraw(showN=values.length){const c=$('chart'),ctx=setup(c),d=info($('dist').value),n=values.length,L=62,R=748,T=35,B=390,shown=values.slice(0,Math.max(1,showN));let lo=Math.min(d.mu,...shown),hi=Math.max(d.mu,...shown),pad=(hi-lo)*.18||.2;lo-=pad;hi+=pad;const X=i=>L+(i-1)/(Math.max(2,n)-1)*(R-L),Y=v=>B-(v-lo)/(hi-lo)*(B-T);ctx.strokeStyle='#dfe5ef';ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(L,T);ctx.lineTo(L,B);ctx.lineTo(R,B);ctx.stroke();ctx.strokeStyle='#177e68';ctx.lineWidth=2;ctx.setLineDash([7,6]);ctx.beginPath();ctx.moveTo(L,Y(d.mu));ctx.lineTo(R,Y(d.mu));ctx.stroke();ctx.setLineDash([]);ctx.strokeStyle='#315efb';ctx.lineWidth=2.6;ctx.beginPath();shown.forEach((v,i)=>i?ctx.lineTo(X(i+1),Y(v)):ctx.moveTo(X(i+1),Y(v)));ctx.stroke();label(ctx,'Observation number n',R,B+28,'right');label(ctx,'Running mean',L,T-12);label(ctx,'μ = '+d.mu.toFixed(3),R-5,Y(d.mu)-9,'right');$('nOut').value=$('n').value;$('nStat').textContent=$('n').value;$('mu').textContent=d.mu.toFixed(4);const last=values[values.length-1];$('mean').textContent=last.toFixed(4);$('error').textContent=Math.abs(last-d.mu).toFixed(4)}$('newRun').onclick=generate;$('dist').onchange=generate;$('n').oninput=generate;$('animate').onclick=()=>{generate();let k=5;timer=setInterval(()=>{draw(k);k+=Math.max(3,Math.floor(values.length/80));if(k>=values.length){draw(values.length);clearInterval(timer);timer=null}},35)};const dieFaces=['⚀','⚁','⚂','⚃','⚄','⚅'];let diceValues=[],diceTimer=null,diceSpin=null;functionrandomDie(){returnMath.floor(Math.random()*6)+1}functionshowDie(value){$('die').textContent=dieFaces[value-1];$('die').setAttribute('aria-label','Die showing '+value)}functionrecordRoll(value){diceValues.push(value);showDie(value);const avg=diceValues.reduce((s,x)=>s+x,0)/diceValues.length;$('lastRoll').textContent=value;$('rollCount').textContent=diceValues.length;$('diceMean').textContent=avg.toFixed(4);$('diceError').textContent=Math.abs(avg-3.5).toFixed(4);drawDiceChart()}functiondrawDiceChart(){const c=$('diceChart'),ctx=setup(c),L=60,R=748,T=30,B=370,n=Math.max(20,diceValues.length),X=i=>L+(i-1)/(n-1)*(R-L),Y=v=>B-(v-1)/5*(B-T);ctx.strokeStyle='#dfe5ef';ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(L,T);ctx.lineTo(L,B);ctx.lineTo(R,B);ctx.stroke();[1,2,3,4,5,6].forEach(v=>{label(ctx,String(v),L-10,Y(v)+4,'right');ctx.strokeStyle='#f3f6fb';ctx.beginPath();ctx.moveTo(L,Y(v));ctx.lineTo(R,Y(v));ctx.stroke()});ctx.strokeStyle='#177e68';ctx.lineWidth=2;ctx.setLineDash([7,6]);ctx.beginPath();ctx.moveTo(L,Y(3.5));ctx.lineTo(R,Y(3.5));ctx.stroke();ctx.setLineDash([]);label(ctx,'μ = 3.5',R-5,Y(3.5)-8,'right');if(diceValues.length){let total=0;ctx.strokeStyle='#315efb';ctx.lineWidth=2.8;ctx.beginPath();diceValues.forEach((v,i)=>{total+=v;const avg=total/(i+1);i?ctx.lineTo(X(i+1),Y(avg)):ctx.moveTo(X(i+1),Y(avg))});ctx.stroke()}label(ctx,'Number of rolls',R,B+28,'right');label(ctx,'Running mean',L,T-12)}functionsetDiceButtons(running){$('rollOnce').disabled=running;$('roll100').disabled=running}functionanimateOneRoll(){if(diceTimer||diceSpin)return;setDiceButtons(true);$('die').classList.add('rolling');let frames=0;diceSpin=setInterval(()=>{showDie(randomDie());frames++;if(frames>=10){clearInterval(diceSpin);diceSpin=null;$('die').classList.remove('rolling');recordRoll(randomDie());setDiceButtons(false)}},55)}functionanimateHundred(){if(diceTimer||diceSpin)return;resetDice();setDiceButtons(true);$('die').classList.add('rolling');let count=0;diceTimer=setInterval(()=>{recordRoll(randomDie());count++;if(count>=100){clearInterval(diceTimer);diceTimer=null;$('die').classList.remove('rolling');setDiceButtons(false)}},65)}functionresetDice(){if(diceTimer){clearInterval(diceTimer);diceTimer=null}if(diceSpin){clearInterval(diceSpin);diceSpin=null}$('die').classList.remove('rolling');diceValues=[];showDie(1);$('lastRoll').textContent='—';$('rollCount').textContent='0';$('diceMean').textContent='—';$('diceError').textContent='—';setDiceButtons(false);drawDiceChart()}$('rollOnce').onclick=animateOneRoll;$('roll100').onclick=animateHundred;$('resetDice').onclick=resetDice;generate();resetDice();})();
Central Limit Theorem
The Central Limit Theorem explains why the sample mean
often behaves like a normal random variable, even when the original data do not.
1. The main idea
Original variable
X
May be discrete, skewed, or non-normal.
→
Take a sample
X₁,…,Xₙ
Use n independent observations.
→
Compute the mean
X̄ = (1/n) ΣXᵢ
Repeat this experiment many times.
CLT statement:
if the observations are independent and identically distributed, with finite mean μ and variance σ²,
then for sufficiently large n,
X̄ ≈ N( μ , σ² / n )
and
(X̄ − μ)/(σ/√n) ≈ N(0,1).
2. What becomes normal?
A common mistake is to think that the original observations become normal.
They do not.
One observation
X
Still follows the original distribution.
One sample
X₁,…,Xₙ
A collection of original observations.
Many sample means
X̄₁,X̄₂,…
Their distribution becomes approximately normal.
3. LLN vs CLT
Law of Large Numbers:
X̄ₙ → μ
tells us where the sample mean goes.
Central Limit Theorem:
X̄ₙ ≈ N(μ,σ²/n)
tells us how the sample mean fluctuates around μ.
4. Why do we repeat the sampling experiment M times?
The simulation uses two numbers, n and M.
They have completely different roles.
n
Sample size
n is the number of observations inside
one experiment / one sample.
X₁, X₂, …, Xₙ → one X̄
Increasing n changes the sampling distribution itself:
SE(X̄) = σ / √n
Therefore, larger n makes the distribution of
X̄ narrower and, under the CLT, generally more bell-shaped.
M
Number of repeated experiments
M is the number of times we repeat the
whole sampling procedure.
X̄₁, X̄₂, …, X̄_M
Each experiment gives one sample mean. Increasing M
gives us more sample means, so the histogram becomes smoother and represents
the theoretical sampling distribution more accurately.
Example: n = 30 and M = 1000
Perform 1000 experiments. In each experiment, roll the die
30 times and calculate one mean. At the end:
X̄₁, X̄₂, …, X̄₁₀₀₀
These 1000 means are the values used to construct the histogram.
Experiment 1: n observations
15362
→
Compute one mean
X̄₁ = 3.40
→
Repeat M times
X̄₁ = 3.40X̄₂ = 3.67X̄₃ = 3.27…
Important:
increasing M does not make the CLT stronger,
and it does not reduce SE = σ/√n.
M is mainly used here so that we can
see the sampling distribution.
If n increases
The distribution of X̄ changes.
n ↑ → σ/√n ↓
The histogram becomes narrower around μ.
If M increases
The theoretical distribution of X̄ does not change.
M ↑ → more X̄ values
The histogram simply becomes smoother and more stable.
Quick check
Choose A or B.
5. Interactive experiment
Choose a distribution, select the sample size n, and choose the number
of experiments M. In each experiment, the computer generates
n observations and computes one sample mean. After
M repetitions, we obtain
X̄₁, X̄₂, …, X̄_M. The histogram below is therefore a histogram of
sample means, not of the original observations.
Theoretical μ3.500
Theoretical σ1.708
SE = σ/√n0.764
Observed mean of X̄—
Click Run simulation, or use Animate to increase n automatically from 1 to 100, one value at a time.
Watch the distribution of sample means become smoother and narrower around μ.
6. Numerical example: fair die
For one fair die roll,
μ = E[X] = (1+2+3+4+5+6)/6 = 3.5
σ² = 35/12 ≈ 2.917
σ ≈ 1.708
If we roll the die 30 times and compute the average, then
X̄ ≈ N(3.5, (1.708/√30)²)
SE = 1.708/√30 ≈ 0.312.
So the sample means are centered near 3.5 and most of them stay relatively close to 3.5.
7. Why the curve gets narrower
The standard deviation of the sampling distribution is
SE(X̄) = σ / √n.
Therefore increasing n reduces the variability of the sample mean.
n = 4 → SE = σ/2
n = 25 → SE = σ/5
n = 100 → SE = σ/10
This is why larger samples give more stable estimates of the population mean.
8. Final intuition
Imagine repeating the same sampling procedure again and again.
Every repetition gives one value of X̄.
The CLT says that the cloud of these sample means approaches a bell-shaped distribution centered at
μ, with spread σ/√n.
This is why the CLT is fundamental for confidence intervals, hypothesis tests, estimation, and statistical inference.
// The original interactive controller is executed by the Observable JS runtime.(function(){const dist =document.getElementById('dist');const nSlider =document.getElementById('n');const repsSlider =document.getElementById('reps');const nVal =document.getElementById('nVal');const repsVal =document.getElementById('repsVal');const muEl =document.getElementById('mu');const sigmaEl =document.getElementById('sigma');const seEl =document.getElementById('se');const obsMeanEl =document.getElementById('obsMean');const runBtn =document.getElementById('run');const animateBtn =document.getElementById('animate');const canvas =document.getElementById('hist');const ctx = canvas.getContext('2d');const interp =document.getElementById('interpretation');let animationToken =0;let processToken =0;const procSample =document.getElementById('procSample');const procMean =document.getElementById('procMean');const procMeans =document.getElementById('procMeans');const procTitle =document.getElementById('procTitle');const oneExpBtn =document.getElementById('showOneExperiment');const manyExpBtn =document.getElementById('showManyExperiments');const quizButtons =Array.from(document.querySelectorAll('[data-answer]'));const quizFeedback =document.getElementById('quizFeedback');functionparams(){if(dist.value==='die'){return {mu:3.5,sigma:Math.sqrt(35/12),min:1,max:6,name:'Fair die'}; }const p =0.30;return {mu:p,sigma:Math.sqrt(p*(1-p)),min:0,max:1,name:'Bernoulli(0.30)'}; }functionsampleOne(){if(dist.value==='die') return1+Math.floor(Math.random()*6);returnMath.random() <0.30?1:0; }functionrenderOneExperiment(index=1, maxShow=12){const n =+nSlider.value;const values = [];let total =0;for(let i=0;i<n;i++){const x =sampleOne(); total += x;if(i <Math.min(n,maxShow)) values.push(x); }const mean = total/n; procTitle.textContent=`Experiment ${index}: n = ${n} observations`; procSample.innerHTML= values.map(v =>`<span class="chip">${v}</span>`).join('') + (n > maxShow ?'<span class="chip">…</span>':''); procMean.textContent=`X̄${index} = ${mean.toFixed(3)}`;return mean; }asyncfunctionanimateRepeatedExperiments(){ processToken++;const token = processToken; oneExpBtn.disabled=true; manyExpBtn.disabled=true; procMeans.innerHTML='';const demoM =20;const means = [];for(let m=1; m<=demoM; m++){if(token !== processToken) break;const mean =renderOneExperiment(m); means.push(mean);const start =Math.max(0, means.length-8); procMeans.innerHTML= means.slice(start).map((v,j) => {const idx = start + j +1;return`<span class="mini-mean">X̄${idx} = ${v.toFixed(2)}</span>`; }).join('');awaitnewPromise(resolve =>setTimeout(resolve,140)); }if(token === processToken){ procMeans.innerHTML+='<span class="mini-mean">… up to M</span>'; } oneExpBtn.disabled=false; manyExpBtn.disabled=false; }functionupdateLabels(){ nVal.textContent= nSlider.value; repsVal.textContent= repsSlider.value;const p =params(); muEl.textContent= p.mu.toFixed(3); sigmaEl.textContent= p.sigma.toFixed(3); seEl.textContent= (p.sigma/Math.sqrt(+nSlider.value)).toFixed(3); }functionmakeMeans(reps){const n =+nSlider.value;const means =newArray(reps);for(let r=0;r<reps;r++){let s=0;for(let i=0;i<n;i++) s +=sampleOne(); means[r]=s/n; }return means; }functionnormalPdf(x,mu,sd){const z=(x-mu)/sd;returnMath.exp(-0.5*z*z)/(sd*Math.sqrt(2*Math.PI)); }functiondraw(means){const W=canvas.width,H=canvas.height; ctx.clearRect(0,0,W,H); ctx.fillStyle='#ffffff'; ctx.fillRect(0,0,W,H);const p=params();const n=+nSlider.value;const se=p.sigma/Math.sqrt(n);let xmin=Math.min(...means), xmax=Math.max(...means);const pad=Math.max((xmax-xmin)*0.12, se*0.8,0.02); xmin=Math.min(xmin-pad, p.mu-4*se); xmax=Math.max(xmax+pad, p.mu+4*se);if(xmax<=xmin){xmin-=1;xmax+=1}const bins=Math.max(12,Math.min(42,Math.round(Math.sqrt(means.length)/1.7)));const counts=newArray(bins).fill(0);for(const x of means){let b=Math.floor((x-xmin)/(xmax-xmin)*bins);if(b<0)b=0;if(b>=bins)b=bins-1; counts[b]++; }const maxC=Math.max(...counts,1);const L=70,R=25,T=25,B=55;const pw=W-L-R, ph=H-T-B; ctx.strokeStyle='#dfe5ef';ctx.lineWidth=1; ctx.fillStyle='#5f6b7f';ctx.font='14px system-ui';for(let k=0;k<=5;k++){const y=T+ph*k/5; ctx.beginPath();ctx.moveTo(L,y);ctx.lineTo(W-R,y);ctx.stroke();const val=Math.round(maxC*(1-k/5)); ctx.fillText(val,12,y+5); } ctx.beginPath();ctx.moveTo(L,T);ctx.lineTo(L,H-B);ctx.lineTo(W-R,H-B);ctx.strokeStyle='#8f9bb0';ctx.stroke();const bw=pw/bins;for(let i=0;i<bins;i++){const h=counts[i]/maxC*ph; ctx.fillStyle='rgba(49,94,251,.72)'; ctx.fillRect(L+i*bw+1,H-B-h,Math.max(1,bw-2),h); }// Normal curve scaled to histogram count scaleconst binWidth=(xmax-xmin)/bins;let maxPdf=0;for(let i=0;i<=300;i++){const x=xmin+(xmax-xmin)*i/300; maxPdf=Math.max(maxPdf,normalPdf(x,p.mu,se)*means.length*binWidth); } ctx.beginPath();for(let i=0;i<=300;i++){const x=xmin+(xmax-xmin)*i/300;const expected=normalPdf(x,p.mu,se)*means.length*binWidth;const px=L+(x-xmin)/(xmax-xmin)*pw;const py=H-B-(expected/maxC)*ph;if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py); } ctx.strokeStyle='#177e68';ctx.lineWidth=3;ctx.stroke();// mu lineconst mux=L+(p.mu-xmin)/(xmax-xmin)*pw; ctx.setLineDash([7,6]);ctx.strokeStyle='#b7791f';ctx.lineWidth=2; ctx.beginPath();ctx.moveTo(mux,T);ctx.lineTo(mux,H-B);ctx.stroke();ctx.setLineDash([]); ctx.fillStyle='#182033';ctx.font='15px system-ui';for(let k=0;k<=5;k++){const x=xmin+(xmax-xmin)*k/5;const px=L+pw*k/5; ctx.fillText(x.toFixed(2),px-18,H-20); } ctx.fillStyle='#177e68';ctx.fillText('Normal approximation',W-205,23); ctx.fillStyle='#b7791f';ctx.fillText('μ',mux+6,T+16); ctx.fillStyle='#5f6b7f';ctx.fillText('sample mean X̄',W/2-48,H-5);const observed=means.reduce((a,b)=>a+b,0)/means.length; obsMeanEl.textContent=observed.toFixed(3);let msg='';if(n===1){ msg='With n = 1, the histogram mostly reflects the original distribution. The CLT effect is not yet visible.'; } elseif(n<10){ msg='The sampling distribution is beginning to concentrate around μ. Increase n further to make the normal shape clearer.'; } elseif(n<30){ msg='The bell shape is becoming more visible, and the sample means are less spread out because SE = σ/√n is smaller.'; } else { msg='Now the CLT is clearly visible: the sample means are approximately normal, centered near μ, with a much smaller spread.'; } interp.innerHTML='<strong>Interpretation:</strong> '+msg; }functionrun(){ animationToken++;updateLabels();draw(makeMeans(+repsSlider.value)); }asyncfunctionanimate(){ animationToken++;const token=animationToken;const originalN=+nSlider.value; animateBtn.disabled=true; runBtn.disabled=true;const seq =Array.from({length:100}, (_,i) => i +1);for(const n of seq){if(token!==animationToken) break; nSlider.value=n;updateLabels();draw(makeMeans(Math.min(+repsSlider.value,1200)));awaitnewPromise(r=>setTimeout(r,110)); }if(token===animationToken){ interp.innerHTML='<strong>Animation complete:</strong> n increased step by step from 1 to 100. Notice how the histogram becomes progressively more bell-shaped and narrower around μ.'; } animateBtn.disabled=false; runBtn.disabled=false; } nSlider.addEventListener('input',updateLabels); repsSlider.addEventListener('input',updateLabels); dist.addEventListener('change',()=>{updateLabels();run();}); runBtn.addEventListener('click',run); animateBtn.addEventListener('click',animate); oneExpBtn.addEventListener('click', () => { processToken++;const mean =renderOneExperiment(1); procMeans.innerHTML=`<span class="mini-mean">X̄₁ = ${mean.toFixed(2)}</span>`+'<span class="mini-mean">Repeat → X̄₂, X̄₃, …, X̄_M</span>'; }); manyExpBtn.addEventListener('click', animateRepeatedExperiments); quizButtons.forEach(btn => { btn.addEventListener('click', () => { quizButtons.forEach(b => b.classList.remove('correct','wrong'));if(btn.dataset.answer==='correct'){ btn.classList.add('correct'); quizFeedback.innerHTML='<strong>Correct.</strong> n appears in SE = σ/√n. Increasing n reduces the standard error. Increasing M only gives us more sample means to visualize the same distribution.'; } else { btn.classList.add('wrong'); quizFeedback.innerHTML='<strong>Not quite.</strong> M does not appear in SE = σ/√n. Increasing M makes the simulated histogram smoother; increasing n changes the sampling distribution itself.'; } }); });updateLabels();run();})();
Hypothesis Testing
Hypothesis testing is a formal way to decide whether the evidence in a sample is
strong enough to question a claim about a population.
1. The idea
We begin with a claim called the null hypothesis,
denoted by H₀. We then ask:
If H₀ were true, would our observed sample result be
reasonably common, or surprisingly extreme?
Null hypothesis
H₀: μ = μ₀
The reference claim. We assume it is true while calculating the probability
of observing results as extreme as ours.
Alternative hypothesis
H₁: μ ≠ μ₀
The competing claim. Depending on the question, it may also be
μ > μ₀ or μ < μ₀.
2. The five-step logic
1
State H₀ and H₁
Define the population claim being tested.
2
Choose α
Typical value: α = 0.05.
3
Compute a statistic
Measure how far the sample is from what H₀ predicts.
4
Find the p-value
Quantify how extreme the result is under H₀.
5
Decision
Compare the p-value with α.
3. Which test statistic?
Case 1 — Population variance known
Use the population standard deviation σ and the standard normal distribution.
z = (X̄ − μ₀) / (σ/√n)
Case 2 — Population variance unknown
Replace σ by the sample standard deviation s and use Student's t distribution.
t = (X̄ − μ₀) / (s/√n), df = n − 1
In both cases, the statistic measures how many estimated standard errors the observed
sample mean is away from the value assumed by H₀.
4. The p-value
The p-value is the probability, assuming H₀ is true,
of obtaining a test statistic at least as extreme as the one observed.
Important: the p-value is not the probability that
H₀ is true.
p ≤ α → reject H₀p > α → fail to reject H₀
5. Choose the case and study the corresponding example
First choose whether the population variance is known or unknown.
The worked example below changes automatically according to your choice.
The same choice is then used in the interactive demonstration in Part 6.
Case 1 — Population variance known → z-test
A manufacturer claims that the mean lifetime of a battery is
μ = 100 hours. The population standard deviation is known:
σ = 12 hours.
A sample of n = 36 batteries gives
X̄ = 104 hours. We use a two-tailed test with
α = 0.05.
Case 2 — Population variance unknown → Student's t-test
The population standard deviation is unknown. A sample of
n = 16 observations gives
X̄ = 54 and sample standard deviation
s = 8. We use a two-tailed test with
α = 0.05.
H₀: μ = 50
H₁: μ ≠ 50
SE = s/√n = 8/√16 = 2t = (54 − 50)/2 = 2df = n − 1 = 15
For df = 15 and a two-tailed test with
α = 0.05, the critical values are approximately
±2.131.
Since |t| = 2 < 2.131,
Fail to reject H₀.
6. Interactive hypothesis test for a population mean
This demonstration uses the variance case selected in Part 5.
Change the sample information below and observe the test statistic, critical region,
p-value, and decision update automatically.
Selected in Part 5: Case 1 — z-test, population standard deviation σ is known.
Standard Error2.000
z statistic2.000
p-value0.0455
Critical value(s)±1.960
Degrees of freedom35
DecisionReject H₀
// The original interactive controller is executed by the Observable JS runtime.(function(){const varianceModeEl =document.getElementById('varianceMode');const mu0El =document.getElementById('mu0');const xbarEl =document.getElementById('xbar');const sigmaEl =document.getElementById('sigma');const nEl =document.getElementById('n');const alphaEl =document.getElementById('alpha');const testTypeEl =document.getElementById('testType');const calcBtn =document.getElementById('calculate');const seOut =document.getElementById('se');const zOut =document.getElementById('zval');const pOut =document.getElementById('pval');const critOut =document.getElementById('crit');const decisionMini =document.getElementById('decisionMini');const statLabel =document.getElementById('statLabel');const spreadLabel =document.getElementById('spreadLabel');const methodBanner =document.getElementById('methodBanner');const dfKpi =document.getElementById('dfKpi');const dfOut =document.getElementById('dfval');const zExampleBlock =document.getElementById('zExampleBlock');const tExampleBlock =document.getElementById('tExampleBlock');const decisionBox =document.getElementById('decisionBox');const canvas =document.getElementById('plot');const ctx = canvas.getContext('2d');functionerf(x){const sign = x <0?-1:1; x =Math.abs(x);const a1=0.254829592,a2=-0.284496736,a3=1.421413741,a4=-1.453152027,a5=1.061405429,p=0.3275911;const t=1/(1+p*x);const y=1-(((((a5*t+a4)*t)+a3)*t+a2)*t+a1)*t*Math.exp(-x*x);return sign*y; }functioncdf(z){return0.5*(1+erf(z/Math.sqrt(2))); }functioninvNorm(p){if(p<=0|| p>=1) returnNaN;const a=[-39.69683028665376,220.9460984245205,-275.9285104469687,138.3577518672690,-30.66479806614716,2.506628277459239];const b=[-54.47609879822406,161.5858368580409,-155.6989798598866,66.80131188771972,-13.28068155288572];const c=[-0.007784894002430293,-0.3223964580411365,-2.400758277161838,-2.549732539343734,4.374664141464968,2.938163982698783];const d=[0.007784695709041462,0.3224671290700398,2.445134137142996,3.754408661907416];const plow=0.02425, phigh=1-plow;let q,r;if(p<plow){ q=Math.sqrt(-2*Math.log(p));return (((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) / ((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1); }if(p>phigh){ q=Math.sqrt(-2*Math.log(1-p));return-(((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) / ((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1); } q=p-0.5;r=q*q;return (((((a[0]*r+a[1])*r+a[2])*r+a[3])*r+a[4])*r+a[5])*q / (((((b[0]*r+b[1])*r+b[2])*r+b[3])*r+b[4])*r+1); }functionpdf(z){returnMath.exp(-0.5*z*z)/Math.sqrt(2*Math.PI); }functionlogGamma(z){const c=[676.5203681218851,-1259.1392167224028,771.32342877765313,-176.61502916214059,12.507343278686905,-0.13857109526572012,9.9843695780195716e-6,1.5056327351493116e-7];if(z <0.5) returnMath.log(Math.PI)-Math.log(Math.sin(Math.PI*z))-logGamma(1-z); z -=1;let x=0.99999999999980993;for(let i=0;i<c.length;i++) x += c[i]/(z+i+1);const t=z+c.length-0.5;return0.5*Math.log(2*Math.PI)+(z+0.5)*Math.log(t)-t+Math.log(x); }functionbetacf(a,b,x){const MAXIT=120, EPS=3e-12, FPMIN=1e-30;const qab=a+b, qap=a+1, qam=a-1;let c=1, d=1-qab*x/qap;if(Math.abs(d)<FPMIN) d=FPMIN; d=1/d;let h=d;for(let m=1;m<=MAXIT;m++){const m2=2*m;let aa=m*(b-m)*x/((qam+m2)*(a+m2)); d=1+aa*d;if(Math.abs(d)<FPMIN)d=FPMIN; c=1+aa/c;if(Math.abs(c)<FPMIN)c=FPMIN; d=1/d; h*=d*c; aa=-(a+m)*(qab+m)*x/((a+m2)*(qap+m2)); d=1+aa*d;if(Math.abs(d)<FPMIN)d=FPMIN; c=1+aa/c;if(Math.abs(c)<FPMIN)c=FPMIN; d=1/d;const del=d*c; h*=del;if(Math.abs(del-1)<EPS) break; }return h; }functionregIncompleteBeta(x,a,b){if(x<=0) return0;if(x>=1) return1;const bt=Math.exp(logGamma(a+b)-logGamma(a)-logGamma(b)+a*Math.log(x)+b*Math.log(1-x));if(x < (a+1)/(a+b+2)) return bt*betacf(a,b,x)/a;return1-bt*betacf(b,a,1-x)/b; }functiontCDF(t,df){if(t===0) return0.5;const x=df/(df+t*t);const ib=regIncompleteBeta(x,df/2,0.5);return t>0?1-0.5*ib :0.5*ib; }functiontPdf(t,df){const logC=logGamma((df+1)/2)-logGamma(df/2)-0.5*Math.log(df*Math.PI);returnMath.exp(logC-((df+1)/2)*Math.log(1+t*t/df)); }functiontInv(p,df){if(p<=0|| p>=1) returnNaN;let lo=-20, hi=20;for(let i=0;i<90;i++){const mid=(lo+hi)/2;if(tCDF(mid,df)<p) lo=mid;else hi=mid; }return (lo+hi)/2; }functionreadValues(){let mu0 =parseFloat(mu0El.value);let xbar =parseFloat(xbarEl.value);let sigma =parseFloat(sigmaEl.value);let n =parseInt(nEl.value,10);let alpha =parseFloat(alphaEl.value);const mode = varianceModeEl.value;if(!Number.isFinite(mu0)) mu0=0;if(!Number.isFinite(xbar)) xbar=mu0;if(!Number.isFinite(sigma) || sigma<=0){sigma=1;sigmaEl.value='1'}const minN = mode==='unknown'?2:1;if(!Number.isFinite(n) || n<minN){n=minN;nEl.value=String(minN)} nEl.min=String(minN);return {mu0,xbar,sigma,n,alpha,type:testTypeEl.value,mode}; }functioncompute(){const v=readValues();const se=v.sigma/Math.sqrt(v.n);const stat=(v.xbar-v.mu0)/se;const isT=v.mode==='unknown';const df=Math.max(1,v.n-1);const CDF=isT ? (x=>tCDF(x,df)) : cdf;const INV=isT ? (p=>tInv(p,df)) : invNorm;let p,critText,critical=[];if(v.type==='two'){ p=2*(1-CDF(Math.abs(stat)));const c=INV(1-v.alpha/2); critical=[-c,c]; critText='±'+c.toFixed(3); } elseif(v.type==='right'){ p=1-CDF(stat);const c=INV(1-v.alpha); critical=[c]; critText=c.toFixed(3); } else { p=CDF(stat);const c=INV(v.alpha); critical=[c]; critText=c.toFixed(3); } p=Math.max(0,Math.min(1,p));const reject=p<=v.alpha;if(isT){ zExampleBlock.style.display='none'; tExampleBlock.style.display='block'; }else{ zExampleBlock.style.display='block'; tExampleBlock.style.display='none'; }if(isT){ spreadLabel.innerHTML='Sample SD s'; statLabel.textContent='t statistic'; methodBanner.innerHTML='<strong>Selected in Part 5:</strong> Case 2 — t-test. Population variance is unknown, so σ is replaced by the sample standard deviation s.'; dfKpi.style.display=''; dfOut.textContent=String(df); }else{ spreadLabel.innerHTML='Population SD σ'; statLabel.textContent='z statistic'; methodBanner.innerHTML='<strong>Selected in Part 5:</strong> Case 1 — z-test. Population standard deviation σ is known.'; dfKpi.style.display='none'; } seOut.textContent=se.toFixed(3); zOut.textContent=stat.toFixed(3); pOut.textContent=p<0.0001?'< 0.0001':p.toFixed(4); critOut.textContent=critText; decisionMini.textContent=reject?'Reject H₀':'Fail to reject H₀'; decisionMini.style.color=reject?'#c62828':'#17743b';let h1='';if(v.type==='two') h1=`μ ≠ ${v.mu0}`;if(v.type==='right') h1=`μ > ${v.mu0}`;if(v.type==='left') h1=`μ < ${v.mu0}`; decisionBox.classList.remove('reject','keep');if(reject){ decisionBox.classList.add('reject'); decisionBox.innerHTML=`<strong>Decision: Reject H₀.</strong> Since p = ${p.toFixed(4)} ≤ α = ${v.alpha.toFixed(2)}, `+`the observed result is sufficiently unusual under H₀. The data provide evidence in favor of H₁: ${h1}.`; } else { decisionBox.classList.add('keep'); decisionBox.innerHTML=`<strong>Decision: Fail to reject H₀.</strong> Since p = ${p.toFixed(4)} > α = ${v.alpha.toFixed(2)}, `+`the observed result is not sufficiently unusual under H₀. This does not prove H₀ is true.`; }drawPlot(stat,v.alpha,v.type,critical,isT,df); }functiondrawPlot(z,alpha,type,critical,isT,df){const W=canvas.width,H=canvas.height;const L=65,R=25,T=25,B=60;const pw=W-L-R, ph=H-T-B;const xmin=-4.2,xmax=4.2;const ymax=0.42; ctx.clearRect(0,0,W,H); ctx.fillStyle='#ffffff';ctx.fillRect(0,0,W,H);functionxpix(x){return L+(x-xmin)/(xmax-xmin)*pw}functionypix(y){return H-B-y/ymax*ph} ctx.strokeStyle='#dfe5ef';ctx.lineWidth=1;for(let x=-4;x<=4;x++){const px=xpix(x); ctx.beginPath();ctx.moveTo(px,T);ctx.lineTo(px,H-B);ctx.stroke(); ctx.fillStyle='#5f6b7f';ctx.font='14px system-ui'; ctx.fillText(String(x),px-4,H-28); } ctx.beginPath();ctx.moveTo(L,H-B);ctx.lineTo(W-R,H-B);ctx.strokeStyle='#8f9bb0';ctx.stroke();// Rejection regionsfunctionshade(a,b,color){ ctx.beginPath(); ctx.moveTo(xpix(a),H-B);for(let i=0;i<=220;i++){const x=a+(b-a)*i/220; ctx.lineTo(xpix(x),ypix(isT ?tPdf(x,df) :pdf(x))); } ctx.lineTo(xpix(b),H-B); ctx.closePath(); ctx.fillStyle=color;ctx.fill(); }if(type==='two'){shade(xmin,critical[0],'rgba(255,142,155,.28)');shade(critical[1],xmax,'rgba(255,142,155,.28)'); } elseif(type==='right'){shade(critical[0],xmax,'rgba(255,142,155,.28)'); } else {shade(xmin,critical[0],'rgba(255,142,155,.28)'); }// Curve ctx.beginPath();for(let i=0;i<=500;i++){const x=xmin+(xmax-xmin)*i/500;const px=xpix(x),py=ypix(isT ?tPdf(x,df) :pdf(x));if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py); } ctx.strokeStyle='#177e68';ctx.lineWidth=3;ctx.stroke();// Critical lines ctx.setLineDash([6,6]); ctx.strokeStyle='#c62828';ctx.lineWidth=2; critical.forEach(c=>{const px=xpix(c); ctx.beginPath();ctx.moveTo(px,T+35);ctx.lineTo(px,H-B);ctx.stroke(); });// Observed z ctx.setLineDash([]);const zx=Math.max(xmin,Math.min(xmax,z));const pz=xpix(zx); ctx.strokeStyle='#9a6700';ctx.lineWidth=3; ctx.beginPath();ctx.moveTo(pz,T);ctx.lineTo(pz,H-B);ctx.stroke(); ctx.fillStyle='#9a6700';ctx.font='bold 15px system-ui'; ctx.fillText(`${isT ?'observed t':'observed z'} = ${z.toFixed(2)}`,Math.min(W-180,Math.max(75,pz+8)),T+18); ctx.fillStyle='#b4232f';ctx.font='14px system-ui'; ctx.fillText('rejection region',W-185,T+42); ctx.fillStyle='#5f6b7f'; ctx.fillText(isT ?`Student t distribution under H₀ (df = ${df})`:'Standard normal distribution under H₀',L,T+18); ctx.fillText('z',W/2,H-8); } calcBtn.addEventListener('click',compute); [mu0El,xbarEl,sigmaEl,nEl,alphaEl,testTypeEl].forEach(el=>{ el.addEventListener('input',compute); el.addEventListener('change',compute); }); varianceModeEl.addEventListener('change',()=>{if(varianceModeEl.value==='known'){ mu0El.value='100'; xbarEl.value='104'; sigmaEl.value='12'; nEl.value='36'; alphaEl.value='0.05'; testTypeEl.value='two'; }else{ mu0El.value='50'; xbarEl.value='54'; sigmaEl.value='8'; nEl.value='16'; alphaEl.value='0.05'; testTypeEl.value='two'; }compute(); });compute();})();