Unlisted
Edited
Oct 4, 2024
Insert cell
Insert cell
{
const context = DOM.context2d(width, 120),
W = d3.sum(weights),
x = d3.scaleLinear([0, 1], [10, width-10]);

context.lineWidth = 2;
context.strokeStyle = "white";

const f = weightedQuantileF(values, weights);
function draw(v) {
let s = x(0),
t = 0;

v = f(v).value;
context.fillStyle = color(v);
context.fillRect(0, 0, width, 120);

let rectangle;
for (const [value, weight] of d3
.zip(values, weights)
.sort((a, b) => d3.ascending(a[0], b[0]))) {
let s1 = x((t += weight / W));
context.fillStyle = color(value);
context.fillRect(s, 10, s1 - s + 1, 100);

if (v === value) rectangle = [Math.floor(s), 11, Math.floor(s1 - s), 98]

s = s1;
}
context.strokeRect(...rectangle);
}
draw(.5);
return d3
.select(context.canvas)
.on("pointermove click", (event) => draw(x.invert(d3.pointer(event)[0])))
.node();
}
Insert cell
color = d3.scaleSequential(d3.interpolateTurbo)
Insert cell
values = Array.from({ length: 15 }, Math.random)
Insert cell
weights = Array.from(values, () => .1 + Math.random())
Insert cell
weightedQuantile(values, weights, 0.333)
Insert cell
weightedQuantileF(values, weights).invert(0.43)
Insert cell
weightedQuantile([0, 1], [1, 2], 1 / 3)
Insert cell
weightedQuantileIndex([0, 1], [1, 2], 1 / 3)
Insert cell
weightedQuantile([0], [1, 2], 1)
Insert cell
weightedQuantileIndex([0], [1, 2], 1)
Insert cell
weightedQuantiles(values, weights, d3.ticks(0, 1, 10))
Insert cell
weightedQuantiles(d3.range(100).map(i => ({value:i, weight: 1+i})), d => d.weight, d3.ticks(0, 1, 10), d => d.value)
Insert cell
function weightedQuantileF(data, weights, accessor) {
// values is an iterable (todo: use *numbers* from d3-array)
const values = accessor ? Float64Array.from(data, (d, i) => accessor(d, i, data)) : Float64Array.from(data);
// weights is an accessor function or an iterable
weights = typeof weights === "function" ? Float64Array.from(data, weights) : Float64Array.from(weights);
const indices = Int32Array.from(values, (_, i) => i)
.sort((i, j) => d3.ascending(values[i], values[j])),
cs = d3.cumsum(indices, i => weights[i]);

const wq = (q) => {
const r = cs[cs.length - 1] * q,
i = d3.bisectLeft(cs, r),
mix = r == cs[i] && i < values.length - 1,
select = mix ? [indices[i], indices[i + 1]] : [indices[i], indices[i]],
value = mix ? (values[select[0]] + values[select[1]]) / 2 : values[select[0]];
return { value, select };
}

wq.invert = (value) => (cs[d3.bisector((i) => values[i]).left(indices, value)-1] ?? 0) / cs[cs.length - 1];

return wq;
}
Insert cell
function weightedQuantile(values, weights, q, accessor) {
return weightedQuantileF(values, weights, accessor)(q).value;
}
Insert cell
function weightedQuantileIndex(values, weights, q, accessor) {
return weightedQuantileF(values, weights, accessor)(q).select;
}
Insert cell
function weightedQuantiles(values, weights, q, accessor) {
return q.map(weightedQuantileF(values, weights, accessor)).map(r => r.value);
}
Insert cell
Insert cell
Plot.plot({
marks: [
Plot.lineY(d3.ticks(0, 1, 1000), {
x: (t) => t,
y: ((f) => (t) => f(t).value)(weightedQuantileF(values, weights)),
stroke: "blue"
}),
Plot.lineY(d3.ticks(0, 1, 1000), {
x: weightedQuantileF(values, weights).invert,
y: (t) => t,
stroke: "red",
mixBlendMode: "multiply"
})
]
})
Insert cell
Insert cell
d3.quantile([0,1], 0.3)
Insert cell
weightedQuantile([0,1], () => 1, 0.3)
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more