ggaction 0.0.19 → 0.0.20
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/CHANGELOG.md +6 -0
- package/README.md +2 -2
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/package.json +1 -1
- package/src/actions/encodings/color/continuous.js +7 -1
- package/src/actions/guides/legends/categorical/actions.js +2 -2
- package/src/actions/guides/legends/continuous/common.js +1 -1
- package/src/actions/marks/area/materialize.js +1 -190
- package/src/actions/scales/consumers/common.js +7 -1
- package/src/grammar/aggregate.js +3 -2
- package/src/grammar/areaSeries.js +1 -415
- package/src/grammar/interval.js +22 -6
- package/src/grammar/pathSeries.js +2 -2
- package/src/grammar/scales/colorConsumers.js +5 -2
- package/src/grammar/statistics/confidenceInterval.js +1 -101
- package/src/grammar/summary.js +1 -320
- package/src/materialization/marks/grouping.js +1 -1
- package/src/version.js +1 -1
- package/types/index.d.ts +2 -0
- package/types/program.d.ts +14 -3
package/src/grammar/interval.js
CHANGED
|
@@ -4,6 +4,9 @@ import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
|
4
4
|
import { aggregateScalarValues } from "./aggregate.js";
|
|
5
5
|
import { requireFiniteResult, stableDecimal } from "./numeric.js";
|
|
6
6
|
import {
|
|
7
|
+
createBootstrapMeanSampler,
|
|
8
|
+
bootstrapMeanBounds,
|
|
9
|
+
validateBootstrapDrawCount,
|
|
7
10
|
confidenceCriticalValue,
|
|
8
11
|
normalizeConfidenceInterval
|
|
9
12
|
} from "./statistics/confidenceInterval.js";
|
|
@@ -91,7 +94,8 @@ export function validateIntervalTransform(transform) {
|
|
|
91
94
|
if (transform.extent === "ci") {
|
|
92
95
|
normalizeConfidenceInterval(transform, {
|
|
93
96
|
defaultMethod: "student-t",
|
|
94
|
-
label: "Interval CI"
|
|
97
|
+
label: "Interval CI",
|
|
98
|
+
allowBootstrap: true
|
|
95
99
|
});
|
|
96
100
|
} else if (transform.method !== undefined) {
|
|
97
101
|
throw new Error("Interval method is supported only for ci extent.");
|
|
@@ -117,11 +121,12 @@ export function normalizeIntervalParameters({
|
|
|
117
121
|
extent = "ci",
|
|
118
122
|
method,
|
|
119
123
|
level
|
|
120
|
-
} = {}) {
|
|
124
|
+
} = {}, { allowBootstrap = false } = {}) {
|
|
121
125
|
const confidence = extent === "ci"
|
|
122
126
|
? normalizeConfidenceInterval({ method, level }, {
|
|
123
127
|
defaultMethod: "student-t",
|
|
124
|
-
label: "Interval CI"
|
|
128
|
+
label: "Interval CI",
|
|
129
|
+
allowBootstrap
|
|
125
130
|
})
|
|
126
131
|
: undefined;
|
|
127
132
|
const candidate = {
|
|
@@ -164,7 +169,11 @@ function isMissing(value) {
|
|
|
164
169
|
}
|
|
165
170
|
|
|
166
171
|
|
|
167
|
-
function deriveGroup(values, transform) {
|
|
172
|
+
function deriveGroup(values, transform, bootstrapSampler) {
|
|
173
|
+
if (bootstrapSampler !== undefined) {
|
|
174
|
+
const bounds = bootstrapMeanBounds(bootstrapSampler(values), transform.level);
|
|
175
|
+
return bounds === undefined ? undefined : { center: aggregateScalarValues(values, "mean"), ...bounds };
|
|
176
|
+
}
|
|
168
177
|
if (transform.center === "median") {
|
|
169
178
|
if (values.length === 0) return undefined;
|
|
170
179
|
return {
|
|
@@ -235,9 +244,16 @@ export function deriveInterval(rows, transform) {
|
|
|
235
244
|
groups.get(key).values.push(value);
|
|
236
245
|
}
|
|
237
246
|
|
|
247
|
+
const bootstrapMethod = transform.method?.type === "bootstrap"
|
|
248
|
+
? normalizeConfidenceInterval(transform, { defaultMethod: "student-t", allowBootstrap: true }).method : undefined;
|
|
249
|
+
if (bootstrapMethod !== undefined) {
|
|
250
|
+
validateBootstrapDrawCount(bootstrapMethod.samples * [...groups.values()].reduce((count, group) => count + group.values.length, 0));
|
|
251
|
+
}
|
|
252
|
+
const bootstrapSampler = bootstrapMethod === undefined ? undefined : createBootstrapMeanSampler(bootstrapMethod);
|
|
253
|
+
|
|
238
254
|
const output = [];
|
|
239
255
|
for (const group of groups.values()) {
|
|
240
|
-
const result = deriveGroup(group.values, transform);
|
|
256
|
+
const result = deriveGroup(group.values, transform, bootstrapSampler);
|
|
241
257
|
if (result === undefined) continue;
|
|
242
258
|
output.push({
|
|
243
259
|
...group.fields,
|
|
@@ -277,7 +293,7 @@ export function normalizeIntervalTransform({
|
|
|
277
293
|
}) {
|
|
278
294
|
nonEmptyString(field, "Interval field");
|
|
279
295
|
const grouping = normalizeGrouping(groupBy);
|
|
280
|
-
const parameters = normalizeIntervalParameters({ center, extent, method, level });
|
|
296
|
+
const parameters = normalizeIntervalParameters({ center, extent, method, level }, { allowBootstrap: true });
|
|
281
297
|
const transform = {
|
|
282
298
|
type: "interval",
|
|
283
299
|
field,
|
|
@@ -42,7 +42,7 @@ export function readPathSeriesFields(rows, layer) {
|
|
|
42
42
|
|
|
43
43
|
function seriesValueIndex(rows, fields, field, channel, encoding = {}) {
|
|
44
44
|
const quantitative = channel === "strokeWidth" || channel === "opacity";
|
|
45
|
-
const values = channel === "stroke"
|
|
45
|
+
const values = channel === "stroke" || (channel === "color" && encoding.fieldType === "quantitative")
|
|
46
46
|
? readScaleField(rows, field, encoding.fieldType ?? "nominal", {
|
|
47
47
|
temporalUnit: encoding.temporalUnit
|
|
48
48
|
})
|
|
@@ -88,7 +88,7 @@ export function validatePathSeriesAppearance(rows, layer) {
|
|
|
88
88
|
if (encoding?.field === undefined) continue;
|
|
89
89
|
const allowed = ["color", "stroke"].includes(channel)
|
|
90
90
|
? channel === "color"
|
|
91
|
-
? ["nominal", "ordinal"]
|
|
91
|
+
? layer.mark.type === "area" ? ["nominal", "ordinal", "quantitative"] : ["nominal", "ordinal"]
|
|
92
92
|
: ["nominal", "ordinal", "quantitative", "temporal"]
|
|
93
93
|
: channel === "strokeDash" ? ["nominal"] : ["quantitative"];
|
|
94
94
|
if (!allowed.includes(encoding.fieldType)) {
|
|
@@ -26,8 +26,8 @@ export function validateContinuousColorConsumer(layer, encoding, scale, {
|
|
|
26
26
|
}
|
|
27
27
|
return undefined;
|
|
28
28
|
}
|
|
29
|
-
if (!["point", "bar", "rect", "text"].includes(kind)) {
|
|
30
|
-
throw new Error("Continuous color requires a Point, aggregate Bar, Rect, or
|
|
29
|
+
if (!["point", "bar", "rect", "text", "area"].includes(kind)) {
|
|
30
|
+
throw new Error("Continuous color requires a Point, aggregate Bar, Rect, Text, or quantitative Area consumer.");
|
|
31
31
|
}
|
|
32
32
|
if (!["quantitative", "temporal"].includes(encoding.fieldType) ||
|
|
33
33
|
(isDiscretizedColorScaleType(scale.type) && encoding.fieldType !== "quantitative")) {
|
|
@@ -36,6 +36,9 @@ export function validateContinuousColorConsumer(layer, encoding, scale, {
|
|
|
36
36
|
if (Object.hasOwn(scale, "unknown") && kind !== "point") {
|
|
37
37
|
throw new Error("Continuous color scale unknown currently requires a row-owned point mark.");
|
|
38
38
|
}
|
|
39
|
+
if (kind === "area" && encoding.fieldType !== "quantitative") {
|
|
40
|
+
throw new Error("Continuous area color requires a quantitative field.");
|
|
41
|
+
}
|
|
39
42
|
if (kind !== "bar") {
|
|
40
43
|
if (encoding.aggregate !== undefined) throw new Error(`${kind} continuous color does not support aggregate.`);
|
|
41
44
|
return undefined;
|
|
@@ -1,101 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { studentTCriticalValue } from "./studentT.js";
|
|
3
|
-
|
|
4
|
-
export const CONFIDENCE_INTERVAL_METHODS = Object.freeze([
|
|
5
|
-
"normal",
|
|
6
|
-
"student-t"
|
|
7
|
-
]);
|
|
8
|
-
|
|
9
|
-
function validateMethod(method, label) {
|
|
10
|
-
if (!CONFIDENCE_INTERVAL_METHODS.includes(method)) {
|
|
11
|
-
throw new Error(`Unsupported ${label} method "${method}".`);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function validateLevel(level, label) {
|
|
16
|
-
if (!Number.isFinite(level) || level <= 0 || level >= 1) {
|
|
17
|
-
throw new RangeError(`${label} level must be between 0 and 1.`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Peter J. Acklam's inverse-normal approximation. The exact 95% compatibility
|
|
22
|
-
// value is handled by normalCriticalValue before this approximation is used.
|
|
23
|
-
function inverseNormal(probability) {
|
|
24
|
-
const a = [
|
|
25
|
-
-3.969683028665376e1, 2.209460984245205e2,
|
|
26
|
-
-2.759285104469687e2, 1.38357751867269e2,
|
|
27
|
-
-3.066479806614716e1, 2.506628277459239
|
|
28
|
-
];
|
|
29
|
-
const b = [
|
|
30
|
-
-5.447609879822406e1, 1.615858368580409e2,
|
|
31
|
-
-1.556989798598866e2, 6.680131188771972e1,
|
|
32
|
-
-1.328068155288572e1
|
|
33
|
-
];
|
|
34
|
-
const c = [
|
|
35
|
-
-7.784894002430293e-3, -3.223964580411365e-1,
|
|
36
|
-
-2.400758277161838, -2.549732539343734,
|
|
37
|
-
4.374664141464968, 2.938163982698783
|
|
38
|
-
];
|
|
39
|
-
const d = [
|
|
40
|
-
7.784695709041462e-3, 3.224671290700398e-1,
|
|
41
|
-
2.445134137142996, 3.754408661907416
|
|
42
|
-
];
|
|
43
|
-
const low = 0.02425;
|
|
44
|
-
const high = 1 - low;
|
|
45
|
-
if (probability < low) {
|
|
46
|
-
const q = Math.sqrt(-2 * Math.log(probability));
|
|
47
|
-
return (((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) /
|
|
48
|
-
((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1);
|
|
49
|
-
}
|
|
50
|
-
if (probability > high) {
|
|
51
|
-
const q = Math.sqrt(-2 * Math.log(1 - probability));
|
|
52
|
-
return -(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) /
|
|
53
|
-
((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1);
|
|
54
|
-
}
|
|
55
|
-
const q = probability - 0.5;
|
|
56
|
-
const r = q * q;
|
|
57
|
-
return (((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q /
|
|
58
|
-
(((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function normalizeConfidenceInterval({
|
|
62
|
-
method,
|
|
63
|
-
level
|
|
64
|
-
} = {}, {
|
|
65
|
-
defaultMethod,
|
|
66
|
-
defaultLevel = 0.95,
|
|
67
|
-
label = "Confidence interval"
|
|
68
|
-
} = {}) {
|
|
69
|
-
validateMethod(defaultMethod, label);
|
|
70
|
-
const resolvedMethod = method ?? defaultMethod;
|
|
71
|
-
const resolvedLevel = level ?? defaultLevel;
|
|
72
|
-
validateMethod(resolvedMethod, label);
|
|
73
|
-
validateLevel(resolvedLevel, label);
|
|
74
|
-
return cloneAndFreeze({ method: resolvedMethod, level: resolvedLevel });
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function normalCriticalValue(level) {
|
|
78
|
-
validateLevel(level, "Normal confidence interval");
|
|
79
|
-
if (level === 0.95) return 1.96;
|
|
80
|
-
return inverseNormal((1 + level) / 2);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function confidenceCriticalValue({
|
|
84
|
-
method,
|
|
85
|
-
level,
|
|
86
|
-
degreesOfFreedom
|
|
87
|
-
}) {
|
|
88
|
-
const normalized = normalizeConfidenceInterval(
|
|
89
|
-
{ method, level },
|
|
90
|
-
{ defaultMethod: method, label: "Confidence interval" }
|
|
91
|
-
);
|
|
92
|
-
if (normalized.method === "normal") {
|
|
93
|
-
return normalCriticalValue(normalized.level);
|
|
94
|
-
}
|
|
95
|
-
if (!Number.isInteger(degreesOfFreedom) || degreesOfFreedom < 1) {
|
|
96
|
-
throw new RangeError(
|
|
97
|
-
"Student-t confidence interval degreesOfFreedom must be a positive integer."
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
return studentTCriticalValue(normalized.level, degreesOfFreedom);
|
|
101
|
-
}
|
|
1
|
+
import{quantileSorted as d,requireFiniteResult as c}from"../numeric.js";import{cloneAndFreeze as m,isPlainObject as w}from"../../core/immutable.js";import{studentTCriticalValue as g}from"./studentT.js";const h=Object.freeze(["normal","student-t"]);function l(e,n,r=!1){if(r&&w(e)){const t=Object.keys(e).find(a=>!["type","seed","samples"].includes(a));if(t!==void 0)throw new Error(`Unknown bootstrap method property "${t}".`);if(e.type!=="bootstrap")throw new Error(`Unsupported ${n} method type.`);if(!Number.isInteger(e.seed)||e.seed<1||e.seed>2147483646)throw new RangeError("Bootstrap seed must be an integer between 1 and 2147483646.");const o=e.samples===void 0?1e3:e.samples;if(!Number.isInteger(o)||o<1||o>1e4)throw new RangeError("Bootstrap samples must be an integer between 1 and 10000.");return{type:"bootstrap",seed:e.seed,samples:o}}if(!h.includes(e))throw new Error(`Unsupported ${n} method "${e}".`);return e}function f(e,n){if(!Number.isFinite(e)||e<=0||e>=1)throw new RangeError(`${n} level must be between 0 and 1.`)}function E(e){const n=[-39.69683028665376,220.9460984245205,-275.9285104469687,138.357751867269,-30.66479806614716,2.506628277459239],r=[-54.47609879822406,161.5858368580409,-155.6989798598866,66.80131188771972,-13.28068155288572],t=[-.007784894002430293,-.3223964580411365,-2.400758277161838,-2.549732539343734,4.374664141464968,2.938163982698783],o=[.007784695709041462,.3224671290700398,2.445134137142996,3.754408661907416],a=.02425,u=1-a;if(e<a){const s=Math.sqrt(-2*Math.log(e));return(((((t[0]*s+t[1])*s+t[2])*s+t[3])*s+t[4])*s+t[5])/((((o[0]*s+o[1])*s+o[2])*s+o[3])*s+1)}if(e>u){const s=Math.sqrt(-2*Math.log(1-e));return-(((((t[0]*s+t[1])*s+t[2])*s+t[3])*s+t[4])*s+t[5])/((((o[0]*s+o[1])*s+o[2])*s+o[3])*s+1)}const p=e-.5,i=p*p;return(((((n[0]*i+n[1])*i+n[2])*i+n[3])*i+n[4])*i+n[5])*p/(((((r[0]*i+r[1])*i+r[2])*i+r[3])*i+r[4])*i+1)}function b({method:e,level:n}={},{defaultMethod:r,defaultLevel:t=.95,label:o="Confidence interval",allowBootstrap:a=!1}={}){l(r,o);const u=l(e??r,o,a),p=n??t;return f(p,o),m({method:u,level:p})}function B(e){return f(e,"Normal confidence interval"),e===.95?1.96:E((1+e)/2)}function q({method:e,level:n,degreesOfFreedom:r}){const t=b({method:e,level:n},{defaultMethod:e,label:"Confidence interval"});if(t.method==="normal")return B(t.level);if(!Number.isInteger(r)||r<1)throw new RangeError("Student-t confidence interval degreesOfFreedom must be a positive integer.");return g(t.level,r)}function x(e){if(!Number.isSafeInteger(e)||e<0||e>5e7)throw new RangeError("Bootstrap resampling cannot exceed 50000000 draws per action.")}function M(e){const n=l(e,"Bootstrap mean interval",!0);if(n?.type!=="bootstrap")throw new Error("Bootstrap sampler requires a bootstrap method.");let r=n.seed;return t=>{if(t.length===0)return[];const o=[];for(let a=0;a<n.samples;a+=1){let u=0;for(let p=0;p<t.length;p+=1)r=(1103515245*r+12345)%2147483647,u+=t[Math.floor(r/2147483647*t.length)];o.push(c(u/t.length,"Bootstrap sample mean"))}return o.sort((a,u)=>a-u)}}function I(e,n=.95){if(f(n,"Bootstrap mean interval"),e.length===0)return;const r=(1-n)/2;return{lower:c(d(e,r),"Bootstrap lower endpoint"),upper:c(d(e,1-r),"Bootstrap upper endpoint")}}export{h as CONFIDENCE_INTERVAL_METHODS,I as bootstrapMeanBounds,q as confidenceCriticalValue,M as createBootstrapMeanSampler,B as normalCriticalValue,b as normalizeConfidenceInterval,x as validateBootstrapDrawCount};
|
package/src/grammar/summary.js
CHANGED
|
@@ -1,320 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { rejectUnknownProperties as rejectUnknownKeys } from "../core/validation.js";
|
|
3
|
-
import { requireStringValue as requireField } from "../core/validation.js";
|
|
4
|
-
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
5
|
-
import {
|
|
6
|
-
aggregateRows,
|
|
7
|
-
isScalarAggregate,
|
|
8
|
-
validateAggregate,
|
|
9
|
-
validateAggregateFieldValues
|
|
10
|
-
} from "./aggregate.js";
|
|
11
|
-
import {
|
|
12
|
-
calculateWeightedAggregate,
|
|
13
|
-
normalizeStatisticalWeight,
|
|
14
|
-
readStatisticalWeights,
|
|
15
|
-
summarizeStatisticalWeights,
|
|
16
|
-
validateWeightedAggregate,
|
|
17
|
-
validateWeightedNumericFields,
|
|
18
|
-
weightedRows
|
|
19
|
-
} from "./weightedStatistics.js";
|
|
20
|
-
|
|
21
|
-
const TRANSFORM_KEYS = Object.freeze([
|
|
22
|
-
"type", "groupBy", "aggregates", "members", "weight", "missing", "empty"
|
|
23
|
-
]);
|
|
24
|
-
const AGGREGATE_KEYS = Object.freeze(["op", "field", "as"]);
|
|
25
|
-
const NOMINAL_OPERATIONS = new Set(["distinct", "valid", "missing"]);
|
|
26
|
-
const MAX_OUTPUT_ROWS = 30_000;
|
|
27
|
-
|
|
28
|
-
function normalizeGroupBy(value) {
|
|
29
|
-
if (value === undefined) return [];
|
|
30
|
-
return Array.isArray(value) ? [...value] : [value];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function normalizeAggregate(value) {
|
|
34
|
-
if (!isPlainObject(value)) return value;
|
|
35
|
-
return {
|
|
36
|
-
...value,
|
|
37
|
-
op: validateAggregate(value.op),
|
|
38
|
-
...(Object.hasOwn(value, "field") ? { field: value.field } : {}),
|
|
39
|
-
as: value.as
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function normalizeSummaryTransform({ groupBy, aggregates, members, weight, missing, empty } = {}) {
|
|
44
|
-
const transform = {
|
|
45
|
-
type: "summary",
|
|
46
|
-
groupBy: normalizeGroupBy(groupBy),
|
|
47
|
-
aggregates: Array.isArray(aggregates)
|
|
48
|
-
? aggregates.map(normalizeAggregate)
|
|
49
|
-
: aggregates,
|
|
50
|
-
...(members === undefined ? {} : { members }),
|
|
51
|
-
...(weight === undefined
|
|
52
|
-
? {}
|
|
53
|
-
: { weight: normalizeStatisticalWeight(weight, "Summary weight") }),
|
|
54
|
-
...(missing === undefined ? {} : { missing }),
|
|
55
|
-
...(empty === undefined ? {} : { empty })
|
|
56
|
-
};
|
|
57
|
-
validateSummaryTransform(transform);
|
|
58
|
-
return cloneAndFreeze(transform);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function validateSummaryTransform(transform) {
|
|
62
|
-
if (!isPlainObject(transform)) {
|
|
63
|
-
throw new TypeError("Summary transform must be a plain object.");
|
|
64
|
-
}
|
|
65
|
-
rejectUnknownKeys(transform, TRANSFORM_KEYS, "summary transform");
|
|
66
|
-
if (transform.type !== "summary") {
|
|
67
|
-
throw new Error(`Unsupported summary transform "${transform.type}".`);
|
|
68
|
-
}
|
|
69
|
-
if (
|
|
70
|
-
!Array.isArray(transform.groupBy) ||
|
|
71
|
-
transform.groupBy.some(field => typeof field !== "string" || field.length === 0)
|
|
72
|
-
) {
|
|
73
|
-
throw new TypeError("Summary groupBy must contain field names.");
|
|
74
|
-
}
|
|
75
|
-
if (new Set(transform.groupBy).size !== transform.groupBy.length) {
|
|
76
|
-
throw new Error("Summary groupBy fields must be unique.");
|
|
77
|
-
}
|
|
78
|
-
if (!Array.isArray(transform.aggregates) || transform.aggregates.length === 0) {
|
|
79
|
-
throw new TypeError("Summary aggregates must be a non-empty array.");
|
|
80
|
-
}
|
|
81
|
-
if (transform.aggregates.length > 64) {
|
|
82
|
-
throw new RangeError("Summary aggregates cannot contain more than 64 outputs.");
|
|
83
|
-
}
|
|
84
|
-
const outputs = new Set(transform.groupBy);
|
|
85
|
-
transform.aggregates.forEach((aggregate, index) => {
|
|
86
|
-
if (!isPlainObject(aggregate)) {
|
|
87
|
-
throw new TypeError(`Summary aggregate ${index} must be a plain object.`);
|
|
88
|
-
}
|
|
89
|
-
rejectUnknownKeys(aggregate, AGGREGATE_KEYS, `summary aggregate ${index}`);
|
|
90
|
-
const operation = validateAggregate(aggregate.op);
|
|
91
|
-
if (transform.weight !== undefined) validateWeightedAggregate(operation);
|
|
92
|
-
if (operation === "count") {
|
|
93
|
-
if (Object.hasOwn(aggregate, "field")) {
|
|
94
|
-
throw new Error("Summary count does not accept a field.");
|
|
95
|
-
}
|
|
96
|
-
} else {
|
|
97
|
-
requireField(aggregate.field, `Summary aggregate ${index} field`);
|
|
98
|
-
}
|
|
99
|
-
const output = requireField(aggregate.as, `Summary aggregate ${index} as`);
|
|
100
|
-
if (outputs.has(output)) {
|
|
101
|
-
throw new Error(`Summary output field "${output}" collides with another output.`);
|
|
102
|
-
}
|
|
103
|
-
outputs.add(output);
|
|
104
|
-
});
|
|
105
|
-
if (transform.members !== undefined) {
|
|
106
|
-
const members = requireField(transform.members, "Summary members");
|
|
107
|
-
if (outputs.has(members)) {
|
|
108
|
-
throw new Error(`Summary members field "${members}" collides with another output.`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
if (transform.weight !== undefined) {
|
|
112
|
-
normalizeStatisticalWeight(transform.weight, "Summary weight");
|
|
113
|
-
}
|
|
114
|
-
if (transform.missing !== undefined && !["error", "drop"].includes(transform.missing)) {
|
|
115
|
-
throw new Error('Summary missing must be "error" or "drop".');
|
|
116
|
-
}
|
|
117
|
-
if (transform.empty !== undefined && !["null", "identity"].includes(transform.empty)) {
|
|
118
|
-
throw new Error('Summary empty must be "null" or "identity".');
|
|
119
|
-
}
|
|
120
|
-
return transform;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function requireSourceFields(rows, transform) {
|
|
124
|
-
if (rows.length === 0) return;
|
|
125
|
-
const fields = new Set(rows.flatMap(row => Object.keys(row)));
|
|
126
|
-
for (const field of transform.groupBy) {
|
|
127
|
-
if (!fields.has(field)) {
|
|
128
|
-
throw new Error(`Summary source does not contain group field "${field}".`);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
for (const aggregate of transform.aggregates) {
|
|
132
|
-
if (aggregate.field !== undefined && !fields.has(aggregate.field)) {
|
|
133
|
-
throw new Error(`Summary source does not contain aggregate field "${aggregate.field}".`);
|
|
134
|
-
}
|
|
135
|
-
const orderBy = isPlainObject(aggregate.op) ? aggregate.op.orderBy : undefined;
|
|
136
|
-
if (orderBy !== undefined && !fields.has(orderBy)) {
|
|
137
|
-
throw new Error(`Summary source does not contain order field "${orderBy}".`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
if (transform.weight !== undefined && !fields.has(transform.weight.field)) {
|
|
141
|
-
throw new Error(`Summary source does not contain weight field "${transform.weight.field}".`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function validateAggregateValues(rows, aggregate) {
|
|
146
|
-
if (aggregate.op === "count") return;
|
|
147
|
-
if (isScalarAggregate(aggregate.op) && NOMINAL_OPERATIONS.has(aggregate.op)) {
|
|
148
|
-
validateAggregateFieldValues(rows, aggregate.field, "nominal");
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
validateAggregateFieldValues(rows, aggregate.field, "quantitative");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function deriveSummaryRows(rows, transform) {
|
|
155
|
-
validateSummaryTransform(transform);
|
|
156
|
-
requireSourceFields(rows, transform);
|
|
157
|
-
const explicitWeighted = transform.weight !== undefined && transform.missing !== undefined;
|
|
158
|
-
let weightEntries;
|
|
159
|
-
if (transform.weight === undefined) {
|
|
160
|
-
for (const aggregate of transform.aggregates) {
|
|
161
|
-
validateAggregateValues(rows, aggregate);
|
|
162
|
-
}
|
|
163
|
-
} else if (!explicitWeighted) {
|
|
164
|
-
weightEntries = readStatisticalWeights(rows, transform.weight, "Summary").entries;
|
|
165
|
-
validateWeightedNumericFields(
|
|
166
|
-
weightEntries,
|
|
167
|
-
[...new Set(transform.aggregates.flatMap(aggregate =>
|
|
168
|
-
aggregate.field === undefined ? [] : [aggregate.field]
|
|
169
|
-
))],
|
|
170
|
-
"Summary"
|
|
171
|
-
);
|
|
172
|
-
} else {
|
|
173
|
-
// Explicit missing policy is evaluated per measure. Validate every
|
|
174
|
-
// nonmissing value now, while leaving nullish value/weight pairs for the
|
|
175
|
-
// reason-priority accounting below.
|
|
176
|
-
readStatisticalWeights(
|
|
177
|
-
rows.filter(row => row[transform.weight.field] !== null && row[transform.weight.field] !== undefined),
|
|
178
|
-
transform.weight,
|
|
179
|
-
"Summary"
|
|
180
|
-
);
|
|
181
|
-
for (const aggregate of transform.aggregates) {
|
|
182
|
-
if (aggregate.field === undefined) continue;
|
|
183
|
-
validateAggregateValues(
|
|
184
|
-
rows.filter(row => row[aggregate.field] !== null && row[aggregate.field] !== undefined),
|
|
185
|
-
aggregate
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
if (transform.missing === "error") {
|
|
190
|
-
if (transform.weight !== undefined) {
|
|
191
|
-
const weightIndex = rows.findIndex(row =>
|
|
192
|
-
row[transform.weight.field] === null || row[transform.weight.field] === undefined
|
|
193
|
-
);
|
|
194
|
-
if (weightIndex !== -1) {
|
|
195
|
-
throw new TypeError(`Summary weight "${transform.weight.field}" is missing at row ${weightIndex}.`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
for (const aggregate of transform.aggregates) {
|
|
199
|
-
if (aggregate.field === undefined) continue;
|
|
200
|
-
const index = rows.findIndex(row => row[aggregate.field] === null || row[aggregate.field] === undefined);
|
|
201
|
-
if (index !== -1) throw new TypeError(`Summary field "${aggregate.field}" is missing at row ${index}.`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const groups = new Map();
|
|
206
|
-
if (transform.groupBy.length === 0) {
|
|
207
|
-
groups.set("all", {
|
|
208
|
-
values: {},
|
|
209
|
-
rows,
|
|
210
|
-
...(weightEntries === undefined ? {} : { entries: weightEntries })
|
|
211
|
-
});
|
|
212
|
-
} else {
|
|
213
|
-
rows.forEach((row, index) => {
|
|
214
|
-
const values = Object.fromEntries(transform.groupBy.map(field => [field, row[field]]));
|
|
215
|
-
const key = transform.groupBy.map(field =>
|
|
216
|
-
scalarKey(row[field], `Summary group field "${field}"`)
|
|
217
|
-
).join("|");
|
|
218
|
-
const group = groups.get(key) ?? {
|
|
219
|
-
values,
|
|
220
|
-
rows: [],
|
|
221
|
-
...(weightEntries === undefined ? {} : { entries: [] })
|
|
222
|
-
};
|
|
223
|
-
group.rows.push(row);
|
|
224
|
-
if (weightEntries !== undefined) group.entries.push(weightEntries[index]);
|
|
225
|
-
groups.set(key, group);
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
if (groups.size > MAX_OUTPUT_ROWS) {
|
|
229
|
-
throw new RangeError(`Summary output cannot exceed ${MAX_OUTPUT_ROWS} groups.`);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const units = [];
|
|
233
|
-
const values = [...groups.values()].map(group => {
|
|
234
|
-
const sharedWeightSummary = transform.weight === undefined || explicitWeighted
|
|
235
|
-
? undefined
|
|
236
|
-
: summarizeStatisticalWeights(
|
|
237
|
-
group.entries,
|
|
238
|
-
transform.weight.kind,
|
|
239
|
-
"Summary group"
|
|
240
|
-
);
|
|
241
|
-
const aggregated = Object.fromEntries(transform.aggregates.map(aggregate => {
|
|
242
|
-
let missingValue = 0;
|
|
243
|
-
let missingWeight = 0;
|
|
244
|
-
let zeroWeightRows = 0;
|
|
245
|
-
let measureWeightSummary = sharedWeightSummary;
|
|
246
|
-
if (explicitWeighted) {
|
|
247
|
-
const eligible = group.rows.filter(row => {
|
|
248
|
-
if (aggregate.field !== undefined &&
|
|
249
|
-
(row[aggregate.field] === null || row[aggregate.field] === undefined)) {
|
|
250
|
-
missingValue += 1;
|
|
251
|
-
return false;
|
|
252
|
-
}
|
|
253
|
-
if (row[transform.weight.field] === null || row[transform.weight.field] === undefined) {
|
|
254
|
-
missingWeight += 1;
|
|
255
|
-
return false;
|
|
256
|
-
}
|
|
257
|
-
return true;
|
|
258
|
-
});
|
|
259
|
-
const entries = readStatisticalWeights(eligible, transform.weight, "Summary").entries;
|
|
260
|
-
zeroWeightRows = entries.filter(entry => entry.weight === 0).length;
|
|
261
|
-
measureWeightSummary = summarizeStatisticalWeights(
|
|
262
|
-
entries,
|
|
263
|
-
transform.weight.kind,
|
|
264
|
-
"Summary group"
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
let value = measureWeightSummary === undefined
|
|
268
|
-
? aggregateRows(group.rows, aggregate.field ?? "__row", aggregate.op)
|
|
269
|
-
: calculateWeightedAggregate(
|
|
270
|
-
measureWeightSummary,
|
|
271
|
-
aggregate.field,
|
|
272
|
-
aggregate.op,
|
|
273
|
-
`Summary aggregate "${aggregate.as}"`
|
|
274
|
-
);
|
|
275
|
-
if (value === undefined && transform.empty !== undefined) {
|
|
276
|
-
const op = typeof aggregate.op === "string" ? aggregate.op : aggregate.op.op;
|
|
277
|
-
value = transform.empty === "identity" && op === "sum" ? 0 : null;
|
|
278
|
-
}
|
|
279
|
-
if (transform.missing !== undefined || transform.empty !== undefined) {
|
|
280
|
-
const missing = explicitWeighted
|
|
281
|
-
? missingValue + missingWeight
|
|
282
|
-
: aggregate.field === undefined ? 0 : group.rows.filter(row => row[aggregate.field] === null || row[aggregate.field] === undefined).length;
|
|
283
|
-
units.push({
|
|
284
|
-
role: aggregate.as,
|
|
285
|
-
group: group.values,
|
|
286
|
-
inputRows: group.rows.length,
|
|
287
|
-
usedRows: group.rows.length - missing,
|
|
288
|
-
excludedRows: missing,
|
|
289
|
-
excludedByReason: missing === 0 ? {} : {
|
|
290
|
-
...(missingValue === 0 && explicitWeighted ? {} : { "missing-value": explicitWeighted ? missingValue : missing }),
|
|
291
|
-
...(missingWeight === 0 ? {} : { "missing-weight": missingWeight })
|
|
292
|
-
},
|
|
293
|
-
...(explicitWeighted ? { zeroWeightRows } : {})
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
return [aggregate.as, value];
|
|
297
|
-
}));
|
|
298
|
-
return {
|
|
299
|
-
...group.values,
|
|
300
|
-
...aggregated,
|
|
301
|
-
...(transform.members === undefined
|
|
302
|
-
? {}
|
|
303
|
-
: {
|
|
304
|
-
[transform.members]: sharedWeightSummary === undefined
|
|
305
|
-
? group.rows
|
|
306
|
-
: weightedRows(sharedWeightSummary)
|
|
307
|
-
})
|
|
308
|
-
};
|
|
309
|
-
});
|
|
310
|
-
if (transform.missing === undefined && transform.empty === undefined) return values;
|
|
311
|
-
return {
|
|
312
|
-
values,
|
|
313
|
-
report: cloneAndFreeze({
|
|
314
|
-
version: 1,
|
|
315
|
-
owner: { kind: "data", id: "pending" },
|
|
316
|
-
inputs: [],
|
|
317
|
-
units
|
|
318
|
-
})
|
|
319
|
-
};
|
|
320
|
-
}
|
|
1
|
+
import{createBootstrapMeanSampler as q,bootstrapMeanBounds as I,validateBootstrapDrawCount as U}from"./statistics/confidenceInterval.js";import{scalarKey as V}from"./transformKeys.js";import{rejectUnknownProperties as W}from"../core/validation.js";import{requireStringValue as E}from"../core/validation.js";import{cloneAndFreeze as v,isPlainObject as g}from"../core/immutable.js";import{aggregateRows as G,isScalarAggregate as K,validateAggregate as B,validateAggregateFieldValues as z}from"./aggregate.js";import{calculateWeightedAggregate as P,normalizeStatisticalWeight as M,readStatisticalWeights as A,summarizeStatisticalWeights as k,validateWeightedAggregate as J,validateWeightedNumericFields as L,weightedRows as Y}from"./weightedStatistics.js";const C=Object.freeze(["type","groupBy","aggregates","members","weight","missing","empty"]),D=Object.freeze(["op","field","as"]),X=new Set(["distinct","valid","missing"]),N=3e4;function H(e){return e===void 0?[]:Array.isArray(e)?[...e]:[e]}function Q(e){return g(e)?{...e,op:B(e.op,{allowBootstrap:!0}),...Object.hasOwn(e,"field")?{field:e.field}:{},as:e.as}:e}function ue({groupBy:e,aggregates:i,members:n,weight:o,missing:u,empty:l}={}){const m={type:"summary",groupBy:H(e),aggregates:Array.isArray(i)?i.map(Q):i,...n===void 0?{}:{members:n},...o===void 0?{}:{weight:M(o,"Summary weight")},...u===void 0?{}:{missing:u},...l===void 0?{}:{empty:l}};return x(m),v(m)}function x(e){if(!g(e))throw new TypeError("Summary transform must be a plain object.");if(W(e,C,"summary transform"),e.type!=="summary")throw new Error(`Unsupported summary transform "${e.type}".`);if(!Array.isArray(e.groupBy)||e.groupBy.some(n=>typeof n!="string"||n.length===0))throw new TypeError("Summary groupBy must contain field names.");if(new Set(e.groupBy).size!==e.groupBy.length)throw new Error("Summary groupBy fields must be unique.");if(!Array.isArray(e.aggregates)||e.aggregates.length===0)throw new TypeError("Summary aggregates must be a non-empty array.");if(e.aggregates.length>64)throw new RangeError("Summary aggregates cannot contain more than 64 outputs.");const i=new Set(e.groupBy);if(e.aggregates.forEach((n,o)=>{if(!g(n))throw new TypeError(`Summary aggregate ${o} must be a plain object.`);W(n,D,`summary aggregate ${o}`);const u=B(n.op,{allowBootstrap:!0});if(e.weight!==void 0&&J(u),u==="count"){if(Object.hasOwn(n,"field"))throw new Error("Summary count does not accept a field.")}else E(n.field,`Summary aggregate ${o} field`);const l=E(n.as,`Summary aggregate ${o} as`);if(i.has(l))throw new Error(`Summary output field "${l}" collides with another output.`);i.add(l)}),e.members!==void 0){const n=E(e.members,"Summary members");if(i.has(n))throw new Error(`Summary members field "${n}" collides with another output.`)}if(e.weight!==void 0&&M(e.weight,"Summary weight"),e.missing!==void 0&&!["error","drop"].includes(e.missing))throw new Error('Summary missing must be "error" or "drop".');if(e.empty!==void 0&&!["null","identity"].includes(e.empty))throw new Error('Summary empty must be "null" or "identity".');return e}function Z(e,i){if(e.length===0)return;const n=new Set(e.flatMap(o=>Object.keys(o)));for(const o of i.groupBy)if(!n.has(o))throw new Error(`Summary source does not contain group field "${o}".`);for(const o of i.aggregates){if(o.field!==void 0&&!n.has(o.field))throw new Error(`Summary source does not contain aggregate field "${o.field}".`);const u=g(o.op)?o.op.orderBy:void 0;if(u!==void 0&&!n.has(u))throw new Error(`Summary source does not contain order field "${u}".`)}if(i.weight!==void 0&&!n.has(i.weight.field))throw new Error(`Summary source does not contain weight field "${i.weight.field}".`)}function F(e,i){if(i.op!=="count"){if(K(i.op)&&X.has(i.op)){z(e,i.field,"nominal");return}z(e,i.field,"quantitative")}}function se(e,i){x(i),Z(e,i);const n=i.aggregates.map(t=>({...t,op:B(t.op,{allowBootstrap:!0})})),o=i.weight!==void 0&&i.missing!==void 0;let u;if(i.weight===void 0)for(const t of n)F(e,t);else if(!o)u=A(e,i.weight,"Summary").entries,L(u,[...new Set(n.flatMap(t=>t.field===void 0?[]:[t.field]))],"Summary");else{A(e.filter(t=>t[i.weight.field]!==null&&t[i.weight.field]!==void 0),i.weight,"Summary");for(const t of n)t.field!==void 0&&F(e.filter(d=>d[t.field]!==null&&d[t.field]!==void 0),t)}if(i.missing==="error"){if(i.weight!==void 0){const t=e.findIndex(d=>d[i.weight.field]===null||d[i.weight.field]===void 0);if(t!==-1)throw new TypeError(`Summary weight "${i.weight.field}" is missing at row ${t}.`)}for(const t of n){if(t.field===void 0)continue;const d=e.findIndex(f=>f[t.field]===null||f[t.field]===void 0);if(d!==-1)throw new TypeError(`Summary field "${t.field}" is missing at row ${d}.`)}}const l=new Map;if(i.groupBy.length===0?l.set("all",{values:{},rows:e,...u===void 0?{}:{entries:u}}):e.forEach((t,d)=>{const f=Object.fromEntries(i.groupBy.map(a=>[a,t[a]])),c=i.groupBy.map(a=>V(t[a],`Summary group field "${a}"`)).join("|"),r=l.get(c)??{values:f,rows:[],...u===void 0?{}:{entries:[]}};r.rows.push(t),u!==void 0&&r.entries.push(u[d]),l.set(c,r)}),l.size>N)throw new RangeError(`Summary output cannot exceed ${N} groups.`);const m=new Map;let O=0;for(const t of n){const d=t.op?.method;if(d?.type!=="bootstrap")continue;const f=JSON.stringify([t.field,d.samples,d.seed]);m.has(f)||(O+=d.samples*e.filter(c=>Number.isFinite(c[t.field])).length,U(O),m.set(f,q(d)))}const $=[],R=[...l.values()].map(t=>{const d=new Map,f=i.weight===void 0||o?void 0:k(t.entries,i.weight.kind,"Summary group"),c=Object.fromEntries(n.map(r=>{let a=0,h=0,j=0,S=f;if(o){const s=t.rows.filter(p=>r.field!==void 0&&(p[r.field]===null||p[r.field]===void 0)?(a+=1,!1):p[i.weight.field]===null||p[i.weight.field]===void 0?(h+=1,!1):!0),y=A(s,i.weight,"Summary").entries;j=y.filter(p=>p.weight===0).length,S=k(y,i.weight.kind,"Summary group")}const w=r.op?.method;let T;if(w?.type==="bootstrap"){const s=JSON.stringify([r.field,w.samples,w.seed]);if(!d.has(s)){const p=t.rows.map(_=>_[r.field]).filter(Number.isFinite);d.set(s,m.get(s)(p))}T=I(d.get(s),r.op.level)?.[r.op.op==="ciLower"?"lower":"upper"]}let b=w?.type==="bootstrap"?T:S===void 0?G(t.rows,r.field??"__row",r.op):P(S,r.field,r.op,`Summary aggregate "${r.as}"`);if(b===void 0&&i.empty!==void 0){const s=typeof r.op=="string"?r.op:r.op.op;b=i.empty==="identity"&&s==="sum"?0:null}if(i.missing!==void 0||i.empty!==void 0){const s=o?a+h:r.field===void 0?0:t.rows.filter(y=>y[r.field]===null||y[r.field]===void 0).length;$.push({role:r.as,group:t.values,inputRows:t.rows.length,usedRows:t.rows.length-s,excludedRows:s,excludedByReason:s===0?{}:{...a===0&&o?{}:{"missing-value":o?a:s},...h===0?{}:{"missing-weight":h}},...o?{zeroWeightRows:j}:{}})}return[r.as,b]}));return{...t.values,...c,...i.members===void 0?{}:{[i.members]:f===void 0?t.rows:Y(f)}}});return i.missing===void 0&&i.empty===void 0?R:{values:R,report:v({version:1,owner:{kind:"data",id:"pending"},inputs:[],units:$})}}export{se as deriveSummaryRows,ue as normalizeSummaryTransform,x as validateSummaryTransform};
|
|
@@ -45,7 +45,7 @@ export function assertPathGroupCompatible(program, layer, dataset, group) {
|
|
|
45
45
|
![undefined, "overlay"].includes(layer.encoding?.color?.layout)
|
|
46
46
|
)) {
|
|
47
47
|
if (group?.fields !== undefined ||
|
|
48
|
-
(layer.encoding?.color !== undefined && field !== layer.encoding.color.field)) {
|
|
48
|
+
(layer.encoding?.color !== undefined && layer.encoding.color.fieldType !== "quantitative" && field !== layer.encoding.color.field)) {
|
|
49
49
|
throw new Error("Area layout requires one group field matching its color field.");
|
|
50
50
|
}
|
|
51
51
|
}
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Must match package.json; enforced by the snapshot producer-version test.
|
|
2
|
-
export const packageVersion = "0.0.
|
|
2
|
+
export const packageVersion = "0.0.20";
|
package/types/index.d.ts
CHANGED
package/types/program.d.ts
CHANGED
|
@@ -563,7 +563,7 @@ export type DatasetIntervalTransform = {
|
|
|
563
563
|
| {
|
|
564
564
|
center: "mean";
|
|
565
565
|
extent: "ci";
|
|
566
|
-
method?:
|
|
566
|
+
method?: MeanConfidenceIntervalMethod;
|
|
567
567
|
level: number;
|
|
568
568
|
}
|
|
569
569
|
| {
|
|
@@ -1001,7 +1001,7 @@ export interface DatasetSummaryTransform {
|
|
|
1001
1001
|
empty?: "null" | "identity";
|
|
1002
1002
|
}
|
|
1003
1003
|
export interface SummaryAggregateOptions {
|
|
1004
|
-
op: AggregateOperation;
|
|
1004
|
+
op: AggregateOperation | { op: "ciLower" | "ciUpper"; method: BootstrapMeanIntervalMethod; level?: number };
|
|
1005
1005
|
field?: string;
|
|
1006
1006
|
as: string;
|
|
1007
1007
|
}
|
|
@@ -1106,6 +1106,13 @@ export type ScalarAggregateOperation =
|
|
|
1106
1106
|
| "variance" | "varianceP" | "stdev" | "stdevP" | "stderr"
|
|
1107
1107
|
| "q1" | "q3" | "ciLower" | "ciUpper";
|
|
1108
1108
|
export type ConfidenceIntervalMethod = "normal" | "student-t";
|
|
1109
|
+
/** Seeded percentile bootstrap of nonweighted means; samples defaults to 1000. */
|
|
1110
|
+
export interface BootstrapMeanIntervalMethod {
|
|
1111
|
+
type: "bootstrap";
|
|
1112
|
+
seed: number;
|
|
1113
|
+
samples?: number;
|
|
1114
|
+
}
|
|
1115
|
+
export type MeanConfidenceIntervalMethod = ConfidenceIntervalMethod | BootstrapMeanIntervalMethod;
|
|
1109
1116
|
export type ParameterizedAggregateOperation =
|
|
1110
1117
|
| { op: "quantile"; probability: number }
|
|
1111
1118
|
| {
|
|
@@ -2372,7 +2379,7 @@ export interface IntervalDataOptions {
|
|
|
2372
2379
|
groupBy?: string | readonly string[];
|
|
2373
2380
|
center?: IntervalCenter;
|
|
2374
2381
|
extent?: IntervalExtent;
|
|
2375
|
-
method?:
|
|
2382
|
+
method?: MeanConfidenceIntervalMethod;
|
|
2376
2383
|
level?: number;
|
|
2377
2384
|
missing?: "error" | "drop";
|
|
2378
2385
|
as?: IntervalOutputFields;
|
|
@@ -4321,6 +4328,10 @@ export type DiscretizedColorScaleOptions =
|
|
|
4321
4328
|
| QuantileColorScaleOptions
|
|
4322
4329
|
| ThresholdColorScaleOptions;
|
|
4323
4330
|
|
|
4331
|
+
/** Area quantitative color requires one finite value per existing series;
|
|
4332
|
+
* aggregate, temporal color and unknown fallback remain mark-specific restrictions.
|
|
4333
|
+
* Area authoring uses the Full facade; createAreaPlot.color stays categorical.
|
|
4334
|
+
*/
|
|
4324
4335
|
export type ColorEncodingOptions =
|
|
4325
4336
|
| CategoricalEncodingOptions
|
|
4326
4337
|
| {
|