Try this first
This lesson has no new ideas in it. It is here so that you find out, with numbers, where your eye is reliable and where it is not.
Paste this into playground.html and play ten rounds before reading past it.
function rand(lo, hi) {
return lo + Math.random() * (hi - lo);
}
let current = null;
function challenge() {
const a = [rand(view.xMin, view.xMax), rand(view.yMin, view.yMax)];
const b = [rand(view.xMin, view.xMax), rand(view.yMin, view.yMax)];
current = [a, b];
clear();
grid();
dot(a[0], a[1], "#c33", 5);
dot(b[0], b[1], "#38a", 5);
out.textContent = "";
log("How far apart are the two dots, in data units?");
}
function reveal(guess) {
const [a, b] = current;
const truth = distance(a, b);
line(a[0], a[1], b[0], b[1], "#999");
const off = (guess - truth) / truth * 100;
log("you said", guess.toFixed(2), " truth", truth.toFixed(2),
" off by", off.toFixed(0) + "%");
}
view.xMin = 0; view.xMax = 10;
view.yMin = 0; view.yMax = 10;
challenge();
Call challenge() for a new pair and reveal(yourGuess) when you have committed to a number. Write down the percentage each round. Ten rounds is enough to see a pattern.
What most people find
Two things usually show up. Guesses on horizontal or vertical pairs are close. Guesses on diagonal pairs are too small, often by fifteen or twenty percent. The canvas is 480 by 360 while the view is 10 by 10, so one unit sideways is drawn 48 pixels wide and one unit upwards is drawn 36 pixels tall. Vertical gaps are squashed, and a diagonal gap borrows from both.
Fix the aspect ratio and try again.
view.xMin = 0; view.xMax = 10;
view.yMin = 0; view.yMax = 7.5; // 480 / 360 = 10 / 7.5
challenge();
Ten more rounds. The diagonal error should mostly disappear. Your eye was fine. The drawing was lying.
Now break it properly
view.xMin = 0; view.xMax = 1000;
view.yMin = 0; view.yMax = 10;
challenge();
Play five rounds. The errors will be enormous, and in one direction: every guess will be far too small. On screen the two dots look a moderate distance apart, but a small gap sideways is worth a hundred units and a large gap upwards is worth three.
This is exactly the situation your distance function was in at the end of the last lesson, with weight in grams against orangeness from 0 to 1. The difference is that your eye told you something felt wrong. The function returned 25.003 and looked perfectly happy.
What to watch
Nothing you did here changed the data. The same two points, the same true distance, three different impressions of it. A plot is a claim about the data, and the claim depends on choices you made about the view. When someone shows you a scatter plot, the axis ranges are part of the argument.
Exercises
- Change
revealto store each error in an array, then print the average of the absolute errors. Run twenty rounds under both the square view and the stretched one, and compare the two averages. - Add a mode where the two dots are always on the same horizontal line. Are your guesses better than the diagonal case? By how much?
- Write
fitAspect(), which adjustsview.yMaxso that the view has the same shape as the canvas. Call it insideautoScaleand confirm the diagonal error drops. - Predict what your errors would look like if the y axis were reversed, and then try it by swapping
yMinandyMax. The next section fits a line to data, and a flipped axis there will make a good model look like a bad one.