Confounders
Relationships between nodes
viewof strength_zx = Inputs.range([0, 1], {
value: 0.5,
step: 0.05,
label: html`<span class="node node-z">Z</span> → <span class="node node-x">X</span> strength`
})
viewof strength_zy = Inputs.range([0, 1], {
value: 0.5,
step: 0.05,
label: html`<span class="node node-z">Z</span> → <span class="node node-y">Y</span> strength`
})
viewof strength_xy = Inputs.range([0, 1], {
value: 0.7,
step: 0.05,
label: html`<span class="node node-x">X</span> → <span class="node node-y">Y</span> strength`
})Adjustments
// ----------------
// Status readout
// ----------------
{
const pctPure = Math.round(y_pure_x / yMax * 100);
const pctConf = Math.round(y_confounded / yMax * 100);
const pctZ = Math.round(y_direct_z / yMax * 100);
const pctOwn = Math.max(0, 100 - pctPure - pctConf - pctZ);
return html`<div class="alert alert-secondary status-readout">
<h5 class="alert-heading">What Y contains</h5>
<table>
<tr>
<td><svg width="12" height="12"><rect width="12" height="12" fill="${dag.colorX}"/></svg></td>
<td><span class="node node-x">X</span>'s direct influence on <span class="node node-y">Y</span></td>
<td>${pctPure}%</td>
</tr>
<tr>
<td><svg width="12" height="12">
<defs>
<pattern id="legend-hatch-conf" patternUnits="userSpaceOnUse"
width="6" height="6" patternTransform="rotate(45)">
<rect width="6" height="6" fill="${dag.colorZ}"/>
<line x1="0" y1="0" x2="0" y2="6"
stroke="${dag.colorX}" stroke-width="2.5"/>
</pattern>
</defs>
<rect width="12" height="12" fill="url(#legend-hatch-conf)"/>
</svg></td>
<td><span class="node node-z">Z</span>'s influence on <span class="node node-y">Y</span> via <span class="node node-x">X</span></td>
<td>${pctConf}%</td>
</tr>
<tr>
<td><svg width="12" height="12"><rect width="12" height="12" fill="${dag.colorZ}"/></svg></td>
<td><span class="node node-z">Z</span>'s direct influence on <span class="node node-y">Y</span></td>
<td>${pctZ}%</td>
</tr>
<tr>
<td><svg width="12" height="12"><rect width="12" height="12" fill="${dag.colorY}"/></svg></td>
<td><span class="node node-y">Y</span>'s own variation</td>
<td>${pctOwn}%</td>
</tr>
<tr class="summary ${adjust_z ? 'dimmed' : ''}">
<td>
<svg width="12" height="12"><rect width="12" height="12" fill="${dag.colorX}"/></svg>
+
<svg width="12" height="12">
<rect width="12" height="12" fill="url(#legend-hatch-conf)"/>
</svg>
</td>
<td>Apparent <span class="node node-x">X</span> → <span class="node node-y">Y</span> effect</td>
<td>${pctPure + pctConf}%</td>
</tr>
<tr class="summary ${adjust_z ? '' : 'dimmed'}">
<td>
<svg width="12" height="12"><rect width="12" height="12" fill="${dag.colorX}"/></svg>
</td>
<td>Unconfounded <span class="node node-x">X</span> → <span class="node node-y">Y</span> effect</td>
<td>${pctPure}%</td>
</tr>
</table>
</div>`;
}// -----------------
// Interactive DAG
// -----------------
{
const width = 600;
const height = 250;
const nodeRadius = 36;
const nodes = {
Z: { x: width / 2, y: 60, label: "Z" },
X: { x: 130, y: 200, label: "X" },
Y: { x: 470, y: 200, label: "Y" }
};
const svg = d3.create("svg")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("width", width)
.attr("height", height)
.style("max-width", "100%");
const defs = svg.append("defs");
dag.addArrowMarkers(defs);
dag.addHatchPattern(
defs, "hatch-confounded", dag.colorZ, dag.colorX, 45
);
dag.addCircleClip(
defs, "x-clip", nodes.X.x, nodes.X.y, nodeRadius
);
dag.addCircleClip(
defs, "y-clip", nodes.Y.x, nodes.Y.y, nodeRadius
);
// Arrows
const edges = [
{
id: "zx", from: nodes.Z, to: nodes.X,
strength: strength_zx, blocked: adjust_z
},
{
id: "zy", from: nodes.Z, to: nodes.Y,
strength: strength_zy, blocked: adjust_z
},
{
id: "xy", from: nodes.X, to: nodes.Y,
strength: strength_xy, blocked: false
}
];
for (const edge of edges) {
dag.drawEdge(svg, edge, nodeRadius);
}
// Nodes
// X: strength directly controls fill proportion
dag.drawNode(svg, nodes.X.x, nodes.X.y, nodeRadius, "x-clip", {
bottomUp: [],
topDown: [
{ prop: strength_zx, fill: dag.colorZ }
]
}, undefined, dag.colorX);
// Y: blue base, incoming effects overlay
dag.drawNode(svg, nodes.Y.x, nodes.Y.y, nodeRadius, "y-clip", {
bottomUp: [
{ prop: Math.min(y_pure_x / yMax, 1), fill: dag.colorX },
{
prop: Math.min(y_confounded / yMax, 1),
fill: "url(#hatch-confounded)"
}
],
topDown: [
{ prop: Math.min(y_direct_z / yMax, 1), fill: dag.colorZ }
]
}, undefined, dag.colorY);
dag.drawSolidNode(
svg, nodes.Z.x, nodes.Z.y, nodeRadius, dag.colorZ
);
// Labels
for (const n of Object.values(nodes)) {
dag.drawLabel(svg, n.x, n.y, n.label);
}
return svg.node();
}