Hands-on: Tweets importeren, analyseren en Text Mining in R

Tweets analyseren met R en tekst analyseren

Net zoals het analyseren van grote hoeveelheden numerieke data, kan het ook waardevol zijn om grote hoeveelheden tekst-data te analyseren. Hierdoor kun je uit grote hoeveelheden tekst de algemene boodschap of het sentiment afleiden. Daarnaast zijn er nog vele andere doeleinden voortekst analyse, bijvoorbeeld de termen die worden gebruikt of vergelijkingen van bepaalde onderwerpen in een grote tekstcollectie.

In dit voorbeeld analyseer ik een grote hoeveelheid tweets die de namen van een aantal Nederlandse steden bevatten. Deze worden daarna geanalyseerd. Het is een hands-on sessie die in R. Je kunt dus meedoen :).

Dit artikel bevat de volgende onderdelen:

  • R packages;
  • Twitter API authenticatie;
  • Tweets streamen;
  • Text mining;
  • Wordcloud.

R packages

Voor het streamen en analyseren van tweets zijn een aantal packages nodig. Als je deze packages nog niet hebt, gebruik dan het volgende command om het package te installeren:

# R package installeren
install.packages("")

Eenmaal geïnstalleerd, kun je deze openen met de “library()” functie.

# Open benodigde R packages

# R package voor de stream API van Twitter
library(streamR)

# R packages voor authenticatie met Twitter
library(ROAuth)
library(RCurl)

# R packages voor visualisatie
library(ggplot2)
library(wordcloud)
library(RColorBrewer)

# R packages voor text mining
library(tm)

Authenticatie

Om tweets te importeren zijn er twee mogelijkheden: (1) de REST API en de (2) Stream API. In dit geval maken we gebruik van de stream API. Hiermee wordt er voor een bepaalde periode “geluisterd” naar de tweets die worden geplaatst door twitteraars.

Het is nodig om een applicatie aan te maken met jouw Twitter account. Deze applicatie krijgt dan informatie zoals de key en secret die je kunt gebruiken voor jouw sessie. Een Twitter-applicatie is makkelijk te maken op https://apps.twitter.com/ :

Als de applicatie eenmaal gemaakt is, kun je de informatie uit die applicatie gebruiken om in het onderstaande code snippet te gebruiken. Vervang  en  dus met de key en secret van jouw aangemaakte applicatie. Met de onderstaande code wordt de applicatie geautoriseerd:

# authentificatie waarden toewijzen
reqURL <- "https://api.twitter.com/oauth/request_token"
accessURL <- "https://api.twitter.com/oauth/access_token"
authURL <- "https://api.twitter.com/oauth/authorize"
consumerKey <- ""
consumerSecret <- ""

# authentificatie laten plaatsvinden met de toegewezen waarden
my_oauth <- OAuthFactory$new(consumerKey=consumerKey,
                             consumerSecret=consumerSecret,
                             requestURL=reqURL,
                             accessURL=accessURL,
                             authURL=authURL)

# authentificeren
my_oauth$handshake(cainfo = "cacert.pem")

Een venster wordt geopend waarin je wordt gevraagd te applicatie the autoriseren:

Hierna wordt je doorverwezen naar een venster met een Pincode.

Deze pincode moet je vervolgens in de R-console plakken. Ook heb je het “cacert.pem” bestand nodig (een certificaat). Deze kun je hier downloaden . Zorg ervoor dat dit bestand in de working directory staat van het huidige project.

Hierna is de authenticatie voltooid en kan het streamen beginnen.

Tweets streamen

Voor dit artikel heb ik de stream voor twee uur opengezet om te luisteren naar tweets die een van de namen steden bevat in de vector die ik de functie gegeven heb. Zoals je kunt zien gebruik ik hier de “filterStream()” functie voor. Natuurlijk kun je ervoor kiezen om voor een veel kortere tijd naar tweets te luisteren, bijvoorbeeld voor een minuut, en 60invullen in plaats van 7200 seconden. Met de “CityTweets.json” waarde geeft ik aan naar welk nieuw bestand de tweets geschreven moeten worden, dit kan iedere naam hebben zolang het maar de .json-extensie heeft. Dit is namelijk het formaat van de Twitter API.

filterStream("CityTweets.json",
             track = c("Utrecht", "Amsterdam", "Rotterdam", "Enschede", "Eindhoven", "Groningen"),
             timeout = 7200, 
             oauth = my_oauth)

Tweets converteren naar R data frame

Als de tweets zijn geïmporteerd, wil ik deze keurig netjes in een R data frame hebben. Daar kan ik de “parseTweets()” functie voor gebruiken.

# Een R data frame aanmaken met de gestreamde tweets 

CityTweets.df <- parseTweets("CityTweets.json", simplify = TRUE)

Hoeveel tweets hebben we?

#
# de dimensies van de twitter data frame weergeven

dim(CityTweets.df)
[1] 7454   31

We hebben dus 7454 tweets. Daarbij hebben we nog 30 andere kolommen die andere informatie over de tweets bevatten zoals de gebruikersnaam en de datum.

Termen in tweets tellen

Ik maak een nieuw data frame aan omdat ik wil visualiseren hoe vaak de steden in de tweets zijn genoemd. Daarna plot ik deze data frame met de “qplot” functie. Dit is een functie uit het “ggplot2” functie en staat voor Quick Plot, het namelijk heel eenvoudig om met deze functie te visualiseren.

# Een data frame maken die per stad aangeeft hoe vaak deze in de gestreamde
# tweets voor komen

CityTweetsCount <- data.frame(term = c("Utrecht", "Amsterdam", "Rotterdam", "Enschede", "Eindhoven", "Groningen"),
                          count = c( length(grep("Utrecht", CityTweets.df$text, ignore.case = TRUE)),
                                     length(grep("Amsterdam", CityTweets.df $text, ignore.case = TRUE)),
                                     length(grep("Rotterdam", CityTweets.df $text, ignore.case = TRUE)),
                                     length(grep("Enschede",  CityTweets.df $text, ignore.case = TRUE)),
                                     length(grep("Eindhoven", CityTweets.df $text, ignore.case = TRUE)),
                                     length(grep("Groningen", CityTweets.df $text, ignore.case = TRUE))
                          ))

# getelde tweets weergeven

qplot(term, data=CityTweetsCount, geom="bar", weight=count, ylab="count")

Er is dus het meest getweet over Amsterdam, namelijk in meer dan 3500 tweets zoals in de grafiek is af te lezen.

Text mining

Nu is het tijd voor Text mining. Hiervoor is wat voorbereiding nodig: (1) De tweets opschonen van rare tekens, (2) stopwoorden en andere ongewenste termen weghalen en (3) een Term-Document matrix maken.

Data cleansing

Als eerst haal ik een aantal ongewenste tekens weg uit de “$text” kolom van de CityTweets.df data frame. Hier gebruik ik de “gsub()” functie voor en een regular expression. De “gsub” staat voor global substitution en zorg ervoor dat ik patronen in een text vector kan vervangen. Ik vervang alle tekens dus voor een lege waarde, oftewel niets, een void.

CityTweets.df$text <- CityTweets.df$text<-gsub('[^[:alnum:]+?!&:"/// ]',
                                               "",
                                               CityTweets.df$text)

Term-Document Matrix maken

De volgende stap is een Corpus maken. Een Corpus is een verzameling van tekstdocumenten. Deze wordt opgeschoond en gevormd naar een Term-Document Matrix (TDM). Een Term-Document Matrix is een matrix die alle termen op de x-as weergeeft en de documenten op de y-as weergeeft. Hierbij zijn de documenten de tweets. Uiteindelijk ontstaat er een reuze matrix met iedere aparte term die in de tweets voorkomt met daarbij het aantal dat deze in een document voorkomt.

# een Corpus maken en hier de gestreamde tweets in plaatsen
tweetCorpus<-Corpus(VectorSource(CityTweets.df$text))

# alle hoofdletters vervangen voor kleine letters
tweetCorpus<-tm_map(tweetCorpus, tolower)

# overige leestekens verwijderen
tweetCorpus<-tm_map(tweetCorpus, removePunctuation)

# getallen/nummers verwijderen
tweetCorpus<-tm_map(tweetCorpus, removeNumbers)

# url's verwijderen
removeURL <- function(x) gsub("http[[:alnum:]]*", "", x)
tweetCorpus <- tm_map(tweetCorpus, removeURL)


# platte tekst maken van de tweets
tweetCorpus<-tm_map(tweetCorpus,PlainTextDocument)

# stopwoorden verwijderen
stopwords<-c(stopwords("english"),stopwords("dutch"),stopwords("spanish"),stopwords("german"),stopwords("french"),tolower("input$term"),"het","bij","en","een","de")
tweetCorpus<-tm_map(tweetCorpus, removeWords,stopwords)

# De Term Document Matrix maken van de zojuist opgeschoonde Corpus

TDM<-TermDocumentMatrix(tweetCorpus,control=list(wordLenths=c(1,Inf)))
TDM

De Term Document Matrix, genaamd TDM, is aangemaakt. Voor het beeld laat ik een stukje van de TDM zien:

# Een gedeelte van de TDM laten zien
inspect(TDM[3000:3010, 100:110])

<<TermDocumentMatrix (terms: 11, documents: 11)>>
Non-/sparse entries: 0/121
Sparsity           : 100%
Maximal term length: 14
Weighting          : term frequency (tf)

                Docs
Terms            character(0) character(0) character(0) character(0) character(0) character(0)
  durftevragen              0            0            0            0            0            0
  durp                      0            0            0            0            0            0
  dusartstraat              0            0            0            0            0            0
  dusdavid                  0            0            0            0            0            0
  dutch                     0            0            0            0            0            0
  dutchdailynews            0            0            0            0            0            0
  dutchdigital              0            0            0            0            0            0
  dutchie                   0            0            0            0            0            0
  dutchindustry             0            0            0            0            0            0
  dutchmfa                  0            0            0            0            0            0
  dutchnewsnl               0            0            0            0            0            0
                Docs
Terms            character(0) character(0) character(0) character(0) character(0)
  durftevragen              0            0            0            0            0
  durp                      0            0            0            0            0
  dusartstraat              0            0            0            0            0
  dusdavid                  0            0            0            0            0
  dutch                     0            0            0            0            0
  dutchdailynews            0            0            0            0            0
  dutchdigital              0            0            0            0            0
  dutchie                   0            0            0            0            0
  dutchindustry             0            0            0            0            0
  dutchmfa                  0            0            0            0            0
  dutchnewsnl               0            0            0            0            0

Dit is echter een zeer klein deel van de TDM, dit verklaard ook dat we alleen maar nullen zien als waarden. Je moet geluk hebben om net het goede stukje te pakken van de TDM, zeker gegeven het feit dat het een zeer grote matrix is:

#
# De dimensies van de TDM weergeven

dim(CityTweets.df)
[1] 12890  7454

12890 verschillende termen voor onze 7454 documenten/tweets die bij gebruiken.

Nu de TDM gemaakt is, kunnen we beginnen met Text Mining.

Frequente termen

We zoeken de meest frequente termen op. Het onderstaande command geeft de Tweets weer die minimaal 100 keer voor zijn gekomen.

findFreqTerms(TDM, lowfreq = 100)

 [1] "amp"           "amsterdam"     "bekend"        "blackout"      "bommelding"   
 [6] "centraal"      "eindhoven"     "enschede"      "groningen"     "grote"        
[11] "hotel"         "jaar"          "january"       "last"          "live"         
[16] "netherlands"   "news"          "nieuws"        "nsonline"      "outage"       
[21] "politie"       "power"         "rijden"        "rit"           "rotterdam"    
[26] "sporen"        "station"       "stroomstoring" "tas"           "trein"        
[31] "treinen"       "treinverkeer"  "utrecht"       "uur"           "vacature"     
[36] "vandaag"       "verdachte"     "via"           "weer"          "wel"          
[41] "zie"  

Term associaties

Met term associaties krijg je een beeld welke termen dicht bij elkaar staan in een tweet en dus enige vorm van samenhang weergeven. De waarde is net als bij een correlatie tussen de 0 en 1. Des te hoger de waarde, des te meer samenhang. Qua berekening is het echter niet te vergelijken met een correlatie. Als voorbeeld bekijken we welke termen een minimale associatie hebben van 0.1 met het woord stroomstoring.

findAssocs(TDM, "stroomstoring", 0.1)

$stroomstoring
         oorzaak           bekend            jklnl        amsterdam         gevolgen 
            0.32             0.30             0.29             0.22             0.20 
    treinverkeer          voorbij          zaandam            zorgt            grote 
            0.18             0.16             0.16             0.16             0.15 
       ontregeld        verholpen          eerdere             rust           serene 
            0.14             0.14             0.13             0.13             0.13 
           treft         probleme            chaos        omstreken        operaties 
            0.13             0.12             0.11             0.11             0.11 
            plat      aardedonker     blacktuesday    bonnekerstens       landelijke 
            0.11             0.10             0.10             0.10             0.10 
    ochtendspits         opgelost     rgjournalist          tacbuzz      twitterpoll 
            0.10             0.10             0.10             0.10             0.10 
verantwoordelijk 
            0.10 

Tweets met bepaalde term terugvinden

Soms wil je de originele tweet terughalen. Dit is eenvoudig in R:

# De eerste 5 tweets weergeven die de term "stroomstoring" bevatten
head(CityTweets.df$text[grep("stroomstoring",  CityTweets.df$text, ignore.case = TRUE)], 5)

[1] "RT Nuon: Storing stadswarmte Amsterdam NieuwWest Westpoort en Noord door stroomstoring Eindtijd onbekend Excuses voor de overlast "  
[2] "NS: stel reis naar Amsterdam Utrecht Schiphol en Lelystad uit https://tco/LtFQOlv9iN stroomstoring"                                  
[3] "NS: stel reis naar Amsterdam Utrecht Schiphol en Lelystad uit https://tco/xhzGgpdJAG stroomstoring"                                  
[4] "RT jeroendiederik: Het enige dat functioneerde in Amsterdam Stroomstoring De prioriteiten van onze Hoofdstad: https://tco/9zy1Oo6D3y"
[5] "Ok who missed their turn on putting money in the meter this morning in Amsterdam  stroomstoring"                                     

Wordcloud

Het oog wilt ook wat, daarom maken we ten slotte een Wordcloud die een goed beeld geeft van de term frequenties.

Wordcloud voorbereiden in een matrix

# een matrix maken die als bron gaat dienen voor de wordcloud
m<-as.matrix(TDM)
v<-sort(rowSums(m),decreasing=TRUE)
words<-names(v)
d<-data.frame(word=words,freq=v)
        
# een wordcloud maken
wordcloud(d$word,
          d$freq,
          min.freq=25,
          random.order=FALSE,
          colors=brewer.pal(8,"Paired"))

Slot

Zoals je kunt zien, is het vrij eenvoudig om Tweets te importeren in R. Eenmaal geïmporteerd, kun je er verschillende dingen mee doen zoals text mining of bepaalde visualisaties toepassen. Voor dit artikel heb ik R gebruikt, maar echter is dit in veel andere tools en programmeertalen mogelijk. Python heeft bijvoorbeeld ook uitstekende manieren om tweets te importeren en te analyseren.

Als je het leuk vindt om tweets te importeren en deze te analyseren zonder dat je daar voor hoeft te programmeren, kun je naar de website TweetExplorer gaan. Deze maakt gebruikt van de REST API waarmee je de afgelopen tweets met een bepaalde term in de applicatie kunt importeren en analyseren met dezelfde methodiek als in dit artikel, maar dan zonder code. Daarbij is het ook een uitstekende tool om tweets simpelweg te downloaden. Dit is handig als je geen zin hebt om een applicatie aan te maken op Twitter of er niet uit komt met de authenticatie.

Mocht je willen leren programmeren met R, kun je het boek “Programmeren en Data Analyseren met R” gratis downloaden en er direct mee aan de slag.

Mocht je vragen of opmerkingen hebben, stuur gerust een berichtje.

Geef een reactie

Vul je gegevens in of klik op een icoon om in te loggen.

WordPress.com logo

Je reageert onder je WordPress.com account. Log uit /  Bijwerken )

Facebook foto

Je reageert onder je Facebook account. Log uit /  Bijwerken )

Verbinden met %s

Deze site gebruikt Akismet om spam te bestrijden. Ontdek hoe de data van je reactie verwerkt wordt.

%d bloggers liken dit: