<p><div><div><div><div data-width="100" data-show-faces="false" data-send="true" data-layout="button_count" data-height="21" data-href="https://www.r-bloggers.com/twitter-sentiment-analysis-with-machine-learning-in-r-using-doc2vec-approach/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="http://analyzecore.com/2017/02/08/twitter-sentiment-analysis-doc2vec/"> R language – AnalyzeCore – data is beautiful, data is a story</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<div></div>
<p><span>Recently I’ve worked with <strong>word2vec</strong> and <strong>doc2vec</strong> algorithms that I found interesting from many perspectives. Even though I used them for another purpose, the main thing they were developed for is Text analysis. As I noticed, my 2014 year’s article <a target="_blank" rel="nofollow" href="http://analyzecore.com/2014/04/28/twitter-sentiment-analysis/">Twitter sentiment analysis</a></span><span> is one of the most popular blog posts on the blog even today. Therefore, I decided to update it with a modern approach.</span></p>
<p><span>The problem with the previous method is that it just computes the number of positive and negative words and makes a conclusion based on their difference. Therefore, when using a simple vocabularies approach for a phrase “not bad” we’ll get a negative estimation.</span></p>
<p><span>But <strong>doc2vec</strong> is a deep learning algorithm that draws context from phrases. It’s currently one of the best ways of sentiment classification for movie reviews. You can use the following method to analyze feedbacks, reviews, comments, and so on. And you can expect better results comparing to tweets analysis because they usually include lots of misspelling.</span></p>
<p><span>We’ll use tweets for this example because it’s pretty easy to get them via Twitter API. We only need to create an app on <a target="_blank" rel="nofollow" href="https://dev.twitter.com/">https://dev.twitter.com</a> (My apps menu) and find an API Key, API secret, Access Token and Access Token Secret on Keys and Access Tokens menu tab.</span></p>
<p><span>First, I’d like to give a credit to <a target="_blank" rel="nofollow" href="http://www.dsnotes.com/">Dmitry Selivanov</a>, the author of the great <a target="_blank" rel="nofollow" href="http://text2vec.org/"><strong>text2vec</strong> R package</a> that we’ll use for sentiment analysis.</span></p>
<p><span>You can download a set of 1.6 million classified tweets <a target="_blank" rel="nofollow" href="https://docs.google.com/file/d/0B04GJPshIjmPRnZManQwWEdTZjg/edit">here</a> and use them to train a model. Before we start the analysis, I want to point your attention to how tweets were classified. There are two grades of sentiment: 0 (negative) and 4 (positive). That means that they are not neutral. I suggest using a probability of positiveness instead of class. In this case, we’ll get a range of values from 0 (completely negative) to 1 (completely positive) and assume that values from 0.35 to 0.65 are somewhere in the middle and they are neutral. In addition, we will use <a target="_blank" rel="nofollow" href="https://en.wikipedia.org/wiki/Tf%E2%80%93idf">TF-IDF</a> method for text preprocessing.</span></p>
<p><span>The following is the R code for training the model. Note that model training can take up to an hour, depending on computer’s configuration:</span></p>
<p><strong title="click to expand R code" tabindex="0">click to expand R code</strong>
<div>
<pre>

# loading packages
library(twitteR)
library(ROAuth)
library(tidyverse)
library(text2vec)
library(caret)
library(glmnet)
library(ggrepel)

### loading and preprocessing a training set of tweets
# function for converting some symbols
conv_fun &lt;- function(x) iconv(x, &quot;latin1&quot;, &quot;ASCII&quot;, &quot;&quot;)

##### loading classified tweets ######
# source: http://help.sentiment140.com/for-students/
# 0 - the polarity of the tweet (0 = negative, 4 = positive)
# 1 - the id of the tweet
# 2 - the date of the tweet
# 3 - the query. If there is no query, then this value is NO_QUERY.
# 4 - the user that tweeted
# 5 - the text of the tweet

tweets_classified &lt;- read_csv('training.1600000.processed.noemoticon.csv',
 col_names = c('sentiment', 'id', 'date', 'query', 'user', 'text')) %>%
 # converting some symbols
 dmap_at('text', conv_fun) %>%
 # replacing class values
 mutate(sentiment = ifelse(sentiment == 0, 0, 1))

# data splitting on train and test
set.seed(2340)
trainIndex &lt;- createDataPartition(tweets_classified$sentiment, p = 0.8, 
 list = FALSE, 
 times = 1)
tweets_train &lt;- tweets_classified[trainIndex, ]
tweets_test &lt;- tweets_classified[-trainIndex, ]

##### doc2vec #####
# define preprocessing function and tokenization function
prep_fun &lt;- tolower
tok_fun &lt;- word_tokenizer

it_train &lt;- itoken(tweets_train$text, 
 preprocessor = prep_fun, 
 tokenizer = tok_fun,
 ids = tweets_train$id,
 progressbar = TRUE)
it_test &lt;- itoken(tweets_test$text, 
 preprocessor = prep_fun, 
 tokenizer = tok_fun,
 ids = tweets_test$id,
 progressbar = TRUE)

# creating vocabulary and document-term matrix
vocab &lt;- create_vocabulary(it_train)
vectorizer &lt;- vocab_vectorizer(vocab)
dtm_train &lt;- create_dtm(it_train, vectorizer)
dtm_test &lt;- create_dtm(it_test, vectorizer)
# define tf-idf model
tfidf &lt;- TfIdf$new()
# fit the model to the train data and transform it with the fitted model
dtm_train_tfidf &lt;- fit_transform(dtm_train, tfidf)
dtm_test_tfidf &lt;- fit_transform(dtm_test, tfidf)

# train the model
t1 &lt;- Sys.time()
glmnet_classifier &lt;- cv.glmnet(x = dtm_train_tfidf, y = tweets_train[['sentiment']], 
 family = 'binomial', 
 # L1 penalty
 alpha = 1,
 # interested in the area under ROC curve
 type.measure = &quot;auc&quot;,
 # 5-fold cross-validation
 nfolds = 5,
 # high value is less accurate, but has faster training
 thresh = 1e-3,
 # again lower number of iterations for faster training
 maxit = 1e3)
print(difftime(Sys.time(), t1, units = 'mins'))

plot(glmnet_classifier)
print(paste(&quot;max AUC =&quot;, round(max(glmnet_classifier$cvm), 4)))

preds &lt;- predict(glmnet_classifier, dtm_test_tfidf, type = 'response')[ ,1]
glmnet:::auc(as.numeric(tweets_test$sentiment), preds)

# save the model for future using
saveRDS(glmnet_classifier, 'glmnet_classifier.RDS')
#######################################################
</pre>
</div>
<p><span>As you can see, both AUC on train and test datasets are pretty high (0.875 and 0.874). Note that we saved the model and you don’t need to train it every time you need to assess some tweets. Next time you do sentiment analysis, you can start with the script below.</span></p>
<p><span>Ok, once we have model trained and validated, we can use it. For this, we start with tweets fetching via Twitter API and preprocessing in the same way as with classified tweets. For instance, the company I work for has just released an ambitious product for Mac users and it’s interesting to analyze how tweets about <a target="_blank" rel="nofollow" href="https://setapp.com/">SetApp</a> are rated.</span></p>
<p><strong title="click to expand R code" tabindex="0">click to expand R code</strong>
<div>
<pre>
### fetching tweets ###
download.file(url = &quot;http://curl.haxx.se/ca/cacert.pem&quot;,
destfile = &quot;cacert.pem&quot;)
setup_twitter_oauth('your_api_key', # api key
'your_api_secret', # api secret
'your_access_token', # access token
'your_access_token_secret' # access token secret
)

df_tweets &lt;- twListToDF(searchTwitter('setapp OR #setapp', n = 1000, lang = 'en')) %>%
# converting some symbols
dmap_at('text', conv_fun)

# preprocessing and tokenization
it_tweets &lt;- itoken(df_tweets$text,
preprocessor = prep_fun,
tokenizer = tok_fun,
ids = df_tweets$id,
progressbar = TRUE)

# creating vocabulary and document-term matrix
dtm_tweets &lt;- create_dtm(it_tweets, vectorizer)

# transforming data with tf-idf
dtm_tweets_tfidf &lt;- fit_transform(dtm_tweets, tfidf)

# loading classification model
glmnet_classifier &lt;- readRDS('glmnet_classifier.RDS')

# predict probabilities of positiveness
preds_tweets &lt;- predict(glmnet_classifier, dtm_tweets_tfidf, type = 'response')[ ,1]

# adding rates to initial dataset
df_tweets$sentiment &lt;- preds_tweets
</pre>
</div>
<p><span>And finally, we can visualize the result with the following code:</span></p>
<p><strong title="click to expand R code" tabindex="0">click to expand R code</strong>
<div>
<pre>
# color palette
cols &lt;- c(&quot;#ce472e&quot;, &quot;#f05336&quot;, &quot;#ffd73e&quot;, &quot;#eec73a&quot;, &quot;#4ab04a&quot;)

set.seed(932)
samp_ind &lt;- sample(c(1:nrow(df_tweets)), nrow(df_tweets) * 0.1) # 10% for labeling

# plotting
ggplot(df_tweets, aes(x = created, y = sentiment, color = sentiment)) +
theme_minimal() +
scale_color_gradientn(colors = cols, limits = c(0, 1),
breaks = seq(0, 1, by = 1/4),
labels = c(&quot;0&quot;, round(1/4*1, 1), round(1/4*2, 1), round(1/4*3, 1), round(1/4*4, 1)),
guide = guide_colourbar(ticks = T, nbin = 50, barheight = .5, label = T, barwidth = 10)) +
geom_point(aes(color = sentiment), alpha = 0.8) +
geom_hline(yintercept = 0.65, color = &quot;#4ab04a&quot;, size = 1.5, alpha = 0.6, linetype = &quot;longdash&quot;) +
geom_hline(yintercept = 0.35, color = &quot;#f05336&quot;, size = 1.5, alpha = 0.6, linetype = &quot;longdash&quot;) +
geom_smooth(size = 1.2, alpha = 0.2) +
geom_label_repel(data = df_tweets[samp_ind, ],
aes(label = round(sentiment, 2)),
fontface = 'bold',
size = 2.5,
max.iter = 100) +
theme(legend.position = 'bottom',
legend.direction = &quot;horizontal&quot;,
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
plot.title = element_text(size = 20, face = &quot;bold&quot;, vjust = 2, color = 'black', lineheight = 0.8),
axis.title.x = element_text(size = 16),
axis.title.y = element_text(size = 16),
axis.text.y = element_text(size = 8, face = &quot;bold&quot;, color = 'black'),
axis.text.x = element_text(size = 8, face = &quot;bold&quot;, color = 'black')) +
ggtitle(&quot;Tweets Sentiment rate (probability of positiveness)&quot;)
</pre>
</div>
<p><a target="_blank" rel="nofollow" href="http://analyzecore.com/wp-content/uploads/2017/02/tweets_sentiment_rate.png"><br>
</a></p>
<p><a target="_blank" rel="nofollow" href="https://i1.wp.com/analyzecore.com/wp-content/uploads/2017/02/tweets_sentiment_rate_1.png"><img sizes="(max-width: 648px) 100vw, 648px" alt="" class="wp-image-1743" src="https://i1.wp.com/analyzecore.com/wp-content/uploads/2017/02/tweets_sentiment_rate_1.png?resize=648%2C454" data-recalc-dims="1"></a></p>
<p>The green line is the boundary of positive tweets and the red one is the boundary of negative tweets. In addition, tweets are colored with red (negative), yellow (neutral) and green (positive) colors.  As you can see, most of the tweets are around the green boundary and it means that they tend to be positive.</p>
<div></div>
<p>The post <a target="_blank" rel="nofollow" href="http://analyzecore.com/2017/02/08/twitter-sentiment-analysis-doc2vec/">Twitter sentiment analysis with Machine Learning in R using doc2vec approach</a> appeared first on <a target="_blank" rel="nofollow" href="http://analyzecore.com/">AnalyzeCore – data is beautiful, data is a story</a>.</p>
<p>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="http://analyzecore.com/2017/02/08/twitter-sentiment-analysis-doc2vec/"> R language – AnalyzeCore – data is beautiful, data is a story</a></strong>.</div>
<hr>
<a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" rel="nofollow" href="https://www.r-project.org/">R</a> news and <a title="R tutorials" rel="nofollow" href="https://www.r-bloggers.com/search/tutorial">tutorials</a> on topics such as: <a title="Data science" rel="nofollow" href="https://www.r-bloggers.com/search/data%20science">Data science</a>, <a title="Big Data" rel="nofollow" href="https://www.r-bloggers.com/search/Big%20Data">Big Data, <a title="R jobs" rel="nofollow" href="https://www.r-users.com/">R jobs</a>, visualization (<a title="ggplot and ggplot2 tutorials" rel="nofollow" href="https://www.r-bloggers.com/search/ggplot2">ggplot2</a>, <a title="Boxplots using lattice and ggplot2 tutorials" rel="nofollow" href="https://www.r-bloggers.com/search/boxplot">Boxplots</a>, <a title="Maps and gis" rel="nofollow" href="https://www.r-bloggers.com/search/map">maps</a>, <a title="Animation in R" rel="nofollow" href="https://www.r-bloggers.com/search/animation">animation</a>), programming (<a title="RStudio IDE for R" rel="nofollow" href="https://www.r-bloggers.com/search/RStudio">RStudio</a>, <a title="Sweave and literate programming" rel="nofollow" href="https://www.r-bloggers.com/search/sweave">Sweave</a>, <a title="LaTeX in R" rel="nofollow" href="https://www.r-bloggers.com/search/LaTeX">LaTeX</a>, <a title="SQL and databases" rel="nofollow" href="https://www.r-bloggers.com/search/SQL">SQL</a>, <a title="Eclipse IDE for R" rel="nofollow" href="https://www.r-bloggers.com/search/eclipse">Eclipse</a>, <a title="git and github, Version Control System" rel="nofollow" href="https://www.r-bloggers.com/search/git">git</a>, <a title="Large data in R using Hadoop" rel="nofollow" href="https://www.r-bloggers.com/search/hadoop">hadoop</a>, <a title="Web Scraping of google, facebook, yahoo, twitter and more using R" rel="nofollow" href="https://www.r-bloggers.com/search/Web+Scraping">Web Scraping</a>) statistics (<a title="Regressions and ANOVA analysis tutorials" rel="nofollow" href="https://www.r-bloggers.com/search/regression">regression</a>, <a title="principal component analysis tutorial" rel="nofollow" href="https://www.r-bloggers.com/search/PCA">PCA</a>, <a title="Time series" rel="nofollow" href="https://www.r-bloggers.com/search/time+series">time series</a>, <a title="finance trading" rel="nofollow" href="https://www.r-bloggers.com/search/trading">trading</a>) and more...
<img height="1" alt="" width="1" src="http://feeds.feedburner.com/~r/RBloggers/~4/WpcmI6EEDIo"></a></div>