Detrás de los motores de recomendación, las redes sociales y el reconocimiento de voz existen conceptos sorprendentemente accesibles. Uno de ellos es el agrupamiento jerárquico, un algoritmo que construye grupos de datos partiendo de una idea simple: encontrar los puntos más cercanos y unirlos de forma iterativa hasta revelar la estructura oculta en la información.
¿Cómo funciona el agrupamiento jerárquico?
El algoritmo parte de un supuesto directo: cada dato es, inicialmente, un grupo individual [0:12]. A partir de ahí, calcula las distancias entre todos los puntos, identifica la distancia más pequeña y une esos dos puntos en un nuevo clúster. Este proceso se repite de manera iterativa: en cada paso se evalúan las distancias entre los grupos existentes y se fusionan los más cercanos, hasta que finalmente queda un solo grupo [0:30].
Podría parecer que tener un único grupo al final no aporta valor. Sin embargo, lo realmente útil no es el grupo final, sino el objeto que se produce durante el proceso: el dendrograma.
¿Qué es un dendrograma y por qué es importante?
Un dendrograma es una representación gráfica que muestra las relaciones entre cada individuo y entre cada grupo conforme se van construyendo [0:48]. Funciona como un árbol invertido donde cada rama indica qué elementos se unieron y en qué momento del proceso.
Permite visualizar relaciones individuales entre datos.
Muestra cómo los grupos se fusionan paso a paso.
El output principal del algoritmo es esta gráfica, no el grupo completo [1:15].
Gracias al dendrograma, es posible decidir en qué nivel "cortar" el árbol para obtener el número de grupos que mejor represente los datos.
¿Qué métricas de distancia se pueden utilizar?
Dado que el algoritmo se basa en encontrar distancias entre puntos, elegir la métrica adecuada es fundamental [1:30]. Entre las opciones disponibles están:
Distancia euclidiana: la línea recta entre dos puntos en el espacio.
Distancia de Manhattan: suma de las diferencias absolutas en cada dimensión.
Distancia de Minkowski: una generalización que incluye las dos anteriores.
Cada métrica tiene ventajas y desventajas según la naturaleza de los datos, por lo que la elección impacta directamente en los resultados del agrupamiento.
¿Cuáles son los métodos de enlace entre grupos?
Además de la métrica, es necesario definir cómo se mide la distancia entre grupos ya formados. Existen al menos tres métodos principales [1:50]:
Single linkage: toma los puntos más cercanos entre dos grupos. Tiende a generar cadenas alargadas de datos.
Complete linkage: toma los puntos más lejanos entre grupos. Produce clústers más compactos.
Average linkage: calcula el promedio de todos los puntos de cada grupo y mide la distancia entre esas medias. Ofrece un balance entre los dos métodos anteriores.
La combinación de métrica de distancia y método de enlace define el comportamiento completo del algoritmo, lo que hace posible ajustarlo según el problema que se quiera resolver.
¿Cómo practicar el agrupamiento jerárquico con la técnica de Feynman?
Una forma efectiva de consolidar este conocimiento es aplicar la técnica de Feynman [2:28]. El proceso consiste en tres pasos:
Comprender el concepto a nivel teórico.
Generar un programa que implemente el algoritmo desde cero, identificando las dificultades reales.
Revisar implementaciones profesionales en librerías como Scikit-learn para entender cómo personas con amplia experiencia han resuelto esos mismos problemas [2:40].
Este enfoque permite pasar de la comprensión superficial a un dominio real del algoritmo. Al escribir tu propia función de agrupamiento jerárquico, descubrirás los retos de calcular distancias, gestionar la fusión de grupos y construir el dendrograma de forma eficiente.
Si ya pusiste en práctica tu implementación, comparte tu función y cuéntanos qué método de enlace y qué métrica de distancia elegiste.
Después de largas horas de trabajo, terminé finalmente con el reto. Este script de Python genera una lista aleatoria de 10 vectores positivos y arma los clusters siguiendo las reglas del agrupamiento jerárquico, mostrando como va quedando cada cluster en consola. Además, si tienen instalado bokeh, grafica los puntos en el plano cartesiano. El código:
import random
import math
from bokeh.plotting import figure, show
classVector:def__init__(self, x, y): self.x = x
self.y = y
defget_coordinates(self):return(self.x, self.y)def__str__(self):returnf'{self.x}#{self.y}'defgenerate_random_vectors(): vectors =[]for _ inrange(10): vector = Vector(random.randint(0,10), random.randint(0,10)) vectors.append(vector)return vectors
# We can use euclidian distance because we are dealing with a cartesian planedefget_euclidian_distance(vector_a, vector_b):return math.sqrt((vector_a.x - vector_b.x)**2+(vector_a.y - vector_b.y)**2)defrun(): vectors = generate_random_vectors()# Generating a graph of the vectors with bokeh x_points =[vector.x for vector in vectors] y_points =[vector.y for vector in vectors] p = figure(plot_width=400, plot_height=400)# add a circle renderer with a size, color, and alpha p.circle(x_points, y_points, size=20, color="navy", alpha=0.5)# show the results show(p) clusters =[]# First, I define a first cluster formed by a first random vector initial_vector = random.choice(vectors) clusters.append([initial_vector]) vectors.remove(initial_vector)# I'll implement memoization with a dict of distances distances ={}# I'll work with the first cluster of my clusters list cluster = clusters[-1][::]while vectors: closest_relationship =''for vector_tracked in cluster:for vector in vectors:iff'{vector_tracked}*{vector}'notin distances.keys(): distance = get_euclidian_distance( vector_tracked, vector) distances[f'{vector_tracked}*{vector}']= distance
if closest_relationship:if distances[closest_relationship]> distances[f'{vector_tracked}*{vector}']: closest_relationship =f'{vector_tracked}*{vector}'else: closest_relationship =f'{vector_tracked}*{vector}' next_vector_x =int(closest_relationship.split('*')[1].split('#')[0]) next_vector_y =int(closest_relationship.split('*')[1].split('#')[1])for vector in vectors:if vector.x == next_vector_x and vector.y == next_vector_y: cluster.append(vector) vectors.remove(vector) clusters.append(cluster[::])breakfor cluster in clusters:for vector in cluster:print(vector)print('\n')print('*'*20)print('\n')if __name__ =="__main__": run()
Muchas gracias por el aporte!
Faltó mencionar al menos tres cosas super importantes relacionadas con el agrupamiento jerárquico.
Este enfoque es "deterministico", es decir, que a menos que cambiemos la métrica, siempre vamos a obtener el mismo dendogram.
la complejidad del algoritmo es del orden O(n^2), lo cual hace que sea muy lento a medida que aumente la dimensionalidad de los datos. por eso se tiende a usar otros métodos como Kmeans.
Si la cantidad de datos es muy grande, el aspecto visual comienza a volverse confuso y poco atractivo.
johanR. Man usted es muy brutal. Veo sus comentarios en varios de los cursos que he tomado. Se nota que sabe mucho de esto. Gracias por estar comentando. :D
Gran aporte, realmente los punto son supremamente importantes. Saber el segundo hace la diferencia para saber que metodo usar.
La técnica de Feynman
Paso 1. Elige el concepto que quieres entender.
Puedes anotarlo como título en una página en blanco.Paso 2. Pretende que le estás enseñando ésta idea a alguien más.
Explica (escribí) al concepto a ti mismo como si fuera que estas enseñando a un niño, utilizando palabras simples en vez de jerga técnica.Paso 3. Si no puedes explicarlo bien, entonces vuelve al libro. Cada vez que te atasques, vuelve nuevamente al material y re-aprende esa parte que no puedes explicar bien.Paso 4. Simplifica tu explicación y crea analogías. El objetivo de esta técnica es que puedas describir el concepto en palabras simples y claras. Si tardas mucho en explicarlo tienes que simplificarlo aún mas, usando analogías si es necesario.
Entro en los comentarios después de solo haber sido capaz de generar los puntos aleatorios y no conseguir ni calcular la distancia mínima entre puntos y me encuentro con soluciones que no se me hubieran ocurrido en años... es verdaderamente desesperante descubrir que, después de haber hecho no sé cuántos cursos de DS y haber entendido perfectamente todo, no soy capaz de implementar un programa por mí mismo...
¿Soy el único que se siente así? ¿Cómo puedo aprender a utilizar mis conocimientos sin tener que romperme la cabeza en ejercicios imposibles sin ayuda de nadie? Aquellos que han logrado implementar su programa solos, ¿han hecho alguna carrera o curso de formación o son gente que se ha formado solo en Platzi?
(Pido por favor que nadie venga con el rollo de "esfuérzate y lo lograrás" porque son comentarios que me desesperan todavía más, puesto que yo también me esfuerzo mucho y no me parece un problema de dedicación)
Gracias y mucha suerte a todos
Hola, te entiendo, estoy en las mismas, la única forma es leer los códigos de los compañeros y plantearte retos, y practicar y practicar.
Un concejo por parte de Pablo Trinidad (Experto en Computer science y Ex trabajador de Google) es que si después de varias horas no logras obtener la solución a un algoritmo o a algún problema, mira las soluciones en este caso de tus compañeros e impleméntalo. Eso ayuda a aprender y ganar experiencia para luego saber como enfrentar un problema parecido. Es una buena manera de aprender, yo lo he aplicado y me ha ayudado bastante.
Lo logré después de pensar mucho y también de algo de investigación de cómo se usa Bokeh, aunque creo que gráficamente podría mejorar.
Terminal:
Gráfica:
Código:
import random
importoperatorfrom bokeh.plottingimport figure, show
from bokeh.modelsimportLabelSet,ColumnDataSourcefrom bokeh.palettesimportCategory20as palette
def distancia_euclidiana(a, b):return(((a[0]- b[0])**2)+((a[1]- b[1])**2))**0.5def distancia_manhattan(a, b):returnabs(a[0]- b[0])+abs(a[1]- b[1])def distancia_chebyshov(a, b):returnmax(abs(a[0]- b[0]),abs(a[1]- b[1]))def generar_datos(rango, cantidad): datos =[]for i inrange(cantidad): datos.append((random.randint(0, rango), random.randint(0, rango)))return datos
def par_mas_cercano(centros, tipo): n =len(centros) distancias ={}for i inrange(n):for j inrange(n-i-1):if tipo ==1: distancia =distancia_euclidiana(centros[i], centros[i + j +1]) elif tipo ==2: distancia =distancia_manhattan(centros[i], centros[i + j +1])else: distancia =distancia_chebyshov(centros[i], centros[i + j +1]) distancias[(centros[i], centros[i + j +1])]= distancia
distancias_sorted =sorted(distancias.items(), key = operator.itemgetter(1)) par_cercano = distancias_sorted[0][0]return par_cercano, distancias_sorted[0][1]def agrupar(datos, tipo): clusters_dict =dict(zip(datos,range(len(datos)))) x =[] y =[] names =[]for coordenada indatos: x.append(coordenada[0]) y.append(coordenada[1]) names.append(str(datos.index(coordenada))) grafica =figure(title ='Datos', x_axis_label ='x', y_axis_label ='y') grafica.circle(x, y) source =ColumnDataSource(data=dict(x=x, y=y, names=names)) labels =LabelSet(x='x', y='y', text='names', level='glyph', x_offset=5, y_offset=5, source=source, render_mode='canvas') grafica.add_layout(labels) radio =0.0whilelen(clusters_dict)>0:print(f'\nClusters {len(datos) - len(clusters_dict) + 1} -> {clusters_dict}') centros =[]for key in clusters_dict.keys(): centros.append(key)iflen(clusters_dict)>1: par =par_mas_cercano(centros, tipo)print(f'Centros más cercanos -> {par[1]}')iftype(clusters_dict[par[0][0]])== int and type(clusters_dict[par[0][1]])== int: new_value =(clusters_dict[par[0][0]], clusters_dict[par[0][1]]) elif type(clusters_dict[par[0][0]])== tuple and type(clusters_dict[par[0][1]])== int: new_value = clusters_dict[par[0][0]]+(clusters_dict[par[0][1]],) elif type(clusters_dict[par[0][0]])== int and type(clusters_dict[par[0][1]])== tuple: new_value =(clusters_dict[par[0][0]],)+ clusters_dict[par[0][1]]else: new_value = clusters_dict[par[0][0]]+ clusters_dict[par[0][1]] x_tot =0 y_tot =0for k innew_value: x_tot += datos[k][0] y_tot += datos[k][1] x_c = x_tot /len(new_value) y_c = y_tot /len(new_value) new_centro =(x_c, y_c) clusters_dict.pop(par[0][0]) clusters_dict.pop(par[0][1]) clusters_dict[new_centro]= new_value
radio = par[1] grafica.circle(new_centro[0], new_centro[1], radius= radio, color = palette[11][(len(datos)-len(clusters_dict))%len(palette[11])], fill_alpha =0.1)else:breakshow(grafica)if __name__ =="__main__": rango =int(input('Ingrese el rango de datos: ')) cantidad =int(input('\nIngrese la cantidad de puntos: ')) tipo_distancia =int(input('\nSeleccione el tipo de medicion: \n[1] = Euclidiana\n[2] = Manhattan\n[3] = Chebyshov\n\nSeleccion: ')) datos =generar_datos(rango, cantidad)print(f'\nDatos iniciales: {datos}')agrupar(datos, tipo_distancia)
esta súper!
Qué código tan elegante y poderoso!
Excelente. Lo mejor de todo es que permite hacer diferentes pruebas y así poder comprender mejor el algoritmo y los resultados.
Saludos
Agrupamiento jerárquico:
Muchas gracias, con esta ejemplo logre entender la utilidad para este algoritmo.
Genial ejemplo, se puede entender mejor el concepto de este tipo de agrupamiento. Gracias Joaquin
Aquí dejo mi aporte, solo funciona hasta 15 puntos (por los colores jeje). Me tomo horas y quería hacerlo animado y que fueran apareciendo los círculos pero no encontré como y me rendí. Si alguien sabe como animarlo, ¿podría decirme por favor?
15 puntos
10 puntos
5 puntos
Mi lógica fue la siguiente:
Crear puntos y almacenarlos en una lista
Preguntar a un elemento de la lista de puntos cual es el punto más cercano a él.
Obtengo el punto más cercano.
Pregunto al punto encontrado cual es el punto más cercano a él.
Si coinciden en ser recíprocos se crea un nuevo punto equidistante entre ellos.
5.1. un círculo de color los encierra.
5.2. remueve los puntos recíprocos de la lista de puntos.
5.3. añade el nuevo punto a la lista de puntos.
Si no son recíprocos, regreso al punto 4.
Los círculos de color son los clusters creados.
Las cruces negras son los puntos creados originalmente
Los puntos negros más chicos son los puntos equidistantes creados.
Anexo el código:
import random
import sys
import matplotlib.pyplotas plt
import matplotlib as mpl
import matplotlib.animation
if __name__ =='__main__': sys.setrecursionlimit(5000) figg=plt.figure(figsize=(10,10), dpi =72) number_of_points =int(input('Introduce the number of points to cluster: ')) colors =['brown','red','tomato','sienna','orange','gold','yellow','lime','green','blue','turquoise','aqua','fuchsia','deeppink','blueviolet'] sizes =[i*12+10for i inrange(30)] clusters =['Cluster '+str(i)for i inrange(1,number_of_points)] points =create_points(number_of_points) original_points = points[:] x_points =[point.xfor point in original_points] y_points =[point.yfor point in original_points]for point inpoints:print('('+str(point.x)+','+str(point.y)+')') plt.scatter(x_points, y_points, c =['black'],s=15)Hierarchical_clustering(points) plt.grid(True) plt.plot(x_points, y_points,'kx',markersize=15) plt.show()
Me gustaría implementarle una interfaz gráfica a este código para que se vea como una animación a medida que se van formando los clusters. Alguien podría ayudarme? Aquí les dejo mi aporte :)
import random
#generar coordenadas random
def generate_coordinates(number_points): points =[]for i inrange(number_points): order = i
x = random.randint(0,20) y = random.randint(0,20) points.append([order, x, y])return points
#calcular la distancia de todas con todas
def calculate_distances(points): distances =[]for i inrange(len(points)):for j inrange(len(points)):#puntos a calcular con el point[i]if j + i +1<len(points): current_point = points[i] next_point = points[j + i +1] distance =((current_point[1]- next_point[1])**2+(current_point[2]- next_point[2])**2)**0.5 distances.append([current_point[0], next_point[0], distance])return distances
#buscar la minima entre 2def search_min_distance(distances): only_distances =[]for distance indistances: only_distances.append(distance[2]) min_vector =min(only_distances)for distance indistances:if min_vector == distance[2]: min_distance = distance
return min_distance
def new_coordinate(pointA, pointB): x_average =(pointA[1]+ pointB[1])/2 y_average =(pointA[2]+ pointB[2])/2 new_cluster =[0, x_average, y_average]return new_cluster
def make_cluster(points): number_recursions =0 cluster ='C'whilelen(points)>1: distances =calculate_distances(points) min_distance =search_min_distance(distances) pointA =[] pointB =[]for point inpoints:if min_distance[0]== point[0]: pointA = point
points.remove(point)for point inpoints:if min_distance[1]== point[0]: pointB = point
points.remove(point) new_cluster =new_coordinate(pointA, pointB) new_cluster[0]='C', number_recursions
points.append(new_cluster) number_recursions +=1print(points)return points
#agruparlas
if __name__ =="__main__": number_points =10 points =generate_coordinates(number_points)print(points) clusters =make_cluster(points)
para los que quieran ver como se realiza el cluster con sklearn
Gracias por este contenido, tiene todo el detalle del agrupamiento jerárquico.
y cuando se explicó lo de feynman?? por lo que veo del concepto en google es un conjunto de unos pocos pasos para llegar a una explicación de un concepto. Por lo tanto mi input deberia ser un concepto pero esto me lleva a pensar cosas que creo que me salgo totalmente del propósito del profesor. Y veo que las respuestas de los alumnos son simplemente algoritmos para hacer la agrupación que se explica en el video. ¿eso es lo que hay que hacer? por que de lo contrario sinceramente no se me ocurre ni como comenzar un algoritmo que vaya filtrando y discriminando palabras como para darle sentido a una explicación final de un concepto. Estoy perdido. Se agradece orientación.
Estos vídeos son una guía sobre lo que hay que saber y nos dan las palabras claves que hay que buscar. Creo que para entender un poco mejor qué es agrupamiento jerárquico hay que leer el articulo sobre wikipedia.
(https://es.wikipedia.org/wiki/Agrupamiento_jerárquico)
Con esto claro, creo que si es posible comenzar a plantear un algoritmo, obviamente, puede que no funcione y tengamos otros algoritmos para ver cómo se hace, pero ya es un buen inicio para comprender mejor el tema
Mi código:
importmathfrom random import randint
classPunto(): def __init__(self, x, y): self.x= x
self.y= y
def getData(self):return[self.x, self.y] def point_print(self):return'p('+str(self.x)+', '+str(self.y)+')' def __repr__(self):return self.point_print() def __str__(self):return self.point_print() def isEqual(self,otro_punto):return(self.x== otro_punto.x)and(self.y== otro_punto.y)def metrica_euclidiana(punto1, punto2):return math.sqrt((punto1.x- punto2.x)**2+(punto1.y- punto2.y)**2)def metrica_manhattan(punto1, punto2):returnabs(punto1.x- punto2.x)+abs(punto1.y- punto2.y)def metrica_chebyshev(punto1, punto2):returnmax(abs(punto1.x-punto2.x),abs(punto1.y- punto2.y))def listar_distancias(lista_puntos, funcion_distancia): distancias =[] copy_list = lista_puntos[:]whilelen(copy_list)>1: ref = copy_list.pop(0) point_list =[]for punto incopy_list: dist =funcion_distancia(ref, punto) metric =(ref.getData(), punto.getData(), dist) point_list.append(metric) point_list.sort(key=lambda p: p[2]) distancias.append(point_list[0])return distancias
if __name__ =="__main__": num_puntos =int(input('Ingrese la cantidad de puntos a generar: ')) dist_functions =[metrica_euclidiana,metrica_manhattan,metrica_chebyshev] dist_select =int(input('''
Seleccione el tipo de medicion:[0]=Euclidiana[1]=Manhattan[2]=ChebyshevSeleccion:''')) #Generamos un rango aleatorio de puntos
puntos =[]for _ inrange(num_puntos): punto =Punto(randint(0,100),randint(0,100)) puntos.append(punto)print(f'Lista inicial de puntos:\n{puntos}')whilelen(puntos)>1: distancias =listar_distancias(puntos, dist_functions[dist_select])#print(f'minimos:\n{distancias}')for metrica indistancias: p1 =Punto(metrica[0][0],metrica[0][1]) p2 =Punto(metrica[1][0],metrica[1][1]) new_p =Punto((p1.x+ p2.x)//2,(p1.y + p2.y)//2)for p inpuntos:if p.isEqual(p1): puntos.remove(p) puntos.append(new_p)for p inpuntos:if p.isEqual(p2): puntos.remove(p)print(f'Lista de puntos:\n{puntos}')
Nice hack! Muy entendible tu código! me puedes decir cuál es la diferencia de str y repr ?? Gracias por el aporte!
Hola
Por lo que he investigado son funciones complementarias. La idea es que ambas devuelvan una cadena que represente la función. Se deben definir ambas para que la funcion print() opere correctamente.
Después de dos tardes planteandome el problema y las iteraciones, logre generar el algoritmo, y conseguí obtener el vector final, así como graficar todos los vectores, los generados aleatoriamente y los generados por un agrupamiento, y un algoritmo optimizado para calcular las distancias entre todos los vectores.
Este es el código:
# Algoritmo de AgrupamientoJerarquicoimportrandomfrom bokeh.plottingimport figure, show, output_file
import os
# limpiamos la consola
os.system('clear')# lsita para pintar los vectores,8 colores
colors =['red','blue','green','cyan','orange','black','yellow','pink']
# clase para instanciar vectores
classVector: # constructor de la clase, construimos coordenadas, si rand, entoces seran aleatorios
def __init__(self, rand, x=0, y=0):ifrand: self.x= random.randint(0,10) self.y= random.randint(0,10)else: self.x= x
self.y= y
# obtenemos las coordenadas del vector
def getCoords(self):return self.x, self.y
# si imprimimos la clase nos dara las coordenadas
def __str__(self):return f'( {self.x}, {self.y} )'
# funcion para instanciar n nuevos vectores
def createRandomVectors(number): # aqui almacenaremos los vectores creados
vectors =[]for _ inrange(number): # construimos el vector con la coordenada
new_vector =Vector(rand=True) # y registramos el vector
vectors.append(new_vector)
# y retornamos los nuevos vectores
return vectors
# funcion para graficar vectores
def plotVectors(vectores_random, vectores_nuevos): # coordenadas de vectores aleatorios
x_coords_rand, y_coords_rand =[],[] # iteramos el numero de vectores de number, los aleatorios
for vector invectores_random: # obtenemos las coordenadas de cada vector
x,y = vector.getCoords() # y las registramos
x_coords_rand.append(x) y_coords_rand.append(y)
# coordenadas de vectores agrupados
x_coords_group, y_coords_group =[],[] # iteramos los vectores que se crearon por agrupamiento, los totales - los solicitados
for vector invectores_nuevos: # obtenemos las coordenadas de cada vector
x,y = vector.getCoords() # y las registramos
x_coords_group.append(x) y_coords_group.append(y) # obtenemos las coordenadas del vector resultante, se pintara sobre un vector_nuevo
x, y = vectores_nuevos[len(vectores_nuevos)-1].getCoords()
# y graficamos
# nombre del html
output_file("Agrupamiento.html") # creamos la grafica
grafica =figure(plot_width=600, plot_height=600, title='Grafica de Vectores')
# dibujamos los vectores generados aleatoriamente
grafica.circle(x_coords_rand, y_coords_rand, size=15, color=colors[1], alpha=0.5) # dibujamos los vectores generados por la agrupacion
grafica.circle(x_coords_group, y_coords_group, size=20, color=colors[4], alpha=0.5) # dibujamos el vector resultante
grafica.circle(x, y, size=25, color=colors[0], alpha=0.5)
# y renderizamos la grafica
show(grafica)# calcular la distancia entre dos instancias de Vector, dos puntos, por metodo euclidiano
def calcDistance(vector1, vector2): # obtenemos las coordenadas de los puntos
x1, y1 = vector1.getCoords() x2, y2 = vector2.getCoords() # retornamos la distancia entre los puntos
return((x2 - x1)**2+(y2 - y1)**2)**.5
# funcion para tratar de ubicar un registro de un diccionario
def findKey(dic, value): # tratamos de buscar
try: # retornamos la clave del valor del diccionario
index =list(dic.values()).index(value)returnlist(dic.keys())[index] # si no se encuentra pues retornamos un False except KeyError:returnFalse except ValueError:returnFalseelse:returnFalse
# funcion para regresar un diccionario que contenga las distancias entre cada punto
def getDistances(vectors): # Diccionario para las distancias
# Estructura:'a,b': distancia
# ejemplo:'0,1':10.2 # diccionario, y lista de distancias
distancias ={} valores =[] # cantidad de vectores
size =len(vectors)
# definimos las listas para iterar
i_puntos =list(range(size -1)) # 0,1,2 itera el punto a respecto al cual calcula distancias
i_sig =list(range(size)) i_sig.pop(0) # 3,2,1 itera los segundos puntos para calcular
# iteramos
# iteramos el punto a para calcular distancias
for p ini_puntos: # 0,1,2 # obtenemos el indice del punto a
punto_a = i_puntos[p]
# iteramos los demas puntos para calcular la distancia del punto a y los demas puntos
# representando i es el indice del iterador de puntos siguiente, y p el punto actual
for i inrange(len(i_sig)): # obtenemos el indice del punto b
punto_b = i_sig[i] # en esta parte tenemos para iterar los puntos o vectores mediante punto_a y punto_b
# calculamos la distancia entre los puntos
distancia =calcDistance(vectors[punto_a], vectors[punto_b])
# definimos la clave y valor para regitrar en el diccionario
clave = f'{punto_a},{punto_b}' valor = distancia
# registramos en el diccionario y la lista de distancias
distancias[clave]= valor
valores.append(valor) # para no repetir [dis]tancias eliminamos la primera de la cadena
i_sig.pop(0)
# ordenamos los valores antes de mandarlos
valores.sort()
# y retornamos el diccionario y los valores ordenados
return distancias, valores
# funcio para obtener los indices de vectores pasando una clave de diccionario como 1,2def getVectorIndex(clave): clave =list(clave)returnint(clave[0]),int(clave[2])# funcion para obtener el punto medio entre dos vectores, con el fin de agruparlos
def middlePoint(vector1, vector2): # obtenemos las coordenadas de los vectores
x1, y1 = vector1.getCoords() x2, y2 = vector2.getCoords() # obtenemos las nuevas coordenadas medias
new_x =(x2 + x1)/2 new_y =(y2 + y1)/2 # retornamos un nuevo vector con las coordenadas medias
returnVector(False, new_x, new_y)# funcion para agrupar vectores, recibe el diccionario de distancias y las distancias ordenadas
def agroup(dic, distancias, vectores): # lista de todos los vetcores para graficar, de primeras son los ya generados aleatoriamente
nuevos_vectores =[]
# proceso para agrupar los vectores
# iteramos la cantidad de vectores existentes, agrupamos, actualizamos y repetimos, hasta que solo hay 1for _ inrange(len(vectores)-1): # obtenemos la distancia menor de la lista de distancias
dist_menor = distancias[0] # obtenemos la clave de dicha distancia
clave =findKey(dic, dist_menor) # y de la clave los indices de los vectores
v1, v2 =getVectorIndex(clave) # ahora obtenemos los vectores
vector1 = vectores[v1] vector2 = vectores[v2] # calculamos las coordenadas del vector resultante, basicamente un punto medio
new_vector =middlePoint(vector1, vector2) # ya tenemos el nuevo vector! nuevos_vectores.append(new_vector) # ahora lo agregamos a la lista para graficar
# ahora, eliminamos de vectores los vectores agrupados
vectores.pop(v1) vectores.pop(v2 -1) # menos uno porque la lista se encogio -1 por el pop anterior
# y registramos dentro de vectors el nuevo
vectores.append(new_vector) # generamos un nuevo diccionario, lista de dist.mediante un nuevo calculo de distancias
dic, distancias =getDistances(vectores) # ese 1 restante sera el vector resultante del agrupamiento de todos, y todos los vectores
return vectores[0], nuevos_vectores
# funcion principal
def main(number): # lista para guardar todos los vectores
vetcores_random =[]
# creamos los n vectores
vectores =createRandomVectors(number)print('Vectores Generados:')
# vemos los coordenadas de todos los vectores creados
for v invectores: # vemos las coordenadas de los vectores generados aleatoriamente
print('-> ',v.getCoords()) # los guardamos, porque seran eliminados de vectors por el agrupamiento
vetcores_random.append(v)
# obtenemos las distancias
distancias, valores =getDistances(vectores)
# vemos el diccionario de las distancias
print('\nDiccionario de distancias:')print(distancias) # realizamos el agrupamiento, y obtenemos un grafo en forma de lista con los vectores
vector_resultante, nuevos_vectores =agroup(distancias, valores, vectores)
# vemos el vector resultante
print('\nVector Resultante:',vector_resultante.getCoords())
# y por ultimo graficamos
plotVectors(vetcores_random ,nuevos_vectores)if __name__ =="__main__":print('*'*60)
# solicitamos el numero de vectores a generar
number =int(input('Cuantos vectores quieres generar: '))main(number)print('*'*60)
Y así lucen las gráficas, los puntos azules son los aleatorios, los naranjas son los que resultaron de un agrupamiento, y el rojo es el resultante
!Grafica de 4 puntos
!Grafica de 8 puntos
!Resultado en consola
En caso de no verse las imágenes aquí dejo los links:
El nuevo conjunto creado tiene como centro el promedio de las coordenadas de los otros agrupados verdad?
La coincidencia tiende a eso.
si, Miguel el centroide de un grupo de puntos, se calcula promediando los valores de x, para las abscisas y los promedios de y para las ordenadas,
los promedios resultantes, dan la posición x,y del centroide de la nube de puntos
Notas:
El agrupamiento jerárquico es el algoritmo que agrupa objetos similares en clusters.
El algoritmo comienza tratando a un cluster de manera individual y luego realiza los siguientes pasos de manera recursiva:
Identifica los dos clusters dependiendo la distancia
Agrupa ambos clusters en uno
Distancias:
Puntos más cercanos (con mayor similitud)
Puntos más lejanos (con menor similitud)
Puntos intermedios o promedios
El output final es un dendrograma que nos permitirá mostrar la relación entre grupos.
Hola, queria correr tu codigo, pero me sale numpy "sin resolver", como puedo improtarlo correctamente?
Hola, @josefernandojaramilloboon. ¿Ya instalaste numpy en tu computadora? Esa librería debe instalarse como se instalo bokeh y otras librerías no nativas de Python. Ya luego puedes importarla en tus módulos.
Sinceramente no entendí que es lo que hay que hacer en el reto...
Hola, @fernandotc89. 🤓
La idea del reto es que programemos en Python las instrucciones del algoritmo que explica David. Como a ti se te ocurra escribe un programa que haga los pasos del agrupamiento jerárquico.
Es un buen reto para ejercitar lo que se aprendió en los cursos anteriores y pasar algoritmos a código.
Gracias datormx, pero y le técnica Feyman?
No hay problema si no logro ejecutar un algoritmo de este tipo?
Hola Angelo,
¿A qué te refieres a no poder ejecutar?
Tienes algún problema en Python?
Saludos
Si te refieres a que no puedes realizarlo tu mismo sin ayuda de nadie, no hay problema solo sigue intentando esfuérzate mucho! :D
Pude implementar este algoritmo, y creé esta visualización para ver el proceso :smile: