La ciencia de datos se encuentra con la política. Desenredando la dinámica del Congreso con… | de Luiz Venosa | Septiembre 2024

La ciencia de datos se encuentra con la política. Desenredando la dinámica del Congreso con… | de Luiz Venosa | Septiembre 2024

Primero, necesitamos datos.

Descargué datos sobre cada ley aprobada y cómo votó cada miembro del Congreso desde 2023 a 2024 hasta el 18 de mayo. Todos los datos disponibles en Brasil Cel portal de datos abiertos del congreso. Luego creé dos marcos de datos de pandas diferentes, uno con todas las leyes aprobadas y otro con cómo votó cada miembro del congreso en cada votación.

votacoes = pd.concat([pd.read_csv('votacoes-2023.csv', header=0, sep=';'),  pd.read_csv('votacoes-2024.csv', header=0, sep=';')])
votacoes_votos_dep = pd.concat([pd.read_csv('votacoesVotos-2023.csv', sep=';', quoting=1) , pd.read_csv('votacoesVotos-2024.csv', sep=';', on_bad_lines='warn', quoting=1, encoding='utf-8')])

En votos marco de datos, seleccioné solo entradas con idOrgao de 180, lo que significa que fueron aprobados por la cámara principal del Congreso. Entonces tenemos datos de votación para la mayoría de los miembros del Congreso. Luego usé esta lista de votacoes_Ids para filtrar el votacoes_votos_dep marco de datos.

plen = votacoes[votacoes['idOrgao'] == 180]
votacoes_ids = plen['id'].unique()
votacoes_votos_dep = votacoes_votos_dep[votacoes_votos_dep['idVotacao'].isin(votacoes_ids)]

Ahora en el votacoes_votos_dep, Cada voto es una línea con el nombre del congresista y el ID de la sesión de votación para identificar a quién y a qué se refiere el voto. Por lo tanto, creé una tabla dinámica para que cada fila represente a un miembro del congreso y cada columna haga referencia a un voto, codificando Sí como 1 y No como 0 y eliminando cualquier voto en el que más de 280 miembros no votaron.

votacoes_votos_dep['voto_numerico'] = votacoes_votos_dep['voto'].map({'Sim': 1, 'Não':0})
votes_pivot = votacoes_votos_dep.pivot_table(index='deputado_nome', columns='idVotacao', values='voto_numerico').dropna(axis=1, thresh=280)

Antes de calcular la matriz de similitud, llené todos los NA restantes con 0,5 para no interferir con el posicionamiento del congresista. Finalmente, calculamos la similitud entre los vectores de cada MP usando similitud de coseno y la almacenamos en un marco de datos.

from sklearn.metrics.pairwise import cosine_similarity
similarity_matrix = cosine_similarity(votes_pivot)
similarity_df = pd.DataFrame(similarity_matrix, index=votes_pivot.index, columns=votes_pivot.index)
Matriz de similitud – Imagen del autor

Ahora use la información sobre las similitudes de votación entre miembros del Congreso para construir una red usando Nredes. Un nodo representará a cada miembro.

import networkx as nx

names = similarity_df.columns
# Create the graph as before
G = nx.Graph()
for i, name in enumerate(names):
G.add_node(name)

Entonces, los bordes que conectan dos nodos representan al menos un 75% de similitud en el comportamiento de votación de los dos miembros del Congreso. Además, para tener en cuenta el hecho de que algunos miembros del Congreso tienen docenas de pares con altos grados de similitud, seleccioné sólo a los 25 miembros principales del Congreso con la mayor similitud para recibir una ventaja.

threshold = 0.75
for i in range(len(similarity_matrix)):
for j in range(i + 1, len(similarity_matrix)):
if similarity_matrix[i][j] > threshold:
# G.add_edge(names[i], names[j], weight=similarity_matrix[i][j])
counter[names[i]].append((names[j], similarity_matrix[i][j]))
for source, target in counter.items():
selected_targets = sorted(target, key=lambda x: x[1], reverse=True)[:26]
for target, weight in selected_targets:
G.add_edge(source, target, weight=weight)

Para visualizar la red, es necesario decidir la posición de cada nodo en el plan. Decidí usar el diseño de resorte, que usa los bordes como resortes que mantienen unidos los nudos mientras intenta separarlos. Agregar una semilla permite la reproducibilidad ya que es un proceso aleatorio.

pos = nx.spring_layout(G, k=0.1,  iterations=50, seed=29)

Finalmente, trazamos la red usando una figura de Go y agregamos bordes y nodos individualmente según su posición.


# Create Edges
edge_x = []
edge_y = []
for edge in G.edges():
x0, y0 = pos[edge[0]]
x1, y1 = pos[edge[1]]
edge_x.extend([x0, x1, None])
edge_y.extend([y0, y1, None])

# Add edges as a scatter plot
edge_trace = go.Scatter(x=edge_x, y=edge_y, line=dict(width=0.5, color='#888'), hoverinfo='none', mode='lines')
# Create Nodes
node_x = []
node_y = []
for node in G.nodes():
x, y = pos[node]
node_x.append(x)
node_y.append(y)

# Add nodes as a scatter plot
node_trace = go.Scatter(x=node_x, y=node_y, mode='markers+text', hoverinfo='text', marker=dict(showscale=True, colorscale='YlGnBu', size=10, color=[], line_width=2))

# Add text to the nodes
node_trace.text = list(G.nodes())

# Create a figure
fig = go.Figure(data=[edge_trace, node_trace],
layout=go.Layout(showlegend=False, hovermode='closest', margin=dict(b=0,l=0,r=0,t=0), xaxis=dict(showgrid=False, zeroline=False, showticklabels=False), yaxis=dict(showgrid=False, zeroline=False, showticklabels=False)))

fig.show()

Resultado:

Imagen del autor

Bueno, ese es un buen comienzo. Se pueden observar diferentes grupos de miembros del Congreso, lo que sugiere que refleja con precisión el alineamiento político y las alianzas en el Congreso. Pero es un desastre y es imposible entender realmente lo que está pasando.

Para mejorar la visualización, hice que el nombre apareciera solo al pasar el cursor sobre el nodo. Además, coloreé los nodos según los partidos políticos y las coaliciones disponibles en el sitio del Congreso y los dimensioné según la cantidad de bordes a los que están conectados.

Imagen del autor

Es mucho mejor. Tenemos tres grupos, con algunos nodos entre ellos y algunos más grandes en cada uno. Además, en cada grupo hay una mayoría de un color particular. Bueno, analicémoslo.