Skip to content

Three ways to be near

The metric is fixed at index creation, and the space your numbers came from has usually already chosen it.

Why

Three indexes, one rgb attribute, three distance functions. The vectors are identical in all three, so every difference in the results is the metric and nothing else.

Cosine measures the angle between two vectors and throws away their length. For colours that means hue without brightness. Euclidean measures the straight-line gap, so brightness is distance. Dot product combines direction and magnitude, which is why a pale mint green beats a crimson against a red query: it is simply a longer vector.

Read the scores carefully, because they do not all run the same way. Cosine and Euclidean are distances: lower is nearer, and a vector against itself scores about zero. Dot product is a similarity: higher is nearer, and it can go negative. Sorting a dot-product result the way you would sort a cosine result gives you a ranking that is exactly inverted, and it still returns TopK rows and still looks like it worked.

You choose the function when you create the index and you cannot change it afterwards; changing your mind means a new index. A table holds at most five vector indexes, and two indexes over the same attribute have to agree on dimensions. Which to pick depends on your model: most sentence embedding models are normalised to unit length, and once every vector is the same length cosine and dot product rank identically.

That framing assumes an embedding, and it is the weaker half of the lesson. Where the numbers came out of a space with its own geometry, the metric is not a preference at all. Project a latitude and longitude onto the unit sphere and Euclidean is forced: on unit vectors the straight-line gap is 2*sin(theta/2), which climbs without ever turning back as the angle between two points grows, so the order it returns is the true great-circle order and the score converts straight back into kilometres. Convert a colour into CIELAB and Euclidean is forced again, because that space was defined so that Euclidean distance in it would be the CIE's own measure of how different two colours look. Choosing cosine in either case would not be a different flavour of nearness, it would be discarding the property the projection was performed for.

So the honest order of operations is the reverse of how this page reads. Decide what the numbers mean, and the distance function usually falls out. It is only when you have three arbitrary numbers, as these paint chips are, that the metric is genuinely yours to pick - and that is a sign the space needs more thought, not that you have a free choice. The library has both cases running: Geospatial Nearest Neighbour and Design Token Similarity.

Cosine ignores magnitude, Euclidean does not, and dot product rewards it; lower is nearer for two of them and higher for the third. The choice is fixed at index creation, and where the vectors came from a real space rather than a model, that space has usually made it for you.

Try it. Then change it.

Predict what each request will do. Run it, inspect the response, then change a value in the workbench and try again.

  1. Brick and Crimson come back first, together. Cosine compares direction and ignores magnitude, so a dark red and a bright red are near-identical to it: same hue, different brightness.

  2. Same vectors, same query, different answer. Crimson is first and Brick has fallen behind Rust, because straight-line distance treats a darker red as genuinely further away.

  3. Now a pale green outranks Crimson. Dot product rewards magnitude as well as direction, so the brightest colours float to the top whatever their hue. Note also that higher is nearer here, the opposite of the other two.

Built on this

PaintMetricsDynamoDB workbench
Ready to run
Explore an access patternSelect to load & run

Brick and Crimson come back first, together. Cosine compares direction and ignores magnitude, so a dark red and a bright red are near-identical to it: same hue, different brightness.

Request
Execute against the local Dynoxide engine
ReturnedFiltered outChanged
pk(pk)
name
family
rgb
COLOUR#crimson
CrimsonS
redS
[220, 20, 60]L
COLOUR#brick
BrickS
redS
[140, 30, 30]L
COLOUR#rose
RoseS
redS
[250, 120, 130]L
COLOUR#navy
NavyS
blueS
[20, 30, 110]L
COLOUR#azure
AzureS
blueS
[40, 110, 230]L
COLOUR#sky
SkyS
blueS
[140, 200, 245]L
COLOUR#forest
ForestS
greenS
[20, 90, 40]L
COLOUR#lime
LimeS
greenS
[110, 220, 60]L
COLOUR#mint
MintS
greenS
[170, 230, 190]L
COLOUR#amber
AmberS
yellowS
[250, 190, 40]L
COLOUR#rust
RustS
yellowS
[180, 90, 20]L
COLOUR#slate
SlateS
neutralS
[90, 100, 110]L
COLOUR#charcoal
CharcoalS
neutralS
[40, 42, 45]L
Awaiting request
Your next query starts here.

Choose an access pattern above, or build your own request. See what comes back and what it costs.

Powered by DynoxideWASMStarts on your first run · Runs locally in your browser