Sitelet https://github.com/cloudkj/lambda-ml/commit/353f7e62674fc6cb37995f98662388073b2bb330
Skip to content

Commit 353f7e6

Browse files
committed
Initial implementation of agglomerative clustering
1 parent 044e67e commit 353f7e6

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

‎src/lambda_ml/clustering/hierarchical.clj‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,45 @@
2121
(pmap/priority-map-keyfn :distance))
2222
(assoc queues i)))
2323
{})))
24+
25+
(defn single-link
26+
[distances x y z]
27+
(min (get-in distances [x y :distance])
28+
(get-in distances [x z :distance])))
29+
30+
(defn agglomerative-clustering
31+
[f points]
32+
(loop [distances (pairwise-distances f points)
33+
queues (distance-queues distances)
34+
active (reduce #(assoc %1 %2 true) {} (range (count points)))
35+
merges []]
36+
(if (<= (count active) 1)
37+
merges
38+
(let [;; Find the two most similar clusters
39+
[_ k1 k2] (->> (keys active)
40+
(reduce (fn [[min-dist k1 k2] i]
41+
(let [[k {dist :distance}] (peek (get queues i))]
42+
(if (< dist min-dist)
43+
[dist i k]
44+
[min-dist k1 k2])))
45+
[Double/MAX_VALUE nil nil]))
46+
;; Clear queue for k1
47+
queues (assoc queues k1 (pmap/priority-map-keyfn :distance))
48+
;; Update distances
49+
[distances queues] (->> (keys active)
50+
(filter #(and (not (= k1 %)) (not (= k2 %))))
51+
(reduce (fn [[d q] i]
52+
;; TODO: specify link function
53+
(let [dist (single-link distances i k1 k2)
54+
d (-> (assoc-in d [i k1 :distance] dist)
55+
(assoc-in [k1 i :distance] dist))
56+
q (-> (update q i #(dissoc % k1))
57+
(update i #(dissoc % k2))
58+
(update i #(assoc % k1 (get-in d [i k1])))
59+
(update k1 #(assoc % i (get-in d [k1 i]))))]
60+
[d q]))
61+
[distances queues]))]
62+
(recur distances
63+
queues
64+
(dissoc active k2) ;; Deactivate cluster k2
65+
(conj merges [k1 k2])))))) ;; Merge k1 and k2

‎test/lambda_ml/clustering/hierarchical_test.clj‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,49 @@
8181
(is (= 0 (first (second (get queues 1)))))
8282
(is (= 0 (first (first (get queues 2)))))
8383
(is (= 1 (first (second (get queues 2)))))))
84+
85+
(deftest test-agglomerative-clustering
86+
(let [distances {"BA" {"BA" 0 "FI" 662 "MI" 877 "NA" 255 "RM" 412 "TO" 996}
87+
"FI" {"BA" 662 "FI" 0 "MI" 295 "NA" 468 "RM" 268 "TO" 400}
88+
"MI" {"BA" 877 "FI" 295 "MI" 0 "NA" 754 "RM" 564 "TO" 138}
89+
"NA" {"BA" 255 "FI" 468 "MI" 754 "NA" 0 "RM" 219 "TO" 869}
90+
"RM" {"BA" 412 "FI" 268 "MI" 564 "NA" 219 "RM" 0 "TO" 669}
91+
"TO" {"BA" 996 "FI" 400 "MI" 138 "NA" 869 "RM" 669 "TO" 0}}
92+
f (fn [a b] (get-in distances [a b]))
93+
merges (agglomerative-clustering f (keys distances))]
94+
(is (= [2 5] (nth merges 0)))
95+
(is (= [3 4] (nth merges 1)))
96+
(is (= [0 3] (nth merges 2)))
97+
(is (= [0 1] (nth merges 3)))
98+
(is (= [0 2] (nth merges 4)))))
99+
100+
(deftest test-agglomerative-clustering2
101+
(let [points [[1 1 1 0 1 0 0 1 1 1]
102+
[1 1 0 1 1 0 0 0 0 1]
103+
[0 1 1 0 1 0 0 1 0 0]
104+
[0 0 0 1 0 1 0 0 0 0]
105+
[1 1 1 0 1 0 1 1 1 0]
106+
[0 1 0 1 1 0 0 0 0 1]
107+
[0 1 1 0 1 1 0 1 1 0]]
108+
merges (agglomerative-clustering d/jaccard points)]
109+
(is (= [1 5] (nth merges 0)))
110+
(is (= [0 4] (nth merges 1)))
111+
(is (= [2 6] (nth merges 2)))
112+
(is (= [0 2] (nth merges 3)))
113+
(is (= [0 1] (nth merges 4)))
114+
(is (= [0 3] (nth merges 5)))))
115+
116+
(deftest test-agglomerative-clustering3
117+
(let [distances {"A" {"A" 0.00 "B" 0.71 "C" 5.66 "D" 3.61 "E" 4.24 "F" 3.20}
118+
"B" {"A" 0.71 "B" 0.00 "C" 4.95 "D" 2.92 "E" 3.54 "F" 2.50}
119+
"C" {"A" 5.66 "B" 4.95 "C" 0.00 "D" 2.24 "E" 1.41 "F" 2.50}
120+
"D" {"A" 3.61 "B" 2.92 "C" 2.24 "D" 0.00 "E" 1.00 "F" 0.50}
121+
"E" {"A" 4.24 "B" 3.54 "C" 1.41 "D" 1.00 "E" 0.00 "F" 1.12}
122+
"F" {"A" 3.20 "B" 2.50 "C" 2.50 "D" 0.50 "E" 1.12 "F" 0.00}}
123+
f (fn [a b] (get-in distances [a b]))
124+
merges (agglomerative-clustering f (keys distances))]
125+
(is (= [3 5] (nth merges 0)))
126+
(is (= [0 1] (nth merges 1)))
127+
(is (= [3 4] (nth merges 2)))
128+
(is (= [2 3] (nth merges 3)))
129+
(is (= [0 2] (nth merges 4)))))

0 commit comments

Comments
 (0)