import streamlit as st import networkx as nx import matplotlib.pyplot as plt import pandas as pd import random # Configuración de la página st.set_page_config(page_title="Visualizador de PageRank", layout="wide") st.title("🕸️ Visualizador Interactivo de PageRank") st.markdown(""" Esta herramienta permite visualizar cómo fluye la importancia (PageRank) a través de los nodos de una red. El **tamaño del nodo** representa su PageRank actual. """) # --- BARRA LATERAL: Configuración --- with st.sidebar: st.header("Configuración del Grafo") num_nodes = st.slider("Número de Nodos", min_value=3, max_value=20, value=8) prob_link = st.slider("Probabilidad de enlace (Densidad)", min_value=0.1, max_value=1.0, value=0.3) damping_factor = st.slider("Factor de Amortiguación (Damping)", min_value=0.5, max_value=0.99, value=0.85) st.markdown("---") if st.button("🔄 Generar Nuevo Grafo"): st.session_state.clear() # Limpia todo para reiniciar # --- LÓGICA DE SESSION STATE --- # Necesitamos mantener el estado del grafo y los valores entre interacciones if 'graph' not in st.session_state: # Crear grafo dirigido aleatorio G = nx.gnp_random_graph(num_nodes, prob_link, directed=True, seed=random.randint(1, 1000)) st.session_state.graph = G # Calcular layout fijo para que los nodos no bailen en cada paso st.session_state.pos = nx.spring_layout(G, k=0.8, iterations=50) # Inicializar PageRank: todos empiezan con 1/N initial_pr = {node: 1.0/num_nodes for node in G.nodes()} st.session_state.pagerank = initial_pr st.session_state.iteration = 0 st.session_state.history = [initial_pr.copy()] # Función para dar un paso del algoritmo def step_pagerank(): G = st.session_state.graph current_pr = st.session_state.pagerank d = damping_factor N = len(G.nodes) new_pr = {} # Fórmula básica de PageRank iterativo for i in G.nodes(): incoming_sum = 0 # Buscar nodos que apuntan a 'i' for node_j in G.predecessors(i): # Out-degree del nodo j out_degree = G.out_degree(node_j) if out_degree > 0: incoming_sum += current_pr[node_j] / out_degree # Aplicar fórmula: (1-d)/N + d * sum(PR(j)/L(j)) val = ((1 - d) / N) + (d * incoming_sum) new_pr[i] = val # Manejo básico de sumideros (nodos sin salidas) para mantener la suma ~1 # En implementaciones complejas se redistribuye la masa perdida total_mass = sum(new_pr.values()) for k in new_pr: new_pr[k] /= total_mass st.session_state.pagerank = new_pr st.session_state.iteration += 1 st.session_state.history.append(new_pr.copy()) # --- INTERFAZ PRINCIPAL --- col1, col2 = st.columns([3, 1]) with col1: st.subheader(f"Grafo - Iteración: {st.session_state.iteration}") # Dibujar Grafo fig, ax = plt.subplots(figsize=(8, 6)) G = st.session_state.graph pos = st.session_state.pos pr_values = st.session_state.pagerank # Escalar tamaño de nodos basado en PR (multiplicador estético) node_sizes = [v * 5000 for v in pr_values.values()] # Colores basados en importancia node_colors = list(pr_values.values()) nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color=node_colors, cmap=plt.cm.viridis, alpha=0.9, ax=ax) nx.draw_networkx_edges(G, pos, arrowstyle='->', arrowsize=20, edge_color='gray', width=1, alpha=0.5, ax=ax, connectionstyle="arc3,rad=0.1") nx.draw_networkx_labels(G, pos, font_size=10, font_color="white", font_weight="bold", ax=ax) ax.axis('off') st.pyplot(fig) # Botón grande para iterar if st.button("▶️ Siguiente Iteración", use_container_width=True): step_pagerank() st.rerun() with col2: st.subheader("Datos") # Crear DataFrame para mostrar valores df = pd.DataFrame.from_dict(st.session_state.pagerank, orient='index', columns=['PageRank']) df = df.sort_values(by='PageRank', ascending=False) st.dataframe(df.style.background_gradient(cmap='viridis'), height=400) # Métrica de convergencia (cambio respecto al paso anterior) if st.session_state.iteration > 0: prev = st.session_state.history[-2] curr = st.session_state.history[-1] diff = sum([abs(curr[n] - prev[n]) for n in curr]) st.metric("Cambio Total (Delta)", f"{diff:.4f}") if diff < 0.001: st.success("¡El algoritmo ha convergido!") # --- GRÁFICO DE EVOLUCIÓN --- st.divider() st.subheader("Evolución de Pesos por Nodo") if st.session_state.iteration > 0: history_df = pd.DataFrame(st.session_state.history) st.line_chart(history_df) else: st.info("Presiona 'Siguiente Iteración' para ver cómo evolucionan los pesos.")