Skip to content

Commit 64133a0

Browse files
committed
Added kmeans manual.
1 parent 32871b6 commit 64133a0

2 files changed

Lines changed: 100 additions & 3 deletions

File tree

docs/15-kmeans-project.md

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,108 @@ plt.show()
134134

135135
# Exercise (k-means)
136136

137-
1. Change K to 6 and re-run the clustering. How do the patterns change?
138-
2. Change `random_state` and re-run. Do you get the same clusters?
139-
3. Why do you think k-means can give slightly different results with different random starts?
137+
Here we try to replicate what scipy does internally using simple Python.
140138

139+
1. Choose K initial centroids (import random)
140+
2. Assign each gene to the closest centroid
141+
3. Update centroids as the mean of assigned genes
142+
4. Repeat until stable
143+
144+
The most important calculation will be the eucledian distance.
145+
Implement your own ``dist( vec1, vec2)`` function.
146+
147+
??? You can compare yours to this after you are finished
148+
149+
```python
150+
def dist(vec1, vec2):
151+
"""
152+
Calculates a single gene <-> gene or gene <-> centroid eucledian distance
153+
"""
154+
diff = vec1 - vec2
155+
return np.sum(diff**2) ** 0.5
156+
```
157+
158+
We need to repeatetly get the eucledian distances of one centroid against all genes.
159+
Implement a ``dist_to_centroid (centroid, mat )`` function.
160+
161+
??? Again only peak after you finished yours
162+
```python
163+
def dist_to_centroid (centroid, mat ):
164+
"""
165+
Calculates eucledian distance between one centroid and a matrix of genes
166+
""""
167+
dists = []
168+
for i in range(mat.shape[0]):
169+
dists.append( dist( centroid, mat[i] ) )
170+
return dists
171+
```
172+
173+
And finally we need to get randomness into our scripts.
174+
For this you normall use a random number generator and as we are already using numpy we should probably take the one from there:
175+
176+
```python
177+
rng = np.random.default_rng(seed)
178+
```
179+
180+
With this module we can then e.g. identify a random set of k ids from a range of n ids:
181+
182+
```python
183+
idx = rng.choice(np.arange(1,11), size=k, replace=False)
184+
```
185+
186+
And the loop should contain these steps:
187+
1. identify k random genes and use them as initial centromers
188+
2. compare each centromer to each gene and find for each gene the closest centromer
189+
3. recalculate the new centromers as the mean of the closest genes
190+
4. if the new centroids look like the old ones - break the loop
191+
5. use the new centroids and restart the loop
141192
---
142193

194+
??? Really - do not peak - use this opportunity to dig into this problem!
195+
```python
196+
def kmeans( data, k, maxiter, seed):
197+
"""
198+
Clusters rows using the kmeans algorithm
199+
"""
200+
data_np = np.asarray(data)
201+
## define a reproducible 'random' state and get the initial centroids as random genes
202+
rng = np.random.default_rng(seed)
203+
idx = rng.choice(np.arange(len(data_np)), size=k, replace=False)
204+
centroids = data_np[idx]
205+
206+
## the main loop
207+
n = len(data_np)
208+
for it in range(maxiter):
209+
# 1) Distance table D: rows=genes, cols=centroids
210+
D = np.zeros((n, k), dtype=float)
211+
for c in range(k):
212+
dists = dist_to_centroid(centroids[c], data_np)
213+
for i in range(n):
214+
D[i, c] = dists[i]
215+
216+
# 2) Assign each gene to nearest centroid
217+
labels = np.zeros(n, dtype=int)
218+
for i in range(n):
219+
labels[i] = int(np.argmin(D[i, :]))
220+
221+
# 3) Update centroids (mean of assigned points)
222+
new_centroids = centroids.copy()
223+
for c in range(k):
224+
members = data_np[labels == c]
225+
if len(members) > 0: # avoid empty cluster crash
226+
new_centroids[c] = np.mean(members, axis=0)
227+
else:
228+
# re-seed empty centroid to a random point (simple, explicit)
229+
new_centroids[c] = data_np[rng.integers(0, n)]
230+
231+
# 4) Stop if centroids no longer move
232+
if np.allclose(new_centroids, centroids):
233+
break
234+
centroids = new_centroids
235+
236+
return labels, centroids, D
237+
```
238+
143239
# The Final Project: Simulated annealing clustering
144240

145241
Your final project will combine everything you learned to implement a clustering algorithm

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ nav:
1818
- Linear models: 14-linear-models.md
1919
- K-means + project: 15-kmeans-project.md
2020
markdown_extensions:
21+
- pymdownx.details
2122
- pymdownx.superfences
2223
- pymdownx.arithmatex:
2324
generic: true

0 commit comments

Comments
 (0)