@uwdata/mosaic-plot 0.7.1 → 0.9.0
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/mosaic-plot.js +4702 -5648
- package/dist/mosaic-plot.min.js +14 -14
- package/package.json +5 -5
- package/src/index.js +1 -0
- package/src/interactors/Highlight.js +6 -3
- package/src/interactors/Interval1D.js +14 -12
- package/src/interactors/Interval2D.js +13 -16
- package/src/interactors/Nearest.js +80 -36
- package/src/interactors/PanZoom.js +7 -9
- package/src/interactors/Toggle.js +29 -37
- package/src/interactors/util/patchScreenCTM.js +2 -0
- package/src/legend.js +150 -29
- package/src/marks/ConnectedMark.js +6 -0
- package/src/marks/ContourMark.js +36 -16
- package/src/marks/DenseLineMark.js +9 -5
- package/src/marks/Density1DMark.js +22 -13
- package/src/marks/Density2DMark.js +33 -18
- package/src/marks/ErrorBarMark.js +50 -0
- package/src/marks/GeoMark.js +7 -8
- package/src/marks/Grid2DMark.js +58 -28
- package/src/marks/HexbinMark.js +10 -2
- package/src/marks/Mark.js +56 -16
- package/src/marks/RasterMark.js +61 -23
- package/src/marks/RasterTileMark.js +39 -20
- package/src/marks/RegressionMark.js +69 -34
- package/src/marks/util/grid.js +94 -86
- package/src/marks/util/handle-param.js +10 -11
- package/src/marks/util/is-constant-option.js +2 -1
- package/src/marks/util/permute.js +10 -0
- package/src/marks/util/stats.js +121 -1
- package/src/marks/util/to-data-columns.js +71 -0
- package/src/plot-attributes.js +11 -3
- package/src/plot-renderer.js +28 -9
- package/src/plot.js +20 -0
- package/src/transforms/bin.js +3 -1
- package/src/marks/util/interpolate.js +0 -205
- package/src/marks/util/to-data-array.js +0 -50
|
@@ -7,17 +7,24 @@ import {
|
|
|
7
7
|
import { qt } from './util/stats.js';
|
|
8
8
|
import { Mark, channelOption } from './Mark.js';
|
|
9
9
|
import { handleParam } from './util/handle-param.js';
|
|
10
|
-
import {
|
|
10
|
+
import { toDataColumns } from './util/to-data-columns.js';
|
|
11
11
|
|
|
12
12
|
export class RegressionMark extends Mark {
|
|
13
13
|
constructor(source, options) {
|
|
14
14
|
const { ci = 0.95, precision = 4, ...channels } = options;
|
|
15
15
|
super('line', source, channels);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
handleParam(
|
|
16
|
+
|
|
17
|
+
const update = () => this.modelFit ? this.confidenceBand().update() : null;
|
|
18
|
+
|
|
19
|
+
/** @type {number} */
|
|
20
|
+
this.ci = handleParam(ci, value => {
|
|
21
|
+
return (this.ci = value, update());
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/** @type {number} */
|
|
25
|
+
this.precision = handleParam(precision, value => {
|
|
26
|
+
return (this.precision = value, update());
|
|
27
|
+
});
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
query(filter = []) {
|
|
@@ -44,10 +51,10 @@ export class RegressionMark extends Mark {
|
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
queryResult(data) {
|
|
47
|
-
this.modelFit =
|
|
54
|
+
this.modelFit = toDataColumns(data);
|
|
48
55
|
|
|
49
56
|
// regression line
|
|
50
|
-
this.lineData = this.modelFit
|
|
57
|
+
this.lineData = linePoints(this.modelFit);
|
|
51
58
|
|
|
52
59
|
// prepare confidence band
|
|
53
60
|
return this.confidenceBand();
|
|
@@ -56,15 +63,17 @@ export class RegressionMark extends Mark {
|
|
|
56
63
|
confidenceBand() {
|
|
57
64
|
// regression ci area
|
|
58
65
|
const { ci, modelFit, precision, plot } = this;
|
|
59
|
-
const
|
|
60
|
-
this.areaData = ci ? modelFit
|
|
66
|
+
const width = plot.innerWidth();
|
|
67
|
+
this.areaData = ci ? areaPoints(modelFit, ci, precision, width) : null;
|
|
61
68
|
return this;
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
plotSpecs() {
|
|
65
72
|
const { lineData, areaData, channels, ci } = this;
|
|
66
|
-
const
|
|
67
|
-
const
|
|
73
|
+
const lcols = lineData.columns;
|
|
74
|
+
const acols = ci ? areaData.columns : {};
|
|
75
|
+
const lopt = { x: lcols.x, y: lcols.y };
|
|
76
|
+
const aopt = { x: acols.x, y1: acols.y1, y2: acols.y2, fillOpacity: 0.1 };
|
|
68
77
|
|
|
69
78
|
for (const c of channels) {
|
|
70
79
|
switch (c.channel) {
|
|
@@ -72,46 +81,72 @@ export class RegressionMark extends Mark {
|
|
|
72
81
|
case 'y':
|
|
73
82
|
case 'fill':
|
|
74
83
|
break;
|
|
84
|
+
case 'tip':
|
|
85
|
+
aopt.tip = channelOption(c, acols);
|
|
86
|
+
break;
|
|
75
87
|
case 'stroke':
|
|
76
|
-
lopt.stroke =
|
|
88
|
+
lopt.stroke = channelOption(c, lcols);
|
|
89
|
+
aopt.fill = channelOption(c, acols);
|
|
77
90
|
break;
|
|
78
91
|
case 'strokeOpacity':
|
|
79
|
-
lopt.strokeOpacity = channelOption(c);
|
|
92
|
+
lopt.strokeOpacity = channelOption(c, lcols);
|
|
80
93
|
break;
|
|
81
94
|
case 'fillOpacity':
|
|
82
|
-
aopt.fillOpacity = channelOption(c);
|
|
95
|
+
aopt.fillOpacity = channelOption(c, acols);
|
|
83
96
|
break;
|
|
84
97
|
default:
|
|
85
|
-
lopt[c.channel] =
|
|
98
|
+
lopt[c.channel] = channelOption(c, lcols);
|
|
99
|
+
aopt[c.channel] = channelOption(c, acols);
|
|
86
100
|
break;
|
|
87
101
|
}
|
|
88
102
|
}
|
|
89
103
|
|
|
90
104
|
return [
|
|
91
|
-
...(ci ? [{ type: 'areaY', data: areaData, options: aopt }] : []),
|
|
92
|
-
{ type: 'line', data: lineData, options: lopt }
|
|
105
|
+
...(ci ? [{ type: 'areaY', data: { length: areaData.numRows }, options: aopt }] : []),
|
|
106
|
+
{ type: 'line', data: { length: lineData.numRows }, options: lopt }
|
|
93
107
|
];
|
|
94
108
|
}
|
|
95
109
|
}
|
|
96
110
|
|
|
97
|
-
function
|
|
111
|
+
function concat(a, b) {
|
|
112
|
+
if (a.concat) return a.concat(b);
|
|
113
|
+
const array = new (a.constructor)(a.length + b.length);
|
|
114
|
+
array.set(a, 0);
|
|
115
|
+
array.set(b, a.length);
|
|
116
|
+
return array;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function linePoints(fit) {
|
|
98
120
|
// eslint-disable-next-line no-unused-vars
|
|
99
|
-
const { x0, x1, xm, intercept, slope, n, ssx, ssy, ...rest } =
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
121
|
+
const { x0, x1, xm, intercept, slope, n, ssx, ssy, ...rest } = fit.columns;
|
|
122
|
+
const predict = (x, i) => intercept[i] + x * slope[i];
|
|
123
|
+
const x = concat(x0, x1);
|
|
124
|
+
const y = concat(x0.map(predict), x1.map(predict));
|
|
125
|
+
for (const name in rest) {
|
|
126
|
+
rest[name] = concat(rest[name], rest[name]);
|
|
127
|
+
}
|
|
128
|
+
return { numRows: x.length, columns: { x, y, ...rest } };
|
|
104
129
|
}
|
|
105
130
|
|
|
106
|
-
function areaPoints(ci, precision,
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
131
|
+
function areaPoints(fit, ci, precision, width) {
|
|
132
|
+
const len = fit.numRows;
|
|
133
|
+
const { x0, x1, xm, intercept, slope, n, ssx, ssy, ...rest } = fit.columns;
|
|
134
|
+
const other = Object.keys(rest);
|
|
135
|
+
const columns = { x: [], y1: [], y2: [] };
|
|
136
|
+
other.forEach(name => columns[name] = []);
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < len; ++i) {
|
|
139
|
+
const pp = precision * (x1[i] - x0[i]) / width;
|
|
140
|
+
const t_sy = qt((1 - ci) / 2, n[i] - 2) * Math.sqrt(ssy[i] / (n[i] - 2));
|
|
141
|
+
range(x0[i], x1[i] - pp / 2, pp).concat(x1[i]).forEach(x => {
|
|
142
|
+
const y = intercept[i] + x * slope[i];
|
|
143
|
+
const ye = t_sy * Math.sqrt(1 / n[i] + (x - xm[i]) ** 2 / ssx[i]);
|
|
144
|
+
columns.x.push(x);
|
|
145
|
+
columns.y1.push(y - ye);
|
|
146
|
+
columns.y2.push(y + ye);
|
|
147
|
+
other.forEach(name => columns[name].push(rest[name][i]));
|
|
116
148
|
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { numRows: columns.x.length, columns };
|
|
117
152
|
}
|
package/src/marks/util/grid.js
CHANGED
|
@@ -1,119 +1,127 @@
|
|
|
1
1
|
import { InternSet, ascending } from 'd3';
|
|
2
|
-
import {
|
|
3
|
-
convertArrowArrayType,
|
|
4
|
-
convertArrowColumn,
|
|
5
|
-
isArrowTable
|
|
6
|
-
} from '@uwdata/mosaic-core';
|
|
7
2
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Array | Int8Array | Uint8Array | Uint8ClampedArray
|
|
5
|
+
* | Int16Array | Uint16Array | Int32Array | Uint32Array
|
|
6
|
+
* | Float32Array | Float64Array
|
|
7
|
+
* } Arrayish - an Array or TypedArray
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate a new array with designated size and type.
|
|
12
|
+
* @param {number} size The size of the array
|
|
13
|
+
* @param {Arrayish} [proto] A prototype object of the desired array type.
|
|
14
|
+
* This may be a typed array or standard array (the default).
|
|
15
|
+
* @returns {Arrayish} The generated array.
|
|
16
|
+
*/
|
|
17
|
+
export function array(size, proto = []) {
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
return new proto.constructor(size);
|
|
12
20
|
}
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// fallback to iterable data
|
|
27
|
-
for (const row of values) {
|
|
28
|
-
grid[row.index] = row[name];
|
|
29
|
-
}
|
|
22
|
+
/**
|
|
23
|
+
* Create a 1D grid for the given sample values
|
|
24
|
+
* @param {number} size The grid size.
|
|
25
|
+
* @param {Arrayish} index The grid indices for sample points.
|
|
26
|
+
* @param {Arrayish} value The sample point values.
|
|
27
|
+
* @returns {Arrayish} The generated value grid.
|
|
28
|
+
*/
|
|
29
|
+
export function grid1d(size, index, value) {
|
|
30
|
+
const G = array(size, value);
|
|
31
|
+
const n = value.length;
|
|
32
|
+
for (let i = 0; i < n; ++i) {
|
|
33
|
+
G[index[i]] = value[i];
|
|
30
34
|
}
|
|
31
|
-
return
|
|
35
|
+
return G;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Create a 2D grid for the given sample values.
|
|
40
|
+
* Can handle multiple grids and groupby values per output row.
|
|
41
|
+
* @param {number} w The grid width.
|
|
42
|
+
* @param {number} h The grid height.
|
|
43
|
+
* @param {Arrayish} index The grid indices for sample points.
|
|
44
|
+
* An index value is an integer of the form (y * w + x).
|
|
45
|
+
* @param {Record<string,Arrayish>} columns Named column arrays with sample point values.
|
|
46
|
+
* @param {string[]} aggregates The names of aggregate columns to grid.
|
|
47
|
+
* @param {string[]} groupby The names of additional columns to group by.
|
|
48
|
+
* @param {function} [interpolate] A grid interpolation function.
|
|
49
|
+
* By default sample values are directly copied to output grid arrays.
|
|
50
|
+
* @returns {{
|
|
51
|
+
* numRows: number;
|
|
52
|
+
* columns: { [key:string]: Arrayish }
|
|
53
|
+
* }} Named column arrays of generated grid values.
|
|
54
|
+
*/
|
|
55
|
+
export function grid2d(w, h, index, columns, aggregates, groupby, interpolate) {
|
|
56
|
+
const numRows = index.length;
|
|
35
57
|
const size = w * h;
|
|
36
|
-
const
|
|
37
|
-
const
|
|
58
|
+
const values = aggregates.map(name => columns[name]);
|
|
59
|
+
const result = {};
|
|
60
|
+
const cells = [];
|
|
61
|
+
const group = new Int32Array(numRows);
|
|
38
62
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
aggr.forEach((name, i) => cell[name] = new Types[i](size));
|
|
44
|
-
return cell;
|
|
45
|
-
};
|
|
46
|
-
const cellMap = {};
|
|
47
|
-
const baseCell = groupby.length ? null : (cellMap[[]] = createCell([]));
|
|
48
|
-
const getCell = groupby.length
|
|
49
|
-
? key => cellMap[key] ?? (cellMap[key] = createCell(key))
|
|
50
|
-
: () => baseCell;
|
|
51
|
-
|
|
52
|
-
// early exit if empty query result
|
|
53
|
-
const numRows = values.numRows;
|
|
54
|
-
if (numRows === 0) return Object.values(cellMap);
|
|
55
|
-
|
|
56
|
-
// extract arrays from arrow table
|
|
57
|
-
const index = convertArrowColumn(values.getChild('index'));
|
|
58
|
-
const value = aggr.map(name => convertArrowColumn(values.getChild(name)));
|
|
59
|
-
const groups = groupby.map(name => values.getChild(name));
|
|
60
|
-
|
|
61
|
-
if (!interpolate) {
|
|
62
|
-
// if no interpolation, copy values over
|
|
63
|
+
// if grouped, generate per-row group indices
|
|
64
|
+
if (groupby?.length) {
|
|
65
|
+
const gvalues = groupby.map(name => columns[name]);
|
|
66
|
+
const cellMap = {};
|
|
63
67
|
for (let row = 0; row < numRows; ++row) {
|
|
64
|
-
const key =
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
const key = gvalues.map(group => group[row]);
|
|
69
|
+
group[row] = cellMap[key] ??= cells.push(key) - 1;
|
|
70
|
+
}
|
|
71
|
+
for (let i = 0; i < groupby.length; ++i) {
|
|
72
|
+
result[groupby[i]] = cells.map(cell => cell[i]);
|
|
69
73
|
}
|
|
70
74
|
} else {
|
|
75
|
+
cells.push([]); // single group
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (interpolate) {
|
|
71
79
|
// prepare index arrays, then interpolate grid values
|
|
72
80
|
const X = index.map(k => k % w);
|
|
73
81
|
const Y = index.map(k => Math.floor(k / w));
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const cell = getCell(key);
|
|
78
|
-
if (!cell.index) { cell.index = []; }
|
|
79
|
-
cell.index.push(row);
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
baseCell.index = index.map((_, i) => i);
|
|
82
|
+
const I = cells.map(() => []);
|
|
83
|
+
for (let row = 0; row < numRows; ++row) {
|
|
84
|
+
I[group[row]].push(row);
|
|
83
85
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
aggregates.forEach((name, i) => {
|
|
87
|
+
const V = values[i];
|
|
88
|
+
result[name] = cells.map((_, j) => interpolate(I[j], w, h, X, Y, V));
|
|
89
|
+
});
|
|
90
|
+
} else {
|
|
91
|
+
// no interpolation, copy values directly to grids
|
|
92
|
+
aggregates.forEach((name, i) => {
|
|
93
|
+
const V = values[i];
|
|
94
|
+
const G = result[name] = cells.map(() => array(size, V));
|
|
95
|
+
for (let row = 0; row < numRows; ++row) {
|
|
96
|
+
G[group[row]][index[row]] = V[row];
|
|
87
97
|
}
|
|
88
|
-
|
|
89
|
-
})
|
|
98
|
+
});
|
|
90
99
|
}
|
|
91
100
|
|
|
92
|
-
|
|
101
|
+
// @ts-ignore
|
|
102
|
+
return { numRows: cells.length, columns: result };
|
|
93
103
|
}
|
|
94
104
|
|
|
95
|
-
export function gridDomainContinuous(grids
|
|
96
|
-
let lo =
|
|
97
|
-
grids.forEach(
|
|
98
|
-
const
|
|
99
|
-
const n = grid.length;
|
|
105
|
+
export function gridDomainContinuous(grids) {
|
|
106
|
+
let lo = Infinity, hi = -Infinity;
|
|
107
|
+
grids.forEach(G => {
|
|
108
|
+
const n = G.length;
|
|
100
109
|
for (let i = 0; i < n; ++i) {
|
|
101
|
-
const v =
|
|
110
|
+
const v = G[i];
|
|
102
111
|
if (v < lo) lo = v;
|
|
103
112
|
if (v > hi) hi = v;
|
|
104
113
|
}
|
|
105
114
|
});
|
|
106
|
-
return (lo
|
|
115
|
+
return (Number.isFinite(lo) && Number.isFinite(hi)) ? [lo, hi] : [0, 1];
|
|
107
116
|
}
|
|
108
117
|
|
|
109
|
-
export function gridDomainDiscrete(grids
|
|
110
|
-
|
|
118
|
+
export function gridDomainDiscrete(grids) {
|
|
119
|
+
// TODO: sort options?
|
|
111
120
|
const values = new InternSet();
|
|
112
|
-
grids.forEach(
|
|
113
|
-
const
|
|
114
|
-
const n = grid.length;
|
|
121
|
+
grids.forEach(G => {
|
|
122
|
+
const n = G.length;
|
|
115
123
|
for (let i = 0; i < n; ++i) {
|
|
116
|
-
values.add(
|
|
124
|
+
values.add(G[i]);
|
|
117
125
|
}
|
|
118
126
|
});
|
|
119
127
|
return Array.from(values).sort(ascending);
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { isParam } from '@uwdata/mosaic-core';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
3
|
+
/**
|
|
4
|
+
* Utility to check if a value is a Param, and if so, bind a listener.
|
|
5
|
+
* @param {*} value A potentially Param-typed value.
|
|
6
|
+
* @param {(value: *) => Promise|void} update Update callback
|
|
7
|
+
* @returns the input value or (if a Param) the current Param value.
|
|
8
|
+
*/
|
|
9
|
+
export function handleParam(value, update) {
|
|
10
|
+
return isParam(value)
|
|
11
|
+
? (value.addEventListener('value', update), value.value)
|
|
12
|
+
: value;
|
|
14
13
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function indices(length) {
|
|
2
|
+
return Array.from({ length }, (_, i) => i);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function permute(data, order) {
|
|
6
|
+
const ord = order.reduce((acc, val, i) => (acc[val] = i, acc), {});
|
|
7
|
+
const idx = indices(data.length);
|
|
8
|
+
idx.sort((a, b) => ord[data[a]] - ord[data[b]]);
|
|
9
|
+
return idx;
|
|
10
|
+
}
|
package/src/marks/util/stats.js
CHANGED
|
@@ -20,6 +20,13 @@
|
|
|
20
20
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
// SOFTWARE.
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* ibetainv function
|
|
25
|
+
* @param {number} p
|
|
26
|
+
* @param {number} a
|
|
27
|
+
* @param {number} b
|
|
28
|
+
* @returns {number}
|
|
29
|
+
*/
|
|
23
30
|
export function ibetainv(p, a, b) {
|
|
24
31
|
var EPS = 1e-8;
|
|
25
32
|
var a1 = a - 1;
|
|
@@ -60,11 +67,18 @@ export function ibetainv(p, a, b) {
|
|
|
60
67
|
return x;
|
|
61
68
|
}
|
|
62
69
|
|
|
70
|
+
/**
|
|
71
|
+
* ibeta function
|
|
72
|
+
* @param {number} x
|
|
73
|
+
* @param {number} a
|
|
74
|
+
* @param {number} b
|
|
75
|
+
* @returns {number}
|
|
76
|
+
*/
|
|
63
77
|
export function ibeta(x, a, b) {
|
|
64
78
|
// Factors in front of the continued fraction.
|
|
65
79
|
var bt =
|
|
66
80
|
x === 0 || x === 1 ? 0 : Math.exp(gammaln(a + b) - gammaln(a) - gammaln(b) + a * Math.log(x) + b * Math.log(1 - x));
|
|
67
|
-
if (x < 0 || x > 1) return
|
|
81
|
+
if (x < 0 || x > 1) return 0;
|
|
68
82
|
if (x < (a + 1) / (a + b + 2))
|
|
69
83
|
// Use continued fraction directly.
|
|
70
84
|
return (bt * betacf(x, a, b)) / a;
|
|
@@ -72,6 +86,13 @@ export function ibeta(x, a, b) {
|
|
|
72
86
|
return 1 - (bt * betacf(1 - x, b, a)) / b;
|
|
73
87
|
}
|
|
74
88
|
|
|
89
|
+
/**
|
|
90
|
+
* betacf function
|
|
91
|
+
* @param {number} x
|
|
92
|
+
* @param {number} a
|
|
93
|
+
* @param {number} b
|
|
94
|
+
* @returns {number}
|
|
95
|
+
*/
|
|
75
96
|
export function betacf(x, a, b) {
|
|
76
97
|
var fpmin = 1e-30;
|
|
77
98
|
var m = 1;
|
|
@@ -112,6 +133,11 @@ export function betacf(x, a, b) {
|
|
|
112
133
|
return h;
|
|
113
134
|
}
|
|
114
135
|
|
|
136
|
+
/**
|
|
137
|
+
* gammaln function
|
|
138
|
+
* @param {number} x
|
|
139
|
+
* @returns {number}
|
|
140
|
+
*/
|
|
115
141
|
export function gammaln(x) {
|
|
116
142
|
var j = 0;
|
|
117
143
|
var cof = [
|
|
@@ -126,8 +152,102 @@ export function gammaln(x) {
|
|
|
126
152
|
return Math.log((2.506628274631 * ser) / xx) - tmp;
|
|
127
153
|
}
|
|
128
154
|
|
|
155
|
+
/**
|
|
156
|
+
* qt function
|
|
157
|
+
* @param {number} p
|
|
158
|
+
* @param {number} dof
|
|
159
|
+
* @returns {number}
|
|
160
|
+
*/
|
|
129
161
|
export function qt(p, dof) {
|
|
130
162
|
var x = ibetainv(2 * Math.min(p, 1 - p), 0.5 * dof, 0.5);
|
|
131
163
|
x = Math.sqrt((dof * (1 - x)) / x);
|
|
132
164
|
return p > 0.5 ? x : -x;
|
|
133
165
|
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Approximate inverse error function.
|
|
169
|
+
* @param {number} x
|
|
170
|
+
* @returns {number}
|
|
171
|
+
*/
|
|
172
|
+
export function erfinv(x) {
|
|
173
|
+
// Implementation from "Approximating the erfinv function" by Mike Giles,
|
|
174
|
+
// GPU Computing Gems, volume 2, 2010.
|
|
175
|
+
// Ported from Apache Commons Math, http://www.apache.org/licenses/LICENSE-2.0
|
|
176
|
+
|
|
177
|
+
// beware that the logarithm argument must be
|
|
178
|
+
// computed as (1.0 - x) * (1.0 + x),
|
|
179
|
+
// it must NOT be simplified as 1.0 - x * x as this
|
|
180
|
+
// would induce rounding errors near the boundaries +/-1
|
|
181
|
+
let w = - Math.log((1 - x) * (1 + x));
|
|
182
|
+
let p;
|
|
183
|
+
|
|
184
|
+
if (w < 6.25) {
|
|
185
|
+
w -= 3.125;
|
|
186
|
+
p = -3.6444120640178196996e-21;
|
|
187
|
+
p = -1.685059138182016589e-19 + p * w;
|
|
188
|
+
p = 1.2858480715256400167e-18 + p * w;
|
|
189
|
+
p = 1.115787767802518096e-17 + p * w;
|
|
190
|
+
p = -1.333171662854620906e-16 + p * w;
|
|
191
|
+
p = 2.0972767875968561637e-17 + p * w;
|
|
192
|
+
p = 6.6376381343583238325e-15 + p * w;
|
|
193
|
+
p = -4.0545662729752068639e-14 + p * w;
|
|
194
|
+
p = -8.1519341976054721522e-14 + p * w;
|
|
195
|
+
p = 2.6335093153082322977e-12 + p * w;
|
|
196
|
+
p = -1.2975133253453532498e-11 + p * w;
|
|
197
|
+
p = -5.4154120542946279317e-11 + p * w;
|
|
198
|
+
p = 1.051212273321532285e-09 + p * w;
|
|
199
|
+
p = -4.1126339803469836976e-09 + p * w;
|
|
200
|
+
p = -2.9070369957882005086e-08 + p * w;
|
|
201
|
+
p = 4.2347877827932403518e-07 + p * w;
|
|
202
|
+
p = -1.3654692000834678645e-06 + p * w;
|
|
203
|
+
p = -1.3882523362786468719e-05 + p * w;
|
|
204
|
+
p = 0.0001867342080340571352 + p * w;
|
|
205
|
+
p = -0.00074070253416626697512 + p * w;
|
|
206
|
+
p = -0.0060336708714301490533 + p * w;
|
|
207
|
+
p = 0.24015818242558961693 + p * w;
|
|
208
|
+
p = 1.6536545626831027356 + p * w;
|
|
209
|
+
} else if (w < 16.0) {
|
|
210
|
+
w = Math.sqrt(w) - 3.25;
|
|
211
|
+
p = 2.2137376921775787049e-09;
|
|
212
|
+
p = 9.0756561938885390979e-08 + p * w;
|
|
213
|
+
p = -2.7517406297064545428e-07 + p * w;
|
|
214
|
+
p = 1.8239629214389227755e-08 + p * w;
|
|
215
|
+
p = 1.5027403968909827627e-06 + p * w;
|
|
216
|
+
p = -4.013867526981545969e-06 + p * w;
|
|
217
|
+
p = 2.9234449089955446044e-06 + p * w;
|
|
218
|
+
p = 1.2475304481671778723e-05 + p * w;
|
|
219
|
+
p = -4.7318229009055733981e-05 + p * w;
|
|
220
|
+
p = 6.8284851459573175448e-05 + p * w;
|
|
221
|
+
p = 2.4031110387097893999e-05 + p * w;
|
|
222
|
+
p = -0.0003550375203628474796 + p * w;
|
|
223
|
+
p = 0.00095328937973738049703 + p * w;
|
|
224
|
+
p = -0.0016882755560235047313 + p * w;
|
|
225
|
+
p = 0.0024914420961078508066 + p * w;
|
|
226
|
+
p = -0.0037512085075692412107 + p * w;
|
|
227
|
+
p = 0.005370914553590063617 + p * w;
|
|
228
|
+
p = 1.0052589676941592334 + p * w;
|
|
229
|
+
p = 3.0838856104922207635 + p * w;
|
|
230
|
+
} else if (Number.isFinite(w)) {
|
|
231
|
+
w = Math.sqrt(w) - 5.0;
|
|
232
|
+
p = -2.7109920616438573243e-11;
|
|
233
|
+
p = -2.5556418169965252055e-10 + p * w;
|
|
234
|
+
p = 1.5076572693500548083e-09 + p * w;
|
|
235
|
+
p = -3.7894654401267369937e-09 + p * w;
|
|
236
|
+
p = 7.6157012080783393804e-09 + p * w;
|
|
237
|
+
p = -1.4960026627149240478e-08 + p * w;
|
|
238
|
+
p = 2.9147953450901080826e-08 + p * w;
|
|
239
|
+
p = -6.7711997758452339498e-08 + p * w;
|
|
240
|
+
p = 2.2900482228026654717e-07 + p * w;
|
|
241
|
+
p = -9.9298272942317002539e-07 + p * w;
|
|
242
|
+
p = 4.5260625972231537039e-06 + p * w;
|
|
243
|
+
p = -1.9681778105531670567e-05 + p * w;
|
|
244
|
+
p = 7.5995277030017761139e-05 + p * w;
|
|
245
|
+
p = -0.00021503011930044477347 + p * w;
|
|
246
|
+
p = -0.00013871931833623122026 + p * w;
|
|
247
|
+
p = 1.0103004648645343977 + p * w;
|
|
248
|
+
p = 4.8499064014085844221 + p * w;
|
|
249
|
+
} else {
|
|
250
|
+
p = Infinity;
|
|
251
|
+
}
|
|
252
|
+
return p * x;
|
|
253
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { convertArrowColumn, isArrowTable } from '@uwdata/mosaic-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Array | Int8Array | Uint8Array | Uint8ClampedArray
|
|
5
|
+
* | Int16Array | Uint16Array | Int32Array | Uint32Array
|
|
6
|
+
* | Float32Array | Float64Array
|
|
7
|
+
* } Arrayish - an Array or TypedArray
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {
|
|
12
|
+
* | { numRows: number, columns: Record<string,Arrayish> }
|
|
13
|
+
* | { numRows: number, values: Arrayish; }
|
|
14
|
+
* } DataColumns
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Convert input data to a set of column arrays.
|
|
19
|
+
* @param {any} data The input data.
|
|
20
|
+
* @returns {DataColumns} An object with named column arrays.
|
|
21
|
+
*/
|
|
22
|
+
export function toDataColumns(data) {
|
|
23
|
+
return isArrowTable(data)
|
|
24
|
+
? arrowToColumns(data)
|
|
25
|
+
: arrayToColumns(data);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Convert an Arrow table to a set of column arrays.
|
|
30
|
+
* @param {import('apache-arrow').Table} data An Apache Arrow Table.
|
|
31
|
+
* @returns {DataColumns} An object with named column arrays.
|
|
32
|
+
*/
|
|
33
|
+
function arrowToColumns(data) {
|
|
34
|
+
const { numRows, numCols, schema: { fields } } = data;
|
|
35
|
+
const columns = {};
|
|
36
|
+
|
|
37
|
+
for (let col = 0; col < numCols; ++col) {
|
|
38
|
+
const name = fields[col].name;
|
|
39
|
+
if (columns[name]) {
|
|
40
|
+
console.warn(`Redundant column name "${name}". Skipping...`);
|
|
41
|
+
} else {
|
|
42
|
+
columns[name] = convertArrowColumn(data.getChildAt(col));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { numRows, columns };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Convert an array of values to a set of column arrays.
|
|
51
|
+
* If the array values are objects, build out named columns.
|
|
52
|
+
* We use the keys of the first object as the column names.
|
|
53
|
+
* Otherwise, use a special "values" array.
|
|
54
|
+
* @param {object[]} data An array of data objects.
|
|
55
|
+
* @returns {DataColumns} An object with named column arrays.
|
|
56
|
+
*/
|
|
57
|
+
function arrayToColumns(data) {
|
|
58
|
+
const numRows = data.length;
|
|
59
|
+
if (typeof data[0] === 'object') {
|
|
60
|
+
const names = numRows ? Object.keys(data[0]) : [];
|
|
61
|
+
const columns = {};
|
|
62
|
+
if (names.length > 0) {
|
|
63
|
+
names.forEach(name => {
|
|
64
|
+
columns[name] = data.map(d => d[name]);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return { numRows, columns };
|
|
68
|
+
} else {
|
|
69
|
+
return { numRows, values: data };
|
|
70
|
+
}
|
|
71
|
+
}
|