Análise de Sentimentos em Redes Sociais:

Neste trabalho, faremos uma análise de sentimentos no twitter sobre a Série “Rick and Morty”, utilizando uma massa de dados de 10.000 tweets. Analisaremos os dados segundo o modelo “Naive Bayes” para criação dos dados de polarização e de emoções. Também criaremos aqui uma Wordcloud e um dendograma para identificação de associações de palavras ao nosso tema principal (“Rick and Morty”).

Pacotes:

Definição do diretório:

## [1] "/home/sandro/Documents/3.Projetos_ds/machine_learning/1.Concluídos/analise_redes_sociais"

Autenticando junto a API do Twitter (requer key e token access):

setup_twitter_oauth(key, secret, token, tokensecret)
## [1] "Using direct authentication"

Captura de Tweets

Vamos capturar uma massa de dados dos últimos 10.000 Tweets com o tema “Rick and Morty”.

tema <- "Rick and Morty"
qtd_tweets <- 10000
lingua <- "pt"
tweetdata = searchTwitter(tema, n = qtd_tweets, lang = lingua)
## Warning in doRppAPICall("search/tweets", n, params = params, retryOnRateLimit =
## retryOnRateLimit, : 10000 tweets were requested but the API can only return 3838

Tratamento dos dados:

Aqui faremos o tratamento dos dados coletados anteriormente.

Vamos converter os dados coletados em um objeto do tipo ‘Corpus’ e em seguida faremos um processo de limpeza,excluíremos as stopwords, ajustaremos o encoding, removeremos acentuação e converteremos as letras para minúsculas.

options(warn=-1)
tweetlist <- sapply(tweetdata, function(x) x$getText())
tweetlist <- iconv(tweetlist, to = "utf-8", sub="")
tweetlist <- limpaTweets(tweetlist)
tweetcorpus <- Corpus(VectorSource(tweetlist))
tweetcorpus <- tm_map(tweetcorpus, removePunctuation)
tweetcorpus <- tm_map(tweetcorpus, content_transformer(tolower))
tweetcorpus <- tm_map(tweetcorpus, function(x)removeWords(x, stopwords()))
termo_por_documento = as.matrix(TermDocumentMatrix(tweetcorpus), control = list(stopwords = c(stopwords("portuguese"))))

Construçao da wordclound e dendogram:

Após as fases de coleta e tratamento dos dados, vizualizaremos as palavras mais associadas a ‘Rick and Morty’. Também construíremos um dendograma. Isso nos auxiliará em nossa análise inicial de associação entre palavras e o tema principal.

pal2 <- brewer.pal(8,"Dark2")
wordcloud(tweetcorpus, 
          min.freq = 2, 
          scale = c(5,1), 
          random.color = F, 
          max.word = 60, 
          random.order = F,
          colors = pal2)

tweettdm <- TermDocumentMatrix(tweetcorpus)
tweettdm
## <<TermDocumentMatrix (terms: 4223, documents: 3838)>>
## Non-/sparse entries: 32316/16175558
## Sparsity           : 100%
## Maximal term length: 57
## Weighting          : term frequency (tf)
findFreqTerms(tweettdm, lowfreq = 11)
##   [1] "consigo"      "falar"        "morty"        "nao"          "que"         
##   [6] "rick"         "vou"          "cara"         "ele"          "faz"         
##  [11] "tbm"          "desenho"      "essa"         "estao"        "mas"         
##  [16] "melhor"       "mundo"        "para"         "voces"        "dnv"         
##  [21] "temporada"    "uma"          "black"        "com"          "isso"        
##  [26] "mirror"       "quarta"       "terminei"     "por"          "assistir"    
##  [31] "kkkkkk"       "muito"        "ruim"         "big"          "esse"        
##  [36] "pessoal"      "quem"         "vejo"         "ver"          "meu"         
##  [41] "minha"        "tempo"        "vida"         "anime"        "favorito"    
##  [46] "bad"          "breaking"     "dead"         "gosto"        "himym"       
##  [51] "nosso"        "parecido"     "pra"          "quao"         "serie"       
##  [56] "vikings"      "walking"      "acabou"       "brasil"       "netflix"     
##  [61] "parte"        "quando"       "segunda"      "vai"          "dias"        
##  [66] "pqp"          "quase"        "caralho"      "eles"         "episodio"    
##  [71] "filme"        "como"         "das"          "incrivel"     "menos"       
##  [76] "pelo"         "final"        "pela"         "temp"         "aqui"        
##  [81] "assiste"      "bem"          "coisa"        "dos"          "qualquer"    
##  [86] "amo"          "pessoas"      "queria"       "alguem"       "cmg"         
##  [91] "foi"          "certo"        "meio"         "nada"         "assistindo"  
##  [96] "ate"          "gente"        "manha"        "esta"         "era"         
## [101] "mesmo"        "viu"          "voce"         "amor"         "star"        
## [106] "fez"          "office"       "sei"          "sempre"       "todos"       
## [111] "tudo"         "dia"          "passar"       "vendo"        "aventura"    
## [116] "hora"         "midnight"     "tipo"         "espero"       "fiz"         
## [121] "foda"         "futuro"       "pro"          "volta"        "existe"      
## [126] "todo"         "mais"         "todas"        "agora"        "mano"        
## [131] "pos"          "sou"          "tem"          "dark"         "sai"         
## [136] "vontade"      "acabei"       "tenho"        "toda"         "noite"       
## [141] "sim"          "falando"      "mal"          "dormir"       "bom"         
## [146] "maluco"       "eps"          "esses"        "assisti"      "treco"       
## [151] "resto"        "family"       "ficar"        "mouth"        "uns"         
## [156] "ela"          "meus"         "tinha"        "pouco"        "sobre"       
## [161] "ultimo"       "chegando"     "duas"         "chega"        "nunca"       
## [166] "merda"        "season"       "assisto"      "outra"        "pedaco"      
## [171] "diz"          "ainda"        "hoje"         "nova"         "preciso"     
## [176] "casa"         "sem"          "tao"          "fala"         "kkkkk"       
## [181] "tanto"        "boa"          "quero"        "vezes"        "acho"        
## [186] "gostar"       "mim"          "altura"       "coisas"       "inuteis"     
## [191] "signo"        "tamanho"      "tatuagens"    "episodios"    "porra"       
## [196] "nem"          "nossa"        "series"       "sherlock"     "vamos"       
## [201] "kkkk"         "semana"       "apenas"       "gosta"        "pessoa"      
## [206] "friends"      "met"          "demais"       "ultima"       "brooklyn"    
## [211] "show"         "entao"        "assistido"    "tivesse"      "ter"         
## [216] "banks"        "elite"        "outer"        "gospel"       "quanto"      
## [221] "rir"          "vez"          "fiquei"       "tava"         "education"   
## [226] "lucifer"      "sex"          "stranger"     "things"       "witcher"     
## [231] "dar"          "humor"        "tambem"       "versao"       "irmao"       
## [236] "novo"         "seja"         "nos"          "estou"        "greys"       
## [241] "house"        "motel"        "naruto"       "aquele"       "quer"        
## [246] "terminar"     "cabeca"       "algum"        "nome"         "qual"        
## [251] "seu"          "inteligente"  "jogo"         "cada"         "bagulho"     
## [256] "comecei"      "odeia"        "amei"         "porque"       "novos"       
## [261] "num"          "sera"         "triste"       "fazer"        "girl"        
## [266] "gossip"       "papel"        "sao"          "pensando"     "bojack"      
## [271] "horseman"     "fico"         "ser"          "msm"          "comigo"      
## [276] "parar"        "chapado"      "cor"          "sabe"         "melhores"    
## [281] "musica"       "fandom"       "cade"         "supernatural" "fui"         
## [286] "vcs"          "transar"      "parece"       "depois"       "mto"         
## [291] "modern"       "bue"          "kkk"          "lembra"       "planeta"     
## [296] "dps"          "dizer"        "criador"      "animacao"     "agr"         
## [301] "ponto"        "comecar"      "maratonar"    "logo"         "namorados"   
## [306] "primeiro"     "pode"         "assim"        "acredito"     "temporadas"  
## [311] "deus"         "umas"         "assistiu"     "quarentena"   "fav"         
## [316] "piercing"     "mae"          "camiseta"     "fas"          "teen"        
## [321] "wolf"         "galera"       "olha"         "scream"       "blinders"    
## [326] "peaky"        "aquela"       "outro"        "maldicao"     "residencia"  
## [331] "fosse"        "animacoes"    "nessa"        "min"          "universo"    
## [336] "fumar"        "concorda"     "respira"      "break"        "prison"      
## [341] "vamo"         "atypical"     "anne"         "got"          "desencanto"  
## [346] "nine"         "estar"        "kkkkkkk"      "mil"          "game"        
## [351] "thrones"      "piada"        "vis"          "ahs"          "moral"       
## [356] "nois"         "twd"          "desenhei"     "estilo"       "mother"      
## [361] "desenhos"     "certeza"      "flash"        "euphoria"     "suits"       
## [366] "grey"         "narcos"       "anatomy"      "control"      "minutos"     
## [371] "good"         "malvado"      "ama"          "skins"        "place"       
## [376] "ouca"         "sexo"         "aguentarias"  "mentir"       "perguntas"   
## [381] "poderia"      "preparar"
findAssocs(tweettdm, 'netflix', 0.60)
## $netflix
## numeric(0)
tweet2tdm <- removeSparseTerms(tweettdm, sparse = 0.9)
tweet2tdmscale <- scale(tweet2tdm)
tweetdist <- dist(tweet2tdmscale, method = "euclidean")
tweetfit <- hclust(tweetdist)
plot(tweetfit)
cutree(tweetfit, k = 4)
##    morty      que     rick    gosto    nosso parecido      pra     quao 
##        1        2        1        3        3        3        3        3 
##    serie desenhei   estilo 
##        3        4        4
rect.hclust(tweetfit, k = 3, border = "red")

Análise de sentimentos:

Aqui construíremos a etapa de análise de sentimentos dos tweets coletados, associando os termos à palavras negativas, positivas e palavras neutras.

sentimento.score = function(sentences, pos.words, neg.words, .progress = 'none')
{
  
  # Criando um array de scores com lapply
  scores = laply(sentences,
                 function(sentence, pos.words, neg.words)
                 {
                   sentence = gsub("[[:punct:]]", "", sentence)
                   sentence = gsub("[[:cntrl:]]", "", sentence)
                   sentence =gsub('\\d+', '', sentence)
                   tryTolower = function(x)
                   {
                     y = NA
                     try_error = tryCatch(tolower(x), error=function(e) e)
                     if (!inherits(try_error, "error"))
                       y = tolower(x)
                     return(y)
                   }
                   
                   sentence = sapply(sentence, tryTolower)
                   word.list = str_split(sentence, "\\s+")
                   words = unlist(word.list)
                   pos.matches = match(words, pos.words)
                   neg.matches = match(words, neg.words)
                   pos.matches = !is.na(pos.matches)
                   neg.matches = !is.na(neg.matches)
                   score = sum(pos.matches) - sum(neg.matches)
                   return(score)
                 }, pos.words, neg.words, .progress = .progress )
  
  scores.df = data.frame(text = sentences, score = scores)
  return(scores.df)
}

Mapeando palavras positivas e negativas e construindo teste de massa de dados:

Nesta etapa, vamos carregar dois arquivos txt contendo nossas palavras positivas e negativas que deverão ser associadas aos sentimentos de nossos tweets coletados. Também criaremos um teste de massa de dados para analisarmos se o modelo está identificando os termos positivos, negativos e neutros adequadamente.

pos = readLines("pos_words.txt")
neg = readLines("neg_words.txt")
teste = c("rick and morty is just awesome", "i dont know what to think about rick and morty",
          "rick and morty is awful")
testesentimento = sentimento.score(teste, pos, neg)
class(testesentimento)
## [1] "data.frame"
testesentimento$score
## [1]  1  0 -1

Interpretando o Score obtido da massa de dados testada:

Utilizando o Naive Bayes para análise de sentimentos:

Aqui vamos utilizar o modelo ‘Naive Bayes’ para analisarmos os sentimentos de uma massa de dados de 10000 tweets, também como tema principal ‘Rick and Morty’.

A vantagem de se utilizar esse modelo é que, aqui não precisamos indicar as palavras positvas ou negativas.

Este modelo também nos permitirá analisar os indicadores de emoções e polaridade.

tweetpt = searchTwitter("Rick and Morty", n = 10000, lang = "pt")
## [1] "Rate limited .... blocking for a minute and retrying up to 119 times ..."
tweetpt = sapply(tweetpt, function(x) x$getText())
# Removendo caracteres especiais
tweetpt = gsub("(RT|via)((?:\\b\\W*@\\w+)+)", "", tweetpt)
# Removendo @
tweetpt = gsub("@\\w+", "", tweetpt)
# Removendo pontuação
tweetpt = gsub("[[:punct:]]", "", tweetpt)
# Removendo digitos
tweetpt = gsub("[[:digit:]]", "", tweetpt)
# Removendo links html
tweetpt = gsub("http\\w+", "", tweetpt)
# Removendo espacos desnecessários
tweetpt = gsub("[ \t]{2,}", "", tweetpt)
tweetpt = gsub("^\\s+|\\s+$", "", tweetpt)
# Criando função para tolower
try.error = function(x)
{
  # Criando missing value
  y = NA
  try_error = tryCatch(tolower(x), error=function(e) e)
  if (!inherits(try_error, "error"))
    y = tolower(x)
  return(y)
}
# Lower case
tweetpt = sapply(tweetpt, try.error)
# Removendo os NAs
tweetpt = tweetpt[!is.na(tweetpt)]
names(tweetpt) = NULL
# Classificando emocao
class_emo = classify_emotion(tweetpt, algorithm = "bayes", prior = 1.0)
emotion = class_emo[,7]
# Substituindo NA's por "Neutro"
emotion[is.na(emotion)] = "Neutro"
# Classificando polaridade
class_pol = classify_polarity(tweetpt, algorithm = "bayes")
polarity = class_pol[,4]
# Gerando um dataframe com o resultado
sent_df = data.frame(text = tweetpt, emotion = emotion,
                     polarity = polarity, stringsAsFactors = FALSE)
sent_df = within(sent_df,
                 emotion <- factor(emotion, levels = names(sort(table(emotion), 
                                                                decreasing=TRUE))))


# Emoções encontradas
ggplot(sent_df, aes(x = emotion)) +
  geom_bar(aes(y = ..count.., fill = emotion)) +
  scale_fill_brewer(palette = "Dark2") +
  labs(x = "Categorias", y = "Numero de Tweets") 

# Polaridade
ggplot(sent_df, aes(x=polarity)) +
  geom_bar(aes(y=..count.., fill=polarity)) +
  scale_fill_brewer(palette="RdGy") +
  labs(x = "Categorias de Sentimento", y = "Numero de Tweets")

Referências:

https://www.datascienceacademy.com.br/