1
+ <!DOCTYPE html>
2
+ < html >
3
+
4
+ < head >
5
+ < meta charset ="""utf-8">
6
+ < title > Compare 2 samples</ title >
7
+ < script src ="https://cdn.plot.ly/plotly-2.24.1.min.js "> </ script >
8
+ < script src ="""https://cdn.jsdelivr.net/npm/d3@7"> </ script >
9
+ < style >
10
+ body {
11
+ font-family : system-ui, sans-serif;
12
+ max-width : 1100px ;
13
+ /* Reduced from 1200px */
14
+ margin : 0 auto;
15
+ padding : 5px ;
16
+ /* Reduced from 10px */
17
+ font-size : 14px ;
18
+ line-height : 1.2 ;
19
+ }
20
+
21
+ .plot-container {
22
+ display : flex;
23
+ gap : 5px ;
24
+ /* Reduced from 10px */
25
+ margin-bottom : 0.5rem ;
26
+ }
27
+
28
+ .plot-box {
29
+ flex : 1 ;
30
+ }
31
+
32
+ .plot-box p {
33
+ margin : 0.2em 0 ;
34
+ }
35
+
36
+ .controls {
37
+ margin : 0.5rem 0 ;
38
+ display : flex;
39
+ gap : 0.5rem ;
40
+ align-items : center;
41
+ font-size : 13px ;
42
+ }
43
+
44
+ .radio-group {
45
+ display : flex;
46
+ gap : 0.3rem ;
47
+ flex-wrap : wrap;
48
+ }
49
+
50
+ button {
51
+ padding : 4px 8px ;
52
+ font-size : 13px ;
53
+ }
54
+
55
+ # sampling-message {
56
+ font-size : 13px ;
57
+ margin-top : 0.2em ;
58
+ }
59
+
60
+ select {
61
+ padding : 2px 4px ;
62
+ font-size : 13px ;
63
+ margin : 0 4px ;
64
+ }
65
+ </ style >
66
+ </ head >
67
+
68
+ < body >
69
+ < div id ="app ">
70
+ < div class ="controls ">
71
+ < div style ="display: flex; gap: 1rem; ">
72
+ < div >
73
+ < label for ="sample-size-1 "> Pop 1 Sample: </ label >
74
+ < select id ="sample-size-1 ">
75
+ < option value ="10 "> 10</ option >
76
+ < option value ="100 "> 100</ option >
77
+ < option value ="1000 "> 1000</ option >
78
+ </ select >
79
+ </ div >
80
+ < div >
81
+ < label for ="sample-size-2 "> Pop 2 Sample: </ label >
82
+ < select id ="sample-size-2 ">
83
+ < option value ="10 "> 10</ option >
84
+ < option value ="100 "> 100</ option >
85
+ < option value ="1000 "> 1000</ option >
86
+ </ select >
87
+ </ div >
88
+ </ div >
89
+ < div >
90
+ < label > Confidence Level: </ label >
91
+ < div class ="radio-group " id ="confidence-levels "> </ div >
92
+ </ div >
93
+ < button id ="take-samples "> Take 1000 Samples</ button >
94
+ </ div >
95
+ < div class ="plot-container ">
96
+ < div class ="plot-box ">
97
+ < div id ="population-plot "> </ div >
98
+ < div id ="population-stats "> </ div >
99
+ </ div >
100
+ < div class ="plot-box ">
101
+ < div id ="sampling-plot "> </ div >
102
+ < div id ="sample-stats "> </ div >
103
+ </ div >
104
+ </ div >
105
+ < div id ="sampling-message "> </ div >
106
+ </ div >
107
+
108
+ < script >
109
+ const CONFIDENCE_LEVELS = {
110
+ 60 : 0.841 ,
111
+ 70 : 1.036 ,
112
+ 80 : 1.28 ,
113
+ 85 : 1.44 ,
114
+ 90 : 1.645 ,
115
+ 95 : 1.96 ,
116
+ 99 : 2.576 ,
117
+ 99.9 : 3.291
118
+ } ;
119
+
120
+ // Generate population data
121
+ function generatePopulation ( size = 10000 , mean = 120 , std = 10 ) {
122
+ return Array . from ( { length : size } , ( ) => {
123
+ let u1 , u2 ;
124
+ do {
125
+ u1 = Math . random ( ) ;
126
+ u2 = Math . random ( ) ;
127
+ } while ( u1 === 0 ) ;
128
+
129
+ const z = Math . sqrt ( - 2.0 * Math . log ( u1 ) ) * Math . cos ( 2.0 * Math . PI * u2 ) ;
130
+ return Math . round ( mean + z * std ) ;
131
+ } ) ;
132
+ }
133
+
134
+ // Initialize state with two populations and sample sizes
135
+ const state = {
136
+ population1 : generatePopulation ( 10000 , 120 , 10 ) ,
137
+ population2 : generatePopulation ( 10000 , 130 , 10 ) ,
138
+ sampleMeans : [ ] ,
139
+ sampleSize1 : 10 ,
140
+ sampleSize2 : 10 ,
141
+ confidenceLevel : 95
142
+ } ;
143
+
144
+ // Calculate population statistics
145
+ function calculatePopulationStats ( population ) {
146
+ const mean = population . reduce ( ( a , b ) => a + b , 0 ) / population . length ;
147
+ const squaredDiffs = population . map ( x => Math . pow ( x - mean , 2 ) ) ;
148
+ const variance = squaredDiffs . reduce ( ( a , b ) => a + b , 0 ) / population . length ;
149
+ const std = Math . sqrt ( variance ) ;
150
+ return { mean, std } ;
151
+ }
152
+
153
+ // Setup UI for both sample size controls
154
+ document . getElementById ( 'sample-size-1' ) . addEventListener ( 'change' , ( e ) => {
155
+ state . sampleSize1 = parseInt ( e . target . value ) ;
156
+ } ) ;
157
+
158
+ document . getElementById ( 'sample-size-2' ) . addEventListener ( 'change' , ( e ) => {
159
+ state . sampleSize2 = parseInt ( e . target . value ) ;
160
+ } ) ;
161
+
162
+ // Setup confidence level radio buttons
163
+ const confidenceLevelsDiv = document . getElementById ( 'confidence-levels' ) ;
164
+ Object . keys ( CONFIDENCE_LEVELS ) . sort ( ( a , b ) => a - b ) . forEach ( level => {
165
+ const radio = document . createElement ( 'input' ) ;
166
+ radio . type = 'radio' ;
167
+ radio . id = `cl-${ level } ` ;
168
+ radio . name = 'confidence-level' ;
169
+ radio . value = level ;
170
+ radio . checked = level == 95 ;
171
+
172
+ const label = document . createElement ( 'label' ) ;
173
+ label . htmlFor = `cl-${ level } ` ;
174
+ label . textContent = `${ level } %` ;
175
+
176
+ confidenceLevelsDiv . appendChild ( radio ) ;
177
+ confidenceLevelsDiv . appendChild ( label ) ;
178
+ } ) ;
179
+
180
+ document . getElementById ( 'take-samples' ) . addEventListener ( 'click' , ( ) => {
181
+ const newMeans = Array . from ( { length : 1000 } , ( ) => {
182
+ const sample1 = Array . from ( { length : state . sampleSize1 } , ( ) =>
183
+ state . population1 [ Math . floor ( Math . random ( ) * state . population1 . length ) ]
184
+ ) ;
185
+ const sample2 = Array . from ( { length : state . sampleSize2 } , ( ) =>
186
+ state . population2 [ Math . floor ( Math . random ( ) * state . population2 . length ) ]
187
+ ) ;
188
+ const mean1 = sample1 . reduce ( ( a , b ) => a + b , 0 ) / sample1 . length ;
189
+ const mean2 = sample2 . reduce ( ( a , b ) => a + b , 0 ) / sample2 . length ;
190
+ return mean2 - mean1 ; // return difference in means
191
+ } ) ;
192
+ state . sampleMeans = newMeans ;
193
+ updatePlots ( ) ;
194
+ } ) ;
195
+
196
+ function updatePlots ( ) {
197
+ const popStats1 = calculatePopulationStats ( state . population1 ) ;
198
+ const popStats2 = calculatePopulationStats ( state . population2 ) ;
199
+
200
+ // Population plots
201
+ const populationTrace1 = {
202
+ x : state . population1 ,
203
+ type : 'histogram' ,
204
+ name : 'Population 1 (120mmHg)' ,
205
+ nbinsx : 40 ,
206
+ marker : {
207
+ color : 'rgba(136, 132, 216, 0.6)' // light purple
208
+ }
209
+ } ;
210
+
211
+ const populationTrace2 = {
212
+ x : state . population2 ,
213
+ type : 'histogram' ,
214
+ name : 'Population 2 (130mmHg)' ,
215
+ nbinsx : 40 ,
216
+ marker : {
217
+ color : 'rgba(130, 202, 157, 0.6)' // light green
218
+ }
219
+ } ;
220
+
221
+ const populationLayout = {
222
+ title : {
223
+ text : 'Two Population Distributions (N=10,000 each)' ,
224
+ font : { size : 14 }
225
+ } ,
226
+ xaxis : {
227
+ title : { text : 'Blood Pressure (mmHg)' , font : { size : 12 } } ,
228
+ range : [ 80 , 170 ] ,
229
+ tickfont : { size : 11 }
230
+ } ,
231
+ yaxis : {
232
+ title : { text : 'Count' , font : { size : 12 } } ,
233
+ tickfont : { size : 11 }
234
+ } ,
235
+ width : 450 , /* Reduced from 500 */
236
+ height : 300 ,
237
+ margin : { t : 30 , r : 20 , l : 40 , b : 40 } ,
238
+ showlegend : true ,
239
+ legend : {
240
+ orientation : 'h' ,
241
+ y : - 0.2 /* Adjusted from -0.3 */
242
+ }
243
+ } ;
244
+
245
+ Plotly . newPlot ( 'population-plot' , [ populationTrace1 , populationTrace2 ] , populationLayout ) ;
246
+
247
+ // Display population statistics
248
+ document . getElementById ( 'population-stats' ) . innerHTML = `
249
+ <p>Population 1 Mean: ${ popStats1 . mean . toFixed ( 2 ) } mmHg</p>
250
+ <p>Population 2 Mean: ${ popStats2 . mean . toFixed ( 2 ) } mmHg</p>
251
+ <p>True Difference: ${ ( popStats2 . mean - popStats1 . mean ) . toFixed ( 2 ) } mmHg</p>
252
+ ` ;
253
+
254
+ // Update sampling distribution plot if we have samples
255
+ if ( state . sampleMeans . length > 0 ) {
256
+ const sampleStats = calculatePopulationStats ( state . sampleMeans ) ;
257
+ const selectedConfidenceLevel = parseInt ( document . querySelector ( 'input[name="confidence-level"]:checked' ) . value ) ;
258
+ const criticalValue = CONFIDENCE_LEVELS [ selectedConfidenceLevel ] ;
259
+
260
+ // Fixed standard error calculation for difference in means
261
+ const se1 = Math . pow ( popStats1 . std , 2 ) / state . sampleSize1 ;
262
+ const se2 = Math . pow ( popStats2 . std , 2 ) / state . sampleSize2 ;
263
+ const standardError = Math . sqrt ( se1 + se2 ) ;
264
+ const marginOfError = criticalValue * standardError ;
265
+
266
+ const trueDiff = popStats2 . mean - popStats1 . mean ;
267
+ const lowerCI = trueDiff - marginOfError ;
268
+ const upperCI = trueDiff + marginOfError ;
269
+
270
+ // Count samples outside CI
271
+ const samplesOutsideCI = state . sampleMeans . filter ( diff =>
272
+ diff < lowerCI || diff > upperCI
273
+ ) . length ;
274
+
275
+ const samplingTrace = {
276
+ x : state . sampleMeans ,
277
+ type : 'histogram' ,
278
+ name : 'Sample Mean Differences' ,
279
+ nbinsx : 30 ,
280
+ marker : {
281
+ color : 'rgba(255, 127, 80, 0.7)' // coral
282
+ }
283
+ } ;
284
+
285
+ const samplingLayout = {
286
+ title : {
287
+ text : 'Sampling Distribution of Mean Differences' ,
288
+ font : { size : 14 }
289
+ } ,
290
+ xaxis : {
291
+ title : { text : 'Difference in Sample Means (mmHg)' , font : { size : 12 } } ,
292
+ range : [ 0 , 20 ] ,
293
+ tickfont : { size : 11 }
294
+ } ,
295
+ yaxis : {
296
+ title : { text : 'Count' , font : { size : 12 } } ,
297
+ tickfont : { size : 11 }
298
+ } ,
299
+ width : 450 , /* Reduced from 500 */
300
+ height : 300 ,
301
+ margin : { t : 30 , r : 20 , l : 40 , b : 40 } ,
302
+ shapes : [
303
+ // True difference line
304
+ {
305
+ type : 'line' ,
306
+ x0 : trueDiff ,
307
+ x1 : trueDiff ,
308
+ y0 : 0 ,
309
+ y1 : 1 ,
310
+ yref : 'paper' ,
311
+ line : {
312
+ color : 'red' ,
313
+ width : 2
314
+ }
315
+ } ,
316
+ // Lower CI line
317
+ {
318
+ type : 'line' ,
319
+ x0 : lowerCI ,
320
+ x1 : lowerCI ,
321
+ y0 : 0 ,
322
+ y1 : 1 ,
323
+ yref : 'paper' ,
324
+ line : {
325
+ color : 'blue' ,
326
+ width : 2 ,
327
+ dash : 'dash'
328
+ }
329
+ } ,
330
+ // Upper CI line
331
+ {
332
+ type : 'line' ,
333
+ x0 : upperCI ,
334
+ x1 : upperCI ,
335
+ y0 : 0 ,
336
+ y1 : 1 ,
337
+ yref : 'paper' ,
338
+ line : {
339
+ color : 'blue' ,
340
+ width : 2 ,
341
+ dash : 'dash'
342
+ }
343
+ }
344
+ ]
345
+ } ;
346
+
347
+ Plotly . newPlot ( 'sampling-plot' , [ samplingTrace ] , samplingLayout ) ;
348
+
349
+ // Update sample statistics
350
+ document . getElementById ( 'sample-stats' ) . innerHTML = `
351
+ <p>Mean Difference: ${ sampleStats . mean . toFixed ( 2 ) } mmHg</p>
352
+ <p>Standard Error: ${ sampleStats . std . toFixed ( 2 ) } mmHg</p>
353
+ <p>${ selectedConfidenceLevel } % CI: ${ lowerCI . toFixed ( 2 ) } to ${ upperCI . toFixed ( 2 ) } mmHg</p>
354
+ <p>Samples outside CI: ${ samplesOutsideCI } </p>
355
+ ` ;
356
+
357
+ document . getElementById ( 'sampling-message' ) . innerHTML = `
358
+ <p>Took ${ state . sampleMeans . length } samples (Population 1 size: ${ state . sampleSize1 } , Population 2 size: ${ state . sampleSize2 } )</p>
359
+ ` ;
360
+ }
361
+ }
362
+
363
+ // Initial plot
364
+ updatePlots ( ) ;
365
+ </ script >
366
+ </ body >
367
+
368
+ </ html >
0 commit comments