How to create a Twitter Sentiment Analysis using R and Shiny

Everytime you release a product or service you want to receive feedback from users so you know what they like and what they don’t. Sentiment Analysis can help you. I will show you how to create a simple application in R and Shiny to perform Twitter Sentiment Analysis in real-time. I use RStudio.

We will be able to see if they liked our products or not. Also, we will create a wordcloud to find out why they liked it and why not.

First, I will create a Shiny Project. To learn how to create a Shiny apps you might read this tutorial by Teja Kodali and another tutorial by Aaron Gowins.

Then, in the ui.R file, I put this code:

shinyUI(fluidPage( 
titlePanel("Sentiment Analysis"), #Title
textOutput("currentTime"),   #Here, I show a real time clock
h4("Tweets:"),   #Sidebar title
sidebarLayout(
sidebarPanel(
dataTableOutput('tweets_table') #Here I show the users and the sentiment
),

Show a plot of the generated distribution:

mainPanel(
plotOutput("distPlot"), #Here I will show the bars graph
sidebarPanel(
plotOutput("positive_wordcloud") #Cloud for positive words
),
sidebarPanel(
plotOutput("negative_wordcloud") #Cloud for negative words
),
sidebarPanel(
plotOutput("neutral_wordcloud") #Cloud for neutral words
)))))

Here, I will show a title, the current time, a table with Twitter user name, a bar graph and wordclouds. Also you have to put your consumer key and secret (replace xxxxxxxxxx). You will have to create and application in Twitter Developers site and then extract this info.

Now, I will create the server side:

library(shiny)
library(tm)
library(wordcloud)
library(twitteR)
 
shinyServer(function(input, output, session) {
  setup_twitter_oauth(consumer_key = "xxxxxxxxxx", consumer_secret = "xxxxxxxxxxxx")
  token <- get("oauth_token", twitteR:::oauth_cache)
  token$cache()
  output$currentTime <- renderText({invalidateLater(1000, session)
                                  paste("Current time is: ",Sys.time())})
   observe({
     invalidateLater(60000,session)
    count_positive = 0
    count_negative = 0
    count_neutral = 0
    positive_text <- vector()
    negative_text <- vector()
    neutral_text <- vector()
    vector_users <- vector()
    vector_sentiments <- vector()
    tweets_result = ""
    tweets_result = searchTwitter("word-to-find-in-twitter")
    for (tweet in tweets_result){
      print(paste(tweet$screenName, ":", tweet$text))
       vector_users <- c(vector_users, as.character(tweet$screenName));
       if (grepl("lindo", tweet$text, ignore.case = TRUE) == TRUE | grepl("Wonderful", tweet$text, ignore.case = TRUE) | grepl("Awesome", tweet$text, ignore.case = TRUE)){
        count_positive = count_positive + 1
        print("positivo")
         vector_sentiments <- c(vector_sentiments, "Positive")
        positive_text <- c(positive_text, as.character(tweet$text))
       } else if (grepl("Boring", tweet$text, ignore.case = TRUE) | grepl("I'm sleeping", tweet$text, ignore.case = TRUE)) { 
        count_negative = count_negative + 1
        print("negativo")
        vector_sentiments <- c(vector_sentiments, "Negative")
        negative_text <- c(negative_text, as.character(tweet$text))
       } else {
        count_neutral = count_neutral + 1
        print("neutral")
        vector_sentiments <- c(vector_sentiments, "Neutral")
        neutral_text <- c(neutral_text, as.character(neutral_text))
      }
    }
    df_users_sentiment <- data.frame(vector_users, vector_sentiments)
    output$tweets_table = renderDataTable({
      df_users_sentiment
    })

    output$distPlot <- renderPlot({
      results = data.frame(tweets = c("Positive", "Negative", "Neutral"), numbers = c(count_positive,count_negative,count_neutral))
      barplot(results$numbers, names = results$tweets, xlab = "Sentiment", ylab = "Counts", col = c("Green","Red","Blue"))
      if (length(positive_text) > 0){
        output$positive_wordcloud <- renderPlot({ wordcloud(paste(positive_text, collapse=" "), min.freq = 0, random.color=TRUE, max.words=100 ,colors=brewer.pal(8, "Dark2"))  })
      }
      if (length(negative_text) > 0) {
        output$negative_wordcloud <- renderPlot({ wordcloud(paste(negative_text, collapse=" "), random.color=TRUE,  min.freq = 0, max.words=100 ,colors=brewer.pal(8,"Set3"))  })
      }
      if (length(neutral_text) > 0){
        output$neutral_wordcloud <- renderPlot({ wordcloud(paste(neutral_text, collapse=" "), min.freq = 0, random.color=TRUE , max.words=100 ,colors=brewer.pal(8, "Dark2"))  })
      }
    })
  })
})

Here a screenshot of the shiny app we created:
post

It's a really simply code, not complex at all. The purpose of it is just for testing and so you guys can practice R language. If you have questions just let me know.

13 Comments

  1. SN
    Sameer Nichite November 28, 2018

    Listening on http://127.0.0.1:7944
    Warning: Error in shinyServer: argument “output” is missing, with no default
    50: shinyServer [#4]
    Error in shinyServer(function(input, output, session) { :
    argument “output” is missing, with no default

    This issue is occur when I run this code.

    Reply
  2. MF
    monikr_on_east forever_a_novic March 6, 2018

    Hi, the article is pretty good for beginners. I realized I wasn’t able to get the application to run because it was behind a firewall in an enterprise network. However, it worked with my home network.

    Kindly help me understand the what does “Vector Users” mean? Which user handles does it fetch tweets from? Also, the sentiment analysis always gives me a Neutral outcome with no WordCloud for the same. Awaiting for your response.

    Reply
  3. RK
    rahul khanna April 12, 2016

    Good tutorial, See I have a question can you take a keyword from user in an input field and search tweets on that keyword and then analyse all the things and plot the histograms.Is that possible?

    Reply
  4. RG
    rahul ganesh April 11, 2016

    Error in curl::curl_fetch_memory(url, handle = handle) :

    Peer certificate cannot be authenticated with given CA certificates

    How to handle this error

    Reply
  5. K
    kmrychl March 3, 2016

    If I have a list of pos words and neg words as txt files, where can I scan this in the code. Right now, I am just getting all neutral tweets so want to define the parameters to be more specific

    Reply
  6. IB
    Indira Burga February 29, 2016

    hola no puedo cargar debido a que tengo este error

    > runApp(“Twitter”)

    Listening on http://127.0.0.1:5509

    Loading required package: tm

    Loading required package: NLP

    Loading required package: wordcloud

    Loading required package: RColorBrewer

    Loading required package: twitteR

    Note: the specification for S3 class “integer64” in package ‘bit64’ seems equivalent to one from package ‘jsonlite’: not turning on duplicate class definitions for this class.

    [1] “Using browser based authentication”

    Use a local file to cache OAuth access credentials between R sessions?

    1: Yes

    2: No

    Warning: Error in init_oauth1.0: Unauthorized (HTTP 401).

    Stack trace (innermost first):

    50:

    49: stop

    48: stop_for_status

    47: init_oauth1.0

    46: self$init_credentials

    45: public_bind_env$initialize

    44: Token1.0$new

    43: oauth1.0_token

    42: token_func

    41: setup_twitter_oauth

    40: server [/home/indira/Dropbox/Maestria Ciencia de Datos/TFM/Dev/R/shiny/Twitter/server.R#19]

    1: runApp

    Error in init_oauth1.0(self$endpoint, self$app, permission = self$params$permission, :

    Unauthorized (HTTP 401).

    Reply
    1. H
      H4X0R January 22, 2018

      did you solved this error?
      because I am also getting same error

      Reply
  7. B
    Brunou January 1, 2016

    We just can analyse 25 records?

    Reply
    1. YD
      Yusuf DemirYürek January 4, 2016

      tweets_result = searchTwitter(“word-to-find-in-twitter”, n=1500). You can add “n=” option (max value is 1500).
      There is a mistake in this line :
      neutral_text <- c(neutral_text, as.character(neutral_text))
      for the neutral worcloud, it is necessary to replace by
      neutral_text <- c(neutral_text, as.character(tweet$text))

      Thanks for this share Diego 🙂

      Reply
      1. B
        Brunou January 4, 2016

        Oh tks, I see this I downloaded the twitterR pdf… 😀 TKS TKS <3

        Reply
  8. VB
    Venu B December 29, 2015

    Thanks Diego! It would be more helpful if you can explain command by command in comments.

    Reply
  9. MO
    Michael O'Donovan December 28, 2015

    Check out the brackets/parentheses on the last few lines on the server.R. Currently you end up with unmatched parens etc.

    Reply
    1. DL
      Diego Lescano December 28, 2015

      Hi Michael, thanks for your comment. It’s updated now. Thanks!

      Reply

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.