0.1 Qual o propósito?

Neste artigo vamos explorar a linguagem R para minerar dados, classifica-los, vamos fazer ranking de palavras, nuvens, e apresentaremos o resultado de uma analise de sentimente tweets de 3 grandes empresas: Google e Microsoft e Amazon. ## Incluindo as bibliotecas nescessárias

library(twitteR)
library(tidyverse)
library(data.table)
library(tidytext)
library(glue)
library(stringr)
library(stringi)
library(rvest)
library(readr)
library(ptstem)
library(wordcloud2)

setando o diretório padrão

0.2 Carregando as stopwords

Achei uma fonte na internet onde continha várias stopwords

stopwordsPage <- read_html("https://www.ranks.nl/stopwords/brazilian", enconding="UTF-8")
stopwordsList <- html_nodes(stopwordsPage,'td') 
#limpando o html, trocando br por tags

xml_find_all(stopwordsList, “.//br”) %>% xml_add_sibling(“p”, “”)
xml_find_all(stopwordsList, “.//br”) %>% xml_remove()

swstr <- html_text(stopwordsList)
#humm temos que tratar os dados para transformar em um dicionário
sw <- unlist(str_split(swstr,'\\n')) 
glimpse(sw)
##  chr [1:128] "a" "ainda" "alem" "ambas" "ambos" "antes" "ao" "aonde" ...

0.2.1 Recuperando stopwords em portugues do TM

Uma biblioteca de mineração de dados muito usada para R é a TM, é uma maneira muito facil de trabalhar com mineração pois a ferramenta possui muitas funções para facilitar nossa vida, portanto não gostei de trabalhar com ela, pois sou iniciante e não posso depender de uma biblioteca que faça tudo então vou apenas usar o dicionário stopwords dela para fazer um merge com meu dicionário.

library(tm)
## Loading required package: NLP
## 
## Attaching package: 'NLP'
## The following object is masked from 'package:ggplot2':
## 
##     annotate
#carregando o stopwords da tm
swList2 <- stopwords('portuguese')
glimpse(swList2)
##  chr [1:203] "de" "a" "o" "que" "e" "do" "da" "em" "um" "para" "com" ...

0.2.2 Fazendo um merge nos dados

#fazendo um merge, usando a biblioteca tidyverse que é extremamente util fazemos o merge
str(sw)
##  chr [1:128] "a" "ainda" "alem" "ambas" "ambos" "antes" "ao" "aonde" ...
sw_merged <- union(sw,swList2) 
summary(sw_merged)
##    Length     Class      Mode 
##       264 character character

Vamos dar uma verificada se deu tudo certo, se não existem elementos repetidos na tabela para isso uso o objeto tibble do dplyr

tibble(word = sw_merged) %>% 
  group_by(word) %>% 
  filter(n()>1)
## # A tibble: 0 x 1
## # Groups:   word [0]
## # ... with 1 variables: word <chr>

Perfeito! nenhum termo repetido

0.3 Carregando os termos de polaridade de análise de Sentimentos em uma tabela geral

Esse foi um trabalho demorado, fiz o mesmo processo que fiz acima com os arquivos baixados da linguateca : http://www.linguateca.pt/Repositorio/ReLi/ Salvei cada arquivo particularmente e classifiquei-os mantendo informações que talvez um dia possam ser nescessárias. Vou demonstrar o processo aqui:

an <- read.csv("adjetivos_negativos.txt", header = F, sep = "\t", strip.white = F,
                     stringsAsFactors = F, encoding="UTF-8")
exn <- read.csv("expressoes_negativas.txt", header = F, sep = "\t", strip.white = F,
              stringsAsFactors = F, encoding="UTF-8")

vn <- read.csv("verbos_negativos.txt", header = F, sep = "\t", strip.white = F, 
               stringsAsFactors = F, encoding="UTF-8")

subn <- read.csv("substantivos_negativos.txt", header = F, sep = "\t", strip.white = F, 
                 stringsAsFactors = F, encoding="UTF-8")

ap <- read.csv("adjetivos_positivos.txt", header = F, sep = "\t", strip.white = F, 
               stringsAsFactors = F, encoding="UTF-8")

exp <- read.csv("expressoes_positivas.txt", header = F, sep = "\t", strip.white = F, 
               stringsAsFactors = F, encoding="UTF-8")

vp <- read.csv("verbos_positivos.txt", header = F, sep = "\t", strip.white = F, 
         stringsAsFactors = F, encoding="UTF-8")

sp <- read.csv("substantivos_positivos.txt", header = F, sep = "\t", strip.white = F, 
         stringsAsFactors = F, encoding="UTF-8")
str(an);str(exn)
## 'data.frame':    110 obs. of  1 variable:
##  $ V1: chr  "aborrecente" "anacrônico" "besta" "bizarro" ...
## 'data.frame':    29 obs. of  1 variable:
##  $ V1: chr  "água com açúcar" "água-com-açúcar" "cabeça-dura" "carregar nas tintas" ...

0.4 Carregando o lexico de sentimentos disponível no Kaggle

Hoje descobri o kaggle, que eles disponibilizaram um lexicon de sentimentos, abaixei os negativos e positivos e vamos adiciona-los tb ao nosso estudo

#from kaggle sentiment words https://www.kaggle.com/rtatman/sentiment-lexicons-for-81-languages/data
poskaggle <- read.csv("positive_words_pt.txt", header = F, sep = "\t", strip.white = F, 
               stringsAsFactors = F, encoding="UTF-8")

negkaggle <- read.csv("negative_words_pt.txt", header = F, sep = "\t", strip.white = F, 
                      stringsAsFactors = F, encoding="UTF-8")
#verificando se está tudo certo
head(negkaggle)
## # A tibble: 6 x 1
##          V1
## *     <chr>
## 1        de
## 2       sem
## 3     tempo
## 4    contra
## 5    partir
## 6 discussão

Tudo certo! Vamos agora criar uma table e popula-la com nossos dados, talvez tenha uma maneira mais rápida de se fazer isso mas como sou iniciante vai ser assim mesmo, vou inserir arquivo por arquivo classificando-os positivo negativos, primeiro vamos com os arquivos da linguateca:

##decidi salvar esses termos em uma tabela e classificalos como tipo e polaridade que sabe depois eu grave um score
##para o peso da polaridade
##Criando um dataframe que salvarei os termos começando pelos adjetivos negativos
dfPolaridades <- an %>% 
                  mutate(word = V1, polaridade = -1, tipo='adjetivo', sentimento='negativo') %>%
                  select(word,polaridade,tipo,sentimento) %>%
                  arrange(word)
head(dfPolaridades,2)
## # A tibble: 2 x 4
##          word polaridade     tipo sentimento
## *       <chr>      <dbl>    <chr>      <chr>
## 1 aborrecente         -1 adjetivo   negativo
## 2  anacrônico         -1 adjetivo   negativo

Tudo certo então prosseguimos:

##aqui faço um count para poder adicionar os dados corretamente
icount <-  length(exn$V1)
dfPolaridades <- bind_rows(dfPolaridades,list(word = exn$V1, polaridade=rep(-1,icount),tipo=rep('expressao',icount),sentimento=rep('negativo',icount)))
dfPolaridades %>% arrange(desc(word)) %>% head(3)
## # A tibble: 3 x 4
##       word polaridade     tipo sentimento
## *    <chr>      <dbl>    <chr>      <chr>
## 1  volúvel         -1 adjetivo   negativo
## 2 violento         -1 adjetivo   negativo
## 3    vazio         -1 adjetivo   negativo
icount <-  length(vn$V1)
dfPolaridades <- bind_rows(dfPolaridades,list(word = vn$V1, polaridade=rep(-1,icount),tipo=rep('verbo',icount),sentimento=rep('negativo',icount)))

icount <-  length(subn$V1)
dfPolaridades <- bind_rows(dfPolaridades,list(word = subn$V1, polaridade=rep(-1,icount),tipo=rep('substantivo',icount),sentimento=rep('negativo',icount)))

icount <-  length(negkaggle$V1)
dfPolaridades <- bind_rows(dfPolaridades,list(word = negkaggle$V1, polaridade=rep(-1,icount),tipo=rep('noclass',icount),sentimento=rep('negativo',icount)))

icount <-  length(ap$V1)
dfPolaridades <- bind_rows(dfPolaridades,list(word = ap$V1, polaridade=rep(1,icount),tipo=rep('adjetivo',icount),sentimento=rep('positivo',icount)))

icount <-  length(exp$V1)
dfPolaridades <- bind_rows(dfPolaridades,list(word = exp$V1, polaridade=rep(1,icount),tipo=rep('expressao',icount),sentimento=rep('positivo',icount)))

icount <-  length(vp$V1)
dfPolaridades <- bind_rows(dfPolaridades,list(word = vp$V1, polaridade=rep(1,icount),tipo=rep('verbo',icount),sentimento=rep('positivo',icount)))

icount <-  length(sp$V1)
dfPolaridades <- bind_rows(dfPolaridades,list(word = sp$V1, polaridade=rep(1,icount),tipo=rep('substantivo',icount),sentimento=rep('positivo',icount)))

icount <-  length(poskaggle$V1)
dfPolaridades <- bind_rows(dfPolaridades,list(word = poskaggle$V1, polaridade=rep(1,icount),tipo=rep('noclass',icount),sentimento=rep('positivo',icount)))
#visualizando como está nosso dataframe
dfPolaridades %>% group_by(word) %>% filter(n() == 1) %>% summarize(n=n())
## # A tibble: 3,930 x 2
##           word     n
##          <chr> <int>
##  1  abandonado     1
##  2    abandono     1
##  3      abater     1
##  4     abatido     1
##  5    abatidos     1
##  6    abençoar     1
##  7   aberração     1
##  8 abertamente     1
##  9    abertura     1
## 10      abismo     1
## # ... with 3,920 more rows

Ops!! temos termos repetidos!, facilmente resolvido com unique

dfPolaridades %>% count()
## # A tibble: 1 x 1
##       n
##   <int>
## 1  4562

0.5 Removendo termos de sentimentos repetidos

dfPolaridadesUnique <- dfPolaridades[!duplicated(dfPolaridades$word),]
dfPolaridadesUnique %>% count()
## # A tibble: 1 x 1
##       n
##   <int>
## 1  4242

lexicon de sentimentos preparado, vamos agora importar os tweets ## Conectando, autorizando e recuperando tweets

twitter_tag <- "#microsoft|#google|#amazon"
# autorizando
setup_twitter_oauth(consumer_key, consumer_secret, access_token, access_secret);
## [1] "Using direct authentication"
tweets <- searchTwitter(twitter_tag, lang = 'pt', resultType="mixed", n=3000);
## Warning in doRppAPICall("search/tweets", n, params = params,
## retryOnRateLimit = retryOnRateLimit, : 3000 tweets were requested but the
## API can only return 1287
tweetxt <- sapply(tweets, function(x) x$getText())
tibble(tweetxt)
## # A tibble: 1,287 x 1
##                                                             tweetxt
##                                                               <chr>
##  1 #Google lança a versão 8.1 do #Android - https://t.co/tWGo8YwJsP
##  2  Massagem Masculina Centro RJ no #Google https://t.co/zelq5zMJzz
##  3  Massagem Masculina Centro RJ no #Google https://t.co/UI8OdxnzPR
##  4  Massagem Masculina Centro RJ no #Google https://t.co/q4tE0XomMQ
##  5  Massagem Masculina Centro RJ no #Google https://t.co/dWsLeddRsc
##  6  Massagem Masculina Centro RJ no #Google https://t.co/W74kVSl2ee
##  7  Massagem Masculina Centro RJ no #Google https://t.co/9QYGuSohdv
##  8  Massagem Masculina Centro RJ no #Google https://t.co/js5Mk0CMVL
##  9  Massagem Masculina Centro RJ no #Google https://t.co/4AknPbSbdp
## 10  Massagem Masculina Centro RJ no #Google https://t.co/2RBRDgKMDK
## # ... with 1,277 more rows

0.6 Processo de limpeza

Vamos fazer o processo de limpeza agora

removeURL <- function(x) gsub("http[^[:space:]]*", "", x)
tweetxtUtf <-  readr::parse_character(tweetxt, locale = readr::locale('pt')) # sapply(tweetxt, function(x) iconv(x, "UTF-8"))
tweetxtUtf <- sapply(tweetxtUtf, function(x) stri_trans_tolower(x,'pt'))
tweetxtUtf <- gsub("(RT|via)((?:\\b\\W*@\\w+)+)", " ",  tweetxtUtf);
tweetxtUtf <- str_replace(tweetxtUtf,"RT @[a-z,A-Z]*: ","")
tweetxtUtf <- gsub("@\\w+", "", tweetxtUtf)
tweetxtUtf <- removeURL(tweetxtUtf)
tweetxtUtf <- str_replace_all(tweetxtUtf,"@[a-z,A-Z]*","")  
tweetxtUtf <- gsub("[^[:alnum:][:blank:]!?]", " ", tweetxtUtf)
tweetxtUtf <- gsub("[[:digit:]]", "", tweetxtUtf)

tibble(tweetxtUtf)
## # A tibble: 1,287 x 1
##                                  tweetxtUtf
##                                       <chr>
##  1   google lança a versão   do  android   
##  2 massagem masculina centro rj no  google 
##  3 massagem masculina centro rj no  google 
##  4 massagem masculina centro rj no  google 
##  5 massagem masculina centro rj no  google 
##  6 massagem masculina centro rj no  google 
##  7 massagem masculina centro rj no  google 
##  8 massagem masculina centro rj no  google 
##  9 massagem masculina centro rj no  google 
## 10 massagem masculina centro rj no  google 
## # ... with 1,277 more rows

0.6.1 Removendo tweets repetidos

O Resultado parece bem satisfatório agora, conseguimos limpar todo o documento deixando bem limpo, agora vou começar o processo de remoção das palavras “stopwords” a definição é que são palavras que podem ser consideradas irrelevantes para o conjunto de resultados a ser exibido em uma busca realizada em uma search engine. Antes vamos remover tweets repetidos

#não esquecer de remover os tweets repetidos vamos ver
length(tweetxtUtf)
## [1] 1287
tibble(tweetxtUtf) %>% unique() %>% count()
## # A tibble: 1 x 1
##       n
##   <int>
## 1   858
tweetxtUtfUnique <- tweetxtUtf %>% unique() 
length(tweetxtUtfUnique)
## [1] 858

0.6.2 Remoção das stopwords

tweetxtUtfUniqueSw <- tm::removeWords(tweetxtUtfUnique,c(sw_merged,'rt'))
tibble(tweetxtUtfUniqueSw)
## # A tibble: 858 x 1
##                                                             tweetxtUtfUniqueSw
##                                                                          <chr>
##  1                                         google lança  versão     android   
##  2                                      massagem masculina centro rj   google 
##  3  trade compliance import export adviser  amsterdam   microsoft  sap  perman
##  4                             consejos   seo   ser penalizado   google  gt   
##  5    esqueça  vc pode tirar  dúvidas c   palestrantes  estande   microsoft   
##  6  motivos pra considerar seriamente  migração    cloud      virtualização  v
##  7                                                           sjcam on  google 
##  8                amazon  bestsellers    reconcíliate con  masculino          
##  9    poncho cape alpaca fashion  fashion  cybermonday  amazon  giftsforher   
## 10                  instalar   amazon appstore   android   amazonappstore  apk
## # ... with 848 more rows

0.7 Nuvem de palavras

0.7.1 tokenizando para observar as frequencias

ttokens <- data_frame(word= tweetxtUtfUniqueSw) %>% unnest_tokens(word,word)
ttokens %>% count(word, sort = T) 
## # A tibble: 3,112 x 2
##           word     n
##          <chr> <int>
##  1      amazon   361
##  2      google   357
##  3   microsoft   116
##  4      oferta    70
##  5 blackfriday    63
##  6           é    55
##  7           r    54
##  8      apenas    53
##  9     android    39
## 10          gt    26
## # ... with 3,102 more rows

Impressionante, aqui no brasil o amazon está na ponta pelo menos na nossa pesquisa… Notou que temos palavras que não significam muito em uma nuvem de palavras como ‘é’,‘r’

ttokens_filter <- ttokens %>% filter(nchar(word) > 3)
ttokens_filter %>% count(word, sort=T)
## # A tibble: 2,804 x 2
##           word     n
##          <chr> <int>
##  1      amazon   361
##  2      google   357
##  3   microsoft   116
##  4      oferta    70
##  5 blackfriday    63
##  6      apenas    53
##  7     android    39
##  8        aqui    23
##  9       paulo    22
## 10       livro    21
## # ... with 2,794 more rows

0.8 Wordclouds

ttokens_freq <- ttokens_filter %>% count(word, sort = T) %>% select(word, freq=n) 
ttokens_freq
## # A tibble: 2,804 x 2
##           word  freq
##          <chr> <int>
##  1      amazon   361
##  2      google   357
##  3   microsoft   116
##  4      oferta    70
##  5 blackfriday    63
##  6      apenas    53
##  7     android    39
##  8        aqui    23
##  9       paulo    22
## 10       livro    21
## # ... with 2,794 more rows
wordcloud2(ttokens_freq , minSize = 2, size = 1, backgroundColor = 'black')
## Warning in if (class(data) == "table") {: a condição tem comprimento > 1 e
## somente o primeiro elemento será usado

A diferença de frequencia do amazon e google como já haviamos vistos nas tabelas é absurda, tão grande que mal podemos visualizar outras palavras vamos omiti-los por enquanto somente para poder vizualiza-las e vou mostrar mais uma opção de visualização antes, o wordcloud 1

0.8.1 Wordcloud 1 opção de visualização

pal2 <- brewer.pal(8,"Dark2")
wordcloud(words =  ttokens_freq$word, freq = ttokens_freq$freq , min.freq = 8,  random.color = T, max.word = 200, random.order = T, colors = pal2)

### Agora sem o google e microsoft e amazon:

wordcloud2(ttokens_freq %>% filter(freq<100) , minSize = 6, size = 1, backgroundColor = 'black')
## Warning in if (class(data) == "table") {: a condição tem comprimento > 1 e
## somente o primeiro elemento será usado

0.8.2 E vou agora salvar tudo que foi feito para podermos continuar com os mesmos dados na segunda parte do artigo, com novos graficos e finalmente a analise sentimental…

0.9 Salvando Datasets:

write_csv(tibble(word = sw_merged),path = ‘parte2/stopwords.csv’)
write_csv(as.tibble(dfPolaridadesUnique),path = ‘parte2/polaridades_pt.csv’)
write_csv(tibble(tweet = tweetxt),path = ‘parte2/tweetxt.csv’)
write_csv(tibble(tweet = tweetxtUtfUniqueSw),path=‘parte2/tweets_limpo.csv’)
write_csv(ttokens_freq, path=‘parte2/tokens.csv’)

Continua na segunda parte…

Regards

Giuliano Lemes Pereira

Cientista de Dados
Microsoft Profile

Meu site Call: +55 (21)999878849
By Giuliano Lemes.