viewof user_b1 = Inputs.range([0, 200], {value: 100, step: 1, label: "Intercept (b₁)"})
viewof user_b2 = Inputs.range([0, 20], {value: 5, step: 0.1, label: "Slope (b₂)"})
ols_scatter_data = [
{x: 3.69, y: 115.22}, {x: 4.39, y: 135.98}, {x: 4.75, y: 119.34}, {x: 6.03, y: 114.96},
{x: 12.47, y: 187.05}, {x: 12.98, y: 243.34}, {x: 16.42, y: 267.43}, {x: 17.58, y: 238.71},
{x: 18.95, y: 295.94}, {x: 20.00, y: 317.78}, {x: 20.18, y: 216.00}, {x: 20.43, y: 269.30},
{x: 21.41, y: 302.49}, {x: 23.66, y: 325.61}, {x: 24.87, y: 301.58}, {x: 25.13, y: 264.47},
{x: 27.83, y: 342.75}, {x: 28.96, y: 339.01}, {x: 29.05, y: 365.52}, {x: 33.40, y: 424.96}
]
ols_ssr = {
let ssr = 0;
for (const d of ols_scatter_data) {
const resid = d.y - user_b1 - user_b2 * d.x;
ssr += resid * resid;
}
return ssr;
}
ols_best = {
const n = ols_scatter_data.length;
const xbar = d3.mean(ols_scatter_data, d => d.x);
const ybar = d3.mean(ols_scatter_data, d => d.y);
const num = d3.sum(ols_scatter_data, d => (d.x - xbar) * (d.y - ybar));
const den = d3.sum(ols_scatter_data, d => (d.x - xbar) ** 2);
const b2 = num / den;
const b1 = ybar - b2 * xbar;
let ssr = 0;
for (const d of ols_scatter_data) {
const resid = d.y - b1 - b2 * d.x;
ssr += resid * resid;
}
return {b1, b2, ssr};
}
Plot.plot({
width: 640,
height: 400,
x: {label: "x (income, $100s)", domain: [0, 36]},
y: {label: "y (food expenditure, $)", domain: [50, 480]},
marks: [
Plot.dot(ols_scatter_data, {x: "x", y: "y", fill: "#1E5A96", r: 5}),
// User's line
Plot.line(
[{x: 0, y: user_b1}, {x: 36, y: user_b1 + user_b2 * 36}],
{x: "x", y: "y", stroke: "#D4A84B", strokeWidth: 2.5, strokeDasharray: "6 3"}
),
// OLS line
Plot.line(
[{x: 0, y: ols_best.b1}, {x: 36, y: ols_best.b1 + ols_best.b2 * 36}],
{x: "x", y: "y", stroke: "#2E8B57", strokeWidth: 2}
),
// Residual segments for user line
Plot.link(ols_scatter_data, {
x1: "x", y1: "y",
x2: "x", y2: d => user_b1 + user_b2 * d.x,
stroke: "#C41E3A", strokeOpacity: 0.3
}),
Plot.text([`Your SSR: ${ols_ssr.toFixed(0)}`], {x: 28, y: 120, fill: "#D4A84B", fontWeight: "bold", fontSize: 13}),
Plot.text([`OLS SSR: ${ols_best.ssr.toFixed(0)}`], {x: 28, y: 90, fill: "#2E8B57", fontWeight: "bold", fontSize: 13})
],
caption: "Dashed gold = your line. Solid green = OLS line. Red segments = residuals for your line."
})