Skip to content

Commit b9ea321

Browse files
committed
add normal distribution section with interactive comparison to t-distribution in stats_primer.qmd; create normal-vs-t-distribution.html for visualization
1 parent cb74f2b commit b9ea321

File tree

2 files changed

+299
-0
lines changed

2 files changed

+299
-0
lines changed

blog/stats_primer.qmd

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,49 @@ format:
2929
- ⁠⁠2 samples: normal and chi square
3030
- Adjusting for confounders - (stratifying) & linear regression. Logistic regression
3131

32+
## Normal distribution
33+
34+
::: {.column-page}
35+
<iframe src="../resources/normal-vs-t-distribution.html" width="100%" height="570px" frameBorder="0"></iframe>
36+
:::
37+
38+
::: {.notes}
39+
40+
**Try this**
41+
42+
1. Start with default settings (mean=0, SD=1, n=30)
43+
2. Reduce sample size to n=5 and observe how t-distribution tails become heavier
44+
3. Increase sample size to n=100 and see how t-distribution converges to normal
45+
4. Adjust mean and standard deviation to see how both distributions shift and scale
46+
5. Compare the width of confidence intervals (dashed lines) between distributions
47+
48+
**Notes**
49+
50+
- The normal distribution (blue) is symmetric and bell-shaped
51+
- The t-distribution (red) has heavier tails than the normal distribution
52+
- As sample size increases, the t-distribution approaches the normal distribution
53+
- The choice between them depends on whether the population standard deviation is known:
54+
- Normal distribution: Used when population standard deviation is known (rare in practice)
55+
- t-distribution: Used when estimating standard deviation from sample data (most common)
56+
- t-distribution accounts for added uncertainty in standard deviation estimate
57+
58+
**Equations**
59+
60+
The mean ($\mu$) and standard deviation ($\sigma$) are calculated as follows:
61+
62+
- Mean ($\mu$):
63+
$$ \mu = \frac{\sum_{i=1}^{n} x_i}{n} $$
64+
65+
- Standard Deviation (σ):
66+
$$ \sigma = \sqrt{\frac{\sum_{i=1}^{n} (x_i - \mu)^2}{n}} $$
67+
68+
Where:
69+
70+
- $x_i$ represents each individual data point
71+
- $n$ is the number of data points
72+
73+
:::
74+
3275
## Coin Flip Simulation
3376

3477
This simulation demonstrates the binomial distribution through repeated coin flips.
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<title>Normal vs t Distribution</title>
7+
<script src="https://cdn.plot.ly/plotly-2.24.1.min.js"></script>
8+
<script src="https://cdn.jsdelivr.net/npm/jstat@latest/dist/jstat.min.js"></script>
9+
<style>
10+
body {
11+
font-family: system-ui, sans-serif;
12+
max-width: 1100px;
13+
margin: 0 auto;
14+
padding: 10px;
15+
font-size: 14px;
16+
}
17+
18+
.controls {
19+
margin: 1rem 0;
20+
display: flex;
21+
gap: 1rem;
22+
align-items: center;
23+
}
24+
25+
.control-group {
26+
display: flex;
27+
flex-direction: column;
28+
gap: 0.5rem;
29+
}
30+
31+
label {
32+
font-size: 13px;
33+
}
34+
35+
input[type="range"] {
36+
width: 200px;
37+
}
38+
39+
#stats {
40+
font-size: 13px;
41+
margin-top: 1rem;
42+
color: #666;
43+
}
44+
</style>
45+
</head>
46+
47+
<body>
48+
<div id="app">
49+
<div class="controls">
50+
<div class="control-group">
51+
<label>Mean: <span id="mean-value">0</span></label>
52+
<input type="range" id="mean" min="-3" max="3" step="0.1" value="0">
53+
</div>
54+
<div class="control-group">
55+
<label>Standard Deviation: <span id="std-value">1</span></label>
56+
<input type="range" id="std" min="0.1" max="3" step="0.1" value="1">
57+
</div>
58+
<div class="control-group">
59+
<label>Sample Size: <span id="size-value">30</span></label>
60+
<input type="range" id="size" min="2" max="100" step="1" value="30">
61+
</div>
62+
</div>
63+
<div id="plot"></div>
64+
<div id="stats"></div>
65+
</div>
66+
67+
<script>
68+
// State
69+
const state = {
70+
mean: 0,
71+
stdDev: 1,
72+
sampleSize: 30
73+
};
74+
75+
// Normal distribution PDF
76+
function normalPDF(x, mu, sigma) {
77+
return (1 / (sigma * Math.sqrt(2 * Math.PI))) *
78+
Math.exp(-0.5 * Math.pow((x - mu) / sigma, 2));
79+
}
80+
81+
// T distribution PDF
82+
function tPDF(x, df) {
83+
function gamma(n) {
84+
if (n === 1) return 1;
85+
if (n === 0.5) return Math.sqrt(Math.PI);
86+
return (n - 1) * gamma(n - 1);
87+
}
88+
89+
const coefficient = gamma((df + 1) / 2) / (Math.sqrt(df * Math.PI) * gamma(df / 2));
90+
return coefficient * Math.pow(1 + (x * x) / df, -(df + 1) / 2);
91+
}
92+
93+
// Calculate confidence intervals
94+
function calculateCriticalValues() {
95+
// Normal distribution (z-score for 95% CI)
96+
const zCritical = 1.96;
97+
const normalCI = {
98+
lower: state.mean - (zCritical * state.stdDev),
99+
upper: state.mean + (zCritical * state.stdDev)
100+
};
101+
102+
// T distribution
103+
const df = state.sampleSize - 1;
104+
const alpha = 0.025; // Two-tailed 95% CI
105+
const tCritical = Math.abs(jStat.studentt.inv(alpha, df));
106+
const tCI = {
107+
lower: state.mean - (tCritical * state.stdDev),
108+
upper: state.mean + (tCritical * state.stdDev)
109+
};
110+
111+
return { normalCI, tCI, tCritical };
112+
}
113+
114+
function generateData() {
115+
const x = [];
116+
const normalY = [];
117+
const tY = [];
118+
const range = state.stdDev * 4;
119+
const step = range / 100;
120+
121+
for (let i = state.mean - range; i <= state.mean + range; i += step) {
122+
x.push(i);
123+
normalY.push(normalPDF(i, state.mean, state.stdDev));
124+
tY.push(tPDF((i - state.mean) / state.stdDev, state.sampleSize - 1));
125+
}
126+
127+
return { x, normalY, tY };
128+
}
129+
130+
function updatePlot() {
131+
const data = generateData();
132+
const criticalValues = calculateCriticalValues();
133+
134+
const traces = [
135+
{
136+
x: data.x,
137+
y: data.normalY,
138+
name: 'Normal Distribution',
139+
line: { color: '#2563eb' }
140+
},
141+
{
142+
x: data.x,
143+
y: data.tY,
144+
name: 't Distribution',
145+
line: { color: '#dc2626' }
146+
}
147+
];
148+
149+
const layout = {
150+
title: {
151+
text: 'Normal vs t Distribution Comparison',
152+
font: { size: 16 }
153+
},
154+
width: 800,
155+
height: 400,
156+
showlegend: true,
157+
legend: {
158+
orientation: 'h',
159+
y: -0.15,
160+
x: 0.5,
161+
xanchor: 'center'
162+
},
163+
margin: {
164+
l: 50,
165+
r: 50,
166+
t: 40,
167+
b: 60 // increased bottom margin to accommodate legend
168+
},
169+
shapes: [
170+
// Normal CI lines
171+
{
172+
type: 'line',
173+
x0: criticalValues.normalCI.lower,
174+
x1: criticalValues.normalCI.lower,
175+
y0: 0,
176+
y1: 0.4,
177+
line: {
178+
color: '#1e40af',
179+
width: 2,
180+
dash: 'dash'
181+
}
182+
},
183+
{
184+
type: 'line',
185+
x0: criticalValues.normalCI.upper,
186+
x1: criticalValues.normalCI.upper,
187+
y0: 0,
188+
y1: 0.4,
189+
line: {
190+
color: '#1e40af',
191+
width: 2,
192+
dash: 'dash'
193+
}
194+
},
195+
// T CI lines
196+
{
197+
type: 'line',
198+
x0: criticalValues.tCI.lower,
199+
x1: criticalValues.tCI.lower,
200+
y0: 0,
201+
y1: 0.4,
202+
line: {
203+
color: '#dc2626',
204+
width: 2,
205+
dash: 'dash'
206+
}
207+
},
208+
{
209+
type: 'line',
210+
x0: criticalValues.tCI.upper,
211+
x1: criticalValues.tCI.upper,
212+
y0: 0,
213+
y1: 0.4,
214+
line: {
215+
color: '#dc2626',
216+
width: 2,
217+
dash: 'dash'
218+
}
219+
}
220+
]
221+
};
222+
223+
Plotly.newPlot('plot', traces, layout);
224+
225+
// Update stats display
226+
document.getElementById('stats').innerHTML = `
227+
<p>Normal Distribution 95% CI: ${criticalValues.normalCI.lower.toFixed(2)} to ${criticalValues.normalCI.upper.toFixed(2)}</p>
228+
<p>t Distribution 95% CI: ${criticalValues.tCI.lower.toFixed(2)} to ${criticalValues.tCI.upper.toFixed(2)}</p>
229+
`;
230+
}
231+
232+
// Setup event listeners
233+
document.getElementById('mean').addEventListener('input', (e) => {
234+
state.mean = parseFloat(e.target.value);
235+
document.getElementById('mean-value').textContent = state.mean.toFixed(2);
236+
updatePlot();
237+
});
238+
239+
document.getElementById('std').addEventListener('input', (e) => {
240+
state.stdDev = parseFloat(e.target.value);
241+
document.getElementById('std-value').textContent = state.stdDev.toFixed(2);
242+
updatePlot();
243+
});
244+
245+
document.getElementById('size').addEventListener('input', (e) => {
246+
state.sampleSize = parseInt(e.target.value);
247+
document.getElementById('size-value').textContent = state.sampleSize;
248+
updatePlot();
249+
});
250+
251+
// Initial plot
252+
updatePlot();
253+
</script>
254+
</body>
255+
256+
</html>

0 commit comments

Comments
 (0)