t-SNE
t-SNE draws high-dimensional data as a 2-D picture by keeping neighbours together — brilliant for spotting clusters, dangerous if you read anything else from the picture.
- 7 min read
- 3 reading levels
- Published
Read these first
On this page 5
One lesson, three depths. Pick the one that fits you today — you can switch any time.
Beginner — No maths. Plain English.
t-SNE turns high-dimensional data into a 2-D picture by making sure each point's closest neighbours stay its closest neighbours.
Think of arranging seating for a giant wedding. The one rule that matters: every guest must sit with their close friends. You will manage that. But some groups end up at opposite corners of the hall, even though they know each other a little. The hall is flat and friendships are complicated. Something has to give.
t-SNE makes exactly this compromise. Local relationships — who is each point's close friend — are preserved beautifully. Global relationships — how far one friend-circle sits from another — get sacrificed whenever the flat page demands it.
Why it exists
You cannot look at 64-dimensional data, and looking is how humans find surprises: hidden groupings, mislabelled samples, odd islands. PCA can draw a 2-D picture, but it preserves the big spread-out directions. In doing so it squashes distinct small clusters on top of each other.
t-SNE inverts the priorities. It ignores the big picture and pours everything into keeping tight neighbourhoods intact. The result: clusters that genuinely exist in the data appear as visibly separate islands on the page — which is what your eye is hunting for.
How it works
in 64 dimensions: on the 2-D page:
each point lists its t-SNE nudges points until
closest neighbours each point's page-neighbours
│ match its real neighbours
└───────> islands of genuinely similar points emergeOne knob matters: perplexity — roughly, how many close friends each point counts. Small perplexity focuses on tiny circles and can shatter data into confetti. Large perplexity considers broad circles and can blur real structure. There is no correct value; there are several informative ones.
A real example you have seen
You have seen maps of a music library where rock, ghazals and devotional songs form separate coloured islands. You have seen the famous pictures of handwritten digits settling into ten clumps. Those are t-SNE plots, or their younger cousin UMAP). Whenever a talk shows "our model's embeddings" as an archipelago of blobs, this is the machine behind it.
Remember this
- t-SNE keeps neighbours faithful and sacrifices everything else.
- Island sizes and the gaps between islands are not measurements — do not read them as distances.
- It is a tool for looking, not a preprocessing step for models.
What to learn next
- UMAP — faster, with new-point projection and somewhat better global structure.
- Reading an embedding plot honestly — the failure modes, measured.
- PCA — the pre-reduction step and the linear baseline to compare against.
Developer — Code and libraries.
Setup
pip install scikit-learnOutputs verified with scikit-learn 1.7.2. t-SNE is stochastic: exact numbers below vary across versions and platforms even with a fixed seed, though the conclusions hold.
Do the islands mean anything? Measure it.
A picture can lie, so score the embedding: if a point's nearest neighbours on the page share its label, the islands reflect reality. The digits dataset ships with sklearn — 1,797 tiny images, no download.
import numpy as np
from sklearn.datasets import load_digits
from sklearn.manifold import TSNE
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
digits = load_digits() # 1,797 tiny 8x8 digit images
X, y = digits.data, digits.target # each image = 64 numbers
emb = TSNE(n_components=2, perplexity=30, random_state=0).fit_transform(X)
print("shape:", X.shape, "->", emb.shape)
def neighbour_agreement(Z, y):
"""Do nearby points share a label? Higher = tighter, purer clusters."""
return cross_val_score(KNeighborsClassifier(10), Z, y, cv=3).mean()
print(f"neighbour agreement in 64-D: {neighbour_agreement(X, y):.3f}")
print(f"neighbour agreement in 2-D: {neighbour_agreement(emb, y):.3f}")shape: (1797, 64) -> (1797, 2) neighbour agreement in 64-D: 0.954 neighbour agreement in 2-D: 0.977
This takes under a minute on an ordinary laptop CPU.
The walkthrough
2-D scored higher than 64-D. Ten-neighbour label agreement rose from 0.954 to 0.977 after crushing 64 numbers to 2. t-SNE did not preserve the space — it sharpened neighbourhoods, pulling same-digit points together more tightly than they sit in pixel space. That is the tool doing its job, and also your warning: the picture is an argument about neighbourhoods, not a faithful map.
To actually look, scatter-plot emb coloured by label with matplotlib — plt.scatter(emb[:, 0], emb[:, 1], c=y, s=5, cmap="tab10"). Ten islands appear.
random_state=0 pins the run, because different seeds produce different — equally valid — pictures. Rotations, flips and island rearrangements are all free moves for t-SNE.
Cost scales hard with rows. Tens of thousands of points are fine; hundreds of thousands get slow. Standard practice for wide data: PCA down to about 50 columns first, then t-SNE — sklearn recommends exactly this, and it also steadies the result by denoising.
Common mistakes
Reading distances between islands. The gap between the "0" island and the "1" island is an artefact of the layout, not a measurement. The next lesson demonstrates this failure with numbers: reading an embedding plot honestly.
Reading island sizes. t-SNE expands dense clusters and compresses sparse ones to fit everything on the page. A big island is not a bigger or more varied class.
Using one perplexity. Run 5, 30, and 100. Structure that survives all three deserves belief; structure that appears at only one setting is probably that setting talking.
Transforming new points. sklearn's TSNE has no transform method — the map is optimised for one fixed dataset, and there is no function to apply to newcomers. Needing that is the classic reason to switch to UMAP.
Feeding the 2-D output into a classifier as features. The axes mean nothing, the layout changes per seed, and new data cannot be mapped. For downstream models, reduce with PCA or train a proper embedding instead.
Try it yourself
Run with perplexity=5 and perplexity=100, computing the agreement score for each. Then plot all three embeddings side by side and note which islands merge, split, or wander — same data, three defensible pictures.
What to learn next
- UMAP — faster, with new-point projection and somewhat better global structure.
- Reading an embedding plot honestly — the failure modes, measured.
- PCA — the pre-reduction step and the linear baseline to compare against.
Researcher — Mathematics and papers.
The objective
van der Maaten and Hinton (2008), Visualizing data using t-SNE, JMLR. In the input space, define conditional probabilities p_{j|i} proportional to exp(-||x_i - x_j||^2 / (2 sigma_i^2)) — the chance that i would pick j as a neighbour — symmetrised to p_ij = (p_{j|i} + p_{i|j}) / (2n). Each sigma_i is set by binary search so that the entropy of p_{.|i} matches log(perplexity): perplexity is the effective neighbour count, and every point gets its own bandwidth, which is how t-SNE adapts to varying density. In the embedding, similarities use a Student-t kernel with one degree of freedom: q_ij proportional to (1 + ||z_i - z_j||^2)^{-1}. The layout minimises KL(P || Q) by gradient descent.
Two asymmetries define the method's character. The KL direction punishes putting neighbours far apart (p large, q small) much more than putting strangers close — hence local fidelity, global indifference. The heavy-tailed t kernel gives moderate repulsion at long range, curing the crowding problem (in 2-D there is not enough room at moderate distances for all the pairs that deserve them) that sank its Gaussian-kernel predecessor SNE.
Costs and fast variants
Exact t-SNE is O(n^2) per iteration in time and memory. Barnes-Hut t-SNE (van der Maaten, 2014) treats repulsion as an N-body problem, quadtree-approximated to O(n log n) — sklearn's default (method="barnes_hut", angle trading accuracy for speed). FIt-SNE (Linderman et al., 2019) interpolates the repulsive field on a grid via FFT, effectively O(n), enabling millions of points. Early exaggeration — multiplying P for the first iterations — lets clusters form before fine-tuning; its scale and the learning rate materially affect outcomes (Belkina et al., 2019 recommend learning rate n/12 for large n).
What the theory licenses
Linderman and Steinerberger (2019) prove early-exaggeration t-SNE recovers well-separated clusters (points from the same cluster land in the same embedded cluster) under spectral-clustering-like conditions — the honest theoretical support for "islands are real". No corresponding guarantee exists for inter-cluster distances, island areas, or within-island shape; Wattenberg, Viegas and Johnson (2016, Distill) demonstrate the failure modes interactively. Kobak and Berens (2019, Nature Communications) codify practice for single-cell data: PCA initialisation (not random), multiple perplexities, and explicit caveats on global geometry — the field's consensus checklist.
What to learn next
- UMAP — faster, with new-point projection and somewhat better global structure.
- Reading an embedding plot honestly — the failure modes, measured.
- PCA — the pre-reduction step and the linear baseline to compare against.