I would like to be able to use nearest neighbors to attempt to find the most similar samples to a subclass of samples (think treated vs untreated) in a dataset with continuous, categorical, and text features.
Toy data set:
import numpy as np
import pandas as pd
from sklearn.preprocessing import OneHotEncoder, QuantileTransformer
from sklearn.neighbors import NearestNeighbors
from sklearn.compose import ColumnTransformer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.pipeline import Pipeline
from sklearn.decomposition import TruncatedSVD
np.set_printoptions(suppress=True)
a = [20, 100, 10000, 500]
b = [1, 2, 3, 2]
c = ['dog', 'cat', 'foo', 'cat']
d = ['apple apple fruit',
'mercedes bmw chevrolet',
'monster dragon snake',
'mercedes chevrolet bmw buick']
z = [a,b,c,d]
names = {0: 'col1', 1:'col2', 2:'col3', 3:'col4'}
X = pd.DataFrame(z).T
X = X.rename(names, axis='columns')
X
Will create:
col1 col2 col3 col4
0 20 1 dog apple apple fruit
1 100 2 cat mercedes bmw chevrolet
2 10000 3 foo monster dragon snake
3 500 2 cat mercedes chevrolet bmw buick
As we can see, samples 1 and 3 are the most related. They have many of the same vocabulary, share two labels (col2 + col3) and considering the distribution of col1 they are fairly close together. We transform them into a feature array and ask for nearest neighbors like so:
numeric = ['col1']
numeric_transformer = Pipeline(steps=[('scaler', QuantileTransformer())])
cat = ['col2', 'col3']
cat_transformer = Pipeline(steps=[('onehot', OneHotEncoder())])
text = ['col4']
text_transformer = Pipeline(steps=[('tfidf', TfidfVectorizer()),
('svd', TruncatedSVD(n_components=2))])
prep = ColumnTransformer(transformers=[('num', numeric_transformer, numeric),
('cat', cat_transformer, cat),
('text', text_transformer, 'col4')], sparse_threshold=0)
X_transformed = prep.fit_transform(X)
nn = NearestNeighbors(n_neighbors=2)
nn.fit(X_transformed)
d, i = nn.kneighbors(X_transformed[1].reshape(1,-1))
i
Correctly returns array([[1, 3]], dtype=int64)
with the 1 indicating the self match and the 3 being the nearest neighbor.
But on a real world dataset, across 100+ dimensions, should I be using a custom distance function? for nearest neighbors with sklearn, if the column was one of the ones that was text, we could use cosine distance, another distance across the high dimensional one hot encoded variables, and another distance calculation with real continuous transformed variables (col1).
But is this....hacky? Is there some way to deal with the heterogenous data for nearest neighbors search without this? I worry that my decisions as to how to weight each of the three types of variables make the end results very subjective and open to criticism.