UMAP vs t-SNE: Speed, Scale, and Structure
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:
- Barnes-Hut t-SNE (van der Maaten, 2014) groups distant points with a space-partitioning tree, which makes each iteration O(n log n). It is the default
methodin scikit-learn'sTSNE. - FFT-accelerated t-SNE (FIt-SNE, Linderman and colleagues, 2019; also built into openTSNE) interpolates the repulsive forces on a grid, so each iteration is roughly linear in n. Kobak and Berens (2019) used it to embed about 1.3 million cells from a single-cell RNA-seq dataset.
- UMAP builds an approximate k-nearest-neighbour graph with NN-descent, then optimizes by sampling the graph's edges, so most of its cost grows roughly linearly with n.
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.
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:
- n_neighbors=5: Tight, well-separated clusters where you want maximum local detail, such as distinct cell types.
- n_neighbors=30: Gradual transitions where you want the broader shape of the data, such as text embeddings whose topics blend into each other.
- n_neighbors=50+: When the coarse arrangement matters more than fine detail, such as hierarchical data.
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.
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:
n_neighbors=20to capture shopping pattern similaritiesmin_dist=0.0to allow tight clusters for downstream clusteringmetric='euclidean'after standardization
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:
n_neighbors=30to keep the broader shape of the datamin_dist=0.1for balanced cluster separationmetric='correlation'or'cosine'for expression data
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:
n_neighbors=15for standard text similaritymin_dist=0.1to keep the map readablemetric='cosine'because these embeddings are usually compared by angle
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.
- 5 to 10: Emphasize local cluster detail. Use with well-separated groups.
- 15 (the default) to 30: Balanced local and global structure.
- 30 to 50: Emphasize the broader shape, for hierarchical data or continuous gradients.
- Above 50: Diminishing returns; runtime increases.
min_dist (impact: MEDIUM): Controls packing tightness in the map:
- 0.0: Tightest packing. Use when feeding the embedding into a clustering algorithm.
- 0.1: Standard for visualization.
- 0.3 to 0.5: Looser and more spread out, to see gradients between overlapping clusters.
metric (impact: HIGH for specific data types):
- Euclidean: Default. Tabular data, images, general purpose.
- Cosine: Text embeddings and other vectors compared by angle.
- Correlation: Data where relative patterns matter more than absolute values.
t-SNE: Perplexity, Initialization and Learning Rate
perplexity (impact: HIGH):
- 5 to 15: Very local. Can over-split clusters. Mostly for small datasets.
- 20 to 50: Standard range. Start with 30.
- Above 50: More global; useful on large datasets, at a higher runtime.
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
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.