Try this first

Here are six items with two features each.

[2.0, 8.5]   [2.4, 8.1]   [1.8, 9.0]
[7.5, 2.2]   [8.1, 2.6]   [7.9, 1.9]

Without drawing anything, say how many groups there are. Then say how long it took you. Then read on.

Why a picture beats a table

You almost certainly said two groups, and it probably took a few seconds of comparing numbers against each other. With six rows that is fine. With six hundred it is hopeless, and the numbers have not changed at all. What changed is how much work your eyes had to do to reach them.

A scatter plot hands that work to your eyes, which are very good at it. One axis per feature. One dot per item. Where the dot sits is what its numbers are. Nothing is added and nothing is lost.

Plot it

Open playground.html. You already have view, dot and clear from the last section. Add this at the bottom.

const points = [
  [2.0, 8.5], [2.4, 8.1], [1.8, 9.0],
  [7.5, 2.2], [8.1, 2.6], [7.9, 1.9],
];

view.xMin = 0; view.xMax = 10;
view.yMin = 0; view.yMax = 10;

clear();
for (const [x, y] of points) dot(x, y);
log("plotted", points.length, "points");

Two clusters, one top left and one bottom right, and you saw it before you finished reading the line. That is the entire argument for plotting.

Making the view fit the data

Setting the view by hand works while you know the numbers. The moment the data comes from somewhere else, you do not. So compute the view from the data.

function autoScale(points, pad = 0.1) {
  const xs = points.map(p => p[0]);
  const ys = points.map(p => p[1]);
  const dx = (Math.max(...xs) - Math.min(...xs)) * pad;
  const dy = (Math.max(...ys) - Math.min(...ys)) * pad;
  view.xMin = Math.min(...xs) - dx;
  view.xMax = Math.max(...xs) + dx;
  view.yMin = Math.min(...ys) - dy;
  view.yMax = Math.max(...ys) + dy;
}

autoScale(points);
clear();
for (const [x, y] of points) dot(x, y);

xs and ys are the two columns. The minimum and maximum of each give the box the data occupies. pad adds ten percent of the width on each side so that points on the edge are not cut in half by the canvas border.

What to watch

A point outside the view does not raise an error. It is drawn off the edge of the canvas and you simply never see it. If a plot looks emptier than your data, check the view before you check anything else.

There is a second thing to notice. The canvas is 480 by 360, and the view is 10 by 10. So one unit across is 48 pixels and one unit up is 36 pixels. A circle in your data is drawn as an ellipse, and a diagonal gap looks shorter than it really is. Keep that in mind for the next two lessons, where we start measuring how far apart points are.

Exercises

  1. Add the point [20, 5] to the list and plot without calling autoScale. Then call autoScale and plot again. Describe what happened to the two clusters and why.
  2. Change pad to 0 and plot. Which points get clipped, and why does that happen at exactly those points?
  3. Write plotBy(data, i, j) that plots feature i against feature j from the fruit dataset in the last lesson. Try all three pairs. Which pair separates apples from oranges most clearly?