@sjcrh/proteinpaint-server 2.146.0 → 2.146.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/routes/correlationVolcano.js +1 -1
- package/routes/termdb.boxplot.js +6 -8
- package/routes/termdb.cohort.summary.js +1 -1
- package/routes/termdb.descrstats.js +90 -33
- package/routes/termdb.violin.js +4 -4
- package/src/app.js +454 -392
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.146.
|
|
3
|
+
"version": "2.146.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "a genomics visualization tool for exploring a cohort's genotype and phenotype data",
|
|
6
6
|
"main": "src/app.js",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"@sjcrh/proteinpaint-python": "2.146.0",
|
|
65
65
|
"@sjcrh/proteinpaint-r": "2.145.1-0",
|
|
66
66
|
"@sjcrh/proteinpaint-rust": "2.146.0",
|
|
67
|
-
"@sjcrh/proteinpaint-shared": "2.
|
|
68
|
-
"@sjcrh/proteinpaint-types": "2.146.
|
|
67
|
+
"@sjcrh/proteinpaint-shared": "2.146.2",
|
|
68
|
+
"@sjcrh/proteinpaint-types": "2.146.2",
|
|
69
69
|
"@types/express": "^5.0.0",
|
|
70
70
|
"@types/express-session": "^1.18.1",
|
|
71
71
|
"better-sqlite3": "^9.4.1",
|
|
@@ -2,7 +2,7 @@ import { CorrelationVolcanoPayload } from "#types/checkers";
|
|
|
2
2
|
import { getData } from "../src/termdb.matrix.js";
|
|
3
3
|
import { run_R } from "@sjcrh/proteinpaint-r";
|
|
4
4
|
import { mayLog } from "#src/helpers.ts";
|
|
5
|
-
import { getStdDev } from "
|
|
5
|
+
import { getStdDev } from "./termdb.descrstats.ts";
|
|
6
6
|
const minArrayLength = 3;
|
|
7
7
|
const api = {
|
|
8
8
|
endpoint: "termdb/correlationVolcano",
|
package/routes/termdb.boxplot.js
CHANGED
|
@@ -2,7 +2,7 @@ import { boxplotPayload } from "#types/checkers";
|
|
|
2
2
|
import { getData } from "../src/termdb.matrix.js";
|
|
3
3
|
import { boxplot_getvalue } from "../src/utils.js";
|
|
4
4
|
import { sortPlot2Values } from "./termdb.violin.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { getDescrStats } from "./termdb.descrstats.ts";
|
|
6
6
|
const api = {
|
|
7
7
|
endpoint: "termdb/boxplot",
|
|
8
8
|
methods: {
|
|
@@ -30,8 +30,8 @@ function init({ genomes }) {
|
|
|
30
30
|
const data = await getData({ filter: q.filter, filter0: q.filter0, terms, __protected__: q.__protected__ }, ds);
|
|
31
31
|
if (data.error) throw data.error;
|
|
32
32
|
const samples = Object.values(data.samples);
|
|
33
|
-
const values = samples.map((s) => s?.[q.tw.$id]?.value).filter((v) => typeof v === "number");
|
|
34
|
-
const
|
|
33
|
+
const values = samples.map((s) => s?.[q.tw.$id]?.value).filter((v) => typeof v === "number" && !q.tw.term.values?.[v]?.uncomputable);
|
|
34
|
+
const descrStats = getDescrStats(values, q.removeOutliers);
|
|
35
35
|
const sampleType = `All ${data.sampleType?.plural_name || "samples"}`;
|
|
36
36
|
const overlayTerm = q.overlayTw;
|
|
37
37
|
const divideTerm = q.divideTw;
|
|
@@ -55,12 +55,10 @@ function init({ genomes }) {
|
|
|
55
55
|
const value = { value: v };
|
|
56
56
|
return value;
|
|
57
57
|
});
|
|
58
|
-
const stats = getDescriptiveStats(sortedValues);
|
|
59
58
|
if (q.removeOutliers) {
|
|
60
|
-
outlierMin = Math.min(outlierMin,
|
|
61
|
-
outlierMax = Math.max(outlierMax,
|
|
59
|
+
outlierMin = Math.min(outlierMin, descrStats.outlierMin.value);
|
|
60
|
+
outlierMax = Math.max(outlierMax, descrStats.outlierMax.value);
|
|
62
61
|
}
|
|
63
|
-
const descrStats = summaryStatsFromStats(stats, true).values;
|
|
64
62
|
const boxplot = boxplot_getvalue(vs, q.removeOutliers);
|
|
65
63
|
if (!boxplot) throw "boxplot_getvalue failed [termdb.boxplot init()]";
|
|
66
64
|
const _plot = {
|
|
@@ -100,7 +98,7 @@ function init({ genomes }) {
|
|
|
100
98
|
absMax: q.removeOutliers ? outlierMax : absMax,
|
|
101
99
|
charts,
|
|
102
100
|
uncomputableValues: setUncomputableValues(uncomputableValues),
|
|
103
|
-
descrStats
|
|
101
|
+
descrStats
|
|
104
102
|
};
|
|
105
103
|
res.send(returnData);
|
|
106
104
|
} catch (e) {
|
|
@@ -20,7 +20,7 @@ function init({ genomes }) {
|
|
|
20
20
|
if (!genome) throw "invalid genome";
|
|
21
21
|
const [ds] = get_ds_tdb(genome, q);
|
|
22
22
|
let count;
|
|
23
|
-
if (
|
|
23
|
+
if (q.filter?.lst?.length) {
|
|
24
24
|
const samples = await get_samples(q, ds);
|
|
25
25
|
count = samples.length;
|
|
26
26
|
} else count = ds.cohort.termdb.q?.getCohortSampleCount?.(q.cohort) || 1;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { descrStatsPayload } from "#types/checkers";
|
|
2
|
-
import { summaryStats } from "#shared/descriptive.stats.js";
|
|
3
2
|
import { getData } from "#src/termdb.matrix.js";
|
|
3
|
+
import computePercentile from "#shared/compute.percentile.js";
|
|
4
|
+
import { roundValueAuto } from "#shared/roundValue.js";
|
|
4
5
|
const api = {
|
|
5
6
|
endpoint: "termdb/descrstats",
|
|
6
7
|
methods: {
|
|
@@ -17,7 +18,6 @@ const api = {
|
|
|
17
18
|
function init({ genomes }) {
|
|
18
19
|
return async (req, res) => {
|
|
19
20
|
const q = req.query;
|
|
20
|
-
let result;
|
|
21
21
|
try {
|
|
22
22
|
const genome = genomes[req.query.genome];
|
|
23
23
|
if (!genome) throw "invalid genome name";
|
|
@@ -26,41 +26,98 @@ function init({ genomes }) {
|
|
|
26
26
|
const tdb = ds.cohort.termdb;
|
|
27
27
|
if (!tdb) throw "invalid termdb object";
|
|
28
28
|
if (!q.tw.$id) q.tw.$id = "_";
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
ds
|
|
32
|
-
);
|
|
33
|
-
if (data.error) throw data.error;
|
|
34
|
-
const values = [];
|
|
35
|
-
for (const key in data.samples) {
|
|
36
|
-
const sample = data.samples[key];
|
|
37
|
-
const v = sample[q.tw.$id];
|
|
38
|
-
if (!v && v !== 0) {
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
const value = v.value;
|
|
42
|
-
if (q.tw.q.hiddenValues?.[value]) {
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
if (q.logScale) {
|
|
46
|
-
if (value === 0) {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
values.push(Number(value));
|
|
51
|
-
}
|
|
52
|
-
if (values.length) {
|
|
53
|
-
result = summaryStats(values);
|
|
54
|
-
} else {
|
|
55
|
-
result = {};
|
|
56
|
-
}
|
|
29
|
+
const result = await trigger_getDescrStats(q, ds);
|
|
30
|
+
res.send(result);
|
|
57
31
|
} catch (e) {
|
|
58
32
|
if (e instanceof Error && e.stack) console.log(e);
|
|
59
|
-
result = { error: e?.message || e };
|
|
33
|
+
const result = { error: e?.message || e };
|
|
34
|
+
res.send(result);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
async function trigger_getDescrStats(q, ds) {
|
|
39
|
+
const data = await getData(
|
|
40
|
+
{ filter: q.filter, filter0: q.filter0, terms: [q.tw], __protected__: q.__protected__ },
|
|
41
|
+
ds
|
|
42
|
+
);
|
|
43
|
+
if (data.error) throw data.error;
|
|
44
|
+
const values = [];
|
|
45
|
+
for (const key in data.samples) {
|
|
46
|
+
const sample = data.samples[key];
|
|
47
|
+
const v = sample[q.tw.$id];
|
|
48
|
+
if (!v && v !== 0) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const value = v.value;
|
|
52
|
+
if (q.tw.term.values?.[value]?.uncomputable) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (q.logScale) {
|
|
56
|
+
if (value === 0) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
60
59
|
}
|
|
61
|
-
|
|
60
|
+
values.push(Number(value));
|
|
61
|
+
}
|
|
62
|
+
const stats = getDescrStats(values);
|
|
63
|
+
return stats;
|
|
64
|
+
}
|
|
65
|
+
function getDescrStats(values, showOutlierRange = false) {
|
|
66
|
+
if (!values.length) {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
if (values.some((v) => !Number.isFinite(v))) throw "non-numeric values found";
|
|
70
|
+
const sorted_arr = values.sort((a, b) => a - b);
|
|
71
|
+
const n = sorted_arr.length;
|
|
72
|
+
const median = computePercentile(sorted_arr, 50, true);
|
|
73
|
+
const mean = getMean(sorted_arr);
|
|
74
|
+
const variance = getVariance(sorted_arr);
|
|
75
|
+
const stdDev = Math.sqrt(variance);
|
|
76
|
+
const p25 = computePercentile(sorted_arr, 25, true);
|
|
77
|
+
const p75 = computePercentile(sorted_arr, 75, true);
|
|
78
|
+
const IQR = p75 - p25;
|
|
79
|
+
const min = sorted_arr[0];
|
|
80
|
+
const max = sorted_arr[sorted_arr.length - 1];
|
|
81
|
+
const outlierMin = p25 - 1.5 * IQR;
|
|
82
|
+
const outlierMax = p75 + 1.5 * IQR;
|
|
83
|
+
const stats = {
|
|
84
|
+
total: { key: "total", label: "Total", value: n },
|
|
85
|
+
min: { key: "min", label: "Minimum", value: min },
|
|
86
|
+
p25: { key: "p25", label: "1st quartile", value: p25 },
|
|
87
|
+
median: { key: "median", label: "Median", value: median },
|
|
88
|
+
p75: { key: "p75", label: "3rd quartile", value: p75 },
|
|
89
|
+
max: { key: "max", label: "Maximum", value: max },
|
|
90
|
+
mean: { key: "mean", label: "Mean", value: mean },
|
|
91
|
+
stdDev: { key: "stdDev", label: "Standard deviation", value: stdDev }
|
|
92
|
+
//variance: { label: 'Variance', value: variance }, // not necessary to report, as it is just stdDev^2
|
|
93
|
+
//iqr: { label: 'Inter-quartile range', value: IQR } // not necessary to report, as it is just p75-p25
|
|
62
94
|
};
|
|
95
|
+
if (showOutlierRange) {
|
|
96
|
+
stats.outlierMin = { key: "outlierMin", label: "Outlier minimum", value: outlierMin };
|
|
97
|
+
stats.outlierMax = { key: "outlierMax", label: "Outlier maximum", value: outlierMax };
|
|
98
|
+
}
|
|
99
|
+
for (const v of Object.values(stats)) {
|
|
100
|
+
const rounded = roundValueAuto(v.value);
|
|
101
|
+
v.value = rounded;
|
|
102
|
+
}
|
|
103
|
+
return stats;
|
|
104
|
+
}
|
|
105
|
+
function getMean(data) {
|
|
106
|
+
return data.reduce((sum, value) => sum + value, 0) / data.length;
|
|
107
|
+
}
|
|
108
|
+
function getVariance(data) {
|
|
109
|
+
const meanValue = getMean(data);
|
|
110
|
+
const squaredDifferences = data.map((value) => Math.pow(value - meanValue, 2));
|
|
111
|
+
return squaredDifferences.reduce((sum, value) => sum + value, 0) / (data.length - 1);
|
|
112
|
+
}
|
|
113
|
+
function getStdDev(data) {
|
|
114
|
+
const variance = getVariance(data);
|
|
115
|
+
return Math.sqrt(variance);
|
|
63
116
|
}
|
|
64
117
|
export {
|
|
65
|
-
api
|
|
118
|
+
api,
|
|
119
|
+
getDescrStats,
|
|
120
|
+
getMean,
|
|
121
|
+
getStdDev,
|
|
122
|
+
getVariance
|
|
66
123
|
};
|
package/routes/termdb.violin.js
CHANGED
|
@@ -4,7 +4,7 @@ import { run_rust } from "@sjcrh/proteinpaint-rust";
|
|
|
4
4
|
import { getData } from "../src/termdb.matrix.js";
|
|
5
5
|
import { createCanvas } from "canvas";
|
|
6
6
|
import { getOrderedLabels } from "../src/termdb.barchart.js";
|
|
7
|
-
import {
|
|
7
|
+
import { getDescrStats } from "./termdb.descrstats.ts";
|
|
8
8
|
import { isNumericTerm } from "#shared/terms.js";
|
|
9
9
|
import { numericBins, parseValues } from "./termdb.boxplot.ts";
|
|
10
10
|
import { run_R } from "@sjcrh/proteinpaint-r";
|
|
@@ -57,9 +57,9 @@ async function trigger_getViolinPlotData(q, ds) {
|
|
|
57
57
|
ds
|
|
58
58
|
);
|
|
59
59
|
const samples = Object.values(data.samples);
|
|
60
|
-
let values = samples.map((s) => s?.[q.tw.$id]?.value).filter((v) => typeof v === "number");
|
|
60
|
+
let values = samples.map((s) => s?.[q.tw.$id]?.value).filter((v) => typeof v === "number" && !q.tw.term.values?.[v]?.uncomputable);
|
|
61
61
|
if (q.unit == "log") values = values.filter((v) => v > 0);
|
|
62
|
-
const descrStats =
|
|
62
|
+
const descrStats = getDescrStats(values);
|
|
63
63
|
const sampleType = `All ${data.sampleType?.plural_name || "samples"}`;
|
|
64
64
|
if (data.error) throw data.error;
|
|
65
65
|
if (q.overlayTw && data.refs.byTermId[q.overlayTw.$id]) {
|
|
@@ -225,7 +225,7 @@ async function createCanvasImg(q, result, ds) {
|
|
|
225
225
|
ctx.stroke();
|
|
226
226
|
});
|
|
227
227
|
plot.src = canvas.toDataURL();
|
|
228
|
-
plot.summaryStats =
|
|
228
|
+
plot.summaryStats = getDescrStats(plot.values);
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
}
|