Hoy voy a continuar trabajando con el set de datos que mencioné en el artículo anterior construido a partir de ratings obtenidos de la pagina:
https://datosmacro.expansion.com/
En primer lugar importo las librerías a utilizar:
import pandas as pd
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
A continuación, importo con Pickle el DataFrame que vamos a utilizar.
Pickle, para quienes no lo sepan, es un modulo que forma parte de la biblioteca estandar de Python (es decir, ya viene incorporado al instalar Python) y que permite exportar e importar en un archivo externo objetos de python como listas, diccionarios, instancias de clase, etc.
En este caso yo exporté por medio de Pickle un DataFrame de Pandas y ahora voy a utilizarlo para importarlo en el presente espacio de trabajo:
tabla=pd.read_pickle("tabla.pkl")
tabla.head()
| Países | Índice de Gini | Índice Felicidad | IDH | Déficit (%PIB) | Esperanza de vida | DoingBusiness | IPC Interanual | Salario Medio | Tasa de desempleo | PIB Per Capita | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | España [+] | 31.5 | 6.436 | 0.905 | -0.0364 | 83.08 | 77.94 | 0.032 | 29863.0 | 0.115 | 32530 |
| 1 | Alemania [+] | 29.3 | 6.892 | 0.942 | -0.0250 | 80.70 | 79.71 | 0.022 | 57958.0 | 0.032 | 52824 |
| 2 | Reino Unido [+] | 33.5 | 6.796 | 0.929 | -0.0551 | 80.70 | 83.55 | 0.032 | 52278.0 | 0.042 | 45943 |
| 3 | Francia [+] | 29.8 | 6.661 | 0.903 | -0.0550 | 82.30 | 76.80 | 0.023 | 43742.0 | 0.074 | 44460 |
| 4 | Italia [+] | 32.7 | 6.405 | 0.895 | -0.0740 | 82.80 | 72.85 | 0.013 | 35649.0 | 0.075 | 38221 |
Lo que voy a intentar hacer en esta ocasión es aplicar un algoritmo de clustering para agrupar en diferentes conjuntos los paises analizados.
Clustering es una técnica utilizada en aprendizaje automatico para agrupar automaticamente en subgrupos los datos en clusters (grupos) de datos que son parecidos entre sí, pero diferentes del resto.
En particular voy a estar usando el algoritmo más conocido de clustering llamado K-Means. La manera en que funciona este algoritmo es la siguiente: Imaginemos que cada uno de los datos de nuestro set de datos ocupa un punto especifico en un espacio que tiene tantas dimensiones como parametros numericos usemos para describir ese punto. Lo que queremos es agrupar esos puntos por la cercanía que tienen entre sí.
El algoritmo K-Means comienza generando en ese espacio un conjunto de puntos al azar, tantos puntos como grupos querramos identificar (El numero de grupos se identifica con la letra K). Estos puntos ubicados inicialmente al azar se llaman centroides. Una vez que se ubican los centroides se calcula la distancia entre cada dato y cada uno de los centroides y se asigna provisionalmente ese dato a un grupo identificado con el centroide mas cercano.
Una vez que todos los datos tienen su grupo, se calcula el promedio de las posiciones de los miembros de cada grupo, es decir, el punto que se encuentra en el medio del espacio que está ocupando ese grupo de datos, y se reubica en esa posición central al centroide correspondiente a ese grupo. Una vez hecho esto se repite el proceso midiendo las distancias de todos los datos respecto de los centroides en su nueva posición.
A medida que el proceso se repite la posición de los centroides va cambiando progresivamente así como puede ir cambiando los grupos a los que pertenece cada datos y sigue ocurriendo esto hasta que el algoritmo converge, es decir, los centroides encuentran su punto optimo, dejan de moverse y los grupos quedan definitivamente conformados.
Este algoritmo es lo que se conoce como un algoritmo de aprendizaje automatico porque el modelo se ajusta solo a partir de la exposición a los datos, y es no supervisado porque en el proceso de entrenarse el modelo no requiere que uno lo informe de cual es la respuesta correcta.
Tratamiento de datos nulos¶
tabla.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 197 entries, 0 to 196 Data columns (total 11 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Países 197 non-null object 1 Índice de Gini 169 non-null float64 2 Índice Felicidad 136 non-null float64 3 IDH 191 non-null float64 4 Déficit (%PIB) 192 non-null float64 5 Esperanza de vida 194 non-null float64 6 DoingBusiness 177 non-null float64 7 IPC Interanual 96 non-null float64 8 Salario Medio 43 non-null float64 9 Tasa de desempleo 46 non-null float64 10 PIB Per Capita 197 non-null int64 dtypes: float64(9), int64(1), object(1) memory usage: 18.5+ KB
Si observamos la información del dataframe a utilizarse se observa que el dataframe tiene 196 filas, pero que en muchos casos el numero de registros que efectivamente contienen datos en cada columna puede ser significativamente menor. Esto es un factor que tenemos que considerar al usar el algoritmo K-Means ya que para usarlo se pueden usar columnas con valores numericos no nulos. En algunos casos vamos a intentar sustituir los valores nulos por el promedio de los valores de esa columna, pero para que el promedio de los datos de una columna sea medianamente representativo de los valores que contiene el numero de valores no nulos tiene que ser alto.
Por este motivo vamos a empezar por excluir del presente análisis las tres columnas que mayor cantidad de valores nulos contienen, que son "IPC Interanual","Salario Medio" y "Tasa de desempleo". Tenemos la suerte, si recordamos el análisis exploratorio que hicimos con anterioridad de estos datos, que "IPC Interanual" y "Tasa de Desempleo" guardaban una correlación muy cercana a cero con respecto a las otras variables del conjunto de datos, con lo cual es posible que estas columnas no aporten información muy relevante a nuestro modelo de agrupamiento. Además, recordemos que "Salario Medio" correlacionaba casi a la perfección con "PIB Per Capita" con lo cual es posible que la informacion que "Salario Medio" pueda llegar a aportar respecto de los paises sea relativamente redundante por estar implicita en gran medida en el "PIB Per Capita".
Una forma de percibir visualmente el peso que tienen los valores nulos en cada columna es aplicar la siguiente linea de codigo:
sns.heatmap(tabla.isnull())
<AxesSubplot:>
Como se ve la cantidad de datos nulos de "IPC Interanual", "Salario Medio" y "Tasa de desempleo" es muy baja. Por lo que vamos a excluir esas columnas de la siguiente manera:
tabla.drop(columns=['IPC Interanual', 'Salario Medio','Tasa de desempleo'],inplace=True)
A continuación vamos a reemplazar los valores nulos restantes por el promedio del valor de la columna en la que se encuentra ese valor nulo:
for columna in tabla.columns[1:]:
tabla[columna].fillna(value=tabla[columna].mean(),inplace=True)
Y listo, si vuelvo a revisar la información del dataframe, vamos a ver que no quedan valores nulos:
tabla.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 197 entries, 0 to 196 Data columns (total 8 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Países 197 non-null object 1 Índice de Gini 197 non-null float64 2 Índice Felicidad 197 non-null float64 3 IDH 197 non-null float64 4 Déficit (%PIB) 197 non-null float64 5 Esperanza de vida 197 non-null float64 6 DoingBusiness 197 non-null float64 7 PIB Per Capita 197 non-null int64 dtypes: float64(6), int64(1), object(1) memory usage: 13.9+ KB
Escalado¶
Un factor importante a considerar es que, para que el algoritmo K-Means pueda medir de manera significativa las distancias de los datos y agruparlos, las dimensiones de ese espacio, es decir, los valores numericos utilizados, deben estar todos en la misma escala. Esto claramente no ocurre en la tabla que estamos utilizando, si tenemos en cuenta que, por ejemplo "Indice Gini" va a tener valores entre 0 y 1 mientras que "PIB Per Capita" entre 0 y 53051.
Para corregir esto lo que hacemos es escalar los datos. Matematicamente esto consiste en colocar todos los valores dentro de una distribución normal estandar, es decir, que tiene la media en cero y desviación estandar en 1.
Este proceso ya está programado entre las herramientas que nos ofrece la librería "Sci Kit Learn" que es también la que vamos a usar para aplicar el algoritmo K-Means.
Pero antes de eso extraigamos de nuestra tabla solamente las columnas con valores numericos (Es decir, dejando afuera la columna con el nombre de los paises), esto lo hacemos de la siguiente manera:
valores=tabla.select_dtypes(include=[np.number])
Ahora si, vamos a importar el metodo StandarScaler de la librería "Sci Kit Learn" (sklearn)
from sklearn.preprocessing import StandardScaler
Creamos una instancia de StandardScaler llamandola "escalador" y ajustamos el modelo a nuestros valores.
Seguidamente, usamos el modelo para transformar los valores a valores escalados:
escalador=StandardScaler()
escalador.fit(valores)
valores_escalados=escalador.transform(valores)
Aplicación del modelo: K-Means¶
Ahora sí, vamos a aplicar el algoritmo K-Means a nuestros valores ya correctamente escalados para ver que ocurre. Para empezar hay que importar el modelo.
from sklearn.cluster import KMeans
A continuación hay que generar una instancia, a la que llamaré simplemente "modelo", de nuestro algoritmo KMeans. A esta instancia hay que ajustarle los hiperparametros, que son configuraciones del modelo que determinan lo que el mismo va a hacer. Acá es donde tenemos que indicarle la cantidad de clusters que quiero que genere. En este caso, solo para probar, voy a poner 6 clusters. También voy a configurar el parametro "random_state" que no es otra cosa que la semilla del generador de números aleatorios que usa el algoritmo para generar la posición inicial de los centroides. El uso de este parametro es opciónal y se puede colocar cualquier numero, pero asignarle un valor va a garantizar que siempre que use el mismo numero obtenga el mismo resultado.
Por último, así como más adelante vamos a aplicar el metodo del codo para encontrar un numero optimo de clusters, nosotros acá no lo vamos a hacer pero es posible hacer lo mismo con el valor de este parametro para probar varias semillas y seleccionar la que genere mejor resultado.
modelo=KMeans(n_clusters=6,random_state=10)
Lo siguiente es, con el modelo configurado, entrenarlo para que se ajuste a nuestos datos. Es en este punto en que los centroides, partiendo de un posicionamiento aleatorio (según la semilla que le configuramos) van a ir buscando su posición optima en relación a nuestros datos. El entrenamiento del modelo se ejecuta de la siguiente manera:
modelo.fit(valores_escalados)
KMeans(n_clusters=6, random_state=10)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
KMeans(n_clusters=6, random_state=10)
Ahora solo resta pedirle que nos devuelva la lista ordenada con los clusters a los que pertenece cada dato de nuestro DataFrame. Ese resultado lo vamos a insertar en nuestra tabla en una nueva columna llamada "Cluster":
predict=modelo.predict(valores_escalados)
tabla["Cluster"]=predict
El resultado es el siguiente:
tabla.head(10)
| Países | Índice de Gini | Índice Felicidad | IDH | Déficit (%PIB) | Esperanza de vida | DoingBusiness | PIB Per Capita | Cluster | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | España [+] | 31.5 | 6.436 | 0.905 | -0.0364 | 83.08 | 77.940000 | 32530 | 1 |
| 1 | Alemania [+] | 29.3 | 6.892 | 0.942 | -0.0250 | 80.70 | 79.710000 | 52824 | 1 |
| 2 | Reino Unido [+] | 33.5 | 6.796 | 0.929 | -0.0551 | 80.70 | 83.550000 | 45943 | 1 |
| 3 | Francia [+] | 29.8 | 6.661 | 0.903 | -0.0550 | 82.30 | 76.800000 | 44460 | 1 |
| 4 | Italia [+] | 32.7 | 6.405 | 0.895 | -0.0740 | 82.80 | 72.850000 | 38221 | 1 |
| 5 | Portugal [+] | 32.0 | 5.968 | 0.866 | 0.0120 | 81.80 | 76.470000 | 27427 | 0 |
| 6 | Estados Unidos [+] | 39.8 | 6.894 | 0.921 | -0.0371 | 76.33 | 62.757571 | 82034 | 1 |
| 7 | Japón [+] | 32.9 | 6.129 | 0.925 | -0.0615 | 84.45 | 62.757571 | 33854 | 1 |
| 8 | China [+] | 37.1 | 5.818 | 0.768 | -0.0752 | 78.21 | 62.757571 | 12670 | 0 |
| 9 | Emiratos Árabes Unidos [+] | 26.0 | 6.571 | 0.911 | 0.0989 | 78.71 | 80.750000 | 53051 | 1 |
Evaluación del desempeño modelo¶
Pero, ¿Como hacemos para saber si el agrupamiento que nos devolvió nuestro modelo es bueno? Existen diferentes maneras y va a depender en gran medida de cual es la finalidad del agrupamiento. A grandes rasgos se habla de evaluaciones de desempeño externas o internas.
Externas son las evaluaciónes en que uno tiene una idea predefinida de como se tienen que agrupar los datos en función de la finalidad que se le va a dar al agrupamiento, en cuyo caso la forma de evaluar va a consistir en comparar los resultados del modelo con los que consideramos que son los resultados que queremos alcanzar.
Internas son las tecnicas de evaluación en las que se usan metricas que miden las caracteristicas inherentes al modelo como ser la cohesión entre los datos de cada grupo y la distancia con respecto a otros grupos. Estas últimas son las que vamos a aplicar en este caso.
Para empezar importamos las metricas a utilizar:
from sklearn.metrics import silhouette_score
from sklearn.metrics import calinski_harabasz_score
from sklearn.metrics import davies_bouldin_score
Ahora veamos que tipo de resultados nos genera cada una y expliquemos en que consiste.
Suma del error cuadrático dentro del cluster (WCSS): Mide la distancia promedio entre cada punto de datos y el centroide de su grupo asignado. Un valor bajo indica que los puntos de datos están agrupados de forma compacta alrededor de sus centroides. En Sklearn se accede a este dato mediante el metodo .inertia_ aplicado al modelo de clustering. Esta metrica no hace falta importarla sino que podemos accederla directamente desde nuestro modelo con el metodo .inertia_
modelo.inertia_
538.7030875937686
El siguiente es el indice Silhouette. Evalúa la cohesión interna y la separación entre clusters. Mide para cada punto la distancia al centroide de su grupo y la compara con la distancia al centroide ajeno más cercano, cuanto mayor sea la diferencia más cercano a 1 es el coeficiente Silhouette. Un valor promedio alto (cercano a 1) de los coeficientes de los puntos indica clusters bien definidos. La formula toma los valores iniciales procesados por el modelo y los agrupamientos resultantes del mismo:
silhouette_score(valores_escalados, predict)
0.19157481184607827
Indice Calinski Harabasz: Similar al Silhouette Score, pero más sensible al tamaño y forma de los clusters. Un valor alto indica clusters bien definidos.
calinski_harabasz_score(valores_escalados, predict)
59.58633390667308
Indice Davies Bouldin: Evalúa la separación entre clusters y su tamaño relativo. Un valor bajo indica clusters bien definidos y de tamaño similar.
davies_bouldin_score(valores_escalados, predict)
1.3445797598453428
Todo esto nos da una idea muy basica de que hacen estas metricas en abstracto, pero esos numeros no nos sirven para nada si no tenemos con que compararlos. ¿Cuanto es un indice Silhouette bajo? ¿Cuanto es un indice Calinski Harabasz alto? ¿Como hago para encontrar la configuración que me devuelva las mejores metricas? Para responder esto, voy a aplicar la tecnica más popular que se usa para ajustar los hiperparametros de un algoritmo de clustering, el método del codo.
El método del codo¶
El método del codo se puede utilizar para buscar la configuración optima de diferentes hiperparametros. Acá lo vamos a utilizar para ajustar el principal hiperparámetro que es la cantidad de grupos.
Lo que vamos a hacer es basicamente entrenar el modelo muchas veces, con diferentes cantidades de grupos, obtener las metricas de desempeño y compararlas en un gráfico de lineas.
En primer lugar voy a generar un dataframe vacío y a continuación voy a entrenar mi modelo probando todos los numeros de clusters entre 2 y 10, aplicarle las metricas de evaluación y volcarlas en el dataframe que generé a este fin.
A continuación el codigo comentado:
desempeños=pd.DataFrame(columns=["grupos","WCSS","Silhouette","CalinskiHarabasz","DaviesBouldin"]) #Creo el dataframe que va a contener los resultados
k_max=10 #establezco el maximo numero de clusters
for k in range(2,k_max+1): #voy a iterar el valor de k entre 2 y 10
#Aplicacion del modelo
modelo=KMeans(n_clusters=k,random_state=10) #instancia del modelo
modelo.fit(valores_escalados) #ajuste del modelo
predict=modelo.predict(valores_escalados) #clusters resultantes
#evaluacion del modelo
WCSS=modelo.inertia_ #Suma del error cuadrático dentro del cluster
S_Score=silhouette_score(valores_escalados, predict) #indice Silhouette
CH_Score=calinski_harabasz_score(valores_escalados, predict) #Indice Calinski Harabasz
DB_Score=davies_bouldin_score(valores_escalados, predict) #Indice Davies Bouldin
resultados=pd.DataFrame({k,WCSS,S_Score,CH_Score,DB_Score}) #genero una serie con estos resultados
desempeños=desempeños.append({"grupos":k,"WCSS":WCSS,"Silhouette":S_Score,"CalinskiHarabasz":CH_Score,"DaviesBouldin":DB_Score}, ignore_index=True) #agrego la serie
#como una linea nueva a nuestro dataframe de resultados
desempeños=desempeños.set_index("grupos") #establezco la columna grupos como indice
C:\Users\mbert\AppData\Local\Temp\ipykernel_14264\1669380870.py:17: FutureWarning: The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.
desempeños=desempeños.append({"grupos":k,"WCSS":WCSS,"Silhouette":S_Score,"CalinskiHarabasz":CH_Score,"DaviesBouldin":DB_Score}, ignore_index=True) #agrego la serie como una linea nueva a nuestro dataframe de resultados
C:\Users\mbert\AppData\Local\Temp\ipykernel_14264\1669380870.py:17: FutureWarning: The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.
desempeños=desempeños.append({"grupos":k,"WCSS":WCSS,"Silhouette":S_Score,"CalinskiHarabasz":CH_Score,"DaviesBouldin":DB_Score}, ignore_index=True) #agrego la serie como una linea nueva a nuestro dataframe de resultados
C:\Users\mbert\AppData\Local\Temp\ipykernel_14264\1669380870.py:17: FutureWarning: The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.
desempeños=desempeños.append({"grupos":k,"WCSS":WCSS,"Silhouette":S_Score,"CalinskiHarabasz":CH_Score,"DaviesBouldin":DB_Score}, ignore_index=True) #agrego la serie como una linea nueva a nuestro dataframe de resultados
C:\Users\mbert\AppData\Local\Temp\ipykernel_14264\1669380870.py:17: FutureWarning: The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.
desempeños=desempeños.append({"grupos":k,"WCSS":WCSS,"Silhouette":S_Score,"CalinskiHarabasz":CH_Score,"DaviesBouldin":DB_Score}, ignore_index=True) #agrego la serie como una linea nueva a nuestro dataframe de resultados
C:\Users\mbert\AppData\Local\Temp\ipykernel_14264\1669380870.py:17: FutureWarning: The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.
desempeños=desempeños.append({"grupos":k,"WCSS":WCSS,"Silhouette":S_Score,"CalinskiHarabasz":CH_Score,"DaviesBouldin":DB_Score}, ignore_index=True) #agrego la serie como una linea nueva a nuestro dataframe de resultados
C:\Users\mbert\AppData\Local\Temp\ipykernel_14264\1669380870.py:17: FutureWarning: The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.
desempeños=desempeños.append({"grupos":k,"WCSS":WCSS,"Silhouette":S_Score,"CalinskiHarabasz":CH_Score,"DaviesBouldin":DB_Score}, ignore_index=True) #agrego la serie como una linea nueva a nuestro dataframe de resultados
C:\Users\mbert\AppData\Local\Temp\ipykernel_14264\1669380870.py:17: FutureWarning: The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.
desempeños=desempeños.append({"grupos":k,"WCSS":WCSS,"Silhouette":S_Score,"CalinskiHarabasz":CH_Score,"DaviesBouldin":DB_Score}, ignore_index=True) #agrego la serie como una linea nueva a nuestro dataframe de resultados
C:\Users\mbert\AppData\Local\Temp\ipykernel_14264\1669380870.py:17: FutureWarning: The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.
desempeños=desempeños.append({"grupos":k,"WCSS":WCSS,"Silhouette":S_Score,"CalinskiHarabasz":CH_Score,"DaviesBouldin":DB_Score}, ignore_index=True) #agrego la serie como una linea nueva a nuestro dataframe de resultados
C:\Users\mbert\AppData\Local\Temp\ipykernel_14264\1669380870.py:17: FutureWarning: The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.
desempeños=desempeños.append({"grupos":k,"WCSS":WCSS,"Silhouette":S_Score,"CalinskiHarabasz":CH_Score,"DaviesBouldin":DB_Score}, ignore_index=True) #agrego la serie como una linea nueva a nuestro dataframe de resultados
El resultado es la siguiente tabla, que contiene las metricas obtenidas para cada cntidad de conjuntos configurados:
desempeños
| WCSS | Silhouette | CalinskiHarabasz | DaviesBouldin | |
|---|---|---|---|---|
| grupos | ||||
| 2.0 | 886.054255 | 0.304995 | 108.485931 | 1.208272 |
| 3.0 | 715.976077 | 0.255304 | 89.826075 | 1.355468 |
| 4.0 | 653.531307 | 0.222928 | 71.414818 | 1.412562 |
| 5.0 | 582.619066 | 0.243318 | 65.611112 | 1.222359 |
| 6.0 | 538.703088 | 0.191575 | 59.586334 | 1.344580 |
| 7.0 | 507.408428 | 0.186866 | 54.394839 | 1.382254 |
| 8.0 | 454.836719 | 0.212112 | 54.860145 | 1.287574 |
| 9.0 | 417.523295 | 0.189119 | 54.116029 | 1.275381 |
| 10.0 | 388.563142 | 0.196563 | 52.961989 | 1.236644 |
La mejor forma de analizar estos resultados es disponiendolos en un gráfico de lineas. Veamos en primer lugar las metricas de suma del error cuadrático dentro del cluster para cada cantidad de clusters. La razón por la que se llama método del codo, es porque se suele considerar como óptimo el numero de clusters en el que se observa una reducción significativa en la metrica, seguida de una estabilización de la misma.
Es decir, el optimo es la cantidad de clusters mas baja que reporta un reducción significativa en la dispersión de los datos que forman parte del cluster sin que se obtenga una reducción muy importante de seguir aumentando el numero de clusters. Esa caida rapida seguida de una estabilización tiene forma de codo y por eso se le da ese nombre.
La razón por la que se considera que este punto es optimo es porque la utilidad del modelo depende de que pueda equilibrar un minimo numero de clusters (capacidad del modelo de generalizar) con un alto grado de precisión (poder descriptivo).
En el presente caso, como vemos en el siguiente gráfico, el codo no se encuentra muy claramente marcado. Al parecer los datos no se encuentran en clusters bien definidos y esto implica que puede obtenerse mayor cohesión en cada cluster aumentando el numero de los mismos, sin que se observen saltos significativos en ningún punto en particular.
A priori, pareciera que la mayor pendiente la obtenemos entre 2 y 3 clusters.
sns.lineplot(data=desempeños[["WCSS"]],markers=True)
<AxesSubplot:xlabel='grupos'>
La observación anterior se verifica en cierta medida en el indice Silhouette. En este caso no buscamos un codo sino que nos interesa ver cuales son los numeros de clusters que obtienen un indice Silhouette más cercano a uno. Lo que se observa en este caso es que el mejor resultado lo obtenemos con tres clusters, ya que en este punto se equilibra una mayor cohesión entre los datos del cluster con una mayor distancia relativa respecto de otros clusters.
sns.lineplot(data=desempeños[["Silhouette"]],markers=True)
<AxesSubplot:xlabel='grupos'>
En el caso del indice Calinsku Harabasz nos interesan los valores altos y pareciera, de acuerdo a esta metrica, que el optimo se encuentra en 2 conjuntos.
sns.lineplot(data=desempeños[["CalinskiHarabasz"]],markers=True)
<AxesSubplot:xlabel='grupos'>
Por último, si utilizamos la metrica Davies Bouldin, el equilibrio optimo entre cohesión interna y distancia externa de los puntos respecto de sus clusters se logra configurando el modelo en 2 clusters.
sns.lineplot(data=desempeños[["DaviesBouldin"]],markers=True)
<AxesSubplot:xlabel='grupos'>
Resultado final¶
Para finalizar, vamos a volver a ejecutar el algoritmo K-Means pero esta vez vamos a usar 2 clusters, número que estimamos como optimo usando el método del codo.
modelo=KMeans(n_clusters=2,random_state=10)
modelo.fit(valores_escalados)
predict=modelo.predict(valores_escalados)
tabla["Cluster"]=predict
A modo de muestra, dejo los primeros 10 registros de cada uno de los dos grupos resultantes:
tabla[tabla["Cluster"]==0].head(10)
| Países | Índice de Gini | Índice Felicidad | IDH | Déficit (%PIB) | Esperanza de vida | DoingBusiness | PIB Per Capita | Cluster | |
|---|---|---|---|---|---|---|---|---|---|
| 12 | Angola [+] | 51.3 | 5.53375 | 0.586 | 0.0068 | 61.64 | 41.290000 | 3558 | 0 |
| 18 | Bangladés [+] | 33.4 | 4.28200 | 0.661 | -0.0412 | 72.38 | 62.757571 | 2721 | 0 |
| 20 | Burkina Faso [+] | 37.4 | 4.63800 | 0.449 | -0.0636 | 59.27 | 51.400000 | 852 | 0 |
| 22 | Burundi [+] | 37.5 | 5.53375 | 0.426 | -0.1207 | 61.66 | 46.770000 | 320 | 0 |
| 23 | Benin [+] | 34.4 | 4.37400 | 0.525 | -0.0556 | 59.82 | 52.400000 | 1340 | 0 |
| 24 | Bolivia [+] | 40.9 | 5.68400 | 0.692 | -0.0712 | 63.63 | 51.660000 | 3669 | 0 |
| 25 | Brasil [+] | 52.0 | 6.12500 | 0.754 | -0.0311 | 72.75 | 62.757571 | 9455 | 0 |
| 26 | Bután [+] | 28.5 | 5.53375 | 0.666 | -0.0752 | 71.82 | 65.970000 | 3245 | 0 |
| 27 | Botsuana [+] | 53.3 | 3.43500 | 0.693 | -0.0151 | 61.14 | 66.200000 | 7864 | 0 |
| 29 | Belice [+] | 53.3 | 5.53375 | 0.683 | -0.0075 | 70.47 | 55.470000 | 6404 | 0 |
tabla[tabla["Cluster"]==1].head(10)
| Países | Índice de Gini | Índice Felicidad | IDH | Déficit (%PIB) | Esperanza de vida | DoingBusiness | PIB Per Capita | Cluster | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | España [+] | 31.5 | 6.436 | 0.905 | -0.0364 | 83.08 | 77.940000 | 32530 | 1 |
| 1 | Alemania [+] | 29.3 | 6.892 | 0.942 | -0.0250 | 80.70 | 79.710000 | 52824 | 1 |
| 2 | Reino Unido [+] | 33.5 | 6.796 | 0.929 | -0.0551 | 80.70 | 83.550000 | 45943 | 1 |
| 3 | Francia [+] | 29.8 | 6.661 | 0.903 | -0.0550 | 82.30 | 76.800000 | 44460 | 1 |
| 4 | Italia [+] | 32.7 | 6.405 | 0.895 | -0.0740 | 82.80 | 72.850000 | 38221 | 1 |
| 5 | Portugal [+] | 32.0 | 5.968 | 0.866 | 0.0120 | 81.80 | 76.470000 | 27427 | 1 |
| 6 | Estados Unidos [+] | 39.8 | 6.894 | 0.921 | -0.0371 | 76.33 | 62.757571 | 82034 | 1 |
| 7 | Japón [+] | 32.9 | 6.129 | 0.925 | -0.0615 | 84.45 | 62.757571 | 33854 | 1 |
| 8 | China [+] | 37.1 | 5.818 | 0.768 | -0.0752 | 78.21 | 62.757571 | 12670 | 1 |
| 9 | Emiratos Árabes Unidos [+] | 26.0 | 6.571 | 0.911 | 0.0989 | 78.71 | 80.750000 | 53051 | 1 |
Si genero un pairplot distinguiendo los clusters, podemos ver de que manera se distinguen al cruzar cada una de las variables entre sí. También podemos ver la distinta distribución de valores en los histogramas correspondientes a cada variable. Tengamos en cuenta que el algoritmo de clustering busca agrupamientos de datos en un espacio multidimensional que, en este caso tiene 7 dimensiones (una por cada variable). No podemos visualizar ese espacio gráficamente pero podemos imaginar que cada uno de los scatterplots siguientes es un corte plano (de dos dimensiones) que nos da una idea de como se distribuyen los datos en ese espacio:
sns.pairplot(tabla,hue="Cluster",corner=True)
<seaborn.axisgrid.PairGrid at 0x1d5435681c0>
Por último, los histogramas correspondientes a cada variable, nos permiten ver la diferencia que cada uno de los grupos presenta en esos aspectos. Así podemos ver que el grupo 0 presenta en general un indice gini mas elevado, un indice de felicidad relativamente menor, menor desarrollo humano, economías ligeramente menos deficitarias, menor esperanza de vida, peor puntaje en el indice DoingBusiness (facilidad para hacer negocios) y un PBI per capita más concentrado en la parte más baja de la distribución.
Comentarios
Publicar un comentario