spatialquery.js 0.0.0-stage → 0.1.1

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/dist/stats.js ADDED
@@ -0,0 +1,141 @@
1
+ // Statistical helpers matching the scipy / statsmodels functions used by SpatialQuery,
2
+ // built on @stdlib/stats where it provides the functionality.
3
+ import padjust from '@stdlib/stats/padjust/lib/index.js';
4
+ import ranks from '@stdlib/stats/ranks/lib/index.js';
5
+ import stridedMean from '@stdlib/stats/strided/mean/lib/index.js';
6
+ import mediansorted from '@stdlib/stats/strided/mediansorted/lib/index.js';
7
+ import hypergeometricCdf from '@stdlib/stats/base/dists/hypergeometric/cdf/lib/index.js';
8
+ import hypergeometricMean from '@stdlib/stats/base/dists/hypergeometric/mean/lib/index.js';
9
+ import normalCdf from '@stdlib/stats/base/dists/normal/cdf/lib/index.js';
10
+ // Python's built-in round() uses round-half-to-even ("banker's rounding"),
11
+ // whereas Math.round rounds half up.
12
+ export function roundHalfEven(x) {
13
+ const r = Math.round(x);
14
+ return (Math.abs(x % 1) === 0.5 && r % 2 !== 0) ? r - 1 : r;
15
+ }
16
+ // Hypergeometric distribution, using the same parameterization as scipy.stats.hypergeom:
17
+ // M = total number of objects,
18
+ // n = total number of "success" objects,
19
+ // N = number of objects drawn without replacement.
20
+ // (In @stdlib, these are N, K, and n, respectively.)
21
+ // Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.hypergeom.html
22
+ export function hypergeom(M, n, N) {
23
+ return {
24
+ mean() {
25
+ return hypergeometricMean(M, n, N);
26
+ },
27
+ // Survival function: P(X > k).
28
+ // Computed as the CDF of the number of failures drawn, N - X ~ Hypergeometric(M, M - n, N):
29
+ // P(X > k) = P(N - X <= N - k - 1). Unlike 1 - CDF, this retains precision for very small p-values.
30
+ sf(k) {
31
+ return hypergeometricCdf(N - Math.floor(k) - 1, M, M - n, N);
32
+ },
33
+ };
34
+ }
35
+ // Survival function of the standard normal distribution, P(Z > z).
36
+ export function normSf(z) {
37
+ return normalCdf(-z, 0, 1);
38
+ }
39
+ export function mean(values) {
40
+ return stridedMean(values.length, values, 1);
41
+ }
42
+ export function median(values) {
43
+ const sorted = values.toSorted((a, b) => a - b);
44
+ return mediansorted(sorted.length, sorted, 1);
45
+ }
46
+ // Ranks of the values (1-based), with ties assigned their average rank
47
+ // (equivalent to scipy.stats.rankdata(method='average') and pandas rank()).
48
+ export function rankAverage(values) {
49
+ return ranks(values, { method: 'average' });
50
+ }
51
+ // Number of arrangements yielding each value of the Mann-Whitney U statistic (0..n1*n2),
52
+ // for sample sizes n1 and n2. These are the coefficients of the Gaussian binomial
53
+ // coefficient [n1+n2 choose n1]_q = prod_{i=1..n1} (1 - q^(n2+i)) / (1 - q^i).
54
+ function mwuFrequencies(n1, n2) {
55
+ const maxU = n1 * n2;
56
+ const freqs = new Float64Array(maxU + 1);
57
+ freqs[0] = 1;
58
+ for (let i = 1; i <= n1; i++) {
59
+ // Multiply by 1 / (1 - q^i) = 1 + q^i + q^2i + ...
60
+ for (let u = i; u <= maxU; u++)
61
+ freqs[u] += freqs[u - i];
62
+ // Multiply by (1 - q^(n2+i)).
63
+ for (let u = maxU; u >= n2 + i; u--)
64
+ freqs[u] -= freqs[u - n2 - i];
65
+ }
66
+ return freqs;
67
+ }
68
+ // Two-sided Mann-Whitney U test (not available in @stdlib/stats).
69
+ // Equivalent to scipy.stats.mannwhitneyu(x, y, alternative='two-sided', method='auto'):
70
+ // the exact null distribution is used when either sample has at most 8 values and there are no ties,
71
+ // otherwise the normal approximation with tie and continuity corrections.
72
+ // Reference: https://github.com/scipy/scipy/blob/main/scipy/stats/_mannwhitneyu.py
73
+ export function mannWhitneyU(x, y) {
74
+ const n1 = x.length;
75
+ const n2 = y.length;
76
+ const xy = [...x, ...y];
77
+ const r = rankAverage(xy);
78
+ let r1 = 0;
79
+ for (let i = 0; i < n1; i++)
80
+ r1 += r[i];
81
+ const u1 = r1 - n1 * (n1 + 1) / 2;
82
+ const u2 = n1 * n2 - u1;
83
+ // Number of values in each group of tied values.
84
+ const valueCounts = new Map();
85
+ for (const v of xy)
86
+ valueCounts.set(v, (valueCounts.get(v) ?? 0) + 1);
87
+ const tieCounts = Array.from(valueCounts.values());
88
+ const hasTies = tieCounts.some(t => t > 1);
89
+ let pValue;
90
+ if ((n1 <= 8 || n2 <= 8) && !hasTies) {
91
+ // Exact: p = 2 * P(U >= max(U1, U2)) = 2 * P(U <= min(U1, U2)) by symmetry.
92
+ const uMin = Math.round(Math.min(u1, u2));
93
+ const freqs = mwuFrequencies(Math.min(n1, n2), Math.max(n1, n2));
94
+ let count = 0;
95
+ let total = 0;
96
+ freqs.forEach((f, u) => {
97
+ total += f;
98
+ if (u <= uMin)
99
+ count += f;
100
+ });
101
+ pValue = 2 * count / total;
102
+ }
103
+ else {
104
+ const n = n1 + n2;
105
+ const mu = n1 * n2 / 2;
106
+ const tieTerm = tieCounts.reduce((acc, t) => acc + t ** 3 - t, 0);
107
+ const s = Math.sqrt(n1 * n2 / 12 * ((n + 1) - tieTerm / (n * (n - 1))));
108
+ // Continuity correction, in the direction that increases the p-value.
109
+ const numerator = u1 - mu - 0.5 * Math.sign(u1 - mu);
110
+ const z = numerator / s;
111
+ pValue = 2 * normSf(Math.abs(z));
112
+ }
113
+ // NaN (e.g., when all values are tied) is propagated, as in scipy.
114
+ return { statistic: u1, pValue: Math.min(Math.max(pValue, 0), 1) };
115
+ }
116
+ // Benjamini-Hochberg FDR correction.
117
+ // Equivalent to statsmodels.stats.multitest.fdrcorrection(pvals, alpha, method='poscorr').
118
+ // Returns the rejection decisions and adjusted p-values, in the original order.
119
+ export function fdrCorrection(pvals, alpha = 0.05) {
120
+ const adjusted = padjust(pvals, 'bh');
121
+ const rejected = adjusted.map(p => p <= alpha);
122
+ return { rejected, adjusted };
123
+ }
124
+ // Determine significance across all tested motifs, applying FDR correction
125
+ // when more than one motif was tested.
126
+ // Reference: https://github.com/ShaokunAn/Spatial-Query/blob/0570c54bd6e046466e05c6b800d6701a7ab15c4a/SpatialQuery/spatial_utils.py#L134
127
+ export function applyFdrCorrection(results, alpha = 0.05) {
128
+ if (results.length === 0) {
129
+ return [];
130
+ }
131
+ if (results.length === 1) {
132
+ // Single-motif case: significance comes straight from the raw p-value, with no adjusted p-value.
133
+ const { adjPValue: _, ...result } = results[0];
134
+ return [{ ...result, ifSignificant: result.pValue < alpha }];
135
+ }
136
+ const { rejected, adjusted } = fdrCorrection(results.map(r => r.pValue), alpha);
137
+ return results
138
+ .map((r, i) => ({ ...r, adjPValue: adjusted[i], ifSignificant: rejected[i] }))
139
+ .toSorted((a, b) => a.adjPValue - b.adjPValue);
140
+ }
141
+ //# sourceMappingURL=stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.js","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,8DAA8D;AAC9D,OAAO,OAAO,MAAM,oCAAoC,CAAC;AACzD,OAAO,KAAK,MAAM,kCAAkC,CAAC;AACrD,OAAO,WAAW,MAAM,yCAAyC,CAAC;AAClE,OAAO,YAAY,MAAM,iDAAiD,CAAC;AAC3E,OAAO,iBAAiB,MAAM,0DAA0D,CAAC;AACzF,OAAO,kBAAkB,MAAM,2DAA2D,CAAC;AAC3F,OAAO,SAAS,MAAM,kDAAkD,CAAC;AAEzE,2EAA2E;AAC3E,qCAAqC;AACrC,MAAM,UAAU,aAAa,CAAC,CAAS;IACrC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACxB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,yFAAyF;AACzF,+BAA+B;AAC/B,yCAAyC;AACzC,mDAAmD;AACnD,qDAAqD;AACrD,6FAA6F;AAC7F,MAAM,UAAU,SAAS,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;IACvD,OAAO;QACL,IAAI;YACF,OAAO,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACrC,CAAC;QACD,+BAA+B;QAC/B,4FAA4F;QAC5F,oGAAoG;QACpG,EAAE,CAAC,CAAS;YACV,OAAO,iBAAiB,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/D,CAAC;KACF,CAAC;AACJ,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,MAAM,CAAC,CAAS;IAC9B,OAAO,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,MAAgB;IACnC,OAAO,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,MAAgB;IACrC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAChD,OAAO,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,uEAAuE;AACvE,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,MAAgB;IAC1C,OAAO,KAAK,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED,yFAAyF;AACzF,kFAAkF;AAClF,+EAA+E;AAC/E,SAAS,cAAc,CAAC,EAAU,EAAE,EAAU;IAC5C,MAAM,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC;IACrB,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IACzC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,mDAAmD;QACnD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE;YAAE,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzD,8BAA8B;QAC9B,KAAK,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE;YAAE,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,kEAAkE;AAClE,wFAAwF;AACxF,qGAAqG;AACrG,0EAA0E;AAC1E,mFAAmF;AACnF,MAAM,UAAU,YAAY,CAAC,CAAW,EAAE,CAAW;IACnD,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC;IACpB,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC;IACpB,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACxB,MAAM,CAAC,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC1B,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE;QAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAExB,iDAAiD;IACjD,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC9C,KAAK,MAAM,CAAC,IAAI,EAAE;QAAE,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAE3C,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACrC,4EAA4E;QAC5E,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QACjE,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACrB,KAAK,IAAI,CAAC,CAAC;YACX,IAAI,CAAC,IAAI,IAAI;gBAAE,KAAK,IAAI,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,MAAM,GAAG,CAAC,GAAG,KAAK,GAAG,KAAK,CAAC;IAC7B,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;QAClB,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACvB,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAClE,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACxE,sEAAsE;QACtE,MAAM,SAAS,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;QACrD,MAAM,CAAC,GAAG,SAAS,GAAG,CAAC,CAAC;QACxB,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC;IACD,mEAAmE;IACnE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AACrE,CAAC;AAED,qCAAqC;AACrC,2FAA2F;AAC3F,gFAAgF;AAChF,MAAM,UAAU,aAAa,CAAC,KAAe,EAAE,KAAK,GAAG,IAAI;IACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC;IAC/C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAChC,CAAC;AAID,2EAA2E;AAC3E,uCAAuC;AACvC,yIAAyI;AACzI,MAAM,UAAU,kBAAkB,CAA2B,OAAY,EAAE,KAAK,GAAG,IAAI;IACrF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,iGAAiG;QACjG,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QAC/C,OAAO,CAAC,EAAE,GAAG,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC,MAAM,GAAG,KAAK,EAAO,CAAC,CAAC;IACpE,CAAC;IACD,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;IAChF,OAAO,OAAO;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;SAC7E,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;AACnD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,44 @@
1
1
  {
2
2
  "name": "spatialquery.js",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.1",
4
+ "description": "Port of Python SpatialQuery methods (An et al. bioRxiv 2026)",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./bundled": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/bundled/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "dependencies": {
23
+ "@stdlib/stats": "^0.4.1",
24
+ "d3-array": "^2.4.0",
25
+ "flatbush": "^4.4.0",
26
+ "internmap": "^2.0.3",
27
+ "node-fpgrowth": "^1.2.1"
28
+ },
29
+ "devDependencies": {
30
+ "@types/d3-array": "^3.2.2",
31
+ "@zarrita/storage": "^0.1.4",
32
+ "anndata.js": "^0.0.3",
33
+ "events": "^3.3.0",
34
+ "hdf5-as-virtual-zarr": "0.1.1",
35
+ "typescript": "^5.8.3",
36
+ "vite": "^7.3.2",
37
+ "vitest": "^3.1.1",
38
+ "zarrita": "^0.7.5"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc && vite build",
42
+ "test": "vitest run"
43
+ }
6
44
  }