I’ve started exploring Quarto’s built in support for Observable JS to make interactive charts. Here I’m working through some options for visualising “paired” data (where there are two groups for comparison: eg male v female, two points in time, or some other category with just two values).
// simplified version of gallery examplesign = (label) => label.match(/female/i) ?-1:1
Overlapping histograms
Rather neat way to show a pair of histograms that overlap, like heights or weights for men and women.
However, if using a simple count, it does need the numbers of observations to be roughly similar. In the rhc data there are a lot more men than women, so I’ve simply taken a 15% sample of the men to get more even numbers. If I were doing this properly, I’d need to look into better normalisation methods.
Plot.plot({subtitle:"Heights of male and female convicts",round:true,color: {legend:true},marks: [ Plot.rectY(ojsRhc, Plot.binX({y2:"count"}, {x:"height_inches",fill:"gender",mixBlendMode:"multiply"})), Plot.ruleY([0]) ]})
Slopegraph
I’ve recently posted on slopegraphs in ggplot; it’s handy to have an Observable version too.
Plot.plot({height:400,width:400,x: {axis:"top",type:"ordinal",tickFormat:"",inset:90,label:null},y: {axis:null,inset:20},marks: [ Plot.line(ojsPetitionsSlope, {x:"year",y:"petitions",z:"topic"}), d3.groups(ojsPetitionsSlope, (d) => d.year===1628).map(([left, petitions]) => Plot.text(petitions,occlusionY({x:"year",y:"petitions",text: left? (d) =>`${d.topic}${d.petitions}`: (d) =>`${d.petitions}${d.topic}`,textAnchor: left ?"end":"start",dx: left ?-3:3,radius:5.5 })) ) ],subtitle:"Compare petition topics in 1628 and 1668"})
Code
// I've just copied this from the example; no idea how it actually works at this point!// OcclusionY adds an initializer that shifts nodes vertically with a tiny force simulation.occlusionY = ({radius =6.5,...options} = {}) => Plot.initializer(options, (data, facets, { y: {value: Y},text: {value: T} }, {y: sy}, dimensions, context) => {for (const index of facets) {const unique =newSet();const nodes =Array.from(index, (i) => ({fx:0,y:sy(Y[i]),visible: unique.has(T[i]) // remove duplicate labels?false:!!unique.add(T[i]), i })); d3.forceSimulation(nodes.filter((d) => d.visible)).force("y", d3.forceY(({y}) => y)) // gravitate towards the original y.force("collide", d3.forceCollide().radius(radius)) // collide.stop().tick(20);for (const { y, node, i, visible } of nodes) Y[i] =!visible ?NaN: y; }return {data, facets,channels: {y: {value: Y}}};})
Dumbbell chart
Another way to easily compare two sets of observations. It’s quite similar to a slopegraph in some ways, a dotplot combined with a line to connect the pairs. It can be effective to reorder by size of the difference.
A fun plot; quite similar to a dumbbell, but with arrows to show direction of difference/change.
Ian Mansfield put together a table of the ages of monarchs and their consorts at marriage (starting in the 9th century, but I’ve just pulled out the monarchs since 1066).
First, whether the monarch was the older or younger of the pair. You can hover over rows to see more details.