<jupyter_start><jupyter_text>Importar dataset<jupyter_code># Leer los datos
data = pd.read_csv('data.csv')

# Se eliminan los valores nulos
data = data.dropna()

# Mostar las primeros 5 filas
data.head()<jupyter_output><empty_output><jupyter_text>Como se puede observar las desviaciones estandar no se encuentran en un mismo rango. Por lo que se hace necesario re escalar los datos. Se utilizará Normalizer.  Rescalamiento de los datos<jupyter_code>def reescalamiento(df):
    """
    reescalamiento: Esta función reescala el dataset usando XXX.

    Parameters
    ----------
    df: DataFrame para normalizar.

    Returns
    -------
    DataFrame normalizado.
    """
    from sklearn.preprocessing import Normalizer
    """Esta función reescala todo el dataset"""

    # Eliminar columna 'CUST_ID'
    df.drop(['CUST_ID'], axis=1, inplace=True)

    # Reescalar el dataframe
    scaler = Normalizer().fit_transform(df.values)

    df_normalizada = normalize(scaler)
    df_normalizada = pd.DataFrame(df_normalizada)

    # Renombrar las columnas
    df_normalizada.columns = data.columns

    return df_normalizada
df_normalizada = reescalamiento(data)
df_normalizada.head()<jupyter_output><empty_output><jupyter_text>Estadística de HopkinsPrueba la aleatoriedad espacial de los datos e indica la tendencia de agrupación o lo bien que los datos pueden ser agrupados* 0,5 o menos -->  los datos están distribuidos uniformemente* 0.77 - 0.99 --> alta tendencia a los cluster<jupyter_code>from sklearn.neighbors import NearestNeighbors
from random import sample
from numpy.random import uniform
import numpy as np
from math import isnan


def hopkins(X):
    """
    hopkins: El estadístico de Hopkins es un camino para medir la tendencía de clusterización de un dataset.

    Parameters
    ----------
    X: DataFrame normalizado para ser evaluado por la estadística de Hopkins.

    Returns
    -------
    Puntaje que corresponde al valor estadístico de Hopkins.
    """
    d = X.shape[1]
    # d = len(vars) # columns
    n = len(X)  # rows
    m = int(0.1 * n)
    nbrs = NearestNeighbors(n_neighbors=1).fit(X.values)

    rand_X = sample(range(0, n, 1), m)

    ujd = []
    wjd = []
    for j in range(0, m):
        u_dist, _ = nbrs.kneighbors(uniform(np.amin(X, axis=0), np.amax(
            X, axis=0), d).reshape(1, -1), 2, return_distance=True)
        ujd.append(u_dist[0][1])
        w_dist, _ = nbrs.kneighbors(
            X.iloc[rand_X[j]].values.reshape(1, -1), 2, return_distance=True)
        wjd.append(w_dist[0][1])

    H = sum(ujd) / (sum(ujd) + sum(wjd))
    if isnan(H):
        print(ujd, wjd)
        H = 0

    return H
# Comprobar estadisticamente si la data si se puede clusterizar
hopkins(df_normalizada)<jupyter_output><empty_output><jupyter_text>Se puede concluir que los datos tienen una alta disposición estadísticamente para clusterizar  K meansK optimizado : Para escoger al mejor número *K* se utiliza la metodologías del mejor **valor de la silueta**, este valor mide cuán similar es un punto a su propio cluster (cohesión) en comparación con otros cluster (separación). Además se tiene el cuenta la métrica de **<NAME>** que es una medida de similitud promedio de cada cluster con su cluster más similar, donde la similitud es la relación entre las distancias dentro del cluster y las distancias entre los clusters. La puntuación mínima es cero, y los valores más bajos indican una mejor agrupación.<jupyter_code>def number_k(df_normalizada):
    """
    number_k: Esta función encuentra el valor de k optimizado a través del valor de silueta. 
    
    Parameters
    ----------
    df_normalizada: DataFrame normalizado para ser evaluado por la estadística de Hopkins.
    
    Returns
    -------
    Gráfico del número óptimo de clusters.
    """
    
    for i in range(1, 11):
        kmeans = KMeans(n_clusters = i, init="k-means++", n_init=100, max_iter=300, random_state=4444) 
        kmeans.fit(df_normalizada)
    
    kmeans_per_k = [KMeans(n_clusters=k, init="k-means++", n_init=100, max_iter=300, random_state=4444).fit(df_normalizada)
                for k in range(1, 10)]
    
    silhouette_score(df_normalizada, kmeans.labels_)

    silhouette_scores = [silhouette_score(df_normalizada, model.labels_)
                         for model in kmeans_per_k[1:]]

    # Gráficar el score de silhoutee
    rcParams['figure.figsize'] = 16, 5
    _ = plt.plot(range(2, 10), silhouette_scores, "bo-", color='blue',
                 linewidth=3, markersize=8, label='Silhoutee curve')
    _ = plt.xlabel("$k$", fontsize=14, family='Arial')
    _ = plt.ylabel("Silhouette score", fontsize=14, family='Arial')
    _ = plt.grid(which='major', color='#cccccc', linestyle='--')

    _ = plt.title('Silhoutte curve for predict optimal number of clusters', family='Arial', fontsize=14)

    # Calcular el número óptimo de clusters
    k = np.argmax(silhouette_scores) + 2

    # Dibujar una línea vertical para marcar el número óptimo de clusters
    _ = plt.axvline(x=k, linestyle='--', c='green', linewidth=3,
                    label='Optimal number of clusters ({})'.format(k))

    _ = plt.scatter(k, silhouette_scores[k-2], c='red', s=400)

    _ = plt.legend(shadow=True)

    _ = plt.show()
    
    for i in range(2, 17):
        kmeans_labels=KMeans(n_clusters=i,random_state=1).fit_predict(df_normalizada)
        print(f'Davies Bouldin Score {i}: {metrics.davies_bouldin_score(df_normalizada,kmeans_labels).round(3)}')
    return _

plot_k = number_k(df_normalizada)<jupyter_output><empty_output><jupyter_text>Tomando como referencia los valores silueta se concluye que el número de clusters óptimo es de **5**.<jupyter_code>def train_feautures(df_normalizada):
    """
    train_feautures: Esta función entrena el modelo utilizando isomap para reducir la data y kmeans para clusterizar.
    
    Parameters
    ----------
    df_normalizada: DataFrame normalizado.
    
    Returns
    -------
    Array que corresponde a los clusters de cada cliente del dataset.
    """
    
    # Reducir la data con isomap 2 componentes y 10 neighbors
    isomap = Isomap(n_components=2, n_neighbors = 10)
    df_reducida = isomap.fit_transform(df_normalizada)

    # Instanciar k means
    kmeans = KMeans(n_clusters=5, init="k-means++", n_init=10, max_iter=300, random_state=4444) 

    # Convertir el array en dataframe
    df_reducida = pd.DataFrame(df_reducida)
    kmeans.fit_predict(df_reducida.to_numpy())

    df_reducida["cluster"] = kmeans.labels_

    print(df_reducida["cluster"].value_counts())
    sns.pairplot(df_reducida,hue='cluster',palette='Dark2',diag_kind='kde')
    
    return kmeans.labels_
# Normalizer con 5 clusters
clusters = train_feautures(df_normalizada)
def features_impotances(df_normalizada, clusters):
    """
    features_impotances: Esta función encuentra las variables más importantes aplicando el clasificador de Random Forest.
                         Posteriormente determina la precisión del clasificador para encontrar las features más importantes.
                         
    Parameters
    ----------
    df_normalizada: DataFrame normalizado.
    clusters: Array con los clusters de cada cliente. 
    
    Returns
    -------
    None.
    """
    
    df_normalizada['cluster'] = clusters
    
    # y hace referencia a la columna de los clusters
    X, y = df_normalizada.iloc[:,:-1], df_normalizada.iloc[:,-1]
    
    # Separar la data con un 80% de entrenamiento y 20% de prueba
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=4444)
    
    # Crear un clasificador de random forest
    clf = RandomForestClassifier(n_estimators=100, random_state=4444, n_jobs=-1)
    
    # Entrenar el clasificador 
    clf.fit(X_train, y_train)
    
    # Crear un objeto selector que utilizará el clasificador de random forest para identificar
    # características que tienen una importancia de más de 0.06
    sfm = SelectFromModel(clf, threshold=0.06)

    # Entrenar el selector
    sfm.fit(X_train, y_train)

    # Imprimir los nombre de las columnas más importantes
    i = 1
    for feature_list_index in sfm.get_support(indices=True):
        print(f'{i}: {df_normalizada.columns[feature_list_index]}')
        i += 1
        
    # Aplicar el clasificador con todas las funciones a los datos de prueba
    y_pred = clf.predict(X_test)

    # Imprimir la precisión del modelo de entrenado para detectar las features mas importantes
    print(f'La precisión es: {accuracy_score(y_test, y_pred).round(3)}')

    return None
features_imp = features_impotances(df_normalizada, clusters)

column_imp = ['BALANCE', 'PURCHASES', 'CASH_ADVANCE', 'CREDIT_LIMIT',
               'PAYMENTS']
final_data = data.copy()
final_data = modelo_final[column_imp]
final_data.head()
def train_model(final_data):
    """
    train_model: Esta función entrena el modelo con las columnas más importantes. Finalmente guarda el modelo
                 para poder utilizar posteriormente. 
    """

    # Instanciar k means
    kmeans = KMeans(n_clusters=5, init="k-means++", n_init=10, max_iter=300, random_state=4444) 

    # Convertir el array en dataframe
    df = pd.DataFrame(final_data)
    kmeans.fit_predict(df.to_numpy())
    
    try:
            joblib.dump(kmeans, 'model.joblib')
            print("Saved correctly!")
    except:
            print("There is a problem to save the model, check on documentation")
    
    return kmeans.labels_
model = train_model(final_data)
model<jupyter_output>Saved correctly!<jupyter_text>Test model<jupyter_code>def test_model(data_test):
    """
    test_model: Esta función carga el modelo y predice nueva data
    """
    clf_model = joblib.load('model.joblib')
    result = clf_model.predict(data_test)[0]
    return result
data_test = test_model(np.array([[78,5449,43,996,14323]]))
data_test<jupyter_output><empty_output><jupyter_text>Guardar datos<jupyter_code># Normalizer con 5 clusters
modelo_final = data.copy()
modelo_final = modelo_final[column_imp]
modelo_final.to_csv('final_data.csv')
modelo_final["cluster"] = model
modelo_final.head(2)
modelo_final.to_csv('cluster.csv')
modelo_final.cluster.value_counts()
data = pd.read_csv('final_data.csv')
data = data.drop(['Unnamed: 0'], axis=1)
data = data.sort_index(axis=0)
data.<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10247725953164312, 'fraction_numerical': 0.015676407973679116, 'mean_word_length': 3.4798439531859557, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '330889', 'license': 'MIT', 'n_tokens_mistral': 3634, 'n_tokens_neox': 3529, 'n_words': 913, 'path': 'segmentation_credit_card/customer_segmentation.ipynb', 'repo_name': 'cbarros7/ML_apps', 'size': '10342'}