Ir al contenido

Métodos de Análisis

El pipeline del Observatorio del Congreso ejecuta seis módulos de análisis que transforman datos brutos de votación nominal en información sobre estructura política y poder. La cadena de procesamiento va desde registros individuales de voto hasta posiciones ideológicas, redes de co-votación, particiones en comunidades, rankings de centralidad e índices de poder formal vs. empírico.

MóduloEntradaSalidaAlgoritmo Central
W-NOMINATEMatriz de votos (legisladores x eventos)Coordenadas de puntos idealesSVD + Newton-Raphson
Co-VotaciónRegistros de votaciónMatriz NxN de similitud + grafoConteo de acuerdos
ComunidadesGrafo de co-votaciónPartición (nodo -> comunidad)Modularidad de Louvain
CentralidadGrafo de co-votaciónPuntuaciones por nodoGrado ponderado, intermediación
Índices de PoderEscaños por partidoValores Shapley-Shubik, BanzhafEnumeración combinatoria
Poder EmpíricoRegistros de votación + escañosFrecuencias de partidos críticosAnálisis de votaciones nominales

W-NOMINATE (Weighted Nominal Three-Step Estimation) es el algoritmo estándar para estimar posiciones ideológicas de legisladores a partir de votaciones nominales, desarrollado por Poole & Rosenthal (1985, 1997).

Referencias:

  • Poole & Rosenthal (1985). “A Spatial Model for Legislative Roll Call Analysis”. American Journal of Political Science, 29(2), 357-384.
  • Poole & Rosenthal (1997). Congress: A Political-Economic History of Roll Call Voting. Oxford University Press.
  • Poole (2005). Spatial Models of Parliamentary Voting. Cambridge University Press.

El algoritmo recibe una matriz binaria de votos y recupera los puntos ideales de los legisladores en un espacio de políticas de baja dimensionalidad.

Paso 1: Binarización. Los strings de voto se mapean a valores binarios:

Tipo de votoValor binario
a_favor1 (Yea)
en_contra0 (Nay)
abstencionNaN (excluido)
ausenteNaN (excluido)

Paso 2: Filtrado. Se eliminan votos con baja información y legisladores inactivos:

min_votes = 10 # mínimo de votos binarios por legislador
min_participants = 10 # mínimo de participantes binarios por evento de voto
lopsided_threshold = 0.975 # filtrar votos casi unánimes

Paso 3: Estimación. El algoritmo estima dos parámetros por legislador (coordenadas en espacio 2D) y dos por evento de voto:

  • Puntos ideales (x_i, y_i): la posición de cada legislador en el espacio de políticas
  • Pesos de relevancia (beta): qué tan abruptamente cae la utilidad de un legislador al alejarse del plano de corte
  • Vectores normales (w_j): definen el plano de corte que separa Yea de Nay para cada voto

La inicialización usa descomposición SVD de la matriz binaria, seguida de optimización Newton-Raphson que maximiza la verosimilitud de clasificación.

MétricaDescripciónInterpretación
Tasa de clasificación% de votos correctamente predichos por el modeloMayor = mejor ajuste; rango típico 85-95%
APREAggregate Proportional Reduction in ErrorMejora sobre el baseline (predicción por mayoría); 0.0 = sin mejora, 1.0 = perfecto
  • nominate_by_legislatura: ejecuta W-NOMINATE por separado para cada legislatura, produciendo espacios ideales independientes por periodo
  • nominate_cross_legislatura: combina todas las legislaturas en una sola ejecución, ubicando a todos los legisladores en un espacio compartido para comparación directa

scipy (svd, minimize, norm), numpy, pandas

La co-votación mide la frecuencia con la que cada par de legisladores vota de la misma manera. Es la base para todo el análisis basado en redes.

  1. Carga de datos: votos, personas y organizaciones desde SQLite
  2. Normalización de partidos: normalize_party() mapea valores mixtos de vote.group a IDs canónicos de organización
  3. Asignación de partido primario: get_primary_party() asigna cada legislador a su partido más frecuente
  4. Construcción de matriz: build_covotacion_matrix() produce una matriz numpy NxN donde la entrada (i,j) = conteo de acuerdos entre legisladores i y j, normalizado a 0-1
  5. Construcción de grafo: build_graph() convierte la matriz en un grafo NetworkX con:
    • Nodos: legisladores, con atributos de partido y género
    • Aristas: pares de co-votación, con weight = similitud normalizada
# Cálculo simplificado del peso de co-votación
for i, j in legislator_pairs:
shared_votes = votes_i.intersection(votes_j)
total_votes = votes_i.union(votes_j)
weight = len(shared_votes) / len(total_votes)

El módulo retorna un diccionario que contiene:

ClaveTipoDescripción
matrixarreglo numpy NxNSimilitud de co-votación por pares
graphnetworkx.GraphRed de co-votación ponderada
party_mapdictmapeo person_id -> partido
org_mapdictmapeo org_id -> nombre del partido
persons_dfDataFrameMetadatos de legisladores

El algoritmo de Louvain detecta comunidades de legisladores que votan de forma similar, yendo más allá de las etiquetas formales de partido para revelar bloques de votación reales.

Louvain realiza optimización iterativa en dos fases:

  1. Movimiento local: cada nodo se mueve a la comunidad vecina que produce la mayor ganancia de modularidad
  2. Agregación: las comunidades se colapsan en super-nodos y el proceso se repite

El parámetro de resolución controla la granularidad de las comunidades:

ResoluciónEfecto
< 1.0Menos comunidades, más grandes (más grueso)
1.0 (default)Modularidad estándar
> 1.0Más comunidades, más pequeñas (más fino)

detect_communities() retorna un diccionario de partición que mapea cada node_id a un community_id.

analyze_communities() produce un análisis detallado por comunidad:

  • Composición partidista: conteo y porcentaje de cada partido dentro de la comunidad
  • Métrica de pureza: porcentaje del partido dominante (100% = bloque puro de partido)
  • Legisladores cruzados: individuos cuya comunidad difiere de su partido formal
  • Sub-bloques: detección de facciones internas dentro de partidos grandes (específicamente sub-bloques de MORENA)

python-louvain (paquete community), networkx

La centralidad identifica legisladores estructuralmente importantes en la red de co-votación. Se utilizan dos métricas complementarias.

centrality[node] = weighted_degree(node) / max_weighted_degree

El grado ponderado de cada nodo es la suma de sus pesos de arista (intensidad total de co-votación con todos los demás legisladores), normalizado por el grado ponderado máximo en el grafo. Los valores van de 0.0 a 1.0.

Interpretación: grado ponderado alto = el legislador co-vota intensamente con muchos otros, indicando alineación con la coalición dominante.

Centralidad de Intermediación (Betweenness)

Sección titulada «Centralidad de Intermediación (Betweenness)»
betweenness = nx.betweenness_centrality(graph, weight=None)

La intermediación se calcula sin ponderar (weight=None). Esta es una decisión deliberada:

Interpretación: intermediación alta = el legislador se ubica en los caminos más cortos entre comunidades distintas, actuando como potencial intermediario o legislador bisagra.

MétricaManejo de pesosCaptura
Grado PonderadoUsa pesosIntensidad general de co-votación
IntermediaciónIgnora pesos (weight=None)Posición estructural de puente

Los índices de poder nominal calculan el poder de negociación de cada partido basándose únicamente en el conteo de escaños, asumiendo que todos los miembros votan con su partido.

Para un partido p, el índice de Shapley-Shubik promedia el poder marginal a través de todas las N! permutaciones de partidos:

for permutation in all_permutations(parties):
coalition = set()
for party in permutation:
coalition.add(party)
if coalition alcanza mayoría AND coalition - {party} no:
party recibe un punto "pivot"
break
# repetir para todas las N! permutaciones
ss_index[party] = pivots[party] / factorial(N)

Un partido es crítico (un pivot) en la posición k de una permutación si la coalición de los primeros k partidos alcanza la mayoría, pero eliminar ese partido la deja por debajo.

Para un partido p, se cuentan todas las coaliciones ganadoras donde p es crítico (su defección cambia el resultado):

for coalition in all_subsets(parties):
if coalition es ganadora AND coalition - {party} es perdedora:
party es crítico en esta coalición
banzhaf_index[party] = critical_count[party] / total_critical_count

La multi-pertenencia (legisladores que pertenecen a más de un partido a lo largo de su carrera) se resuelve mediante:

  1. Recopilar todas las membresías de partido para cada legislador
  2. Asignar al partido donde el legislador emitió más votos
  3. Empates se resuelven con la membresía con start_date más reciente

Ambos índices soportan análisis separado para Diputados (camara='D') y Senado (camara='S').

El poder nominal asume disciplina partidista. El poder empírico mide lo que realmente ocurre en las votaciones nominales.

Para cada evento de votación, el módulo identifica qué partidos fueron necesarios para alcanzar el umbral de mayoría:

winning_coalition = partidos que votaron con la mayoría
for party in winning_coalition:
seats_without = majority_seats - party_seats
if seats_without < majority_threshold:
party es "crítico" para esta votación
empirical_power[party] = times_critical[party] / total_vote_events

Esto produce una puntuación de 0.0 a 1.0 que refleja qué tan frecuentemente los votos de un partido fueron realmente decisivos.

El módulo identifica:

  • Votaciones cerradas: eventos donde el margen fue estrecho (cerca del umbral de mayoría)
  • Legisladores bisagra (swing voters): legisladores individuales cuyo voto podría haber cambiado el resultado
  • Principales disidentes: legisladores que votaron en contra de la línea de su partido con mayor frecuencia, clasificados por conteo de disidencias

La salida clave es una comparación de cuatro dimensiones:

ÍndiceBaseQué Mide
Nominal (escaños)Conteo de escañosRepresentación formal
Shapley-ShubikDistribución de escañosPoder de negociación (basado en permutaciones)
BanzhafDistribución de escañosPoder de negociación (basado en coaliciones)
EmpíricoVotos realesRelevancia en la práctica

Las divergencias entre poder nominal y empírico revelan partidos formalmente pequeños pero estratégicamente críticos (o viceversa).

Todos los métodos soportan análisis temporal a través de legislaturas.

Cada legislatura recibe su propio análisis independiente:

  • Espacios ideales W-NOMINATE separados
  • Grafos de co-votación y detección de comunidades independientes
  • Índices de poder específicos por cámara que reflejan cambios en escaños
  • nominate_cross_legislatura ubica a todos los legisladores en un espacio ideal compartido, permitiendo comparación directa entre periodos
  • Seguimiento de evolución de comunidades: qué legisladores cambian de comunidad entre legislaturas, y qué implica eso sobre realineamiento de coaliciones

El seguimiento del puntaje de modularidad de Louvain a través de legislaturas revela cambios en la cohesión de los bloques de votación:

  • Modularidad creciente: los partidos votan de forma más cohesiva, divisiones partidistas más marcadas
  • Modularidad decreciente: aumenta la votación cruzada, los bloques se disuelven o realinean
  • Caídas súbitas: pueden indicar un evento legislativo importante (votación de reforma, cambio de liderazgo) que interrumpió los patrones normales de votación