Collecte et visualisation de données législatives avec R

Le groupe d’utilisateurs R du Muséum national d’Histoire naturelle de Paris (le groupe “Sémin-R”) organise une conférence R demain. Cliquez sur l’image ci-dessous pour le programme complet :

JourneeR-2016-Programme

J’y présenterai les slides résumées ci-dessous (cliquez sur n’importe quelle image pour la version PDF complète, ou utilisez la version en ligne), et si tout se passe bien, j’aurai un court papier technique à partager sur le sujet dans moins d’un mois :

Briatte_R_MNHN_2016-2

Briatte_R_MNHN_2016-3

Briatte_R_MNHN_2016-4

La présentation reprend, en substance, une brève note que j’ai récemment publiée dans Network Science. Cliquez sur l’image ci-dessous pour accéder au preprint ou lisez directement son annexe pour le détail des données :

Briatte_NWS_published

Comme d’habitude, le code et les données sont en ligne — j’en ai déjà parlé à plusieurs occasions cette année, et vous risquez d’en entendre encore parler dans les années qui arrivent (plus de détails quand il y en aura à partager).

Affaire à suivre, donc, mais bon, c’est pas comme si je ne vous avais pas déjà un peu cassé les pieds avec R et la visualisation de réseaux de collaborations législatives depuis quelques temps déjà.

J’en profite pour saluer amicalement les collègues suisses, et Marion Deville en particulier, qui participent à la Semaine de la démocratie de Genève, et qui ont prévu d’utiliser certains de mes graphiques dans une présentation de leurs travaux lors d’un hackathon organisé le 23 septembre. Je n’ai pas beaucoup plus d’informations là-dessus, mais vous pouvez écrire à Marion si vous êtes intéressés.

En attendant, il reste quelques places pour la conférence de demain : écrivez aux gentils organisateurs (leurs coordonnées sont sur le site du Sémin-R), ou faîtes-moi très vite signe, et je ferai suivre. À demain !

Mise à jour, le jour même : malgré un léger gros problème qui m’a fait rater la matinée, je peux confirmer que la journée R du MNHN était, comme prévu, hyper-chouette, le cadre du Muséum d’Histoire Naturelle étant très agréable par ailleurs.

À noter dans les présentations, qui seront bientôt en ligne sur le site du RUG MNHN :

  • les dernières améliorations du package FactoMineR – et Factoshiny
  • l’utilisation de scripts R sur un serveur Apache
  • la visualisation de cartes auto-organisatrices
  • un exemple d’interfaçage R + Java
  • la sonification (!) de graphiques R/ggplot2

La dernière présentation, par Jérôme Sueur (l’un des deux organisateurs de la journée, avec Amandine Blin) était très divertissante. Pour comprendre pourquoi, jetez un coup d’oeil au package playitbyr.

Notes sur R et l’analyse de réseaux

Comme promis la semaine dernière, nouvelle annonce qui consacre mes deux obsessions pour la programmation statistique avec R et l’analyse de réseaux, toujours avec R :

r-notes

Le blog est syndiqué sur R-Bloggers, qui agrège plein de blogs sur R. Vous pouvez aussi vous y abonner par son flux Atom ou par son flux RSS.

Le moteur du blog est une version bidouillée de Dropplets.

Quelques notes déjà rédigées

J’ai commencé ce nouveau blog pour stocker des notes dont je suis le premier utilisateur, notamment sur les sujets suivants :

Je me suis aussi servi du blog pour documenter des résultats bizarres, faire des suggestions sur l’enseignement avec RStudio, et tenter des petites expériences avec jQuery, telles que celle-ci avec l’API Pinboard.

Il s’agit vraiment de notes plus que d’un blog : tout est rédigé dans un jargon pas très amusant, et il n’y a pas de commentaires, ni même de lien évident pour me contacter — d’où cette nouvelle publication en dehors du ‘bistro.

Dernière petite chose, vous pouvez désormais apercevoir les derniers billets du Polit’bistro et de R / Notes sur ma homepage universitaire, encore une fois grâce à jQuery, et plus précisément grâce au plugin FeedEk, qui permet de récupérer des contenus de fils RSS :

blogs-rss-feedek

Pour les fans, il y a aussi un peu de jQuery (avec beaucoup de JavaScript et un peu d’animations CSS3) sur la page où j’héberge ma frimousse.

Bonne(s) lecture(s) !

Notice technique, illustrée par la communication officielle du Front national

Suite aux séries de billets sur les “unes” de Charlie Hebdo, les revues de la sociologie française et les réseaux législatifs, j’ai reçu quelques emails – merci ! – me demandant de réexpliquer “la base” du truc :

  • comment est-ce que l’on fait pour récupérer les données ?
  • comment est-ce que l’on fait pour en sortir un réseau ?

Je vais prendre un petit exemple facile pour illustrer le principe : comment récupérer la communication officielle du Front national à partir de son site Internet, et comment en extraire un réseau de co-occurrences.

Pour cet exemple, je suppose que vous connaissez un minimum de langage HTML. Si ce n’est pas le cas, vous aurez besoin d’une semaine au maximum pour apprendre tout ce qu’il faut savoir. Le langage HTML servant à “écrire Internet”, c’est aussi le langage le mieux documenté sur Internet : commencez ici, par exemple.

Les outils

Commençons par la base.

Il vous faudra une bonne connexion Internet : idéalement de la fibre optique, mais plus généralement, une connexion qui ne va pas tomber en rade et rajouter des erreurs de téléchargement dans une procédure qui risque déjà d’en générer par elle-même. Votre connexion Ethernet universitaire, qui est probablement filtrée d’une manière ou d’une autre, n’est peut-être pas la candidate idéale ; le wifi de votre université, encore moins.

Ensuite, il vous faut un peu d’espace disque. Utiliser un disque externe pour télécharger des centaines de fichiers est une très mauvaise idée : la moindre erreur d’écriture peut envoyer l’intégralité du disque dans les Limbes. L’idéal est d’utiliser une clé USB pas chère mais de bonne qualité, histoire que le disque dur ne soit jamais menacé. Si votre disque dur est un SSD, le risque de le foutre en l’air est toutefois minime.

Enfin, il vous faut un langage de programmation, et si possible, un langage qui va vous permettre d’enchaîner les opérations (téléchargement, visualisation, modélisation) de manière fluide. J’utilise R, mais Python est un autre bon candidat. R est bien documenté, bien illustré, la communauté répond vite et bien aux questions, et les toutes dernières techniques de modélisation y sont très bien représentées.

Il est vivement conseillé d’utiliser RStudio pour coder en R. Il vous faudra aussi un navigateur Internet avec une interface de développement intégrée, pour visualiser le code source des pages HTML que vous voulez récupérer : Chrome, Firefox, Opera ou Safari feront l’affaire.

Les packages

Quel que soit votre choix de langage, vous allez devoir vous servir d’extensions pour accomplir les différentes opérations dont vous avez besoin. Pour ce petit exemple, on va utiliser les cinq packages R suivants :

  • rvest, pour récupérer les données
  • lubridate, pour en convertir les dates
  • stringr, pour manipuler les données textuelles
  • dplyr, pour manipuler les données
  • ggplot2, pour visualiser les données

Incidemment, tous ces packages ont été développés par la même personne, avec l’aide de plusieurs autres.

On rajoutera quelques packages quand on en arrivera à l’étape “réseaux”.

La source

Toutes les donnés que l’on va récupérer proviennent de la section “Actualités” du site Internet frontnational.com. La structure de l’information qui s’y trouve est régulière : la section contient une suite d’articles qui appartiennent à une ou plusieurs catégories, avec parfois un ou plusieurs mots-clés supplémentaires. Chaque article possède un titre, qui renvoie à une page individuelle, et une date, exprimée en français :

Screen Shot 2015-03-15 at 12.58.52

Jetez à présent un coup d’oeil à la source HTML de la page “Actualités” avec les Developer Tools de votre navigateur (j’utilise ceux de Chrome) : en quelques clics et recherches par mots-clés, vous allez y trouver le code HTML qui structure l’information. Ici, les articles de la page se trouvent dans le diviseur #pk_content, les titres des articles sont codés comme des titres de niveau 3 de classe .pk_entry_title, et chaque titre fait l’objet d’un lien a :

Screen Shot 2015-03-15 at 13.06.58

Vous savez à présent ce que vous allez chercher dans chaque page. Dernière étape : combien de pages va-t-on récupérer ? Le système de pagination du site est ultra-simple à identifier :

Screen Shot 2015-03-15 at 13.19.13

Chaque page est numérotée de 2 à X, l’adresse étant de type /actualites/page/4, par exemple. La page finale est identifiable par essai-erreur : si vous essayez d’accéder à la page /actualites/page/200, vous obtenez une erreur. Cherchez un peu, et vous allez trouver qu’à l’heure actuelle, il y a 179 pages qui vous ramènent jusqu’à l’actualité du Front national en avril 2013—soit presque deux ans de données.

Le code

Pour récupérer les titres, liens, dates, catégories et mots-clés de tous les articles de la section “Actualités” du site Internet du Front national :

  1. On crée une boucle qui va de 179 à 1 ;
  2. On télécharge la page correspondant à cette valeur ; et
  3. On extrait ces informations de la page.

La base du truc ressemble à ça :

for(i in 179:1) {

  # l'adresse de la page sur le site Internet
  p = paste0("http://www.frontnational.com/actualites/page/", i)

  # le nom de fichier de la page téléchargée
  f = paste0("page", i, ".html")

  # télécharger la page si le fichier est absent
  if(!file.exists(f))
    download.file(p, f, mode = "wb", quiet = TRUE)

}

La condition if(!file.exists(f)) permet de relancer le code en cas d’erreur—mais il n’y en aura pas dans cet exemple : le site ne pose pas de souci, et ma connexion Internet n’en a pas posé non plus.

On dispose à présent de toutes les pages HTML de la section “Actualités”, et à chaque itération, l’adresse du fichier de la page est stocké dans la variable f. Pour extraire les informations de cette page :

# lire la page
h = html(f)

# extraire le lien de chaque article
url = html_nodes(h, "#pk_content h3.pk_entry_title a") %>% 
  html_attr("href")

# extraire le titre de chaque article
title = html_nodes(h, "#pk_content h3.pk_entry_title a") %>% 
  html_text()

# extraire les dates, catégories et mots-clés
meta = html_nodes(h, ".pk_entry_meta p") %>% 
  html_text()

On utilise ici les fonctions du package rvest. Rapide explication de texte :

  • Les deux premiers appels de la fonction html_nodes extraient les titres ;
  • html_attr("href") en extrait le lien individuel, html_text() en extrait le titre; et
  • l’opérateur %>% permet d’enchaîner les opérations de manière lisible

Cette syntaxe est une simplification de la syntaxe XPath, qui sert à parcourir les documents XML, dont font partie les documents HTML. Le package XML permet de faire la même chose, mais de manière moins lisible.

On obtient trois différentes variables textuelles à partir de ces opérations :

  • La variable url va contenir des liens de ce type :
    [1] "http://www.frontnational.com/2015/03/lettre-ouverte-de-marine-le-pen-aux-acteurs-des-politiques-du-handicap/"                              
    [2] "http://www.frontnational.com/2015/03/document-de-campagne-departementales-22-et-29-mars-2015/"                                             
    [3] "http://www.frontnational.com/2015/03/lettre-de-marine-le-pen-a-lattention-de-monsieur-olivier-schrameck-president-du-csa/"
  • La variable title va contenir des titres :
    [1] "Lettre ouverte de Marine Le Pen aux acteurs des politiques du handicap"                                
    [2] "Document de campagne départementales 22 et 29 mars 2015"                                               
    [3] "Lettre de Marine Le Pen à l’attention de Monsieur Olivier Schrameck, Président du CSA"
  • La variable meta va contenir le reste :
    [1] "13 mars 2015/Tribunes libres/Mots-clefs: Handicap"                          
    [2] "12 mars 2015/Annonces/Mots-clefs: Départementales 2015"                     
    [3] "12 mars 2015/Tribunes libres/Mots-clefs: CSA, Injures"

La dernière variable contient plusieurs informations, il va donc falloir un peu plus de code pour la séparer dans ses différents éléments :

  • On commence par extraire le texte de la date à partir d’une expression régulière qui en repère l’année (élément \\d{4}, qui cherche quatre chiffres consécutifs), puis on la convertit du format français “jour mois année” vers un format générique :
    date = str_extract(meta, "(.*)\\d{4}/")
    date = parse_date_time(str_sub(date, end = -2), 
                           "%d %B %Y", locale = "FR_fr")
    date = as.Date(date)
  • On extrait ensuite la ou les catégorie(s), qui se situent toujours après le premier caractère / de la variable meta (c’est-à-dire après la date). Pour ce faire, on utilise le caractère / pour diviser la variable en plusieurs petits bouts de texte, et l’on sélectionne le second :
    category = sapply(str_split(meta, "/"), function(x) x[2])
  • Enfin, on répète l’opération pour le bloc de texte correspondant aux mots-clés, en prenant soin de virer le texte dont on n’a pas besoin au passage. Il n’y pas toujours de mots-clés : on se prémunit donc contre cette possibilité en prévoyant la possibilité d’une variable manquante NA.
    tags = sapply(str_split(meta, "/"), function(x) {
      x = x[ grepl("Mots-clefs", x) ]
      x = gsub("Mots-clefs: ", "", x)
      ifelse(length(x), x, NA)
    })

En mettant toutes ces opérations à la chaîne, et en stockant les informations que l’on extrait dans un jeu de données, on arrive à 42 lignes de code qui collectent 3 581 liens et titres d’articles de la source. Tous les articles sont catégorisés, mais 728 d’entre eux n’ont pas de mots-clés.

Visualisation hyper-basique

Cherchons à visualiser un aspect très simple du corpus : qui parle au nom du Front national auprès des médias, et dans quelles proportions ?

Pour cela, on retient les 977 articles qui appartiennent à la catégorie “Médias”, et l’on extrait les noms des intervenants, encore une fois à l’aide d’une expression régulière1. L’expression en question est élaborée en jetant un rapide coup d’oeil aux titres des articles, et en rassemblant leurs intervenants dans une même expression un peu longue :

"Marine (L|l)e Pen|Marion( |-)Maréchal|Nicolas Bay|Gollnisch|F(l)?(orian|\\.) (P|p)hilippot|Louis Aliot|Steeve Briois|Sébastien Chenu|Stéphane Ravier|Thibault de la Tocnaye|Wallerand de Saint( |-)Just|Marie-Christine Arnautu|Bousquet-Cassagne|Gilbert Collard|Julien Sanchez|Julien Rochedy|Jean-Marie Le Pen|Lacoste-Lareymondie|Philippe Murer|Sophie Montel|Laurent Lopez|Gaëtan Dussausaye|David Rachline|Bernard Monot|Aymeric Chauprade|Michel Guiniot|Edouard Cavin|Jean-Claude Otto-Bruc|Dominique Martin"

Cette expression repère tous les intervenants, et ne laisse que 16 articles sans intervenants de côté.

Il ne reste plus qu’à visualiser la quantité d’articles produites par le Front national pour sa catégorie “Médias” entre avril 2013 et aujourd’hui, en coloriant les articles par les noms des intervenants, et après avoir regroupé les intervenants avec moins de 10 articles (seuil arbitraire) dans une catégorie résiduelle. Ce qui nous donne :

fn_medias_bars

Florian Philippot est en brun, et Marine Le Pen est en bleu (clair). Je vous promets que je n’ai pas choisi les couleurs : il s’agit des couleurs par défaut de l’excellent ggplot2.

Un dernier petit coup d’oeil, pour terminer, aux mois qui ont donné le plus d’articles, et aux intervenants de ces articles. Cette fois-ci, on utilise les fonctions du package dplyr pour agréger le corpus (objet m) par mois (variable ym) et par intervenant (variable who2), puis pour compter “leurs” articles, puis pour voir les dix premiers mois classés par nombre d’articles :

filter(m, who2 != "(autres)") %>% 
  group_by(who2, ym) %>% 
  summarise(n = n()) %>% 
  group_by() %>% 
  arrange(-n)

Ce qui nous donne Florian Philippot et Marine Le Pen en tête, et très loin devant le reste du peloton. Leurs mois d’activité médiatique les plus intenses correspondent aux élections municipales de mars 2014 et à la formation du premier gouvernement Valls :

1  florian philippot  2014-5 27
2      marine le pen  2014-3 23
3  florian philippot 2013-10 21
4  florian philippot  2015-2 20
5  florian philippot 2014-11 19
6  florian philippot  2013-6 18
7  florian philippot  2015-1 18
8  florian philippot 2014-12 17
9      marine le pen  2013-9 17
10 florian philippot 2013-12 16

Enfin, si l’on répète l’opération avec la catégorie “Interventions”, on trouve les députés Gilbert Collard et Marion Maréchal-Le Pen en tête de cortège, suivis des eurodéputés frontistes, mais sur des quantités moindres (il n’y a que 336 articles, soit trois fois moins de données) et plus irrégulières :

fn_interventions_bars

Pour reproduire ces résultats, allez voir le code, il y en a pour même pas 100 lignes.

Visualisation en réseau

Revenons sur la totalité du corpus. Ce corpus contient 3 581 articles constituant les “actualités” du Front national, et 80% des articles possèdent des mots-clés. Il y a 699 mots-clés différents dans le corpus. Voici les premiers d’entre eux, qui apparaissent plus de 100 fois dans le corpus, c’est-à-dire dans plus de 3% des articles possédant des mots-clés :

Européennes 2014 118
Immigration      133
Insécurité       102
Islam radical    111
Municipales 2014 261
Paris            150
Valls            122

Aucune surprise. Voyons à présent quelles associations de mots-clés reviennent fréquemment dans ce corpus.

Première étape : créer un jeu de données à deux colonnes, où chaque ligne contient une association de deux mots-clés conjointement présents sur un même article. En langage réseau, ça s’appelle une edge list non dirigée, et en langage R, ça peut se construire comme ça, à partir de la variable b$tags contenant les mots-clés de chaque article :

# associations de mots-clés dans chaque article
e = lapply(b$tags, function(x) {
  
  y = unique(unlist(strsplit(x, ",\\s?")))
  y = expand.grid(i = y, j = y, stringsAsFactors = FALSE)
  y = filter(y, i != j)
  y = apply(y, 1, function(x) paste0(sort(x), collapse = "///"))
  y = unique(y)
  data.frame(i = gsub("(.*)///(.*)", "\\1", y),
             j = gsub("(.*)///(.*)", "\\2", y),
             stringsAsFactors = FALSE)
  
})

# assemblage des associations en une seule liste
e = bind_rows(e)

# pondération de chaque association par sa fréquence
e = group_by(e, i, j) %>% summarise(n = n())

Pour la suite, il va nous falloir utiliser le package network pour convertir cette liste en objet “réseau”, auquel on va rajouter la pondération de chaque association de mots-clés, c’est-à-dire la fréquence de chacune de ces associations dans le corpus, que l’on a calculée à la toute fin du bloc de code précédent :

n = network(e[, 1:2 ], directed = FALSE)
set.edge.attribute(n, "weight", e[, 3])

Bon, à ce stade, on a le réseau complet des co-occurrences de mots-clés dans les articles “Actualités” du site du Front national, d’avril 2013 à aujourd’hui. Ça fait beaucoup, ce dont on peut se rendre compte en visualisant le réseau à l’état brut. On produit cette visualisation avec la fonction ggnet, dont on a déjà parlé :

fn_network_0

L’épaisseur des liens entre deux mots-clés est ici proportionnelle à leur fréquence d’apparition dans le corpus, simplifiée en cinq catégories correspondant à des fréquences d’associations de 1 (co-occurrence unique), 2, 3, 4 et 5 ou plus. Le code de visualisation ressemble à ça :

# diviser les co-occurrences en 5 catégories : 1, 2, 3, 4 et 5+
w = as.numeric(cut(e[, 3], c(0, 1, 2, 3, 4, Inf)))
w = w / max(w)
# visualiser le réseau complet en faisant varier l'épaisseur des liens
ggnet(n, size = 1,
  segment.color = "black",
  segment.alpha = .5,
  segment.size = w)

Ce que l’on voit au centre, ce sont les associations de mots-clés qui reviennent fréquemment ; ce que l’on voit en périphérie, ce sont les associations de mots-clés beaucoup moins fréquentes. Concentrons-nous sur le centre en ne retenant que les associations de mots-clés apparaissant au moins cinq fois dans le corpus :

# sous-réseau des co-occurrences de fréquence 5+
nn = n
delete.edges(nn, which(w < 1))
delete.vertices(nn, isolates(nn))

ggnet(nn, size = 1,
      segment.alpha = .5, segment.color = "black",
      label.nodes = TRUE,
      label.size = 3 * as.numeric(cut(degree(nn), unique(quantile(degree(n))), 
                                      include.lowest = TRUE)))

On a ici rajouté le nécessaire pour faire apparaître les mots-clés en question, et pour leur donner une taille proportionnelle au nombre d’associations dans lesquelles ils apparaissent. Le résultat ne va pas non plus vous étonner :

fn_network_5

Visualisation animée

On introduit à présent un dernier package, animation, qui va nous permettre de montrer le réseau de co-occurrences de mots-clés à chaque étape de l’affinage entre le réseau complet, qui montre toutes les co-occurrences, jusqu’à la dernière, c’est-à-dire la co-occurrence de mots-clés la plus fréquente du corpus.

Le code ressemble à ce que l’on a déjà vu, mais dans une boucle qui élimine progressivement les associations de mots-clés les moins fréquentes :

# animation
saveGIF({
  
  w = sort(unique(e[, 3]))
  for(i in w[ -length(w) ]) {
    
    nn = n
    delete.edges(nn, which(e[, 3] <= i))
    delete.vertices(nn, isolates(nn))
    
    g = ggnet(nn, size = 0,
              segment.alpha = .5, segment.color = "black",
              label.nodes = TRUE,
              label.size = 3 * as.numeric(cut(degree(nn), 
                                              unique(quantile(degree(n))),
                                              include.lowest = TRUE))) +
      ggtitle(paste("Associations apparaissant au moins", i, "fois"))
    
    print(g)
    
  }
}, movie.name = "fn_network.gif", interval = 1)

Ce qui nous donne une suite de réseaux de plus en plus “vides”, où les mots-clés “Taubira” et “Paris” ont une assez longue durée de vie, et où les mots-clés “Paris” et “insécurité” survivent jusqu’en fin de chaîne :

fn_network

Voici le code complet de la partie “réseaux”.

Bilan technique

Dans ce billet, on a utilisé :

  • R et tout plein de packages R
  • Un navigateur Internet
  • De la syntaxe HTML et XPath
  • Des expressions régulières2
  • Un tout petit peu d’analyse de réseaux

Toutes ces technologies sont gratuites et bien documentées.

Le corpus “Actualités du Front national” a servi d’exemple parce qu’il ne présentait que des difficultés minimales, qu’il ne m’a fallu que quatre minutes pour le télécharger, et que je ne comptais en présenter que des aspects très simples. Je n’ai même pas téléchargé les articles complets, par exemple, ni envisagé leur analyse textuelle.

Les exemples que je montre sur ce blog sont souvent plus larges : le corpus des revues et articles de la plateforme Cairn.info, par exemple, que j’ai utilisé pour parler des revues de sociologie, contient plus de 400 revues et à peu près 20 000 articles. Mais la logique de base reste proche de celle détaillée ci-dessus.

Mise à jour, 18 décembre 2015 : le Front national ayant légèrement modifié les réglages de son serveur, il faut désormais faire semblant d’utiliser un navigateur pour accéder à son site Internet de manière programmatique. Le code de ce billet a été mis à jour pour ce faire, ce qui permet au passage d’étendre les données jusqu’à décembre 2015, soit 228 pages d’actualités contenant 1 329 interventions de représentants du Front national dans les médias.

Le Canard enchaîné de cette semaine s’est par ailleurs fait le relais des conflits internes au Front national qui opposent Florian Philippot aux soutiens de Marion Maréchal-Le Pen, une source indiquant à cette occasion que Nicolas Bay était de plus en plus envoyé dans les médias par le parti pour infléchir la représentation de ce dernier par Florian Philippot.

On peut vérifier cette affirmation de plein de manières, mais l’on se contentera ici de la plus simple, qui consiste à calculer, à partir de notre source (le seul site Internet du Front national), le pourcentage d’interventions médias du Front national représenté par Florian Philippot. Les barres en rouge indiquent les mois d’élection (municipales, européennes, départementales et régionales), et l’on a retiré les mois de juillet/août pour ne pas inclure de ratios calculés sur des petits dénominateurs :

fn_medias_philippot_bars_updated

Ce graphique permet de constater qu’avec moins d’une invention média sur cinq, Florian Philippot est effectivement à son plus bas taux de représentation médiatique du Front national en décembre 2015. Une diminution similaire était observable au moment des élections municipales. Jugé sur le même critère, Nicolas Bay reste une figure moins médiatique du parti sur la période observée, sauf justement à quelques occasions, dont le mois de décembre 2015 :

fn_medias_bay_bars

Je profite de cette mise à jour pour signaler que Joël est apparu dans l’émission Envoyé spécial du 17 décembre 2015, pour parler justement de Marion Maréchal-Le Pen.

  1. Attention, celle-ci n’extrait que le premier intervenant : s’il y en a plusieurs, le deuxième passe à la trappe. Il n’y a que cinq articles où le problème se pose. []
  2. Alexandre fait remarquer qu’on devrait les appeler “expressions rationnelles”. S’il vous faut d’autres exemples, en voici quelques-uns. []

Extraire un réseau de coauteurs depuis la base PubMed

La base MEDLINE contient environ 23 millions d’articles scientifiques, accessibles via le moteur de recherche PubMed depuis une vingtaine d’années. Ces articles sont utilisés pour toutes sortes d’analyse, des méta-analyses aux analyses de communautés sémantiques. La base PubMed, c’est très littéralement du big data, et les chercheurs comme les entreprises le savent depuis un bail1.

Le sujet vous donne une idée de ce que je vais faire de mes vacances, mais la phase données devrait être indolore : MEDLINE est une base extrêmement bien entretenue, et les opérations ci-dessous font moins saigner des yeux que, par exemple, au hasard (p < .0001), les fichiers du Ministère de l’Intérieur. De plus, on va travailler sur des requêtes précises plutôt que sur la base intégrale.

Allez découvrir PubMed si vous n’avez jamais fait un tour, c’est un tour de force du point de vue de la science archivistique et documentaire, et un fleuron discret de la médecine scientifique contemporaine, “basée sur les preuves” (id est publiée dans une revue scientifique avec un impact factor aussi élevé que possible ; pour les preuves, la maison demande un délai d’environ quinze années supplémentaires) :

Screen Shot 2014-06-14 at 5.26.38 PM

Télécharger une requête PubMed

Les articles en accès ouvert via PubMed Central (PMC) sont facilement téléchargeables depuis un serveur FTP, mais pour obtenir les résultats d’une requête sur la base intégrale, c’est un peu plus long. Heureusement, grâce à Kristoffer Magnusson, il y a ce bout de code vieux de deux ans, qui méritait un petit remix.

La première étape consiste à envoyer une requête PubMed du même type que celle que l’on rentre dans le moteur de recherche en ligne2, et à récupérer en retour un fichier XML qui contient la requête développée en plusieurs mots-clés, après vérification de leur disponibilité dans les mots-clés du thesaurus MeSH (Medical Subject Headings) de la National Library of Medicine. La requête health (inequalit*) donne ainsi :

("health"[MeSH Terms] OR "health"[All Fields]) AND (inequalites[All Fields] OR inequaliti[All Fields] OR inequalities[All Fields] OR inequalities'[All Fields] OR inequalitiies[All Fields] OR inequalitiy[All Fields] OR inequality[All Fields] OR inequality'[All Fields] OR inequality's[All Fields] OR inequalityin[All Fields] OR inequalitys[All Fields])

Mais vous avez plutôt intérêt à formuler la requête health AND (inequality or inequalities), qui va identifier les mots-clés correspondant à l’entrée MeSH consacrée aux facteurs socio-économiques, le nom donné par PubMed aux recherches sur les inégalités de santé. La requête devient alors, sans l’antislash de formatage3 :

("health"[MeSH Terms] OR "health"[All Fields]) AND \(("socioeconomic factors"[MeSH Terms] OR ("socioeconomic"[All Fields] AND "factors"[All Fields]) OR "socioeconomic factors"[All Fields] OR "inequality"[All Fields]) OR ("socioeconomic factors"[MeSH Terms] OR ("socioeconomic"[All Fields] AND "factors"[All Fields]) OR "socioeconomic factors"[All Fields] OR "inequalities"[All Fields]))

Le nombre total d’articles correspondant à la requête se trouve dans le même fichier XML et ne demande que peu d’efforts pour être récupéré, mais c’est surtout le “numéro unique” de la requête qui va nous intéresser : la variable NCID est une chaîne unique qui permet de récupérer les résultats de notre recherche (métadonnées et résumés des articles) sans se fatiguer, une fois placée dans une boucle récupérant n articles k fois.

C’est la seconde et dernière étape : en quelques lignes, on écrit une boucle de téléchargement qui demande les mille premiers articles PubMed correspondant à la requête, qu’on sauvegarde dans un fichier XML, puis les mille suivants, et ainsi de suite. Un fichier XML, c’est du contenu entouré de balises de marquage sémantique, comme dans cet exemple :

Screen Shot 2014-06-14 at 5.04.11 PM

La boucle de téléchargement écrit un rapport qui contient la datation des fichiers, histoire que vous puissiez la lancer en plusieurs fois si nécessaire sans recommencer au début. Le code, qui fait 300 lignes à tout casser, permet aussi de compter le nombre d’articles par année, par revue, ou les deux, ou par nombre d’auteurs.

Démo : la Convention-cadre OMS FCTC

La démo du code télécharge 236 articles en rapport avec la Convention-cadre de l’OMS pour la lutte antitabac. Quand on jette un coup d’oeil aux auteurs les plus fréquents dans le corpus, on trouve par exemple J. Collin et H. M. Mamudu, des noms connus dans ce segment de la littérature :

> pubmed_names('pubmed_fctc', min = 5)
538 Warren CW    14
30     Asma S    13
169 Glantz SA    13
225  Jones NR    13
414     Raw M     8
479  Sinha DN     8
150   Fong GT     7
316 McNeill A     7
67  Borland R     6
87  Chapman S     6
98   Collin J     6
298 Mamudu HM     6

Si l’on regarde les dates, le plus intéressant est au début : les premiers articles apparaissent trois ans avant l’adoption du texte, dans une revue de l’OMS et sous la plume de chargés de mission affectés à son élaboration. Celui-ci bénéficie ensuite d’une couverture exceptionnelle en 2005, son année d’application, et sa couverture reste élevée dans les années récentes4.

> head(pubmed_years('pubmed_fctc'))
  year count
1 2000     1
2 2001     2
3 2002     3
4 2004     1
5 2005    16
6 2006     3

Faites-vous une idée des articles téléchargés en allant lire le plus ancien, qui évoque la Convention-cadre et surtout les accords du GATT : la perspective d’une remise à plat des règles du commerce mondial offre une fenêtre d’opportunité à l’OMS pour produire son propre corpus de normes, en s’inspirant d’autres conventions comme celle de Basel5.

Screen Shot 2014-06-14 at 7.59.48 PM

Voici à présent le nombre d’articles publiés chaque année en lien avec la Convention-cadre par année. Les couleurs soulignent les revues qui ont le publié le plus d’articles (sans surprise, des revues de santé publique comme Tobacco Control et Addiction), ainsi que le Bulletin de l’OMS, le BMJ et le Lancet (il n’y a pas d’articles NEJM dans le corpus).

example

Comme il n’y a pas trop de données, on peut assez bien voir les petites aberrations du corpus, comme par exemple, ci-dessous en 2005, le numéro spécial d’un institut indépendant. La revue était alors basée à Vanves et publiée majoritairement en français ; elle a changé de nom et est à présent publiée par Sage Publications.

pubmed_fctc_lang

Mais… mais où est le réseau ?

Ah, je suis content que vous posiez la question. Pour les habitués, voici donc le réseau des co-auteurs dans le corpus « Convention-cadre OMS anti-tabac », où l’on retrouve les auteurs cités avec une de leurs collègues, plus des connexions entre chercheurs américains et britanniques, le tout entouré de beaucoup d’isolats.

pubmed_fctc_net

Le code pour créer le réseau produit un réseau non dirigé, où chaque lien indique un article co-signé par les deux auteurs. Le poids du lien est inversement proportionnel au nombre de coauteurs par article, additionné pour chaque paire de coauteurs (méthode “Newman-Fowler” ; appellation non contrôlée)6.

Le code du graphique lui-même, qui utilise ggnet (actuellement en soldes !), tient en dix lignes une fois les dépendances installées (packages devtools pour lire la fonction depuis GitHub sans vous embêter, network, sna et intergraph pour les réseaux, et RColorBrewer et ggplot2 pour le graphe).

source_url("https://raw.githubusercontent.com/briatte/ggnet/master/ggnet.R")
d = pubmed_edges("pubmed_fctc")
n = network(d[, 1:2])
n %e% "w" = d[, 3] / max(d[ , 3])
n %e% "q" = as.numeric(cut(n %e% "w", quantile(n %e% "w"), include.lowest = TRUE)) / 4
ggnet(n, segment.alpha = n %e% "q",
      segment.color = "black", size = 1) + 
  geom_text(label = ifelse(degree(n) > quantile(degree(n), .9),
                           network.vertex.names(n), NA), size = 4)
ggsave("pubmed_fctc_net.pdf", width = 11, height = 11)

Les données sont quasi-exhaustives, si l’on en croit le tests initiaux de 2011-2012. En revanche, le graphe peut éventuellement contenir plusieurs fois le même auteur s’il a été identifié par plusieurs combinaisons d’initiales et/ou de noms de famille (par exemple, “C Marquez” et “CR Marquez”).

Plus tard

On ira plus loin une prochaine fois, en regardant les mots-clés et les auteurs de plus près. La méthode de construction du réseau utilisée dans la demo est très basique : d’une part, elle ne collecte aucun attribut sur les auteurs, d’autre part, elle aplanit un réseau bipartite auteur-article où le mode secondaire (l’article) est très intéressant. À suivre, donc.

  1. L’industrie pharmaceutique , par exemple, a largement investi le champ de l’édition scientifique à des fins de légitimation de ses produits. []
  2. L’opérateur par défaut est AND. Les connecteurs logiques doivent être rentrés en majuscules et en anglais. Le reste des consignes est donné dans l’aide de PubMed. []
  3. L’antislash ne sert qu’à éviter un problème d’affichage avec le moteur de formatage du blog ; il ne doit pas figurer dans le code. []
  4. Il y a eu jusqu’à 44 articles sur le sujet en 2012, année d’adoption d’un protocole supplémentaire. Pour rappel, la recherche initiale n’utilise que les résumés (et les titres) des articles. []
  5. Le problème étant, bien évidemment, que jamais l’OMS n’a pu appliquer le genre de sanctions très dissuasives que l’OMC peut employer sur les marchés mondiaux. []
  6. La méthode Newman-Fowler étant additive, ses liens peuvent donc représenter un ou plusieurs articles co-signés. Le code renvoie aussi la version brute des liens au format CSV. []

Utiliser les résultats électoraux du ministère de l’intérieur

Toux ceux qui ont eu à utiliser un jour ou l’autre les fichiers de résultats électoraux diffusés par le ministère de l’Intérieur français savent que ceux-ci nécessitent un assez lourd travail de mise en forme afin d’être exploitables, en tout cas pour les élections pour lesquelles l’offre électorale n’est pas homogène sur le territoire (c’est-à-dire toutes sauf les présidentielles). En effet, les fichiers Excel diffusés par le ministère comprennent une ligne par territoire (commune, par exemple), et autant de colonnes que nécessaires pour mettre les résultats de tous les candidats. Il serait pourtant tellement simple de mettre le fichier en format « long », c’est-à-dire une ligne par combinaison territoire/candidat… Mais il faut croire que personne au ministère n’a jamais eu à lire un de leur fichier dans R ou un autre logiciel de statistiques (ou de cartographie). Au passage, on peut regretter que le site data.gouv ne demande ni même ne suggère à ses contributeurs de fournir les données en format tabulaire…

Quoi qu’il en soit, ceux qui ont à utiliser ces données ont généralement créé leurs petites routines pour traiter ces données. Sous Excel, le processus est particulièrement douloureux. Je me rappelle avoir fait planté des ordinateurs en faisant tourner des macros VBA…

Ayant eu à bricoler un script sous R pour importer ce genre de fichiers, j’ai décidé de le diffuser – ce qui a le mérite de m’obliger à le nettoyer un peu, et ce qui permettra, je l’espère, que d’autres l’améliorent. J’ai donc créé un tout petit package (il n’a qu’une fonction !) que j’ai mis sur mon compte Github.

Précision importante : le script, à l’heure actuelle, agrège les résultats en fonction des étiquettes attribuées par le ministère de l’Intérieur. C’est une limite importante, mais qui suffit à la plupart des utilisations courantes.

C’est une version 0.1 mais qui est fonctionnelle. À l’heure actuelle, traiter le fichier de résultat des européennes par communes (36762+ lignes, 227 colonnes) prend de l’ordre de cinq minutes sur un ordinateur de bureau classique. Ce n’est pas dramatique mais c’est un peu long. À l’avenir, je vais essayer d’accélérer cela – a priori en codant le cœur du script en C++, ce qui me forcera à m’y mettre. Mais peut-être d’autres auront-ils d’autres pistes – je ne suis pas sûr que mon algorithme soit optimal.

Sur ma todolist également : construire une application Shiny autour du package, pour que ceux qui n’utilisent pas R puissent quand même l’utiliser pour transformer leurs données.
Autre idée : permettre d’agréger les résultats autrement que par étiquette. C’est techniquement assez trivial, mais ça pose des problèmes de fond, du fait de l’hétérogénéité territoriale de l’offre électorale. Mais on pourrait imaginer la possibilité, par exemple, que l’utilisateur fournisse au script une sorte de « dictionnaire » associant les listes ou candidats à une nomenclature spécifique.

Je suis preneur en tout cas de tous les retours et suggestions des utilisateurs ! Et j’espère que ce sera utile à quelques uns !

Law Factory = Law Factory++

Le projet final du projet de recherche « La fabrique de la loi » a été présenté à la conférence « Law Factory II« . C’est en ligne depuis jeudi dernier, allez jeter un coup d’oeil :

Screen Shot 2014-06-02 at 3.22.22 PM

La conférence m’a permis de présenter mes résultats définitifs et de revoir oplein de gens chouettes, dont l’un des auteurs de Parltrack, qui m’avait appris XPath (avec Bram) il y a quelques années.

Comme j’aime beaucoup Parltrack, j’ai voulu jouer un peu avec les données. voici une visualisation interactive des amendements déposés dans les commissions du Parlement européen lors de la législature 2009-2014 (à jour du 26 mai 2014) :

Screen Shot 2014-06-02 at 3.03.09 PM

Screen Shot 2014-06-02 at 3.09.00 PM

Comme pour le Parlement français, tout est codé en une page PHP + HTML/CSS + Javascript qui utilise Sigma.js et des données exportées depuis R ; le code donne tout ce qu’il faut pour reproduire les graphiques ou la visualisation, en utilisant ggnet et rgexf.

Une page séparée montre les réseaux sous-jacents, qui sont très peu denses lorsque les commissions comptent peu de membres : la commission “Crises”, par exemple, n’a pas l’air de passionner les foules de législateurs en délire. Les dimensions des réseaux résument ces différences :

density

Note de lecture : les abréviations des commissions sont positionnées selon le nombre d’eurodéputés présents dans le réseaux (y), et le nombre total de cosignatures dans la commission (x). La taille des libellés est proportionnelle à la synthèse des deux informations, la densité du réseau.

La modularité change du tout au tout selon la commission. La pêche et les pétitions sont des commissions peu polarisées, de même que celles sur les transports, le budget ou les droits des femmes. Cette représentation change dans les commissions « Culture » ou « Agriculture », où la modularité est deux fois plus importante. Les commissions les plus polarisées (CRIS et SURE) ont trop peu de noeuds pour que la mesure soit pertinente.

modularity

Note de lecture : les abréviations des commissions sont positionnées selon la modularité de leur réseau. Les points gris donnent la modularité maximale mesurable (algorithmes Louvain et Walktrap). La couleur donne le ratio entre modularité empirique et modularité maximisée. Toutes les mesures sont comprises dans l’intervalle [0,1].

Toutes les interventions plénières des eurodéputés, en 100 lignes (de code)

Mise à jour, juin 2015 : si ce billet vous intéresse, allez voir le travail de Derek Greene et James P. Cross, qui ont modélisé les documents décrits ci-dessous de manière plus fine que ce que j’avais codé.

Quelques notes tirées de trois conversations sur les sept derniers jours. C’était plus long à décrire qu’à coder.

Commençons par récupérer la liste des eurodéputés, en trois étapes :

  1. La source : l’index complet est disponible en une seule page, ce qui rend les choses faciles. Le zéro final de l’adresse est le numéro de législature ; il y en a 7 au total, et la dernière possède un index des députés un peu plus détaillé, que l’on utilisera peut-être un autre jour.
  2. La méthode : il y a quatre ans, on m’a fait découvrir XPath, qui est une syntaxe servant à sélectionner des éléments dans un document structuré. XPath, c’est un peu comme les expressions régulières, ou découvrir comment trafiquer un PDF sans laisser de traces dans les métadonnées : ça change la vie1.
  3. La syntaxe : le site Internet du Parlement européen est rédigé en HTML propre (car généré automatiquement), avec des éléments bien imbriqués les uns dans les autres : c’est le cas idéal. La source de l’index, consultable dans tout navigateur, donne la syntaxe exacte des éléments utilisés ci-dessous.

Illustration de l’explication ci-dessus :

Screen Shot 2014-05-19 at 9.26.24 PM

À présent, dans R :

  1. On souhaite juste sauvegarder un « annuaire » minimal des 3 000 et quelques eurodéputés, et le sauvegarder dans un fichier qui servira d’index général où chaque eurodéputé est identifiable par trois éléments : le nom complet, inutilisable car plein d’accents et non unique2, l’URL (l’adresse de sa page personnelle), et dans cette URL, un numéro, 4484 par exemple, qui servira d’identifiant unique plus tard.
  2. La fonction de téléchargement va lire la page HTML, en extraire les éléments pertinents à partir de sa structure, et renvoyer tout ça dans un jeu de données. On sauvegardera les données au format CSV, un format en texte simple qui sauvegardera correctement les accents dans les noms si l’on s’assure que le parser les lit en encodage UTF-8.
  3. Petite nuance : quand vous voulez extraire un lien, la syntaxe XPath vous le permet directement ; mais pour extraire le texte d’un lien, le truc sur lequel on clique pour aller au lien en question, il vous faut la « valeur XML », d’où la fonction xmlValue ci-dessous. Le reste du code demande surtout de se renseigner sur XPath, et ce n’est pas compliqué du tout, surtout si l’on apprend par essai-erreur.

Il va donc vous falloir installer puis charger les librairies (a.k.a packages, extensions, plugins, comme vous voulez) jsonlite, plyr et XML, et 20 lignes plus tard, vous avez tous les eurodéputés dans un fichier CSV.

Plus compliqué, obtenir toutes leurs interventions parlementaires :

  1. Sur la page de chaque eurodéputé (exemple), un lien fournit toutes les interventions en séance plénière (exemple). En lisant encore une fois la source de la page, on trouve le petit bout de Javascript qui génère cette information, et surtout l’URL qui renvoie cette information depuis la base de données utilisée en coulisses du site.
  2. L’information est au format JSON, pour lequel il y a non pas un, ni deux, mais trois packages R qui peuvent assurer la conversion vers le format classique des jeux de données R, data.frame. Jeroen Ooms m’a prouvé en cinq lignes que son package était le meilleur, et j’ai pu vérifier depuis qu’il a entièrement raison3.
  3. Les fonctions conditionnelles de type if else, qui permettent par exemple de ne rien faire si aucune donnée n’a été trouvée, servent à contrôler l’exécution du script. Le code ci-dessous contient deux autres structures apparentées, while et try. Quelques détails chiants si vous ne les utilisez pas fréquemment :
    • while est un itérateur habituellement moins préférable que for (ou, dans R, qu’une vectorisation de type *apply), mais qui marche très bien ici pour lister les interventions de chaque eurodéputé(e)4.
    • try permet de continuer l’exécution même si un problème de connexion Internet empêche ponctuellement de lire une page (erreurs 404, 502, 503… amusez-vous bien)5.

Voilà pour le détail, et 30 lignes plus tard, vous pouvez récupérer les interventions de n’importe quel eurodéputé dans n’importe quelle législature (1 à 7), en utilisant son numéro d’identifiant unique (id)6.

Ça se complique encore un peu mais on y est presque. Le workflow final est le suivant :

  1. Pour chaque eurodéputé dans l’index général, extraire le numéro d’identifiant unique de l’adresse de sa page Internet, puis aller chercher ses interventions publiques dans chacune des 7 législatures parlementaires. La partie « pour chaque député » est purement itérative (for), mais la partie « chacune des législatures » est vectorisée : on va de 1 à 7, et on applique à chaque fois la fonction get_cre de 20 lignes décrite plus haut. Le seul bénéfice avéré de la vectorisation dans ce cas réside dans l’économie d’accolades et de lignes de code à gérer.
  2. Il ne reste que les soucis de format. Le premier est très simple : la partie vectorisée du code renvoie une liste, que l’on va aplatir au format data.frame avec rbind.fill, la fonction la plus utile du monde après paste (si si). Enfin, dernier problème : les fichiers JSON utilisent des listes, mais vos jeux de données sont des tableaux (i.e. des matrices avec des attributs) qui utilisent des cellules. Une liste vide est NULL, alors qu’une cellule vide est NA : welcome to the R Inferno ! Heureusement, une fois le problème détecté, il se règle en une ligne, à appliquer à chaque colonne susceptible de contenir des listes/cellules nulles/vides afin de les « purger » — sinon, pas d’écriture possible au format CSV.
  3. Prévoir, dans quelques heures, environ 1700 fichiers de résultats (car tous les eurodéputés n’interviennent pas, loin de là), pour environ 90 MB d’espace disque. Cet exemple est très « léger » et relativement rapide, mais d’expérience, vous avez vraiment intérêt à toujours penser à la taille des résultats sur disque avant de faire quoi que ce soit — à moins d’avoir la trésorerie pour détruire un disque dur de temps à autre7.

Illustration de l’avertissement ci-dessus, tiré d’un autre projet dont les résultats des modélisations font plusieurs gigaoctets de plus que les données brutes, ce qui est logique quand on utilise des simulations ou, comme ici, un bootstrap :

Screen Shot 2014-05-16 at 1.39.12 PM

On termine en écrivant poliment le nombre d’eurodéputés restants à l’écran, et avec 20 lignes de plus, l’index général est mis à profit pour télécharger, dans un dossier records à créer préalablement, les métadonnées de tout ce que les eurodéputés ont raconté en séance plénière depuis qu’il y a des données disponibles, dans des fichiers nommés UID_cre.csvUID est l’identifiant unique de chaque eurodéputé.

Ça fait 100 lignes en tout, avec quelques trucs en plus.

Le résultat à l’écran ressemble à l’illustration ci-dessous, avec des pseudo-barres de progression quand il y a des fichiers en téléchargement (je montre une exécution avec les résultats déjà présents sur le disque, d’où les « 0 » jamais suivis des « 100, 200, 300… » correspondant au téléchargement de chaque page d’interventions). Le code est exécuté dans TextMate via son plugin R, (qui doit exister pour d’autres éditeurs comme SublimeText).

Screen Shot 2014-05-19 at 9.32.46 PM

Sans avoir regardé en détail, les données intégrales ne semblent concerner que les trois dernières législatures. Le même code peut aussi collecter les opinions prononcées en tant que rapporteur, les motions, les questions écrites etc. avec à peu près autant de facilité, et pour rajouter des variables aux eurodéputés eux-mêmes, il suffit d’extraire plus d’informations de leurs pages personnelles—mais cela dépasserait le temps que je voulais passer sur cet exercice.

Je termine en pensant aux vraies feignasses dans mon genre : le code et les données en une seule archive. Add. : et si ça vous intéresse, j’ai aussi un fichier CSV de 400 MB contenant toutes les interventions orales des eurodéputés au cours des législatures 5–7 (1999–2004, 2004–2009 et 2009-2014).

  1. Les métadonnées, c’est ce que la NSA sait sur vous et sur votre grand-mère. Ça sert aussi à détecter des trucs bizarres dans les copies d’étudiants, ou à savoir qui rédige vraiment les rapports publics que vous trouvez parfois dans la presse en ligne. []
  2. Ce menu détail rend les dernières données de VoteWatch inutiles pour cet exercice, mais voici quand même un guide à leur réutilisation avec R, en trois parties : un, deux, trois. []
  3. La vignette du package explique très bien pourquoi, et en pratique, c’est l’argument flatten = TRUE qui fait la moitié du boulot de virer les listes pour mettre des colonnes, histoire que le résultat ressemble moins à un flux qu’à un tableau de données. L’autre moitié du boulot prend une ligne, lire plus haut. []
  4. La variable idx est lue dans le fichier JSON et correspond à la pagination des interventions publiques, qui sont fournies par tranche de 100 interventions en partant de zéro, parce que Javascript compte à partir de zéro. Elle est égale à 100, 200, 300 ... tant qu’il reste des pages d’interventions publiques dans la pagination du site Internet, -1 quand on se trouve sur la dernière page d’interventions. La variable est directement égale à 0 quand il n’y a aucune intervention publique. []
  5. Vu le grand nombre de pages à couvrir, N eurodéputés * P pages d’environ 100 interventions, le script va nécessairement avoir quelques soucis : il faut le réexécuter quelques fois pour arriver à télécharger tout ce qui est possiblement téléchargeable. Je laisse de côté les fonctions de mise à jour, (il suffit d’effacer les fichiers-cibles en amont). []
  6. Par exemple, le président actuel du Parlement est codé 1911, donc la fonction get_cre(1911, 7) renvoie les interventions (nombreuses, car majoritairement procédurales) de son mandat en cours. []
  7. N’imaginez même pas essayer d’écrire les résultats sur une clé USB : ce n’est pas vraiment un support froid, et je garantis à 200% que tout essai sur une clé USB de plus de 8 GB aura pour seul effet de la rendre complètement et définitivement inutilisable. []

Porngrams avec R

Pour celles et ceux qui auront besoin d’inclure les résultats de Sexualitics dans leurs papiers, voici la fonction R qui renvoie le graphique correspondant, avec la possibilité de visualiser jusqu’à dix mots-clés :

porngram("asian", "american", "french", adjust = FALSE)

porngram1

Il est assez fréquent de vouloir ajuster les courbes en incluant un diviseur commun pour contrôler par le volume de données disponible pour chaque année. Reprise du même exemple avec ce paramètre :

porngram("asian", "american", "french", adjust = "xxx")

porngram2

La fonction reprend des bouts de code que j’avais écrit pour le package ngramr. Malheureusement, les données de Google Ngram s’arrêtent en 2008, ce qui ne permet pas d’ajuster les Porngrams par les Ngrams.

Un exemple plus complet :

geo

Pour analyser ces bêtises, il faut faire un petit effort de manipulation en différenciant la série temporelle de chaque mot-clé sur les années 2008-2012, et en extrayant les composantes principales de la transposée du résultat : l’année 2011 est celle où les variations de volume des mots-clés sélectionnés ont été les plus importantes.

Quantification du travail parlementaire, pour quelques variables en plus

À l’occasion d’un coup de ménage estival, voici une mise à jour prévue depuis longtemps au dossier flegscrap, qui permet de récupérer les documents déposés sous les cinq dernières législatures de l’Assemblée nationale :

[image depots.year.png effacée, désolé ; voir les nouveaux graphes ]

Collecter des données pendant la session législative implique de récupérer des documents déposés le jour même, ce qui donne des comparatifs intéressants où l’on voit, par exemple, le déplacement des amendements sur les articles d’une loi en cours d’examen :

Screen Shot 2013-07-14 at 12.16.51 AM

Les projets et propositions de loi qui s’insèrent sur l’agenda parlementaires apparaissent d’abord dans les sources HTML que le code télécharge, puis rejoignent le jeu de données entre chaque itération, donnant à voir comment les données sont progressivement complétées :

Screen Shot 2013-07-18 at 8.27.59 PM

Les données montrent deux espaces, dominés respectivement par les projets de loi (probabilités de discussion et d’adoption élevées) et les propositions de loi (probabilités de discussion et d’adoption faibles). En rentrant dans le détail, on voit aussi que la distribution des séances de débat en séance publique s’achètent cher (auprès de la conférence des Présidents, si je ne m’abuse).

[ image depots.quadrant.png effacée, désolé ; voir les nouveaux graphes ]

En attendant que le site Internet de l’Assemblée nationale réorganise ses données, voire mieux, en rajoute. Cela ne fera pas disparaître la nécessité de bien nettoyer les données.

Un réseau de 68 203 co-signatures sur 11 569 amendements

J’ai récemment évoqué les co-signatures d’amendements, en me demandant tout du long si le réseau des co-signataires reflétait la même structuration partisane que le réseau des députés sur Twitter. Voici le résultat final sur les 11 500 amendements co-signés depuis le début de la 14e législature en cours :

network.fruchtermanreingold

J’ai rapidement annoté le graphique que m’a sorti ggnet après un calcul de douze minutes pour constituer le réseau1. Pour générer ou actualiser les données, il suffit de récupérer les dossiers parlementaires puis d’en extraire les co-signataires. Le résultat est une gigantesque liste de listes à décomposer en 68 203 paires uniques de co-signataires :

neta.prep

Pour tester mes versions intermédiaires, j’ai utilisé des échantillons réduits. Sur ces échantillons, le groupe GDR est complètement isolé, car il rédige moins d’amendements en valeur absolue et n’en co-signe pas souvent avec des membres d’autres groupes parlementaires. Voici le réseau avec 500 co-signatures sélectionnées au hasard :

neta.500

Puis, lorsque l’on augmente la “résolution” en prenant un échantillon dix fois plus large de 5 000 amendements, les connexions commencent à apparaître, plaçant le groupe GDR dans l’opposition, en dehors du groupe de “soutien” radical-écologiste placé à côté de la majorité socialiste :

neta.5000

Le réseau place également les élus frontistes dans cette opposition, mais comme il manque quelques données, les points gris ne sont pas tous des élus NI. Tout est perfectible, mais je n’ai pas le temps de corriger ce problème, créé par la présence d’homonymes. J’ai rédigé cette version dans le train et vais m’en tenir à ça.

La force du marqueur partisan est aussi évident que dans le cas des députés sur Twitter : l’immense majorité des liens se tisse à l’intérieur des groupes parlementaires. Cet effet ne révèle toutefois rien d’inconnu, et surtout rien de complet : le travail parlementaire trans-groupe, déjà identifiable dans ces visualisations, est simplement plus discret que ces signaux publics que sont les liens par Twitter ou par co-signature d’amendements2.

Pour terminer, une visualisation de la centralité des points montre les inégalités d’investissement dans le travail de co-signature d’amendements (tout en sachant que ce travail exclut de facto celui, non négligeable en quantité et certainement en temps, sur les amendements déposés au nom d’un seul député) :

network.centrality

Le code et les données sont en ligne, avec les notes de construction et les instructions pour actualiser les données. Il doit être possible, moyennant des retouches mineures au code du scraper, de récupérer les mêmes données pour les législatures précédentes.

Mise à jour : le code permet désormais de travailler sur les deux législatures précédentes et de visualiser les données sous forme de réseaux bipartites, un type de réseau à “deux modes” où l’on représente les acteurs et les événements (ici, les co-signatures d’amendements) sur le même graphe. Ces réseaux sont à l’honneur dans le dernier numéro de Social Networks.

Voici à quoi ressemble un réseau bipartite de la législature en cours, à partir d’un échantillon aléatoire de 5 500 amendements. La stratégie d’obstruction législative dans l’opposition est parfaitement lisible, de même que la division du travail entre les groupes et leurs positions respectives vis-à-vis du nexus majorité/opposition :

network.bipartite.14

Je n’avais pas prévu d’aller vers ce type de visualisation, mais Pedro Jordano m’a convaincu et a fourni le code qui sert à convertir les données entre single-mode et two-mode. En raison de la taille et de la nature du réseau, il faut également penser à le stocker dans une matrice “économe” qui compresse l’espace pris par les tonnes de “zéros” qu’on trouve dans les réseaux bipartites : le code pour cette sparse matrix est venu d’un billet de Solomon Messing sur le sujet.

  1. Ce n’est pas du “big data”, mais il faut quand même aplanir une matrice de plusieurs millions de lignes pour créer le réseau. []
  2. Il suffit de penser, par exemple, aux amendements déposés en commission, exclus des données uniquement nominatives montrées ici. []