Umap based on hamming metric is not invariant to changes in the way values are encoded
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 8.3k
- Forks
- 871
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 5
Description
I think that embedding with based on hamming distance should be invariant to the changes in numbers used, as an example, to encode categorical variables. When I add a fixed number to a dataframe of integers - embedding is not changed, but when all instances of one number in the dataframe are replaced with another number which was not present in dataframe, the embedding changes. I've put toy a below example with nice figures , and while in it the embedding works ok, in more complex data the results are substantially different. I've included t-SNE embedding for same data and it remains the same in all cases.
import numpy as np
import pandas as pd
import umap
from matplotlib import pyplot as plt
from sklearn.manifold import TSNE
fig, axs = plt.subplots(2,3)
fig.set_size_inches(15,10)
### Generate matrix of integer drawn from two different distributions, two distributions provide contrast
dataInt=np.random.randint(0, 5, size=(100, 50)) #matrix of random integers between 0 and 5
dataTri=np.matrix.round( np.random.triangular(1, 3, 6, size=(100, 50)) ) #matrix of random integers between 0 and 5
dataDF=pd.DataFrame(np.vstack( (dataInt, dataTri )) ) #cleaner to replace values in dataframe
dataDF.apply(pd.to_numeric, downcast="integer") #ensure all values are integers
### Embed and plot using base data
embedding=umap.UMAP(n_neighbors=50, min_dist=0.01, unique=True, metric="hamming", random_state=42).fit_transform(dataDF) #umap embedding using hamming distances
tsne=TSNE(n_components=2, perplexity=25, metric="hamming",square_distances=True, random_state=42).fit_transform(dataDF)
axs[0,0].scatter(embedding[:,0],embedding[:,1], s=5, c=["blue" if i<100 else "red" for i in range(0,dataDF.shape[0])])
axs[1,0].scatter(tsne[:,0],tsne[:,1], s=5, c=["blue" if i<100 else "red" for i in range(0,dataDF.shape[0])])
axs[0,0].set_title("Base case")
axs[1,0].set_title("t-SNE on same data")
### Shift base data by 10, embed and plot
dataDF=dataDF+10#.replace(1,100, inplace=True) #replace 1 with 100, the latter does not occur in matrix so should not impact hamming distances.
embedding=umap.UMAP(n_neighbors=50, min_dist=0.01, unique=True, metric="hamming", random_state=42).fit_transform(dataDF) #second embedding using hamming distances
tsne=TSNE(n_components=2, perplexity=25, metric="hamming",square_distances=True, random_state=42).fit_transform(dataDF)
axs[0,1].scatter(embedding[:,0],embedding[:,1], s=5, c=["blue" if i<100 else "red" for i in range(0,dataDF.shape[0])])
axs[1,1].scatter(tsne[:,0],tsne[:,1], s=5, c=["blue" if i<100 else "red" for i in range(0,dataDF.shape[0])])
axs[0,1].set_title("All numbers increased by 10\nembedding unchanged")
axs[1,1].set_title("t-SNE on same data")
### Replace all instaces of 10 in shifted data with 100, embed and plot
dataDF.replace(10,100, inplace=True) #replace 1 with 100, the latter does not occur in matrix so should not impact hamming distances.
embedding=umap.UMAP(n_neighbors=50, min_dist=0.01, unique=True, metric="hamming", random_state=42).fit_transform(dataDF) #second embedding using hamming distances
tsne=TSNE(n_components=2, perplexity=25, metric="hamming",square_distances=True, random_state=42).fit_transform(dataDF)
axs[0,2].scatter(embedding[:,0],embedding[:,1], s=5, c=["blue" if i<100 else "red" for i in range(0,dataDF.shape[0])])
axs[1,2].scatter(tsne[:,0],tsne[:,1], s=5, c=["blue" if i<100 else "red" for i in range(0,dataDF.shape[0])])
axs[0,2].set_title("Numbers '10' replaced with '100'\nembedding changes")
axs[1,2].set_title("t-SNE on same data")
### Both umap runs are supposed to give identical figure because random_state is the same and hamming distance should be invariant to replacement of one integer
### with another provided second integer does not occur in original matrix. In fact, sklearn distances (if calculated using sklearn.metrics.pairwise_distances)
### do not change. Yet UMAP results are similar, but different
### In t-SNE on the same data the result remains unchanged in all cases.
plt.show()
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the provided Python example and comparing the Hamming distances with the three umap.UMAP(...).fit_transform results; the report says sklearn pairwise distances remain unchanged while UMAP changes. Trace the metric handling and neighbor construction used by UMAP, then add a regression check showing equivalent categorical encodings produce the same embedding with the fixed random state.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- machine-learning
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100