10 Classificação: Dicionários e Análise de Sentimentos
Objetivos deste capítulo:
- Apresentar uma introdução à análise de sentimentos utilizando dicionários
- Apresentar como criar e utilizar dicionários/léxicos em classificação
- Como fazer isso com diferentes pacotes do R
10.1 Classificação
Podemos classificar palavras, sentenças, textos, documentos, corpus, etc. através de diferentes métodos. A análise de sentimentos ou emoções é um sub-item particular dentre os tipos de classificação. Entre as diferentes formas de classificação, seguindo a tipologia de Welbers et al. (2017) temos:
Welbers, K., Van Atteveldt, W., & Benoit, K. (2017). Text Analysis in R. Communication Methods and Measures, 11 (4), 245-265. http://www.tandfonline.com/doi/10.1080/19312458.2017.1387238
- Dicionários e métodos de contagem:
- as categorias e os termos que compõem estas categorias são disponibilizados previamente pelo pesquisador.
- Aprendizado de máquinas supervisionado, ou supervised machine learning:
- Um (ou vários) seres humanos classificam textos com base em categorias estabelecidas por estes e o algoritmo irá tentar aprender o padrão e aplicar na classificação subsequente.
- Aprendizado de máquinas não-supervisionado ou unsupervised machine learning.
- Exemplo é a modelagem de tópicos (topic modeling) que tem o algoritmo Latent Dirichlet Allocation (LDA) como exemplo mais famoso. O algoritmo estabelece as categorias e os termos, cabendo muitas vezes ao pesquisador apenas estabelecer o número de categorias.
- categorização estatística:
- não possui categorias prévias dadas por humanos, mas também não é machine learning.
Há várias formas de analisar sentimentos. O modo mais simples e talvez mais recorrente se dá com o uso de dicionários/léxicos com termos/palavras e seu sentimento ou uma pontuação (score) correspondente. Mas há também formas mais elaboradas de análise sentimento, e também mais complicadas, que mixam outras técnicas de processamento de linguagem natural com a análise de sentimentos.
10.2 Análise de sentimentos (Sentiment Analysis) por meio de dicionário/léxico
Como sempre, o R possui diversos pacotes também para análise de sentimentos utilizando léxico de palavras, como por exemplo, o SentimentAnalysis, syuzhet, o Tidytext e o Quanteda.
De modo simplificado, a análise de sentimentos pode ser entendida como uma forma de classificação em categorias pré-definidas. Tais categorias podem ser “positivo” e “negativo” em sua versão mais simples e binária, trinaria se para além das duas se incorporar a categoria “neutro”, ou pode-se ainda utilizar de diversas outras categorias, como “espanto”, “indecisão”, etc. Pode-se ainda atribuir diferentes pesos aos termos dentro de um escopo de pontuação, por exemplo, entre -5 e +5, ou entre -10 a +10. Isto faz sentido nas situações onde queira se diferenciar termos como “ruim” de “péssimo” e de “pior”.
10.2.1 Datasets de sentimentos/emoções
Além de possibilitar criar seu próprio dicionário, com suas próprias categorias, é possível usar léxicos de sentimentos já pronto, contendo dezenas de milhares de palavras já categorizadas. Diferentes pacotes vem com diferentes dicionários, alguns com dicionários em comum. Alguns pacotes são apenas de datasets de sentimentos a serem usados.
O pacote tidytext vem com os seguintes dicionários: - Bing: palavras rotuladas em positivo e negativo - AFINN: palavras com escores entre -5 e 5. - NRC: Emoções são classificados em 8 emoções. Além de positivo e negativo: - “trust”, “fear”, “sadness”,“anger”, “surprise”, disgust”, “joy” e “anticipation” - Loughran-McDonald Sentiment lexicon. Requer licença para uso comercial.
str(tidytext::get_sentiments("bing"))
## tibble [6,786 × 2] (S3: tbl_df/tbl/data.frame)
## $ word : chr [1:6786] "2-faces" "abnormal" "abolish" "abominable" ...
## $ sentiment: chr [1:6786] "negative" "negative" "negative" "negative" ...
str(tidytext::get_sentiments("afinn"))
## spec_tbl_df [2,477 × 2] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
## $ word : chr [1:2477] "abandon" "abandoned" "abandons" "abducted" ...
## $ value: num [1:2477] -2 -2 -2 -2 -2 -2 -3 -3 -3 -3 ...
## - attr(*, "spec")=
## .. cols(
## .. word = col_character(),
## .. value = col_double()
## .. )
## - attr(*, "problems")=<externalptr>
str(tidytext::get_sentiments("loughran"))
## tibble [4,150 × 2] (S3: tbl_df/tbl/data.frame)
## $ word : chr [1:4150] "abandon" "abandoned" "abandoning" "abandonment" ...
## $ sentiment: chr [1:4150] "negative" "negative" "negative" "negative" ...
str(tidytext::get_sentiments("nrc"))
## tibble [13,875 × 2] (S3: tbl_df/tbl/data.frame)
## $ word : chr [1:13875] "abacus" "abandon" "abandon" "abandon" ...
## $ sentiment: chr [1:13875] "trust" "fear" "negative" "sadness" ...
O pacote SentimentAnalysis vem com os seguintes léxicos: - QDAP (somente seu dicionário, não o pacote QDAP), - Harvard IV, dicionário piscológico, com a lista utilizada no software General Inquirer. - Henry’s Financial dictionary (Henry 2008), dicionário focado em finanças - Loughran-McDonald Financial dictionary (Loughran and McDonald 2011)
str(SentimentAnalysis::DictionaryGI) # Harvard-IV
## List of 2
## $ negative: chr [1:2005] "abandon" "abandonment" "abate" "abdicate" ...
## $ positive: chr [1:1637] "abide" "ability" "able" "abound" ...
str(SentimentAnalysis::DictionaryHE) # Henry’s Financial dictionary
## List of 2
## $ negative: chr [1:85] "below" "challenge" "challenged" "challenges" ...
## $ positive: chr [1:105] "above" "accomplish" "accomplished" "accomplishes" ...
str(SentimentAnalysis::DictionaryLM) # Loughran-McDonald Financial dictionary
## List of 3
## $ negative : chr [1:2355] "abandon" "abandoned" "abandoning" "abandonment" ...
## $ positive : chr [1:354] "able" "abundance" "abundant" "acclaimed" ...
## $ uncertainty: chr [1:297] "abeyance" "abeyances" "almost" "alteration" ...
::loadDictionaryQDAP() |> str() # polarity words do QDAP
SentimentAnalysis## List of 2
## $ positiveWords: chr [1:1280] "a plus" "abound" "abund" "access" ...
## $ negativeWords: chr [1:2952] "abnorm" "abolish" "abomin" "abort" ...
## - attr(*, "class")= chr "SentimentDictionaryBinary"
- O pacote corpus possui o WordNet-Affect Lexicon, o “AFINN Sentiment Lexicon”
- O pacote lexicon possui muitos datasets, para citar alguns dos diversos:
lexicon::key_sentiment_jockers
: Jockers Sentiment Keylexicon::hash_sentiment_huliu
: dataset com 6.874 termos com valores que vão de -2 a 1, baseado em Hu & Liu’slexicon::hash_emojis
: com descrição de emojislexicon::nrc_emotions
: 14.182 linhas em 9 variáveis/categorias: term, anger, anticipation, disgust, fear, joy, sadness, surprise e trust.lexicon::profanity_*
com vários datasets com palavrões, termos racistas/linguagem de ódio, ouhash_internet_slang
com gírias de internet.- É possível realizar uma busca do que há disponível no pacote com a função
lexicon::available_data('termo')
O pacote LexiconPT possui três léxicos de sentimentos em português (o utilizaremos mais à frente), dois deles mais de 30 mil termos.
str(lexiconPT::oplexicon_v2.1)
## 'data.frame': 30673 obs. of 3 variables:
## $ term : chr "ababadar" "ababelar-se" "ababelar" "abacanar" ...
## $ type : chr "vb" "vb" "vb" "vb" ...
## $ polarity: int 0 1 -1 1 1 -1 -1 -1 -1 -1 ...
str(lexiconPT::oplexicon_v3.0)
## 'data.frame': 32191 obs. of 4 variables:
## $ term : chr "=[" "=@" "=p" "=P" ...
## $ type : chr "emot" "emot" "emot" "emot" ...
## $ polarity : int -1 -1 -1 -1 -1 1 1 1 1 -1 ...
## $ polarity_revision: chr "A" "A" "A" "A" ...
str(lexiconPT::sentiLex_lem_PT02)
## 'data.frame': 7014 obs. of 5 variables:
## $ term : chr "a-vontade" "abafado" "abafante" "abaixado" ...
## $ grammar_category : chr "N" "Adj" "Adj" "Adj" ...
## $ polarity : num 1 -1 -1 -1 -1 1 -1 1 1 -1 ...
## $ polarity_target : chr "N0" "N0" "N0" "N0" ...
## $ polarity_classification: chr "MAN" "JALC" "MAN" "JALC" ...
Mas é sempre bom ressaltar e repetir: o melhor é ter um dicionário adequado para o contexto de aplicação. A aplicação de dicionários tem de ser validada!
“The main take-home message is, however, that a human eye is still required to guarantee the validity of measuring sentiment in content analysis… we strongly recommend that every automatic text analysis project should start with coding a validation set.” (van Atteveldt et al,2021, p.3)
10.2.2 Pacote SentimentAnalysis
- Pacote SentimentAnalysis, link no Cran, manual em pdf e página vignette (de exemplo/tutorial)
Vamos iniciar criando nosso próprio dicionário de sentimentos para entender melhor o conceito de análise de sentimentos.
Após instalar o pacote utilizando o comando install.packages("SentimentAnalysis")
# carregando o pacote
library(SentimentAnalysis)
# Vetor com frases a serem analisadas
<- c("Café é bom", "Rúcula é ruim", "Bem mais ou menos",
documentos "refrigerante é péssimo", "ruim é ter de trabalhar")
# Criando um dicionário (no caso, do tipo binário)
<- SentimentDictionaryBinary(
dict_pt # vetor de palavras com conotação positiva
c("bom","boa", "excelente"),
# vetor de palavras com conotação negativa
c("ruim", "péssimo"))
# realizando a análise de sentimento a partir do dicionário criado
<- analyzeSentiment(documentos,
AnaliseSentimentos language="portuguese",
rules=list("pontos"=list(ruleSentiment, dict_pt)))
# visualizando
AnaliseSentimentos ## pontos
## 1 0.5000000
## 2 -0.5000000
## 3 0.0000000
## 4 -0.5000000
## 5 -0.3333333
# Apresentando este resultado de forma mais legível para humanos, como um data frame.
<- data.frame(frase = documentos,
sentimentosDF score.sentimentos = AnaliseSentimentos,
# convertendo os valores em direções: pos., neg. e neutro
sentimento = convertToDirection(AnaliseSentimentos$pontos))
sentimentosDF ## frase pontos sentimento
## 1 Café é bom 0.5000000 positive
## 2 Rúcula é ruim -0.5000000 negative
## 3 Bem mais ou menos 0.0000000 neutral
## 4 refrigerante é péssimo -0.5000000 negative
## 5 ruim é ter de trabalhar -0.3333333 negative
Para criar dicionário ponderado (weighted), isto é, com valores contínuos, onde cada termo recebe pontuação, fazemos da seguinte forma: colocamos como primeiro vetor os termos e o segundo com suas respectivas pontuações.
# Opção 1:
# Léxico de sentimentos a partir de dois vetores
<- SentimentDictionaryWeighted(c("péssimo", "ruim", "bom", "boa", "excelente", "top"),
d c(-10, -5, +5, +5, +10, +10),)
d## Type: weighted (words with individual scores)
## Intercept: 0
## -10.00 péssimo
## -5.00 ruim
## 5.00 bom
## 5.00 boa
## 10.00 excelente
## 10.00 top
# Opção 2
# Montando o léxico de sentimentos a partir de uma tabela
<- read.table(header = FALSE, text = '
tabela péssimo -10
ruim -5
ok 0
bom +5
boa +5
excelente +10
top +10')
<- SentimentDictionaryWeighted(tabela[,1], tabela[,2])
d
d## Type: weighted (words with individual scores)
## Intercept: 0
## -10.00 péssimo
## -5.00 ruim
## 0.00 ok
## 5.00 bom
## 5.00 boa
## 10.00 excelente
## 10.00 top
Também é possível utilizar dicionários/léxicos de sentimentos prontos em português.
É sempre bom lembrar que certos termos em um contexto, podem bem significar outra coisa em outros contextos.
Por isso há diversidade de dicionários, e sempre deve-se procurar, sempre que possível, utilizar um dicionário que tenha sido criado num contexto parecido ao que vamos utilizar, quando não, criarmos nosso próprio léxico para a ocasião, ou adaptar algum. Além disso, o ideal é sempre validarmos o dicionário.
O pacote LexiconPT disponibiliza alguns desses dicionários/léxicos na língua de Camões, alguns com mais de 30 mil termos já catalogados.
Para padronização, os termos se encontram em minúsculo e sem acentuação, alguns até com emoticons catalogados ;)
.
Após a instalação do pacote com install.pacakges("lexiconPT")
, vamos carregar a biblioteca/pacote com:
library(lexiconPT)
Para checar se uma palavra específica está presente nos léxicos do LexiconPT
e quais pontuações recebe:
::get_word_sentiment("comemorar")
lexiconPT## $oplexicon_v2.1
## term type polarity
## 6622 comemorar vb 0
##
## $oplexicon_v3.0
## term type polarity polarity_revision
## 7298 comemorar vb 0 A
##
## $sentilex
## [1] "Word not present in dataset"
::get_word_sentiment("gritar")
lexiconPT## $oplexicon_v2.1
## term type polarity
## 16646 gritar vb 1
##
## $oplexicon_v3.0
## term type polarity polarity_revision
## 17567 gritar vb 1 A
##
## $sentilex
## [1] "Word not present in dataset"
::get_word_sentiment("espernear")
lexiconPT## $oplexicon_v2.1
## [1] "Word not present in dataset"
##
## $oplexicon_v3.0
## [1] "Word not present in dataset"
##
## $sentilex
## [1] "Word not present in dataset"
::get_word_sentiment("confrontar")
lexiconPT## $oplexicon_v2.1
## term type polarity
## 7049 confrontar vb -1
##
## $oplexicon_v3.0
## term type polarity polarity_revision
## 7736 confrontar vb -1 A
##
## $sentilex
## term grammar_category polarity polarity_target
## 1582 confrontar V 1 N0:N1
## polarity_classification
## 1582 MAN
::get_word_sentiment("detestar")
lexiconPT## $oplexicon_v2.1
## term type polarity
## 10573 detestar vb 1
##
## $oplexicon_v3.0
## term type polarity polarity_revision
## 11314 detestar vb 1 A
##
## $sentilex
## term grammar_category polarity polarity_target polarity_classification
## 2353 detestar V 0 N0:N1 MAN
::get_word_sentiment("odiar")
lexiconPT## $oplexicon_v2.1
## term type polarity
## 22371 odiar vb 1
##
## $oplexicon_v3.0
## term type polarity polarity_revision
## 23549 odiar vb 1 A
##
## $sentilex
## term grammar_category polarity polarity_target polarity_classification
## 5215 odiar V 0 N0:N1 MAN
Para podermos trocar de dicionário de modo fácil, vamos criar um vetor com estes léxicos disponíveis no pacote LexiconPT
# Escolhendo manuamente um léxico vamos usar.
<- oplexicon_v2.1
lex
# Criando o vetor com léxicos
<- c("oplexicon_v2.1", "oplexicon_v3.0", "sentiLex_lem_PT02")
lex
# para que captemos não o nome, mas o conteúdo do dataframe, rodamos o seguinte
# comando, que pega o segundo item do vetor
<- eval(parse(text = lex[2]))
lexico str(lexico)
## 'data.frame': 32191 obs. of 4 variables:
## $ term : chr "=[" "=@" "=p" "=P" ...
## $ type : chr "emot" "emot" "emot" "emot" ...
## $ polarity : int -1 -1 -1 -1 -1 1 1 1 1 -1 ...
## $ polarity_revision: chr "A" "A" "A" "A" ...
Os dicionários do LexiconPT possuem vários termos duplicados, e isto impossibilita de usá-los diretamente no pacote SentimentAnalysis. Para identificar duplicados usamos o comando duplicated()
, que retorna um vetor de mesmo tamanho, com valores lógicos.
# "True" indica quantos valores duplicados nós temos
summary(duplicated(lexico$term))
## Mode FALSE TRUE
## logical 31951 240
# Se quisermos ver quais os termos duplicados:
<- lexico$term[duplicated(lexico$term)]
duplicados 1:35]
duplicados[## [1] ";)" ";D" ";p" ":("
## [5] ":)" ":@" ":*" ":P"
## [9] ":x" "aberrantes" "academica" "academicas"
## [13] "academico" "academicos" "acoitar" "acoutar"
## [17] "adultera" "adulteras" "adultero" "adulteros"
## [21] "afinar" "anacronica" "anacronicas" "anacronico"
## [25] "anacronicos" "anemica" "anemicas" "anemico"
## [29] "anemicos" "anomalo" "antiquissima" "antiquissimas"
## [33] "antiquissimo" "antiquissimos" "apedrar"
Uma solução rápida seria retirar estes termos duplicados, ficando apenas com os termos que apareceram uma única vez. Fazemos isto com a função unique()
.
EM EXPANSÃO Este trecho sobre uso do LexiconPT será expandido em breve
A função lexiconPT::compareDictionaries()
compara dicionários de dois em dois, mostrando total de palavras únicas e as compartilhadas.
<- SentimentAnalysis::SentimentDictionary(c("incerto", "possível", "provável", "talvez"))
dic1 <- SentimentAnalysis::SentimentDictionary(c("preferencialmente", "pretender", "provável"))
dic2
# Comparando os dicionários
<- SentimentAnalysis::compareDictionaries(dic1, dic2)
cmp ## Comparing: wordlist vs wordlist
##
## Total unique words: 6
## Matching entries: 1 (0.1666667%)
cmp## $totalUniqueWords
## [1] 6
##
## $totalSameWords
## [1] 1
##
## $ratioSameWords
## [1] 0.1666667
10.2.4 Pacote Quanteda
O Quanteda torna fácil o uso de dicionários, e a análise de sentimentos é um caso de dicionário. Veja aqui a sessão “Grouping words by dictionary or equivalence class” para se ter uma ideia.
EM EXPANSÃO Este trecho será expandido
10.2.5 Pacote syuzhet
O pacote syuzhet e seu vingette.
Carregando o pacote
library(syuzhet)
Criando um dicionário personalizado
<- "É comum adorar esta definição.
texto Mas é fácil odiar esta outra.
A beleza está nos detalhes"
# tokenizando
<- get_sentences(texto)
token_frases
# criando um data frame com sentimentos, usando vetores
<- data.frame(word=c("adorar", "odiar", "beleza", "feio"),
meu_lexicon value=c(1,-1,1, -1))
# aplicando
<- get_sentiment(token_frases,
valores_frases method = "custom",
lexicon = meu_lexicon)
valores_frases## [1] 1 -1 1
EM EXPANSÃO Este trecho será expandido
Por fim, vale dizer que a análise de sentimentos não se resume ao uso de dicionários prontos, havendo toda uma miríade de outras técnicas/ técnicas mistas utilizadas ali, como word embedding, topic models, etc.
10.3 Dicas finais: Bibliografia indicada
Dicas: Análise de Sentimentos
- Em termos teóricos, para saber mais sobre análise de sentimentos, recomendo ver - JURAFSKY, Dan.; MARTIN, James H. Speech and Language Processing - An Introduction to Natural Language Processing, Computational Linguistics, and Speech Recognition. 3ª Edição. 2020. especialmente o cap.4 Naive Bayes and Sentiment Classification para entender os mecanismos de classificação e cap.20 Lexicons for Sentiment, Affect, and Connotation para entender os diferentes léxicos utilizados.
- Uma boa introdução à lógica da análise de sentimentos, sobre definições e conceitos básicos, problemas, entradas e saídas, e como os resultados podem ser usados na prática, ver:
- LIU, Bing. Sentiment Analysis and Subjectivity. 2010
- Para uma visão mais panorâmica das diferentes abordagens em análise de sentimentos, recomenda-se:
- Wouter van Atteveldt, Mariken A. C. G. van der Velden & Mark Boukes (2021) The Validity of Sentiment Analysis: Comparing Manual Annotation, Crowd-Coding, Dictionary Approaches, and Machine Learning Algorithms, Communication Methods and Measures, 15:2, 121-140, DOI: 10.1080/19312458.2020.1869198 pdf
- Sobre os diferentes tipos de dicionários/léxicos:
- Ribeiro, F.N., Araújo, M., Gonçalves, P. et al. SentiBench - a benchmark comparison of state-of-the-practice sentiment analysis methods. EPJ Data Sci. 5, 23 (2016). https://doi.org/10.1140/epjds/s13688-016-0085-1
- Para uma lista extensa de bibliografia acerca do tema, ver Opinion Mining, Sentiment Analysis, and Opinion Spam Detection