Para la elaboración de este proyecto se seleccionó la base de datos “Diamonds Characteristics and Pricing Analysis”, una fuente pública ampliamente utilizada en estudios estadísticos, modelado predictivo y análisis exploratorio.
¿Cómo se agrupan los diamantes según cut, color y clarity, y cómo difieren estos grupos en el precio promedio? Variables: cut, color, clarity, price Objetivo: Identificar clústeres basados en calidad y evaluar cómo varía el precio entre ellos.
¿Cuál es la relación entre carat, price y cut, y cómo interactúan para influir en el valor del diamante? Variables: carat, price, cut Objetivo: Analizar cómo el peso y la calidad del corte afectan conjuntamente el precio.
¿Cómo influyen depth y table en el price y carat? Variables: depth, table, price, carat Objetivo: Evaluar si las proporciones físicas del diamante afectan su valor y su relación con el peso.
import pandas as pd # Manejo del dataframe
import matplotlib.pyplot as plt # Para plotear
import seaborn as sns # Para plotear
import plotly.express as px # Para gráficos interactivos
from umap import UMAP # Paquete para usar el UMAP
from sklearn.cluster import KMeans # Para usar KMeans
import numpy as np # Habilita el Python Numérico para análisis de datos. análisis de datos.
#Funciones
def radar_plot(centros, labels):
from math import pi
centros = np.array([
((n - min(n)) / (max(n) - min(n)) * 100) if max(n) != min(n) else (n/n * 50)
for n in centros.T
])
angulos = [n / float(len(labels)) * 2 * pi for n in range(len(labels))]
angulos += angulos[:1]
ax = plt.subplot(111, polar=True)
ax.set_theta_offset(pi / 2)
ax.set_theta_direction(-1)
plt.xticks(angulos[:-1], labels)
ax.set_rlabel_position(0)
plt.yticks(
[10,20,30,40,50,60,70,80,90,100],
["10%","20%","30%","40%","50%","60%","70%","80%","90%","100%"],
color="grey",
size=8
)
plt.ylim(-10,100)
for i in range(centros.shape[1]):
valores = centros[:, i].tolist()
valores += valores[:1]
ax.plot(angulos, valores, linewidth=1, linestyle="solid", label=f"Cluster {i}")
ax.fill(angulos, valores, alpha=0.3)
plt.legend(loc="upper right", bbox_to_anchor=(0.1, 0.1))
def centroide(num_cluster, datos, clusters):
ind = clusters == num_cluster
return pd.DataFrame(datos[ind].mean()).T
def calcular_centros(cant_clusters, datos, clusters):
centro = centroide(0, datos, clusters)
for j in range(1, cant_clusters):
centro = pd.concat([centro, centroide(j, datos, clusters)])
return np.array(centro)
def scatter_2D(df, grupos, label="cluster", title=""):
fig = px.scatter(
df,
x="dim1",
y="dim2",
color=grupos.astype(str),
hover_data={"Individuo": df.index.values},
labels={"color": label}
)
fig.update_layout(title_text=title, title_x=0.5)
return fig.show()
Se inserta el data set
datos = pd.read_csv(r"C:\UIA\Data\diamonds.csv", delimiter=',', decimal=".", index_col=0)
datos_dum = pd.get_dummies(datos, drop_first=True)
datos_dum.shape
## (53940, 20)
print(datos_dum.columns.tolist())
## ['depth', 'table', 'price', 'cut_Good', 'cut_Ideal', 'cut_Premium', 'cut_Very Good', 'color_E', 'color_F', 'color_G', 'color_H', 'color_I', 'color_J', 'clarity_IF', 'clarity_SI1', 'clarity_SI2', 'clarity_VS1', 'clarity_VS2', 'clarity_VVS1', 'clarity_VVS2']
datos.describe()
## depth table price
## count 53940.000000 53940.000000 53940.000000
## mean 61.749405 57.457184 3932.799722
## std 1.432621 2.234491 3989.439738
## min 43.000000 43.000000 326.000000
## 25% 61.000000 56.000000 950.000000
## 50% 61.800000 57.000000 2401.000000
## 75% 62.500000 59.000000 5324.250000
## max 79.000000 95.000000 18823.000000
vars_deseadas = [
"carat", "depth", "table", "price",
"cut_Good", "cut_Ideal", "cut_Premium", "cut_Very Good",
"color_E", "color_F", "color_G", "color_H",
"clarity_SI1", "clarity_VS1", "clarity_VVS1"
]
vars_cor = [c for c in vars_deseadas if c in datos_dum.columns]
sub = datos_dum[vars_cor]
corr = sub.corr()
import matplotlib.pyplot as plt
import seaborn as sns
fig, ax = plt.subplots(figsize=(10, 8), dpi=150)
paleta = sns.diverging_palette(220, 10, as_cmap=True).reversed()
sns.heatmap(
corr,
vmin=-1, vmax=1, cmap=paleta,
square=True, annot=True, fmt=".2f",
annot_kws={"size": 7},
cbar=True, ax=ax
)
plt.xticks(rotation=45, ha="right")
## (array([ 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5,
## 11.5, 12.5, 13.5]), [Text(0.5, 0, 'depth'), Text(1.5, 0, 'table'), Text(2.5, 0, 'price'), Text(3.5, 0, 'cut_Good'), Text(4.5, 0, 'cut_Ideal'), Text(5.5, 0, 'cut_Premium'), Text(6.5, 0, 'cut_Very Good'), Text(7.5, 0, 'color_E'), Text(8.5, 0, 'color_F'), Text(9.5, 0, 'color_G'), Text(10.5, 0, 'color_H'), Text(11.5, 0, 'clarity_SI1'), Text(12.5, 0, 'clarity_VS1'), Text(13.5, 0, 'clarity_VVS1')])
plt.yticks(rotation=0)
## (array([ 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5,
## 11.5, 12.5, 13.5]), [Text(0, 0.5, 'depth'), Text(0, 1.5, 'table'), Text(0, 2.5, 'price'), Text(0, 3.5, 'cut_Good'), Text(0, 4.5, 'cut_Ideal'), Text(0, 5.5, 'cut_Premium'), Text(0, 6.5, 'cut_Very Good'), Text(0, 7.5, 'color_E'), Text(0, 8.5, 'color_F'), Text(0, 9.5, 'color_G'), Text(0, 10.5, 'color_H'), Text(0, 11.5, 'clarity_SI1'), Text(0, 12.5, 'clarity_VS1'), Text(0, 13.5, 'clarity_VVS1')])
plt.tight_layout()
plt.show()
Se generó una matriz de correlación para identificar relaciones entre variables. Este análisis permite observar qué atributos tienen mayor influencia entre sí, y sirve como insumo para la interpretación de los clústeres generados por UMAP + K-Means.
Se cargó la base original de diamonds y se transformaron todas las variables categóricas en variables dummy mediante get_dummies(). El dataset resultante contiene 53,940 registros y 20 variables numéricas, lo cual es necesario para aplicar UMAP y K-Means, ya que ambos algoritmos requieren datos completamente numéricos.
umap_data = UMAP(n_components=2,n_neighbors=10,min_dist=0.1,random_state=42).fit_transform(datos_dum)
## C:\Users\sebas\DOCUME~1\VIRTUA~1\R-RETI~1\Lib\site-packages\umap\umap_.py:1952: UserWarning:
##
## n_jobs value 1 overridden to 1 by setting random_state. Use no seed for parallelism.
##
## C:\Users\sebas\DOCUME~1\VIRTUA~1\R-RETI~1\Lib\site-packages\sklearn\manifold\_spectral_embedding.py:328: UserWarning:
##
## Graph is not fully connected, spectral embedding may not work as expected.
Se configuró K-Means con 4 clústeres, max_iter=2000, n_init=150 y random state 42 para garantizar estabilidad en la asignación de grupos. Esta configuración mejora la calidad de los clústeres al reducir la variabilidad entre ejecuciones.
df_umap = pd.DataFrame(umap_data, columns = ["dim1","dim2"], index = datos.index.values)
kmedias = KMeans(n_clusters=4,max_iter=2000,n_init=150,random_state=42)
kmedias.fit(df_umap)
KMeans(max_iter=2000, n_clusters=4, n_init=150, random_state=42)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
| n_clusters | 4 | |
| init | 'k-means++' | |
| n_init | 150 | |
| max_iter | 2000 | |
| tol | 0.0001 | |
| verbose | 0 | |
| random_state | 42 | |
| copy_x | True | |
| algorithm | 'lloyd' |
Se aplicó UMAP para reducir las 20 variables numéricas a dos dimensiones (dim1 y dim2), permitiendo visualizar patrones y evidenciando tres zonas bien diferenciadas en los datos. Con estas proyecciones se ejecutó K-Means con k = 4, utilizando 150 inicializaciones y un máximo de 2000 iteraciones y random state 42 para asegurar una agrupación estable.
grupos = kmedias.predict(df_umap)
df_umap["cluster"] = grupos
df_umap
## dim1 dim2 cluster
## 0.23 16.626076 -8.711778 1
## 0.21 16.614927 -8.702290 1
## 0.23 16.625465 -8.710221 1
## 0.29 16.614407 -8.700451 1
## 0.31 16.615726 -8.699868 1
## ... ... ... ...
## 0.72 1.383233 -2.598651 2
## 0.72 1.412605 -2.589294 2
## 0.70 1.435360 -2.655919 2
## 0.86 1.384377 -2.606956 2
## 0.75 1.377521 -2.563674 2
##
## [53940 rows x 3 columns]
centros = calcular_centros(4, datos_dum, np.array(df_umap["cluster"]))
radar_plot(centros, datos_dum.columns)
plt.show()
Clúster 0 – Diamantes grandes pero de calidad baja/media
Clúster 1 – Diamantes equilibrados (calidad media, precio moderado)
Clúster 2 – Diamantes premium (alta claridad, buen color y cortes superiores)
Clúster 3 – Diamantes caros por tamaño y proporciones