/
KDE2D.js
112 lines (99 loc) · 3.82 KB
/
KDE2D.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import density2D from './util/density2D';
import {Transform, ingest} from 'vega-dataflow';
import {accessorName, inherits} from 'vega-util';
/**
* Perform 2D kernel-density estimation of point data.
* @constructor
* @param {object} params - The parameters for this operator.
* @param {Array<number>} params.size - The [width, height] extent (in
* units of input pixels) over which to perform density estimation.
* @param {function(object): number} params.x - The x-coordinate accessor.
* @param {function(object): number} params.y - The y-coordinate accessor.
* @param {function(object): number} [params.weight] - The weight accessor.
* @param {Array<function(object): *>} [params.groupby] - An array of accessors
* to groupby.
* @param {number} [params.cellSize] - Contour density calculation cell size.
* This parameter determines the level of spatial approximation. For example,
* the default value of 4 maps to 2x reductions in both x- and y- dimensions.
* A value of 1 will result in an output raster grid whose dimensions exactly
* matches the size parameter.
* @param {Array<number>} [params.bandwidth] - The KDE kernel bandwidths,
* in pixels. The input can be a two-element array specifying separate
* x and y bandwidths, or a single-element array specifying both. If the
* bandwidth is unspecified or less than zero, the bandwidth will be
* automatically determined.
* @param {boolean} [params.counts=false] - A boolean flag indicating if the
* output values should be probability estimates (false, default) or
* smoothed counts (true).
* @param {string} [params.as='grid'] - The output field in which to store
* the generated raster grid (default 'grid').
*/
export default function KDE2D(params) {
Transform.call(this, null, params);
}
KDE2D.Definition = {
'type': 'KDE2D',
'metadata': {'generates': true},
'params': [
{ 'name': 'size', 'type': 'number', 'array': true, 'length': 2, 'required': true },
{ 'name': 'x', 'type': 'field', 'required': true },
{ 'name': 'y', 'type': 'field', 'required': true },
{ 'name': 'weight', 'type': 'field' },
{ 'name': 'groupby', 'type': 'field', 'array': true },
{ 'name': 'cellSize', 'type': 'number' },
{ 'name': 'bandwidth', 'type': 'number', 'array': true, 'length': 2 },
{ 'name': 'counts', 'type': 'boolean', 'default': false },
{ 'name': 'as', 'type': 'string', 'default': 'grid'}
]
};
const PARAMS = ['x', 'y', 'weight', 'size', 'cellSize', 'bandwidth'];
export function params(obj, _) {
PARAMS.forEach(param => _[param] != null ? obj[param](_[param]) : 0);
return obj;
}
inherits(KDE2D, Transform, {
transform(_, pulse) {
if (this.value && !pulse.changed() && !_.modified())
return pulse.StopPropagation;
var out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS),
source = pulse.materialize(pulse.SOURCE).source,
groups = partition(source, _.groupby),
names = (_.groupby || []).map(accessorName),
kde = params(density2D(), _),
as = _.as || 'grid',
values = [];
function set(t, vals) {
for (let i=0; i<names.length; ++i) t[names[i]] = vals[i];
return t;
}
// generate density raster grids
values = groups.map(g => ingest(
set({[as]: kde(g, _.counts)}, g.dims)
));
if (this.value) out.rem = this.value;
this.value = out.source = out.add = values;
return out;
}
});
export function partition(data, groupby) {
var groups = [],
get = f => f(t),
map, i, n, t, k, g;
// partition data points into groups
if (groupby == null) {
groups.push(data);
} else {
for (map={}, i=0, n=data.length; i<n; ++i) {
t = data[i];
k = groupby.map(get);
g = map[k];
if (!g) {
map[k] = (g = []);
g.dims = k;
groups.push(g);
}
g.push(t);
}
}
return groups;
}