Politics

We show how to use scikit-network to analyse the way deputies vote and their proximity to the majority. We here consider the French National Assembly (XVth legislature, from 2017 to 2020). The considered graph is the bipartite graph between deputies and bills.

[1]:
from IPython.display import SVG
[2]:
import numpy as np
[3]:
from sknetwork.data import load_netset
from sknetwork.clustering import Louvain
from sknetwork.regression import Diffusion
from sknetwork.ranking import top_k
from sknetwork.embedding import Spectral
from sknetwork.visualization import visualize_graph

Data

The dataset is part of the NetSet collection.

[4]:
graph = load_netset('fr-assembly')
Downloading fr-assembly from NetSet...
Unpacking archive...
Parsing files...
Done.
[5]:
biadjacency = graph.biadjacency
position = graph.position
names = graph.names_row
bills = graph.names_col
labels = graph.labels
label_colors = graph.label_colors
names_labels = graph.names_labels
[6]:
n_deputy, n_bill = biadjacency.shape
[7]:
print(names_labels)
['Non inscrit' 'Les Républicains'
 'Les Constructifs : républicains, UDI, indépendants'
 'Libertés et Territoires' 'UDI et Indépendants'
 'UDI, Agir et Indépendants' 'Mouvement Démocrate et apparentés'
 'La République en Marche' 'Socialistes et apparentés' 'Nouvelle Gauche'
 'Gauche démocrate et républicaine' 'La France insoumise']
[8]:
# parameters for visualization
node_size = 4
width = 480
height = 300
[9]:
image = visualize_graph(position=position, labels=labels, node_size=node_size, width=width, height=height,
                  label_colors=label_colors)
SVG(image)
[9]:
../_images/use_cases_votes_11_0.svg
[10]:
labels_majority = [6,7]
[11]:
print(names_labels[labels_majority])
['Mouvement Démocrate et apparentés' 'La République en Marche']
[12]:
# majority
majority = np.isin(labels, labels_majority)
np.sum(majority)
[12]:
356
[13]:
# opposition
opposition = ~np.isin(labels, labels_majority)
np.sum(opposition)
[13]:
221

Votes

[14]:
biadjacency_for = biadjacency > 0
biadjacency_against = (-biadjacency > 0)
[15]:
biadjacency_for
[15]:
<577x2807 sparse matrix of type '<class 'numpy.bool_'>'
        with 119901 stored elements in Compressed Sparse Row format>
[16]:
bills_for = biadjacency_for.T.dot(np.ones(n_deputy))
bills_against = biadjacency_against.T.dot(np.ones(n_deputy))
bills_total = bills_for + bills_against
[17]:
print('Average participation = ', np.round(np.sum(bills_total) / n_bill / n_deputy, 2))
Average participation =  0.17

Clustering

[18]:
louvain = Louvain()
[19]:
labels_pred = louvain.fit_predict(biadjacency_for)
[20]:
np.unique(labels_pred, return_counts=True)
[20]:
(array([0, 1, 2]), array([213, 363,   1]))
[21]:
labels_pred_majority, counts_majority = np.unique(labels_pred[majority], return_counts=True)
[22]:
label_pred_majority = labels_pred_majority[np.argmax(counts_majority)]
[23]:
image = visualize_graph(position=position, labels=labels_pred, node_size=node_size, width=width, height=height, label_colors=['red','blue', 'lightgrey'])
SVG(image)
[23]:
../_images/use_cases_votes_27_0.svg
[24]:
neutral = np.argwhere(labels_pred==2).ravel()
[25]:
print(names[neutral])
['Laure de La Raudière']
[26]:
# dissident
print(names[majority * (labels_pred!=label_pred_majority)])
['Frédérique Dumas' 'Maud Petit' 'Sonia Krimi' 'Richard Ramos'
 'Sébastien Nadot']

Diffusion

[27]:
diffusion = Diffusion(n_iter=4)
[28]:
values = diffusion.fit_predict(biadjacency_for, values_row=majority)
[29]:
image = visualize_graph(position=position, scores=values, node_size=node_size,
                  width=width, height=height)
SVG(image)
[29]:
../_images/use_cases_votes_34_0.svg
[30]:
# top-10 deputies for majority
index = np.argwhere(majority).ravel()
top = index[top_k(values[index], 10)]
print(names[top])
['Émilie Chalas' 'Didier Paris' 'Guillaume Vuilletet'
 'Élise Fajgeles|||Benjamin Griveaux' 'Marie Guévenoux' 'Richard Ferrand'
 'Yaël Braun-Pivet' 'Sacha Houlié' 'Jean Terlier' 'Thomas Rudigoz']
[31]:
# bottom-10 deputies for majority
bottom = index[top_k(-values[index], 10)]
print(names[bottom])
['Frédérique Dumas' 'Maud Petit' 'Richard Ramos' 'Agnès Thill'
 'Brahim Hammouche' 'Jimmy Pahun' 'Sébastien Nadot' 'Josy Poueyto'
 'Max Mathiasin' 'Sonia Krimi']
[32]:
# top-10 deputies for opposition
index = np.argwhere(opposition).ravel()
top = index[top_k(-values[index], 10)]
print(names[top])
['Ugo Bernalicis' 'Adrien Quatennens' 'Alain Bruneel' 'Clémentine Autain'
 'Nicolas Dupont-Aignan' 'José Evrard' 'Sébastien Jumel' 'Fabien Roussel'
 'Alexis Corbière' 'Hubert Wulfranc']
[33]:
# bottom-10 deputies for opposition
bottom = index[top_k(values[index], 10)]
print(names[bottom])
['Jean-Luc Warsmann' 'Olivier Dassault' 'Napole Polutele|||Sylvain Brial'
 'Stéphane Demilly' 'Michèle Tabarot' 'Thierry Robert|||Jean-Luc Poudroux'
 'Bernard Deflesselles' 'Franck Riester|||Patricia Lemoine'
 'Philippe Gomès' 'Franck Marlin']

Bills

[34]:
# labels are on deputies so you need an odd number of iterations
diffusion = Diffusion(n_iter=5)
[35]:
diffusion.fit(biadjacency_for, values_row=majority)
[35]:
Diffusion(n_iter=5, damping_factor=0.5)
[36]:
values_bill = diffusion.values_col_
[37]:
# top-5 bills for majority
for i in top_k(values_bill, 5):
    print(bills[i] + '\n')
l'article 27 du projet de loi de programmation 2018-2022 et de réforme pour la justice (première lecture).

l'amendement n° 2362  de Mme Marsaud après l'article 60 du projet de loi portant évolution du logement, de l'aménagement et du numérique (première lecture)

l'amendement de suppression n° 71 du Gouvernement à l'article 3 de la proposition de loi d'orientation et de programmation relative à la sécurité intérieure (première lecture).

l'amendement de suppression n° 72 du Gouvernement à l'article 9 de la proposition de loi d'orientation et de programmation relative à la sécurité intérieure (première lecture).

l'article 13 du projet de loi de programmation 2018-2022 et de réforme pour la justice (première lecture).

[38]:
# top-5 bills for opposition
for i in top_k(-values_bill, 5):
    print(bills[i] + '\n')
l'amendement n° 208 de Mme Obono après l'article 31 bis du projet de loi de programmation 2018-2022 et de réforme pour la justice (première lecture).

l'amendement n° 205 de Mme Obono après l'article 30 du projet de loi de programmation 2018-2022 et de réforme pour la justice (première lecture).

l'amendement n° 602 de M. Bernalicis à l'article 32 du projet de loi de programmation 2018-2022 et de réforme pour la justice (première lecture).

l'amendement n° 11 de M. Bernalicis à l'article unique de la proposition de loi renforçant la lutte contre les rodéos motorisés (première lecture).

l'amendement n° 272 de Mme Obono à l'article 50 du projet de loi de programmation 2018-2022 et de réforme pour la justice (première lecture).

[39]:
# top-5 controversial
for i in top_k(-np.abs(values_bill-0.5), 5):
    print(bills[i] + '\n')
l'amendement n? 195 de M. Chassaigne et l'amendement identique suivant a l'article 18 du projet de loi relatif a la programmation militaire pour les annees 2019 a 2025 et portant diverses dispositions interessant la defense (premiere lecture).

l'amendement n° 1310 de M. Lagarde après l'article 59 du projet de loi portant évolution du logement, de l'aménagement et du numérique (première lecture)

l'amendement n° 1123 de M. Chiche avant l'article 2 du projet de loi de finances rectificative pour 2020 (première lecture).

l'amendement n° 14 de M. Abad à l'article 2 bis de la proposition de loi visant à la consolidation du modèle français du don du sang (première lecture).

l'amendement n? 223 de M. Aviragnet a l'article 5 du projet de loi organique pour la confiance dans la vie publique (premiere lecture).

Embedding

[40]:
spectral = Spectral(2, normalized=False)
[41]:
embedding = spectral.fit_transform(biadjacency_for)
[42]:
image = visualize_graph(position=embedding, names=names, labels=labels, node_size=5, width=400, height=1000,
                  label_colors=label_colors)
SVG(image)

[42]:
../_images/use_cases_votes_49_0.svg