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. []

Du code et des liens (avec des expressions régulières)

Dans la série “on n’a pas le temps de bloguer, mais d’autres s’en chargent à notre place…” Quelques actualités quand même.

1. Un député américain a demandé la suppression des crédits de recherche pour les sciences sociales.

Bizarre, pour un pays qui devrait avoir besoin de quelques recherches sur le wahhabisme. Ah, il a spécifiquement demandé la suppression de la science politique. Pas de chance, c’est là où se publient les recherches sur les trajectoires des terroristes. Ah, il a aussi une meilleur poste de dépense à conseiller : la recherche médicale (les blouses blanches qui sauvent des vies). Sur les “death panels” ?

Merci au lecteur qui nous a signalé le truc, récemment relaté en détail sur The Monkey Cage avec les manoeuvres précédentes. Je ne mets pas de lien précis, car pour être honnête, c’est vu et revu, et stupide. L’argument sur le wahhabisme est par ailleurs emprunté à un rapport public sur les sciences humaines et sociales, signé par Maurice Godelier. Je l’aime bien, cet argument.

2. Il faut lire Christophe Bouillaud sur les élections en Italie (quel bordel), et le film-documentaire TPB AFK (quelle aventure) sur le procès du site Internet The Pirate Bay.

3. Un excellent exemple de scraping avec expressions régulières sur le blog Quanti, par les auteurs du blog Data Sciences Sociales ; les deux sur la même plateforme que le ‘Bistro.

(La suite est ultra-nerd mais peut-être de salut public pour ceux qui enseignent avec R.)

Ah, l’utilité des expressions régulières… Si je devais apprendre deux choses à toute personne qui utilise un ordinateur, ce serait les raccourcis clavier et les “regex”.

Je m’en sers par exemple dans ce fichier pour compiler tout un cours en une seule frappe clavier qui sélectionne tous les fichiers .Rmd, les compile sous knitr, puis les copie au bon endroit. En pratique, ça donne ça :

html <- dir(pattern="(index|[0-9]{2,}_\\w+).html|style.css")
code <- dir("code", pattern="([0-9]{2,}_\\w+).R$")

La première ligne signifie “sélectionner le fichier index.html, tous les fichiers .html commençant par deux chiffres ou plus suivis d’un underscore et d’un mot, et le fichier style.css”. La seconde ligne signifie presque la même chose mais appliquée uniquement aux scripts .R. Une commande ultérieure les copie dans un dossier, qui est ensuite placé sur GitHub et publié par GitHub Pages.

Un autre exemple dans ce document .Rprofile, qui contient quelques fonctions pour enseigner le même cours. L’objectif était de permettre à une vingtaine d’étudiants débutants de télécharger tous les fichiers du jour en une commande, et de leur épargner les installations et chargements de packages que demande fréquemment R en s’en chargeant à leur place.

La fonction ida.prep() fait ce travail : (1) elle télécharge tous les fichiers pour une séance du cours, puis (2) elle fait scanner les fichiers par la fonction ida.scan() pour voir quels packages y sont chargés, et enfin (3) elle fait charger les packages par la fonction ida.load(), en les téléchargeant au préalable si nécessaire, le plus silencieusement possible (c’est-à-dire pas assez).

Voici les lignes qui détectent toutes les utilisations des commandes library() et require() :

grepl("(library|require)\\(([a-zA-Z0-9]*)\\)", text)
gsub(".*(library|require)\\(([a-zA-Z0-9]*)\\).*", "\\2", text)

Les lignes proviennent de la fonction ida.scan(). La première ligne “attrape” toutes les lignes de code contenant les termes library ou require suivis de quelque chose entre parenthèses. La seconde ligne extrait le contenu des parenthèses. J’envoie ensuite ce vecteur de noms de packages à la fonction ida.load().

Allez, un dernier exemple bien tordu pour la route :

grepl(paste0("^(", paste0(x, 0:3, collapse = "|"), ")_"), prep)

Cette ligne requiert un argument x, qui est le numéro de la séance. Chaque séance est divisée en quatre segments numérotés de 0 à 3. Cette ligne colle le numéro de séance à “0”, puis à “1”, puis à “2”, puis à “3”, puis colle tout ça ensemble avec des barres “|”, qui sont l’opérateur logique “ou” en regex. Tout cela doit être situé en début de ligne (“^”) et suivi d’un underscore.

Cette dernière expression sert à extraire les pages de cours pour une séance donnée, à partir de la page d’index du cours. Le reste du code montre comment exécuter tout le boulot décrit ci-dessus en moins de 100 lignes, qui permettent de gagner beaucoup de temps en classe en réduisant tout le travail de (télé)chargement à une commande, de type ida.prep(n).

4. Plus rapidement, sur le même sujet, il y a de chouettes projets de packages pour R autour de moi, et comme pour les “blogs SHS” il fut un temps, j’aimerais aider la communauté à se former.

Si vous programmez un peu sur R et voulez aider au développement de fonctions pour les analyses de données pour les sciences sociales (et notamment pour les analyses de données d’enquête, comme le permettent par exemple les packages survey et memisc), faîtes signe au Polit’bistro sur Twitter, ou dans les commentaires si vous avez comme moi du mal à tout mettre en 128 caractères.

5. Je n’ai plus le temps de bloguer sur tous les chouettes trucs qu’on peut découvrir sur les blogs de Baptiste Coulmont et Jérôme Cukier, mais vous avez les adresses.

6. Est-ce qu’on peut vraiment tolérer, au sein de l’Union européenne, un territoire qui se dispense d’élection au suffrage universel ?

Je me souviens avoir lu quelque part que le Vatican ne pratiquait pas non plus la transférabilité des peines, ce qui fait qu’un meurtrier ou un violeur au Vatican n’a pas de casier judiciaire à l’extérieur (il est simplement excommunié, en application du droit canonique). À vérifier, mais un juriste avait monté quelque chose d’assez conséquent sur le sujet ; je n’arrive plus à mettre la main dessus.

C’est à présent l’heure d’aller vraiment au bistro, histoire de savoir qui a gagné ce soir.

Paru, à paraître

Très rapidement car toujours pas le temps :

Vu le prix des ouvrages, il va falloir calculer avec Noël… Même les ouvrages de l’EHESS, sur lesquels une effort a été fait, coûtent une petite fortune.

Le café reste en carafe pour l’instant, mais allez voir à côté pour une jolie explication de texte sur des thèmes qui me sont chers, les regex et le scraping.

Expressions régulières (et disponibilité des données)

J’ai déjà évoqué les “regex” au sujet du nettoyage de données : il s’agit d’une chaîne de caractères qui permet de sélectionner des extraits de n’importe quel texte en se basant sur une syntaxe régulière, et de les remplacer par autre chose. Savoir se servir des expressions régulières rend un nombre de services incalculable.

Il existe des variantes intéressantes de ce procédé. Voici une ligne de code pratique pour renommer des fichiers dans un terminal Unix ou Mac OS X, et qui utilise une expression régulière1 très basique :

ls Bild*.jpg | awk '{print("mv "$1" "$1)}' | sed 's/Bild/Photo/2' | /bin/sh

Mon téléphone portable est configuré en allemand (ne me demandez pas pourquoi), et les photos qu’il prend sont appelées “Bild0561.jpg”, “Bild0562.jpg” et ainsi de suite. Cette ligne de code remplace “Bild” par “Photo”.

Sous Stata, un procédé similaire est utilisé par la commande non-native renvars qui permet de renommer des groupes de variables en une seule ligne. Outre les noms de variables, les expressions régulières permettent d’effectuer certaines étapes de la préparation de données très rapidement.

Les applications ne manquent pas. Les premiers principes de manipulation s’apprennent très vite, et ils économisent des heures de travail manuel, à condition d’avoir un éditeur de texte – comme TextMate sur Mac OS X – qui permet d’utiliser les expressions régulières dans sa fonction find/replace.

Ce type d’outil devient très pratique dès lors que l’on cherche à travailler sur des données originales, assemblées par ses propres soins ou qui n’existent pas sous la forme de dataset dans l’un des formats courants en analyse quantitative. La faible visibilité des travaux de recherche souffre de ce travers supplémentaire que les données des auteurs sont rarement publiques.

Heureusement, même si la pratique qui consiste à publier ses données dans un format exploitable reste encore très faible, elle est au moins reconnue comme bonne pratique. Je cite souvent un texte de Jeremy Freese sur ce sujet, et je suis régulièrement le développement de Dataverse, la plateforme de publication de données promue par Gary King. La France n’a pas encore trouvé ses porte-étendards de l’open data access.

Un exemple récent : en lisant un compte-rendu d’ouvrage2 écrit par Nicolas Mariot, j’ai voulu récupérer les données du vote qui amène Adolf Hitler au pouvoir le 23 mars 1933. Par chance, Gary King a travaillé sur le sujet, et a publié ses données.

(Avez-vous remarqué que les trois chercheurs cités dans cette note disposent tous d’un site Internet ? Une autre forme de disponibilité publique.)

  1. Add. février 2013 : en réaction à ce billet, un ami m’a fait remarquer il y a fort longtemps que « * » n’était pas une « regex » mais un caractère « catch-all » (attrape-tout) que l’on appelle une « wildcard« . Désolé Alexandre de n’avoir corrigé ce billet que plusieurs années après cette juste remarque. []
  2. À noter : ce texte est en accès libre grâce à la revue Sociologie et à sa plateforme de diffusion, basée sur un partenariat entre Revues.org (gratuit) et Cairn (payant). Ce modèle de publication est le plus moderne à mes yeux. La prochaine étape sera l’open access intégral, mais c’est un autre débat. []

Geek stuff

J’ai souri en lisant les vignettes d’une doctorante qui a fait son coming-out geek.

Dernière manipulation geek en date : une étudiante a eu une idée originale pour un cours de statistiques, où elle a décidé d’analyse un échantillon des trois millions de tweets #iranelection envoyés lors de l’élection iranienne. Son échantillon commence le 19 juin 2009, et son filtre a récupéré principalement des tweets en langue anglaise, avec quelques observations en Farsi et dans d’autres langues. Le fichier qu’elle m’a fourni contient 45 000 observations environ.

Pour lui fabriquer un échantillon plus maniable, j’ai appliqué la méthode suivante :

  • Assigner un nombre aléatoire entre 0 et 1 à chaque ligne.
  • Ordonner les observations par ce numéro.
  • Extraire les 4000 premières entrées vers un nouveau fichier.
  • Recommencer, extraire les 3000 premières.
  • Recommencer, extraire les 2000 premières.

L’opération sous Excel m’a créé un fichier d’environ 165MB qui a bien ralenti mon système, mais le fichier final est correctement échantillonné. Ensuite, il a fallu appliquer pas mal d’expressions régulières, du genre \|en\|(.*).jpg\|, pour nettoyer les données, ce qui m’a valu de perdre 144 observations mais de terminer avec un fichier CSV presque directement exploitable, après deux heures de manipulation.

Vos suggestions sur comment analyser le fichier sont les bienvenues ! Comme le cours se déroule sous Stata, j’ai pensé lui faire utiliser wordscores, mais il y a certainement une meilleure méthode pour analyser le corpus.

Nettoyage de données

Sur un billet précédent, j’ai évoqué le nettoyage de données sous TextMate ; en réalité, n’importe quel éditeur de texte brut permettant d’effectuer des substitutions de texte par expressions régulières fera l’affaire.

J’explique ma méthode ici car, comme le disait à juste titre un enseignant, en pratique, le data mining et le data cleaning sont tout aussi importants que ce que l’on enseigne en data analysis et data visualization. En revanche, le contenu de ce billet est un peu obscur si l’on ne connaît rien aux expressions régulières. Je tente d’expliquer mes manipulations ci-dessous, mais cela reste assez difficile à déchiffrer, et la seule manière d’apprendre consiste à reproduire l’exercice. La syntaxe utilisée pour les expressions régulières est la syntaxe Oniguruma, et j’ai réuni les commandes dans un fichier texte commenté.

Voici mon protocole :

  1. Copier-coller du fichier de base dans un document texte brut. Je ne mets ni la source, ni mon fichier, à disposition, pour ne pas gêner les personnes nommément citées dans le document.

    Dans ce qui suit, les lignes F: renvoient aux expressions de recherche (Find) et les lignes R: aux expressions de remplacement (Replace).

  2. Retrait des en-têtes (“Concours commun…”), des pieds-de-page (“Page X sur 41”) et des en-têtes de tableaux (“Candidat, Nom, Prénom, Rang, Affectation”) :

    F:(.*)\nCONCOURS (.*)\n(.*)Affectation
    R:{ø} (laisser vide)

    Comme ceci. On vient de retirer tout ce qui ne concerne pas directement les candidats. Les sigles ésotériques correspondent aux sélecteurs standard en expressions régulières pour “tout caractère en toute quantité” – (.*) – et retour à la ligne (\n). Les parenthèses sont superflues, je les garde pour la lisibilité. Les parenthèses servent à mémoriser le texte sélectionné par l’expression, on s’en sert plus bas.

  3. Retrait manuel du premier en-tête et du dernier pied-de-page dans le fichier, qui ont “résisté” au nettoyage précédent.

  4. Soyons malins : chaque candidat possède un numéro d’identification à cinq chiffres, et les rangs de classement s’arrêtent à 1025, donc à quatre chiffres… Donc si je passe une commande “insérer un retour à la ligne avant chaque nombre à cinq chiffres”, tout ira bien, je me retrouverai avec un(e) candidat(e) par ligne de fichier.

    F:([0-9]{5})
    R:\n$1

    Comme cela. [0-9]{5} capture le numéro du candidat, $1 le restitue après avoir inséré un saut de ligne (et je supprime un espace situé avant le numéro d’identification au passage, pour avoir des données vraiment propres).

  5. En regardant le résultat, j’aperçois de légères erreurs causées par le copier-coller sauvage depuis un document PDF : des espaces persistent là où j’ai besoin d’avoir des tabulations, comme ici… Je rectifie avec une commande qui transforme les espaces en tabulations entre le numéro d’affectation et le nom de famille du candidat.

    F:([0-9]{5}) (la ligne se finit par un espace)
    R:$1\t

    Vous l’avez deviné, \t insère une tabulation.

  6. Soyons précis… Je remarque qu’il reste des espaces là où l’on devrait avoir une tabulation, entre le rang du candidat et le choix d’établissement. Je remarque cela grâce aux caractères invisibles, affichés dans cet exemple. Un petit défaut à supprimer, donc.

    F: ([0-9]) (la ligne se finit par un espace)
    R:$1\t

    C’est la même manipulation qu’au-dessus… et j’obtiens une soixantaine de remplacements. Heureusement que je l’ai vu, cela aurait gravement affecté mon import Excel.

  7. Restons méfiants… En regardant les espaces, j’aperçois le même problème de l’autre côté du rang, entre le prénom et le rang. Même manipulation, donc, mais “à l’envers”.

    F: ([0-9]) (avec un espace avant le rang)
    R:\t$1

    La tabulation est rajoutée avant le rang. CQFD. L’éditeur me prévient qu’il a effectué quelques remplacements de plus.

  8. Je pense avoir terminé : je sauvegarde en .txt et importe sous Excel au format tab-delimited.

    Je trouve visuellement deux erreurs sur 1025 lignes de données, que je corrige à la main ; nettoyage réussi et terminé, l’analyse peut commencer ! Temps total de la manipulation : environ cinq minutes. La même opération effectuée “manuellement”, ligne par ligne, sous Excel, aurait pris très longtemps…

J’ai déjà nettoyé de très gros volumes de données par expressions régulières : ce système est extrêmement flexible, et l’on peut monter dans des gammes de complexité assez inouïes (ce qui est présenté ici correspond vraiment à une pratique très basique). Je ne suis naturellement pas payé par Macromates pour vanter les louanges de leur logiciel ; encore une fois, tout logiciel avec caractères invisibles et expressions régulières fera l’affaire (et c’est encore mieux si le logiciel propose aussi la colorisation de syntaxe ainsi que des raccourcis clavier efficaces).

À défaut d’un éditeur séparé, on peut utiliser les expressions régulières directement sous R ou sous Stata.

(Question-bonus : pourquoi se donner tant de mal et ne pas se contenter de remplacer les espaces par des tabulations ? Parce que les noms de famille incluent aussi des espaces…)