Filter data by searching. Uses spaces between terms.
Select individual rows with floating checkboxes next to the first column. Sort the table by clicking column heads.
Filter data by searching. Uses spaces between terms.
Select individual rows with floating checkboxes next to the first column. Sort the table by clicking column heads.
Plot.plot({
grid: false,
marginTop: 36, // more room for facets
marginBottom: 48,
marks: [
Plot.axisX({ // Axis with just the ticks in the default fontSize
label: null,
// ticks: (scatterFacetX !== "") ? 4 : 8,
}),
Plot.axisX({ // Axis with just the label in custom fontSize
label: formatLabel(scatterX),
fontSize: largeFontSize,
labelOffset: 36,
ticks: [],
}),
Plot.axisY({ // Axis with just the ticks in the default fontSize
label: null,
// ticks: (scatterFacetX !== "") ? 4 : 8,
}),
Plot.axisY({ // Axis with just the label in the custom fontSize
label: formatLabel(scatterY),
fontSize: largeFontSize,
labelOffset: 36,
ticks: [],
}),
(scatterRegr=="Line")?Plot.linearRegressionY(
filtered, {
x: scatterX,
y: scatterY,
stroke: scatterRegrFill,
ci: 0}):[],
(scatterRegr=="± 95% CI")?Plot.linearRegressionY(
filtered, {
x: scatterX,
y: scatterY,
stroke: scatterRegrFill,
ci: 0.95}):[],
Plot.dot(
filtered, {
x: scatterX,
y: scatterY,
r: 5,
fill: scatterFill,
opacity: scatterOpacity,
}),
],
height: scatterHeight,
width: scatterWidth,
marginLeft: 60
});html
`<div id="scatterStats" autocorrect="off" spellcheck="false" style="display: ${scatterStatsDisplay}; padding-left: 1em; font-size: 80%;">
<caption>
<b>Least-squares linear regression</b>
<p><span style="color: gray;">Best-fit line: y = </span>${myOLSModelLinearRegr.coef[1].toPrecision(3)}<span style="color: gray;">x ${(myOLSModelLinearRegr.coef[0] < 0) ? "-" : "+"}</span> ${Math.abs(myOLSModelLinearRegr.coef[0].toPrecision(3))}</p>
</caption>
<table style="line-height: 75%;">
<tr>
<td style="padding: 0.5em; color: gray;">Source</td>
<td style="padding: 0.5em; color: gray;">Deg fr</td>
<td style="padding: 0.5em; color: gray;">Sum Sq</td>
<td style="padding: 0.5em; color: gray;">Mean Sq</td>
<td style="padding: 0.5em; color: gray;"><i>F</i></td>
<td style="padding: 0.5em; color: gray;"><i>P</i> - value</td>
</tr>
<tbody>
<tr>
<td style="padding: 0.5em; color: gray;">Regression</td>
<td style="padding: 0.5em;">${myOLSModelLinearRegr.df_model}</td>
<td style="padding: 0.5em;">${myOLSModelLinearRegr.SSE.toPrecision(5)}</td>
<td style="padding: 0.5em;">${(myOLSModelLinearRegr.SSE / myOLSModelLinearRegr.df_model).toPrecision(5)}</td>
<td style="padding: 0.5em; color: steelblue;">${myOLSModelLinearRegr.f.F_statistic.toPrecision(5)}</td>
<td style="padding: 0.5em; color: steelblue;">${pValStr(myOLSModelLinearRegr.f.pvalue)}</td>
</tr>
<tr>
<td style="padding: 0.5em; color: gray;">Residual</td>
<td style="padding: 0.5em;">${myOLSModelLinearRegr.df_resid}</td>
<td style="padding: 0.5em;">${myOLSModelLinearRegr.SSR.toPrecision(5)}</td>
<td style="padding: 0.5em;">${(myOLSModelLinearRegr.SSR / myOLSModelLinearRegr.df_resid).toPrecision(5)}</td>
<td style="padding: 0.5em;"></td>
<td style="padding: 0.5em;"></td>
</tr>
<tr>
<td style="padding: 0.5em; color: gray;">Total<br><br><span style="color: steelblue;"><i>R</i><sup> 2</sup> = ${myOLSModelLinearRegr.R2.toPrecision(5)}</span></td>
<td style="padding: 0.5em;">${myOLSModelLinearRegr.df_model + myOLSModelLinearRegr.df_resid}</td>
<td style="padding: 0.5em;">${myOLSModelLinearRegr.SST.toPrecision(5)}</td>
<td style="padding: 0.5em;"></td>
<td style="padding: 0.5em;"></td>
</tr>
</tbody>
</table>
</div>`html
`<div id="scatterEditor" autocorrect="off" spellcheck="false">
<p>Figure #. <b>Title.</b></p><p>Caption.</p>
</div>`myHisto = Plot.plot({
height: histoHeight,
width: histoWidth,
y: {grid: true},
// figure: true,
facet: {
data: filtered,
x: histFacetX,
y: histFacetY,
marginRight: 80
},
color: {
legend: false, // true,
label: formatLabel(histoFill),
tickFormat: formatLabel,
},
marginTop: 36, // more room for facets
marginBottom: 48,
marks: [
Plot.axisX({ // Axis with just the ticks in the default fontSize
label: null,
}),
Plot.axisX({ // Axis with just the label in custom fontSize
label: formatLabel(histoX),
fontSize: largeFontSize,
labelOffset: 36,
ticks: [],
}),
(histFacetX !== "") ? Plot.axisFx({ // Facet axis with just the ticks
label: null,
tickFormat: formatLabel,
}) : [],
(histFacetX !== "") ? Plot.axisFx({ // Facet axis with just the label
label: formatLabel(histFacetX),
fontSize: largeFontSize,
ticks: [ ],
}) : [],
Plot.axisY({ // Axis with just the ticks in the default fontSize
label: null,
}),
Plot.axisY({ // Axis with just the label in the custom fontSize
label: formatLabel(histoY),
fontSize: largeFontSize,
labelOffset: 36,
ticks: [],
}),
(histFacetY !== "") ? Plot.axisFy({ // Facet axis with just the ticks
label: null,
tickFormat: formatLabel,
}) : [],
(histFacetY !== "") ? Plot.axisFy({ // Facet axis with just the label
label: formatLabel(histFacetY),
labelAnchor: "top",
fontSize: largeFontSize,
ticks: [ ],
}) : [],
Plot.rectY(filtered,
Plot.binX({y: histoY},
{x: histoX,
fill: "steelblue", // (histoFill !== "") ? histoFill : "steelblue",
opacity: histoOpacity,
// thresholds: 20,
// insetLeft: 6, insetRight: 6,
})),
// Mean and SE marks
( (histoStats == "± SE") || (histoStats == "± 95% CI") )?
Plot.dot(filtered,
Plot.groupY(
{x: "mean"},
{x: histoX,
y: 0,
fill:
// (histoFill !== "") ? histoFill :
"black",
r: 5}
)) : [],
(histoStats == "± SE") ?
Plot.link(filtered,
Plot.groupY({
x1: (filtered) => d3.mean(filtered)
- d3.deviation(filtered)/Math.sqrt(d3.count(filtered)),
x2: (filtered) => d3.mean(filtered)
+ d3.deviation(filtered)/Math.sqrt(d3.count(filtered))
},
{x: histoX,
y: 0,
stroke:
// (histoFill !== "") ? histoFill :
"black",
strokeWidth: 3}
)) : [],
(histoStats == "± 95% CI") ?
Plot.link(filtered,
Plot.groupY({
x1: (filtered) => d3.mean(filtered)
- 1.96*d3.deviation(filtered)/Math.sqrt(d3.count(filtered)),
x2: (filtered) => d3.mean(filtered)
+ 1.96*d3.deviation(filtered)/Math.sqrt(d3.count(filtered))
},
{x: histoX,
y: 0,
stroke:
// (histoFill !== "") ? histoFill :
"black",
strokeWidth: 3}
)) : [],
// Plot.ruleY([0])
]
});
html
`<div id="histoStatistics" autocorrect="off" spellcheck="false" style="display: ${histoStatsDisplay}; padding-left: 1em; font-size: 80%;">
<caption>
<b>One-way ANOVA</b>
</caption>
<table style="line-height: 75%;">
<tr>
<td style="padding: 0.5em; color: gray;">Source</td>
<td style="padding: 0.5em; color: gray;">Deg fr</td>
<td style="padding: 0.5em; color: gray;">Sum Sq</td>
<td style="padding: 0.5em; color: gray;">Mean Sq</td>
<td style="padding: 0.5em; color: gray;"><i>F</i></td>
<td style="padding: 0.5em; color: gray;"><i>P</i> - value</td>
</tr>
<tbody>
<tr>
<td style="padding: 0.5em; color: gray;">Between grps</td>
<td style="padding: 0.5em;">${myOLSModelHistoANOVA.df_model}</td>
<td style="padding: 0.5em;">${myOLSModelHistoANOVA.SSE.toPrecision(5)}</td>
<td style="padding: 0.5em;">${(myOLSModelHistoANOVA.SSE / myOLSModelHistoANOVA.df_model).toPrecision(5)}</td>
<td style="padding: 0.5em; color: steelblue;">${myOLSModelHistoANOVA.f.F_statistic.toPrecision(5)}</td>
<td style="padding: 0.5em; color: steelblue;">${pValStr(myOLSModelHistoANOVA.f.pvalue)}</td>
</tr>
<tr>
<td style="padding: 0.5em; color: gray;">Within grps</td>
<td style="padding: 0.5em;">${myOLSModelHistoANOVA.df_resid}</td>
<td style="padding: 0.5em;">${myOLSModelHistoANOVA.SSR.toPrecision(5)}</td>
<td style="padding: 0.5em;">${(myOLSModelHistoANOVA.SSR / myOLSModelHistoANOVA.df_resid).toPrecision(5)}</td>
<td style="padding: 0.5em;"></td>
<td style="padding: 0.5em;"></td>
</tr>
<tr>
<td style="padding: 0.5em; color: gray;">Total<br><br><span style="color: steelblue;"><i>R</i><sup> 2</sup> = ${myOLSModelHistoANOVA.R2.toPrecision(5)}</span></td>
<td style="padding: 0.5em;">${myOLSModelHistoANOVA.df_model + myOLSModelHistoANOVA.df_resid}</td>
<td style="padding: 0.5em;">${myOLSModelHistoANOVA.SST.toPrecision(5)}</td>
<td style="padding: 0.5em;"></td>
<td style="padding: 0.5em;"></td>
</tr>
</tbody>
</table>
</div>`html
`<div id="histoEditor" autocorrect="off" spellcheck="false">
<p>Figure #. <b>Title.</b></p><p>Caption.</p>
</div>`( ((stripOrient == "Vertical ↑") && (stripX == stripDotFill)) ||
((stripOrient == "Horizontal →") && (stripY == stripDotFill)) ) ?
// Big IF stripDotFill is the same as the main x or y variable
// No subplots needed
Plot.plot({
axis: null, // Will specify these in detail with axis marks
symbol:
{
legend: true,
tickFormat: formatLabel,
label: formatLabel(stripDotFill),
},
facet: {
marginRight: (stripOrient == "Horizontal →") ? 90 : [],
},
height: stripHeight,
insetTop: ((stripOrient == "Horizontal →") &&
(dotsJitterOrDodge == "Jitter")) ?
10 : [],
insetBottom: ((stripOrient == "Horizontal →") &&
(dotsJitterOrDodge == "Jitter")) ?
10 : [],
insetLeft: ((stripOrient == "Vertical ↑") &&
(dotsJitterOrDodge == "Jitter")) ?
10 : [],
insetRight: ((stripOrient == "Vertical ↑") &&
(dotsJitterOrDodge == "Jitter")) ?
10 : [],
// marginTop: 36, // more room for facets
marginBottom: 42,
marks: [
// Axis marks
(stripOrient == "Vertical ↑") ? // Main axis w/o label
Plot.axisY({
label: null,
}) :
Plot.axisX({
label: null,
}),
(stripOrient == "Vertical ↑") ? // Main axis label, large font
Plot.axisY({
label: formatLabel(stripY),
fontSize: largeFontSize,
labelOffset: 36,
ticks: [],
}) :
Plot.axisX({
label: formatLabel(stripX),
fontSize: largeFontSize,
labelOffset: 36,
ticks: [],
}),
(stripOrient == "Vertical ↑") ? // Facet axis w/o label
Plot.axisFx({
anchor: "bottom",
label: null,
tickFormat: formatLabel,
}) :
Plot.axisFy({
label: null,
tickFormat: formatLabel,
}),
(stripOrient == "Vertical ↑") ? // Facet axis label, large font
Plot.axisFx({
anchor: "bottom",
label: formatLabel(stripX),
fontSize: largeFontSize,
ticks: [],
}) :
Plot.axisFy({
label: formatLabel(stripY),
labelAnchor: "top",
fontSize: largeFontSize,
ticks: [],
}),
// Grid marks
(stripOrient == "Vertical ↑") ?
Plot.gridY({}) :
Plot.gridX({}),
// Box marks
((stripOrient == "Vertical ↑") && (stripStats == "BoxPlot")) ?
Plot.boxY(filtered,
{fx: stripX,
y: stripY,
fill: "gray",
fillOpacity: 0.35,
stroke: "gray"}) : [],
((stripOrient == "Horizontal →") && (stripStats == "BoxPlot")) ?
Plot.boxX(filtered,
{fy: stripY,
x: stripX,
fill: "gray",
fillOpacity: 0.35,
stroke: "gray"}) : [],
// Jitter marks
((stripOrient == "Vertical ↑") && (dotsJitterOrDodge == "Jitter")) ?
Plot.dot(filtered, {
fx: stripX,
x: Math.random,
y: stripY,
fill: "steelblue", // stripDotFill,
opacity: dotsOpacity,
symbol: stripDotFill,
}) : [],
((stripOrient == "Horizontal →") && (dotsJitterOrDodge == "Jitter")) ?
Plot.dot(filtered, {
axis: null,
x: stripX,
fy: stripY,
y: Math.random,
fill: stripDotFill,
opacity: dotsOpacity,
symbol: stripDotFill,
}) : [],
// Dodge marks
((stripOrient == "Vertical ↑") && (dotsJitterOrDodge == "Dodge")) ?
Plot.dot(filtered,
Plot.dodgeX("middle", {
fx: stripX,
y: stripY,
fill: "steelblue", // stripDotFill,
opacity: dotsOpacity,
symbol: stripDotFill,})) : [],
((stripOrient == "Horizontal →") && (dotsJitterOrDodge == "Dodge")) ?
Plot.dot(filtered, // Horizontal
Plot.dodgeY("middle", {
fy: stripY,
x: stripX,
fill: stripDotFill,
opacity: dotsOpacity,
symbol: stripDotFill,})) : [],
// Mean and SE marks
((stripOrient == "Vertical ↑") && (stripStats == "± SE")) ?
Plot.dot(
filtered,
Plot.groupX(
{y: "mean"},
{y: stripY,
fx: stripX,
x: 0.5,
fill: "black",
r: 5}
)) : [],
((stripOrient == "Vertical ↑") && (stripStats == "± SE")) ?
Plot.link(filtered,
Plot.groupX({
y1: (filtered) => d3.mean(filtered)
- d3.deviation(filtered)/Math.sqrt(d3.count(filtered)),
y2: (filtered) => d3.mean(filtered)
+ d3.deviation(filtered)/Math.sqrt(d3.count(filtered))
},
{y: stripY,
fx: stripX,
x: 0.5,
stroke: "black",
strokeWidth: 2}
)) : [],
((stripOrient == "Horizontal →") && (stripStats == "± SE")) ?
Plot.dot(filtered,
Plot.groupY(
{x: "mean"},
{x: stripX,
fy: stripY,
y: 0.5,
fill: "black",
r: 5}
)) : [],
((stripOrient == "Horizontal →") && (stripStats == "± SE")) ?
Plot.link(filtered,
Plot.groupY({
x1: (filtered) => d3.mean(filtered)
- d3.deviation(filtered)/Math.sqrt(d3.count(filtered)),
x2: (filtered) => d3.mean(filtered)
+ d3.deviation(filtered)/Math.sqrt(d3.count(filtered))
},
{x: stripX,
fy: stripY,
y: 0.5,
stroke: "black",
strokeWidth: 3}
)) : [],
// Mean and confidence interval marks
((stripOrient == "Vertical ↑") && (stripStats == "± 95% CI")) ?
Plot.dot(
filtered,
Plot.groupX(
{y: "mean"},
{y: stripY,
fx: stripX,
x: 0.5,
fill: "black",
r: 5}
)) : [],
((stripOrient == "Vertical ↑") && (stripStats == "± 95% CI")) ?
Plot.link(filtered,
Plot.groupX({
y1: (filtered) => d3.mean(filtered)
- 1.96*d3.deviation(filtered)/Math.sqrt(d3.count(filtered)),
y2: (filtered) => d3.mean(filtered)
+ 1.96*d3.deviation(filtered)/Math.sqrt(d3.count(filtered))
},
{y: stripY,
fx: stripX,
x: 0.5,
stroke: "black",
strokeWidth: 2}
)) : [],
((stripOrient == "Horizontal →") && (stripStats == "± 95% CI")) ?
Plot.dot(filtered,
Plot.groupY(
{x: "mean"},
{x: stripX,
fy: stripY,
y: 0.5,
fill: "black",
r: 5}
)) : [],
((stripOrient == "Horizontal →") && (stripStats == "± 95% CI")) ?
Plot.link(filtered,
Plot.groupY({
x1: (filtered) => d3.mean(filtered)
- 1.96*d3.deviation(filtered)/Math.sqrt(d3.count(filtered)),
x2: (filtered) => d3.mean(filtered)
+ 1.96*d3.deviation(filtered)/Math.sqrt(d3.count(filtered))
},
{x: stripX,
fy: stripY,
y: 0.5,
stroke: "black",
strokeWidth: 3}
)) : [],
],
width: stripWidth
})
// Big ELSE stripDotFill is the NOT same as the main x or y variable
// Subplots needed. Subplots implemented using undocumented render
// transform, as demonstrated by the extraordinary Fil:
// https://observablehq.com/@fil/subplots-1870
// see also: https://observablehq.com/@observablehq/plot-of-plots
:
Plot.plot({
height: stripHeight,
width: stripWidth,
marginLeft: (stripOrient == "Horizontal →") ? 80 : 40,
marginRight: (stripOrient == "Horizontal →") ? 80 : [],
marginBottom: 36,
marginTop: 36,
symbol:
{ legend: true,
tickFormat: formatLabel,
label: formatLabel(stripDotFill),
},
x: (stripOrient == "Horizontal →") ?
{ domain: [Math.min(...Plot.valueof(filtered, stripX)),
Math.max(...Plot.valueof(filtered, stripX))],
} : [],
y: (stripOrient == "Vertical ↑") ?
{ domain: [Math.min(...Plot.valueof(filtered, stripY)),
Math.max(...Plot.valueof(filtered, stripY))],
} : [],
fx: (stripOrient == "Horizontal →") ?
{ axis: null } : [], // Will set with axis mark
fy: (stripOrient == "Vertical ↑") ?
{ axis: null } : [], // Will set with axis mark
marks: [
// Grid and axis marks
(stripOrient == "Vertical ↑") ? Plot.gridY() : Plot.gridX(),
(stripOrient == "Horizontal →") ?
Plot.axisFy({
label: formatLabel(stripY),
fontSize: largeFontSize,
labelAnchor: "top",
ticks: [],
}) : [],
(stripOrient == "Vertical ↑") ?
Plot.axisFx({
label: formatLabel(stripX),
fontSize: largeFontSize,
ticks: [],
}) : [],
(stripOrient == "Horizontal →") ?
Plot.axisFy({
label: null,
tickFormat: formatLabel,
}) : [],
(stripOrient == "Vertical ↑") ?
Plot.axisFx({
label: null,
tickFormat: formatLabel,
}) : [],
(stripOrient == "Horizontal →") ?
Plot.axisX({
label: formatLabel(stripX),
fontSize: largeFontSize,
labelOffset: 36,
ticks: [],
}) : [],
(stripOrient == "Vertical ↑") ?
Plot.axisY({
label: formatLabel(stripY),
fontSize: largeFontSize,
labelOffset: 36,
ticks: [],
}) : [],
(stripOrient == "Horizontal →") ?
Plot.axisX({
label: null,
tickFormat: formatLabel,
}) : [],
(stripOrient == "Vertical ↑") ?
Plot.axisY({
label: null,
tickFormat: formatLabel,
}) : [],
// This Kluge puts symbols in the color legend
Plot.dot(filtered, {
fill: stripDotFill,
symbol: stripDotFill,
}),
// Text marks - Where the subplots are
(stripOrient == "Horizontal →") ?
Plot.text(getUniqueValsOfColumn(filtered, stripY), {
frameAnchor: "middle",
text: Plot.identity,
fy: Plot.identity, // facets vertically by stripY
render([i], { scales }, { channels }, dimensions) {
return svg`<g>${
Plot.plot({
...dimensions,
...scales,
x: { ...scales.x, axis: null },
y: { axis: null },
fy: { axis: null },
marks:[
// Axis marks
Plot.axisFy({
anchor: "left",
tickFormat: formatLabel,
label: formatLabel(stripDotFill),
}),
// Box marks
(stripStats == "BoxPlot") ?
Plot.boxX(filtered, {
filter: (d) => d[stripY] === channels.text.value[i],
fy: stripDotFill,
x: stripX,
// fill: stripStatsColor,
fill: "gray",
fillOpacity: 0.35,
stroke: "gray"}) : [],
// Dot marks -- Jitter
(dotsJitterOrDodge == "Jitter") ?
Plot.dot(filtered, {
filter: (d) => d[stripY] === channels.text.value[i],
fy: stripDotFill,
y: Math.random,
x: stripX,
fill: stripDotFill,
opacity: dotsOpacity,
symbol: stripDotFill,
}) : [],
// Dot marks -- Dodge
(dotsJitterOrDodge == "Dodge") ?
Plot.dot(filtered,
Plot.dodgeY("middle", {
filter: (d) => d[stripY] === channels.text.value[i],
fy: stripDotFill,
x: stripX,
fill: stripDotFill,
opacity: dotsOpacity,
symbol: stripDotFill,
})) : [],
// Mean marks
((stripStats == "± SE") || (stripStats == "± 95% CI")) ?
Plot.dot(
filtered,
Plot.groupY(
{x: "mean"},
{
filter: (d) => d[stripY] === channels.text.value[i],
x: stripX,
fy: stripDotFill,
y: 0.5,
fill: "black",
r: 5
}
)) : [],
// SE marks
(stripStats == "± SE") ?
Plot.link(filtered,
Plot.groupY({
x1: (filtered) => d3.mean(filtered)
- d3.deviation(filtered)/Math.sqrt(d3.count(filtered)),
x2: (filtered) => d3.mean(filtered)
+ d3.deviation(filtered)/Math.sqrt(d3.count(filtered))
},
{
filter: (d) => d[stripY] === channels.text.value[i],
x: stripX,
fy: stripDotFill,
y: 0.5,
stroke: "black",
strokeWidth: 2}
)) : [],
// 95% CI marks
(stripStats == "± 95% CI") ?
Plot.link(filtered,
Plot.groupY({
x1: (filtered) => d3.mean(filtered)
- 1.96*d3.deviation(filtered)/Math.sqrt(d3.count(filtered)),
x2: (filtered) => d3.mean(filtered)
+ 1.96*d3.deviation(filtered)/Math.sqrt(d3.count(filtered))
},
{
filter: (d) => d[stripY] === channels.text.value[i],
x: stripX,
fy: stripDotFill,
y: 0.5,
stroke: "black",
strokeWidth: 2}
)) : [],
]
})
}`;
}
}) : [],
// Text marks - Where the subplots are
(stripOrient == "Vertical ↑") ?
Plot.text(getUniqueValsOfColumn(filtered, stripX), {
frameAnchor: "middle",
text: Plot.identity,
fx: Plot.identity, // facets vertically by stripX
render([i], { scales }, { channels }, dimensions) {
return svg`<g>${
Plot.plot({
...dimensions,
...scales,
insetLeft: 4,
insetRight: 4,
y: { ...scales.y, axis: null },
x: { axis: null },
fx: { axis: null },
marks:[
// Axis marks
Plot.axisFx({
anchor: "bottom",
label: formatLabel(stripDotFill),
tickFormat: formatLabel,
}),
// Box marks
(stripStats == "BoxPlot") ?
Plot.boxY(filtered, {
filter: (d) => d[stripX] === channels.text.value[i],
fx: stripDotFill,
y: stripY,
fill: "gray",
fillOpacity: 0.35,
stroke: "gray"}) : [],
// Dot marks -- Jitter
(dotsJitterOrDodge == "Jitter") ?
Plot.dot(filtered, {
filter: (d) => d[stripX] === channels.text.value[i],
fx: stripDotFill,
x: Math.random,
y: stripY,
fill: stripDotFill,
opacity: dotsOpacity,
symbol: stripDotFill,
}) : [],
// Dot marks -- Dodge
(dotsJitterOrDodge == "Dodge") ?
Plot.dot(filtered,
Plot.dodgeX("middle", {
filter: (d) => d[stripX] === channels.text.value[i],
fx: stripDotFill,
y: stripY,
fill: stripDotFill,
opacity: dotsOpacity,
symbol: stripDotFill,
})) : [],
// Mean marks
((stripStats == "± SE") || (stripStats == "± 95% CI")) ?
Plot.dot(
filtered,
Plot.groupX(
{y: "mean"},
{
filter: (d) => d[stripX] === channels.text.value[i],
y: stripY,
fx: stripDotFill,
x: 0.5,
fill: "black",
r: 5
}
)) : [],
// SE marks
(stripStats == "± SE") ?
Plot.link(filtered,
Plot.groupX({
y1: (filtered) => d3.mean(filtered)
- d3.deviation(filtered)/Math.sqrt(d3.count(filtered)),
y2: (filtered) => d3.mean(filtered)
+ d3.deviation(filtered)/Math.sqrt(d3.count(filtered))
},
{
filter: (d) => d[stripX] === channels.text.value[i],
y: stripY,
fx: stripDotFill,
x: 0.5,
stroke: "black",
strokeWidth: 2}
)) : [],
// 95% CI marks
(stripStats == "± 95% CI") ?
Plot.link(filtered,
Plot.groupX({
y1: (filtered) => d3.mean(filtered)
- 1.96*d3.deviation(filtered)/Math.sqrt(d3.count(filtered)),
y2: (filtered) => d3.mean(filtered)
+ 1.96*d3.deviation(filtered)/Math.sqrt(d3.count(filtered))
},
{
filter: (d) => d[stripX] === channels.text.value[i],
y: stripY,
fx: stripDotFill,
x: 0.5,
stroke: "black",
strokeWidth: 2}
)) : [],
]
})
}`;
}
}) : [],
]
})
;html
`<div id="stripStatistics" autocorrect="off" spellcheck="false" style="display: ${stripStatsDisplay}; padding-left: 1em; font-size: 80%;">
<caption>
<b>One-way ANOVA</b>
</caption>
<table style="line-height: 75%;">
<tr>
<td style="padding: 0.5em; color: gray;">Source</td>
<td style="padding: 0.5em; color: gray;">Deg fr</td>
<td style="padding: 0.5em; color: gray;">Sum Sq</td>
<td style="padding: 0.5em; color: gray;">Mean Sq</td>
<td style="padding: 0.5em; color: gray;"><i>F</i></td>
<td style="padding: 0.5em; color: gray;"><i>P</i> - value</td>
</tr>
<tbody>
<tr>
<td style="padding: 0.5em; color: gray;">Between grps</td>
<td style="padding: 0.5em;">${myOLSModelStripANOVA.df_model}</td>
<td style="padding: 0.5em;">${myOLSModelStripANOVA.SSE.toPrecision(5)}</td>
<td style="padding: 0.5em;">${(myOLSModelStripANOVA.SSE / myOLSModelStripANOVA.df_model).toPrecision(5)}</td>
<td style="padding: 0.5em; color: steelblue;">${myOLSModelStripANOVA.f.F_statistic.toPrecision(5)}</td>
<td style="padding: 0.5em; color: steelblue;">${pValStr(myOLSModelStripANOVA.f.pvalue)}</td>
</tr>
<tr>
<td style="padding: 0.5em; color: gray;">Within grps</td>
<td style="padding: 0.5em;">${myOLSModelStripANOVA.df_resid}</td>
<td style="padding: 0.5em;">${myOLSModelStripANOVA.SSR.toPrecision(5)}</td>
<td style="padding: 0.5em;">${(myOLSModelStripANOVA.SSR / myOLSModelStripANOVA.df_resid).toPrecision(5)}</td>
<td style="padding: 0.5em;"></td>
<td style="padding: 0.5em;"></td>
</tr>
<tr>
<td style="padding: 0.5em; color: gray;">Total<br><br><span style="color: steelblue;"><i>R</i><sup> 2</sup> = ${myOLSModelStripANOVA.R2.toPrecision(5)}</span></td>
<td style="padding: 0.5em;">${myOLSModelStripANOVA.df_model + myOLSModelStripANOVA.df_resid}</td>
<td style="padding: 0.5em;">${myOLSModelStripANOVA.SST.toPrecision(5)}</td>
<td style="padding: 0.5em;"></td>
<td style="padding: 0.5em;"></td>
</tr>
</tbody>
</table>
</div>`html
`<div id="stripEditor" autocorrect="off" spellcheck="false">
<p>Figure #. <b>Title.</b></p><p>Caption.</p>
</div>`chart = {
// From https://observablehq.com/@observablehq/plot-marimekko
const xy = (options) => marimekko({...options,
x: "mXVar",
y: "mYVar",
value: "mVal"});
//Marimekko
return Plot.plot({
width: barChartWidth,
height: barChartHeight,
label: null,
color: {legend: true, tickFormat: formatLabel},
marginTop: 35,
marginBottom: 48,
x: {percent: true},
y: {percent: true, ticks: 0, tickFormat: (d) => d === 100 ? `100%` : d},
marks: [
Plot.frame({opacity: 0.25}),
// Added these
Plot.axisX({ // Axis with just the ticks in the default fontSize
label: null,
// ticks: 11,
tickFormat: (d) => d === 100 ? `100%` : d
}),
Plot.axisX({ // Axis with just the label in custom fontSize
label: formatLabel(barXVar),
fontSize: largeFontSize,
labelOffset: 36,
ticks: [],
}),
(barXVar !== barFill) ?
Plot.axisY({ // Axis with just the ticks in the default fontSize
label: null,
ticks: (barFacetY == "") ? 10 : 5,
tickFormat: (d) => d === 100 ? `100%` : d
}) : [],
(barXVar !== barFill) ?
Plot.axisY({ // Axis with just the label in the custom fontSize
label: formatLabel(barFill),
fontSize: largeFontSize,
labelOffset: 36,
ticks: [],
}) : [],
Plot.rect(mData, xy({fill: "mYVar", fillOpacity: barOpacity})),
Plot.text(mData, xy({text: d => (d.mVal > 0) ?
[formatLabel(d.mVal.toLocaleString("en")),
(barFill !== barXVar) ?
"y: " + formatLabel(d.mYVar) : formatLabel(d.mYVar),
(barFill !== barXVar) ?
"x: " + formatLabel(d.mXVar)
:
""
].join("\n") : "" })),
]
});
};
html
`<div id="marimekkoStatistics" autocorrect="off" spellcheck="false" style="display: ${marimekkoStatsDisplay}; padding-left: 1em; font-size: 80%;">
<div style="display: flex; flex-direction: row;">
<div style="flex: 5;">
</div>
<div style="flex: 50;">
<caption>
<b>χ<sup>2</sup> test</b>
</caption>
<p style="padding-top: 1em;">
${chiSq}
</p>
</div>
<div style="flex: 5;">
</div>
<div style="flex: 100;">
<caption>
<b>Expected values</b>
</caption>
<p></p>
${expChart}
</div>
</div>
<div style="flex: 5;">
</div>
</div>`html
`<div id="barEditor" autocorrect="off" spellcheck="false">
<p>Figure #. <b>Title.</b></p><p>Caption.</p>
</div>`
I wrote ardeaStat for my introductory biology students. The idea is to give them a chance to explore, with interesting datasets and as frictionlessly as possible, a few relatively straightforward statistical tests that can be done with data displayed in some common types of graphs. My hope is that once students learn a bit about what formal statistical analyses can do for them, they’ll be motivated to master more general-purpose and complex software tools.
My work on ardeaStat was inspired by SimBio’s GraphSmarts assessments, by the other participants in the GraphSmarts Faculty Mentoring Network, and by easyPlot. It was partially funded by Grappling with graphs: New Tools For Improving Graphing Practices of Undergraduate Biology Students (NSF# 2111150).
If you use ardeaStat, I’d love to get an email about your experience.
Thanks,Jon C. Herron
ardeaStat is made with
Quarto, including:
Observable js, especially:
Many thanks to the creators of these libraries!
Sources for built-in datasets
Palmer Penguins — observations with missing data (NA) have been removed.
Elephants: Mduduzi Ndlovu et al. 2018
Primates and carnivores: Daniel L. Bowling et al. 2020
Hurricane lizards one: Colin M. Donihue et al. 2018
Hurricane lizards two: Colin M. Donihue et al. 2020
d3 = require("d3@7"); // Used for basic functionality
Quill = require("https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"); // Used for captions
// ML = require("https://www.lactame.com/lib/ml/6.0.0/ml.min.js");
// Used for linear models. See:
// https://observablehq.com/@observablehq/simple-linear-regression-in-observable
// Might be useful for simulations, b/c
// https://mljs.github.io/matrix/classes/CholeskyDecomposition.html
myJstat = require("https://cdn.jsdelivr.net/npm/jstat@latest/dist/jstat.min.js");
// Global variables
largeFontSize = 13;
myColors = ["tomato", "forestgreen", "steelblue", "slateblue", "black"];
// Functions used across pages
function formatLabel(myString) {
if ((typeof myString !== "string") || (myString === "pH")){
return myString;
} else if ( (myString == null) || (myString.length === 0) ) {
return "";
} else {
myString = myString.replaceAll("_", " ");
return myString.charAt(0).toUpperCase() + myString.slice(1);
};
};
function getCategoricalColumns(data) {
if (!data || data.length === 0) {
return [];
};
const categoricalColumns = [];
const dataKeys = Object.keys(data[0]);
for (let i = 0; i < dataKeys.length; i++) {
let j = 0;
let myType = undefined;
while ( (j < data.length) && (myType !== 'string' ) && (myType !== 'number') ) {
myType = typeof data[j][dataKeys[i]];
j++;
};
if (myType === 'string') {
categoricalColumns.push(dataKeys[i]);
};
};
return categoricalColumns;
};
function getNumericalColumns(data) {
if (!data || data.length === 0) {
return [];
};
const numericalColumns = [];
const dataKeys = Object.keys(data[0]);
for (let i = 0; i < dataKeys.length; i++) {
let j = 0;
let myType = undefined;
while ( (j < data.length) && (myType !== 'string' ) && (myType !== 'number') ) {
myType = typeof data[j][dataKeys[i]];
j++;
};
if (myType === 'number') {
numericalColumns.push(dataKeys[i]);
};
};
return numericalColumns;
};
// May replicate Plot.valueof (see: https://observablehq.com/plot/features/transforms)
function columnToArray(data, column) {
return data.map((row) => row[column]);
};
function getUniqueVals(array) {
return array.filter((value, index, self) => self.indexOf(value) === index);
};
function getUniqueValsOfColumn(data, column) {
return getUniqueVals(columnToArray(data, column));
};
function set(input, value) {
input.value = value;
input.dispatchEvent(new Event("input", {bubbles: true}));
};
populateWithOptions = function(myArray, defIndex) {
const returnArray = [];
for (let i = 0; i < myArray.length; i++) {
if (i == defIndex) {
returnArray.push("<option value='" + myArray[i] + "' selected>" + formatLabel(myArray[i]) + "</option>")
} else {
returnArray.push("<option value='" + myArray[i] + "'>" + formatLabel(myArray[i]) + "</option>")
};
};
return returnArray;
};
function pValStr(pVal) {
if (pVal < 0.000001) {
return "< 0.000001";
} else {
const myStr = "";
return myStr.concat(pVal.toPrecision(5));
}
};
function quillDisplay(myBoolean, myElement) {
const elem = document.getElementById(myElement);
if (myBoolean == true) {
elem.style.display = "block";
} else {
elem.style.display = "none";
};
};
function statsDisplay(myBoolean,
//myElement
) {
//const elem = document.getElementById(myElement);
if (myBoolean == true) {
// elem.style.display = "block";
return "block";
} else {
// elem.style.display = "none";
return "none";
};
};
function quillResetText(myQuill) {
myQuill.setContents([
{ insert: 'Figure #. ' },
{ insert: 'Title', attributes: { bold: true } },
{ insert: '\n' },
{ insert: 'Caption.' },
{ insert: '\n' },
]);
};
function quillSetWidth(myNum, myElement) {
const elem = document.getElementById(myElement);
const myString = myNum.toString().concat("px");
elem.style.width = myString;
};
// Experimental - can detect clicks on window; set global flag
// Using this as a kluge to avoid caclulating complex models when they're not visible
mutable currentHash = "";
addEventListener('click', function() {
mutable currentHash = window.location.hash;
});