Passons aux choses sérieuses : extraction et préparation des données

[Je sais pas trop à qui tout ça s’adresse… J’ai pensé écrire pour un utilisateur sans aucune expérience, mais dans ce cas il y a quand même un ou deux passages où il faudra faire preuve d’imagination.]Nous allons dans ce post récupérer les données et les préparer à se retrouver transformées en graphe. Attention à bien avoir lu le post précédent.
Tout d’abord, dans R, ne pas oublier de charger les packages qui nous intéressent, twitteR et igraph :
library(twitteR)
library(igraph)
Une fois ceci fait, définir les critères qui nous intéressent et récupérer les tweets. Il y a de nombreux moyens d’en récupérer. Pour chaque pastille, une fonction différente (et parfois un output dans une forme différente) :
  • searchTwitter les trie suivant des mots-clés (pas forcément un hashtag),
> searchTwitter("#sarkocasuffit",n=5)

[[1]]
[1] "LaurentFaustine: \" Nous n'acceptons aucune leçon d'un président qui a un bilan aussi lourd\" #Besançon #FH2012 #sarkocasuffit"

[[2]]
[1] "Sophie_Pons: #SarkoCaSuffit sentirait monter la vague... moi aussi : la vague de l'indignation, la vague du #changement #FH2012 #Besançon"

[[3]]
[1] "LaurentFaustine: \"Pour vaincre le 6 mai, c'est la vague du changement qui arrive !\" #Besançon #FH2012 #sarkocasuffit"

[[4]]
[1] "Marcjaures: @avec_hollande @fhollande Et ça coutera 200 Ms d'euros/an  !!! #Sarkopipo #Sarkodégage #SarkoCaSuffit"

[[5]]
[1] "LaurentFaustine: \"Je sens monté la vague du peuple qui n'en peut plus\" #sarkocasuffit #FH2012 #Besançon"
  • showStatus va nous donner un tweet en particulier en fonction de son ID (sur twitter.com, à côté d’un tweet, cliquer sur ouvrir/détails pour voir son ID),
> showStatus(189778960819818496)

[1] "AidanJohnMoffat: I'M HAVING A PARTY AND YOU'RE INVITED! Right here on Twitter, tomorrow at 10pm. Here's how: http://t.co/E6WZscbV"
  • publicTimeline donne les derniers tweets publiés tous utilisateurs confondus,
  • userTimeline donne les derniers tweets d’un utilisateur (attention, il y a des limites dans la portée de cette recherche… comme dans toutes les autre, en fait)
userTimeline("dariusrochebin")

[[1]]
[1] "DariusRochebin: @G_Berling @nicolassarkozy La chaise sera-t-elle bien vissée cette fois ?"

[[2]]
[1] "DariusRochebin: @Munsterma L'information se répandra. Ce sera plus dur pour les confrères français de Tv déjà au courant de mimer la surprise à 20h."
Noter qu’avec un peu de pratique, il est possible de s’authentifier à Twitter via twitteR. C’est-à-dire consulter ses propres mentions, retweets, etc., mais aussi consulter ses messages privés (DM) ou même poster (une fois authentifié, de nombreuses autres fonctions deviennent disponibles). Avouez que c’est peut-être bien le client Twitter le plus sexy 😮
À partir d’ici, le code risque de ne pas fonctionner si vous le recopiez aveuglément (par exemple les dates ne seront plus valables).
Retour à #EnLD : je cherche à récupérer tous les tweets mentionnant l’émission, c’est-à-dire comportant le tag #EnLD. Ensuite, je vais considérer tous les auteurs de ces tweets, puis toutes les personnes mentionnées. Le tag n’a servi qu’à créer cet échantillon de tweets. Le graphe est ensuite généré en reliant chaque auteur aux personnes mentionnées (en gros, on tire une flèche d’un utilisateur à un autre).Niveau interprétation, grosso modo : plus un auteur mentionne, plus il est actif dans le débat. Plus il est mentionné, plus il est important. Bien sûr, cette interprétation à l’emporte-pièce se discute plus ou moins au cas par cas… Let’s go.
J’ai récupéré les tweets avec la chaîne de caractères « #EnLD » de ces derniers jours. Je l’enregistre dans un objet res (créé pour l’occasion, avant il n’existait pas) :
res <- searchTwitter("#enld", n= 10^3, since ="2012-04-01")
Attention, pour la suite, j’ai utilisé les tweets postés entre le mercredi 4 à midi et le jeudi 5 à midi, c’est-à-dire tous les tweets concernant un seul débat, ce qui permettra de tirer des conclusions bien plus intéressantes qu’en mélangeant tout. Pour récupérer mon jeu de données, écrivez-moi. Si vous voulez voir ce que vous avez enregistré, il suffit de taper res dans R pour que les tweets s’affichent (n’importe comment). Pour que ce soit plus joli (et plus facilement accesible), on va transformer tout ceci en data frame, un objet avec plein de jolies propriétés dans le langage de R :
res.df <- twListToDF(res)
En tapant res.df dans R, vous remarquez que c’est en fait encore plus le chenit qu’avant.
Ça n’est qu’une impression. La fonction str( ) vous décrit toutes les entrées du data frame, et c’est là qu’on va commencer à rigoler :
> str(res.df)

'data.frame': 224 obs. of  10 variables:
 $ text        : chr  "Et revoilà la photo du débat signée @politiquevd #EnLd #ps #gauche http://t.co/GhfpnnsI" "Le @pssuisse est-il encore un parti de gauche ? C'était la question d'#EnLD ce matin . Le débat se récoute ici http://t.co/AmRt"| __truncated__ "Avec #VD2012 derrière nous et #enld en vacances... on va faire quoi ?" "@C_Domenjoz nous n'y avions même pas pensé :-) #enld" ...
 $ favorited   : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ replyToSN   : logi  NA NA NA NA NA NA ...
 $ created     : POSIXct, format: "2012-04-05 08:02:34" "2012-04-05 07:46:45" "2012-04-05 07:29:53" "2012-04-05 07:25:55" ...
 $ truncated   : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ replyToSID  : chr  NA NA NA "187792689314533376" ...
 $ id          : chr  "187812454858895360" "187808477488496640" "187804234710921216" "187803236265230337" ...
 $ replyToUID  : logi  NA NA NA NA NA NA ...
 $ statusSource: chr  "&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;" "&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;" "&lt;a href=&quot;http://levelupstudio.com/plume&quot; rel=&quot;nofollow&quot;&gt;Plume for Android&lt;/a&gt;" "&lt;a href=&quot;http://levelupstudio.com/plume&quot; rel=&quot;nofollow&quot;&gt;Plume for Android&lt;/a&gt;" ...
 $ screenName  : chr  "Lamagouille" "RTSinfoplus" "politiquevd" "politiquevd" ...
Ceci est la structure de notre data frame. C’est-à-dire tout ce que contient l’objet res.df
Par exemple, vous voulez tous les textes des tweets que vous avez enregistrés dans votre data frame ? Vous accolez $text au nom de votre data frame  :
> res.df$text

  [1] "Et revoilà la photo du débat signée @politiquevd #EnLd #ps #gauche http://t.co/GhfpnnsI"
  [2] "Le @pssuisse est-il encore un parti de gauche ? C'était la question d'#EnLD ce matin. Le débat se récoute ici http://t.co/AmRtAdb0"
  [3] "Avec #VD2012 derrière nous et #enld en vacances... on va faire quoi ?"
Vous voulez toutes les dates ? res.df$created  Et ainsi de suite…
Bon, vous voyez venir le problème ? Il va falloir extraire tous les usernames de ce bourbier… On va enregistrer les auteurs avec la dernière option ci-dessus (« $screenName »). Après, il faudra récupérer les personnes mentionnées dans le texte. Et les mettre en relation avec l’auteur correspondant. Une chose après l’autre, les auteurs :
> actors.out <- res.df$screenName

> actors.out

  [1] "Lamagouille"     "RTSinfoplus"     "politiquevd"     "politiquevd"     "KafeKreme"       "Toshikoshi"      "Toshikoshi"      "buercher"        "KafeKreme"       "KafeKreme"      
 [11] "Toshikoshi"      "C_Domenjoz"      "braoru"          "KafeKreme"       "braoru"          "braoru"          "RTSinfoplus"     "braoru"          "RTSinfoplus"     "KafeKreme"      
 [21] "KafeKreme"       "braoru"          "braoru"          "braoru"          "Toshikoshi"      "braoru"          "Jem087"          "braoru"          "Lamagouille"     "Toshikoshi"     
 [31] "KafeKreme"       "Toshikoshi"      "KafeKreme"       "KafeKreme"       "braoru"          "Toshikoshi"      "Toshikoshi"      "braoru"          "braoru"          "braoru"         
 [41] "KafeKreme"       "braoru"          "politiquevd"     "Toshikoshi"      "KafeKreme"       "braoru"          "braoru"          "Toshikoshi"      "KafeKreme"       "nashtags"       
 [51] "nashtags"        "nashtags"        "nashtags"        "CuriousHat"      "Munsterma"       "CuriousHat"      "politiquevd"     "BoschettiSteen"  "CuriousHat"      "Munsterma"      
 [61] "BoschettiSteen"  "kaltezar"        "BoschettiSteen"  "fredalvar"       "kaltezar"        "BoschettiSteen"  "BoschettiSteen"  "tchernopuss"     "tchernopuss"     "tchernopuss"    
 [71] "Le_PeCuLe"       "kaltezar"        "tchernopuss"     "tchernopuss"     "tchernopuss"     "benhattar"       "rhodine75"       "frankrheme"      "KafeKreme"       "buercher"       
 [81] "GrandjeanMartin" "benhattar"       "benhattar"       "Toshikoshi"      "benhattar"       "kaltezar"        "Le_PeCuLe"       "Le_PeCuLe"       "GrandjeanMartin" "GrandjeanMartin"
 [91] "GrandjeanMartin" "KafeKreme"       "GrandjeanMartin" "jsneuch"         "GrandjeanMartin" "jcschwaab"       "jcschwaab"       "Lamagouille"     "Ballymag"        "GrandjeanMartin"
[101] "KilchenmannE"    "Lamagouille"     "KilchenmannE"    "jcschwaab"       "Toshikoshi"      "Lamagouille"     "jcschwaab"       "DeGauCHe"        "Ducommnath"      "Ducommnath"     
[111] "Le_PeCuLe"       "pascalbernheim"  "RTSinfoplus"     "fredalvar"       "Le_PeCuLe"       "Lamagouille"     "KafeKreme"       "Lamagouille"     "loicdobler"      "joanabo"        
[121] "Ducommnath"      "CuriousHat"      "DeGauCHe"        "Le_PeCuLe"       "fredalvar"       "Ducommnath"      "Le_PeCuLe"       "Ducommnath"      "DeGauCHe"        "joanabo"        
[131] "anne_pap"        "benhattar"       "Ducommnath"      "kaltezar"        "anne_pap"        "Le_PeCuLe"       "Lamagouille"     "Lamagouille"     "Le_PeCuLe"       "Le_PeCuLe"      
[141] "anne_pap"        "buercher"        "buercher"        "Lamagouille"     "Lamagouille"     "Lamagouille"     "kaltezar"        "buercher"        "bonpourtonpoil"  "benhattar"      
[151] "buercher"        "anne_pap"        "kaltezar"        "buercher"        "buercher"        "Lamagouille"     "kaltezar"        "polychimel"      "tchernopuss"     "buercher"       
[161] "tchernopuss"     "kaltezar"        "tchernopuss"     "tchernopuss"     "Lamagouille"     "nashtags"        "kaltezar"        "kaltezar"        "tchernopuss"     "Apleonexe"      
[171] "tchernopuss"     "tchernopuss"     "kaltezar"        "tchernopuss"     "kaltezar"        "tchernopuss"     "kaltezar"        "tchernopuss"     "tchernopuss"     "buercher"       
[181] "tchernopuss"     "tchernopuss"     "tchernopuss"     "kaltezar"        "tchernopuss"     "tchernopuss"     "tchernopuss"     "Lamagouille"     "tchernopuss"     "buercher"       
[191] "kaltezar"        "Lamagouille"     "Lamagouille"     "kaltezar"        "rhodine75"       "kaltezar"        "mdemontmollin"   "mdemontmollin"   "mdemontmollin"   "Lamagouille"    
[201] "mdemontmollin"   "benoitgaillard"  "buercher"        "buercher"        "benoitgaillard"  "CuriousHat"      "GrandjeanMartin" "benoitgaillard"  "Ballymag"        "CuriousHat"     
[211] "Ballymag"        "benoitgaillard"  "Ballymag"        "pascalbernheim"  "Lamagouille"     "GrandjeanMartin" "Ballymag"        "1dex_Valais"     "RTSinfoplus"     "Lamagouille"    
[221] "GrandjeanMartin" "tchernopuss"     "tchernopuss"     "GrandjeanMartin"
Puis on a besoin des personnes citées. Là c’est le petit passage compliqué, on va récupérer tout le texte encadré par un « @ » et un espace ou un signe de ponctuation. Pour ça, on va utiliser les expressions régulières. Le bout de code qui va suivre correspond à ce que je viens de décrire, c’est-à-dire qu’on veut tout le texte commençant par un « @ », composé de chiffres, de lettres majuscules ou minuscules (et peu importe l’ordre) ou d’un « _ » (underscore). La fonction gregexpr nous permet d’extraire du texte.
actors.in <- gregexpr("@[0-9A-Za-z_]+",res.df$text)
À chaque fois qu’on trouve un « @ », on va saisir tous les caractères suivants, tant qu’il y en a (ce rôle est joué par le « + »). Cette recherche, on la fait dans res.df$text et on l’enregistre dans actors.in
Ensuite il y a un bout de code pour mettre les utilisateurs-auteurs et utilisateurs-mentionnés en relation (version du code commentée sur demande). Attention, en copiant-collant, le code risque de ne pas fonctionner : faire une recherche sur les espaces et les remplacer par des espaces usuels. Me demandez pas pourquoi…
enld.aretes <- matrix(1,ncol=2)
for (i in 1:length(actors.in)) {
 if (actors.in[[i]][1] != -1) {
  emetteur <- actors.out[i]
  for (j in 1:length(actors.in[[i]])) {
   arobase.pos <- actors.in[[i]][j]
   recepteur <- substr(res.df$text[i],
                       arobase.pos+1, arobase.pos +
                       attr(actors.in[[i]], 
                       which="match.length")[j] - 1)
   enld.aretes <-rbind(enld.aretes, 
                       c(as.character(emetteur),
                       as.character(recepteur)))
  }
 }
}

enld.aretes <- enld.aretes[-1,]
enld.aretes <- tolower(enld.aretes)
Après cette étape (mettre les utilisateurs en relation, retirer la première ligne du data frame, tout passer en minuscules), nous avons toutes les relations établies durant le débat #EnLD du 4-5 avril.
> enld.aretes

       [,1]              [,2]             
  [1,] "lamagouille"     "politiquevd"    
  [2,] "rtsinfoplus"     "pssuisse"       
  [3,] "politiquevd"     "c_domenjoz"     
  [4,] "kafekreme"       "toshikoshi"     
  [5,] "toshikoshi"      "kafekreme"
  [6,] 

  ...

  [176,] "rtsinfoplus"     "lamagouille"    
  [177,] "grandjeanmartin" "1dex_valais"
L’objet enld.aretes contient toutes les relations, ce qu’on appelle les arcs du graphe. Dans igraph (qu’on utilise à partir de maintenant), c’est une edgelist. Le graphe se crée de la manière suivante :
g <- graph.edgelist(enld.aretes)
Ici on l’enregistre dans g. Certaines relations existant à plusieurs exemplaires (si A écrit à B plusieurs fois, par exemple s’ils conversent), on va les dénombrer et les remplacer par une seule relation. On supprime aussi les boucles (auto-citations). Le nombre d’échanges servira à déterminer la taille de l’arc sur le dessin.
E(g)$weight <- count.multiple(g)
g <- simplify(g)
On peut avoir un aperçu du graphe avec
> summary(g)

Vertices: 47 
Edges: 105 
Directed: TRUE 
No graph attributes.
Vertex attributes: name.
Edge attributes: weight.
Et on peut le visualiser avec 

plot(g,layout=layout.circle)

 [oui, c’est moche, on verra comment régler ça plus tard]

À suivre…
Publicités