Having state election coming soon in Victoria (Australian state where I live) I decided to make a quick analysis and compare what politicians from major Australian party’s post in their Twitter accounts.

I have collected Twitter hanldes of Australian Parliament members (also referred in text and code as MP that states for Member of Parliament) grouped by party. If you are interested how I did it using RSelenium you are welcome to check it in my blog post.

Now I'll use this dataset to collect tweets and do a quick analysis. Start from loading required libraries and the dataframe with Twitter handles.

library(dplyr)
library(purrr)
library(twitteR)
#load dataset from CSV file
mps <- read.csv('mps.csv')

Filter out MPS without Twitter accounts and smaller parties and separate it to different objects

majors <- c("Australian Labor Party", "Liberal Party of Australia", "The Nationals")
mp2 <- mps %>% filter(twitter!="NA" & party %in% majors) %>% group_by(party)
labor <- mp2 %>% filter(party=="Australian Labor Party")
libs <- mp2 %>% filter(party=="Liberal Party of Australia")
nationals <- mp2 %>% filter(party=="The Nationals")

No lets check number of accounts per party

mp2 %>% summarise(n = n())
## # A tibble: 3 x 2
##   party                          n
##   <fct>                      <int>
## 1 Australian Labor Party        48
## 2 Liberal Party of Australia    37
## 3 The Nationals                  9

Looks correct. So we can start using Twitter API to collect the data. There is a handy package for R called TwitterR that we alreay loaded.
If you don't have an app registered in Twitter you need one. You can do it at Twitter Developer website

## download cert file
download.file(url = "http://curl.haxx.se/ca/cacert.pem",
              destfile = "cacert.pem")

# You need your own API key in format
# apikeys <- c ('YOU API KEY', # api key
#               'YOUR API SECRET', # api secret
#               'YOU API ACCESS TOKEN', # access token
#               'YOUR API SECRET TOKEN') # access token secret)
# 
source("api_key.r") #local file as per the structue above, you need to create your own
setup_twitter_oauth(apikeys[1], apikeys[2],apikeys[3], apikeys[4])

You may be asked to prompt saying “Use a local file to cache OAuth access credentials between R sessions?, where you should answer Yes. If you getting errors try to install or update openssl package.

Now define own search function based on, conduct the search and save the results so, can use it later

tsearch <- function (username, n=100){
        q=paste0('from:',as.character(username))
        result <-searchTwitter(q, resultType="recent", n=n, retryOnRateLimit=5)
        return(result)
}
# collecting tweets 
tweets_nat <- map(nationals$twitter, ~tsearch(.x, n=200))
tweets_lab<- map(labor$twitter, ~tsearch(.x, n=200))
tweets_lib<- map(libs$twitter, ~tsearch(.x, n=200))
## we need the last 3 objetcs in analysis script
save(tweets_nat, tweets_lab, tweets_lib, file = "tweets.RData")

Data collection is done. We can move to data analysis.

Data analysis

Good way to have quick outlook to text data is to use word clouds. Let’s build them!

Load several libraries

require(stringr)
require(tm)
require(wordcloud)
require(SnowballC)

Load data – tweets we collected and prepare it for building word clouds

load("tweets.Rdata")
# Function to extract text only 
gettext<- function (mylist) {
        text <- character()
        for (i in 1:length(mylist)){
        text <- append(text,  mylist[[i]][[".->text"]])        
        }
        return(text)        
}
# for some reasons unlist inside function doesn't work, so sample call should look like
text <- gettext(unlist(tweets_nat))

Next we'll build text corpus (here tm package functionality used, SnowballC used for stemming). To be honest stemming result is a bit clunky, you may see words like 'communiti' or 'minist' that probably coming from there. So if you know better stemming approach – I'll appreciate a reference.

# build and clean corpus
corp <- function (text) {
        text_df <- data_frame(line = 1, text = text)     
        # clean up some stuff
        text_df <- sapply(text_df$text,function(row) iconv(row, "latin1", "ASCII", sub=""))
        #corpus is a collection of text documents
        t_corpus <- Corpus(VectorSource(text_df))
        #clean text and stemming
        t_clean <- tm_map(t_corpus, removePunctuation)
        t_clean <- tm_map(t_clean, content_transformer(tolower))
        t_clean <- tm_map(t_clean, removeWords, stopwords("english"))
        t_clean <- tm_map(t_clean, removeNumbers)
        t_clean <- tm_map(t_clean, stripWhitespace)
        t_clean <- tm_index(t_clean, stemDocument)
        return(t_clean)        
}

lab_corp <- gettext(unlist(tweets_lab)) %>% corp()
lib_corp <- gettext(unlist(tweets_lib)) %>% corp()
nat_corp <- gettext(unlist(tweets_nat)) %>% corp()

Now we can start building word clouds.
First define own function with nicer than default formatting.

mywordcloud <- function (corp) {
        wc <-wordcloud(corp, random.order=F,max.words=80, col=rainbow(80), scale=c(3,0.2))        
        return(wc)
}

Next we'll build wordclouds for all parties.

Labor

plot.new()
lab_wc <- lab_corp %>% mywordcloud()

Liberals

plot.new()
lib_wc <- lib_corp %>% mywordcloud()

Nationals

plot.new()
nat_wc <- nat_corp %>% mywordcloud()

Commonality and differences

We can also check what is common in different between content of Twitter posts of different parties. First, we need wrange the data a bit

## Reducing dimensions - one document per party

# defining function
my_tm <- function (corp, name){
        term.matrix <- termFreq (corp) %>% as.matrix() 
        colnames(term.matrix)<- name
        # transpose to merge later
        #term.matrix <- t(term.matrix)
        term.matrix <- as.data.frame(term.matrix)
        term.matrix$word <- row.names(term.matrix)
        return(term.matrix)
}
# to new df
atm_nats <-  my_tm (nat_corp, "Nationals")
atm_libs <-  my_tm (lib_corp, "Liberals")
atm_labs <-  my_tm (lab_corp, "Labor")
# join
atm <- full_join(atm_nats, atm_libs, by = 'word') %>% full_join(atm_labs,by = 'word')
# formatting for further use
row.names(atm)<-atm$word
atm <- atm[c(1,3,4)]
atm <- as.matrix(atm)
atm[is.na(atm)] <- 0

Next we can build comparison word cloud and commonality cloud

Comparison word cloud

# Comparison Cloud, words that are party specific
plot.new()
comparison.cloud(atm,max.words=80,scale=c(3,.2), random.order=FALSE, colors=brewer.pal(max(3,ncol(atm)),"Dark2"),
                 use.r.layout=FALSE, title.size=3,
                 title.colors=NULL, match.colors=FALSE,
                 title.bg.colors="grey90")

Commonality word cloud

plot.new()
commonality.cloud(atm, max.words=80,random.order=FALSE)     

Quick observations

You may do your own analysis of the results, but there are several things that I've noticed:

Full source code and resulting files can be found in Github repository