Skip to content

Instantly share code, notes, and snippets.

@gomesfellipe
Created February 20, 2018 04:00
Show Gist options
  • Save gomesfellipe/4d6cd8754ba63fd1ed2162201c3ae020 to your computer and use it in GitHub Desktop.
Save gomesfellipe/4d6cd8754ba63fd1ed2162201c3ae020 to your computer and use it in GitHub Desktop.
Função para criar nuvem de palavras com funcoes de mineracao de texto de forma simples
# Como utilizar a funcao wordcloud_sentiment():
#----------------.-------------------------------------------------------------------------------------------------.----------------------
# Argumento | Definicao | Default
#----------------|------------------------------------------------------------------------------------------------------------------------
# x | Coluna com textos, ou url (ver type) |
#----------------|------------------------------------------------------------------------------------------------------------------------
# type | Tipo de arquivo para produzir a nuvem |
# |- url: Endereço da qual deseja-se fazer a nuvem |
# |- text: Coluna com textos (cada linha representa um comentario) |
#----------------|------------------------------------------------------------------------------------------------------------------------
# lang | Lingua utilizada na nuvem (para remover as stopwords em portugues) | Default="portuguese"
#----------------|------------------------------------------------------------------------------------------------------------------------
# excludeWords | Vetor de stopwords adicionais para serem removidas | Default=NULL ou seja, sem palavras adicionais por padrao
#----------------|------------------------------------------------------------------------------------------------------------------------
# freq | Palavras com frequência abaixo de freq não serão plotadas | Default=15
#----------------|------------------------------------------------------------------------------------------------------------------------
# tf_idf | Realiza a transformação tf.idf | Default=F
#----------------|------------------------------------------------------------------------------------------------------------------------
# max | Número máximo de palavras a serem plotadas. Menos termos freqüentes caíram | Default=200
#----------------|------------------------------------------------------------------------------------------------------------------------
# print | Controla se a nuvem de palavras sera exibida: | Default = TRUE
# |- TRUE para plotar a nuvem de palavras, |
# |- FALSE para obter apenas a tabela de frequencias |
#----------------|------------------------------------------------------------------------------------------------------------------------
# ngrams | Controla a frequencia sequencias de n palavras : | Default = 1
# |- 2: sequencias de duas palavras |
# |- 3: sequencias de tres palavras |
# |- n: sequencias de n palavras |
#----------------|------------------------------------------------------------------------------------------------------------------------
# sentiment | Controla se sera utilizada a analise de sentimentos | Default = FALSE
# |- TRUE para , pintar de azul (positivo), vermelho (negativo) ou cinza (neutro) |
# |- FALSE para obter nuvem com cor default |
#----------------|------------------------------------------------------------------------------------------------------------------------
# Funcao retorna | Além da nuvem de palavras, retorna: |
# |- $freq = tabela de frequencia em ordem decrescente |
#----------------'-------------------------------------------------------------------------------------------------'----------------------
# Funcao wordcloud_sentiment() --------------------------------------------
wordcloud_sentiment = function(x, # Uma coluna de textos
type=c("text", "url"), # Tipo de entrada dos dados
lang = "portuguese", # Idioma dos dados
excludeWords=NULL, # Palavras que sera excluidas
freq=15, # Frequencia minima da palavra para aparecer na nuvem
tf_idf=F, # Transformacao tf-idf
max= 200, # Numero maximo de palavras na nuvem
print=T, # Define se a nuvem sera exibida
ngrams=1, # Ordem da sequencia de palavras
sentiment=F, # Se a analise de sentimentos sera realizada
horizontal=0.35, # Porcentagem de palavras na horizontal (De zero a um)
textStemming=F){ # Define se os sufixos serao removidos
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#+ Importanto pacotes utilizados no aplicativo +
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
library(flexdashboard) #Pacote responsavel por gerar o aplicativo como dashboard
library(stringr) #Pacote para manipulação de strings
library(dplyr) #Pacote para manipulação de dados
library(tm) #Pacote de para text mining
library(wordcloud) #Pacote para nuvem de palavras
library(readxl) #Pacote para leitura de dados excel
library(tidytext) #Manipulação de textos
library(reshape2) #Manipulação de dados
library(lexiconPT) #Importar palavras de sentimentos
library(memoise) #Cache resultados de uma função
library(SnowballC) #Para steamming
library(purrr) #Ferramentas de programação funcional
library(DT) #Renderizar tabela da segunda pagina
library(ngram) #Busca por sequencias de palavras
#+--------------------------------------+---------------------------------------------+
#Pacotes desativados: (Nessa nova versao o RWeka nao sera mais utilizado)
# library(rJava)
# library(RWeka)
#-----------------------------------------------------------------------------------------------
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#+ Importando fontes nativas do windows para as letras +
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
windowsFonts(
A=windowsFont("Arial Black"),
B=windowsFont("Bookman Old Style"),
C=windowsFont("Comic Sans MS"),
D=windowsFont("Symbol")
)
#-----------------------------------------------------------------------------------------------
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#+ Download e analise de webpage +
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
html_to_text<-function(url){
library(RCurl)
library(XML)
# download html
html.doc <- getURL(url)
#convert to plain text
doc = htmlParse(html.doc, asText=TRUE)
# "//text()" returns all text outside of HTML tags.
# We also don’t want text such as style and script codes
text <- xpathSApply(doc, "//text()[not(ancestor::script)][not(ancestor::style)][not(ancestor::noscript)][not(ancestor::form)]", xmlValue)
# Format text vector into one character string
return(paste(text, collapse = " "))
}
#Fonte: http://www.sthda.com/english/wiki/print.php?id=159
#-----------------------------------------------------------------------------------------------
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#+ Captação de erros de codificacao: +
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
catch.error = function(x){
y = NA # Cria um vetor com valor faltante para teste
catch_error = tryCatch(tolower(x), error=function(e) e) # Tente pegar esse erro (NA) que acabamos de criar
if (!inherits(catch_error, "error")) # Se não for um erro
y = tolower(x) # verificar resultado se houver erro, caso contrário, a função funciona normalmente
return(y)
}
#Fonte: https://sites.google.com/site/miningtwitter/questions/talking-about/given-topic
#-----------------------------------------------------------------------------------------------
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#+ Limpeza de caracteres especiais +
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
cleanTweets<- function(tweet){
# Limpe o tweet para análise de sentimentos
tweet = gsub("(f|ht)(tp)(s?)(://)(.*)[.|/](.*)", " ", tweet) # Remove html links
tweet = gsub("(RT|via)((?:\\b\\W*@\\w+)+)", " ", tweet) # Remove retweet
tweet = gsub("#\\w+", " ", tweet) # Remove todos "#Hashtag"
tweet = gsub("@\\w+", " ", tweet) # Remove todos "@people"
tweet = gsub("[[:punct:]]", " ", tweet) # Remove todas as pontuacoes
tweet = gsub("[[:digit:]]", " ", tweet) # Remover numeros, precisamos apenas de texto para análise
tweet = gsub("[ \t]{2,}", " ", tweet) # Remove espaços desnecessarios
tweet = gsub("^\\s+|\\s+$", "", tweet) # (espacos em branco, tabs etc)
tweet = gsub('https://','',tweet) # Remove https://
tweet = gsub('http://','',tweet) # Remove http://
tweet = gsub('[^[:graph:]]', ' ',tweet) # Remove strings gráficos como emoticons
tweet = gsub('[[:punct:]]', '', tweet) # Remove pontuacao
tweet = gsub('[[:cntrl:]]', '', tweet) # Remove strings de controle
tweet = gsub('\\d+', '', tweet) # Remove numeros
tweet=str_replace_all(tweet,"[^[:graph:]]", " ") # Remove strings gráficos como emoticons
#tweet=SnowballC::wordStem(tweet,language = lang) # Aplica steamming (desativado)
#Converte tudo para minusculo
tweet = catch.error(tweet) # Aplica a funcao catch.error
return(tweet)
}
#Referencia: https://sites.google.com/site/miningtwitter/questions/talking-about/wordclouds/comparison-cloud
#-----------------------------------------------------------------------------------------------
#Leitura da coluna de texto informada:
if(type[1]=="url"){ text <- html_to_text(x)}
else{ if(type[1]=="text") text <- x}
text=as.data.frame(text)
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# Funcao para remover acentuacao +
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
rm_accent <- function(str,pattern="all") {
# Rotinas e funções úteis V 1.0
# rm.accent - REMOVE ACENTOS DE PALAVRAS
# Função que tira todos os acentos e pontuações de um vetor de strings.
# Parâmetros:
# str - vetor de strings que terão seus acentos retirados.
# patterns - vetor de strings com um ou mais elementos indicando quais acentos deverão ser retirados.
# Para indicar quais acentos deverão ser retirados, um vetor com os símbolos deverão ser passados.
# Exemplo: pattern = c("´", "^") retirará os acentos agudos e circunflexos apenas.
# Outras palavras aceitas: "all" (retira todos os acentos, que são "´", "`", "^", "~", "¨", "ç")
if(!is.character(str))
str <- as.character(str)
pattern <- unique(pattern)
if(any(pattern=="Ç"))
pattern[pattern=="Ç"] <- "ç"
symbols <- c(
acute = "áéíóúÁÉÍÓÚýÝ",
grave = "àèìòùÀÈÌÒÙ",
circunflex = "âêîôûÂÊÎÔÛ",
tilde = "ãõÃÕñÑ",
umlaut = "äëïöüÄËÏÖÜÿ",
cedil = "çÇ"
)
nudeSymbols <- c(
acute = "aeiouAEIOUyY",
grave = "aeiouAEIOU",
circunflex = "aeiouAEIOU",
tilde = "aoAOnN",
umlaut = "aeiouAEIOUy",
cedil = "cC"
)
accentTypes <- c("´","`","^","~","¨","ç")
if(any(c("all","al","a","todos","t","to","tod","todo")%in%pattern)) # opcao retirar todos
return(chartr(paste(symbols, collapse=""), paste(nudeSymbols, collapse=""), str))
for(i in which(accentTypes%in%pattern))
str <- chartr(symbols[i],nudeSymbols[i], str)
return(str)
}
#Fonte: https://pt.stackoverflow.com/questions/46473/remover-acentos
#-----------------------------------------------------------------------------------------------
text=apply(text,1,rm_accent) #Aplica no objeto "text, por linha, a funcao definida acima
#Criando corpus:
text=data.frame(doc_id=1:length(text), #Criando o data.frame para criar o corpus
text=text)
myCorpus = Corpus(DataframeSource(as.data.frame(text))) #Criando o corpus a partir de um data.frame
# if(textStemming) myCorpus <- Corpus(VectorSource(text)) #Para stemming (Com atualizacao do pacote tm nao eh mais necessario)
#Tratamento do corpus:
myCorpus=myCorpus%>%
tm_map(content_transformer(tolower))%>% # Converte para minusculo
tm_map(removeNumbers)%>% # Remove numeros do corpus
tm_map(removeWords, stopwords(lang))%>% # Remove stopwords do dicionario portgues
tm_map(removePunctuation)%>% # Remove pontuacao
tm_map(stripWhitespace)%>% # Remove excessos de espacos em branco
tm_map(removeWords, excludeWords) # Exclue palavras adicionais
#Realiza o steamming se textStemming==V
if(textStemming) myCorpus <- tm_map(myCorpus, stemDocument,language=lang)
#Agora sera feita a procura por sequencias de palavras se ngrams for diferente de 1:
#Abordagem com RWeka (Off)--------------------------------------------------------------------
# Abordagem antiga utilizava o pacote RWeka, esse pacote nao sera mais utilizado
# Tokenizer <- function(x) NGramTokenizer(x, Weka_control(min = ngrams, max = ngrams))
# myDTM = TermDocumentMatrix(myCorpus,control = list(tokenize = Tokenizer))
#---------------------------------------------------------------------------------------------
# Se ngrams for difernte de 1:
if(ngrams!=1){
temp=ngram::ngram(ngram::concatenate(myCorpus),ngrams) # Objeto temporario recebe objeto que guarda sequencias
temp=get.phrasetable(temp) # Obtendo tabela de sequencias do objeto acima
temp$ngrams=temp$ngrams%>% # Limpeza das sequencias obtidas:
str_replace_all(pattern = "^([A-Za-z] [A-Za-z])+","")%>% # Remover sequencias de apenas 1 letras
str_replace_all(pattern = "[:punct:]","")%>% # Remover caracteres especiais
str_replace_all(pattern = "\n","")%>% # Remover o marcador de "nova linha"
str_trim() # Remover espaços em branco sobrando
#Apos a limpeza..
temp=temp[temp$ngrams!="",] # Selecionando apenas as linhas que contenham informacao
temp=temp%>% # Novamente manipulando o objeto que contem a tabela de sequencias
group_by(ngrams) %>% # Agrupando por "ngrams" (sequencias obtidas)
summarise(freq=sum(freq))%>% # Resumir as linhas repetidas pela soma das frequencias
arrange(desc(freq))%>% # Organizando da maior para a menos frequencia
as.matrix() # Alterando o tipo de objeto para matrix
rownames(temp)=str_c(temp[,1]) # O nome das linhas passa a ser a sequencia correspondente
v = sort(temp[,2],decreasing = T) # Retorna um objeto com as frequencias em ordem decrescente e linhas nomeadas
d <- data.frame(words = names(v),freq=v)
# Caso contrario, se ngrams for igual a 1 (sem sequencias)
}else{
myDTM = TermDocumentMatrix(myCorpus, # Obtendo matriz de termos:
control = list(minWordLength = 1)) # Com no minimo 1 ocorrencia
#Se se ngrams for igual a 1 (sem sequencias) e tf.idf for verdadeiro:
if(tf_idf==T){
myDTM=weightTfIdf(myDTM,normalize=T) # Realiza transformacao tf-idf
}
m = as.matrix(myDTM) # Transforma o objeto em matrix
v = sort(rowSums(m),decreasing=TRUE) # Retorna um objeto com as frequencias em ordem decrescente e linhas nomeadas
d <- data.frame(words = names(v),freq=v)
}
if(print==T){ # Se a imagem vai ser exibida
# Analise de sentimentos
if(sentiment){ # Se a opcao de sentimentos estiver selecionada
sentiLex_lem_PT02 <- lexiconPT::sentiLex_lem_PT02 # Obtem o dicionario lexico em Portugues
dicionary=data.frame(cbind(sentiLex_lem_PT02$term, # Seleciona as palavras e
sentiLex_lem_PT02$polarity)) # a polaridade de cada uma
matriz=d # Cria uma base temporaria para o join
names(dicionary)=c("words", "sentiment") # Alterando os nomes das colunas do dicionario e da base obtida
names(matriz)=c("words", "freq") # (O nome deve ser o mesmo para o join)
dicionary$words=as.character(dicionary$words) # Transformando as duas
matriz$words=as.character(matriz$words) # bases obtidas em strings
if(textStemming){ # Se textStemming estiver ligada
dicionary$words <- wordStem(dicionary$words, # Steamming o dicionario lexico tambem
language = lang) # De acordo com a lingua portuguesa
}
dicionary=dicionary[ dicionary$sentiment==1 | dicionary$sentiment==0 | dicionary$sentiment==-1, ] # Obtendo so os polos -1, 1 e 0
dicionary$sentiment=as.factor(dicionary$sentiment) # Transformando em fator
#Alterando o nome dos fatores para o respectivo sentimento:
levels(dicionary$sentiment)[levels(dicionary$sentiment)==-1]=c("Negativo")
levels(dicionary$sentiment)[levels(dicionary$sentiment)==0]=c("Neutro")
levels(dicionary$sentiment)[levels(dicionary$sentiment)==1]=c("Positivo")
#Join das strings do documento com as strings nativas do pacote LexiconPT
sentimentos=data.frame(matriz) %>% # O objeto "sentimentos" recebe
left_join(data.frame(dicionary),by="words") %>% # left_join mantem todas as linhas da base matriz (esquerda) e busca por pares com o dicionario
select(words,sentiment,freq)%>% # Seleciona as colunas das strings, o sentimento e sua frequencia
distinct(words,.keep_all = T) # Seleciona apenas linhas distintas
rownames(d)=d$words # Nomeia as linhas do data.frame d
sentimentos$sentiment[is.na(sentimentos$sentiment)]="Neutro" # Neutro para palavras fora do dicionario
#Criando coluna de cores para cada sentimento
sentimentos$col=c( # Criando uma nova coluna para objeto "sentimentos"
ifelse(sentimentos$sentiment=="Neutro","#666666", # Se for "Neutro" sera cinza, se nao:
ifelse(sentimentos$sentiment=="Positivo","blue","red"))) #Se for "Positivo" recebe azul, se nao é vermelho
#dev.new(width=10, height=10)
par(bg="#f2f2f2") # Define a cor do fundo da figura
set.seed(1234) # Semente para gerar a mesma nuvem
wordcloud(names(v), freq=as.numeric(v), # Funcao repetitiva wordcloud, objeto v foi retornado pela funcao global getTermMatrix()
scale=c(4,0.5), # Um vetor de comprimento 2 que indica o range do tamanho das palavras
min.freq = freq, # Input informado pelo usuario
max.words=max, # Input informado pelo usuario
colors=sentimentos$col, # Input informado pelo usuario
random.order=FALSE, # Plot das palavras em ordem aleatória. Se falso, eles serão plotados em frequência decrescente
rot.per=(1-horizontal), # Proporção de palavras com rotação de 90 graus
use.r.layout=FALSE, # Se falso, o código c ++ é usado para detecção de colisão, caso contrário, R é usado
family = "C", # Seleciona a fonte informada em windowsFonts no inicio do documento
font = 2) # 1:default, 2:negrito, 3:italico, 4:negrito+italico
return(freq = d) # Retorna os termos detectados ordenados de acordo com a sua frequencia
#Se a analise de sentimentos nao for selecionada
}else{
#dev.new(width=10, height=10)
par(bg="#f2f2f2") # Define a cor do fundo da figura
set.seed(1234) # Semente para gerar a mesma nuvem
wordcloud(names(v), freq=as.numeric(v), # As entradas serao as mesmas mensionadas acima, com excessao da cor
scale=c(4,0.5), # Um vetor de comprimento 2 que indica o range do tamanho das palavras
min.freq = freq, # Input informado pelo usuario
max.words=max, # Input informado pelo usuario
colors=c("#dd003f","#002637","#0bdc99","#00bac5","#ff8947","#c20037","#00a2ab")%>% #Cores desejadas
rev(), # Altera a ordem que as cores participarao da figura
random.order=FALSE, # Plot das palavras em ordem aleatória. Se falso, eles serão plotados em frequência decrescente
rot.per=(1-horizontal), # Proporção de palavras com rotação de 90 graus
use.r.layout=FALSE, # Se falso, o código c ++ é usado para detecção de colisão, caso contrário, R é usado
family = "C", # Seleciona a fonte informada em windowsFonts no inicio do documento
font = 2) # 1:default, 2:negrito, 3:italico, 4:negrito+italico
return(freq = d) # Retorna os termos detectados ordenados de acordo com a sua frequencia
}
}else{
return(freq = d)} # Retorna os termos detectados ordenados de acordo com a sua frequencia
}
# Testando a funcao: -----------------------------------------------------
library(readr) # Carregando pacote para leitura do arquivo
base <- read_csv("base.csv") # Leitura do arquivo exemplo
base=base[,2] # Selecionando apenas a coluna do texto
# Obtendo nuvem e salvando tabela num objeto com nome teste:
teste=wordcloud_sentiment(base,
type = "text",
sentiment = F,
excludeWords = c("nao"),
ngrams = 3,
tf_idf = T,
max = 100,
freq = 10,
horizontal = 1,
textStemming = F,
print=T)
# Conferindo o conteudo do objeto criado:
teste
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment