正态分布,这条优美的钟形曲线,可以说是统计学中最重要、最无处不在的概率分布。从自然现象(如身高、测量误差)到金融市场,再到机器学习,它的身影随处可见。但你是否真正理解它为何如此普遍?仅仅看公式和定义可能有些枯燥,不如动手实践!
最近,我创建了一个简单的 HTML 交互式页面,通过三个经典的实验,让你直观地感受和探索正态分布的奥秘。无需安装任何软件,在浏览器中即可运行。
你可以将下面的完整 HTML 代码保存为一个 .html
文件,然后用浏览器打开它,即可开始交互式实验。
(如果你正在使用支持 Artifact 的界面,可以直接在旁边预览和交互)
下面,我们来详细介绍一下这三个实验以及它们背后的原理。
核心思想: 这是正态分布“王者地位”的关键原因之一。中心极限定理指出,无论原始数据来自什么分布(只要它有明确的均值和方差),只要你从中抽取足够多的样本,并计算这些样本的均值,那么这些样本均值的分布就会趋近于正态分布。样本量越大,这种趋近性越好。
交互实验:
T
次试验,每次试验都抽取 M
个这样的随机数,然后计算这 M
个数的平均值。最后,我们将这 T
个平均值进行“标准化”处理(减去理论均值,再除以理论标准差),并绘制它们的分布直方图。核心思想: 高尔顿板是一个物理装置,小球从顶部落下,经过多层交错排列的钉子,每次碰到钉子时,小球有同等机会向左或向右落下。最终,小球落在底部的不同槽中,形成近似正态分布的堆积。这本质上是二项分布的一个直观展示,而当层数(试验次数)很多时,二项分布可以很好地用正态分布来近似。
交互实验:
L
和下落的小球数量 B
。每个小球独立下落 L
层,每层随机向左(位置-0.5)或向右(位置+0.5)。我们记录每个小球最终的水平位置。核心思想: 前两个实验展示了正态分布是如何“自然产生”或作为极限情况出现的。而 Box-Muller 变换则是一种直接生成符合标准正态分布 (N(0,1)) 随机数的常用算法。它利用两个独立的、来自标准均匀分布 U(0,1) 的随机数,通过一个精巧的数学变换,生成两个独立的、来自标准正态分布 N(0,1) 的随机数。
交互实验:
N
。非常简单:
.html
文件 (例如 normal_experiments.html
)。通过这三个交互式实验,希望你能更直观地理解:
统计学不仅仅是冰冷的公式,动手实验和模拟是理解其深刻内涵的绝佳途径。快去亲自试试看,调整参数,观察变化,感受正态分布的魅力吧!
DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>交互式正态分布实验title>
<script src="https://cdn.tailwindcss.com">script>
<script src="https://cdn.jsdelivr.net/npm/chart.js">script>
<style>
.chart-container {
position: relative;
height: 300px;
width: 100%;
}
label {
display: block;
margin-bottom: 0.25rem;
font-weight: 500;
}
input[type="number"], input[type="range"] {
width: 100%;
padding: 0.5rem;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
margin-bottom: 0.5rem;
}
button {
background-color: #3b82f6;
color: white;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
cursor: pointer;
transition: background-color 0.2s;
}
button:hover {
background-color: #2563eb;
}
.description {
font-size: 0.875rem;
color: #4b5563;
margin-bottom: 1rem;
}
style>
head>
<body class="bg-gray-100 p-6">
<h1 class="text-3xl font-bold mb-8 text-center">交互式正态分布实验h1>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-semibold mb-3">实验1:中心极限定理h2>
<p class="description">
从任意分布(这里使用均匀分布)中抽取多个样本,计算这些样本的均值。根据中心极限定理,当样本量足够大时,这些样本均值的分布将趋近于正态分布。
p>
<div>
<label for="clt-trials">试验次数 (T): <span id="clt-trials-val">5000span>label>
<input type="range" id="clt-trials" min="100" max="10000" value="5000" step="100" oninput="document.getElementById('clt-trials-val').textContent = this.value;">
div>
<div>
<label for="clt-sample-size">每次试验的样本量 (M): <span id="clt-sample-size-val">30span>label>
<input type="range" id="clt-sample-size" min="2" max="100" value="30" step="1" oninput="document.getElementById('clt-sample-size-val').textContent = this.value;">
div>
<button onclick="runCLTExperiment()">运行实验button>
<div class="chart-container mt-4">
<canvas id="chart1">canvas>
div>
div>
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-semibold mb-3">实验2:高尔顿板模拟h2>
<p class="description">
模拟小球通过钉板下落的过程。小球每次碰到钉子时,随机向左或向右下落。最终小球落入底部的槽中,其分布接近二项分布。当层数足够多时,二项分布近似于正态分布。
p>
<div>
<label for="galton-levels">钉板层数 (L): <span id="galton-levels-val">20span>label>
<input type="range" id="galton-levels" min="5" max="50" value="20" step="1" oninput="document.getElementById('galton-levels-val').textContent = this.value;">
div>
<div>
<label for="galton-balls">小球数量 (B): <span id="galton-balls-val">5000span>label>
<input type="range" id="galton-balls" min="100" max="10000" value="5000" step="100" oninput="document.getElementById('galton-balls-val').textContent = this.value;">
div>
<button onclick="runGaltonExperiment()">运行实验button>
<div class="chart-container mt-4">
<canvas id="chart2">canvas>
div>
div>
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-semibold mb-3">实验3:Box-Muller 变换h2>
<p class="description">
利用均匀分布的随机数生成符合标准正态分布 (N(0,1)) 的随机数。这是生成正态分布样本的常用算法。
p>
<div>
<label for="boxmuller-samples">生成样本数量 (N): <span id="boxmuller-samples-val">5000span>label>
<input type="range" id="boxmuller-samples" min="100" max="10000" value="5000" step="100" oninput="document.getElementById('boxmuller-samples-val').textContent = this.value;">
div>
<button onclick="runBoxMullerExperiment()">运行实验button>
<div class="chart-container mt-4">
<canvas id="chart3">canvas>
div>
div>
div>
<script>
let chart1, chart2, chart3;
// 标准正态分布 PDF
function normalPDF(x, mu = 0, sigma = 1) {
return (1 / (sigma * Math.sqrt(2 * Math.PI))) * Math.exp(-0.5 * Math.pow((x - mu) / sigma, 2));
}
// 数据分箱 (用于直方图)
function binData(data, binCount = 40) {
if (!data || data.length === 0) return { counts: [], centers: [], binWidth: 0 };
let min = Math.min(...data), max = Math.max(...data);
if (min === max) { // Handle case where all data points are the same
min -= 0.5;
max += 0.5;
}
const binWidth = (max - min) / binCount;
const bins = Array(binCount).fill(0);
const centers = Array(binCount).fill(0).map((_, i) => min + (i + 0.5) * binWidth);
data.forEach(v => {
let idx = Math.floor((v - min) / binWidth);
if (idx >= binCount) idx = binCount - 1; // Handle max value
if (idx < 0) idx = 0; // Handle min value potentially slightly off
bins[idx]++;
});
return { counts: bins, centers, binWidth };
}
// 绘制直方图
function drawHistogram(ctx, chartInstance, data, binCount = 40, title, theoreticalPdf = null, pdfParams = {}) {
const { counts, centers, binWidth } = binData(data, binCount);
const N = data.length;
const datasets = [{
label: '实验频数',
data: counts,
backgroundColor: 'rgba(59, 130, 246, 0.5)',
borderColor: 'rgba(59, 130, 246, 1)',
borderWidth: 1,
barPercentage: 1.0,
categoryPercentage: 1.0
}];
if (theoreticalPdf) {
const pdfValues = centers.map(x => theoreticalPdf(x, pdfParams.mu, pdfParams.sigma) * N * binWidth);
datasets.push({
label: '理论分布 (缩放后)',
data: pdfValues,
type: 'line',
borderColor: 'rgba(234, 88, 12, 1)',
borderWidth: 2,
fill: false,
pointRadius: 0,
tension: 0.1
});
}
if (chartInstance) {
chartInstance.destroy();
}
return new Chart(ctx, {
type: 'bar',
data: {
labels: centers.map(c => c.toFixed(2)),
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: { display: true, text: title },
tooltip: {
mode: 'index',
intersect: false,
}
},
scales: {
x: {
title: { display: true, text: '值' },
ticks: {
maxRotation: 0,
minRotation: 0,
autoSkip: true,
maxTicksLimit: 10 // Limit number of x-axis labels for readability
}
},
y: {
title: { display: true, text: '频数' },
beginAtZero: true
}
}
}
});
}
// --- 实验 1: 中心极限定理 ---
function runCLTExperiment() {
const trials = parseInt(document.getElementById('clt-trials').value);
const sampleSize = parseInt(document.getElementById('clt-sample-size').value);
const means = [];
// 均匀分布 U[0, 1] 的均值为 0.5,方差为 1/12
const mu_uniform = 0.5;
const var_uniform = 1 / 12;
// 根据CLT,样本均值的均值为 mu_uniform,方差为 var_uniform / sampleSize
const mu_mean = mu_uniform;
const sigma_mean = Math.sqrt(var_uniform / sampleSize);
// 标准化后的均值 Z = (Mean - mu_mean) / sigma_mean,应服从 N(0,1)
for (let i = 0; i < trials; i++) {
let sum = 0;
for (let j = 0; j < sampleSize; j++) {
sum += Math.random();
}
const mean = sum / sampleSize;
const standardized_mean = (mean - mu_mean) / sigma_mean;
means.push(standardized_mean);
}
chart1 = drawHistogram(
document.getElementById('chart1').getContext('2d'),
chart1,
means,
40, // bins
`样本均值的分布 (T=${trials}, M=${sampleSize})`,
normalPDF, // Theoretical PDF is N(0,1) after standardization
{ mu: 0, sigma: 1 }
);
}
// --- 实验 2: 高尔顿板 ---
function runGaltonExperiment() {
const levels = parseInt(document.getElementById('galton-levels').value);
const balls = parseInt(document.getElementById('galton-balls').value);
const finalPositions = [];
for (let i = 0; i < balls; i++) {
let position = 0;
for (let j = 0; j < levels; j++) {
position += (Math.random() < 0.5) ? -0.5 : 0.5; // 左 -0.5, 右 +0.5
}
finalPositions.push(position);
}
// 二项分布 B(n, p) 的均值为 np, 方差为 np(1-p)
// 这里 n=levels, p=0.5 (向右的概率)
// 每次移动是 +0.5 或 -0.5。等价于计算向右次数 k,位置 = k*0.5 + (levels-k)*(-0.5) = k - levels/2
// 向右次数 k ~ B(levels, 0.5)。均值 E[k] = levels*0.5。方差 Var(k) = levels*0.5*0.5 = levels/4
// 最终位置 Position = k - levels/2
// 均值 E[Position] = E[k] - levels/2 = levels*0.5 - levels/2 = 0
// 方差 Var(Position) = Var(k) = levels/4
const mu_galton = 0;
const sigma_galton = Math.sqrt(levels / 4);
// 确定分箱数量,最好是层数+1,如果层数不多
const binCount = Math.min(levels + 1, 50);
chart2 = drawHistogram(
document.getElementById('chart2').getContext('2d'),
chart2,
finalPositions,
binCount,
`高尔顿板落点分布 (L=${levels}, B=${balls})`,
normalPDF, // Compare with Normal distribution
{ mu: mu_galton, sigma: sigma_galton }
);
}
// --- 实验 3: Box-Muller ---
function sampleNormalBoxMuller(n) {
const samples = [];
for (let i = 0; i < Math.ceil(n / 2); i++) {
const u1 = Math.random();
const u2 = Math.random();
const R = Math.sqrt(-2 * Math.log(u1));
const theta = 2 * Math.PI * u2;
samples.push(R * Math.cos(theta));
samples.push(R * Math.sin(theta));
}
return samples.slice(0, n); // Ensure exact number of samples
}
function runBoxMullerExperiment() {
const numSamples = parseInt(document.getElementById('boxmuller-samples').value);
const samples = sampleNormalBoxMuller(numSamples);
chart3 = drawHistogram(
document.getElementById('chart3').getContext('2d'),
chart3,
samples,
40, // bins
`Box-Muller 生成的 N(0,1) 样本 (N=${numSamples})`,
normalPDF, // Theoretical PDF is N(0,1)
{ mu: 0, sigma: 1 }
);
}
// Initial runs on page load
window.onload = () => {
runCLTExperiment();
runGaltonExperiment();
runBoxMullerExperiment();
};
script>
body>
html>