ds@mcpanalytics.aiA data scientist you can email your data and question to, and get a reliable response. Not AI slop.A data scientist you can email. Not AI slop.

UMAP vs t-SNE: Speed, Scale, and Structure

By MCP Analytics Team | Published
Quick answer: Use UMAP by default for large datasets, for maps you will reuse (it places new points with transform()), or when you want more than two output dimensions before clustering. Use t-SNE when you want the clearest 2D picture of local clusters, or results comparable with published t-SNE work.

The speed gap is smaller than often claimed. Exact t-SNE is O(n²), but the Barnes-Hut t-SNE in scikit-learn is O(n log n), and FFT-accelerated t-SNE (FIt-SNE, openTSNE) scales to millions of points. UMAP is usually still faster on the same data. On global structure, both depend heavily on initialization: started from the same PCA or spectral layout, they arrange distant clusters far more alike than their default settings suggest.

Neither method preserves distances between clusters or cluster sizes. Read which points group together, not how far apart the groups are.

UMAP and t-SNE both turn high-dimensional data (embeddings, gene expression, customer features) into a 2D or 3D map where similar points sit together. They rest on similar ideas and often produce similar-looking pictures, so the choice comes down to scale, reuse, and what you want to read from the plot. This guide compares them on each, with the parameters that change results and the mistakes that produce misleading maps.

UMAP vs t-SNE at a Glance

t-SNE UMAP
Introduced van der Maaten and Hinton (2008) McInnes, Healy and Melville (2018)
What it optimizes Matches neighbour probabilities (KL divergence), with a heavy-tailed Student-t kernel in the map Matches a fuzzy nearest-neighbour graph (cross-entropy), by sampling edges with negative sampling
Runtime Exact: O(n²). Barnes-Hut: O(n log n). FFT-accelerated: roughly linear Approximate nearest neighbours (NN-descent) plus sampled optimization: roughly linear in practice
Main parameters perplexity (default 30) n_neighbors (default 15), min_dist (default 0.1)
New points No transform() in scikit-learn; openTSNE can add points to an existing map transform() places new points on a fitted map
Output dimensions Usually 2; Barnes-Hut supports at most 3 Any number; 5 to 10 is common before clustering
Default initialization PCA in scikit-learn (since version 1.2) Spectral embedding of the neighbour graph
Python sklearn.manifold.TSNE, openTSNE umap-learn (import umap)

Speed and Scale: What the Complexity Really Is

A common claim is that t-SNE is O(n²) and therefore stops being usable around 100,000 points. That is true only of exact t-SNE, which computes every pairwise similarity. The implementations people actually run approximate it:

In practice UMAP is usually faster than Barnes-Hut t-SNE on the same machine, and the gap grows with dataset size; against FFT-accelerated t-SNE it narrows. Both run on a single machine for datasets in the millions if memory allows. For large data, choose the implementation as carefully as the algorithm: scikit-learn's TSNE is typically slower than openTSNE.

Common Mistake: Downsampling to a few thousand points so that t-SNE finishes. Rare groups can disappear from the sample. Try openTSNE or FIt-SNE on the full data, or UMAP, before throwing rows away.

Global Structure: Less Different Than It Looks

t-SNE's objective mostly rewards keeping neighbours together, and its heavy-tailed kernel pushes clusters apart, so the gaps between well-separated clusters in a t-SNE map carry little meaning. UMAP is often described as preserving global structure better, and with default settings its maps usually do keep more of the coarse arrangement.

Much of that difference comes from how each method starts. Kobak and Linderman (2021, Nature Biotechnology) showed that UMAP's spectral initialization, not its objective, accounts for most of its better global layout: t-SNE started from PCA preserves global structure about as well, and UMAP started from a random layout loses much of it. scikit-learn's TSNE now starts from PCA by default.

What neither method preserves: distances between clusters, cluster sizes, and densities. A tight cluster on the map can be spread out in the data, and two clusters that look far apart can be close. Read which points group together and which clusters sit next to which, then check any distance-based conclusion in the original space. (UMAP's densmap=True option tries to keep local density.)

Parameter Tuning: n_neighbors vs Perplexity

Both algorithms have one main parameter that sets the size of the neighbourhood each point looks at. Understanding what it does decides whether a map shows real structure or an artifact of the setting.

UMAP's n_neighbors: Controlling Pattern Scale

The n_neighbors parameter sets how many neighbours UMAP uses to build the high-dimensional graph. Low values (5 to 10) focus on very local structure. High values (30 to 50) bring in more global information.

The default is 15, a good starting point. When to adjust:

The map changes with n_neighbors, but usually gradually: more or less detail within a similar overall shape.

t-SNE's Perplexity: Check More Than One Value

Perplexity is a smooth measure of how many effective neighbours each point has, set through the Gaussian kernel's bandwidth. The default is 30, and values between 5 and 50 are typical.

Different perplexity values can split or merge clusters on the same data. Without ground-truth labels, a cluster that appears at only one perplexity is suspect, so compare two or three values and keep what survives. Larger datasets often need a larger perplexity.

Other Critical Parameters

min_dist (UMAP only): Controls how tightly points pack in the map. The default of 0.1 suits visualization. Use 0.0 when the embedding feeds a clustering step. Use 0.3 to 0.5 for looser, more spread-out maps.

metric: Both algorithms accept other distance metrics. Euclidean is the default and suits most tabular data. Use cosine for text embeddings and other data compared by angle. Use correlation when relative patterns matter more than absolute values, as in some gene expression work.

# UMAP implementation with parameter tuning
import umap
import numpy as np
from sklearn.preprocessing import StandardScaler

# Load your high-dimensional data
# X shape: (n_samples, n_features)
X = np.load('customer_embeddings.npy')

# Always standardize first
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# Standard configuration for balanced local/global structure
reducer = umap.UMAP(
    n_neighbors=15,        # Default - good starting point
    min_dist=0.1,          # Standard for visualization
    n_components=2,        # 2D for plotting
    metric='euclidean',    # Use 'cosine' for text embeddings
    random_state=42        # Reproducibility
)

embedding = reducer.fit_transform(X_scaled)

# For large datasets (>100K points), reduce memory use
reducer_fast = umap.UMAP(
    n_neighbors=15,
    min_dist=0.1,
    n_components=2,
    metric='euclidean',
    low_memory=True,       # Reduces memory footprint
    random_state=42
)

embedding_fast = reducer_fast.fit_transform(X_scaled)

When t-SNE Is the Better Choice

The clearest 2D view of local clusters: t-SNE's heavy-tailed kernel separates clusters strongly, which often makes cleaner figures for presentation.

Matching published work: If you are extending research that used t-SNE and need comparable results, use t-SNE with the same parameters. Switching algorithms changes results enough to make comparison difficult.

A tested existing pipeline: If you have a working t-SNE setup with checked parameters, switching to UMAP means checking it again. A working solution can beat a theoretically better one.

Before Choosing t-SNE on Large Data: Use a PCA initialization and an FFT-accelerated implementation (openTSNE or FIt-SNE). Most complaints about t-SNE being slow or scrambling global structure come from exact or randomly initialized runs.

Implementation Patterns for Real Datasets

Three common scenarios, with parameter recommendations for each.

E-commerce: Customer Segmentation with Transaction Data

You have customer purchase histories encoded as high-dimensional features (product categories, frequency, recency, monetary value). You want to find segments that are not obvious from simple RFM analysis.

Use UMAP with:

A map like this can separate, for example, high-value sporadic buyers from frequent low-value buyers with similar lifetime value. Confirm any segment it suggests against the original features before acting on it.

Genomics: Single-Cell RNA Sequencing

You have gene expression profiles for hundreds of thousands of cells. You need to identify cell types and subtypes, including rare populations below 1% of cells.

Use UMAP with:

Both methods are standard in this field. Cells often lie on continuous differentiation paths rather than in discrete groups; UMAP's default settings tend to keep those paths continuous, and t-SNE with a PCA start and a larger perplexity shows many of them too.

NLP: Document Embedding Visualization

You have a large set of documents embedded as 768-dimensional vectors from BERT or a similar model. You want to see topic clusters and find documents that bridge topics.

Use UMAP with:

With cosine distance, documents on a mixed topic (say, machine learning in healthcare) tend to sit between the two topic clusters, which helps find content that bridges audiences.

# Complete pipeline for e-commerce customer segmentation
import umap
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import HDBSCAN
import matplotlib.pyplot as plt

# Load customer feature matrix
# Rows: customers, Columns: features (RFM, product affinities, etc.)
customer_features = np.load('customer_features.npy')

# Standardize features
scaler = StandardScaler()
X_scaled = scaler.fit_transform(customer_features)

# UMAP dimensionality reduction
reducer = umap.UMAP(
    n_neighbors=20,
    min_dist=0.0,
    n_components=2,
    metric='euclidean',
    random_state=42
)

embedding_2d = reducer.fit_transform(X_scaled)

# Cluster in the reduced space using HDBSCAN
clusterer = HDBSCAN(
    min_cluster_size=50,
    min_samples=10,
    metric='euclidean'
)

labels = clusterer.fit_predict(embedding_2d)

# Visualize with cluster labels
plt.figure(figsize=(12, 8))
scatter = plt.scatter(
    embedding_2d[:, 0],
    embedding_2d[:, 1],
    c=labels,
    cmap='Spectral',
    s=5,
    alpha=0.5
)
plt.colorbar(scatter)
plt.title('Customer Segments from UMAP + HDBSCAN')
plt.xlabel('UMAP Component 1')
plt.ylabel('UMAP Component 2')
plt.savefig('customer_segments.png', dpi=300, bbox_inches='tight')

# Then profile each cluster on the ORIGINAL features
# before treating it as a real segment

The Decision Matrix: UMAP vs t-SNE

Start at the top and work down until a condition matches your situation.

Condition Algorithm Reason
New points must be placed on the same map later UMAP transform() reuses the fitted map
More than 3 output dimensions (for example, before clustering) UMAP Barnes-Hut t-SNE supports at most 3
Millions of points UMAP, or FFT-accelerated t-SNE Both scale with the right implementation; UMAP is usually faster
The coarse layout of clusters matters Either, with PCA or spectral initialization Initialization drives the global layout
Clearest local cluster separation in a 2D figure t-SNE Its heavy-tailed kernel separates clusters strongly
Replicating published work that used t-SNE t-SNE Direct comparability
Exploratory work on large data, no other constraint UMAP Fast, reusable, and few settings to start with

Hyperparameter Tuning: What Actually Matters

UMAP: The Parameters That Change Results

n_neighbors (impact: HIGH): The primary tuning parameter.

min_dist (impact: MEDIUM): Controls packing tightness in the map:

metric (impact: HIGH for specific data types):

t-SNE: Perplexity, Initialization and Learning Rate

perplexity (impact: HIGH):

init (impact: HIGH for global layout): Use init="pca" (the scikit-learn default since 1.2). A random start is the main reason t-SNE maps place clusters arbitrarily.

learning_rate: "auto" in scikit-learn scales it with the number of points, which avoids poorly converged maps on large data.

Common Implementation Mistakes and How to Avoid Them

These errors are the ones that most often produce misleading maps.

Mistake 1: Not Standardizing Features

Both UMAP and t-SNE are sensitive to feature scale. If one feature ranges from 0 to 1,000 and another from 0 to 1, the large-scale feature dominates the distances, and the map effectively shows one feature.

Fix: Scale features with StandardScaler or MinMaxScaler before running either algorithm, unless the features already share a meaningful scale (as embeddings usually do).

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Now run UMAP/t-SNE on X_scaled

Mistake 2: Treating Map Distances as Real Distances

Distances in a UMAP or t-SNE map are not quantitatively meaningful. You cannot say "clusters A and B are twice as far apart as C and D, so A and B are twice as different."

What is meaningful: cluster membership, which points are neighbours, and (with a good initialization) the rough arrangement of clusters.

Fix: Use the map for visualization and for finding candidate clusters. If you need distances, compute them in the original space.

Mistake 3: Clustering a 2D t-SNE Map

t-SNE exaggerates the gaps between clusters and compresses points within them. Running k-means or DBSCAN on a 2D t-SNE map finds clusters of the picture, not of your data.

Fix: Cluster in the original space or a PCA-reduced space. A UMAP embedding with min_dist=0.0 and 5 to 10 dimensions is a common input to HDBSCAN, but validate the clusters against the original features.

Mistake 4: Ignoring Random Seed Variation

Both algorithms use stochastic optimization, so different random seeds give different maps. A cluster that appears with only some seeds is probably not real.

Fix: Run 3 to 5 seeds. Structure that is consistent across runs is likely real; structure that changes is noise.

# Check stability across random seeds
import umap
import numpy as np

results = []
for seed in [42, 123, 456, 789, 1011]:
    reducer = umap.UMAP(n_neighbors=15, random_state=seed)
    embedding = reducer.fit_transform(X_scaled)
    results.append(embedding)

# Visually compare results or use clustering consistency metrics
# If clusters remain similar across seeds, structure is stable

Your turn

Bring your own data and the question you actually need answered.

CympleData Scientist Send me your data and question, I’ll send you the analytics. ds@mcpanalytics.ai

Frequently Asked Questions

Is UMAP better than t-SNE?
Neither is better in general. UMAP is usually faster, can place new points with transform(), and works well in more than two dimensions, so it is the common default for large or reused embeddings. t-SNE often gives cleaner separation of local clusters in a 2D plot. Started from the same initialization, their global layouts are closer than their default settings suggest.
Is UMAP faster than t-SNE?
Usually, on the same data and hardware, and the gap grows with dataset size. But the often-quoted O(n²) for t-SNE applies only to exact t-SNE. Barnes-Hut t-SNE, the scikit-learn default, is O(n log n), and FFT-accelerated t-SNE in openTSNE or FIt-SNE scales to millions of points.
Does UMAP preserve global structure better than t-SNE?
With default settings it usually keeps more of the coarse layout, but Kobak and Linderman (2021) showed this comes mostly from UMAP's spectral initialization. t-SNE started from PCA preserves global structure about as well. Neither method preserves distances between clusters or cluster sizes.
When should I still use t-SNE instead of UMAP?
When you want the clearest picture of local clusters in a 2D figure, when you need results comparable with published t-SNE work, or when you already have a tested t-SNE pipeline. Use a PCA initialization, and check two or three perplexity values.
How do I choose between UMAP's n_neighbors and t-SNE's perplexity?
Both set the size of the neighbourhood each point looks at. UMAP's default n_neighbors is 15: lower values show finer detail, and 30 to 50 show more of the broader shape. t-SNE's default perplexity is 30, usually tried between 5 and 50. Change one at a time, and trust the clusters that survive the change.
Can I cluster on a UMAP or t-SNE embedding?
Avoid clustering a 2D t-SNE map: it finds clusters of the picture, not of the data. A UMAP embedding with min_dist=0.0 and 5 to 10 dimensions is a common input to HDBSCAN, but validate the clusters in the original feature space.

Conclusion: Choosing Between UMAP and t-SNE

For most large or reused embeddings, start with UMAP: it is fast, it can place new points on an existing map, and it works in more than two dimensions. Choose t-SNE when the goal is the clearest 2D view of local clusters or comparability with earlier work, and use an FFT-accelerated implementation when the data are large.

Whichever you pick, set the initialization deliberately, check that clusters survive a change of seed and neighbourhood size, and read distances between clusters as layout, not as data.