keefereuther commited on
Commit
e9fe73b
·
1 Parent(s): b2009f4

more upload

Browse files
Files changed (4) hide show
  1. README-HF.md +34 -0
  2. package.json +15 -0
  3. script.js +222 -0
  4. ui.js +40 -0
README-HF.md ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Confidence Intervals Interactive App
2
+
3
+ This interactive application uses OJS (Observable JavaScript) and D3.js to demonstrate confidence intervals concepts including:
4
+
5
+ - Single sample confidence intervals
6
+ - Multiple samples visualization
7
+ - Bootstrapping methods
8
+
9
+ ## Features
10
+
11
+ - Interactive visualizations
12
+ - Real-time calculations
13
+ - Educational guiding questions
14
+ - Responsive design
15
+
16
+ ## About
17
+
18
+ This application was originally created by Keefe Reuther and converted to OJS/D3 format for broader accessibility.
19
+
20
+ ## Usage
21
+
22
+ Simply interact with the sliders, input fields, and buttons to see how different parameters affect confidence intervals.
23
+
24
+ ## Setup
25
+
26
+ If you want to run this locally:
27
+
28
+ 1. Clone the repository
29
+ 2. Run `npm install` (if you've downloaded the full repo with package.json)
30
+ 3. Run `npm start` or use any static file server
31
+
32
+ ## License
33
+
34
+ MIT
package.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "confidence-intervals-ojs",
3
+ "version": "1.0.0",
4
+ "description": "Confidence Intervals interactive app using OJS and D3",
5
+ "main": "index.html",
6
+ "scripts": {
7
+ "start": "npx http-server -o",
8
+ "test": "echo \"Error: no test specified\" && exit 1"
9
+ },
10
+ "author": "",
11
+ "license": "MIT",
12
+ "devDependencies": {
13
+ "http-server": "^14.1.1"
14
+ }
15
+ }
script.js ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Observable JS module
2
+ export default function define(runtime, observer) {
3
+ const main = runtime.module();
4
+
5
+ // Define inputs and reactive values
6
+ main.variable(observer("viewof singleSampleSD")).define("viewof singleSampleSD", ["html"], html => {
7
+ return html`<div class="control-panel">
8
+ <h3><strong>The Population</strong></h3>
9
+ <h4><strong>Population Mean: 0</strong></h4>
10
+ <div class="input-group">
11
+ <label for="normalSD">Population SD:</label>
12
+ <input id="normalSD" type="number" min="0.01" step="0.1" value="1">
13
+ </div>
14
+
15
+ <hr>
16
+
17
+ <h3><strong>Your Single Sample</strong></h3>
18
+ <div class="input-group">
19
+ <label for="sampleSize">Sample Size:</label>
20
+ <input id="sampleSize" type="number" min="1" value="50">
21
+ </div>
22
+
23
+ <div class="input-group">
24
+ <label for="confidenceLevel">Confidence Level (%):</label>
25
+ <input id="confidenceLevel" type="range" min="1" max="99" value="95" step="1">
26
+ <span id="confidenceLevelValue">95%</span>
27
+ </div>
28
+
29
+ <button id="generateBtn">Generate New Sample</button>
30
+
31
+ <hr>
32
+
33
+ <h4><strong>Guiding Questions:</strong></h4>
34
+ <h5>1. How does increasing _______ affect the certainty that your sample mean is close to the population mean?</h5>
35
+ <ul>
36
+ <li>Sample size</li>
37
+ <li>Standard deviation</li>
38
+ </ul>
39
+ <h5>2. If the standard deviation is 4, what is the minimum sample size you should consider collecting if you want your sample mean to be likely within 1 of the population mean?</h5>
40
+ </div>`;
41
+ });
42
+
43
+ // Extract input values
44
+ main.variable(observer("singleSampleSD")).define("singleSampleSD", ["viewof singleSampleSD"], view => {
45
+ return {
46
+ sd: parseFloat(view.querySelector("#normalSD").value),
47
+ sampleSize: parseInt(view.querySelector("#sampleSize").value),
48
+ confidenceLevel: parseInt(view.querySelector("#confidenceLevel").value)
49
+ };
50
+ });
51
+
52
+ // Generate random sample
53
+ main.variable(observer("singleSample")).define("singleSample", ["singleSampleSD", "normrnd"], (params, normrnd) => {
54
+ return normrnd(0, params.sd, params.sampleSize);
55
+ });
56
+
57
+ // Define the plot
58
+ main.variable(observer("singleSamplePlot")).define("singleSamplePlot", ["d3", "singleSample", "singleSampleSD"], (d3, sample, params) => {
59
+ const width = 800;
60
+ const height = 400;
61
+ const margin = {top: 30, right: 30, bottom: 50, left: 50};
62
+
63
+ // Calculate statistics
64
+ const mean = d3.mean(sample);
65
+ const sd = d3.deviation(sample);
66
+ const se = sd / Math.sqrt(sample.length);
67
+
68
+ // Calculate CI
69
+ const alpha = 1 - (params.confidenceLevel / 100);
70
+ const zCrit = -d3.quantileNormal(alpha / 2);
71
+ const halfwidth = zCrit * se;
72
+ const ciLower = mean - halfwidth;
73
+ const ciUpper = mean + halfwidth;
74
+
75
+ // Create SVG
76
+ const svg = d3.create("svg")
77
+ .attr("width", width)
78
+ .attr("height", height)
79
+ .attr("viewBox", [0, 0, width, height])
80
+ .attr("style", "max-width: 100%; height: auto;");
81
+
82
+ // Create scales
83
+ const x = d3.scaleLinear()
84
+ .domain([-7, 7])
85
+ .range([margin.left, width - margin.right]);
86
+
87
+ const y = d3.scaleLinear()
88
+ .domain([0, 0.5])
89
+ .range([height - margin.bottom, margin.top]);
90
+
91
+ // Generate histogram
92
+ const bins = d3.bin()
93
+ .domain(x.domain())
94
+ .thresholds(20)
95
+ (sample);
96
+
97
+ // Normalize bin heights
98
+ const binMax = d3.max(bins, d => d.length) / sample.length;
99
+ const yScale = d3.scaleLinear()
100
+ .domain([0, binMax])
101
+ .range([height - margin.bottom, margin.top]);
102
+
103
+ // Draw histogram
104
+ svg.append("g")
105
+ .selectAll("rect")
106
+ .data(bins)
107
+ .join("rect")
108
+ .attr("x", d => x(d.x0))
109
+ .attr("y", d => yScale(d.length / sample.length))
110
+ .attr("width", d => Math.max(0, x(d.x1) - x(d.x0) - 1))
111
+ .attr("height", d => yScale(0) - yScale(d.length / sample.length))
112
+ .attr("fill", "lightblue");
113
+
114
+ // Draw normal curve (population)
115
+ const curve = d3.line()
116
+ .x(d => x(d))
117
+ .y(d => y(d3.randomNormal.pdf(d, 0, params.sd)));
118
+
119
+ const points = d3.range(-7, 7, 0.1);
120
+
121
+ svg.append("path")
122
+ .attr("d", curve(points))
123
+ .attr("stroke", "red")
124
+ .attr("stroke-width", 2)
125
+ .attr("fill", "none");
126
+
127
+ // Draw axes
128
+ svg.append("g")
129
+ .attr("transform", `translate(0,${height - margin.bottom})`)
130
+ .call(d3.axisBottom(x));
131
+
132
+ // Add vertical lines for population and sample means
133
+ svg.append("line")
134
+ .attr("x1", x(0))
135
+ .attr("x2", x(0))
136
+ .attr("y1", margin.top)
137
+ .attr("y2", height - margin.bottom)
138
+ .attr("stroke", "red")
139
+ .attr("stroke-width", 2)
140
+ .attr("stroke-dasharray", "5,5");
141
+
142
+ svg.append("line")
143
+ .attr("x1", x(mean))
144
+ .attr("x2", x(mean))
145
+ .attr("y1", margin.top)
146
+ .attr("y2", height - margin.bottom)
147
+ .attr("stroke", "blue")
148
+ .attr("stroke-width", 2)
149
+ .attr("stroke-dasharray", "5,5");
150
+
151
+ // Add CI line
152
+ svg.append("line")
153
+ .attr("x1", x(ciLower))
154
+ .attr("x2", x(ciUpper))
155
+ .attr("y1", y(0.5))
156
+ .attr("y2", y(0.5))
157
+ .attr("stroke", "black")
158
+ .attr("stroke-width", 3);
159
+
160
+ // Add CI endpoints
161
+ svg.append("line")
162
+ .attr("x1", x(ciLower))
163
+ .attr("x2", x(ciLower))
164
+ .attr("y1", y(0.48))
165
+ .attr("y2", y(0.52))
166
+ .attr("stroke", "black")
167
+ .attr("stroke-width", 3);
168
+
169
+ svg.append("line")
170
+ .attr("x1", x(ciUpper))
171
+ .attr("x2", x(ciUpper))
172
+ .attr("y1", y(0.48))
173
+ .attr("y2", y(0.52))
174
+ .attr("stroke", "black")
175
+ .attr("stroke-width", 3);
176
+
177
+ // Add legend
178
+ const legend = svg.append("g")
179
+ .attr("transform", `translate(${width - margin.right - 200}, ${margin.top + 20})`);
180
+
181
+ const legendItems = [
182
+ { label: "Population distribution", color: "red", type: "line" },
183
+ { label: "Population mean", color: "red", type: "dashed" },
184
+ { label: "Sample mean", color: "blue", type: "dashed" },
185
+ { label: "95% CI", color: "black", type: "line" }
186
+ ];
187
+
188
+ legendItems.forEach((item, i) => {
189
+ legend.append("line")
190
+ .attr("x1", 0)
191
+ .attr("x2", 30)
192
+ .attr("y1", i * 25)
193
+ .attr("y2", i * 25)
194
+ .attr("stroke", item.color)
195
+ .attr("stroke-width", 2)
196
+ .attr("stroke-dasharray", item.type === "dashed" ? "5,5" : null);
197
+
198
+ legend.append("text")
199
+ .attr("x", 40)
200
+ .attr("y", i * 25 + 5)
201
+ .text(item.label);
202
+ });
203
+
204
+ return svg.node();
205
+ });
206
+
207
+ // Helper function for normal random numbers
208
+ main.variable(observer("normrnd")).define("normrnd", ["d3"], d3 => {
209
+ return (mean, sd, n) => {
210
+ const normal = d3.randomNormal(mean, sd);
211
+ return Array.from({length: n}, normal);
212
+ };
213
+ });
214
+
215
+ // Add normal PDF calculation
216
+ d3.randomNormal.pdf = (x, mean, sd) => {
217
+ return (1 / (sd * Math.sqrt(2 * Math.PI))) *
218
+ Math.exp(-0.5 * Math.pow((x - mean) / sd, 2));
219
+ };
220
+
221
+ return main;
222
+ }
ui.js ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Wait for the DOM to be fully loaded
2
+ document.addEventListener('DOMContentLoaded', () => {
3
+ // Get all tab buttons
4
+ const tabButtons = document.querySelectorAll('.tab-btn');
5
+
6
+ // Add click event to each tab button
7
+ tabButtons.forEach(button => {
8
+ button.addEventListener('click', () => {
9
+ // Remove active class from all buttons
10
+ tabButtons.forEach(btn => btn.classList.remove('active'));
11
+
12
+ // Add active class to clicked button
13
+ button.classList.add('active');
14
+
15
+ // Get the tab to show based on data-tab attribute
16
+ const tabToShow = button.getAttribute('data-tab');
17
+
18
+ // Logic to show/hide content based on selected tab
19
+ // This would be expanded as we develop the full application
20
+ console.log(`Tab ${tabToShow} selected`);
21
+
22
+ // For now, we'll just update some visibility (to be expanded)
23
+ if (tabToShow === 'single-sample') {
24
+ document.getElementById('tab1').style.display = 'block';
25
+ document.getElementById('singleSamplePlot').style.display = 'block';
26
+ // Hide other tabs' content when implemented
27
+ }
28
+ });
29
+ });
30
+
31
+ // Update confidence level display when slider changes
32
+ const confidenceLevelInput = document.querySelector('#confidenceLevel');
33
+ const confidenceLevelValue = document.querySelector('#confidenceLevelValue');
34
+
35
+ if (confidenceLevelInput && confidenceLevelValue) {
36
+ confidenceLevelInput.addEventListener('input', () => {
37
+ confidenceLevelValue.textContent = `${confidenceLevelInput.value}%`;
38
+ });
39
+ }
40
+ });