UN ANALISIS DE CLUSTER JERARQUICO DE UN CUESTIONARIO ESCALA LIKERT

# Importar las librerías necesarias
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial.distance import pdist, squareform
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
from sklearn.decomposition import PCA
import statsmodels.formula.api as smf
from scipy.cluster.hierarchy import dendrogram, linkage, fcluster
from sklearn.preprocessing import StandardScaler
from scipy.spatial.distance import pdist, squareform
from sklearn.preprocessing import StandardScaler

# 1. Leer el archivo Excel (asegúrate que el archivo '6.1.Hábitos.25.xlsx' esté en el directorio de trabajo)
# Nota: Si tienes varias hojas o se requiere especificar la hoja, añade el parámetro sheet_name.
df = pd.read_excel("6.1.Hábitos.25.xlsx")
print(df.head())
   Sexo  Edad  Entretenido  Compras  Tiempo  Online  Interés
0     1    20            6        7       4       2        3
1     1    21            2        1       3       5        4
2     0    20            7        6       2       1        5
3     0    20            4        4       6       3        4
4     0    20            1        2       3       6        2
# 2. Seleccionar las columnas a usar: asumiremos que queremos usar todas las variables numéricas.
# Si se desea excluir o transformar alguna (ej. Sexo) se podría hacerlo aquí.
# Por ejemplo, si "Sexo" es 1/0 y se desea incluirlo, lo dejamos.
cols = ["Sexo", "Edad", "Entretenido", "Compras", "Tiempo", "Online", "Interés"]
data = df[cols].copy()

# 3. Estandarizar las variables
scaler = StandardScaler()
data_scaled = scaler.fit_transform(data)  # resultará en un array NumPy
# Puedes volver a crear un DataFrame para facilitar la visualización:
data_scaled_df = pd.DataFrame(data_scaled, columns=cols)
print("\nDatos estandarizados:")

Datos estandarizados:
print(data_scaled_df.head())
       Sexo      Edad  Entretenido   Compras    Tiempo    Online   Interés
0  1.224745 -0.294884     1.229837  1.527966 -0.055556 -0.615457 -0.771631
1  1.224745  1.548141    -1.006231 -1.487757 -0.750000  1.230915 -0.149348
2 -0.816497 -0.294884     1.788854  1.025346 -1.444444 -1.230915  0.472935
3 -0.816497 -0.294884     0.111803  0.020105  1.333333  0.000000 -0.149348
4 -0.816497 -0.294884    -1.565248 -0.985136 -0.750000  1.846372 -1.393915
# 2. Mostrar la matriz de correlaciones de los datos originales (usando la correlación de Pearson)
print("Matriz de correlaciones (datos originales):")
Matriz de correlaciones (datos originales):
print(data.corr())
                 Sexo      Edad  Entretenido  ...    Tiempo    Online   Interés
Sexo         1.000000  0.060193    -0.273861  ... -0.102062  0.100504  0.233722
Edad         0.060193  1.000000    -0.049454  ... -0.272358  0.181489  0.001835
Entretenido -0.273861 -0.049454     1.000000  ...  0.099381 -0.440386 -0.219852
Compras     -0.106701  0.117090     0.818194  ... -0.110577 -0.395957 -0.272237
Tiempo      -0.102062 -0.272358     0.099381  ...  1.000000 -0.205152 -0.094725
Online       0.100504  0.181489    -0.440386  ... -0.205152  1.000000  0.137876
Interés      0.233722  0.001835    -0.219852  ... -0.094725  0.137876  1.000000

[7 rows x 7 columns]
# 4. Calcular la matriz de distancias (euclídea) usando los datos estandarizados
distance_array = pdist(data_scaled_df, metric="euclidean")
distance_matrix = pd.DataFrame(squareform(distance_array), 
                               index=df.index, columns=df.index)

print("\nMatriz de distancias (datos estandarizados):")

Matriz de distancias (datos estandarizados):
print(distance_matrix)
          0         1         2   ...        22        23        24
0   0.000000  4.665823  2.930621  ...  3.559773  3.816474  5.211993
1   4.665823  0.000000  5.349924  ...  4.321576  3.010967  2.216145
2   2.930621  5.349924  0.000000  ...  4.844045  4.281047  5.273849
3   3.222631  4.116125  3.666258  ...  2.678541  3.244183  4.792325
4   5.022444  3.171162  5.359955  ...  5.070884  4.165047  4.386539
5   2.862660  4.226936  2.949975  ...  4.370138  4.384533  4.952251
6   2.998262  4.443275  2.584171  ...  4.474509  4.601541  4.826576
7   3.847857  4.631688  3.740127  ...  5.820424  4.699785  5.216789
8   3.249183  2.378135  4.597811  ...  3.104711  2.551134  3.476588
9   3.772401  2.863164  4.714704  ...  3.178631  1.117263  3.163174
10  4.708911  2.920295  4.593339  ...  4.342578  4.154422  3.515659
11  2.718835  3.908834  2.688488  ...  3.808173  3.148382  4.500133
12  5.162470  4.359117  4.575370  ...  4.991862  3.516301  4.457320
13  3.671954  4.799675  3.453447  ...  2.684842  3.233749  4.785268
14  3.329464  5.879272  3.541914  ...  3.647173  4.919430  6.633771
15  3.838665  3.742499  3.761265  ...  3.518057  2.299736  3.814634
16  1.279545  4.379682  3.608978  ...  3.547350  3.943426  5.064583
17  4.420126  4.215723  5.290802  ...  3.072003  4.294239  5.527264
18  3.856533  4.372897  3.683506  ...  2.613289  2.882805  4.499663
19  4.416885  2.741037  4.632655  ...  3.982495  1.023416  2.047313
20  2.344743  4.820212  1.888451  ...  3.586291  3.818970  5.042685
21  1.249311  4.622167  2.557105  ...  3.787378  3.559285  4.699883
22  3.559773  4.321576  4.844045  ...  0.000000  3.435058  5.015953
23  3.816474  3.010967  4.281047  ...  3.435058  0.000000  2.613117
24  5.211993  2.216145  5.273849  ...  5.015953  2.613117  0.000000

[25 rows x 25 columns]
# 4. Calcular la matriz de distancias (euclídea) usando los datos estandarizados
distance_array = pdist(data, metric="euclidean")
distance_matrix = pd.DataFrame(squareform(distance_array), 
                               index=df.index, columns=df.index)
                               
                               
print(distance_matrix)
          0         1         2   ...        22        23        24
0   0.000000  8.000000  3.464102  ...  6.324555  6.708204  8.660254
1   8.000000  0.000000  8.366600  ...  6.164414  4.123106  3.605551
2   3.464102  8.366600  0.000000  ...  7.211103  6.557439  8.185353
3   4.472136  5.291503  5.830952  ...  3.162278  4.123106  6.403124
4   8.306624  3.000000  9.327379  ...  7.416198  6.000000  5.656854
5   2.000000  6.928203  3.741657  ...  6.000000  6.082763  7.937254
6   2.449490  7.211103  3.162278  ...  6.000000  6.403124  7.681146
7   4.582576  7.937254  5.385165  ...  8.774964  6.928203  8.717798
8   6.000000  2.828427  7.211103  ...  4.898979  4.123106  4.795832
9   6.633250  3.741657  7.211103  ...  5.099020  1.732051  4.123106
10  7.280110  3.605551  7.681146  ...  5.385165  5.291503  4.690416
11  3.316625  5.385165  4.358899  ...  5.385165  4.242641  6.324555
12  8.124038  2.828427  7.745967  ...  6.480741  3.872983  3.316625
13  5.291503  6.633250  5.477226  ...  3.162278  4.123106  6.403124
14  3.741657  7.348469  4.898979  ...  4.242641  6.855655  8.774964
15  5.744563  4.582576  6.244998  ...  4.795832  2.000000  4.472136
16  2.236068  7.416198  5.000000  ...  6.244998  6.782330  8.246211
17  7.000000  5.000000  8.660254  ...  3.872983  6.000000  7.483315
18  5.830952  5.656854  6.000000  ...  2.828427  3.316625  5.744563
19  7.874008  3.464102  7.483315  ...  6.164414  1.732051  1.732051
20  2.236068  7.280110  3.000000  ...  5.196152  5.656854  7.483315
21  2.000000  7.874008  2.828427  ...  6.480741  6.244998  7.810250
22  6.324555  6.164414  7.211103  ...  0.000000  5.385165  7.280110
23  6.708204  4.123106  6.557439  ...  5.385165  0.000000  3.162278
24  8.660254  3.605551  8.185353  ...  7.280110  3.162278  0.000000

[25 rows x 25 columns]
# Opcional: Visualización del dendrograma
from scipy.cluster.hierarchy import dendrogram, linkage
Z = linkage(data_scaled, method='ward')
plt.figure(figsize=(10, 6))
dendrogram(Z, labels=df.index, leaf_rotation=90)
{'icoord': [[15.0, 15.0, 25.0, 25.0], [5.0, 5.0, 20.0, 20.0], [35.0, 35.0, 45.0, 45.0], [12.5, 12.5, 40.0, 40.0], [75.0, 75.0, 85.0, 85.0], [65.0, 65.0, 80.0, 80.0], [55.0, 55.0, 72.5, 72.5], [26.25, 26.25, 63.75, 63.75], [95.0, 95.0, 105.0, 105.0], [125.0, 125.0, 135.0, 135.0], [115.0, 115.0, 130.0, 130.0], [100.0, 100.0, 122.5, 122.5], [155.0, 155.0, 165.0, 165.0], [145.0, 145.0, 160.0, 160.0], [111.25, 111.25, 152.5, 152.5], [185.0, 185.0, 195.0, 195.0], [175.0, 175.0, 190.0, 190.0], [215.0, 215.0, 225.0, 225.0], [235.0, 235.0, 245.0, 245.0], [220.0, 220.0, 240.0, 240.0], [205.0, 205.0, 230.0, 230.0], [182.5, 182.5, 217.5, 217.5], [131.875, 131.875, 200.0, 200.0], [45.0, 45.0, 165.9375, 165.9375]], 'dcoord': [[0.0, np.float64(1.0234160496059925), np.float64(1.0234160496059925), 0.0], [0.0, np.float64(1.629877636852292), np.float64(1.629877636852292), np.float64(1.0234160496059925)], [0.0, np.float64(2.2161449413120926), np.float64(2.2161449413120926), 0.0], [np.float64(1.629877636852292), np.float64(3.7272843799267723), np.float64(3.7272843799267723), np.float64(2.2161449413120926)], [0.0, np.float64(2.667664759947838), np.float64(2.667664759947838), 0.0], [0.0, np.float64(3.307799062539864), np.float64(3.307799062539864), np.float64(2.667664759947838)], [0.0, np.float64(3.883666150193121), np.float64(3.883666150193121), np.float64(3.307799062539864)], [np.float64(3.7272843799267723), np.float64(5.398827900685266), np.float64(5.398827900685266), np.float64(3.883666150193121)], [0.0, np.float64(0.7946163872959462), np.float64(0.7946163872959462), 0.0], [0.0, np.float64(1.6499962670989876), np.float64(1.6499962670989876), 0.0], [0.0, np.float64(1.834032139802367), np.float64(1.834032139802367), np.float64(1.6499962670989876)], [np.float64(0.7946163872959462), np.float64(2.7383096147109276), np.float64(2.7383096147109276), np.float64(1.834032139802367)], [0.0, np.float64(3.0720033027310594), np.float64(3.0720033027310594), 0.0], [0.0, np.float64(3.7542086331293056), np.float64(3.7542086331293056), np.float64(3.0720033027310594)], [np.float64(2.7383096147109276), np.float64(4.095089332847157), np.float64(4.095089332847157), np.float64(3.7542086331293056)], [0.0, np.float64(1.249310867745043), np.float64(1.249310867745043), 0.0], [0.0, np.float64(1.5447406553347172), np.float64(1.5447406553347172), np.float64(1.249310867745043)], [0.0, np.float64(1.5198370312540974), np.float64(1.5198370312540974), 0.0], [0.0, np.float64(1.8884514885845634), np.float64(1.8884514885845634), 0.0], [np.float64(1.5198370312540974), np.float64(3.113036398344433), np.float64(3.113036398344433), np.float64(1.8884514885845634)], [0.0, np.float64(3.860547321316396), np.float64(3.860547321316396), np.float64(3.113036398344433)], [np.float64(1.5447406553347172), np.float64(4.90671736941339), np.float64(4.90671736941339), np.float64(3.860547321316396)], [np.float64(4.095089332847157), np.float64(7.524715999865525), np.float64(7.524715999865525), np.float64(4.90671736941339)], [np.float64(5.398827900685266), np.float64(9.776084784410148), np.float64(9.776084784410148), np.float64(7.524715999865525)]], 'ivl': [9, 19, 23, 1, 24, 12, 10, 4, 8, 13, 18, 11, 3, 15, 14, 17, 22, 16, 0, 21, 7, 5, 6, 2, 20], 'leaves': [9, 19, 23, 1, 24, 12, 10, 4, 8, 13, 18, 11, 3, 15, 14, 17, 22, 16, 0, 21, 7, 5, 6, 2, 20], 'color_list': ['C1', 'C1', 'C1', 'C1', 'C1', 'C1', 'C1', 'C1', 'C2', 'C2', 'C2', 'C2', 'C2', 'C2', 'C2', 'C3', 'C3', 'C3', 'C3', 'C3', 'C3', 'C3', 'C0', 'C0'], 'leaves_color_list': ['C1', 'C1', 'C1', 'C1', 'C1', 'C1', 'C1', 'C1', 'C1', 'C2', 'C2', 'C2', 'C2', 'C2', 'C2', 'C2', 'C2', 'C3', 'C3', 'C3', 'C3', 'C3', 'C3', 'C3', 'C3']}
plt.title("Dendrograma - Método Ward")
plt.xlabel("Número de Observación")
plt.ylabel("Distancia")
plt.tight_layout()
plt.show()

# Cortar el árbol para obtener 3 clusters (ajusta este número según el dendrograma)
num_clusters = 3
cluster_labels = fcluster(Z, num_clusters, criterion='maxclust')
df["Cluster"] = cluster_labels
print("\nAsignación de clusters (ordenada por cluster):")

Asignación de clusters (ordenada por cluster):
print(df[["Cluster"] + cols].sort_values("Cluster"))
    Cluster  Sexo  Edad  Entretenido  Compras  Tiempo  Online  Interés
12        1     0    19            2        1       2       4        5
19        1     1    20            2        2       3       4        7
23        1     1    20            3        3       4       4        7
10        1     0    21            1        2       3       2        3
9         1     1    20            3        3       5       5        6
8         1     1    20            2        3       4       4        3
24        1     1    21            1        2       2       4        7
4         1     0    20            1        2       3       6        2
1         1     1    21            2        1       3       5        4
15        2     0    20            3        4       5       4        6
22        2     1    20            4        2       7       1        4
3         2     0    20            4        4       6       3        4
11        2     0    20            5        5       4       4        4
13        2     0    20            4        4       6       1        6
14        2     0    19            6        4       5       1        2
17        2     0    20            3        2       7       4        2
18        2     0    20            4        3       6       2        6
21        3     1    20            5        7       3       1        4
20        3     0    20            5        6       4       1        4
7         3     0    21            7        7       3       6        4
6         3     0    21            5        6       3       1        3
2         3     0    20            7        6       2       1        5
5         3     0    21            6        6       4       3        3
16        3     1    20            4        7       4       2        2
0         3     1    20            6        7       4       2        3
# ===============================
# 6. Métricas de Rendimiento
# (a) Silhouette Score
# ===============================
sil_score = silhouette_score(data_scaled_df, cluster_labels, metric='euclidean')
print("\nSilhouette Score global: {:.3f}".format(sil_score))

Silhouette Score global: 0.224
# (b) Suma de cuadrados intra-cluster (WSS)
def compute_wss(X, labels):
    wss = 0
    for label in np.unique(labels):
        cluster_points = X[labels == label]
        centroid = cluster_points.mean(axis=0)
        wss += np.sum((cluster_points - centroid) ** 2)
    return wss

wss_value = compute_wss(data_scaled, cluster_labels)
print("Suma total de cuadrados intra-cluster (WSS): {:.3f}".format(wss_value))
Suma total de cuadrados intra-cluster (WSS): 98.903

# ===============================
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.cluster.hierarchy import dendrogram, linkage, fcluster
from scipy.spatial.distance import pdist, squareform
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
import statsmodels.formula.api as smf

# --- Suposición: Ya has leído los datos, estandarizado, obtenido clustering y agregado la columna "Cluster". 
# Por ejemplo:
df = pd.read_excel("6.1.Hábitos.25.xlsx")
cols = ["Sexo", "Edad", "Entretenido", "Compras", "Tiempo", "Online", "Interés"]
data = df[cols].copy()

# Estandarizar
scaler = StandardScaler()
data_scaled = scaler.fit_transform(data)
data_scaled_df = pd.DataFrame(data_scaled, columns=cols)

# Clustering Jerárquico
Z = linkage(data_scaled, method='ward')
num_clusters = 3
cluster_labels = fcluster(Z, num_clusters, criterion='maxclust')
cluster_labels = np.array(cluster_labels)  # asegurar que es un array
df["Cluster"] = cluster_labels
data_scaled_df["Cluster"] = cluster_labels

import statsmodels.api as sm  # Importing from statsmodels.api instead of statsmodels.formula.api

# Loop over the columns (variables) to perform ANOVA
for var in cols:
    formula = f"{var} ~ C(Cluster)"
    model = smf.ols(formula, data=df).fit()
    anova_table = sm.stats.anova_lm(model, typ=2)  # Use statsmodels.api here
    print(f"\nANOVA para {var}:")
    print(anova_table)

ANOVA para Sexo:
            sum_sq    df         F    PR(>F)
C(Cluster)    1.25   2.0  2.894737  0.076554
Residual      4.75  22.0       NaN       NaN

ANOVA para Edad:
              sum_sq    df         F    PR(>F)
C(Cluster)  1.054444   2.0  1.839471  0.182516
Residual    6.305556  22.0       NaN       NaN

ANOVA para Entretenido:
               sum_sq    df          F        PR(>F)
C(Cluster)  60.361111   2.0  33.809052  1.951175e-07
Residual    19.638889  22.0        NaN           NaN

ANOVA para Compras:
               sum_sq    df          F        PR(>F)
C(Cluster)  84.071111   2.0  62.112239  8.941876e-10
Residual    14.888889  22.0        NaN           NaN

ANOVA para Tiempo:
               sum_sq    df          F    PR(>F)
C(Cluster)  32.909444   2.0  19.122729  0.000015
Residual    18.930556  22.0        NaN       NaN

ANOVA para Online:
               sum_sq    df         F    PR(>F)
C(Cluster)  21.569444   2.0  5.340106  0.012868
Residual    44.430556  22.0       NaN       NaN

ANOVA para Interés:
               sum_sq    df        F    PR(>F)
C(Cluster)   8.171111   2.0  1.59397  0.225699
Residual    56.388889  22.0      NaN       NaN
# --- Obtener los centroides (usando solo las columnas originales) ---
centroids = data_scaled_df.groupby("Cluster")[cols].mean()
print("\nCentroides (datos estandarizados) por cluster:")

Centroides (datos estandarizados) por cluster:
print(centroids)
             Sexo      Edad  Entretenido  ...    Tiempo    Online   Interés
Cluster                                   ...                              
1        0.544331  0.114677    -1.068344  ... -0.595679  0.752226  0.403793
2       -0.561341 -0.525262     0.181681  ...  1.159722 -0.307729  0.006223
3       -0.051031  0.396250     1.020206  ... -0.489583 -0.538525 -0.460490

[3 rows x 7 columns]
# --- Paso 8: Identificar individuos representativos en cada cluster ---
from collections import defaultdict
cluster_members = defaultdict(pd.DataFrame)
representatives = {}

for label in np.unique(cluster_labels):
    # Filtrar solo filas correspondientes al cluster actual
    indices = data_scaled_df.index[data_scaled_df["Cluster"] == label]
    # Usar únicamente las columnas originales (sin la columna "Cluster")
    cluster_data = data_scaled_df.loc[indices, cols]
    centroid = cluster_data.mean(axis=0).values
    # Calcular la distancia euclidiana de cada observación al centroide
    distances = np.sqrt(np.sum((cluster_data - centroid) ** 2, axis=1))
    temp = pd.DataFrame({"Index": indices, "Distance": distances})
    temp_sorted = temp.sort_values("Distance")
    cluster_members[label] = temp_sorted
    representatives[label] = temp_sorted.iloc[0]
    print(f"\nCluster {label} - Individuo representativo (mínima distancia):")
    print(temp_sorted.iloc[0])

Cluster 1 - Individuo representativo (mínima distancia):
Index       19.000000
Distance     1.551139
Name: 19, dtype: float64

Cluster 2 - Individuo representativo (mínima distancia):
Index       3.000000
Distance    0.579015
Name: 3, dtype: float64

Cluster 3 - Individuo representativo (mínima distancia):
Index       20.000000
Distance     1.418952
Name: 20, dtype: float64
for label in np.unique(cluster_labels):
    print(f"\nCluster {label} - Miembros ordenados por distancia al centroide:")
    print(cluster_members[label])

Cluster 1 - Miembros ordenados por distancia al centroide:
    Index  Distance
19     19  1.551139
8       8  1.589412
23     23  1.803437
1       1  1.841860
9       9  1.856740
24     24  2.287521
4       4  2.592749
10     10  2.726297
12     12  2.825885

Cluster 2 - Miembros ordenados por distancia al centroide:
    Index  Distance
3       3  0.579015
18     18  1.223506
13     13  1.501514
15     15  1.698910
11     11  1.810862
17     17  2.155986
22     22  2.333767
14     14  2.628963

Cluster 3 - Miembros ordenados por distancia al centroide:
    Index  Distance
20     20  1.418952
0       0  1.582239
5       5  1.610928
6       6  1.655912
21     21  1.713046
2       2  1.995000
16     16  2.014747
7       7  2.901573
# --- Paso 9: Clasificar un nuevo individuo y graficarlo ---
# Supongamos un nuevo individuo con los siguientes valores:
new_individual = pd.DataFrame({
    "Sexo": [1],
    "Edad": [20],
    "Entretenido": [6],
    "Compras": [5],
    "Tiempo": [4],
    "Online": [3],
    "Interés": [2]
})
# Estandarizar el nuevo individuo usando el mismo escalador
new_ind_scaled = scaler.transform(new_individual)

# Calcular distancias a cada centroide de los clusters (centroids ya calculado)
distances_to_centroids = {}
for label, row in centroids.iterrows():
    centroid_arr = row.values  # vector con las 7 variables
    dist = np.linalg.norm(new_ind_scaled - centroid_arr)
    distances_to_centroids[label] = dist
    print(f"Distancia del nuevo individuo al centroide del cluster {label}: {dist:.3f}")
Distancia del nuevo individuo al centroide del cluster 1: 3.480
Distancia del nuevo individuo al centroide del cluster 2: 2.906
Distancia del nuevo individuo al centroide del cluster 3: 2.017
    
assigned_cluster = min(distances_to_centroids, key=distances_to_centroids.get)
print(f"\nEl nuevo individuo es asignado al cluster: {assigned_cluster}")

El nuevo individuo es asignado al cluster: 3
# --- Paso 10: Visualización con PCA ---
pca = PCA(n_components=2)
# Realizamos PCA sobre las columnas originales
data_pca = pca.fit_transform(data_scaled_df[cols])
new_pca = pca.transform(new_ind_scaled)
C:\Users\MINEDU~1\AppData\Local\Programs\Python\PYTHON~1\Lib\site-packages\sklearn\utils\validation.py:2739: UserWarning: X does not have valid feature names, but PCA was fitted with feature names
  warnings.warn(
plt.figure(figsize=(10, 6))
plt.scatter(data_pca[:,0], data_pca[:,1], c=cluster_labels, cmap="viridis", alpha=0.7, label="Observaciones")
plt.scatter(new_pca[0,0], new_pca[0,1], color="red", s=200, marker="X", label="Nuevo individuo")
plt.title("Visualización de Clusters (PCA)")
plt.xlabel("Componente Principal 1")
plt.ylabel("Componente Principal 2")
plt.legend()
plt.show()

# ===============================
# 8. Determinar individuos representativos en cada cluster
# (Se calcula la distancia de cada miembro al centroide de su cluster)
# ===============================
from collections import defaultdict
cluster_members = defaultdict(pd.DataFrame)
representatives = {}

for label in np.unique(cluster_labels):
    indices = data_scaled_df.index[df["Cluster"] == label]
    cluster_data = data_scaled_df.loc[indices]
    centroid = cluster_data.mean().values
    distances = np.sqrt(np.sum((cluster_data - centroid) ** 2, axis=1))
    temp = pd.DataFrame({"Index": indices, "Distance": distances})
    temp_sorted = temp.sort_values("Distance")
    cluster_members[label] = temp_sorted
    representatives[label] = temp_sorted.iloc[0]
    print(f"\nCluster {label} - Individuo representativo (mínima distancia):")
    print(temp_sorted.iloc[0])

Cluster 1 - Individuo representativo (mínima distancia):
Index       19.000000
Distance     1.551139
Name: 19, dtype: float64

Cluster 2 - Individuo representativo (mínima distancia):
Index       3.000000
Distance    0.579015
Name: 3, dtype: float64

Cluster 3 - Individuo representativo (mínima distancia):
Index       20.000000
Distance     1.418952
Name: 20, dtype: float64
    
for label in np.unique(cluster_labels):
    print(f"\nCluster {label} - Miembros ordenados por distancia al centroide:")
    print(cluster_members[label])

Cluster 1 - Miembros ordenados por distancia al centroide:
    Index  Distance
19     19  1.551139
8       8  1.589412
23     23  1.803437
1       1  1.841860
9       9  1.856740
24     24  2.287521
4       4  2.592749
10     10  2.726297
12     12  2.825885

Cluster 2 - Miembros ordenados por distancia al centroide:
    Index  Distance
3       3  0.579015
18     18  1.223506
13     13  1.501514
15     15  1.698910
11     11  1.810862
17     17  2.155986
22     22  2.333767
14     14  2.628963

Cluster 3 - Miembros ordenados por distancia al centroide:
    Index  Distance
20     20  1.418952
0       0  1.582239
5       5  1.610928
6       6  1.655912
21     21  1.713046
2       2  1.995000
16     16  2.014747
7       7  2.901573
import numpy as np
# --- Obtener los centroides (usando solo las columnas originales) ---
centroids = data_scaled_df.groupby("Cluster")[cols].mean()
print("\nCentroides (datos estandarizados) por cluster:")

Centroides (datos estandarizados) por cluster:
print(centroids)
             Sexo      Edad  Entretenido  ...    Tiempo    Online   Interés
Cluster                                   ...                              
1        0.544331  0.114677    -1.068344  ... -0.595679  0.752226  0.403793
2       -0.561341 -0.525262     0.181681  ...  1.159722 -0.307729  0.006223
3       -0.051031  0.396250     1.020206  ... -0.489583 -0.538525 -0.460490

[3 rows x 7 columns]
# --- Paso 8: Identificar individuos representativos en cada cluster ---
from collections import defaultdict
cluster_members = defaultdict(pd.DataFrame)
representatives = {}

for label in np.unique(cluster_labels):
    # Filtrar solo filas correspondientes al cluster actual
    indices = data_scaled_df.index[data_scaled_df["Cluster"] == label]
    # Usar únicamente las columnas originales (sin la columna "Cluster")
    cluster_data = data_scaled_df.loc[indices, cols]
    centroid = cluster_data.mean(axis=0).values
    # Calcular la distancia euclidiana de cada observación al centroide
    distances = np.sqrt(np.sum((cluster_data - centroid) ** 2, axis=1))
    temp = pd.DataFrame({"Index": indices, "Distance": distances})
    temp_sorted = temp.sort_values("Distance")
    cluster_members[label] = temp_sorted
    representatives[label] = temp_sorted.iloc[0]
    print(f"\nCluster {label} - Individuo representativo (mínima distancia):")
    print(temp_sorted.iloc[0])

Cluster 1 - Individuo representativo (mínima distancia):
Index       19.000000
Distance     1.551139
Name: 19, dtype: float64

Cluster 2 - Individuo representativo (mínima distancia):
Index       3.000000
Distance    0.579015
Name: 3, dtype: float64

Cluster 3 - Individuo representativo (mínima distancia):
Index       20.000000
Distance     1.418952
Name: 20, dtype: float64
for label in np.unique(cluster_labels):
    print(f"\nCluster {label} - Miembros ordenados por distancia al centroide:")
    print(cluster_members[label])

Cluster 1 - Miembros ordenados por distancia al centroide:
    Index  Distance
19     19  1.551139
8       8  1.589412
23     23  1.803437
1       1  1.841860
9       9  1.856740
24     24  2.287521
4       4  2.592749
10     10  2.726297
12     12  2.825885

Cluster 2 - Miembros ordenados por distancia al centroide:
    Index  Distance
3       3  0.579015
18     18  1.223506
13     13  1.501514
15     15  1.698910
11     11  1.810862
17     17  2.155986
22     22  2.333767
14     14  2.628963

Cluster 3 - Miembros ordenados por distancia al centroide:
    Index  Distance
20     20  1.418952
0       0  1.582239
5       5  1.610928
6       6  1.655912
21     21  1.713046
2       2  1.995000
16     16  2.014747
7       7  2.901573
# --- Paso 9: Clasificar un nuevo individuo y graficarlo ---
# Supongamos un nuevo individuo con los siguientes valores:
new_individual = pd.DataFrame({
    "Sexo": [1],
    "Edad": [20],
    "Entretenido": [6],
    "Compras": [5],
    "Tiempo": [4],
    "Online": [3],
    "Interés": [2]
})
# Estandarizar el nuevo individuo usando el mismo escalador
new_ind_scaled = scaler.transform(new_individual)

# Calcular distancias a cada centroide de los clusters (centroids ya calculado)
distances_to_centroids = {}
for label, row in centroids.iterrows():
    centroid_arr = row.values  # vector con las 7 variables
    dist = np.linalg.norm(new_ind_scaled - centroid_arr)
    distances_to_centroids[label] = dist
    print(f"Distancia del nuevo individuo al centroide del cluster {label}: {dist:.3f}")
Distancia del nuevo individuo al centroide del cluster 1: 3.480
Distancia del nuevo individuo al centroide del cluster 2: 2.906
Distancia del nuevo individuo al centroide del cluster 3: 2.017
    
assigned_cluster = min(distances_to_centroids, key=distances_to_centroids.get)
print(f"\nEl nuevo individuo es asignado al cluster: {assigned_cluster}")

El nuevo individuo es asignado al cluster: 3
# --- Paso 10: Visualización con PCA ---
pca = PCA(n_components=2)
# Realizamos PCA sobre las columnas originales
data_pca = pca.fit_transform(data_scaled_df[cols])
new_pca = pca.transform(new_ind_scaled)
C:\Users\MINEDU~1\AppData\Local\Programs\Python\PYTHON~1\Lib\site-packages\sklearn\utils\validation.py:2739: UserWarning: X does not have valid feature names, but PCA was fitted with feature names
  warnings.warn(
plt.figure(figsize=(10, 6))
plt.scatter(data_pca[:,0], data_pca[:,1], c=cluster_labels, cmap="viridis", alpha=0.7, label="Observaciones")
plt.scatter(new_pca[0,0], new_pca[0,1], color="red", s=200, marker="X", label="Nuevo individuo")
plt.title("Visualización de Clusters (PCA)")
plt.xlabel("Componente Principal 1")
plt.ylabel("Componente Principal 2")
plt.legend()
plt.show()

import matplotlib.pyplot as plt
from sklearn.cluster import AgglomerativeClustering
from sklearn.decomposition import PCA
import seaborn as sns

# Suponiendo que 'data_scaled' son los datos estandarizados
# Clustering jerárquico
hc_model = AgglomerativeClustering(n_clusters=3, linkage="ward")
clusters = hc_model.fit_predict(data_scaled)

# Reducir dimensiones a 2 componentes con PCA
pca = PCA(n_components=2)
data_pca = pca.fit_transform(data_scaled)

# Crear un scatterplot de los clusters
plt.figure(figsize=(8, 6))
sns.scatterplot(x=data_pca[:, 0], y=data_pca[:, 1], hue=clusters, palette="viridis", s=100)
plt.title("Visualización de Clusters (PCA)")
plt.xlabel("Componente Principal 1")
plt.ylabel("Componente Principal 2")
plt.legend(title="Cluster")
plt.show()

# --- Cargar librerías necesarias ---
library(readxl)      # Para leer archivos Excel
library(cluster)     # Para el cálculo del silhouette, etc.
library(factoextra)  # Para visualización del clustering y silhouette
Warning: package 'factoextra' was built under R version 4.4.3
Cargando paquete requerido: ggplot2
Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
library(dplyr)       # Para manejo y resumen de datos

Adjuntando el paquete: 'dplyr'
The following objects are masked from 'package:stats':

    filter, lag
The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
# --- Leer el archivo Excel ---
# Asegúrate de que "6.1.Hábitos.25.xlsx" se encuentre en tu directorio de trabajo
df <- read_excel("6.1.Hábitos.25.xlsx")

# Seleccionar las columnas de interés
cols <- c("Sexo", "Edad", "Entretenido", "Compras", "Tiempo", "Online", "Interés")
data <- df[, cols]

# Verificamos las primeras filas
print("Datos originales:")
[1] "Datos originales:"
print(head(data))
# A tibble: 6 × 7
   Sexo  Edad Entretenido Compras Tiempo Online Interés
  <dbl> <dbl>       <dbl>   <dbl>  <dbl>  <dbl>   <dbl>
1     1    20           6       7      4      2       3
2     1    21           2       1      3      5       4
3     0    20           7       6      2      1       5
4     0    20           4       4      6      3       4
5     0    20           1       2      3      6       2
6     0    21           6       6      4      3       3
cat("Matriz de correlaciones (datos originales):\n")
Matriz de correlaciones (datos originales):
cor_matrix <- cor(data)
print(cor_matrix)
                   Sexo         Edad Entretenido    Compras      Tiempo
Sexo         1.00000000  0.060192927 -0.27386128 -0.1067009 -0.10206207
Edad         0.06019293  1.000000000 -0.04945354  0.1170896 -0.27235806
Entretenido -0.27386128 -0.049453536  1.00000000  0.8181945  0.09938080
Compras     -0.10670085  0.117089603  0.81819447  1.0000000 -0.11057650
Tiempo      -0.10206207 -0.272358058  0.09938080 -0.1105765  1.00000000
Online       0.10050378  0.181488502 -0.44038551 -0.3959571 -0.20515248
Interés      0.23372246  0.001835014 -0.21985196 -0.2722371 -0.09472537
                Online      Interés
Sexo         0.1005038  0.233722458
Edad         0.1814885  0.001835014
Entretenido -0.4403855 -0.219851955
Compras     -0.3959571 -0.272237086
Tiempo      -0.2051525 -0.094725368
Online       1.0000000  0.137876033
Interés      0.1378760  1.000000000
# --- Estandarización de los datos ---
data_scaled <- scale(data)
data_scaled_df <- as.data.frame(data_scaled)
colnames(data_scaled_df) <- cols

# Verificar algunas filas de los datos estandarizados
print("Datos estandarizados:")
[1] "Datos estandarizados:"
print(head(data_scaled_df))
  Sexo      Edad Entretenido     Compras      Tiempo     Online    Interés
1  1.2 -0.288926   1.2049896  1.49709500 -0.05443311 -0.6030227 -0.7560413
2  1.2  1.516862  -0.9859006 -1.45769777 -0.73484692  1.2060454 -0.1463306
3 -0.8 -0.288926   1.7527122  1.00462954 -1.41526074 -1.2060454  0.4633802
4 -0.8 -0.288926   0.1095445  0.01969862  1.30639453  0.0000000 -0.1463306
5 -0.8 -0.288926  -1.5336232 -0.96523231 -0.73484692  1.8090681 -1.3657521
6 -0.8  1.516862   1.2049896  1.00462954 -0.05443311  0.0000000 -0.7560413
# --- Calcular la matriz de distancias (usando los datos estandarizados) ---
d <- dist(data_scaled_df, method = "euclidean")
cat("\nMatriz de distancias (vista parcial, primeras 10 filas):\n")

Matriz de distancias (vista parcial, primeras 10 filas):
print(as.matrix(d)[1:10, 1:10])
          1        2        3        4        5        6        7        8
1  0.000000 4.571554 2.871411 3.157520 4.920970 2.804822 2.937685 3.770114
2  4.571554 0.000000 5.241833 4.032962 3.107091 4.141535 4.353503 4.538109
3  2.871411 5.241833 0.000000 3.592184 5.251662 2.890373 2.531960 3.664562
4  3.157520 4.032962 3.592184 0.000000 3.549151 2.766687 3.244059 3.947526
5  4.920970 3.107091 5.251662 3.549151 0.000000 4.329973 4.626433 4.648754
6  2.804822 4.141535 2.890373 2.766687 4.329973 0.000000 1.489130 2.156377
7  2.937685 4.353503 2.531960 3.244059 4.626433 1.489130 0.000000 3.302299
8  3.770114 4.538109 3.664562 3.947526 4.648754 2.156377 3.302299 0.000000
9  3.183536 2.330087 4.504916 2.833683 2.613767 3.821937 3.984879 4.574984
10 3.696183 2.805316 4.619448 2.819046 3.688627 4.172146 4.653958 4.432122
          9       10
1  3.183536 3.696183
2  2.330087 2.805316
3  4.504916 4.619448
4  2.833683 2.819046
5  2.613767 3.688627
6  3.821937 4.172146
7  3.984879 4.653958
8  4.574984 4.432122
9  0.000000 2.114787
10 2.114787 0.000000
# --- Realizar clustering jerárquico con el método Ward y graficar el dendrograma ---
hc <- hclust(d, method = "ward.D2")
plot(hc, main = "Dendrograma - Método Ward", xlab = "Observación", sub = "")

# --- Cortar el árbol para obtener 3 clusters ---
num_clusters <- 3
clusters <- cutree(hc, k = num_clusters)

# Agregar la variable cluster al dataframe original y a los datos estandarizados
df$Cluster <- clusters
data_scaled_df$Cluster <- clusters

cat("\nAsignación de clusters:\n")

Asignación de clusters:
print(df[, c("Cluster", cols)])
# A tibble: 25 × 8
   Cluster  Sexo  Edad Entretenido Compras Tiempo Online Interés
     <int> <dbl> <dbl>       <dbl>   <dbl>  <dbl>  <dbl>   <dbl>
 1       1     1    20           6       7      4      2       3
 2       2     1    21           2       1      3      5       4
 3       1     0    20           7       6      2      1       5
 4       3     0    20           4       4      6      3       4
 5       2     0    20           1       2      3      6       2
 6       1     0    21           6       6      4      3       3
 7       1     0    21           5       6      3      1       3
 8       1     0    21           7       7      3      6       4
 9       2     1    20           2       3      4      4       3
10       2     1    20           3       3      5      5       6
# ℹ 15 more rows
# --- 7a. Calcular el ancho de silueta ---
sil <- silhouette(clusters, d)
cat("\nSilhouette width global:", mean(sil[, "sil_width"]), "\n")

Silhouette width global: 0.2236486 
fviz_silhouette(sil)
  cluster size ave.sil.width
1       1    8          0.24
2       2    9          0.19
3       3    8          0.24

# --- 7b. Calcular la suma total de cuadrados intra-cluster (WSS) ---
wss <- function(X, labels) {
  unique_labels <- unique(labels)
  total_wss <- 0
  for (label in unique_labels) {
    cluster_data <- X[labels == label, ]
    centroid <- colMeans(cluster_data)
    total_wss <- total_wss + sum(rowSums((cluster_data - centroid)^2))
  }
  return(total_wss)
}
wss_value <- wss(as.matrix(data_scaled_df[, cols]), clusters)
cat("\nSuma total de cuadrados intra-cluster (WSS):", round(wss_value, 3), "\n")

Suma total de cuadrados intra-cluster (WSS): 239.047 
cat("\n=== ANOVA por variable (evaluación de importancia) ===\n")

=== ANOVA por variable (evaluación de importancia) ===
for (var in cols) {
  cat("\nANOVA para", var, ":\n")
  model <- aov(as.formula(paste(var, "~ factor(Cluster)")), data = df)
  print(summary(model))
}

ANOVA para Sexo :
                Df Sum Sq Mean Sq F value Pr(>F)  
factor(Cluster)  2   1.25  0.6250   2.895 0.0766 .
Residuals       22   4.75  0.2159                 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

ANOVA para Edad :
                Df Sum Sq Mean Sq F value Pr(>F)
factor(Cluster)  2  1.054  0.5272   1.839  0.183
Residuals       22  6.306  0.2866               

ANOVA para Entretenido :
                Df Sum Sq Mean Sq F value   Pr(>F)    
factor(Cluster)  2  60.36  30.181   33.81 1.95e-07 ***
Residuals       22  19.64   0.893                     
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

ANOVA para Compras :
                Df Sum Sq Mean Sq F value   Pr(>F)    
factor(Cluster)  2  84.07   42.04   62.11 8.94e-10 ***
Residuals       22  14.89    0.68                     
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

ANOVA para Tiempo :
                Df Sum Sq Mean Sq F value   Pr(>F)    
factor(Cluster)  2  32.91   16.45   19.12 1.54e-05 ***
Residuals       22  18.93    0.86                     
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

ANOVA para Online :
                Df Sum Sq Mean Sq F value Pr(>F)  
factor(Cluster)  2  21.57   10.79    5.34 0.0129 *
Residuals       22  44.43    2.02                 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

ANOVA para Interés :
                Df Sum Sq Mean Sq F value Pr(>F)
factor(Cluster)  2   8.17   4.086   1.594  0.226
Residuals       22  56.39   2.563               
# --- Calcular los centroides (en los datos estandarizados) por cluster ---
centroids <- data_scaled_df %>% group_by(Cluster) %>% summarise(across(everything(), mean))
cat("\nCentroides (datos estandarizados) por cluster:\n")

Centroides (datos estandarizados) por cluster:
print(centroids)
# A tibble: 3 × 8
  Cluster    Sexo   Edad Entretenido Compras Tiempo Online  Interés
    <int>   <dbl>  <dbl>       <dbl>   <dbl>  <dbl>  <dbl>    <dbl>
1       1 -0.0500  0.388       1.00    1.25  -0.480 -0.528 -0.451  
2       2  0.533   0.112      -1.05   -0.911 -0.584  0.737  0.396  
3       3 -0.55   -0.515       0.178  -0.227  1.14  -0.302  0.00610
# --- Identificar individuos representativos en cada cluster ---
representatives <- list()

for(i in unique(clusters)){
  # Filtrar sólo los datos para el cluster actual, usando las columnas originales
  cluster_data <- data_scaled_df[data_scaled_df$Cluster == i, cols]
  centroid <- colMeans(cluster_data)
  distances <- apply(cluster_data, 1, function(x) sqrt(sum((x - centroid)^2)))
  rep_ind <- names(which.min(distances))
  cat("\nCluster", i, "- Individuo representativo (mínima distancia):\n")
  print(paste("Índice:", rep_ind, "con distancia:", min(distances)))
  representatives[[as.character(i)]] <- data_scaled_df[rep_ind, ]
  
  # Opcional: Imprimir todos los miembros ordenados por distancia
  temp <- data.frame(Index = rownames(cluster_data), Distance = distances)
  temp <- temp[order(temp$Distance), ]
  cat("\nMiembros ordenados por distancia al centroide (Cluster", i, "):\n")
  print(temp)
}

Cluster 1 - Individuo representativo (mínima distancia):
[1] "Índice: 21 con distancia: 1.39028300123338"

Miembros ordenados por distancia al centroide (Cluster 1 ):
   Index Distance
21    21 1.390283
1      1 1.550271
6      6 1.578381
7      7 1.622456
22    22 1.678436
3      3 1.954693
17    17 1.974040
8      8 2.842950

Cluster 2 - Individuo representativo (mínima distancia):
[1] "Índice: 20 con distancia: 1.51979994335226"

Miembros ordenados por distancia al centroide (Cluster 2 ):
   Index Distance
20    20 1.519800
9      9 1.557299
24    24 1.767000
2      2 1.804647
10    10 1.819226
25    25 2.241304
5      5 2.540365
11    11 2.671215
13    13 2.768790

Cluster 3 - Individuo representativo (mínima distancia):
[1] "Índice: 4 con distancia: 0.56731615664938"

Miembros ordenados por distancia al centroide (Cluster 3 ):
   Index  Distance
4      4 0.5673162
19    19 1.1987866
14    14 1.4711771
16    16 1.6645851
12    12 1.7742755
18    18 2.1124264
23    23 2.2866152
15    15 2.5758474
# --- Clasificar un nuevo individuo ---
# Definir un nuevo individuo con los siguientes valores:
# Sexo=1, Edad=20, Entretenido=6, Compras=5, Tiempo=4, Online=3, Interés=2
new_ind <- data.frame(Sexo = 1, Edad = 20, Entretenido = 6, Compras = 5, Tiempo = 4, Online = 3, Interés = 2)

# Estandarizar el nuevo individuo utilizando los parámetros de data_scaled
data_means <- attr(data_scaled, "scaled:center")
data_sds   <- attr(data_scaled, "scaled:scale")
new_ind_scaled <- scale(new_ind, center = data_means, scale = data_sds)
new_ind_scaled_vec <- as.numeric(new_ind_scaled)

# Calcular la distancia del nuevo individuo a cada uno de los centroides
centroids_matrix <- as.matrix(centroids[, -1])  # Excluir la columna de Cluster
distances_new <- apply(centroids_matrix, 1, function(centroid) sqrt(sum((new_ind_scaled_vec - centroid)^2)))
cat("\nDistancias del nuevo individuo a cada cluster:\n")

Distancias del nuevo individuo a cada cluster:
print(distances_new)
[1] 1.976008 3.409636 2.846843
assigned_cluster <- as.numeric(names(which.min(distances_new)))
cat("El nuevo individuo se asocia al cluster:", assigned_cluster, "\n")
El nuevo individuo se asocia al cluster:  
# --- Realizar PCA para visualizar los clusters ---
pca_res <- prcomp(data_scaled_df[, cols], center = FALSE, scale. = FALSE)
pca_data <- as.data.frame(pca_res$x[, 1:2])
pca_data$Cluster <- factor(clusters)

# Proyectar el nuevo individuo al espacio PCA
new_pca <- predict(pca_res, newdata = new_ind_scaled)
new_pca <- data.frame(PC1 = new_pca[1], PC2 = new_pca[2])

library(ggplot2)
ggplot(pca_data, aes(x = PC1, y = PC2, color = Cluster)) +
  geom_point(size = 3) +
  geom_point(data = new_pca, aes(x = PC1, y = PC2), color = "red", size = 5, shape = 8) +
  labs(title = "Visualización de Clusters (PCA)",
       x = "Componente Principal 1", y = "Componente Principal 2") +
  theme_minimal()

# Cargar librerías
library(factoextra)

# Realizar clustering jerárquico
hc <- hclust(dist(data_scaled), method = "ward.D2")

# Cortar el dendrograma en 3 clusters
clusters <- cutree(hc, k = 3)

# Añadir los clusters al DataFrame
data_scaled_df$Cluster <- as.factor(clusters)

# Visualización de los grupos en un espacio bidimensional (PCA)
fviz_cluster(list(data = data_scaled, cluster = clusters),
             geom = "point", stand = FALSE,
             show.clust.cent = TRUE,
             ellipse.type = "convex",
             ggtheme = theme_minimal())

# Supongamos que ya tienes un DataFrame llamado "data_scaled_df" con datos estandarizados
# y que has realizado el clustering. Ahora vamos a crear y proyectar un nuevo individuo.

# Datos del nuevo individuo
new_ind <- data.frame(Sexo = 1, Edad = 20, Entretenido = 6, Compras = 5, Tiempo = 4, Online = 3, Interés = 2)

# Estandarizar el nuevo individuo usando los parámetros del dataset original
data_means <- attr(data_scaled, "scaled:center")
data_sds   <- attr(data_scaled, "scaled:scale")
new_ind_scaled <- scale(new_ind, center = data_means, scale = data_sds)

# Realizar PCA
library(FactoMineR)  # Para realizar PCA
Warning: package 'FactoMineR' was built under R version 4.4.3
library(factoextra)  # Para visualización
pca_res <- PCA(data_scaled_df[, cols], graph = FALSE)

# Proyectar al nuevo individuo en el espacio PCA
new_pca <- predict.PCA(pca_res, newdata = as.data.frame(new_ind_scaled))

# Ver los resultados de la proyección
print(new_pca$coord)  # Coordenadas del nuevo individuo en el espacio PCA
        Dim.1     Dim.2     Dim.3    Dim.4      Dim.5
[1,] 1.122929 0.3225323 0.3283062 1.432437 -0.8235621
# Convertir la columna `Sexo` en un factor
df$Sexo <- as.factor(df$Sexo)
# Convertir las coordenadas del nuevo individuo a un DataFrame
coords_new <- as.data.frame(new_pca$coord)
colnames(coords_new) <- c("Dim.1", "Dim.2")  # Asegúrate de nombrar las columnas correctamente
# Graficar los clusters existentes con factoextra
fviz_pca_ind(pca_res,
             geom.ind = "point",      # Mostrar puntos para las observaciones
             col.ind = df$Sexo,       # Usar la columna `Sexo` como colores
             palette = "jco",         # Paleta de colores (discreta para factores)
             repel = TRUE,            # Evitar solapamiento de etiquetas
             ggtheme = theme_minimal()) + 
  geom_point(data = coords_new, 
             aes(x = Dim.1, y = Dim.2), 
             color = "red", size = 4, shape = 8) +  # Nuevo individuo como "X" roja
  labs(title = "Clasificación del nuevo individuo",
       x = "Componente Principal 1", 
       y = "Componente Principal 2")