3-D projections
By default Umap produces a 2-D embedding. To project to three dimensions instead, pass dimensions: 3 to the constructor.
var umap = new Umap(dimensions: 3);
var epochs = umap.InitializeFit(vectors);
for (var i = 0; i < epochs; i++)
{
umap.Step();
}
// embedding[i] is now a float[3] of (x, y, z)
float[][] embedding = umap.GetEmbedding();
When to use 3-D
2-D is the workhorse — every static chart library can render it, it fits on screen without interaction, and the eye reads cluster topology cleanly. 3-D is worth the extra dimension when:
- Two-dimensional projection mangles structure you know is there (overlapping clusters that should be separable).
- You will render interactively — Plotly, three.js, Babylon.js — where the user can rotate the cloud.
- You are feeding the embedding into a downstream model where one more coordinate genuinely helps.
For static thumbnails and dashboards, stick with 2-D.
Rendering with Plotly
The classic shipping-the-embedding-to-the-browser path: compute in C#, write to JSON, plot with Plotly. A complete loop:
using System.IO;
using System.Linq;
using System.Text.Json;
using UMAP;
var umap = new Umap(
distance: Umap.DistanceFunctions.CosineForNormalizedVectors,
dimensions: 3);
var epochs = umap.InitializeFit(vectors);
for (var i = 0; i < epochs; i++) umap.Step();
var embedding = umap.GetEmbedding();
var payload = new
{
x = embedding.Select(p => p[0]).ToArray(),
y = embedding.Select(p => p[1]).ToArray(),
z = embedding.Select(p => p[2]).ToArray(),
text = labels,
};
File.WriteAllText("embedding.json", JsonSerializer.Serialize(payload));
Then on the page:
<div id="plot" style="width: 100%; height: 800px;"></div>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script>
fetch('embedding.json')
.then(r => r.json())
.then(data => Plotly.newPlot('plot', [{
type: 'scatter3d',
mode: 'markers',
x: data.x, y: data.y, z: data.z, text: data.text,
marker: { size: 3, opacity: 0.8 }
}]));
</script>
The original UMAP-Sharp README links to a CodePen example using exactly this pipeline with MNIST embeddings.
Performance cost
Going from 2-D to 3-D adds roughly 30–50% to the cost of the optimisation step (the SGD inner loop iterates one extra coordinate per sample). The fit phase — by far the more expensive part for large datasets — is unchanged, since it operates on the original high-dimensional space.
Higher dimensions
dimensions accepts any positive integer, though anything beyond 3 is rarely useful for visualisation. If you genuinely need higher-dimensional output (e.g. as input to a downstream model where 50 dims is too much but 10 is fine), you can ask for it:
var umap = new Umap(dimensions: 10);
Cost scales linearly with dimensions for the SGD step.
Next
- MNIST Example — uses 2-D for the static images but is trivially adapted to 3-D.
- Configuration — the rest of the knobs.