Skip to content

Commit 262e34a

Browse files
committed
WIP: add labkar functions
1 parent 335b69a commit 262e34a

File tree

4 files changed

+236
-8
lines changed

4 files changed

+236
-8
lines changed

labkar/analyte.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
export default class Analyte {
2+
3+
constructor(analyte) {
4+
this.analyte = analyte;
5+
}
6+
7+
getResults() {
8+
return typeof this.analyte.res.userResults === "string" ? [] :
9+
_.values(this.analyte.res.userResults);
10+
}
11+
12+
getNumericResults() {
13+
return this.getResults()
14+
.filter(r => r.result !== null && r.result !== "" && !isNaN(r.result))
15+
.map(r => Object.assign({}, r, {
16+
result: parseFloat(r.result)
17+
}))
18+
.sort((a, b) => a.result - b.result);
19+
}
20+
21+
getNumericResultValues() {
22+
return this.getNumericResults().map(r => r.result);
23+
}
24+
25+
/**
26+
* Returns the stats from the analyte
27+
* Makes sure all values are numbers
28+
*/
29+
getStatistics() {
30+
const statistics = Object.assign({}, this.analyte.res.statistics);
31+
for (let key in statistics) {
32+
statistics[key] = parseFloat(statistics[key]);
33+
}
34+
return statistics;
35+
}
36+
}

labkar/index.js

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
const _ = require("lodash");
2+
const grubbs = require('./../grubbs');
3+
const jStat = require("jStat").jStat;
4+
const ss = require("simple-statistics");
5+
6+
const fFactors = {
7+
20: [1.59, 0.57],
8+
19: [1.6, 0.59],
9+
18: [1.62, 0.62],
10+
17: [1.64, 0.64],
11+
16: [1.67, 0.68],
12+
15: [1.69, 0.71],
13+
14: [1.72, 0.75],
14+
13: [1.75, 0.8],
15+
12: [1.79, 0.86],
16+
11: [1.83, 0.93],
17+
10: [1.88, 1.01],
18+
9: [1.94, 1.11],
19+
8: [2.01, 1.25],
20+
7: [2.1, 1.43],
21+
};
22+
23+
function G(i, h, hPrevious) {
24+
if (i == 0) {
25+
return 0;
26+
} else if (i == 1) {
27+
return h * 0.5;
28+
} else {
29+
return (h + hPrevious) * 0.5;
30+
}
31+
}
32+
33+
module.exports = {
34+
outliers: function(results, alpha = 0.01) {
35+
const options = {
36+
alpha,
37+
};
38+
39+
if (results.length < 7) {
40+
return _.map(results, (result) => {
41+
result.outlier = false;
42+
return result;
43+
});
44+
}
45+
46+
let t = grubbs.test(_.map(results, "result"), options);
47+
const outliers = _.reduce(
48+
t,
49+
(all, iteration) => {
50+
return all.concat(iteration.outlierIndexes);
51+
},
52+
[]
53+
);
54+
55+
return _.map(results, (result, index) => {
56+
result.outlier = outliers.indexOf(index) >= 0;
57+
return result;
58+
});
59+
},
60+
61+
q: function(results, precision = 8) {
62+
const sortedResults = _.sortBy(results, "result");
63+
const values = _.map(sortedResults, "result");
64+
65+
let deltas = [];
66+
67+
for (let i = 0; i < values.length; i++) {
68+
for (let k = i + 1; k < values.length; k++) {
69+
deltas.push(
70+
Math.abs(_.round(parseFloat(values[i] - values[k]), precision))
71+
);
72+
}
73+
}
74+
75+
const sortedDeltas = _.sortBy(deltas);
76+
const sortedUniqueDeltas = _.sortedUniq(sortedDeltas);
77+
const hMultiplier = 2 / (results.length * (results.length - 1));
78+
79+
const calculations = [];
80+
81+
for (let i = 0; i < sortedUniqueDeltas.length; i++) {
82+
const value = sortedUniqueDeltas[i];
83+
const frequency = _.filter(sortedDeltas, (v) => v === value).length;
84+
const cumFrequency = _.sumBy(calculations, "frequency") + frequency;
85+
const h1 = cumFrequency * hMultiplier;
86+
87+
calculations.push({
88+
h1,
89+
value,
90+
frequency,
91+
cumFrequency,
92+
g: G(i, h1, _.get(calculations, i - 1 + ".h1")),
93+
});
94+
}
95+
96+
// START OF Q Calc.
97+
const firstParameter = calculations[0].h1 * 0.75 + 0.25;
98+
const secondParameter = calculations[0].h1 * 0.375 + 0.625;
99+
100+
const indexHigh = _.findIndex(calculations, (c, i) => {
101+
return firstParameter < c.g;
102+
});
103+
104+
const indexLow = indexHigh - 1;
105+
106+
const cHigh = calculations[indexHigh];
107+
const cLow = calculations[indexLow];
108+
109+
const slope = (cHigh.value - cLow.value) / (cHigh.g - cLow.g);
110+
const gInverse = cLow.value + (firstParameter - cLow.g) * slope;
111+
112+
const inverseF = jStat.normal.inv(secondParameter, 0, 1);
113+
let q = gInverse / (Math.sqrt(2) * inverseF);
114+
return parseFloat(q.toFixed(4));
115+
},
116+
hampel: function(results, q) {
117+
if (q === 0) {
118+
return 0;
119+
}
120+
const sortedResults = _.sortBy(results, "result");
121+
const values = _.map(sortedResults, "result");
122+
123+
const findWi = (qi) => {
124+
qi = Math.abs(qi);
125+
if (qi > 4.5) {
126+
return 0;
127+
} else if (qi > 3) {
128+
return (4.5 - qi) / qi;
129+
} else if (qi > 1.5) {
130+
return 1.5 / qi;
131+
}
132+
return 1;
133+
};
134+
135+
const calculateRecursive = (values, x) => {
136+
const iteration = _.map(values, (value) => {
137+
const qi = Math.abs((value - x) / q);
138+
139+
const wi = findWi(qi);
140+
const wiValue = wi * value;
141+
142+
return {
143+
value,
144+
qi,
145+
wi,
146+
wiValue,
147+
};
148+
});
149+
150+
let wiValueWi = _.sumBy(iteration, "wiValue") / _.sumBy(iteration, "wi");
151+
let error = (0.01 * q) / Math.sqrt(values.length);
152+
153+
if (Math.abs(wiValueWi - x) >= error) {
154+
return calculateRecursive(values, wiValueWi);
155+
}
156+
return wiValueWi;
157+
};
158+
159+
let x = ss.median(values);
160+
return calculateRecursive(values, x);
161+
},
162+
checkStability: (data, homogenityData) => {
163+
let psd = data.reproducibility / 2.8;
164+
let avgDiff = Math.abs(data.avg - homogenityData.avg);
165+
166+
let extended = false;
167+
let result = avgDiff <= psd * 0.3;
168+
169+
if (result) {
170+
return { result, extended };
171+
}
172+
173+
extended = true;
174+
let hUncertainty = Math.pow(
175+
(1.25 * homogenityData.sd) / Math.sqrt(homogenityData.tests),
176+
2
177+
);
178+
let sUncertainty = Math.pow((1.25 * data.sd) / Math.sqrt(data.tests), 2);
179+
let extensionFactor = Math.sqrt(hUncertainty + sUncertainty) * 2;
180+
181+
return {
182+
extended,
183+
result: avgDiff <= psd * 0.3 + extensionFactor,
184+
};
185+
},
186+
};

package-lock.json

Lines changed: 11 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
"dependencies": {
77
"average": "^1.0.0",
88
"compute-stdev": "^1.0.0",
9+
"jstat": "^1.9.5",
10+
"lodash": "^4.17.21",
911
"mathjs": "^10.6.1",
1012
"simple-statistics": "^7.7.5"
1113
},
12-
"devDependencies": {
14+
"devDependencies": {
1315
"nodemon": "^2.0.16",
1416
"typescript": "^4.7.2"
1517
},

0 commit comments

Comments
 (0)