En primer lugar, necesitamos datos.
Descargué datos de todas las leyes votadas y cómo votó cada miembro del Congreso desde 2023 a 2024 hasta el 18 de mayo. Todos los datos están disponibles en la C brasileñaportal de datos abiertos del congreso. Luego creé dos marcos de datos de pandas diferentes, uno con todas las leyes votadas 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')])
Hacia votacoes marco de datos, seleccioné solo las entradas con idOrgao de 180, lo que significa que fueron votados en la cámara principal del Congreso. Entonces, tenemos los datos de los votos de la mayoría de los congresistas. Luego usé esta la 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 fila 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 diputados 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é todas las NA restantes con 0,5 para no interferir con el posicionamiento del congresista. Finalmente, calculamos la similitud entre los vectores de cada diputado utilizando la similitud del 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)
Ahora, use la información sobre las similitudes de votación entre congresistas para construir una red usando Networkx. Un nodo representará a cada miembro.
import networkx as nxnames = 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 una similitud de al menos el 75% del comportamiento electoral de los dos congresistas. Además, para abordar el hecho de que algunos congresistas tienen docenas de pares con altos grados de similitud, solo seleccioné a los primeros 25 congresistas con la mayor similitud para darles 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 plano. Decidí usar el diseño de resorte, que usa los bordes como resortes que mantienen los nodos cerca 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 individualmente los bordes y nodos 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:
Bueno, es un buen comienzo. Se pueden ver diferentes grupos de congresistas, lo que sugiere que captura con precisión la alineación política y las alianzas en el Congreso. Pero es un desastre y es imposible discernir realmente qué está pasando.
Para mejorar la visualización, hice que el nombre apareciera solo cuando pasas el cursor sobre el nodo. Además, coloreé los nodos según los partidos políticos y coaliciones disponibles en el sitio del Congreso y los dimensioné según la cantidad de bordes a los que están conectados.
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.