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
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" ...
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" ...
#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
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" ...
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
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
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
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
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
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
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
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
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.