UNPKG

16.8 kBJavaScriptView Raw
1var chiSquared = require('chi-squared');
2var decimalAdjust = require('decimal-adjust');
3
4var z = 1.959964;
5
6(function(){
7 if (!Math.round10) {
8 Math.round10 = function(value, exp) {
9 return decimalAdjust('round', value, exp);
10 };
11 }
12 // Decimal floor
13 if (!Math.floor10) {
14 Math.floor10 = function(value, exp) {
15 return decimalAdjust('floor', value, exp);
16 };
17 }
18 // Decimal ceil
19 if (!Math.ceil10) {
20 Math.ceil10 = function(value, exp) {
21 return decimalAdjust('ceil', value, exp);
22 };
23 }
24})();
25
26function getDiagnosticTest(testValues) {
27 var nr1, nc1,
28 nr2, nc2;
29
30 var bigN;
31
32 var sensitivity, sensitivityConfidence;
33 var specificity, specificityConfidence;
34 var ppv, ppvConfidence;
35 var npv, npvConfidence;
36 var lrPlusLowerLimit, lrPlusUpperLimit;
37 var lrMinusLowerLimit, lrMinusUpperLimit;
38
39 var a = testValues.testPositiveDisease;
40 var b = testValues.testPositiveNoDisease;
41 var c = testValues.testNegativeDisease;
42 var d = testValues.testNegativeNoDisease;
43
44 var lrPlus = testValues.lrPlus;
45 var lrNegative = testValues.lrMinus;
46
47 nr1 = a + b;
48 nc1 = a + c;
49 nc2 = b + d;
50 nr2 = c + d;
51
52 bigN = a + b + c + d;
53
54 if (nc1 === 0 || nc2 === 0 || nr1 === 0 || nr2 === 0) {
55 return false;
56 }
57
58 sensitivity = a / nc1;
59 sensitivityConfidence = _getDiagnosticTestConfidence(a, c, nc1);
60
61 if (typeof sensitivity !== 'number' || isNaN(sensitivity) || typeof sensitivityConfidence !== "object" || sensitivityConfidence === null) {
62 return false;
63 }
64
65 specificity = d / nc2;
66 specificityConfidence = _getDiagnosticTestConfidence(d, b, nc2);
67
68 if (typeof specificity !== 'number' || isNaN(specificity) || typeof specificityConfidence !== "object" || specificityConfidence === null) {
69 return false;
70 }
71
72 ppv = a / nr1;
73 ppvConfidence = _getDiagnosticTestConfidence(a, b, nr1);
74
75 if (typeof ppv !== 'number' || isNaN(ppv) || typeof ppvConfidence !== "object" || ppvConfidence === null) {
76 return false;
77 }
78
79 npv = d / nr2;
80 npvConfidence = _getDiagnosticTestConfidence(d, c, nr2);
81
82 if (typeof ppv !== 'number' || isNaN(ppv) || typeof ppvConfidence !== "object" || ppvConfidence === null) {
83 return false;
84 }
85
86 if ((1 - specificity) === 0 || specificity === 0 || nc1 * b === 0 || a * nc1 === 0 || b * nc2 === 0 || nc1 * d === 0 || c * nc1 === 0 || d * nc2 === 0) {
87 return false;
88 }
89
90 lrPlus = sensitivity / (1 - specificity);
91 lrPlusLowerLimit = Math.exp(Math.log((nc2 * a) / (nc1 * b)) - (z * Math.sqrt((c / (a * nc1)) + (d / (b * nc2)))));
92 lrPlusUpperLimit = Math.exp(Math.log((nc2 * a) / (nc1 * b)) + (z * Math.sqrt((c / (a * nc1)) + (d / (b * nc2)))));
93
94 if (typeof lrPlus !== 'number' || isNaN(lrPlus)) {
95 return false;
96 }
97
98 if (typeof lrPlusLowerLimit !== 'number' || isNaN(lrPlusLowerLimit)) {
99 return false;
100 }
101
102 if (typeof lrPlusUpperLimit !== 'number' || isNaN(lrPlusUpperLimit)) {
103 return false;
104 }
105
106 lrMinus = (1 - sensitivity) / specificity;
107 lrMinusLowerLimit = Math.exp(Math.log((nc2 * c) / (nc1 * d)) - (z * Math.sqrt((a / (c * nc1)) + (b / (d * nc2)))));
108 lrMinusUpperLimit = Math.exp(Math.log((nc2 * c) / (nc1 * d)) + (z * Math.sqrt((a / (c * nc1)) + (b / (d * nc2)))));
109
110 if (typeof lrMinus !== 'number' || isNaN(lrMinus)) {
111 return false;
112 }
113
114 if (typeof lrMinusLowerLimit !== 'number' || isNaN(lrMinusLowerLimit)) {
115 return false;
116 }
117
118 if (typeof lrMinusUpperLimit !== 'number' || isNaN(lrMinusUpperLimit)) {
119 return false;
120 }
121
122 return {
123 'graph': true,
124 'sensitivity': Math.round10(sensitivity, -3),
125 'sensitivityLowerLimit': Math.round10(sensitivityConfidence['lower'], -3),
126 'sensitivityUpperLimit': Math.round10(sensitivityConfidence['upper'], -3),
127 'specificity': Math.round10(specificity, -3),
128 'specificityLowerLimit': Math.round10(specificityConfidence['lower'], -3),
129 'specificityUpperLimit': Math.round10(specificityConfidence['upper'], -3),
130 'ppv': Math.round10(ppv, -3),
131 'ppvLowerLimit': Math.round10(ppvConfidence['lower'], -3),
132 'ppvUpperLimit': Math.round10(ppvConfidence['upper'], -3),
133 'npv': Math.round10(npv, -3),
134 'npvLowerLimit': Math.round10(npvConfidence['lower'], -3),
135 'npvUpperLimit': Math.round10(npvConfidence['upper'], -3),
136 'lrPlus': Math.round10(lrPlus, -3),
137 'lrPlusLowerLimit': Math.round10(lrPlusLowerLimit, -3),
138 'lrPlusUpperLimit': Math.round10(lrPlusUpperLimit, -3),
139 'lrMinus': Math.round10(lrMinus, -3),
140 'lrMinusLowerLimit': Math.round10(lrMinusLowerLimit, -3),
141 'lrMinusUpperLimit': Math.round10(lrMinusUpperLimit, -3)
142 };
143}
144
145function getProspectiveStudy(testValues) {
146 var a = testValues.treatedDisease;
147 var b = testValues.treatedNoDisease;
148 var c = testValues.notTreatedDisease;
149 var d = testValues.notTreatedNoDisease;
150
151 var chiSquared = _getChiSquared(a, b, c, d);
152
153 var pValue;
154 var rr, rrNumerator, rrDenominator, rrrLowerLimit, rrrUpperLimit;
155
156 var arr, arrConfidence;
157 var nnt, nntConfidence;
158
159 if (typeof chiSquared !== 'number' || isNaN(chiSquared)) {
160 return false;
161 }
162
163 pValue = _getPValue(chiSquared);
164
165 if (typeof pValue !== 'number' || isNaN(pValue)) {
166 return false;
167 }
168
169 rrNumerator = a * (c + d);
170 rrDenominator = c * (a + b);
171
172 if (typeof rrDenominator !== 'number' || isNaN(rrDenominator)) {
173 return false;
174 }
175
176 rr = rrNumerator / rrDenominator;
177
178 if (typeof rr !== 'number' || isNaN(rr)) {
179 return false;
180 }
181
182 if (c === 0 || (c + d) === 0 || a === 0 || a + b === 0) {
183 return false
184 }
185
186 rrLowerLimit = Math.exp(Math.log(rr) - (z * Math.sqrt((1 / c) - (1 / (c + d)) + (1 / a) - (1 / (a + b)))));
187 rrUpperLimit = Math.exp(Math.log(rr) + (z * Math.sqrt((1 / c) - (1 / (c + d)) + (1 / a) - (1 / (a + b)))));
188
189 if (typeof rrLowerLimit !== 'number' || isNaN(rrLowerLimit)) {
190 return false;
191 }
192 if (typeof rrUpperLimit !== 'number' || isNaN(rrUpperLimit)) {
193 return false;
194 }
195
196 arr = _getArr(a, b, c, d);
197 arrConfidence = _getArrConfidence(arr, a, b, c, d);
198
199 if (typeof arr !== 'number' || isNaN(arr)) {
200 return false;
201 }
202
203 if (typeof arrConfidence !== 'object' || arrConfidence === null) {
204 return false;
205 }
206
207 nnt = _getNnt(arr);
208 nntConfidence = _getNntConfidence(arrConfidence);
209
210 if (typeof nnt !== 'number' || isNaN(nnt)) {
211 return false;
212 }
213
214 if (typeof nntConfidence !== 'object' || nntConfidence === null) {
215 return false;
216 }
217
218 return {
219 'chiSquared': Math.round10(chiSquared, -3),
220 'pValue': Math.round10(pValue, -3),
221 'rr': Math.round10(rr, -3),
222 'rrLowerLimit': Math.round10(rrLowerLimit, -3),
223 'rrUpperLimit': Math.round10(rrUpperLimit, -3),
224 'arr': Math.round10(arr, -3),
225 'arrLowerLimit': Math.round10(arrConfidence['lower'], -3),
226 'arrUpperLimit': Math.round10(arrConfidence['upper'], -3),
227 'nnt': Math.round10(nnt, -3),
228 'nntLowerLimit': Math.round10(nntConfidence['lower'], -3),
229 'nntUpperLimit': Math.round10(nntConfidence['upper'], -3)
230 };
231}
232
233function getCaseControlStudy(testValues) {
234 var a = testValues.caseExposed;
235 var b = testValues.caseNotExposed;
236 var c = testValues.controlExposed;
237 var d = testValues.controlNotExposed;
238
239 var chiSquared = _getChiSquared(a, b, c, d);
240
241 var pValue;
242 var orNumerator, orDenominator, or;
243 var orLowerLimit, orUpperLimit;
244
245 if (typeof chiSquared !== 'number' || isNaN(chiSquared)) {
246 return false;
247 }
248
249 pValue = _getPValue(chiSquared);
250
251 if (typeof pValue !== 'number' || isNaN(pValue)) {
252 return false;
253 }
254
255 orNumerator = a * d;
256 orDenominator = b * c;
257
258 if (orDenominator === 0) {
259 return false;
260 }
261
262 or = orNumerator / orDenominator;
263
264 if (typeof or !== 'number' || isNaN(or)) {
265 return false;
266 }
267
268 if (a === 0 || b === 0 || c === 0 || d === 0) {
269 return false;
270 }
271
272 orLowerLimit = Math.exp(Math.log(or) - (z * Math.sqrt((1 / a) + (1 / b) + (1 / c) + (1 / d))));
273 orUpperLimit = Math.exp(Math.log(or) + (z * Math.sqrt((1 / a) + (1 / b) + (1 / c) + (1/d))));
274
275 if (typeof orLowerLimit !== 'number' || isNaN(orLowerLimit)) {
276 return false;
277 }
278
279 if (typeof orUpperLimit !== 'number' || isNaN(orUpperLimit)) {
280 return false;
281 }
282
283 return {
284 "chiSquared": Math.round10(chiSquared, -3),
285 "pValue": Math.round10(pValue, -3),
286 "or": Math.round10(or, -3),
287 "orLowerLimit": Math.round10(orLowerLimit, -3),
288 "orUpperLimit": Math.round10(orUpperLimit, -3)
289 };
290}
291
292function getRct(testValues) {
293 var a = testValues.experimentalOutcome;
294 var b = testValues.experimentalNoOutcome;
295 var c = testValues.controlOutcome;
296 var d = testValues.controlNoOutcome;
297
298 var chiSquared = _getChiSquared(a, b, c, d);
299 var pValue;
300 var rrr, rrrNumerator, rrrDenominator;
301 var rrrLowerLimit, rrrUpperLimit;
302 var arr, arrConfidence;
303 var nnt, nntConfidence;
304
305 if (typeof chiSquared !== 'number' || isNaN(chiSquared)) {
306 return false;
307 }
308
309 pValue = _getPValue(chiSquared);
310
311 if (typeof pValue !== 'number' || isNaN(pValue)) {
312 return false;
313 }
314
315 if ((c + d) === 0 || (a + b) === 0) {
316 return false;
317 }
318
319 rrrNumerator = (c / (c + d)) - (a / (a + b));
320 rrrDenominator = c / (c + d);
321
322 if (rrrDenominator === 0) {
323 return false;
324 }
325
326 rrr = rrrNumerator / rrrDenominator;
327
328 if (typeof rrr !== 'number' || isNaN(rrr)) {
329 return false;
330 }
331
332 if ((c * (a + b)) === 0 || c === 0 || (c + d) === 0 || a === 0 || (a + b) === 0) {
333 return false;
334 }
335
336 rrrLowerLimit = 1 - (Math.exp(Math.log((a * (c+d)) / (c * (a + b))) + z * Math.sqrt((1 / c) - (1 / (c + d)) + (1 / a) - (1 / (a + b)))));
337 rrrUpperLimit = 1 - (Math.exp(Math.log((a * (c+d)) / (c * (a + b))) - z * Math.sqrt((1 / c) - (1 / (c + d)) + (1 / a) - (1 / (a + b)))));
338
339 if (typeof rrrLowerLimit !== 'number' || isNaN(rrrLowerLimit)) {
340 return false;
341 }
342
343 if (typeof rrrUpperLimit !== 'number' || isNaN(rrrUpperLimit)) {
344 return false;
345 }
346
347 arr = _getArr(a, b, c, d);
348 arrConfidence = _getArrConfidence(arr, a, b, c, d);
349
350 if (typeof arr !== 'number' || isNaN(arr)) {
351 return false;
352 }
353
354 if (typeof arrConfidence !== 'object' || arrConfidence === null) {
355 return false;
356 }
357
358 nnt = _getNnt(arr);
359 nntConfidence = _getNntConfidence(arrConfidence);
360
361 if (typeof nnt !== 'number' || isNaN(nnt)) {
362 return false;
363 }
364
365 if (typeof nntConfidence !== 'object' || nntConfidence === null) {
366 return false;
367 }
368
369 return {
370 "chiSquared": Math.round10(chiSquared, -3),
371 "pValue": Math.round10(pValue, -3),
372 "rrr": Math.round10(rrr, -3),
373 "rrrLowerLimit": Math.round10(rrrLowerLimit, -3),
374 "rrrUpperLimit": Math.round10(rrrUpperLimit, -3),
375 "arr": Math.round10(arr, -3),
376 "arrLowerLimit": Math.round10(arrConfidence['lower'], -3),
377 "arrUpperLimit": Math.round10(arrConfidence['upper'], -3),
378 "nnt": Math.round10(nnt, -3),
379 "nntLowerLimit": Math.round10(nntConfidence['lower'], -3),
380 "nntUpperLimit": Math.round10(nntConfidence['upper'], -3)
381 };
382}
383
384function getCoordinatesOfCurve(lr, canvas) {
385 var points = [];
386
387 var pretestProb = 0;
388
389 //y = L*​x/​((L-​1)*​x+​1)
390
391 while (pretestProb <= 1) {
392 pretestProb = pretestProb + 0.001;
393
394 pretestOdds = 0;
395 if (pretestProb != 1) {
396 pretestOdds = pretestProb / (1 - pretestProb);
397 }
398
399 posttestProbNumerator = pretestOdds * lr;
400 posttestProbDenominator = 1 + (pretestOdds * lr);
401
402 posttestProb = 0;
403 if (posttestProbDenominator != 0) {
404 posttestProb = posttestProbNumerator / posttestProbDenominator;
405 }
406
407 x = pretestProb * canvas.width;
408 y = canvas.height - posttestProb * canvas.height;
409
410 var point = {
411 "x": x,
412 "y": y
413 };
414
415 points.push(point);
416 }
417 return points;
418}
419
420function _getDiagnosticTestConfidence(y, t, n) {
421 var lowerNumerator, upperNumerator, denominator;
422 var newUpper, newLower;
423
424 if (typeof y !== 'number' || isNaN(y)) {
425 return false;
426 }
427 if (typeof t !== 'number' || isNaN(t)) {
428 return false;
429 }
430 if (typeof n !== 'number' || isNaN(n)) {
431 return false;
432 }
433
434 if (n === 0) {
435 return false;
436 }
437
438 lowerNumerator = (2 * y) + Math.pow(z, 2) - (z * Math.sqrt((4 * y * t / n) + Math.pow(z, 2)));
439 upperNumerator = (2 * y) + Math.pow(z, 2) + (z * Math.sqrt((4 * y * t / n) + Math.pow(z, 2)));
440 denominator = (2 * n) + (2 * Math.pow(z, 2));
441
442 if (denominator === 0) {
443 return false;
444 }
445
446 newUpper = upperNumerator / denominator;
447 newLower = lowerNumerator / denominator;
448
449 if (typeof newLower !== 'number' || isNaN(newLower)) {
450 return false;
451 }
452 if (typeof newUpper !== 'number' || isNaN(newUpper)) {
453 return false;
454 }
455
456 return {
457 "upper": newUpper,
458 "lower": newLower
459 };
460}
461
462function _getChiSquared(a, b, c, d) {
463 var chiSquaredNumerator;
464 var chiSquaredDenominator;
465 var chiSquared;
466
467 var bigN = a + b + c + d;
468
469 var nr1 = a + b;
470 var nc1 = a + c;
471 var nc2 = b + d;
472 var nr2 = c + d;
473
474 chiSquaredNumerator = bigN * Math.pow((Math.abs((a * d) - (b * c)) - (bigN / 2)), 2);
475 chiSquaredDenominator = nr1 * nr2 * nc1 * nc2;
476
477 chiSquared = chiSquaredNumerator / chiSquaredDenominator;
478
479 if (typeof chiSquared !== 'number' || isNaN(chiSquared)) {
480 return false;
481 }
482
483 return chiSquared;
484}
485
486function _getPValue(chiVal) {
487 if (typeof chiVal !== 'number' || isNaN(chiVal)) {
488 return false;
489 }
490
491 return 1 - chiSquared.cdf(chiVal, 1);
492}
493
494function _getArr(a, b, c, d) {
495 var arr;
496
497 if (c + d === 0) {
498 return false;
499 }
500 if (a + b === 0) {
501 return false;
502 }
503
504 arr = (c / (c + d)) - (a / (a + b));
505
506 if (typeof arr !== 'number' || isNaN(arr)) {
507 return false;
508 }
509
510 return arr;
511}
512
513function _getArrConfidence(arr, a, b, c, d) {
514 var u1Numerator, u1Denominator, u1;
515 var u2Numerator, u2Denominator, u2;
516 var w1Numerator, w1Denominator, w1;
517 var w2Numerator, w2Denominator, w2;
518
519 var nr1 = a + b;
520 var nc1 = a + c;
521 var nc2 = b + d;
522 var nr2 = c + d;
523
524 var newUpper, newLower;
525
526 if (typeof arr !== 'number' || isNaN(arr)) {
527 return false;
528 }
529
530 if (nr1 === 0 || nr2 === 0) {
531 return false;
532 }
533
534 u1Numerator = (2 * c) + Math.pow(z, 2) + (z * Math.sqrt((4 * c * d / nr2) + Math.pow(z, 2)));
535 u1Denominator = (2 * nr2) + (2 * Math.pow(z, 2));
536
537 if (u1Denominator === 0) {
538 return false;
539 }
540
541 u1 = u1Numerator / u1Denominator;
542
543 u2Numerator = (2 * a) + Math.pow(z, 2) + (z * Math.sqrt((4 * a * b / nr1) + Math.pow(z, 2)));
544 u2Denominator = (2 * nr1) + (2 * Math.pow(z, 2));
545
546 if (u2Denominator === 0) {
547 return false;
548 }
549
550 u2 = u2Numerator / u2Denominator;
551
552 w1Numerator = (2 * c) + Math.pow(z, 2) - (z * Math.sqrt((4 * c * d / nr2) + Math.pow(z, 2)));
553 w1Denominator = (2 * nr2) + (2 * Math.pow(z, 2));
554
555 if (w1Denominator === 0) {
556 return false;
557 }
558
559 w1 = w1Numerator / w1Denominator;
560
561 w2Numerator = (2 * a) + Math.pow(z, 2) - (z * Math.sqrt((4 * a * b / nr1) + Math.pow(z, 2)));
562 w2Denominator = (2 * nr1) + (2 * Math.pow(z, 2));
563
564 if (w2Denominator === 0) {
565 return false;
566 }
567
568 w2 = w2Numerator / w2Denominator;
569
570 newLower = arr - (z * Math.sqrt((u2 * (1 - u2) / (a + b)) + (w1 * (1 - w1) / (c + d))));
571 newUpper = arr + (z * Math.sqrt((u1 * (1 - u1) / (c + d)) + (w2 * (1 - w2) / (a + b))));
572
573
574 if (typeof newLower !== 'number' || isNaN(newLower)) {
575 return false;
576 }
577
578 if (typeof newUpper !== 'number' || isNaN(newUpper)) {
579 return false;
580 }
581
582 return {
583 "lower": newLower,
584 "upper": newUpper
585 };
586}
587
588function _getNnt(arr) {
589 var nnt;
590
591 if (typeof arr !== 'number' || isNaN(arr)) {
592 return false;
593 }
594
595 if (arr === 0) {
596 return false;
597 }
598
599 nnt = Math.floor10(1 / arr);
600
601 if (typeof nnt !== 'number' || isNaN(nnt)) {
602 return false;
603 }
604
605 return nnt;
606}
607
608function _getNntConfidence(arrConfidence) {
609 var newUpper, newLower;
610
611 if (typeof arrConfidence !== "object" || arrConfidence === null) {
612 return false;
613 }
614
615 if (typeof arrConfidence['upper'] !== 'number' || isNaN(arrConfidence['upper'])) {
616 return false;
617 }
618
619 if (typeof arrConfidence['lower'] !== 'number' || isNaN(arrConfidence['lower'])) {
620 return false;
621 }
622
623 if (arrConfidence['upper'] === 0) {
624 return false;
625 }
626
627 if (arrConfidence['lower'] === 0) {
628 return false;
629 }
630
631 newUpper = Math.round10((1 / arrConfidence['upper']), -1);
632 newLower = Math.round10((1 / arrConfidence['lower']), -1);
633
634 if (typeof newUpper !== 'number' || isNaN(newUpper)) {
635 return false;
636 }
637
638 if (typeof newLower !== 'number' || isNaN(newLower)) {
639 return false;
640 }
641
642 return {
643 "lower": newLower,
644 "upper": newUpper
645 };
646}
647
648module.exports = {
649 getDiagnosticTest: getDiagnosticTest,
650 getCaseControlStudy: getCaseControlStudy,
651 getRct: getRct,
652 getProspectiveStudy: getProspectiveStudy,
653 getCoordinatesOfCurve: getCoordinatesOfCurve
654}
\No newline at end of file