ggaction 0.0.13 → 0.0.15
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 +78 -0
- package/README.md +20 -11
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/package.json +8 -3
- package/src/actions/basic.js +2 -2
- package/src/actions/boxPlots/components.js +14 -4
- package/src/actions/boxPlots/edit.js +31 -5
- package/src/actions/boxPlots/materialize.js +11 -3
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/categoryOrder/index.js +15 -4
- package/src/actions/charts/area.js +5 -1
- package/src/actions/charts/bar.js +2 -1
- package/src/actions/charts/density.js +6 -2
- package/src/actions/charts/ecdf.js +4 -1
- package/src/actions/charts/endpoints.js +25 -3
- package/src/actions/charts/heatmap.js +2 -1
- package/src/actions/charts/histogram.js +4 -3
- package/src/actions/charts/horizon.js +3 -1
- package/src/actions/charts/interval-regression.js +5 -2
- package/src/actions/charts/line.js +3 -1
- package/src/actions/charts/parallel.js +8 -2
- package/src/actions/charts/pie.js +10 -2
- package/src/actions/charts/polar.js +15 -4
- package/src/actions/charts/radar.js +8 -2
- package/src/actions/charts/radial.js +10 -2
- package/src/actions/charts/rug-strip.js +6 -2
- package/src/actions/charts/scatter.js +3 -1
- package/src/actions/composition/actions.js +13 -4
- package/src/actions/coordinates/actions.js +1 -1
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/bin.js +1 -1
- package/src/actions/data/bin2d.js +57 -185
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +2 -2
- package/src/actions/data/create.js +2 -2
- package/src/actions/data/density.js +12 -20
- package/src/actions/data/derived.js +47 -17
- package/src/actions/data/edit.js +539 -0
- package/src/actions/data/filter.js +14 -2
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +14 -0
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +16 -21
- package/src/actions/data/revisionBindings.js +145 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/summary.js +1 -1
- package/src/actions/data/timeUnit.js +4 -1
- package/src/actions/data/window.js +1 -1
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/index.js +2 -1
- package/src/actions/encodings/density.js +5 -2
- package/src/actions/encodings/histogram.js +3 -2
- package/src/actions/encodings/index.js +4 -0
- package/src/actions/encodings/offset.js +20 -13
- package/src/actions/encodings/position/apply.js +53 -7
- package/src/actions/encodings/position/index.js +10 -2
- package/src/actions/encodings/position/policies/bar.js +9 -0
- package/src/actions/encodings/position/resolve.js +24 -7
- package/src/actions/encodings/remove.js +9 -4
- package/src/actions/encodings/ruleAppearance.js +1 -23
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/shared.js +82 -14
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/errorBands/create.js +23 -3
- package/src/actions/errorBands/edit.js +21 -6
- package/src/actions/errorBars/create.js +35 -7
- package/src/actions/errorBars/edit.js +19 -4
- package/src/actions/facets/actions.js +237 -57
- package/src/actions/facets/derive.js +91 -26
- package/src/actions/gradientPlots/components.js +10 -2
- package/src/actions/gradientPlots/edit.js +14 -4
- package/src/actions/gradientPlots/materialize.js +3 -1
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/guides/applicability.js +8 -2
- package/src/actions/guides/axes/labels.js +58 -12
- package/src/actions/guides/axes/lines.js +6 -2
- package/src/actions/guides/axes/parallel/resolve.js +6 -2
- package/src/actions/guides/axes/tickGroups.js +2 -1
- package/src/actions/guides/axes/ticks.js +6 -2
- package/src/actions/guides/axes/titles.js +11 -3
- package/src/actions/guides/grids/resolve.js +7 -2
- package/src/actions/guides/guides.js +19 -3
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +134 -48
- package/src/actions/guides/legends/categorical/components.js +17 -12
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +97 -15
- package/src/actions/guides/legends/categorical/options.js +2 -2
- package/src/actions/guides/legends/categorical/recipes.js +111 -20
- package/src/actions/guides/legends/categorical/resolve.js +12 -8
- package/src/actions/guides/legends/categorical/symbols.js +85 -30
- package/src/actions/guides/legends/continuous/common.js +30 -12
- package/src/actions/guides/legends/continuous/gradient.js +10 -7
- package/src/actions/guides/legends/continuous/index.js +2 -0
- package/src/actions/guides/legends/continuous/interval.js +8 -3
- package/src/actions/guides/legends/continuous/opacity.js +8 -5
- package/src/actions/guides/legends/continuous/stroke.js +525 -0
- package/src/actions/guides/legends/creation.js +14 -1
- package/src/actions/guides/legends/edit.js +158 -59
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +54 -19
- package/src/actions/guides/legends/lifecycle.js +19 -8
- package/src/actions/guides/legends/remove.js +13 -3
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +93 -40
- package/src/actions/guides/legends/strokeWidth.js +34 -28
- package/src/actions/guides/legends/target.js +112 -1
- package/src/actions/guides/legends/transition.js +165 -16
- package/src/actions/guides/polar/axes/facade.js +4 -4
- package/src/actions/guides/polar/axes/labels.js +24 -5
- package/src/actions/guides/polar/axes/lines.js +10 -4
- package/src/actions/guides/polar/axes/shared.js +16 -0
- package/src/actions/guides/polar/axes/ticks.js +1 -1
- package/src/actions/guides/polar/axes/titles.js +1 -1
- package/src/actions/guides/polar/grids.js +1 -1
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/index.js +14 -3
- package/src/actions/marks/arc/actions.js +77 -19
- package/src/actions/marks/area/actions.js +34 -9
- package/src/actions/marks/area/materialize.js +17 -1
- package/src/actions/marks/bar/create.js +11 -2
- package/src/actions/marks/bar/edit.js +20 -3
- package/src/actions/marks/bar/materialize.js +22 -2
- package/src/actions/marks/index.js +10 -1
- package/src/actions/marks/line/actions.js +37 -14
- package/src/actions/marks/line/materialize.js +29 -10
- package/src/actions/marks/point/create.js +8 -2
- package/src/actions/marks/point/edit.js +18 -6
- package/src/actions/marks/point/materialize.js +49 -15
- package/src/actions/marks/rect/actions.js +33 -12
- package/src/actions/marks/references.js +601 -30
- package/src/actions/marks/remove.js +71 -1
- package/src/actions/marks/rule/actions.js +47 -10
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +74 -9
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +154 -6
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +73 -0
- package/src/actions/marks/tick/actions.js +22 -5
- package/src/actions/primitives/editGraphics.js +26 -0
- package/src/actions/primitives/semantic.js +2 -1
- package/src/actions/primitives/semanticAction.js +83 -10
- package/src/actions/primitives/semanticValidation/guide.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +5 -0
- package/src/actions/primitives/semanticValidation/scale.js +28 -7
- package/src/actions/regression/components.js +26 -11
- package/src/actions/regression/create.js +10 -2
- package/src/actions/regression/edit.js +21 -7
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +172 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +1 -1
- package/src/actions/scales/consumers/families.js +3 -2
- package/src/actions/scales/consumers/index.js +19 -2
- package/src/actions/scales/consumers/seriesLayout.js +28 -14
- package/src/actions/scales/create.js +54 -22
- package/src/actions/scales/definitions.js +94 -40
- package/src/actions/scales/edit.js +66 -11
- package/src/actions/scales/editPolicy.js +81 -11
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +4 -3
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/preview.js +111 -10
- package/src/actions/selection/actions.js +154 -30
- package/src/actions/selection/index.js +3 -0
- package/src/actions/theme/actions.js +68 -13
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +9 -2
- package/src/actions/theme/reconcile.js +315 -67
- package/src/actions/theme/state.js +160 -0
- package/src/actions/violinPlots/create.js +5 -2
- package/src/actions/violinPlots/edit.js +1 -0
- package/src/core/action.js +10 -0
- package/src/core/compositionState.js +6 -1
- package/src/core/programState.js +20 -0
- package/src/core/resourceReferences.js +332 -0
- package/src/core/vocabulary.js +16 -3
- package/src/grammar/bars/aggregate.js +4 -1
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bin.js +54 -9
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +265 -64
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +7 -47
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/impute.js +340 -0
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +18 -1
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/pathSeries.js +26 -8
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/parameters.js +23 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/colorConsumers.js +20 -1
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +27 -4
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +179 -30
- package/src/grammar/schemas/semanticPath.js +7 -2
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +78 -16
- package/src/grammar/timeUnit.js +216 -5
- package/src/grammar/transformTopology.js +4 -0
- package/src/grammar/transforms.js +394 -28
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +221 -16
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +59 -5
- package/src/layout/labels.js +343 -0
- package/src/materialization/bars/aggregate.js +25 -2
- package/src/materialization/bars/histogram.js +84 -24
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/composition.js +3 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +36 -0
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +8 -2
- package/src/materialization/facetGuides/preparation.js +12 -7
- package/src/materialization/facetHeaders.js +346 -0
- package/src/materialization/facets.js +15 -65
- package/src/materialization/guides/resources.js +26 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/index.js +11 -5
- package/src/materialization/marks/policies.js +2 -1
- package/src/materialization/rect.js +25 -12
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/scales/map.js +5 -3
- package/src/materialization/scales/policies/binnedPosition.js +2 -2
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +4 -1
- package/src/materialization/scales/resolve.js +22 -6
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +30 -3
- package/src/materialization/selection/items/common.js +19 -4
- package/src/materialization/selection/items/rect.js +28 -4
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +265 -14
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/line.js +2 -3
- package/src/renderers/canvas/native.js +4 -4
- package/src/renderers/canvas/path.js +2 -3
- package/src/renderers/canvas/rect.js +2 -3
- package/src/renderers/canvas/stroke.js +11 -0
- package/src/renderers/svg.js +19 -0
- package/src/selectors/datasets.js +32 -1
- package/src/selectors/layers.js +4 -0
- package/src/theme/defaults.js +101 -0
- package/types/basic.d.ts +6 -0
- package/types/index.d.ts +85 -0
- package/types/program.d.ts +910 -117
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { isPlainObject } from "../core/immutable.js";
|
|
2
|
+
import {
|
|
3
|
+
interpolateNumber,
|
|
4
|
+
maximumMagnitude,
|
|
5
|
+
requireFiniteResult,
|
|
6
|
+
restoreFiniteScale,
|
|
7
|
+
stableFiniteSum
|
|
8
|
+
} from "./numeric.js";
|
|
9
|
+
|
|
10
|
+
const WEIGHT_KINDS = Object.freeze(["frequency", "reliability"]);
|
|
11
|
+
const WEIGHT_KEYS = Object.freeze(["field", "kind"]);
|
|
12
|
+
|
|
13
|
+
export const WEIGHTED_AGGREGATE_OPERATIONS = Object.freeze([
|
|
14
|
+
"count", "sum", "mean", "variance", "varianceP", "stdev", "stdevP",
|
|
15
|
+
"stderr", "median", "q1", "q3", "quantile"
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
function requireField(value, label) {
|
|
19
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
20
|
+
throw new TypeError(`${label} must be a non-empty string.`);
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function normalizeStatisticalWeight(value, label = "Statistical weight") {
|
|
26
|
+
if (!isPlainObject(value)) {
|
|
27
|
+
throw new TypeError(`${label} must be a plain object.`);
|
|
28
|
+
}
|
|
29
|
+
const unknown = Object.keys(value).find(key => !WEIGHT_KEYS.includes(key));
|
|
30
|
+
if (unknown !== undefined) {
|
|
31
|
+
throw new Error(`Unknown ${label.toLowerCase()} property "${unknown}".`);
|
|
32
|
+
}
|
|
33
|
+
const field = requireField(value.field, `${label} field`);
|
|
34
|
+
if (!WEIGHT_KINDS.includes(value.kind)) {
|
|
35
|
+
throw new Error(`Unsupported statistical weight kind "${value.kind}".`);
|
|
36
|
+
}
|
|
37
|
+
return { field, kind: value.kind };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function validateWeightedAggregate(operation) {
|
|
41
|
+
const name = typeof operation === "string" ? operation : operation?.op;
|
|
42
|
+
if (!WEIGHTED_AGGREGATE_OPERATIONS.includes(name)) {
|
|
43
|
+
throw new Error(`Aggregate "${name}" does not support statistical weight.`);
|
|
44
|
+
}
|
|
45
|
+
return operation;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function readStatisticalWeights(
|
|
49
|
+
rows,
|
|
50
|
+
definition,
|
|
51
|
+
label = "Statistical"
|
|
52
|
+
) {
|
|
53
|
+
if (!Array.isArray(rows)) {
|
|
54
|
+
throw new TypeError(`${label} rows must be an array.`);
|
|
55
|
+
}
|
|
56
|
+
const weight = normalizeStatisticalWeight(definition, `${label} weight`);
|
|
57
|
+
const entries = rows.map((row, index) => {
|
|
58
|
+
const value = row?.[weight.field];
|
|
59
|
+
if (weight.kind === "frequency") {
|
|
60
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
61
|
+
throw new RangeError(
|
|
62
|
+
`${label} frequency weight "${weight.field}" must contain non-negative safe integers; row ${index} is invalid.`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
} else if (!Number.isFinite(value) || value < 0) {
|
|
66
|
+
throw new RangeError(
|
|
67
|
+
`${label} reliability weight "${weight.field}" must contain non-negative finite numbers; row ${index} is invalid.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return { row, index, weight: value };
|
|
71
|
+
});
|
|
72
|
+
return { definition: weight, entries };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function validateWeightedNumericFields(entries, fields, label = "Statistical") {
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
for (const field of fields) {
|
|
78
|
+
if (!Number.isFinite(entry.row?.[field])) {
|
|
79
|
+
throw new TypeError(
|
|
80
|
+
`${label} field "${field}" must contain a finite number at row ${entry.index}.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function summarizeStatisticalWeights(
|
|
88
|
+
entries,
|
|
89
|
+
kind,
|
|
90
|
+
label = "Statistical group"
|
|
91
|
+
) {
|
|
92
|
+
const positive = entries.filter(entry => entry.weight > 0);
|
|
93
|
+
if (positive.length === 0) {
|
|
94
|
+
throw new Error(`${label} must have positive total weight.`);
|
|
95
|
+
}
|
|
96
|
+
if (kind === "frequency") {
|
|
97
|
+
let total = 0;
|
|
98
|
+
for (const entry of positive) {
|
|
99
|
+
total += entry.weight;
|
|
100
|
+
if (!Number.isSafeInteger(total)) {
|
|
101
|
+
throw new RangeError(`${label} frequency total weight must be a safe integer.`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const weightScale = maximumMagnitude(positive.map(entry => entry.weight));
|
|
105
|
+
const normalizedWeights = positive.map(entry => entry.weight / weightScale);
|
|
106
|
+
return {
|
|
107
|
+
kind,
|
|
108
|
+
positive,
|
|
109
|
+
weightScale,
|
|
110
|
+
normalizedWeights,
|
|
111
|
+
normalizedTotal: stableFiniteSum(normalizedWeights, `${label} weight`),
|
|
112
|
+
effectiveSize: total,
|
|
113
|
+
totalWeight: total
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const weightScale = maximumMagnitude(positive.map(entry => entry.weight));
|
|
118
|
+
const normalizedWeights = positive.map(entry => entry.weight / weightScale);
|
|
119
|
+
const normalizedTotal = stableFiniteSum(normalizedWeights, `${label} weight`);
|
|
120
|
+
const normalizedSquares = stableFiniteSum(
|
|
121
|
+
normalizedWeights.map(value => value ** 2),
|
|
122
|
+
`${label} squared weight`
|
|
123
|
+
);
|
|
124
|
+
const effectiveSize = requireFiniteResult(
|
|
125
|
+
normalizedTotal ** 2 / normalizedSquares,
|
|
126
|
+
`${label} effective sample size`
|
|
127
|
+
);
|
|
128
|
+
return {
|
|
129
|
+
kind,
|
|
130
|
+
positive,
|
|
131
|
+
weightScale,
|
|
132
|
+
normalizedWeights,
|
|
133
|
+
normalizedTotal,
|
|
134
|
+
normalizedSquares,
|
|
135
|
+
effectiveSize
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function restoreWeight(summary, normalized, label) {
|
|
140
|
+
return restoreFiniteScale(normalized, summary.weightScale, label);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function statisticalWeightTotal(summary, label = "Statistical count") {
|
|
144
|
+
return summary.kind === "frequency"
|
|
145
|
+
? summary.totalWeight
|
|
146
|
+
: restoreWeight(summary, summary.normalizedTotal, label);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function weightedMoments(summary, field, label) {
|
|
150
|
+
const values = summary.positive.map(entry => entry.row[field]);
|
|
151
|
+
const valueScale = maximumMagnitude(values) || 1;
|
|
152
|
+
const normalizedMean = stableFiniteSum(
|
|
153
|
+
values.map((value, index) =>
|
|
154
|
+
summary.normalizedWeights[index] * (value / valueScale)
|
|
155
|
+
),
|
|
156
|
+
`${label} mean`
|
|
157
|
+
) / summary.normalizedTotal;
|
|
158
|
+
const mean = restoreFiniteScale(normalizedMean, valueScale, `${label} mean`);
|
|
159
|
+
const normalizedSquared = stableFiniteSum(
|
|
160
|
+
values.map((value, index) => {
|
|
161
|
+
const difference = value / valueScale - normalizedMean;
|
|
162
|
+
return summary.normalizedWeights[index] * difference ** 2;
|
|
163
|
+
}),
|
|
164
|
+
`${label} squared deviation`
|
|
165
|
+
);
|
|
166
|
+
return { mean, normalizedMean, normalizedSquared, valueScale };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function restoreWeightedProduct(value, firstScale, secondScale, label) {
|
|
170
|
+
const candidates = [
|
|
171
|
+
() => value * firstScale * secondScale,
|
|
172
|
+
() => value * secondScale * firstScale,
|
|
173
|
+
() => firstScale * secondScale * value
|
|
174
|
+
];
|
|
175
|
+
for (const candidate of candidates) {
|
|
176
|
+
const result = candidate();
|
|
177
|
+
if (Number.isFinite(result)) return result === 0 ? 0 : result;
|
|
178
|
+
}
|
|
179
|
+
throw new RangeError(`${label} is outside the finite numeric range.`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function weightedSum(summary, field, label) {
|
|
183
|
+
const values = summary.positive.map(entry => entry.row[field]);
|
|
184
|
+
const valueScale = maximumMagnitude(values) || 1;
|
|
185
|
+
const normalized = stableFiniteSum(
|
|
186
|
+
values.map((value, index) =>
|
|
187
|
+
summary.normalizedWeights[index] * (value / valueScale)
|
|
188
|
+
),
|
|
189
|
+
label
|
|
190
|
+
);
|
|
191
|
+
return restoreWeightedProduct(
|
|
192
|
+
normalized,
|
|
193
|
+
summary.weightScale,
|
|
194
|
+
valueScale,
|
|
195
|
+
label
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function weightedDeviation(summary, moments, sample, label) {
|
|
200
|
+
const denominator = sample
|
|
201
|
+
? summary.kind === "frequency"
|
|
202
|
+
? (summary.totalWeight - 1) / summary.weightScale
|
|
203
|
+
: summary.normalizedTotal -
|
|
204
|
+
summary.normalizedSquares / summary.normalizedTotal
|
|
205
|
+
: summary.normalizedTotal;
|
|
206
|
+
if (!Number.isFinite(denominator) || denominator <= 0) {
|
|
207
|
+
throw new Error(`${label} requires an effective sample size greater than one.`);
|
|
208
|
+
}
|
|
209
|
+
return restoreFiniteScale(
|
|
210
|
+
Math.sqrt(moments.normalizedSquared / denominator),
|
|
211
|
+
moments.valueScale,
|
|
212
|
+
label
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function frequencyRankValue(summary, field, rank) {
|
|
217
|
+
let cumulative = 0;
|
|
218
|
+
for (const item of summary.ordered) {
|
|
219
|
+
cumulative += item.weight;
|
|
220
|
+
if (cumulative > rank) return item.value;
|
|
221
|
+
}
|
|
222
|
+
return summary.ordered.at(-1).value;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function weightedQuantile(summary, field, probability, label = "Weighted quantile") {
|
|
226
|
+
if (!Number.isFinite(probability) || probability < 0 || probability > 1) {
|
|
227
|
+
throw new RangeError(`${label} probability must be between 0 and 1.`);
|
|
228
|
+
}
|
|
229
|
+
const ordered = summary.positive
|
|
230
|
+
.map(entry => ({ value: entry.row[field], weight: entry.weight }))
|
|
231
|
+
.sort((left, right) => left.value - right.value);
|
|
232
|
+
if (summary.kind === "frequency") {
|
|
233
|
+
const ranked = { ...summary, ordered };
|
|
234
|
+
const position = (summary.totalWeight - 1) * probability;
|
|
235
|
+
const lower = frequencyRankValue(ranked, field, Math.floor(position));
|
|
236
|
+
const upper = frequencyRankValue(ranked, field, Math.ceil(position));
|
|
237
|
+
return interpolateNumber(lower, upper, position - Math.floor(position));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (probability === 0) return ordered[0].value;
|
|
241
|
+
if (probability === 1) return ordered.at(-1).value;
|
|
242
|
+
const combined = [];
|
|
243
|
+
for (const item of ordered) {
|
|
244
|
+
const previous = combined.at(-1);
|
|
245
|
+
if (previous !== undefined && Object.is(previous.value, item.value)) {
|
|
246
|
+
previous.weight += item.weight / summary.weightScale;
|
|
247
|
+
} else {
|
|
248
|
+
combined.push({ value: item.value, weight: item.weight / summary.weightScale });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const target = probability * summary.normalizedTotal;
|
|
252
|
+
let cumulative = 0;
|
|
253
|
+
for (const item of combined) {
|
|
254
|
+
cumulative += item.weight;
|
|
255
|
+
if (cumulative >= target) return item.value;
|
|
256
|
+
}
|
|
257
|
+
return combined.at(-1).value;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function calculateWeightedAggregate(
|
|
261
|
+
summary,
|
|
262
|
+
field,
|
|
263
|
+
operation,
|
|
264
|
+
label = "Weighted aggregate"
|
|
265
|
+
) {
|
|
266
|
+
validateWeightedAggregate(operation);
|
|
267
|
+
const name = typeof operation === "string" ? operation : operation.op;
|
|
268
|
+
if (name === "count") return statisticalWeightTotal(summary, `${label} count`);
|
|
269
|
+
if (["median", "q1", "q3", "quantile"].includes(name)) {
|
|
270
|
+
const probability = name === "median" ? 0.5
|
|
271
|
+
: name === "q1" ? 0.25
|
|
272
|
+
: name === "q3" ? 0.75
|
|
273
|
+
: operation.probability;
|
|
274
|
+
return weightedQuantile(summary, field, probability, label);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (name === "sum") return weightedSum(summary, field, `${label} sum`);
|
|
278
|
+
const moments = weightedMoments(summary, field, label);
|
|
279
|
+
if (name === "mean") return moments.mean;
|
|
280
|
+
const sample = ["variance", "stdev", "stderr"].includes(name);
|
|
281
|
+
const deviation = weightedDeviation(summary, moments, sample, label);
|
|
282
|
+
if (name === "stdev" || name === "stdevP") return deviation;
|
|
283
|
+
if (name === "stderr") {
|
|
284
|
+
return requireFiniteResult(
|
|
285
|
+
deviation / Math.sqrt(summary.effectiveSize),
|
|
286
|
+
`${label} standard error`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
return requireFiniteResult(deviation ** 2, `${label} variance`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function weightedRows(summary) {
|
|
293
|
+
return summary.positive.map(entry => entry.row);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function weightedEntriesExtent(summary, field) {
|
|
297
|
+
let minimum = Infinity;
|
|
298
|
+
let maximum = -Infinity;
|
|
299
|
+
for (const entry of summary.positive) {
|
|
300
|
+
minimum = Math.min(minimum, entry.row[field]);
|
|
301
|
+
maximum = Math.max(maximum, entry.row[field]);
|
|
302
|
+
}
|
|
303
|
+
return [minimum, maximum];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function sumEntryWeights(entries, summary, label = "Statistical mass") {
|
|
307
|
+
if (entries.length === 0) return 0;
|
|
308
|
+
if (summary.kind === "frequency") {
|
|
309
|
+
let total = 0;
|
|
310
|
+
for (const entry of entries) {
|
|
311
|
+
total += entry.weight;
|
|
312
|
+
if (!Number.isSafeInteger(total)) {
|
|
313
|
+
throw new RangeError(`${label} frequency total weight must be a safe integer.`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return total;
|
|
317
|
+
}
|
|
318
|
+
const normalized = stableFiniteSum(
|
|
319
|
+
entries.map(entry => entry.weight / summary.weightScale),
|
|
320
|
+
label
|
|
321
|
+
);
|
|
322
|
+
return restoreWeight(summary, normalized, label);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function weightedBandwidth(summary, field, label = "Density auto bandwidth") {
|
|
326
|
+
if (summary.effectiveSize <= 1) {
|
|
327
|
+
throw new Error(`${label} requires an effective sample size greater than one.`);
|
|
328
|
+
}
|
|
329
|
+
const moments = weightedMoments(summary, field, label);
|
|
330
|
+
const deviation = weightedDeviation(summary, moments, true, label);
|
|
331
|
+
const interquartileRange = weightedQuantile(summary, field, 0.75, label) -
|
|
332
|
+
weightedQuantile(summary, field, 0.25, label);
|
|
333
|
+
const robustDeviation = interquartileRange / 1.34;
|
|
334
|
+
const spread = robustDeviation > 0
|
|
335
|
+
? Math.min(deviation, robustDeviation)
|
|
336
|
+
: deviation;
|
|
337
|
+
const bandwidth = spread * (1.06 * summary.effectiveSize ** -0.2);
|
|
338
|
+
if (!Number.isFinite(bandwidth) || bandwidth <= 0) {
|
|
339
|
+
throw new Error(`${label} requires varying finite values.`);
|
|
340
|
+
}
|
|
341
|
+
return bandwidth;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function weightedKernelSum(summary, field, sample, bandwidth, kernel, label) {
|
|
345
|
+
const normalized = stableFiniteSum(
|
|
346
|
+
summary.positive.map((entry, index) =>
|
|
347
|
+
summary.normalizedWeights[index] * kernel((sample - entry.row[field]) / bandwidth)
|
|
348
|
+
),
|
|
349
|
+
label
|
|
350
|
+
);
|
|
351
|
+
return { normalized, unit: normalized / summary.normalizedTotal / bandwidth };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export function weightedKernelEstimate(
|
|
355
|
+
summary,
|
|
356
|
+
field,
|
|
357
|
+
sample,
|
|
358
|
+
bandwidth,
|
|
359
|
+
kernel,
|
|
360
|
+
normalization,
|
|
361
|
+
label = "Density estimate"
|
|
362
|
+
) {
|
|
363
|
+
const { normalized, unit } = weightedKernelSum(
|
|
364
|
+
summary,
|
|
365
|
+
field,
|
|
366
|
+
sample,
|
|
367
|
+
bandwidth,
|
|
368
|
+
kernel,
|
|
369
|
+
label
|
|
370
|
+
);
|
|
371
|
+
return normalization === "unit"
|
|
372
|
+
? requireFiniteResult(unit, label)
|
|
373
|
+
: restoreFiniteScale(normalized / bandwidth, summary.weightScale, label);
|
|
374
|
+
}
|