# Normal Distribution and K-means clustering demonstration

I have two classes, the 1st one aims to display the Normal Distribution, and the 2nd one aims to perform K-means clustering.

The emphasis is to build classes of the mathematical methods as examples only, study material, demonstration. Visuals (plots) are very appreciated here. But it needs to be done neatly and very useful. How to improve the classes? Thanks.

The NormalDistribution can display multiple distributions at once.

The Kmeans2D can cluster the 2D points by the method .classify(n = ...) as many as n times. The implementation uses Point and Cluster class for readability.

Example implementation:

fig, ax = plt.subplots(1, 2)

model = NormalDistribution([0, 2, 3], [0.1, 0.2, 0.3])
model.plot(ax[0])

x = [random.uniform(0, 1) for i in range(100)]
y = [random.uniform(0, 1) for i in range(100)]
model2 = Kmeans2D(xp=x, yp=y, k=3)
model2.classify()
model2.plot(ax[1], colors = ['red', 'black', 'blue'], ms = 10)

fig.show()


Classes:

class NormalDistribution(object):

def __init__(self, mu, s, bound = [-7, 7], N = 1000, name = 'Normal Distribution'):
self.dx = (bound[1]-bound[0])/(N-1)
self.x = [bound[0] + i*self.dx for i in range(N)]
self.mu = mu
self.s = s
self.name = name

def pdf(self, x, mu, s):
factor = 1/sqrt(2*pi*(s**2))
y = [ factor*e**(-((i-mu)**2)/(2*(s**2))) for i in x ]
return y

def cdf(self, pdf):
y = [ self.dx*sum(pdf[0:i+1]) for i in range(len(pdf)) ]
return y

def plot(self, ax, func = 'pdf', color = 'blue', spectrum = None, legend = True):
if isinstance(color, str):
color = mplcolors.hex2color(mplcolors.cnames[color])
for mu, s in zip(self.mu, self.s):
if spectrum == 'mu':
col = (color[0]*mu/max(self.mu), color[1]*mu/max(self.mu), color[2]*mu/max(self.mu), 0.8)
elif spectrum == 's':
col = (color[0]*s/max(self.s), color[1]*s/max(self.s), color[2]*s/max(self.s), 0.8)
else:
col = color
if func=='pdf':
ax.plot(self.x, self.pdf(self.x, mu, s), '-', color = col, lw = 1.5)
elif func=='cdf':
ax.plot(self.x, self.cdf(self.pdf(self.x, mu, s)), '-', color = col, lw = 1.5)
if legend:
ax.legend(labels = [r'$\mu$ = {}, s = {}'.format(mu, s) for mu,s in zip(self.mu, self.s)])

class Kmeans2D(object):

class Point(object):
def __init__(self, x, y, size = 2):
self.x = x
self.y = y

class Cluster(object):
def __init__(self, xp= None, yp= None, color = ''):
self.x = xp
self.y = yp
self.points = [Kmeans2D.Point(x, y) for x,y in zip(self.x, self.y)]
def update(self, x, y):
self.x.append(x)
self.y.append(y)
self.points.append(Kmeans2D.Point(x,y))

def __init__(self, xp = None, yp = None, k = 2, init_centers = [], random_init = True):
self.points = [Kmeans2D.Point(ix, iy) for ix, iy in zip(xp, yp)]
self.np = len(xp)
self.k = k
if random_init:
self.init_centers = []
index = list(range(self.np))
for i in range(self.k):
rand = random.choice(index)
self.init_centers.append(self.points[rand])
index.remove(rand)
else:
self.init_centers = [Kmeans2D.Point(c[0], c[1]) for c in init_centers]

def distance(p1, p2):
return sqrt((p2[1]-p1[1])**2 + (p2[0]-p1[0])**2)

def classify_once(self, centers):
self.clusters = [Kmeans2D.Cluster(xp = [], yp = []) for i in range(self.k)]
dum_points = self.points
for i in dum_points:
distances = [Kmeans2D.distance((j.x, j.y), (i.x, i.y)) for j in centers]
shortest = min(distances)
self.clusters[distances.index(shortest)].update(i.x, i.y)
self.centers = [ Kmeans2D.Point( x = numpy.mean(i.x), y = numpy.mean(i.y) ) for i in self.clusters ]

def classify(self, n=10):
for i in range(n):
if 'centers' in dir(self):
self.classify_once(self.centers)
else:
self.classify_once(self.init_centers)

def plot(self, ax, colors = [], ms = 5):
for i in range(self.k):
ax.plot(self.clusters[i].x, self.clusters[i].y, 'o', \
color = colors[i], markerfacecolor = (0,0,0,0), \
markeredgecolor = colors[i], markersize = ms)