pagerank / src /streamlit_app.py
javifmz's picture
Update src/streamlit_app.py
f41b181 verified
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.")