# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# ## Practica sobre K-Means y GMM

import numpy as np
import pandas as pd
import random
import seaborn as sns; sns.set()
import matplotlib.pyplot as plt
from  sklearn.mixture import GaussianMixture


base_data_or = pd.read_csv('p1_estaturas.csv')

base_data = base_data_or.to_numpy()
#base_data

sns.scatterplot(x="Edad", y="Estatura",  palette="Set1",s=75, data=base_data_or)


def k_predict(k, valores):
    # (x-x*)  (y - y*)
    diferencias = valores[:,None]- k

    # (x-x*)^2 + (y - y*)^2
    suma_cuadrados = np.sum(np.square(diferencias),axis=2)

    # distancia euclidiana sqrt( suma_cuadrados )
    distancia_euc = np.sqrt(suma_cuadrados)

    # seleccion del k con la distancia mas cercana
    assigned = np.argmin(distancia_euc,axis=1)
    return assigned


# +

def kmeans(data,epochs = 5,k = 5,status=False, seed=False):
    if seed:
        random.seed(101)
    j = np.array([])
    total = len(data)
    # seleccion aleatoria de elementos como k iniciales
    randomlist = random.sample(range(0, total-1), k)
    listk = np.array([data[i] for i in randomlist])
    # registro para saber donde iniciaron los k
    init_k =  np.copy(listk)
    
    for i in range(epochs):
        # (x-x*)  (y - y*)
        diferencias = data[:,None]- listk

        # (x-x*)^2 + (y - y*)^2
        suma_cuadrados = np.sum(np.square(diferencias),axis=2)

        # distancia euclidiana sqrt( suma_cuadrados )
        distancia_euc = np.sqrt(suma_cuadrados)

        # seleccion del k con la distancia mas cercana
        assigned = np.argmin(distancia_euc,axis=1)
        #print(assigned)

        # sumatoria de las distancias minimas de cada elemento 
        #   :distancia_euc tiene todas las k-esimas distancias, 
        #   por eso se toma el minimo. en este caso
        j = np.append(j,sum(np.min(distancia_euc,axis=1))/total)
        if status:
            print(j[i])
        # reasignacion de k
        for i in range(k):
            # filtro del los datos para cada k
            subgrupo = data[assigned==i]
            # nuevo centroide de k
            listk[i] = np.average(subgrupo,axis=0)
    #print(j)
    
    return (listk,init_k,j, assigned)


# -

# ### Con los datos crudos

ks,k_init,j,assigned = kmeans(base_data, epochs = 5,k = 5,status=True, seed=False)
print(ks)
print('J: ',j)

# #### Grafica J

plt.plot(j)

# ##### Grafica de clusters
# Se prepresenta el universo de elementos agrupados por colores y por medio de X los centroides definidos

base_data_or['k'] = assigned
sns.scatterplot(x="Edad", y="Estatura",  palette="Set1",s=75,
                     hue="k", data=base_data_or)
# definicion de k
sns.scatterplot(x=ks[:,1], y=ks[:,0],  marker="x",s=100)
# puntos iniciales
#sns.scatterplot(x=init_k[:,1], y=init_k[:,0],  marker="s",s=75,color=".2" )

# ### Escalado de datos

esc_data = (base_data - np.min(base_data, axis=0))/ ( np.max(base_data, axis=0) - np.min(base_data, axis=0) )
esc_data[0:3]

# #### k=5

ks5,k_init5,j5,assigned5 = kmeans(esc_data, epochs = 5,k = 5,status=True,seed=False)
print(ks5)
print('J: ',j5)

# #### Grafica J

plt.plot(j5)

sns.scatterplot(x=esc_data[:,1], y=esc_data[:,0],  palette="Set1",s=75,
                     hue=assigned5)
# definicion de k
sns.scatterplot(x=ks5[:,1], y=ks5[:,0],  marker="x",s=100)
# puntos iniciales
#sns.scatterplot(x=init_k[:,1], y=init_k[:,0],  marker="s",s=75,color=".2" )

# En base diversas ejecuciones y por el medio elbow se estima que un buen k seria de 2, considerando que el algoritmo converve en menos de 5 epochs.
#
# Aunque en varias ocuaciones elbow sugiere hacer un solo grupo; pero seria lo mismo que no hacer nada por lo que se deja la opcion de crear almenos 2 grupos

# #### k=2

ks2,k_init2,j2,assigned2 = kmeans(esc_data, epochs = 5,k = 2,status=True,seed=False)
print('J: ',j2)
plt.plot(j2)

sns.scatterplot(x=esc_data[:,1], y=esc_data[:,0],  palette="Set1",s=75,
                     hue=assigned2)
# definicion de k
sns.scatterplot(x=ks2[:,1], y=ks2[:,0],  marker="x",s=100)

# #### k=3

ks3,k_init3,j3,assigned3 = kmeans(esc_data, epochs = 5,k = 3,status=False,seed=False)
sns.scatterplot(x=esc_data[:,1], y=esc_data[:,0],  palette="Set1",s=75,hue=assigned3)
print('J: ',j3)

# #### k=6

ks6,_,_,assigned6 = kmeans(esc_data, epochs = 5,k = 6,status=False,seed=False)
sns.scatterplot(x=esc_data[:,1], y=esc_data[:,0],  palette="Set1",s=75,hue=assigned6)

# #### k=4

ks4,_,_,assigned4 = kmeans(esc_data, epochs = 5,k = 4,status=False,seed=False)
sns.scatterplot(x=esc_data[:,1], y=esc_data[:,0],  palette="Set1",s=75,hue=assigned4)

# ### GMM

gmm2 = GaussianMixture(n_components=2).fit(esc_data)
k_gmm2 = gmm2.predict(esc_data)
sns.scatterplot(x=esc_data[:,1], y=esc_data[:,0], hue=k_gmm2,  palette="Set1",s=75)

# #### k=5

gmm5 = GaussianMixture(n_components=5).fit(esc_data)
k_gmm5 = gmm5.predict(esc_data)
sns.scatterplot(x=esc_data[:,1], y=esc_data[:,0], hue=k_gmm5,  palette="Set1",s=75)

# #### k=3

gmm3 = GaussianMixture(n_components=3).fit(esc_data)
k_gmm3 = gmm3.predict(esc_data)
sns.scatterplot(x=esc_data[:,1], y=esc_data[:,0], hue=k_gmm3,  palette="Set1",s=75)

# ## Conclusiones

# Para la realizacion de los clustes fue importante realizar el escalado de los datos ya que la mayoria de los valores de estatura no superan el valor de 2 y los de edad estan alrededor de 30.  Antes de esto los grupos se separaban de forma vertical; depues del escalado de los datos el patron cambio.
#
# <img src="k_comparacion_data.PNG" width="650px">

# Segun el k que se defina se logran diferentes resultados; para el caso de k=3, los grupos se pueden interpretar como:
#
# 1. Los de edad mas joven con alturas altas, 
# 2. Los de edad media con altura baja
# 3. Los de edad mayor

sns.scatterplot(x=esc_data[:,1], y=esc_data[:,0],  palette="Set1",s=75,hue=assigned3)

# Debido a la naturaleza de los datos los clusters no son tan claros debido a que sabemos que la edad no esta relacionada directamente con la edad, independientemente de la altura estos llegaran a una dada mayor.  Por esta misma razon los cluster convergen de forma diferente en distintas ejecuciones.  A la vez que los datos convergen en pocas iteraciones por esta misma razon y la cantidad de datos.

# ##### Con respecto al metodo del codo

# Debido a que los resultados no son consistentes en cada ejecucion la representacion de J varia para estos datos entre 1 y 3 indicando que k esta funciona para estos datos.
#
# La seleccion de k debe de ser 1, 2 o 3; pero no mas de eso.  En base a estos datos no se logra tener mas variaciones.  A pesar de que es posible calcular grupos para k>3 la cohesion de los grupos no es tanta como para que sean significativos.  

# ##### Estimacion 
# Datos

# +
pruebas = pd.read_csv('p1_pruebas.csv')
pruebas = pruebas.to_numpy()

# se escalan los datos dentro de la misma escala que los datos de entrenamiento
pruebas = (pruebas - np.min(base_data, axis=0))/ ( np.max(base_data, axis=0) - np.min(base_data, axis=0) )
pruebas[0:3]
# -

# Predicciones de ***GMM*** para un modelos de k = 3

gmm3.predict(pruebas)

# Preciccion de probabilidades de pertenecia a cada grupo segun *GMM*

gmm3.predict_proba(pruebas)

# Prediccion de **k-means** manual de k=3

k_predict(ks3, pruebas)

# Debido a que el proceso de inicializacion es aleatorio los grupos de cada uno no necesariamente corresponden a otra ejecucion.  En otras palabras a pesar que de se creen los mismo *clusters* no necesariamente seran en el mismo orden; es posible que k1 de una ejecucion sea diferente a otro k1.
#
# En este caso lo que vemos es que 
# `gmm3.predict(pruebas)` genera `[0, 1, 2, 2, 2, 2, 2, 2]`
#
# `k_predict(ks3, pruebas)` genera `[1, 1, 2, 2, 1, 0, 0, 2]`
#
# Se corrio el modelo a manera de crear en el mismo orden los clusters para tener un mejor parametro de comparacion.  Como se ve en la siguientes graficas, los clustres estan creados de forma diferente

# GMM con datos de prueba
sns.scatterplot(x=esc_data[:,1], y=esc_data[:,0], hue=k_gmm3,  palette="Set1",s=75)
sns.scatterplot(x=pruebas[:,1], y=pruebas[:,0],  marker="x",s=100)

# knn con datos de prueba
sns.scatterplot(x=esc_data[:,1], y=esc_data[:,0],  palette="Set1",s=75,hue=assigned3)
sns.scatterplot(x=pruebas[:,1], y=pruebas[:,0],  marker="x",s=100)



# ##### Prediccion con k=5
# En este caso si se logran grupos diferentes, en la grafica anterior de GMM con k=5 se persive que los grupos no son uniformes

print(gmm5.predict(pruebas))
print(k_predict(ks5, pruebas))

gmm5.predict_proba(pruebas)



# # Parte 2 
# ## Deteccion de Anomalias

from scipy.stats import multivariate_normal
from sklearn.metrics import f1_score

# para quitar la notacion cientifica
np.set_printoptions(suppress=True)

base_data = pd.read_csv('p1_estaturas.csv').to_numpy()
pruebas = pd.read_csv('p1_pruebas.csv').to_numpy()
anomalias = pd.read_csv('p1_anomalias.csv').to_numpy()
prue_y = np.zeros(len(pruebas))
anom_y = np.ones(len(anomalias))

data_anomalias = np.append(base_data,anomalias, axis=0)
data_pruebas = np.append(base_data,pruebas, axis=0)
#data_pruebas.shape

def estimateGaussian(dataset):
    mu = np.mean(dataset, axis=0)
    sigma = np.cov(dataset.T)
    return mu, sigma



def multivariateGaussian(dataset,mu,sigma):
    p = multivariate_normal(mean=mu, cov=sigma)
    return p.pdf(dataset)



def selectThresholdByCV(probs,gt):
    best_epsilon = 0
    best_f1 = 0
    f = 0
    stepsize = (max(probs) - min(probs)) / 2000
    #print(stepsize)
    epsilons = np.arange(min(probs),max(probs),stepsize)
    for epsilon in np.nditer(epsilons):
        #print(epsilon)
        predictions = (probs < epsilon) 
        #print(predictions)
        f = f1_score(gt, predictions,average='binary')
        #print(f,epsilon)
        #print('------')
        if f > best_f1:
            #print('----mejor')
            best_f1 = f
            best_epsilon = epsilon
    
    return best_f1, best_epsilon


# Pruebas con datos **normales + valtest**

mu, sigma = estimateGaussian(data_pruebas)
p = multivariateGaussian(data_pruebas,mu,sigma)
print(mu, sigma)
#print(p)

p_cv = multivariateGaussian(pruebas,mu,sigma)
fscore, ep = selectThresholdByCV(p_cv,prue_y)
#print(fscore,ep)

# seleccion de anomalias
outliers = np.asarray(np.where(p < ep))
outliers

# Para este caso los datos no presentan anomalias

plt.figure()
plt.plot(data_pruebas[:,1],data_pruebas[:,0],'bx')
plt.plot(data_pruebas[outliers,1],data_pruebas[outliers,0],'ro')
plt.show()

# Pruebas con datos **normales + anomalias**

mu, sigma = estimateGaussian(data_anomalias)
p = multivariateGaussian(data_anomalias,mu,sigma)
print(mu, sigma)
#print(p)

p_cv = multivariateGaussian(anomalias,mu,sigma)
fscore, ep = selectThresholdByCV(p_cv,anom_y)
#print(fscore,ep)
print(ep)

# seleccion de anomalias
outliers = np.asarray(np.where(p < ep))
outliers

plt.figure()
plt.plot(data_anomalias[:,1],data_anomalias[:,0],'bx')
plt.plot(data_anomalias[outliers,1],data_anomalias[outliers,0],'ro')
plt.show()

En este caso se encuentran las anomalias con los datos

data_anomalias[outliers]

# ### Conclusiones - Deteccion de Anomalias
#
# Para la deteccion de anomalias resulto necesario entrenar el modelo con datos normales y anomalos para que puedan ser considerados al momento de la seleccion de epsilon.  Con esto ya se podria tener para posibles casos anomalos adicionales.
#
# A pesar que no implica mucho codigo gracias a ciertas librerias, requiere cuidado la estructura de entrenamiento para una definicion apropiada de epsilon y el calculo de las probabilidades de los elementos a ser filtrados.



StarcoderdataJupyterScriptsDedupFiltered