Skip to content

Commit a82e2fe

Browse files
authored
group reduce: "distinct" (observablehq#427)
* group reduce: "distinct" closes observablehq#412 * missing test * document "distinct" reduce
1 parent 72c77da commit a82e2fe

5 files changed

Lines changed: 156 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,7 @@ The following aggregation methods are supported:
973973
* *first* - the first value, in input order
974974
* *last* - the last value, in input order
975975
* *count* - the number of elements (frequency)
976+
* *distinct* - the number of distinct values
976977
* *sum* - the sum of values
977978
* *proportion* - the sum proportional to the overall total (weighted frequency)
978979
* *proportion-facet* - the sum proportional to the facet total

src/transforms/group.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {group as grouper, sort, sum, deviation, min, max, mean, median, variance} from "d3";
1+
import {group as grouper, sort, sum, deviation, min, max, mean, median, variance, InternSet} from "d3";
22
import {firstof} from "../defined.js";
33
import {valueof, maybeColor, maybeInput, maybeTransform, maybeTuple, maybeLazyChannel, lazyChannel, first, identity, take, labelof, range} from "../mark.js";
44

@@ -139,6 +139,7 @@ export function maybeReduce(reduce, value) {
139139
case "first": return reduceFirst;
140140
case "last": return reduceLast;
141141
case "count": return reduceCount;
142+
case "distinct": return reduceDistinct;
142143
case "sum": return value == null ? reduceCount : reduceSum;
143144
case "proportion": return reduceProportion(value, "data");
144145
case "proportion-facet": return reduceProportion(value, "facet");
@@ -201,6 +202,15 @@ const reduceCount = {
201202
}
202203
};
203204

205+
const reduceDistinct = {
206+
label: "Distinct",
207+
reduce: (I, X) => {
208+
const s = new InternSet();
209+
for (const i of I) s.add(X[i]);
210+
return s.size;
211+
}
212+
};
213+
204214
const reduceSum = reduceAccessor(sum);
205215

206216
function reduceProportion(value, scope) {

test/output/mobyDickLetterPairs.svg

Lines changed: 115 additions & 0 deletions
Loading

test/plots/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export {default as metroUnemploymentNormalize} from "./metro-unemployment-normal
5656
export {default as metroUnemploymentRidgeline} from "./metro-unemployment-ridgeline.js";
5757
export {default as mobyDickFaceted} from "./moby-dick-faceted.js";
5858
export {default as mobyDickLetterFrequency} from "./moby-dick-letter-frequency.js";
59+
export {default as mobyDickLetterPairs} from "./moby-dick-letter-pairs.js";
5960
export {default as mobyDickLetterPosition} from "./moby-dick-letter-position.js";
6061
export {default as mobyDickLetterRelativeFrequency} from "./moby-dick-letter-relative-frequency.js";
6162
export {default as morleyBoxplot} from "./morley-boxplot.js";
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as Plot from "@observablehq/plot";
2+
import * as d3 from "d3";
3+
4+
export default async function() {
5+
const mobydick = await d3.text("data/moby-dick-chapter-1.txt");
6+
const letters = [...mobydick].map(d => /\w/.test(d) ? d.toUpperCase() : "*");
7+
const pairs = d3.pairs(letters).map(([letter, next]) => ({letter, next}));
8+
return Plot.plot({
9+
x: { axis: null },
10+
y: { label: null },
11+
marks: [
12+
Plot.barX(pairs, Plot.groupY({x: "distinct"}, {x: "next", y: "letter"})),
13+
Plot.text(pairs, Plot.stackX(
14+
Plot.group({
15+
text: "first",
16+
y: "first",
17+
x: "distinct"
18+
}, {
19+
y: "letter",
20+
z: "next",
21+
x: "next",
22+
text: "next",
23+
fill: "white"
24+
})
25+
))
26+
]
27+
});
28+
}

0 commit comments

Comments
 (0)