130 lines
4.4 KiB
Python
130 lines
4.4 KiB
Python
"""
|
|
Agglomerative clustering with different metrics
|
|
===============================================
|
|
|
|
Demonstrates the effect of different metrics on the hierarchical clustering.
|
|
|
|
The example is engineered to show the effect of the choice of different
|
|
metrics. It is applied to waveforms, which can be seen as
|
|
high-dimensional vector. Indeed, the difference between metrics is
|
|
usually more pronounced in high dimension (in particular for euclidean
|
|
and cityblock).
|
|
|
|
We generate data from three groups of waveforms. Two of the waveforms
|
|
(waveform 1 and waveform 2) are proportional one to the other. The cosine
|
|
distance is invariant to a scaling of the data, as a result, it cannot
|
|
distinguish these two waveforms. Thus even with no noise, clustering
|
|
using this distance will not separate out waveform 1 and 2.
|
|
|
|
We add observation noise to these waveforms. We generate very sparse
|
|
noise: only 6% of the time points contain noise. As a result, the
|
|
l1 norm of this noise (ie "cityblock" distance) is much smaller than it's
|
|
l2 norm ("euclidean" distance). This can be seen on the inter-class
|
|
distance matrices: the values on the diagonal, that characterize the
|
|
spread of the class, are much bigger for the Euclidean distance than for
|
|
the cityblock distance.
|
|
|
|
When we apply clustering to the data, we find that the clustering
|
|
reflects what was in the distance matrices. Indeed, for the Euclidean
|
|
distance, the classes are ill-separated because of the noise, and thus
|
|
the clustering does not separate the waveforms. For the cityblock
|
|
distance, the separation is good and the waveform classes are recovered.
|
|
Finally, the cosine distance does not separate at all waveform 1 and 2,
|
|
thus the clustering puts them in the same cluster.
|
|
"""
|
|
# Author: Gael Varoquaux
|
|
# License: BSD 3-Clause or CC-0
|
|
|
|
import matplotlib.pyplot as plt
|
|
import numpy as np
|
|
|
|
from sklearn.cluster import AgglomerativeClustering
|
|
from sklearn.metrics import pairwise_distances
|
|
|
|
np.random.seed(0)
|
|
|
|
# Generate waveform data
|
|
n_features = 2000
|
|
t = np.pi * np.linspace(0, 1, n_features)
|
|
|
|
|
|
def sqr(x):
|
|
return np.sign(np.cos(x))
|
|
|
|
X = list()
|
|
y = list()
|
|
for i, (phi, a) in enumerate([(.5, .15), (.5, .6), (.3, .2)]):
|
|
for _ in range(30):
|
|
phase_noise = .01 * np.random.normal()
|
|
amplitude_noise = .04 * np.random.normal()
|
|
additional_noise = 1 - 2 * np.random.rand(n_features)
|
|
# Make the noise sparse
|
|
additional_noise[np.abs(additional_noise) < .997] = 0
|
|
|
|
X.append(12 * ((a + amplitude_noise)
|
|
* (sqr(6 * (t + phi + phase_noise)))
|
|
+ additional_noise))
|
|
y.append(i)
|
|
|
|
X = np.array(X)
|
|
y = np.array(y)
|
|
|
|
n_clusters = 3
|
|
|
|
labels = ('Waveform 1', 'Waveform 2', 'Waveform 3')
|
|
|
|
# Plot the ground-truth labelling
|
|
plt.figure()
|
|
plt.axes([0, 0, 1, 1])
|
|
for l, c, n in zip(range(n_clusters), 'rgb',
|
|
labels):
|
|
lines = plt.plot(X[y == l].T, c=c, alpha=.5)
|
|
lines[0].set_label(n)
|
|
|
|
plt.legend(loc='best')
|
|
|
|
plt.axis('tight')
|
|
plt.axis('off')
|
|
plt.suptitle("Ground truth", size=20)
|
|
|
|
|
|
# Plot the distances
|
|
for index, metric in enumerate(["cosine", "euclidean", "cityblock"]):
|
|
avg_dist = np.zeros((n_clusters, n_clusters))
|
|
plt.figure(figsize=(5, 4.5))
|
|
for i in range(n_clusters):
|
|
for j in range(n_clusters):
|
|
avg_dist[i, j] = pairwise_distances(X[y == i], X[y == j],
|
|
metric=metric).mean()
|
|
avg_dist /= avg_dist.max()
|
|
for i in range(n_clusters):
|
|
for j in range(n_clusters):
|
|
plt.text(i, j, '%5.3f' % avg_dist[i, j],
|
|
verticalalignment='center',
|
|
horizontalalignment='center')
|
|
|
|
plt.imshow(avg_dist, interpolation='nearest', cmap=plt.cm.gnuplot2,
|
|
vmin=0)
|
|
plt.xticks(range(n_clusters), labels, rotation=45)
|
|
plt.yticks(range(n_clusters), labels)
|
|
plt.colorbar()
|
|
plt.suptitle("Interclass %s distances" % metric, size=18)
|
|
plt.tight_layout()
|
|
|
|
|
|
# Plot clustering results
|
|
for index, metric in enumerate(["cosine", "euclidean", "cityblock"]):
|
|
model = AgglomerativeClustering(n_clusters=n_clusters,
|
|
linkage="average", affinity=metric)
|
|
model.fit(X)
|
|
plt.figure()
|
|
plt.axes([0, 0, 1, 1])
|
|
for l, c in zip(np.arange(model.n_clusters), 'rgbk'):
|
|
plt.plot(X[model.labels_ == l].T, c=c, alpha=.5)
|
|
plt.axis('tight')
|
|
plt.axis('off')
|
|
plt.suptitle("AgglomerativeClustering(affinity=%s)" % metric, size=20)
|
|
|
|
|
|
plt.show()
|