UNPKG

4.52 kBJavaScriptView Raw
1import parseDist from './util/Distributions';
2import {Transform, ingest} from 'vega-dataflow';
3import {sampleCurve} from 'vega-statistics';
4import {error, extent, inherits} from 'vega-util';
5
6/**
7 * Grid sample points for a probability density. Given a distribution and
8 * a sampling extent, will generate points suitable for plotting either
9 * PDF (probability density function) or CDF (cumulative distribution
10 * function) curves.
11 * @constructor
12 * @param {object} params - The parameters for this operator.
13 * @param {object} params.distribution - The probability distribution. This
14 * is an object parameter dependent on the distribution type.
15 * @param {string} [params.method='pdf'] - The distribution method to sample.
16 * One of 'pdf' or 'cdf'.
17 * @param {Array<number>} [params.extent] - The [min, max] extent over which
18 * to sample the distribution. This argument is required in most cases, but
19 * can be omitted if the distribution (e.g., 'kde') supports a 'data' method
20 * that returns numerical sample points from which the extent can be deduced.
21 * @param {number} [params.minsteps=25] - The minimum number of curve samples
22 * for plotting the density.
23 * @param {number} [params.maxsteps=200] - The maximum number of curve samples
24 * for plotting the density.
25 * @param {number} [params.steps] - The exact number of curve samples for
26 * plotting the density. If specified, overrides both minsteps and maxsteps
27 * to set an exact number of uniform samples. Useful in conjunction with
28 * a fixed extent to ensure consistent sample points for stacked densities.
29 */
30export default function Density(params) {
31 Transform.call(this, null, params);
32}
33
34const distributions = [
35 {
36 'key': {'function': 'normal'},
37 'params': [
38 { 'name': 'mean', 'type': 'number', 'default': 0 },
39 { 'name': 'stdev', 'type': 'number', 'default': 1 }
40 ]
41 },
42 {
43 'key': {'function': 'lognormal'},
44 'params': [
45 { 'name': 'mean', 'type': 'number', 'default': 0 },
46 { 'name': 'stdev', 'type': 'number', 'default': 1 }
47 ]
48 },
49 {
50 'key': {'function': 'uniform'},
51 'params': [
52 { 'name': 'min', 'type': 'number', 'default': 0 },
53 { 'name': 'max', 'type': 'number', 'default': 1 }
54 ]
55 },
56 {
57 'key': {'function': 'kde'},
58 'params': [
59 { 'name': 'field', 'type': 'field', 'required': true },
60 { 'name': 'from', 'type': 'data' },
61 { 'name': 'bandwidth', 'type': 'number', 'default': 0 }
62 ]
63 }
64];
65
66const mixture = {
67 'key': {'function': 'mixture'},
68 'params': [
69 { 'name': 'distributions', 'type': 'param', 'array': true,
70 'params': distributions },
71 { 'name': 'weights', 'type': 'number', 'array': true }
72 ]
73};
74
75Density.Definition = {
76 'type': 'Density',
77 'metadata': {'generates': true},
78 'params': [
79 { 'name': 'extent', 'type': 'number', 'array': true, 'length': 2 },
80 { 'name': 'steps', 'type': 'number' },
81 { 'name': 'minsteps', 'type': 'number', 'default': 25 },
82 { 'name': 'maxsteps', 'type': 'number', 'default': 200 },
83 { 'name': 'method', 'type': 'string', 'default': 'pdf',
84 'values': ['pdf', 'cdf'] },
85 { 'name': 'distribution', 'type': 'param',
86 'params': distributions.concat(mixture) },
87 { 'name': 'as', 'type': 'string', 'array': true,
88 'default': ['value', 'density'] }
89 ]
90};
91
92inherits(Density, Transform, {
93 transform(_, pulse) {
94 const out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS);
95
96 if (!this.value || pulse.changed() || _.modified()) {
97 const dist = parseDist(_.distribution, source(pulse)),
98 minsteps = _.steps || _.minsteps || 25,
99 maxsteps = _.steps || _.maxsteps || 200;
100 let method = _.method || 'pdf';
101
102 if (method !== 'pdf' && method !== 'cdf') {
103 error('Invalid density method: ' + method);
104 }
105 if (!_.extent && !dist.data) {
106 error('Missing density extent parameter.');
107 }
108 method = dist[method];
109
110 const as = _.as || ['value', 'density'],
111 domain = _.extent || extent(dist.data()),
112 values = sampleCurve(method, domain, minsteps, maxsteps)
113 .map(v => {
114 const tuple = {};
115 tuple[as[0]] = v[0];
116 tuple[as[1]] = v[1];
117 return ingest(tuple);
118 });
119
120 if (this.value) out.rem = this.value;
121 this.value = out.add = out.source = values;
122 }
123
124 return out;
125 }
126});
127
128function source(pulse) {
129 return () => pulse.materialize(pulse.SOURCE).source;
130}