burnout-tracker / src /smote.py
Katherine
cleaned up code/print statements/comments
e703447
Raw
History Blame Contribute Delete
1.32 kB
# minimal SMOTE implementation (no imbalanced-learn dependency)
import numpy as np
def smote(X, y, k=5, random_state=42):
rng = np.random.default_rng(random_state)
classes, counts = np.unique(y, return_counts=True)
minority_class = classes[np.argmin(counts)]
majority_class = classes[np.argmax(counts)]
n_majority = counts[np.argmax(counts)]
n_minority = counts[np.argmin(counts)]
n_synthetic = n_majority - n_minority
X_min = X[y == minority_class]
# pairwise euclidean distances among minority samples
diffs = X_min[:, None, :] - X_min[None, :, :]
sq_dists = (diffs ** 2).sum(axis=2)
np.fill_diagonal(sq_dists, np.inf)
k = min(k, n_minority - 1)
nn_idx = np.argsort(sq_dists, axis=1)[:, :k]
synthetic = np.empty((n_synthetic, X.shape[1]))
for i in range(n_synthetic):
base = rng.integers(0, n_minority)
neighbour = nn_idx[base, rng.integers(0, k)]
lam = rng.uniform(0, 1)
synthetic[i] = X_min[base] + lam * (X_min[neighbour] - X_min[base])
X_res = np.vstack([X, synthetic])
y_res = np.concatenate([y, np.full(n_synthetic, minority_class)])
# shuffle so minority examples aren't all at the end
idx = rng.permutation(len(y_res))
return X_res[idx], y_res[idx]