Disclamer: This is a joint post with Avner Bar-Hen, a.k.a. @a_bh, Benjamin Guedj, a.k.a. @bguedj and Nathalie Villa, a.k.a. @Natty_V2
Organised annually since 1970 by the French Society of Statistics (SFdS), the Journées de Statistique (JdS) are the most important scientific event of the French statistical community. More than 400 researchers, teachers and practitioners meet at each edition. In 2015, JDS took place in Lille, in France.
SFdS regularly tweets (with the account @Statfr) and for the first year a live-tweet was organized durind JdS. The Hashtag was #JDSLille. The aim of this post is a (brief) statistical analysis of the live-tweet.
Before starting, let us load some appropriate packages (and clean our R session)
> rm(list=ls()) > graphics.off() > library(twitteR) > library(tm) > library(ggplot2) > library(wordcloud) > library(igraph) > library(stringr) > library(sna)
Either you know how to scrap twitter data (see https://rhandbook.wordpress.com/tag/registertwitteroauth/ for example) and you can use the following code (after entering all the standard security parameters)
> setup_twitter_oauth(.............) > tweet =searchTwitter("#JDSLille",n=218) > df <- do.call("rbind", lapply(tweet, as.data.frame))
or you can use directly the dataframe by downloading the tweets
> load(url("http://freakonometrics.free.fr/JDSLille.RData"))
Now we have the data stored. Including the RT we found 219 tweets that represent 48 different users that posted atleast one tweet with the hashtag #JDSLille.
> length(tweet) [1] 219
The most active Twitter account was @bguedj, who initiates the tweet live. The second one is @Statfr the official account of SFdS.
> counts=table(df$screenName) > subset(counts,counts>9) bguedj LaurenceBroze Lionning13 46 12 24 melinaGALL nc233 StatFr 12 15 26
Let have a look at first to the RT. The graph below counts the number of RT per Twitter account
> df$text <- sapply(df$text, + function(x) iconv(x,to='UTF-8')) > trim <- function (x) sub('@','',x) > df$rt=sapply(df$text,function(tweet) trim(str_match(tweet,"^RT (@[[:alnum:]_]*)")[2])) > ggplot()+geom_bar(aes(x=na.omit(df$rt)))+ theme(axis.text.x=element_text(angle=-90,size=10))+xlab(NULL)
It is also possible to see when the tweet are posted. colors indicated the RT and grey indicated new posts.
> ggplot()+geom_bar(aes(x=df$created, + fill=df$rt ))
Let’s have a look at the most common words within tweets.
> myCorpus <- Corpus(VectorSource(df$text)) > myCorpus <- tm_map(myCorpus, stripWhitespace) > myCorpus <- tm_map(myCorpus, removePunctuation) > myCorpus <- tm_map(myCorpus, removeNumbers) > myStopwords <- c(stopwords("fr"), "RT", names(counts)) > myCorpus <- tm_map(myCorpus, removeWords, myStopwords) > corpus.tdm <- TermDocumentMatrix(myCorpus, control = list(wordLengths = c(3,20))) > findFreqTerms(corpus.tdm, lowfreq=10) [1] "aléatoires" "amphi" "bigdata" [4] "conférence" "exposé" "gala" [7] "graphes" "jdslille" "journées" [10] "les" "lionning" "matin" [13] "présente" "session" "soirée" [16] "statistique"
Let us visualize the network
> aretes = cbind(df$rt,df$screenName) > aretes = aretes[apply(aretes,1,function(x) + sum(is.na(x))==0),] > reseau = graph.data.frame(aretes,directed=T) > plot(reseau)
As usual there is a big component (with 36 Twitter account) and few small ones (6 small clusters).
Let’go further.
> graphe = matrix(0,nrow=length(unique(c(df$rt, + df$screenName)))-1,ncol=length(unique(c(df$rt,df$screenName)))-1 ) > nom = unique(c(df$rt,df$screenName)) > rownames(graphe) = colnames(graphe) = nom[!is.na(nom)] > for (i in 1:nrow(aretes)) graphe[aretes[i,1], + aretes[i,2]]=1 > net=graph.adjacency(adjmatrix=graphe, + mode="undirected",weighted=TRUE,diag=FALSE) > net.components <- clusters(net) > net.components$no [1] 7
Let’s vizualize it
> couleur = findInterval(apply(graphe,1,sum), + c(0,1,5,30)) > gplot(graphe,usearrows =FALSE, + displayisolates = TRUE, vertex.col=couleur, + label=colnames(graphe)
or if we zoom in
Black means that none of the post was RT (most of the time they only RT another post). Red is between 1 and 5 RT and green is for more than 5 RT.
Let’s look at the largest component. Density is around 8.1% and transitivity is about 23.7%. Therefore we have a strong preferential attachment.
> net.lcc <- induced.subgraph(net, + net.components$membership== + which.max(net.components$csize)) > graph.density(net.lcc) [1] 0.08108108 > transitivity(net.lcc) [1] 0.2234043
We can also use
> net.clusters <- spinglass.community(net.lcc) > table(net.clusters$membership) 1 2 3 4 5 6 7 5 6 5 3 6 7 5 > V(net.lcc)$community <- net.clusters$membership > plot(net.lcc, main="Communities", + vertex.frame.color=rainbow(max( +net.clusters$members))[net.clusters$membership], + vertex.color=rainbow(max( +net.clusters$members))[net.clusters$membership], + vertex.label=net.clusters$names, + edge.color="grey")
or if we zoom in
For sure, many other results can be obtained from the dataset. Please feel free to use it. And all comments and feedback are welcome!
The interesting point with this code is that it is possible to adapt it for any conference! Use for instance the function
conference = function(hash="#JDSLille",file=url("http://freakonometrics.free.fr/JDSLille.RData")){ if(is.na(file)){ tweet =searchTwitter(hash) df <- do.call("rbind", lapply(tweet, as.data.frame)) } if(!is.na(file)){ load(file) } counts=table(df$screenName) top_tw=rev(sort(subset(counts,counts>quantile(counts,.9)))) cat("Top 10% Twitter Accounts -------------\n") for(i in 1:length(top_tw)) cat(" @",names(top_tw)[i],": ",top_tw[i]," tweets\n",sep="") df$text <- sapply(df$text,function(x) iconv(x,to='UTF-8')) trim <- function (x) sub('@','',x) df$rt=sapply(df$text,function(tweet) trim(str_match(tweet,"^RT (@[[:alnum:]_]*)")[2])) ggplot()+geom_bar(aes(x=na.omit(df$rt)))+theme(axis.text.x=element_text(angle=-90,size=10))+xlab(NULL) ggplot()+geom_bar(aes(x=df$created,fill=df$rt )) myCorpus <- Corpus(VectorSource(df$text)) myCorpus <- tm_map(myCorpus, stripWhitespace) myCorpus <- tm_map(myCorpus, removePunctuation) myCorpus <- tm_map(myCorpus, removeNumbers) myStopwords <- c(stopwords("fr"), "RT", names(counts)) myCorpus <- tm_map(myCorpus, removeWords, myStopwords) corpus.tdm <- TermDocumentMatrix(myCorpus, control = list(wordLengths = c(3,20))) top_terms=findFreqTerms(corpus.tdm, lowfreq=10) cat("Top Words -------------\n") for(i in 1:length(top_terms)) cat(" ",top_terms[i],"\n",sep="") aretes = cbind(df$rt,df$screenName) aretes = aretes[apply(aretes,1,function(x)sum(is.na(x))==0),] reseau = graph.data.frame(aretes,directed=T) plot(reseau) graphe = matrix(0,nrow=length(unique(c(df$rt,df$screenName)))-1,ncol=length(unique(c(df$rt,df$screenName)))-1 ) nom = unique(c(df$rt,df$screenName)) rownames(graphe) = colnames(graphe) = nom[!is.na(nom)] for (i in 1:nrow(aretes)) graphe[aretes[i,1],aretes[i,2]]=1 net=graph.adjacency(adjmatrix=graphe,mode="undirected",weighted=TRUE,diag=FALSE) net.components <- clusters(net) net.components$no couleur = findInterval(apply(graphe,1,sum),c(0,1,5,30)) gplot(graphe,usearrows =FALSE,displayisolates = TRUE,vertex.col=couleur,label=colnames(graphe)) net.lcc <- induced.subgraph(net,net.components$membership== which.max(net.components$csize)) graph.density(net.lcc) transitivity(net.lcc) net.clusters <- spinglass.community(net.lcc) table(net.clusters$membership) V(net.lcc)$community <- net.clusters$membership plot(net.lcc, main="Communities", vertex.frame.color=rainbow(max(net.clusters$members))[net.clusters$membership], vertex.color=rainbow(max(net.clusters$members))[net.clusters$membership], vertex.label=net.clusters$names, edge.color="grey") }
Note that the very last tweet related to the hashtag #JDSLille (during those five days) was
Fin du Debriefing, point final des #JDSLille Rendez vous est pris pour l’année prochaine aux #JDSMontpellier
— Soc. Fr. Stat (SFdS) (@StatFr) 5 Juin 2015
Indeed. That is a good idea ! See you next year for an update version of the statistical analysis of the tweet-live, on #JDSMontpellier.
OpenEdition suggests that you cite this post as follows:
Arthur Charpentier (June 7, 2015). Who interacts on Twitter during a conference (#JDSLille). Freakonometrics. Retrieved December 2, 2024 from https://doi.org/10.58079/ouzu
Dear Arthur,
Thanks for this nice idea of studying “our own data”! You can find on my blog some further cluster analyses on the data you extracted: http://w3.mi.parisdescartes.fr/~cbouveyr/blog/post2
Best,
Charles