-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
182 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/** | ||
* F factors for homogenity calculation | ||
* Array contains F1 and F2 values for each sample size | ||
*/ | ||
const fFactors = { | ||
20: [1.59, 0.57], | ||
19: [1.6, 0.59], | ||
18: [1.62, 0.62], | ||
17: [1.64, 0.64], | ||
16: [1.67, 0.68], | ||
15: [1.69, 0.71], | ||
14: [1.72, 0.75], | ||
13: [1.75, 0.8], | ||
12: [1.79, 0.86], | ||
11: [1.83, 0.93], | ||
10: [1.88, 1.01], | ||
9: [1.94, 1.11], | ||
8: [2.01, 1.25], | ||
7: [2.1, 1.43], | ||
}; | ||
|
||
export default fFactors; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { round, sum } from 'lodash'; | ||
import { average, sampleStandardDeviation } from 'simple-statistics'; | ||
import fFactors from './fFactors'; | ||
|
||
export type HomogenityTestResult = { | ||
/** | ||
* Label of the sample used in the test | ||
*/ | ||
label: string; | ||
|
||
/** | ||
* The result(s) of the test | ||
*/ | ||
values: number[]; | ||
}; | ||
|
||
const R_DIVIDER = 2.8; | ||
|
||
/** | ||
* | ||
* @param results An array of test results | ||
* @param R reproducibility constant | ||
*/ | ||
export function homogenity(results: HomogenityTestResult[], r: number) { | ||
if (results.length < 1) { | ||
throw new Error('At least one test result is required'); | ||
} | ||
|
||
if (results[0].values.length !== 2) { | ||
throw new Error( | ||
'We currently only support two values per test. You provided ' + | ||
results[0].values.length + | ||
' values.' | ||
); | ||
} | ||
|
||
const numTests = results.length; | ||
const numRepetitions = results[0].values.length; | ||
|
||
const enrichedResults = results.map((result) => ({ | ||
...result, | ||
avg: average(result.values), | ||
deltaPow: Math.pow(Math.abs(result.values[0] - result.values[1]), 2), | ||
})); | ||
|
||
const xAvg = average(enrichedResults.map((r) => r.avg)) | ||
|
||
const sd = round( | ||
sampleStandardDeviation(enrichedResults.map((r) => r.avg)), | ||
3 | ||
); | ||
|
||
const sw = Math.sqrt( | ||
sum(enrichedResults.map((r) => r.deltaPow)) / (numTests * numRepetitions) | ||
); | ||
|
||
const ss2 = Math.pow(sd, 2) - Math.pow(sw, 2) / 2; | ||
|
||
const ss = ss2 < 0 ? 0 : Math.sqrt(ss2); | ||
|
||
const fValues = fFactors[numTests as keyof typeof fFactors]; | ||
|
||
if (!fValues) { | ||
throw new Error( | ||
'No F values found for ' + numTests + ' tests. Supported range: 7 to 20.' | ||
); | ||
} | ||
|
||
const sigmaAllow2 = Math.pow((r / R_DIVIDER) * 0.3, 2); | ||
const c = fValues[0] * sigmaAllow2 + fValues[1] * Math.pow(sw, 2); | ||
|
||
const cSqrt = Math.sqrt(c); | ||
const homogenity = ss < cSqrt; | ||
|
||
return { | ||
xAvg, | ||
sd, | ||
sw, | ||
ss2, | ||
ss, | ||
c, | ||
cSqrt, | ||
homogenity, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { homogenity, HomogenityTestResult } from '../src/homogenity'; | ||
|
||
describe('homogenity', () => { | ||
it('should correctly calculate homogenity for given dataset', () => { | ||
// Arrange | ||
const testData: HomogenityTestResult[] = [ | ||
{ label: 'X', values: [0.452, 0.438] }, | ||
{ label: 'X', values: [0.436, 0.432] }, | ||
{ label: 'X', values: [0.435, 0.434] }, | ||
{ label: 'X', values: [0.456, 0.441] }, | ||
{ label: 'X', values: [0.434, 0.433] }, | ||
{ label: 'X', values: [0.439, 0.430] }, | ||
{ label: 'X', values: [0.433, 0.430] }, | ||
{ label: 'X', values: [0.434, 0.429] }, | ||
{ label: 'X', values: [0.434, 0.436] } | ||
]; | ||
const r = 0.07; | ||
|
||
// Act | ||
const result = homogenity(testData, r); | ||
|
||
// Assert | ||
expect(result.homogenity).toBe(true); | ||
expect(result.xAvg).toBeCloseTo(0.436, 3); | ||
expect(result.sw).toBeCloseTo(0.006, 3); | ||
expect(result.ss).toBeCloseTo(0.005, 3); | ||
expect(result.ss2).toBeCloseTo(0.00002, 5); | ||
expect(result.c).toBeCloseTo(0.0001, 4); | ||
expect(result.cSqrt).toBeCloseTo(0.012, 3); | ||
}); | ||
|
||
it('should correctly calculate homogenity for second dataset with 10 measurements', () => { | ||
// Arrange | ||
const testData: HomogenityTestResult[] = [ | ||
{ label: 'X', values: [0.452, 0.438] }, | ||
{ label: 'X', values: [0.436, 0.432] }, | ||
{ label: 'X', values: [0.435, 0.434] }, | ||
{ label: 'X', values: [0.456, 0.441] }, | ||
{ label: 'X', values: [0.434, 0.433] }, | ||
{ label: 'X', values: [0.439, 0.43] }, | ||
{ label: 'X', values: [0.433, 0.430] }, | ||
{ label: 'X', values: [0.434, 0.429] }, | ||
{ label: 'X', values: [0.434, 0.436] }, | ||
{ label: 'X', values: [0.47, 0.43] } | ||
]; | ||
|
||
const r = 0.07; | ||
|
||
// Act | ||
const result = homogenity(testData, r); | ||
|
||
// Assert | ||
expect(result.homogenity).toBe(true); | ||
expect(result.xAvg).toBeCloseTo(0.438, 3); | ||
expect(result.sw).toBeCloseTo(0.011, 2); | ||
expect(result.ss).toBeCloseTo(0.000, 3); | ||
expect(result.ss2).toBeCloseTo(-0.00001, 4); | ||
expect(result.c).toBeCloseTo(0.0002, 4); | ||
expect(result.cSqrt).toBeCloseTo(0.015, 3); | ||
}); | ||
|
||
it('should throw error when input array is empty', () => { | ||
expect(() => homogenity([], 0.07)).toThrow('At least one test result is required'); | ||
}); | ||
|
||
it('should throw error when values array does not contain exactly 2 values', () => { | ||
const invalidData: HomogenityTestResult[] = [ | ||
{ label: 'X', values: [0.452, 0.438, 0.434] } | ||
]; | ||
|
||
expect(() => homogenity(invalidData, 0.07)).toThrow('We currently only support two values per test'); | ||
}); | ||
}); |