svelteplot 0.7.1-pr-280.9 → 0.7.1-pr-280.11
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.
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
* implementation based on science.js by Jason Davies
|
|
3
3
|
*/
|
|
4
4
|
import type { TransformArg } from '../types';
|
|
5
|
-
type
|
|
5
|
+
type Kernel = 'uniform' | 'triangular' | 'epanechnikov' | 'quartic' | 'triweight' | 'gaussian' | 'cosine' | ((u: number) => number);
|
|
6
|
+
type DensityOptions<T> = {
|
|
6
7
|
/**
|
|
7
8
|
* The kernel function to use for smoothing.
|
|
8
9
|
*/
|
|
9
|
-
kernel?:
|
|
10
|
+
kernel?: Kernel;
|
|
10
11
|
/**
|
|
11
12
|
* The bandwidth to use for smoothing. Can be a fixed number or a function that computes the bandwidth based on the data.
|
|
12
13
|
*/
|
|
@@ -19,22 +20,18 @@ type DensityOptions = {
|
|
|
19
20
|
*
|
|
20
21
|
*/
|
|
21
22
|
trim?: boolean;
|
|
23
|
+
weight?: (d: T) => number;
|
|
22
24
|
};
|
|
23
25
|
/**
|
|
24
26
|
* One-dimensional kernel density estimation
|
|
25
27
|
*/
|
|
26
|
-
export declare function densityX<T>(args: TransformArg<T>, options: DensityOptions & {
|
|
28
|
+
export declare function densityX<T>(args: TransformArg<T>, options: DensityOptions<T> & {
|
|
27
29
|
channel?: 'y' | 'y1' | 'y2';
|
|
28
30
|
}): TransformArg<T>;
|
|
29
31
|
/**
|
|
30
32
|
* One-dimensional kernel density estimation
|
|
31
33
|
*/
|
|
32
|
-
export declare function densityY<T>(args: TransformArg<T>, options: DensityOptions & {
|
|
34
|
+
export declare function densityY<T>(args: TransformArg<T>, options: DensityOptions<T> & {
|
|
33
35
|
channel?: 'x' | 'x1' | 'x2';
|
|
34
36
|
}): TransformArg<T>;
|
|
35
|
-
/**
|
|
36
|
-
* TODO: Two-dimensional kernel density estimation
|
|
37
|
-
*
|
|
38
|
-
*/
|
|
39
|
-
export declare function density<T>({ data, ...options }: TransformArg<T>, { kernel, bandwidth }: DensityOptions): TransformArg<T>;
|
|
40
37
|
export {};
|
|
@@ -14,28 +14,7 @@ import { maybeInterval } from '../helpers/autoTicks';
|
|
|
14
14
|
import { groupFacetsAndZ } from '../helpers/group';
|
|
15
15
|
import isDataRecord from '../helpers/isDataRecord';
|
|
16
16
|
import { resolveChannel } from '../helpers/resolve';
|
|
17
|
-
|
|
18
|
-
function gaussian2D(dx, dy, bwX, bwY) {
|
|
19
|
-
return (Math.exp(-0.5 * ((dx * dx) / (bwX * bwX) + (dy * dy) / (bwY * bwY))) /
|
|
20
|
-
(2 * Math.PI * bwX * bwY));
|
|
21
|
-
}
|
|
22
|
-
// kde2d function
|
|
23
|
-
function kde2d(data, evalXs, evalYs, bwX, bwY) {
|
|
24
|
-
const n = data.length;
|
|
25
|
-
const density = [];
|
|
26
|
-
for (let xi = 0; xi < evalXs.length; xi++) {
|
|
27
|
-
for (let yi = 0; yi < evalYs.length; yi++) {
|
|
28
|
-
let sum = 0;
|
|
29
|
-
for (let i = 0; i < n; i++) {
|
|
30
|
-
const dx = evalXs[xi] - data[i][0];
|
|
31
|
-
const dy = evalYs[yi] - data[i][1];
|
|
32
|
-
sum += gaussian2D(dx, dy, bwX, bwY);
|
|
33
|
-
}
|
|
34
|
-
// Store: [x, y, density]
|
|
35
|
-
density.push([evalXs[xi], evalYs[yi], sum / n]);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
17
|
+
import { ORIGINAL_NAME_KEYS } from '../constants.js';
|
|
39
18
|
// see https://github.com/jasondavies/science.js/blob/master/src/stats/kernel.js
|
|
40
19
|
const KERNEL = {
|
|
41
20
|
uniform(u) {
|
|
@@ -112,6 +91,7 @@ function bandwidthSilverman(x) {
|
|
|
112
91
|
return lo * Math.pow(x.length, -0.2);
|
|
113
92
|
}
|
|
114
93
|
const VALUE = Symbol('value');
|
|
94
|
+
const WEIGHT = Symbol('weight');
|
|
115
95
|
function roundToTerminating(x, sig = 2) {
|
|
116
96
|
if (!isFinite(x) || x === 0)
|
|
117
97
|
return x;
|
|
@@ -120,7 +100,7 @@ function roundToTerminating(x, sig = 2) {
|
|
|
120
100
|
const factor = 10 ** decimals; // denominator 10^decimals → terminating decimal
|
|
121
101
|
return Math.round(x * factor) / factor;
|
|
122
102
|
}
|
|
123
|
-
function density1d(independent, { data, ...channels }, options = {}) {
|
|
103
|
+
function density1d(independent, { data, weight, ...channels }, options = {}) {
|
|
124
104
|
const densityChannel = independent === 'x' ? 'y' : 'x';
|
|
125
105
|
const { kernel, bandwidth, interval, trim, channel } = {
|
|
126
106
|
kernel: 'epanechnikov',
|
|
@@ -136,8 +116,15 @@ function density1d(independent, { data, ...channels }, options = {}) {
|
|
|
136
116
|
const isRawDataArray = Array.isArray(data) && !isDataRecord(data[0]) && channels[independent] == null;
|
|
137
117
|
// compute bandwidth before grouping
|
|
138
118
|
const resolvedData = (isRawDataArray
|
|
139
|
-
? data.map((d) => ({
|
|
140
|
-
|
|
119
|
+
? data.map((d) => ({
|
|
120
|
+
[VALUE]: d,
|
|
121
|
+
[WEIGHT]: typeof weight === 'function' ? weight(d) : 1
|
|
122
|
+
}))
|
|
123
|
+
: data.map((d) => ({
|
|
124
|
+
[VALUE]: resolveChannel(independent, d, channels),
|
|
125
|
+
[WEIGHT]: typeof weight === 'function' ? weight(d) : 1,
|
|
126
|
+
...d
|
|
127
|
+
}))).filter((d) => isValid(d[VALUE]) && isValid(d[WEIGHT]) && d[WEIGHT] >= 0);
|
|
141
128
|
const values = resolvedData.map((d) => d[VALUE]);
|
|
142
129
|
// compute bandwidth from full data
|
|
143
130
|
const bw = typeof bandwidth === 'function'
|
|
@@ -155,7 +142,8 @@ function density1d(independent, { data, ...channels }, options = {}) {
|
|
|
155
142
|
// let maxX = -Infinity;
|
|
156
143
|
const res = groupFacetsAndZ(resolvedData, channels, (items, groupProps) => {
|
|
157
144
|
const values = items.map((d) => d[VALUE]);
|
|
158
|
-
|
|
145
|
+
const weights = items.map((d) => d[WEIGHT]);
|
|
146
|
+
let kdeValues = kde1d(values, weights, atValues, k, bw)
|
|
159
147
|
.filter(([x, density]) => x != null && !isNaN(density))
|
|
160
148
|
.sort((a, b) => a[0] - b[0]);
|
|
161
149
|
if (!trim) {
|
|
@@ -177,23 +165,22 @@ function density1d(independent, { data, ...channels }, options = {}) {
|
|
|
177
165
|
[independent]: CHANNELS[independent],
|
|
178
166
|
[channel]: CHANNELS[densityChannel],
|
|
179
167
|
...res,
|
|
168
|
+
[ORIGINAL_NAME_KEYS[densityChannel]]: 'Density',
|
|
169
|
+
[ORIGINAL_NAME_KEYS[independent]]: typeof channels[independent] === 'string' ? channels[independent] : undefined,
|
|
180
170
|
sort: [{ channel: CHANNELS[independent], order: 'ascending' }],
|
|
181
171
|
data: outData
|
|
182
172
|
};
|
|
183
173
|
}
|
|
184
|
-
|
|
185
|
-
* TODO: Two-dimensional kernel density estimation
|
|
186
|
-
*
|
|
187
|
-
*/
|
|
188
|
-
export function density({ data, ...options }, { kernel, bandwidth }) { }
|
|
189
|
-
function kde1d(values, atValues, kernel, bw) {
|
|
174
|
+
function kde1d(values, weights, atValues, kernel, bw) {
|
|
190
175
|
const n = values.length;
|
|
176
|
+
const weightSum = weights.reduce((a, b) => a + b, 0);
|
|
191
177
|
return atValues.map((x) => {
|
|
192
178
|
let sum = 0;
|
|
193
179
|
for (let i = 0; i < n; i++) {
|
|
194
|
-
|
|
180
|
+
const u = (x - values[i]) / bw;
|
|
181
|
+
sum += weights[i] * kernel(u);
|
|
195
182
|
}
|
|
196
|
-
return [x, sum / (
|
|
183
|
+
return [x, sum / (weightSum * bw)];
|
|
197
184
|
});
|
|
198
185
|
}
|
|
199
186
|
function maybeKernel(kernel) {
|