File size: 7,127 Bytes
e9fe73b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// Observable JS module
export default function define(runtime, observer) {
  const main = runtime.module();
  
  // Define inputs and reactive values
  main.variable(observer("viewof singleSampleSD")).define("viewof singleSampleSD", ["html"], html => {
    return html`<div class="control-panel">
      <h3><strong>The Population</strong></h3>
      <h4><strong>Population Mean: 0</strong></h4>
      <div class="input-group">
        <label for="normalSD">Population SD:</label>
        <input id="normalSD" type="number" min="0.01" step="0.1" value="1">
      </div>
      
      <hr>
      
      <h3><strong>Your Single Sample</strong></h3>
      <div class="input-group">
        <label for="sampleSize">Sample Size:</label>
        <input id="sampleSize" type="number" min="1" value="50">
      </div>
      
      <div class="input-group">
        <label for="confidenceLevel">Confidence Level (%):</label>
        <input id="confidenceLevel" type="range" min="1" max="99" value="95" step="1">
        <span id="confidenceLevelValue">95%</span>
      </div>
      
      <button id="generateBtn">Generate New Sample</button>
      
      <hr>
      
      <h4><strong>Guiding Questions:</strong></h4>
      <h5>1. How does increasing _______ affect the certainty that your sample mean is close to the population mean?</h5>
      <ul>
        <li>Sample size</li>
        <li>Standard deviation</li>
      </ul>
      <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>
    </div>`;
  });

  // Extract input values
  main.variable(observer("singleSampleSD")).define("singleSampleSD", ["viewof singleSampleSD"], view => {
    return {
      sd: parseFloat(view.querySelector("#normalSD").value),
      sampleSize: parseInt(view.querySelector("#sampleSize").value),
      confidenceLevel: parseInt(view.querySelector("#confidenceLevel").value)
    };
  });

  // Generate random sample
  main.variable(observer("singleSample")).define("singleSample", ["singleSampleSD", "normrnd"], (params, normrnd) => {
    return normrnd(0, params.sd, params.sampleSize);
  });

  // Define the plot
  main.variable(observer("singleSamplePlot")).define("singleSamplePlot", ["d3", "singleSample", "singleSampleSD"], (d3, sample, params) => {
    const width = 800;
    const height = 400;
    const margin = {top: 30, right: 30, bottom: 50, left: 50};
    
    // Calculate statistics
    const mean = d3.mean(sample);
    const sd = d3.deviation(sample);
    const se = sd / Math.sqrt(sample.length);
    
    // Calculate CI
    const alpha = 1 - (params.confidenceLevel / 100);
    const zCrit = -d3.quantileNormal(alpha / 2);
    const halfwidth = zCrit * se;
    const ciLower = mean - halfwidth;
    const ciUpper = mean + halfwidth;
    
    // Create SVG
    const svg = d3.create("svg")
      .attr("width", width)
      .attr("height", height)
      .attr("viewBox", [0, 0, width, height])
      .attr("style", "max-width: 100%; height: auto;");
    
    // Create scales
    const x = d3.scaleLinear()
      .domain([-7, 7])
      .range([margin.left, width - margin.right]);
      
    const y = d3.scaleLinear()
      .domain([0, 0.5])
      .range([height - margin.bottom, margin.top]);
    
    // Generate histogram
    const bins = d3.bin()
      .domain(x.domain())
      .thresholds(20)
      (sample);
    
    // Normalize bin heights
    const binMax = d3.max(bins, d => d.length) / sample.length;
    const yScale = d3.scaleLinear()
      .domain([0, binMax])
      .range([height - margin.bottom, margin.top]);
    
    // Draw histogram
    svg.append("g")
      .selectAll("rect")
      .data(bins)
      .join("rect")
        .attr("x", d => x(d.x0))
        .attr("y", d => yScale(d.length / sample.length))
        .attr("width", d => Math.max(0, x(d.x1) - x(d.x0) - 1))
        .attr("height", d => yScale(0) - yScale(d.length / sample.length))
        .attr("fill", "lightblue");
    
    // Draw normal curve (population)
    const curve = d3.line()
      .x(d => x(d))
      .y(d => y(d3.randomNormal.pdf(d, 0, params.sd)));
    
    const points = d3.range(-7, 7, 0.1);
    
    svg.append("path")
      .attr("d", curve(points))
      .attr("stroke", "red")
      .attr("stroke-width", 2)
      .attr("fill", "none");
    
    // Draw axes
    svg.append("g")
      .attr("transform", `translate(0,${height - margin.bottom})`)
      .call(d3.axisBottom(x));
    
    // Add vertical lines for population and sample means
    svg.append("line")
      .attr("x1", x(0))
      .attr("x2", x(0))
      .attr("y1", margin.top)
      .attr("y2", height - margin.bottom)
      .attr("stroke", "red")
      .attr("stroke-width", 2)
      .attr("stroke-dasharray", "5,5");
      
    svg.append("line")
      .attr("x1", x(mean))
      .attr("x2", x(mean))
      .attr("y1", margin.top)
      .attr("y2", height - margin.bottom)
      .attr("stroke", "blue")
      .attr("stroke-width", 2)
      .attr("stroke-dasharray", "5,5");
    
    // Add CI line
    svg.append("line")
      .attr("x1", x(ciLower))
      .attr("x2", x(ciUpper))
      .attr("y1", y(0.5))
      .attr("y2", y(0.5))
      .attr("stroke", "black")
      .attr("stroke-width", 3);
      
    // Add CI endpoints
    svg.append("line")
      .attr("x1", x(ciLower))
      .attr("x2", x(ciLower))
      .attr("y1", y(0.48))
      .attr("y2", y(0.52))
      .attr("stroke", "black")
      .attr("stroke-width", 3);
      
    svg.append("line")
      .attr("x1", x(ciUpper))
      .attr("x2", x(ciUpper))
      .attr("y1", y(0.48))
      .attr("y2", y(0.52))
      .attr("stroke", "black")
      .attr("stroke-width", 3);
    
    // Add legend
    const legend = svg.append("g")
      .attr("transform", `translate(${width - margin.right - 200}, ${margin.top + 20})`);
      
    const legendItems = [
      { label: "Population distribution", color: "red", type: "line" },
      { label: "Population mean", color: "red", type: "dashed" },
      { label: "Sample mean", color: "blue", type: "dashed" },
      { label: "95% CI", color: "black", type: "line" }
    ];
    
    legendItems.forEach((item, i) => {
      legend.append("line")
        .attr("x1", 0)
        .attr("x2", 30)
        .attr("y1", i * 25)
        .attr("y2", i * 25)
        .attr("stroke", item.color)
        .attr("stroke-width", 2)
        .attr("stroke-dasharray", item.type === "dashed" ? "5,5" : null);
        
      legend.append("text")
        .attr("x", 40)
        .attr("y", i * 25 + 5)
        .text(item.label);
    });
    
    return svg.node();
  });

  // Helper function for normal random numbers
  main.variable(observer("normrnd")).define("normrnd", ["d3"], d3 => {
    return (mean, sd, n) => {
      const normal = d3.randomNormal(mean, sd);
      return Array.from({length: n}, normal);
    };
  });
  
  // Add normal PDF calculation
  d3.randomNormal.pdf = (x, mean, sd) => {
    return (1 / (sd * Math.sqrt(2 * Math.PI))) * 
           Math.exp(-0.5 * Math.pow((x - mean) / sd, 2));
  };
  
  return main;
}