Created
June 4, 2020 16:24
-
-
Save ljcolling/048a8203123e9cf8761b81057675909e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const d3 = Object.assign({}, | |
| require("d3-selection"), | |
| require("d3-scale"), | |
| require("d3-array"), | |
| require("d3-shape"), | |
| require("d3-transition"), | |
| require("d3-ease"), | |
| require("d3-timer"), | |
| require("d3-format")); | |
| import React from "react"; | |
| import { Form, Row, Col } from "react-bootstrap"; | |
| import "./styles.css"; | |
| export const id = "clt"; | |
| export const name = "The Central Limit Theorem"; | |
| export const readme = "Let \\(x_1 \\dots x_n\\) be a set of independent, identically distributed random variables following a distribution \\(f\\) with mean \\(\\mu\\) and finite variance \\(\\sigma^2\\). In the limit of large \\(n\\), the arithmetic mean \\[\\bar{x} = \\frac{1}{n}\\sum_{i=1}^{n}x_i\\] follows a normal distribution with mean \\(\\mu\\) and variance \\(\\frac{\\sigma^2}{n}\\). This holds regardless of the specific form of \\(f\\). In this example, \\(f\\) is a uniform distribution (set \\(n = 1\\) to see this) and \\(n = 4\\) by default."; | |
| export const sources = [ | |
| { url: "https://en.wikipedia.org/wiki/Central_limit_theorem", description: ["Central Limit Theorem", "(Wikipedia)"] } | |
| ]; | |
| var timer; | |
| export function controls() { | |
| return ( | |
| <Form> | |
| <Form.Group as={Row}> | |
| <Form.Label column md={2}> | |
| Number | |
| </Form.Label> | |
| <Col md={3}> | |
| <Form.Control id="control-clt-number" as="select" defaultValue="4"> | |
| <option value="1">n = 1</option> | |
| <option value="4">n = 4</option> | |
| <option value="8">n = 8</option> | |
| <option value="16">n = 16</option> | |
| </Form.Control> | |
| </Col> | |
| </Form.Group> | |
| <Form.Group as={Row}> | |
| <Form.Label column md={2}> | |
| Speed | |
| </Form.Label> | |
| <Col md={3} style={{paddingTop: 10}}> | |
| <input id="control-clt-speed" type="range" min="0" max="1" defaultValue="0" step="0.01"/> | |
| </Col> | |
| </Form.Group> | |
| </Form> | |
| ); | |
| } | |
| export function create(el, props) { | |
| var margin = { top: 20, right: 10, bottom: 20, left: 10 }; | |
| var width = 960 - margin.left - margin.right, | |
| height = 500 - margin.top - margin.bottom; | |
| var svg = d3.select(el).append("svg") | |
| .attr("width", "100%") | |
| .attr("height", "100%") | |
| .attr("viewBox", `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`) | |
| .attr("preserveAspectRatio", "xMidYMid meet") | |
| .append("g") | |
| .attr("transform", `translate(${margin.left},${margin.top})`); | |
| var dt = 1000, // time step | |
| n = 4; // sample size | |
| var speed = d3.scaleLinear() | |
| .domain([0, 1]) | |
| .range([dt, 100]); | |
| var f = { | |
| sample: Math.random, | |
| mu: 1 / 2, | |
| sigma: 1 / (2 * Math.sqrt(3)) | |
| }; | |
| var pdf = x => Math.sqrt(n) * Math.exp(-n * (x - f.mu) * (x - f.mu) / (2 * f.sigma * f.sigma)) / Math.sqrt(2 * Math.PI) / f.sigma; | |
| var x = d3.scaleLinear() | |
| .domain([0, 1]) | |
| .rangeRound([0, width]); | |
| var y1 = height / 3, | |
| y2 = height / 2; | |
| var y = d3.scaleLinear() | |
| .domain([0, pdf(f.mu)]) | |
| .range([0, height - y2]); | |
| var histogram = d3.histogram() | |
| .domain(x.domain()) | |
| .thresholds(x.ticks(20)); | |
| var area = d3.area() | |
| .x(d => x(d[0])) | |
| .y0(y2) | |
| .y1(d => y2 + y(d[1])) | |
| .curve(d3.curveBasis); | |
| svg.append("path") | |
| .attr("class", "line"); | |
| svg.append("g") | |
| .attr("class", "bars"); | |
| var counts = []; | |
| var axis = svg.selectAll(".axis") | |
| .data([{ y: 0, label: "draw" }, { y: y1, label: "average" }, { y: y2, label: "count" }]) | |
| .enter().append("g") | |
| .attr("class", "axis") | |
| .attr("transform", d => `translate(0,${d.y})`); | |
| axis.append("path") | |
| .attr("d", `M0,0H${width}`) | |
| axis.append("text") | |
| .attr("dominant-baseline", "hanging") | |
| .attr("dy", 5) | |
| .text(d => d.label); | |
| function renderBars() { | |
| if (counts.length < 1) | |
| return; | |
| var data = histogram(counts) | |
| .map(d => { d.y = d.length / counts.length; return d; }) | |
| .filter(d => d.x1 > d.x0); | |
| var ymax = d3.max(data, d => d.y); | |
| y.domain([0, ymax / (1 / 20)]); | |
| var bar = svg.select(".bars").selectAll(".bar").data(data); | |
| var g = bar.enter().append("g") | |
| .attr("class", "bar") | |
| .attr("transform", d => `translate(${x(d.x0)},${y2})`); | |
| g.append("rect") | |
| .attr("width", d => (x(d.x1) - x(d.x0)) - 2); | |
| g.append("text") | |
| .attr("x", x(1 / 40)) | |
| .attr("dy", 10) | |
| .attr("dominant-baseline", "hanging") | |
| .attr("text-anchor", "middle"); | |
| bar = g.merge(bar); | |
| var t = d3.transition().duration(dt / 4); | |
| bar.select("rect") | |
| .transition(t) | |
| .attr("height", d => y(d.y / (1 / 20))); | |
| bar.select("text") | |
| .text(d => d.y > 0 ? d3.format(".0%")(d.y) : ""); | |
| svg.select(".line") | |
| .datum(d3.range(0, 1.05, 0.05).map(x => [x, pdf(x)])) | |
| .transition(t) | |
| .attr("d", area); | |
| } | |
| function renderBalls() { | |
| var data = d3.range(n).map(f.sample); | |
| var mean = d3.mean(data); | |
| var ball = svg.append("g").selectAll(".ball").data(data); | |
| var i = 0; | |
| ball.enter().append("circle") | |
| .attr("class", "ball") | |
| .attr("cx", d => x(d)) | |
| .attr("cy", 0) | |
| .attr("r", 5) | |
| .transition().duration(dt).ease(d3.easeBounce) | |
| .attr("cy", y1 - 5) | |
| .on("end", function() { | |
| d3.select(this) | |
| .transition().duration(dt / 4) | |
| .attr("cy", (y2 + y1) / 2) | |
| .transition().duration(dt / 4) | |
| .attr("cx", x(mean)) | |
| .transition().duration(dt / 4).ease(d3.easeBounce) | |
| .attr("cy", y2 - 3) | |
| .attr("r", 3) | |
| .each(() => ++i) | |
| .on("end", function() { | |
| if (!--i) { | |
| counts.push(mean); | |
| } else { | |
| d3.select(this).remove(); | |
| } | |
| }); | |
| }); | |
| } | |
| function renderAll() { | |
| renderBars(); | |
| renderBalls(); | |
| } | |
| function start() { | |
| return d3.interval(renderAll, dt); | |
| } | |
| timer = start(); | |
| d3.select("#control-clt-number") | |
| .on("change", function() { | |
| svg.selectAll(".ball").interrupt().remove(); | |
| svg.selectAll(".bar").interrupt().remove(); | |
| svg.select(".line").interrupt().attr("d", null); | |
| svg.select(".line").attr("opacity", +this.value > 1 ? 1 : 0); | |
| counts = []; | |
| n = +this.value; | |
| }); | |
| d3.select("#control-clt-speed") | |
| .on("change", function() { | |
| timer.stop(); | |
| dt = speed(this.value); | |
| timer = start(); | |
| }); | |
| } | |
| export function destroy() { | |
| if (typeof timer !== "undefined") timer.stop(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment