tutoriales.com

Visualizando Redes Bipartitas con Python: Conectando Nodos de Dos Mundos

Este tutorial explora las redes bipartitas, un tipo especial de grafo que modela relaciones entre dos conjuntos disjuntos de nodos. Aprenderás a construir, manipular y visualizar estas redes usando las poderosas bibliotecas NetworkX y Matplotlib en Python, desentrañando patrones de conexión en diversos dominios.

Intermedio18 min de lectura10 views
Reportar error

Las redes son omnipresentes en nuestra vida. Desde las redes sociales hasta las redes de transporte, pasando por las redes biológicas, la teoría de grafos nos proporciona herramientas invaluables para entender sistemas complejos. Dentro de este vasto campo, las redes bipartitas (o bigrafos) ocupan un lugar especial al modelar relaciones entre dos tipos distintos de entidades.

Imagina una red donde un conjunto de nodos representa a usuarios y otro conjunto representa a películas. Una arista entre un usuario y una película significaría que el usuario ha visto o valorado esa película. En esta red, no hay conexiones entre usuarios (directamente) ni entre películas (directamente), solo entre usuarios y películas. ¡Esto es una red bipartita!

Este tutorial te guiará a través del proceso de creación, manipulación y visualización de redes bipartitas en Python, utilizando las bibliotecas NetworkX para la manipulación de grafos y Matplotlib para la visualización. Prepárate para conectar dos mundos de datos de una manera visualmente impactante y analíticamente potente.

🎯 ¿Qué Aprenderás en Este Tutorial?

Al finalizar este tutorial, serás capaz de:

  • Entender el concepto y la importancia de las redes bipartitas.
  • Construir redes bipartitas desde cero usando NetworkX.
  • Añadir nodos y aristas a una red bipartita de forma correcta.
  • Visualizar redes bipartitas de manera efectiva con Matplotlib.
  • Realizar análisis básicos específicos para redes bipartitas, como la proyección.
  • Aplicar estos conocimientos a ejemplos prácticos.

📖 ¿Qué Son las Redes Bipartitas? Una Introducción

Una red bipartita es un grafo especial cuyos vértices (nodos) pueden dividirse en dos conjuntos disjuntos, digamos $U$ y $V$, de tal manera que cada arista conecta un vértice de $U$ con uno de $V$. Es decir, no hay aristas que conecten vértices dentro del mismo conjunto. Los nodos de $U$ solo se conectan con nodos de $V$, y viceversa.

📌 Nota: Esta propiedad significa que un grafo bipartito no contiene ciclos de longitud impar.

Ejemplos Cotidianos de Redes Bipartitas:

Las redes bipartitas son sorprendentemente comunes. Aquí tienes algunos ejemplos:

  • Usuarios y Productos: Los usuarios compran productos. (Usuario $ ightarrow$ Producto).
  • Actores y Películas: Los actores actúan en películas. (Actor $ ightarrow$ Película).
  • Científicos y Artículos: Los científicos publican artículos. (Científico $ ightarrow$ Artículo).
  • Plagas y Cultivos: Ciertas plagas afectan a ciertos cultivos. (Plaga $ ightarrow$ Cultivo).
  • Estudiantes y Cursos: Los estudiantes se inscriben en cursos. (Estudiante $ ightarrow$ Curso).

En cada uno de estos ejemplos, puedes ver claramente dos tipos de entidades y las relaciones que existen entre ellas, pero no dentro de ellas.

Red Bipartita Simple Conjunto U Conjunto V U1 U2 U3 V1 V2 V3

💡 Ventajas de Usar Redes Bipartitas

  • Claridad de Modelado: Permiten modelar relaciones entre entidades de diferente naturaleza de forma explícita.
  • Análisis Específico: Existen algoritmos y métricas específicos para redes bipartitas que pueden revelar patrones únicos.
  • Proyecciones: Es posible transformar una red bipartita en dos redes unimodales (no bipartitas), una para cada conjunto de nodos, mostrando las co-ocurrencias o similitudes.

🛠️ Preparando el Entorno: Instalación de Librerías

Antes de sumergirnos en la acción, necesitamos asegurarnos de tener las librerías necesarias instaladas. Si aún no las tienes, puedes instalarlas fácilmente usando pip:

pip install networkx matplotlib
💡 Consejo: Es recomendable trabajar en un entorno virtual para mantener las dependencias de tus proyectos organizadas.

🏗️ Construyendo una Red Bipartita con NetworkX

NetworkX es la biblioteca de facto en Python para la creación, manipulación y estudio de la estructura, dinámica y funciones de redes complejas. Nos proporciona herramientas específicas para manejar redes bipartitas.

1. Creando un Grafo Bipartito Vacío

Aunque NetworkX no tiene un tipo de grafo BipartiteGraph per se, podemos crear un grafo normal (Graph o DiGraph) y marcar qué nodos pertenecen a cada conjunto. La forma más común de hacerlo es usando un atributo de nodo.

import networkx as nx
import matplotlib.pyplot as plt

# Creamos un grafo vacío
G = nx.Graph()

# Definimos los dos conjuntos de nodos
# Por ejemplo, usuarios y películas
users = ['Alice', 'Bob', 'Charlie']
movies = ['Movie A', 'Movie B', 'Movie C', 'Movie D']

# Añadimos los nodos al grafo, marcando a qué conjunto pertenecen
G.add_nodes_from(users, bipartite=0) # Conjunto 0 (ej. usuarios)
G.add_nodes_from(movies, bipartite=1) # Conjunto 1 (ej. películas)

print(f"Nodos en el grafo: {G.nodes(data=True)}")

Fíjate en el atributo bipartite. Esto es crucial para que NetworkX reconozca la estructura bipartita del grafo cuando uses funciones específicas de bigrafos.

2. Añadiendo Aristas

Las aristas siempre deben conectar un nodo del conjunto 0 con un nodo del conjunto 1.

# Añadimos las aristas (conexiones entre usuarios y películas)
G.add_edges_from([
    ('Alice', 'Movie A'),
    ('Alice', 'Movie B'),
    ('Bob', 'Movie B'),
    ('Bob', 'Movie C'),
    ('Charlie', 'Movie A'),
    ('Charlie', 'Movie D')
])

print(f"Número de nodos: {G.number_of_nodes()}")
print(f"Número de aristas: {G.number_of_edges()}")

3. Verificando la Bipartición

NetworkX tiene una función para verificar si un grafo es bipartito. Es una buena práctica usarla.

if nx.is_bipartite(G):
    print("El grafo es bipartito.")
    # Podemos obtener los dos conjuntos de nodos usando partition
    # Aunque ya los hemos definido, esto lo verifica para un grafo arbitrario
    bottom_nodes, top_nodes = nx.bipartite.sets(G)
    print(f"Nodos del conjunto inferior (bipartite=0): {bottom_nodes}")
    print(f"Nodos del conjunto superior (bipartite=1): {top_nodes}")
else:
    print("El grafo NO es bipartito.")
⚠️ Advertencia: `nx.bipartite.sets(G)` funciona si NetworkX puede inferir la bipartición. Si no has usado el atributo `bipartite` y el grafo es complejo, podrías necesitar otras estrategias o la función podría no devolver los conjuntos esperados si la bipartición no es única o clara sin metadatos. Sin embargo, para grafos construidos explícitamente con `bipartite=0/1`, suele funcionar correctamente.

📊 Visualizando Redes Bipartitas con Matplotlib

Visualizar redes bipartitas requiere una disposición especial para que la estructura de los dos conjuntos sea evidente. Una forma común es colocar los nodos de un conjunto en la parte superior y los nodos del otro conjunto en la parte inferior, o en dos columnas separadas.

1. Disposición Bipartita con draw_bipartite

NetworkX ofrece una función draw_bipartite que facilita esta tarea. Necesitas especificar los nodos de uno de los conjuntos (bottom_nodes o top_nodes).

plt.figure(figsize=(10, 7))

# Obtener los conjuntos de nodos (si no los tenemos ya)
# Asumimos que los tenemos de la sección anterior
# bottom_nodes = set(n for n, d in G.nodes(data=True) if d['bipartite'] == 0)
# top_nodes = set(n for n, d in G.nodes(data=True) if d['bipartite'] == 1)

# Una forma más robusta de obtenerlos si no los hemos guardado
# Utiliza nx.bipartite.sets si el grafo es bipartito
if nx.is_bipartite(G):
    bottom_nodes, top_nodes = nx.bipartite.sets(G)
else:
    print("El grafo no es bipartito. No se puede usar draw_bipartite.")
    exit()

# Definir colores para los dos tipos de nodos
color_map = []
for node in G.nodes():
    if node in bottom_nodes:
        color_map.append('skyblue') # Color para usuarios
    else:
        color_map.append('lightcoral') # Color para películas

# Definir el layout bipartito
pos = nx.bipartite.layout(G, top_nodes)

# Dibujar los nodos y las aristas
nx.draw_networkx_nodes(G, pos, nodelist=list(bottom_nodes), node_color='skyblue', node_size=2000, alpha=0.9)
nx.draw_networkx_nodes(G, pos, nodelist=list(top_nodes), node_color='lightcoral', node_size=2000, alpha=0.9)
nx.draw_networkx_edges(G, pos, width=1.0, alpha=0.5)
nx.draw_networkx_labels(G, pos, font_size=10, font_weight='bold')

plt.title("Red Bipartita de Usuarios y Películas", size=15)
plt.axis('off')
plt.show()

En este código:

  • nx.bipartite.layout(G, top_nodes) genera un diseño de grafo donde los nodos de top_nodes se colocan arriba y los otros abajo. Podrías pasar bottom_nodes para invertirlos.
  • Hemos usado diferentes colores para distinguir visualmente los dos conjuntos de nodos.
Alice Bob Charlie Movie A Movie B Movie C Movie D USERS MOVIES

2. Personalizando la Visualización

Podemos hacer la visualización más atractiva añadiendo leyendas, ajustando tamaños y fuentes.

plt.figure(figsize=(12, 8))

# Obtener los conjuntos de nodos (si no los tenemos ya)
bottom_nodes, top_nodes = nx.bipartite.sets(G)

# Definir posiciones personalizadas para un diseño más limpio
pos = {}
# Posiciones para el conjunto de usuarios (eje Y fijo, X variando)
pos.update((node, (i, 1)) for i, node in enumerate(bottom_nodes))
# Posiciones para el conjunto de películas (eje Y fijo, X variando)
pos.update((node, (i, 0)) for i, node in enumerate(top_nodes))

# Dibujar nodos de usuarios (conjunto 0)
nx.draw_networkx_nodes(G, pos, nodelist=list(bottom_nodes), node_color='skyblue', node_size=3000, alpha=0.9, label='Usuarios')

# Dibujar nodos de películas (conjunto 1)
nx.draw_networkx_nodes(G, pos, nodelist=list(top_nodes), node_color='lightcoral', node_size=3000, alpha=0.9, label='Películas')

# Dibujar aristas
nx.draw_networkx_edges(G, pos, width=1.5, alpha=0.6, edge_color='gray')

# Dibujar etiquetas de nodos
nx.draw_networkx_labels(G, pos, font_size=9, font_weight='bold', font_color='black')

plt.title("Red Bipartita de Usuarios y Películas (Personalizada)", size=18)
plt.legend(scatterpoints=1, bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.) # Añadir leyenda
plt.axis('off')
plt.show()

En esta versión, hemos creado las posiciones manualmente para tener un control más preciso. enumerate es útil para distribuir los nodos de cada conjunto uniformemente en una línea.


🔎 Análisis Básico de Redes Bipartitas: Proyecciones

Uno de los análisis más poderosos que podemos realizar en una red bipartita es la proyección. Una proyección transforma la red bipartita en una red unimodal, donde los nodos son solo de uno de los conjuntos originales y las aristas representan conexiones indirectas a través del otro conjunto.

Por ejemplo, si proyectamos la red de usuarios y películas sobre el conjunto de usuarios, obtenemos una red donde una arista entre dos usuarios significa que han visto al menos una película en común. De manera similar, una proyección sobre el conjunto de películas mostraría películas que han sido vistas por al menos un usuario en común.

NetworkX facilita la creación de estas proyecciones.

1. Proyección al Conjunto de Usuarios (Co-visualización de películas)

# Asegurémonos de tener los conjuntos de nodos
bottom_nodes, top_nodes = nx.bipartite.sets(G)

# Proyección sobre el conjunto de usuarios
G_users = nx.bipartite.project(G, nodes=bottom_nodes)

plt.figure(figsize=(8, 6))
pos_users = nx.spring_layout(G_users, seed=42) # Diseño para la visualización

nx.draw_networkx_nodes(G_users, pos_users, node_color='skyblue', node_size=2500, alpha=0.9)
nx.draw_networkx_edges(G_users, pos_users, width=1.5, alpha=0.7, edge_color='gray')
nx.draw_networkx_labels(G_users, pos_users, font_size=10, font_weight='bold')

plt.title("Proyección en el Conjunto de Usuarios (Películas en Común)", size=15)
plt.axis('off')
plt.show()

print("Aristas en la red de usuarios (quiénes comparten películas):")
print(G_users.edges(data=True))

En este ejemplo, G_users ahora es una red donde Alice y Bob están conectados porque ambos vieron 'Movie B'. Alice y Charlie también están conectados porque ambos vieron 'Movie A'. Bob y Charlie no comparten ninguna película directamente.

Proyección de Usuarios Película B Película A Alice Bob Charlie Los nodos representan usuarios y las aristas conexiones por películas compartidas.

2. Proyección al Conjunto de Películas (Co-ocurrencia de usuarios)

# Proyección sobre el conjunto de películas
G_movies = nx.bipartite.project(G, nodes=top_nodes)

plt.figure(figsize=(8, 6))
pos_movies = nx.spring_layout(G_movies, seed=42)

nx.draw_networkx_nodes(G_movies, pos_movies, node_color='lightcoral', node_size=2500, alpha=0.9)
nx.draw_networkx_edges(G_movies, pos_movies, width=1.5, alpha=0.7, edge_color='gray')
nx.draw_networkx_labels(G_movies, pos_movies, font_size=10, font_weight='bold')

plt.title("Proyección en el Conjunto de Películas (Usuarios en Común)", size=15)
plt.axis('off')
plt.show()

print("Aristas en la red de películas (cuáles comparten usuarios):")
print(G_movies.edges(data=True))

Aquí, 'Movie A' y 'Movie B' están conectadas porque ambas fueron vistas por 'Alice'. 'Movie B' y 'Movie C' están conectadas porque ambas fueron vistas por 'Bob'. 'Movie A' y 'Movie D' no están conectadas (no tienen usuarios en común).

Alice Bob Movie A Movie B Movie C Movie D Proyección de conexiones entre películas
🔥 Importante: Las proyecciones pueden ser muy densas. Considera filtrarlas por un número mínimo de co-ocurrencias para visualizar solo las conexiones más fuertes.

📈 Aplicaciones Avanzadas y Métricas

Además de la visualización y las proyecciones, las redes bipartitas permiten cálculos de métricas de centralidad adaptadas y la detección de comunidades.

1. Centralidad Bipartita

Métricas como el grado (número de conexiones) son directamente aplicables. Sin embargo, en redes bipartitas, el grado de un nodo de tipo $U$ nos dice cuántos nodos de tipo $V$ está conectado, y viceversa.

# Grado de los nodos
print("Grados de los nodos:")
for node, degree in G.degree():
    print(f"Nodo {node}: Grado {degree}")

# También puedes calcular el grado de la proyección para ver la "importancia" en el mundo unimodal
print("\nGrados en la proyección de usuarios:")
for node, degree in G_users.degree():
    print(f"Usuario {node}: Grado {degree}")

# Puedes ver que Alice tiene grado 2 en la proyección de usuarios (conecta con Bob y Charlie)
# Bob tiene grado 1 (conecta con Alice)
# Charlie tiene grado 1 (conecta con Alice)

2. Detección de Comunidades

En redes bipartitas, la detección de comunidades puede revelar grupos de nodos de un tipo que se conectan con grupos específicos de nodos del otro tipo. Por ejemplo, grupos de usuarios que tienden a ver el mismo tipo de películas.

Para esto, a menudo se aplican algoritmos de detección de comunidades a las proyecciones del grafo.

Ejemplo de detección de comunidades (requiere `python-louvain`)

Para esto, generalmente se utiliza el algoritmo de Louvain en una de las proyecciones.

# pip install python-louvain
# import community as co_louvain

# particion_usuarios = co_louvain.best_partition(G_users)
# print("Comunidades de usuarios:", particion_usuarios)

# Visualización de comunidades (similar a la visualización de la proyección, pero coloreando por comunidad)
# ... (código similar al de dibujar G_users pero con colores basados en 'particion_usuarios')

Esto es un tema más avanzado, pero es una extensión natural del análisis de redes bipartitas.


🚀 Ejemplos Prácticos y Casos de Uso

Las redes bipartitas son fundamentales en muchos campos:

  1. Sistemas de Recomendación: Modelar las interacciones usuario-ítem (por ejemplo, usuario-producto, usuario-película). Las proyecciones pueden ayudar a encontrar usuarios similares (para recomendar productos) o productos similares (para recomendar a usuarios).
  2. Ecología: Redes de interacción planta-polinizador, huésped-parásito. Ayudan a entender los patrones de co-dependencia y la robustez del ecosistema.
  3. Análisis de Patentes: Inventor-patente. Muestra qué inventores colaboran en qué patentes.
  4. Ciencias Sociales: Persona-evento, afiliación persona-organización.
Paso 1: Recolección de Datos: Obtén tus datos de interacciones entre dos tipos de entidades (ej. `(usuario, pelicula)`).
Paso 2: Construcción del Grafo: Usa `NetworkX` para crear el grafo bipartito, marcando los nodos de cada conjunto.
Paso 3: Visualización Inicial: Dibuja la red bipartita para una comprensión visual de la estructura general.
Paso 4: Análisis de Proyecciones: Proyecta el grafo en cada conjunto para revelar co-ocurrencias y similitudes dentro de cada grupo de entidades.
Paso 5: Métricas y Algoritmos: Aplica métricas de centralidad o detección de comunidades para obtener insights más profundos.

🏁 Conclusión

Has llegado al final de este completo tutorial sobre redes bipartitas en Python. Hemos cubierto desde la comprensión fundamental de qué son estas redes hasta su construcción, visualización y análisis básico utilizando las potentes bibliotecas NetworkX y Matplotlib. Ahora tienes las herramientas para modelar y explorar relaciones entre dos conjuntos distintos de entidades, desentrañando patrones ocultos y generando nuevos insights.

Las redes bipartitas son una herramienta analítica y visual increíblemente versátil en el arsenal de cualquier científico de datos o analista de redes. ¡Esperamos que este tutorial te inspire a explorar más a fondo sus aplicaciones en tus propios proyectos!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!