Clustering Jerárquico – Aglomerativo
Vamos a ver la técnica de Clustering Jerárquico Aglomerativo. Es un enfoque de abajo hacia arriba (bottom up). Este enfonque es más popular que el Clustering Divisivo. También vamos a utilizar el enlace completo como Criterio de Enlaces. También se podría usar Enlaces Promedio.
Primero, importemos las librerías necesarias:
[sourcecode language=»python» wraplines=»false» collapse=»false»]
import numpy as np
import pandas as pd
from scipy import ndimage
from scipy.cluster import hierarchy
from scipy.spatial import distance_matrix
from matplotlib import pyplot as plt
from sklearn import manifold, datasets
from sklearn.cluster import AgglomerativeClustering
from sklearn.datasets.samples_generator import make_blobs
%matplotlib inline
[/sourcecode]
Generando los Datos Aleatorios
Vamos a generar un conjunto de datos usando la clase make_blobs. Para ello usamos los siguientes parámetros:
- n_samples: Total de puntos divididos equitativamente entre los clusters. Elegimos un número entre 10-1500
- centers: El número de centros para generar, o las ubicaciones de centro fijas. Elegimos arrays de coordenadas x, y para generar los centros.
- cluster_std: El desvío estándar de los clusters. Mientras más grande el número, más separados estarán los clusters. Elegimos un número entre 0.5-1.5
Guarda el resultado en X1 y en y1.
[sourcecode language=»python» wraplines=»false» collapse=»false»]
X1, y1 = make_blobs(n_samples=50, centers=[[4,4], [-2, -1], [1, 1], [10,4]], cluster_std=0.9)
[/sourcecode]
Dibujo de la distribución de los puntos de los datos generados al azar
[sourcecode language=»python» wraplines=»false» collapse=»false»]
plt.scatter(X1[:, 0], X1[:, 1], marker=’o’)
[/sourcecode]
<matplotlib.collections.PathCollection at 0x7f9669b85dd8>
Clustering Aglomerativo
Comenzaremos haciendo el clustering de los datos aleatorios de los puntos que hemos creado.
La clase de Clustering Aglomerativo necesita dos entradas:
- n_clusters: El número de clusters a formar y el número de centroides a generar. Pondremos 4
- linkage: Criterio de Enlace a utilizar. El criterio de enlace determina la distancia a usar entre varias observaciones. El algoritmos agrupará en pares los clusters que minimizarán este criterio. El valor que vamos a utilizar es ‘complete’ (Se recomienda también intentar todo con ‘average’)
Guardamos el resultado en una variable llamada agglom
[sourcecode language=»python» wraplines=»false» collapse=»false»]
agglom = AgglomerativeClustering(n_clusters = 4, linkage = ‘average’)
[/sourcecode]
Ajustamos el modelo con X2 y y2 a partir de los datos generados anteriormente.
[sourcecode language=»python» wraplines=»false» collapse=»false»]
agglom.fit(X1,y1)
[/sourcecode]
AgglomerativeClustering(affinity='euclidean', compute_full_tree='auto', connectivity=None, linkage='average', memory=None, n_clusters=4, pooling_func='deprecated')
El siguiente código muestra el clustering.
[sourcecode language=»python» wraplines=»false» collapse=»false»]
# Crear una figura de 6 pulgadas (aprox 15cm) por 4 pulgadas (aprox 10 cm).
plt.figure(figsize=(6,4))
# Estas dos líneas de código se usan para reducir los puntos de datos,
# porque sino los puntos de datos se verían muy separados y dispersos.
# Crear un rango mínimo y máximo de X1.
x_min, x_max = np.min(X1, axis=0), np.max(X1, axis=0)
# Obtener la distancia promedio para X1.
X1 = (X1 – x_min) / (x_max – x_min)
# Este loop muestra todos los puntos de datos.
for i in range(X1.shape[0]):
# Reemplaza los puntos de datos con su valor de cluster respectivo
# (ej. 0) está codificado con un mapa de colores (plt.cm.spectral)
plt.text(X1[i, 0], X1[i, 1], str(y1[i]),
color=plt.cm.nipy_spectral(agglom.labels_[i] / 10.),
fontdict={‘weight’: ‘bold’, ‘size’: 9})
# Elimina los ticks x, ticks y, y los ejes x e y
plt.xticks([])
plt.yticks([])
#plt.axis(‘off’)
# Muestra el punteado de los datos originales ántes de clustering
plt.scatter(X1[:, 0], X1[:, 1], marker=’.’)
# Muestra el punteo
plt.show()
[/sourcecode]
Dendograma Asociado al Clustering Aglomerativo Jerárquico
Recuerda que una matriz de distancia contiene la distancia de cada punto hacia cualquier otro punto del set de datos. Usamos la función distance_matrix, la cual necesita dos entradas. Usamos la Matriz de Confusión, X2 como ambas entradas para guardar la matriz de distancia en una variable de nombre dist_matrix. Recuerda que los valores de distancia son simétricos, con una diagonal de ceros. Esta es una forma de asegurarse que tu matriz es correcta. Imprimimos dist_matrix para asegurarnos que es correcta:
[sourcecode language=»python» wraplines=»false» collapse=»false»]
dist_matrix = distance_matrix(X1,X1)
print(dist_matrix)
[/sourcecode]
[[0. 0.768101 0.99584945 ... 0.3722758 1.09321893 0.97574908] [0.768101 0. 0.243825 ... 0.63657093 0.34126614 0.24149435] [0.99584945 0.243825 0. ... 0.87975678 0.09840829 0.04685827] ... [0.3722758 0.63657093 0.87975678 ... 0. 0.97587667 0.87704455] [1.09321893 0.34126614 0.09840829 ... 0.97587667 0. 0.1212985 ] [0.97574908 0.24149435 0.04685827 ... 0.87704455 0.1212985 0. ]]
Usando la clase de enlace de la jerarquía, pasamos los parámetros:
- La matriz de distancia
- ‘complete’ se refiere al enlace completo
Guardamos el resultado en una variable de nombre Z
[sourcecode language=»python» wraplines=»false» collapse=»false»]
Z = hierarchy.linkage(dist_matrix, ‘complete’)
[/sourcecode]
Un clustering jerárquico se visualiza como un dendograma. Cada agrupamiento se representa por una linea horizontal. La coordenada y de la linea horizontal se refiere a la similitud entre dos clusters que se agruparon, donde las observaciones se visualizan como clusters individuales. Moviéndose para arriba desde la capa inferior hacia el nodo superior, un dendograma nos permite reconstruir la historia de agrupamientos que resultaron en el clustering representado.
Guardamos el dendograma en una variable llamada dendro. Al hacer esto, el dendograma también se dibujará. Utilizando la clase dendrograma de la jerarquía, se pasa en el parámetro Z
[sourcecode language=»python» wraplines=»false» collapse=»false»]
Z = hierarchy.linkage(dist_matrix, ‘complete’)
[/sourcecode]
Si usamos el enlace promedio podemos ver cómo el dendograma cambia.
[sourcecode language=»python» wraplines=»false» collapse=»false»]
Z = hierarchy.linkage(dist_matrix, ‘average’)
dendro = hierarchy.dendrogram(Z)
[/sourcecode]
Ejmplo con datos de Vehículos
Imaginemos que una fábrica de vehículos desarrolla prototipos para un nuevo vehículo. Antes de presentar el nuevo modelo, el fabracante quiere saber que vehículos existen en el mercado similares al prototipo, es decir, cómo se pueden agrupar los vehículos, qué grupo es el más parecido al del modelo y de esta forma, qué modelos competirán con el nuevo.
Nuestro objetivo es utilizar métodos de clustering para encontrar los clusters más diferentes de vehículos. Se resumirán los vehículos actuales y ayudará al proceso de fabricación para tomar mejores decisiones para hacer modelos más simples.
Descargar los datos
Para descargar los datos, usaremos !wget
[sourcecode language=»python» wraplines=»false» collapse=»false»]
!wget -O cars_clus.csv https://s3-api.us-geo.objectstorage.softlayer.net/cf-courses-data/CognitiveClass/ML0101ENv3/labs/cars_clus.csv
[/sourcecode]
--2020-02-21 17:21:39-- https://s3-api.us-geo.objectstorage.softlayer.net/cf-courses-data/CognitiveClass/ML0101ENv3/labs/cars_clus.csv Resolving s3-api.us-geo.objectstorage.softlayer.net (s3-api.us-geo.objectstorage.softlayer.net)... 67.228.254.196 Connecting to s3-api.us-geo.objectstorage.softlayer.net (s3-api.us-geo.objectstorage.softlayer.net)|67.228.254.196|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 17774 (17K)
[text language=»/csv»][/text]
Saving to: ‘cars_clus.csv’
cars_clus.csv 100%[===================>] 17.36K –.-KB/s in 0.02s
2020-02-21 17:21:40 (833 KB/s) – ‘cars_clus.csv’ saved [17774/17774]
Veamos las características de los fabricantes que se han secopilado sobre los modelos existentes.
[sourcecode language=»python» wraplines=»false» collapse=»false»]
filename = ‘cars_clus.csv’
#Read csv
pdf = pd.read_csv(filename)
print ("Forma del set de datos: ", pdf.shape)
pdf.head(5)
[/sourcecode]
Forma del set de datos: (159, 16)
manufact | model | sales | resale | type | price | engine_s | horsepow | wheelbas | width | length | curb_wgt | fuel_cap | mpg | lnsales | partition | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Acura | Integra | 16.919 | 16.360 | 0.000 | 21.500 | 1.800 | 140.000 | 101.200 | 67.300 | 172.400 | 2.639 | 13.200 | 28.000 | 2.828 | 0.0 |
1 | Acura | TL | 39.384 | 19.875 | 0.000 | 28.400 | 3.200 | 225.000 | 108.100 | 70.300 | 192.900 | 3.517 | 17.200 | 25.000 | 3.673 | 0.0 |
2 | Acura | CL | 14.114 | 18.225 | 0.000 |
null |
3.200 | 225.000 | 106.900 | 70.600 | 192.000 | 3.470 | 17.200 | 26.000 | 2.647 | 0.0 |
3 | Acura | RL | 8.588 | 29.725 | 0.000 | 42.000 | 3.500 | 210.000 | 114.600 | 71.400 | 196.600 | 3.850 | 18.000 | 22.000 | 2.150 | 0.0 |
4 | Audi | A4 | 20.397 | 22.255 | 0.000 | 23.990 | 1.800 | 150.000 | 102.600 | 68.200 | 178.000 | 2.998 | 16.400 | 27.000 | 3.015 | 0.0 |
Los conjuntos de datos incluyen el precio en miles (price), tamaño de motor (engine_s), caballos de fuerza (horsepow), distancia entre ejes (wheelbas), ancho (width), largo (length), peso en vació (curb_wgt), capacidad de combustible (fuel_cap) y eficiencia de combustible (mpg).
Limpieza de Datos
Limpiemos el set de datos eliminando filas que tienen valores nulos:
[sourcecode language=»python» wraplines=»false» collapse=»false»]
print ("Shape of dataset before cleaning: ", pdf.size)
pdf[[ ‘sales’, ‘resale’, ‘type’, ‘price’, ‘engine_s’,
‘horsepow’, ‘wheelbas’, ‘width’, ‘length’, ‘curb_wgt’, ‘fuel_cap’,
‘mpg’, ‘lnsales’]] = pdf[[‘sales’, ‘resale’, ‘type’, ‘price’, ‘engine_s’,
‘horsepow’, ‘wheelbas’, ‘width’, ‘length’, ‘curb_wgt’, ‘fuel_cap’,
‘mpg’, ‘lnsales’]].apply(pd.to_numeric, errors=’coerce’)
pdf = pdf.dropna()
pdf = pdf.reset_index(drop=True)
print ("Forma del dataset luego de la limpieza: ", pdf.size)
pdf.head(5)
[/sourcecode]
Shape of dataset before cleaning: 2544 Forma del dataset luego de la limpieza: 1872
manufact | model | sales | resale | type | price | engine_s | horsepow | wheelbas | width | length | curb_wgt | fuel_cap | mpg | lnsales | partition | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Acura | Integra | 16.919 | 16.360 | 0.0 | 21.50 | 1.8 | 140.0 | 101.2 | 67.3 | 172.4 | 2.639 | 13.2 | 28.0 | 2.828 | 0.0 |
1 | Acura | TL | 39.384 | 19.875 | 0.0 | 28.40 | 3.2 | 225.0 | 108.1 | 70.3 | 192.9 | 3.517 | 17.2 | 25.0 | 3.673 | 0.0 |
2 | Acura | RL | 8.588 | 29.725 | 0.0 | 42.00 | 3.5 | 210.0 | 114.6 | 71.4 | 196.6 | 3.850 | 18.0 | 22.0 | 2.150 | 0.0 |
3 | Audi | A4 | 20.397 | 22.255 | 0.0 | 23.99 | 1.8 | 150.0 | 102.6 | 68.2 | 178.0 | 2.998 | 16.4 | 27.0 | 3.015 | 0.0 |
4 | Audi | A6 | 18.780 | 23.555 | 0.0 | 33.95 | 2.8 | 200.0 | 108.7 | 76.1 | 192.0 | 3.561 | 18.5 | 22.0 | 2.933 | 0.0 |
Selección de Característica
Elijamos nuestro set en cuestión:
[sourcecode language=»python» wraplines=»false» collapse=»false»]
featureset = pdf[[‘engine_s’, ‘horsepow’, ‘wheelbas’, ‘width’, ‘length’, ‘curb_wgt’, ‘fuel_cap’, ‘mpg’]]
[/sourcecode]
Normalización
Ahora podemos normalizar el set. MinMaxScaler transforma poniendo en escala a un rango. Por omisión es (0, 1). Es decir, las escalas dee estimación se traducen cada una individualmente de forma tal de quedar entre cero y uno.
[sourcecode language=»python» wraplines=»false» collapse=»false»]
from sklearn.preprocessing import MinMaxScaler
x = featureset.values #returns a numpy array
min_max_scaler = MinMaxScaler()
feature_mtx = min_max_scaler.fit_transform(x)
feature_mtx [0:5]
[/sourcecode]
array([[0.11428571, 0.21518987, 0.18655098, 0.28143713, 0.30625832, 0.2310559 , 0.13364055, 0.43333333], [0.31428571, 0.43037975, 0.3362256 , 0.46107784, 0.5792277 , 0.50372671, 0.31797235, 0.33333333], [0.35714286, 0.39240506, 0.47722343, 0.52694611, 0.62849534, 0.60714286, 0.35483871, 0.23333333], [0.11428571, 0.24050633, 0.21691974, 0.33532934, 0.38082557, 0.34254658, 0.28110599, 0.4 ], [0.25714286, 0.36708861, 0.34924078, 0.80838323, 0.56724368, 0.5173913 , 0.37788018, 0.23333333]])
Agrupando utilizando Scipy
En esta parte, usaremos el paquete Scipy para agrupar el set de datos. Primero, calcularemos la matriz de distancia.
[sourcecode language=»python» wraplines=»false» collapse=»false»]
import scipy
leng = feature_mtx.shape[0]
D = scipy.zeros([leng,leng])
for i in range(leng):
for j in range(leng):
D[i,j] = scipy.spatial.distance.euclidean(feature_mtx[i], feature_mtx[j])
[/sourcecode]
En el clustering aglomerativo, en cada iteración, el algoritmo debe actualizar la matriz para refrejar la distancia del nuevo cluster formado a partir de los clusters restantes. Los métodos siguientes los soporta Scipy para calcular la distancia entre los recientementes formados clusters y para cada: – simple – completo – promedio – ponderado – centroide
Usaremos completo para nuestro caso, pero si deseas puedes cambiar para ver los diferentes resultados.
[sourcecode language=»python» wraplines=»false» collapse=»false»]
import pylab
import scipy.cluster.hierarchy
Z = hierarchy.linkage(D, ‘complete’)
[/sourcecode]
Esencencialmente, el clustering jerárquico no necesita de un número específico de clusters. Sin embargo, algunas aplicaciones que queremos una partición de clusters disjuntos como si fueran clustering tradicional. Por lo tanto podrás usar una linea en el medio:
[sourcecode language=»python» wraplines=»false» collapse=»false»]
from scipy.cluster.hierarchy import fcluster
max_d = 3
clusters = fcluster(Z, max_d, criterion=’distance’)
clusters
[/sourcecode]
array([ 1, 5, 5, 6, 5, 4, 6, 5, 5, 5, 5, 5, 4, 4, 5, 1, 6, 5, 5, 5, 4, 2, 11, 6, 6, 5, 6, 5, 1, 6, 6, 10, 9, 8, 9, 3, 5, 1, 7, 6, 5, 3, 5, 3, 8, 7, 9, 2, 6, 6, 5, 4, 2, 1, 6, 5, 2, 7, 5, 5, 5, 4, 4, 3, 2, 6, 6, 5, 7, 4, 7, 6, 6, 5, 3, 5, 5, 6, 5, 4, 4, 1, 6, 5, 5, 5, 6, 4, 5, 4, 1, 6, 5, 6, 6, 5, 5, 5, 7, 7, 7, 2, 2, 1, 2, 6, 5, 1, 1, 1, 7, 8, 1, 1, 6, 1, 1], dtype=int32)
También, podrás determinar la cantidad de clusters directamente:
[sourcecode language=»python» wraplines=»false» collapse=»false»]
from scipy.cluster.hierarchy import fcluster
k = 5
clusters = fcluster(Z, k, criterion=’maxclust’)
clusters
[/sourcecode]
array([1, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3, 2, 2, 3, 1, 3, 3, 3, 3, 2, 1, 5, 3, 3, 3, 3, 3, 1, 3, 3, 4, 4, 4, 4, 2, 3, 1, 3, 3, 3, 2, 3, 2, 4, 3, 4, 1, 3, 3, 3, 2, 1, 1, 3, 3, 1, 3, 3, 3, 3, 2, 2, 2, 1, 3, 3, 3, 3, 2, 3, 3, 3, 3, 2, 3, 3, 3, 3, 2, 2, 1, 3, 3, 3, 3, 3, 2, 3, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 3, 3, 1, 1, 1, 3, 4, 1, 1, 3, 1, 1], dtype=int32)
Ahora, se trazará el dendograma:
[sourcecode language=»python» wraplines=»false» collapse=»false»]
fig = pylab.figure(figsize=(18,50))
def llf(id):
return ‘[%s %s %s]’ % (pdf[‘manufact’][id], pdf[‘model’][id], int(float(pdf[‘type’][id])) )
dendro = hierarchy.dendrogram(Z, leaf_label_func=llf, leaf_rotation=0, leaf_font_size =12, orientation = ‘right’)
[/sourcecode]
Clustering utilizando scikit-learn
Volvamos a construir el cluster, pero esta vez usando el paquete scikit-learn:
[sourcecode language=»python» wraplines=»false» collapse=»false»]
dist_matrix = distance_matrix(feature_mtx,feature_mtx)
print(dist_matrix)
[/sourcecode]
[[0. 0.57777143 0.75455727 ... 0.28530295 0.24917241 0.18879995] [0.57777143 0. 0.22798938 ... 0.36087756 0.66346677 0.62201282] [0.75455727 0.22798938 0. ... 0.51727787 0.81786095 0.77930119] ... [0.28530295 0.36087756 0.51727787 ... 0. 0.41797928 0.35720492] [0.24917241 0.66346677 0.81786095 ... 0.41797928 0. 0.15212198] [0.18879995 0.62201282 0.77930119 ... 0.35720492 0.15212198 0. ]]
Ahora, podemos usar la función ‘AgglomerativeClustering’ de la librería scikit-learn para agrupar el set de datos. Esta función hace un clustering jerárquico por medio de un enfoque de abajo hacia arriba (bottom up). El criterio de enlace determina la métrica utilizada para la estrategia de unificación:
- Ward minimiza la suma de las diferencias cuadráticas dentro de todos los clusters. Es una enfoque minimizado y en este sentido se parece al la función de objetivo de k-medias pero está encarado con un enfoque jerárquico aglomerativo.
- El Enlace máximo o completo, minimiza la distancia máxima entre las observaciones de pares de clusters.
- El Enlace promedio minimiza el promedio de las distancias entre todas las observaciones de pares de clusters.
[sourcecode language=»python» wraplines=»false» collapse=»false»]
agglom = AgglomerativeClustering(n_clusters = 6, linkage = ‘complete’)
agglom.fit(feature_mtx)
agglom.labels_
[/sourcecode]
array([1, 2, 2, 1, 2, 3, 1, 2, 2, 2, 2, 2, 3, 3, 2, 1, 1, 2, 2, 2, 5, 1, 4, 1, 1, 2, 1, 2, 1, 1, 1, 5, 0, 0, 0, 3, 2, 1, 2, 1, 2, 3, 2, 3, 0, 3, 0, 1, 1, 1, 2, 3, 1, 1, 1, 2, 1, 1, 2, 2, 2, 3, 3, 3, 1, 1, 1, 2, 1, 2, 2, 1, 1, 2, 3, 2, 3, 1, 2, 3, 5, 1, 1, 2, 3, 2, 1, 3, 2, 3, 1, 1, 2, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1])
Podemos agregar un nuevo campo a nuestro marco de datos para mostrar el cluster de cada fila:
[sourcecode language=»python» wraplines=»false» collapse=»false»]
pdf[‘cluster_’] = agglom.labels_
pdf.head()
[/sourcecode]
manufact | model | sales | resale | type | price | engine_s | horsepow | wheelbas | width | length | curb_wgt | fuel_cap | mpg | lnsales | partition | cluster_ | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Acura | Integra | 16.919 | 16.360 | 0.0 | 21.50 | 1.8 | 140.0 | 101.2 | 67.3 | 172.4 | 2.639 | 13.2 | 28.0 | 2.828 | 0.0 | 1 |
1 | Acura | TL | 39.384 | 19.875 | 0.0 | 28.40 | 3.2 | 225.0 | 108.1 | 70.3 | 192.9 | 3.517 | 17.2 | 25.0 | 3.673 | 0.0 | 2 |
2 | Acura | RL | 8.588 | 29.725 | 0.0 | 42.00 | 3.5 | 210.0 | 114.6 | 71.4 | 196.6 | 3.850 | 18.0 | 22.0 | 2.150 | 0.0 | 2 |
3 | Audi | A4 | 20.397 | 22.255 | 0.0 | 23.99 | 1.8 | 150.0 | 102.6 | 68.2 | 178.0 | 2.998 | 16.4 | 27.0 | 3.015 | 0.0 | 1 |
4 | Audi | A6 | 18.780 | 23.555 | 0.0 | 33.95 | 2.8 | 200.0 | 108.7 | 76.1 | 192.0 | 3.561 | 18.5 | 22.0 | 2.933 | 0.0 | 2 |
[sourcecode language=»python» wraplines=»false» collapse=»false»]
import matplotlib.cm as cm
n_clusters = max(agglom.labels_)+1
colors = cm.rainbow(np.linspace(0, 1, n_clusters))
cluster_labels = list(range(0, n_clusters))
# Crear una figura de 6 pulgadas (aprox 15cm) por 4 pulgadas (aprox 10cm).
plt.figure(figsize=(16,14))
for color, label in zip(colors, cluster_labels):
subset = pdf[pdf.cluster_ == label]
for i in subset.index:
plt.text(subset.horsepow[i], subset.mpg[i],str(subset[‘model’][i]), rotation=25)
plt.scatter(subset.horsepow, subset.mpg, s= subset.price*10, c=color, label=’cluster’+str(label),alpha=0.5)
# plt.scatter(subset.horsepow, subset.mpg)
plt.legend()
plt.title(‘Clusters’)
plt.xlabel(‘horsepow’)
plt.ylabel(‘mpg’)
[/sourcecode]
Text(0, 0.5, 'mpg')
Como se puede ver, es una distribución de cada cluster utilizando una trama esparcida, pero no está muy claro dónde es el centroide de cada cluster. Es más, hay 2 tipos de vehículos en nuestro set de datos, «truck» (valor de 1 en la columna tipo) y «car» (valor 1 en la columna tipo). Asi que los usaremos para distinguir las clases y sumarizar el cluster. Primero contamos la cantidad de casos de cada grupo:
[sourcecode language=»python» wraplines=»false» collapse=»false»]
pdf.groupby([‘cluster_’,’type’])[‘cluster_’].count()
[/sourcecode]
cluster_ type 0 1.0 6 1 0.0 47 1.0 5 2 0.0 27 1.0 11 3 0.0 10 1.0 7 4 0.0 1 5 0.0 3 Name: cluster_, dtype: int64
Ahora, podemos examinar a las características de cada cluster:
[sourcecode language=»python» wraplines=»false» collapse=»false»]
agg_cars = pdf.groupby([‘cluster_’,’type’])[‘horsepow’,’engine_s’,’mpg’,’price’].mean()
agg_cars
[/sourcecode]
horsepow | engine_s | mpg | price | ||
---|---|---|---|---|---|
cluster_ | type | ||||
0 | 1.0 | 211.666667 | 4.483333 | 16.166667 | 29.024667 |
1 | 0.0 | 146.531915 | 2.246809 | 27.021277 | 20.306128 |
1.0 | 145.000000 | 2.580000 | 22.200000 | 17.009200 | |
2 | 0.0 | 203.111111 | 3.303704 | 24.214815 | 27.750593 |
1.0 | 182.090909 | 3.345455 | 20.181818 | 26.265364 | |
3 | 0.0 | 256.500000 | 4.410000 | 21.500000 | 42.870400 |
1.0 | 160.571429 | 3.071429 | 21.428571 | 21.527714 | |
4 | 0.0 | 55.000000 | 1.000000 | 45.000000 | 9.235000 |
5 | 0.0 | 365.666667 | 6.233333 | 19.333333 | 66.010000 |
Es obvio que tenemos 3 clusters principales donde están la mayoría de los vehículos en ellos.
- Coches:
- Cluster 1: con algo mpg, y poco caballos de fuerza.
- Cluster 2: con buenos mpg y caballos de fuerza, pero un precio más alto que el promedio.
- Cluster 3: con bajo mpg, muchos caballos de fuerza y el precio más alto de todos.
- Camiones:
- Cluster 1: con el más alto mpg entre los camiones, y lo más bajo en caballos de fuerza y precio.
- Cluster 2: con bajo mpg y media de caballos de fuerza, pero el precio más alto que el promedio.
- Cluster 3: con bueno mpg y caballos de fuerza, bajo precio.
Notar que no utilizamos type, y price de autos en el proceso de clustering, sino que utilizamos clustering Jerárquico para discriminar los clusters con una precisión bastante alta.
[sourcecode language=»python» wraplines=»false» collapse=»false»]
plt.figure(figsize=(16,10))
for color, label in zip(colors, cluster_labels):
subset = agg_cars.loc[(label,),]
for i in subset.index:
plt.text(subset.loc[i][0]+5, subset.loc[i][2], ‘type=’+str(int(i)) + ‘, price=’+str(int(subset.loc[i][3]))+’k’)
plt.scatter(subset.horsepow, subset.mpg, s=subset.price*20, c=color, label=’cluster’+str(label))
plt.legend()
plt.title(‘Clusters’)
plt.xlabel(‘horsepow’)
plt.ylabel(‘mpg’)
[/sourcecode]
Text(0, 0.5, 'mpg')