@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.146.0",
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.145.2",
68
- "@sjcrh/proteinpaint-types": "2.146.0",
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 "#shared/descriptive.stats.js";
5
+ import { getStdDev } from "./termdb.descrstats.ts";
6
6
  const minArrayLength = 3;
7
7
  const api = {
8
8
  endpoint: "termdb/correlationVolcano",
@@ -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 { summaryStats, getDescriptiveStats, summaryStatsFromStats } from "#shared/descriptive.stats.js";
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 statsAllSummary = summaryStats(values).values;
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, stats.outlierMin);
61
- outlierMax = Math.max(outlierMax, stats.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: statsAllSummary
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 (ds.cohort.termdb.getAdditionalFilter) {
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 data = await getData(
30
- { filter: q.filter, filter0: q.filter0, terms: [q.tw], __protected__: q.__protected__ },
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
- res.send(result);
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
  };
@@ -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 { summaryStats } from "#shared/descriptive.stats.js";
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 = summaryStats(values).values;
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 = summaryStats(plot.values).values;
228
+ plot.summaryStats = getDescrStats(plot.values);
229
229
  }
230
230
  }
231
231
  }