Nearest Neighbors
kNN Classify by Majority Vote
Classify a query point by the majority class among its k=3 nearest
neighbors. One loop computes (squared_distance, index) pairs to avoid sqrt
(squaring preserves ranking); .sort() orders them; a second loop over the
first 3 tallies votes by looking up labels via index; max(votes, key=votes.get) picks the winner. Library:
sklearn.neighbors.KNeighborsClassifier(n_neighbors=3).fit(X, y) .predict([query]). RESULT: predicted class label.
By hand
X_train=[[1,1],[2,1],[4,1],[8,7],[9,8],[7,9]], y=['A','B','A','B','B','B']. Query=[2,2]. Squared distances: [2,1]→1(B), [1,1]→2(A), [4,1]→5(A), others≥61. Three nearest: B, A, A → vote A:2, B:1 → majority A.
X_train = [[1,1], [2,1], [4,1], [8,7], [9,8], [7,9]]
y_train = ['A', 'B', 'A', 'B', 'B', 'B']
query = [2, 2]
n_train = len(X_train)
dists = []
for i in range(n_train):
d0 = X_train[i][0] - query[0]
d1 = X_train[i][1] - query[1]
dists.append((d0*d0 + d1*d1, i))
dists.sort()
votes = {}
for i in range(3):
lbl = y_train[dists[i][1]]
votes[lbl] = votes.get(lbl, 0) + 1
winner = max(votes, key=votes.get)
print('RESULT:', repr(winner))
X_train ← [[1, 1], [2, 1], [4, 1], [8, 7], [9, 8], [7, 9]]
1X_train = [[1,1], [2,1], [4,1], [8,7], [9,8], [7,9]]2y_train = ['A', 'B', 'A', 'B', 'B', 'B']values this step[[1, 1], [2, 1], [4, 1], [8, 7], [9, 8], [7, 9]]X_trainy_train ← ['A', 'B', 'A', 'B', 'B', 'B']
1X_train = [[1,1], [2,1], [4,1], [8,7], [9,8], [7,9]]2y_train = ['A', 'B', 'A', 'B', 'B', 'B']3query = [2, 2]values this step['A', 'B', 'A', 'B', 'B', 'B']y_trainquery ← [2, 2]
2y_train = ['A', 'B', 'A', 'B', 'B', 'B']3query = [2, 2]4n_train = len(X_train)values this step[2, 2]queryn_train ← 6
3query = [2, 2]4n_train = len(X_train)5dists = []values this step6n_traindists ← []
4n_train = len(X_train)5dists = []6for i in range(n_train):values this step[]distsi ← 0, d0 ← -1, d1 ← -1, dists ← [(2, 0)]
pass 1 of 65dists = []6for i in range(n_train):7 d0 = X_train[i][0] - query[0]8 d1 = X_train[i][1] - query[1]9 dists.append((d0*d0 + d1*d1, i))10dists.sort()values this step0i-1d0-1d1[] → [(2, 0)]distsAll 6 passes — pass 1 is the card above pass id0d1dists1 0 -1 -1 [] → [(2, 0)] 2 0 → 1 -1 → 0 — [(2, 0)] → [(2, 0), (1, 1)] 3 1 → 2 0 → 2 — [(2, 0), (1, 1)] → [(2, 0), (1, 1), (5, 2)] 4 2 → 3 2 → 6 -1 → 5 [(2, 0), (1, 1), (5, 2)] → [(2, 0), (1, 1), (5, 2), (61, 3)] 5 3 → 4 6 → 7 5 → 6 [(2, 0), (1, 1), (5, 2), (61, 3)] → [(2, 0), (1, 1), (5, 2), (61, 3), (85, 4)] 6 4 → 5 7 → 5 6 → 7 [(2, 0), (1, 1), (5, 2), (61, 3), (85, 4)] → [(2, 0), (1, 1), (5, 2), (61, 3), (85, 4), (74, 5)] for i in range(n_train):
5dists = []6for i in range(n_train):7 d0 = X_train[i][0] - query[0]dists ← [(1, 1), (2, 0), (5, 2), (61, 3), (74, 5), (85, 4)]
9 dists.append((d0*d0 + d1*d1, i))10dists.sort()11votes = {}values this step[(2, 0), (1, 1), (5, 2), (61, 3), (85, 4), (74, 5)] → [(1, 1), (2, 0), (5, 2), (61, 3), (74, 5), (85, 4)]distsvotes ← {}
10dists.sort()11votes = {}12for i in range(3):values this step{}votesi ← 0, lbl ← 'B', votes ← {'B': 1}
pass 1 of 311votes = {}12for i in range(3):13 lbl = y_train[dists[i][1]]14 votes[lbl] = votes.get(lbl, 0) + 115winner = max(votes, key=votes.get)values this step5 → 0i'B'lbl{} → {'B': 1}votesAll 3 passes — pass 1 is the card above pass ilblvotes1 5 → 0 'B' {} → {'B': 1} 2 0 → 1 'B' → 'A' {'B': 1} → {'B': 1, 'A': 1} 3 1 → 2 — {'B': 1, 'A': 1} → {'B': 1, 'A': 2} for i in range(3):
11votes = {}12for i in range(3):13 lbl = y_train[dists[i][1]]winner ← 'A'
14 votes[lbl] = votes.get(lbl, 0) + 115winner = max(votes, key=votes.get)16print('RESULT:', repr(winner))values this step'A'winnerstdout ← RESULT: 'A'
15winner = max(votes, key=votes.get)16print('RESULT:', repr(winner))values this stepRESULT: 'A'stdout
With scikit-learn
KNeighborsClassifier(n_neighbors=3) uses Euclidean distance by default.
kneighbors([query]) exposes the 3 nearest distances and indices for the
snapshot. RESULT is the string predicted by .predict.
from sklearn.neighbors import KNeighborsClassifier
from dalib.display import set_display
set_display()
X_train = [[1,1], [2,1], [4,1], [8,7], [9,8], [7,9]]
y_train = ['A', 'B', 'A', 'B', 'B', 'B']
query = [2, 2]
clf = KNeighborsClassifier(n_neighbors=3)
clf.fit(X_train, y_train)
dists, indices = clf.kneighbors([query])
neighbors = [y_train[i] for i in indices[0]]
pred = str(clf.predict([query])[0])
print('k=3 neighbors:', neighbors)
print('distances:', [round(float(d), 4) for d in dists[0]])
print('RESULT:', repr(pred))
k=3 neighbors: ['B', 'A', 'A']
distances: [1.0, 1.4142, 2.2361]
RESULT: 'A'
Implementation notes
- Squared distances are used for ranking (avoids
math.sqrt): √a < √b ↔ a < b for non-negative a, b. The votes are over labels, not distances, so the actual distance value is not needed. - The 2-1 vote (A:2, B:1) shows majority overriding the single nearest neighbor (which is class B at sq=1). A pure 3-0 unanimous vote would not demonstrate majority voting.
- Training points and query are chosen so no two training points are equidistant from the query and the 3rd/4th boundary is unambiguous (sq=5 vs sq=61) — eliminating tie-breaking ambiguity between naive and sklearn.
- sklearn's KNeighborsClassifier auto-selects an efficient neighbor-search
structure (algorithm='auto'); it applies the same Euclidean metric,
so distances match
euclidean-distance(this chapter) exactly. - With k=3 and a unanimous vote (3-0), prediction is unambiguous. Ties (e.g. k=2 with one A and one B) require a tie-breaking rule — sklearn uses the class with the smaller index in classes_.
- Cross-reference:
euclidean-distance(this chapter) for the distance formula used to rank neighbors.