O volume descomunal de dados que circulam nas redes sociais torna este um prato cheio e suculento para qualquer ser humano que busque conhecimento. Obter informações sobre a percepção das pessoas a determinada funcionalidade, sistema ou produto; o alcance e popularidade de uma marca ou até mesmo o sentimento das pessoas sobre determinadas ações de governo e políticas sociais, são apenas alguns exemplos do valor escondido por detrás das postagens das redes sociais.
O objetivo deste trabalho é apresentar algumas abordagens técnicas utilizando o R para extração de informações de um conjunto de tweets, onde as manifestações da rede social carregam um conjunto interessante de dados a serem explorados.
Para este contexto, utilizei um conjunto de tweets relacionados ao SISU coletados entre o período de finalização das inscrições e o início das matrículas (24/02/2023 a 03/03/2023).
Este trabalho não tem como objetivo esclarecer conceitos acerca do Processamento de Linguagem Natural, Text Mining e demais domínios de conhecimento relacionados a análise de sentimentos.
Fonte: website GreenBook.org
Conforme apresentado no portal do Ministério da Educação, o SISU, Sistema de Seleção Unificada, reúne em um sistema eletrônico gerido pelo MEC as vagas ofertadas por instituições públicas de ensino superior de todo o Brasil, sendo a grande maioria delas ofertada por instituições federais (universidades e institutos). O sistema executa a seleção dos estudantes com base na nota do Exame Nacional do Ensino Médio (Enem). Até o limite da oferta das vagas, por curso e modalidade de concorrência, de acordo com as escolhas dos candidatos inscritos, eles são selecionados por ordem de maior classificação, em cada uma das duas edições anuais do SISU.
Foram coletados 2000 tweets no período de 24/02/2023 a 04/03/2023 por meio da API V2 do Twitter, em seguida, realizado a limpeza e o tratamento dos dados, depois, a análise exploratória e por último, a análise de tópicos e de sentimentos.
Etapas:
# Função para instalação dos pacotes requeridos caso não tenha sido instalado.
loadlibrary <- function(nome_pacote){
if (!require(nome_pacote, character.only = TRUE, quietly = TRUE)) {
install.packages(nome_pacote, dependencies = TRUE)
if(!require(nome_pacote, character.only = TRUE, quietly = TRUE)) {
stop("Pacote '", nome_pacote, "' não encontrado")
}
}
}
Para se ter acesso a API é necessário submeter a solicitação de uma conta de desenvolvedor para o Twitter. Para o trabalho em questão, foi configurado a conta de um APP em um projeto específico, para este se conectar a API. Para cada APP configurado é gerado um conjunto de tokens e chaves que são utilizadas na autenticação da API.
Como não fazem parte do escopo deste trabalho, não entrarei nos detalhes de criação e configuração da conta developer do Twitter.
# Autenticação
# bearer_token <- "seuTokenTwitter" #variável com o token pessoal da API
# auth <- rtweet_app(bearer_token)
# Obtendo 2000 tweets que possuam "SISU" em seu texto de postagem. Estão sendo excluídos da coleta os retweets, pois o que me interessa nesta análise são apenas os tweets originais.
# Obs.: a função search_tweets retorna os tweets que coincidem com os critérios informados nos parâtros e retorna apenas os dados compreendidos nos últimos 9 dias.
# dfSISU <- search_tweets("SISU", token = auth, lang = "pt-br", n = 2000, include_rts = FALSE)
# Carregando o dataframe com os dados coletados no período de 24/02/2023 à 04/03/2023
df_SISU <- readRDS("dfSISU.RDS")
# Verificando a estrutura do dataframe
glimpse(df_SISU)
## Rows: 2,000
## Columns: 43
## $ created_at <dttm> 2023-03-03 14:31:15, 2023-02-28 11:37:1…
## $ id <dbl> 1.631709e+18, 1.630578e+18, 1.629123e+18…
## $ id_str <chr> "1631708832253476897", "1630577889010647…
## $ text <chr> "#SISUFF | Pessoal da 1ª chamada do SISU…
## $ full_text <chr> "#SISUFF | Pessoal da 1ª chamada do SISU…
## $ truncated <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE…
## $ entities <list> [[<data.frame[1 x 2]>], [<data.frame[1 …
## $ source <chr> "<a href=\"https://mobile.twitter.com\" …
## $ in_reply_to_status_id <dbl> NA, NA, NA, 1.632045e+18, NA, NA, 1.6307…
## $ in_reply_to_status_id_str <chr> NA, NA, NA, "1632044976761651200", NA, N…
## $ in_reply_to_user_id <dbl> NA, NA, NA, 1.538912e+18, NA, NA, 1.2162…
## $ in_reply_to_user_id_str <chr> NA, NA, NA, "1538911919012290561", NA, N…
## $ in_reply_to_screen_name <chr> NA, NA, NA, "marimizes", NA, NA, "sousa_…
## $ geo <list> [], [], [], [], [], [], [], [], [], [],…
## $ coordinates <list> [<data.frame[1 x 3]>], [<data.frame[1 x…
## $ place <list> [<data.frame[1 x 3]>], [<data.frame[1 x…
## $ contributors <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ is_quote_status <lgl> FALSE, FALSE, FALSE, FALSE, TRUE, FALSE,…
## $ retweet_count <int> 16, 402, 290, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ favorite_count <int> 42, 8749, 3949, 0, 0, 0, 0, 1, 0, 0, 0, …
## $ favorited <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE…
## $ favorited_by <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ retweeted <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE…
## $ scopes <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ lang <chr> "pt", "pt", "pt", "pt", "pt", "pt", "pt"…
## $ possibly_sensitive <lgl> FALSE, FALSE, NA, NA, FALSE, NA, FALSE, …
## $ display_text_width <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ display_text_range <dbl> 206, 40, 178, 208, 10, 160, 240, 124, 23…
## $ retweeted_status <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ quoted_status <list> [<data.frame[1 x 27]>], [<data.frame[1 …
## $ quoted_status_id <dbl> NA, NA, NA, NA, 1.631659e+18, NA, NA, NA…
## $ quoted_status_id_str <chr> NA, NA, NA, NA, "1631659196407054337", N…
## $ quoted_status_permalink <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ quote_count <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ timestamp_ms <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ reply_count <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ filter_level <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ metadata <list> [<data.frame[1 x 2]>], [<data.frame[1 x…
## $ query <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ withheld_scope <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ withheld_copyright <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ withheld_in_countries <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ possibly_sensitive_appealable <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
# Exibindo as 4 primeiras colunas de alguns tweets
kbl(head(df_SISU[,1:4])) %>% kable_paper("hover", full_width = TRUE)
| created_at | id | id_str | text |
|---|---|---|---|
| 2023-03-03 14:31:15 | 1.631709e+18 | 1631708832253476897 |
#SISUFF | Pessoal da 1ª chamada do SISU: o trem da pré-matrícula só passa até 8/03 🗓️ Acessem o portal do COSEAC e façam a pré-matrícula com calma 👩💻 OBS.: Procrastinadores, não deixem para última hora 😭 https://t.co/YRCiQQIPMB |
| 2023-02-28 11:37:18 | 1.630578e+18 | 1630577889010647040 | Cadê as minhas aprovadas do SISU/Enem? 😭 https://t.co/sY2LbFjiPE |
| 2023-02-24 11:16:48 | 1.629123e+18 | 1629123182199287808 | Vigorosos e Vigorentos, hoje é o último dia para se inscrever no SISU e concorrer a uma vaga na universidade!! Não percam tempo não, viu? Corram que ainda dá tempo, bora estudar! |
| 2023-03-04 13:03:48 | 1.632049e+18 | 1632049211960074240 | @marimizes @UfmgSpotted O último ministro da educação era tão incompetente que não conseguiu montar um calendário decente e aí o resultado do Sisu esse ano saiu muito em cima da hora pra eles entrarem regular |
| 2023-03-04 12:56:33 | 1.632047e+18 | 1632047385965654017 | eu no sisu https://t.co/gyh3J5TIcO |
| 2023-03-04 12:52:42 | 1.632046e+18 | 1632046418067161093 | to mal que mesmo tendo passado na chamada regular do sisu agora eu só entrei pro segundo semestre vou passar um ano sem fazer nada, eu queria tanto estudar :((( |
Função para realizar a limpeza dos dados, pois se tratando de postagens de uma rede social a escrita tende a ser muito informal, com emprego de repetições, gíria, emojis e muitos erros gramaticais. O intuito aqui é deixarmos um texto minimamente trabalhável (padronizado) mantendo apenas o conteúdo textual.
## Função para limpeza dos textos
limpar_texto <- function(texto) {
# Converte o texto para minúsculo
texto <- tolower(texto)
# Substitui apostrofo
texto <- gsub("'", "",texto)
# Remove html tags
texto <- gsub("<.*?>", "", texto)
# Remove o usuário adicionado no comentário
# texto <- gsub("@\\w+", " ", texto)
texto <- gsub('@\\S+', "", texto)
# Remove as pontuações
texto <- gsub("[[:punct:]]", "", texto)
# Remove links
texto <- gsub("http.+", "", texto)
# Remove numerações
texto <- gsub("\\d+\\w*\\d*", "", texto)
# Remove caracteres de controle \s e \n
texto <- gsub("[[:cntrl:]]", "", texto)
# Remove emojis
texto <- gsub("[^[:graph:]]", " ", texto)
# Remove palavras que tenham 3 letras consecutivas repetidas
texto <- gsub("\\b\\w*(\\w)\\1{2,}\\w*\\b", " ", texto)
# Remove tabulacoes e substitui por espaços simples
texto <- gsub("[ \t]{2,}"," ",texto)
# Remove os espaços em branco no início do texto e novas linhas das sentenças
texto <- gsub("^\\s+","",texto)
texto <- gsub("^ ", "", texto)
# Remove espaços no final do texto
texto <- gsub(" $", "", texto)
texto <- gsub("\\s+$","", texto)
return(texto)
}
# texto puro, sem tratamento
tail(df_SISU$text)
## [1] "atualiza aí sisu pufavo"
## [2] "a última parcial do sisu já é meio que a oficial?"
## [3] "obg sisu, você colabora muito com a minha ansiedade"
## [4] "9 dias de sisu, sem cabimento.. https://t.co/oURNXL5Omh"
## [5] "Se o sisu demorar pra mostrar a parcial da nota corte de propósito eu juro que vou me vingar nos índices de sui"
## [6] "EU NÃO AGUENTO MAIS SER HUMILHADA PELO SISU"
# Chamada da função de tratamento de dados
df_SISU$textoLimpo <- limpar_texto(df_SISU$text)
# texto limpo
tail(df_SISU$textoLimpo)
## [1] "atualiza aí sisu pufavo"
## [2] "a última parcial do sisu já é meio que a oficial"
## [3] "obg sisu você colabora muito com a minha ansiedade"
## [4] "dias de sisu sem cabimento"
## [5] "se o sisu demorar pra mostrar a parcial da nota corte de propósito eu juro que vou me vingar nos índices de sui"
## [6] "eu não aguento mais ser humilhada pelo sisu"
O gráfico abaixo apresenta a distribuição dos tweets ao longo do período coletado (24/02/2023 a 04/03/2023).
É possível observar que o pico inicial indicado na data de 24/02 concentra o maior volume de tweets no momento de encerramento das inscrições, depois, é possível observar um outro pico, menos expressivo, durante o período de divulgação dos resultados e por último um pequeno contingente de manifestações correspondendo ao período de matrícula.
ts_plot(df_SISU, by = "hours", color = "blue")+
theme_minimal()+
theme(plot.title = element_text(face = "bold"))+
labs(
x = NULL, y = "Quantidade tweets/hora",
title = "Frequência dos tweets durante o período de 24/02 a 04/03",
subtitle = "O período abrange a finalização das inscrições, a seleção/divulgação e o início das matrículas",
caption = "Fonte: coleta de tweets por meio da API do Twitter"
)
Identificação da localização das postagens coletadas.
Os pontos indicados no mapa representam apenas uma parcela dos registros, pois como a informação de localização é uma opção dentro das configurações de “Privacidade e Segurança” do aplicativo do Twitter, que por padrão vem desabilitada, nem todos os tweets coletados apresentam esse dado.
localizacao <- rtweet::lat_lng(df_SISU)
localizacao <- na.omit(localizacao[,c("lat","lng")])
par(mar = c(0, 0, 0, 0))
map("world","Brazil", fill=TRUE, col="grey90")
map.cities(country = "Brazil", minpop = 1000000, pch = 19, cex = 1.2, label = TRUE)
with(localizacao, points(
lng, lat, pch = 19,
cex = 2.2,
col = 'red')
)
Aqui estão representados os dispositivos (ou plataformas) utilizados pelos usuários nas postagens.
# Extração das informações sobre source por meio de expressão regular
dfSISU_dispositivos <- df_SISU %>%
extract(
source,
into = c("source_href", "source"),
regex = '<a href="([^"]+)"[^>]+>([^<]+)</a>'
)
# Registros com representatividade (n > 5).
tweet_dispositivo_cont <- dfSISU_dispositivos %>%
count(source) %>%
filter(n > 5) %>%
mutate(pct = n/sum(n))
# Visualização dos dispositivos
ggplot(tweet_dispositivo_cont) +
aes(x = source, y = n, label = n) +
geom_col(fill = "#1DA1F2") +
geom_text(nudge_y = 45) +
labs(
x = "Tipo de dispositivo utilizado",
y = "Quantidade",
title = "Análise dos dispositivos utilizados nos tweets",
subtitle = "Recorte dos dados com quantidade > 5 para melhor representatividade"
) +
coord_flip() +
theme_minimal()
Abaixo, destaco os 3 tweets mais retweetados
topRetweet <- df_SISU %>%
arrange(-retweet_count) %>%
select(created_at, id_str, retweet_count,text) %>%
head(3)
kbl(topRetweet
) %>%
kable_paper("hover", full_width = TRUE)
| created_at | id_str | retweet_count | text |
|---|---|---|---|
| 2023-02-28 11:37:18 | 1630577889010647040 | 402 | Cadê as minhas aprovadas do SISU/Enem? 😭 https://t.co/sY2LbFjiPE |
| 2023-02-24 11:16:48 | 1629123182199287808 | 290 | Vigorosos e Vigorentos, hoje é o último dia para se inscrever no SISU e concorrer a uma vaga na universidade!! Não percam tempo não, viu? Corram que ainda dá tempo, bora estudar! |
| 2023-02-24 00:17:13 | 1628957191397904384 | 231 | Eu depois de 8 dias de Sisu: https://t.co/PCOj76nL9s |
# Exibindo o mais retweetado
topRetweet_url <- tweet_url(topRetweet[1,2])
webshot(
topRetweet_url,
file = "topRetweet.png",
delay = 2,
cliprect = c(200, 5, 600, 600)
)
Apresento os 3 tweets mais curtidos.
topCurtidos <- df_SISU %>%
arrange(-favorite_count) %>%
select(created_at, id_str, favorite_count, text) %>%
head(3)
kbl(topCurtidos) %>%
kable_paper("hover", full_width = TRUE)
| created_at | id_str | favorite_count | text |
|---|---|---|---|
| 2023-02-28 11:37:18 | 1630577889010647040 | 8749 | Cadê as minhas aprovadas do SISU/Enem? 😭 https://t.co/sY2LbFjiPE |
| 2023-02-24 11:16:48 | 1629123182199287808 | 3949 | Vigorosos e Vigorentos, hoje é o último dia para se inscrever no SISU e concorrer a uma vaga na universidade!! Não percam tempo não, viu? Corram que ainda dá tempo, bora estudar! |
| 2023-02-24 00:17:13 | 1628957191397904384 | 778 | Eu depois de 8 dias de Sisu: https://t.co/PCOj76nL9s |
É possível observar na tabela acima, que os tweets que apresentam maior número de curtidas (favorite_count) são os mesmos que foram mais retweetados.
Abaixo, apresento a disperção dos tweets retweetados e dos tweets curtidos.
df_SISU_Popularidade <- df_SISU %>%
select(retweet_count, favorite_count) %>%
filter(retweet_count > 5 | favorite_count > 5) %>%
count(retweet_count, favorite_count)
ggplot(df_SISU_Popularidade) +
aes(x = favorite_count, y = retweet_count, size = n) +
geom_point(shape = "circle", colour = "tomato2") +
scale_x_continuous(trans = "log10") +
scale_y_continuous(trans = "log1p") +
theme_gray()
É possível observar que a maioria dos tweets apresentaram baixa retweetagem e curtidas. Contudo, os outliers indicam uma crescente na retweetagem e um número expressivo de curtidas. Os outliers não foram removidos do escopo analisado, pois neste contexto, um desdobramento dessa análise pode ser sua influência e alcance (não abordada nesse estudo).
Abaixo, destaco as menções das postagens. Uma menção é quando o usuário utiliza o @nomeContaTwitter em sua postagem para notificar o respectivo usuário da conta. Essas menções são formas de manifestação dos usuários para chamar a atenção de alguém em específico para a postagem em questão.
Mencoes <-
entity(df_SISU, "user_mentions") %>%
drop_na(name) %>%
select("screen_name") %>%
count(screen_name) %>%
arrange(desc(n))
wordcloud2(Mencoes, color = "#F5F8FA", backgroundColor = "#1DA1F2" ,fontFamily = 'Barlow Light', rotateRatio = 0,size = 0.4, minSize = 3)
O Text Analytics trata do processamento e transformação de um texto não estruturado em um formato estruturado para auxiliar na identificação de padrões, gerar insights e conhecimento.
Nesta seção eu crio um Corpus com o conteúdo dos tweets presentes no dataframe. Corpus é um conjunto estruturado de textos disponível para análise. Este é um padrão essencial para quem está trabalhando com processamento de linguagem natural (PNL).
Em seguida, realizo a remoção das stopwords, que são palavras que podem ser suprimidas do texto sem prejudicar seu sentido. Como por exemplo: “o”, “a”, “em”, “no”, “aos”, “lhe”, “me”, “de”, “da”, dentre outras.
# Convertendo o dataframe em um Corpus para poder ser trabalhado como uma coleção de documentos, necessário para o processamento da linguagem natural.
text_corpus <- Corpus(VectorSource(df_SISU$textoLimpo))
# Crio um dataframe de stopwords personalizado
custom_stopwords <- c("sexta", "feira", "assim", "nesta", "nessa","nesse","desse","dessa","tava","então","aqui", "toda","todas","tanto","acho","ainda", "sisu","sisu2023", "fazer", "tudo", "pode", "todo", "todos", "quero", "quer", "meio", "post", "acho", "cada", "quiser", "mesma", "fica", "coisa", "passo", "sobre", "sei")
text_corpus <- tm_map(text_corpus, function(x)removeWords(x, stopwords("pt"))) # Removendo as stopwords do corpus.
## Warning in tm_map.SimpleCorpus(text_corpus, function(x) removeWords(x,
## stopwords("pt"))): transformation drops documents
text_corpus <- tm_map(text_corpus, removeWords, custom_stopwords) # Remove stopwords personalizado
## Warning in tm_map.SimpleCorpus(text_corpus, removeWords, custom_stopwords):
## transformation drops documents
text_corpus <- tm_map(text_corpus, stripWhitespace) # Remove os espaços em branco
## Warning in tm_map.SimpleCorpus(text_corpus, stripWhitespace): transformation
## drops documents
# Armazeno os textos tratados em um vetor de carcteres para ser utilizado na modelagem de tópicos
Textos_text_corpus <- content(text_corpus)
Neste passo realizo outro procedimento para estruturação adequada do conteúdo, criando um DTM (Document Term Matrix), ou seja, uma matriz de termos dos documentos.
DTM_tweets_temp <- DocumentTermMatrix(text_corpus)
# Elimino os documentos que possuem linhas vazias
totalLinhas <- apply(DTM_tweets_temp, 1, sum)
DTM_tweets <- DTM_tweets_temp[totalLinhas > 0, ]
Verifico se os documentos e termos presentes no Corpus foram carregados na estrutura matricial e apresento uma pequena amostra.
# Número de documentos carregados.
nDocs(DTM_tweets)
## [1] 1991
# Número de termos carregados. Cada palavra (após processo de limpeza) identificada nos Documents.
nTerms(DTM_tweets)
## [1] 3778
# Visualiza os termos carregados
tail(Terms(DTM_tweets),20)
## [1] "sino" "ufpb" "novidade" "xuxa" "camarada"
## [6] "preguiçoso" "direto" "garante" "grupos" "melhore"
## [11] "aparecer" "cristo" "desalmados" "monstros" "pufavo"
## [16] "cabimento" "propósito" "sui" "vingar" "índices"
Como curiosidade, vamos visualizar um tweet de uma postagem específica com base em um termo qualquer observado no resultado acima.
termoObservado <- "xuxa"
grep(termoObservado,df_SISU$textoLimpo, value = TRUE)
## [1] "nenhuma novidade o site da xuxa do sisu não abriu ainda tô aqui pelo povo"
TweetObservado <- df_SISU %>%
filter(str_detect(textoLimpo,termoObservado))
tweet_url <-tweet_url(TweetObservado$id_str)
webshot(
tweet_url,
file = "tweetObservado.png",
delay = 0.2,
cliprect = c(200, 5, 600, 300)
)
# Obtendo os 500 termos mais frequentes no Corpus. Estou considerando apenas palavras com mais de 4 letras por fazerem parte de um escopo mais significativo para o nosso contexto.
freq500 <- freq_terms(text_corpus, top = 500, at.least = 4, extend = TRUE)
wordcloud2(freq500, color = "white", backgroundColor = "#1DA1F2",fontFamily = 'Barlow Light', rotateRatio = 0, size = 0.7, minSize = 6)
# Visualização dos 10 termos mais frequentes
top10 <- freq_terms(text_corpus, top = 10, at.least = 4, extend = TRUE)
ggplot(top10) +
aes(x = reorder(WORD, +FREQ), weight = FREQ) +
geom_bar(fill = "#1DA1F2") +
labs(
x = "Termo",
y = "Contagem",
title = "Top 10 dos termos mais utilizados nos tweets",
subtitle = "Escopo de termos após a limpeza e tratamento"
) +
coord_flip() +
theme_minimal()
A análise de cluster é uma técnica utilizada para classificação de agrupamentos. Para realização desta análise utilizarei o modelo de distância Euclidiana para calcular a distância entre os termos, além do método de Ward para geração dos agrupamentos. Por último, apresento os agrupamentos gerados em um dendrograma.
De uma forma sucinta e breve, o método de Ward apresenta como agrupamento hierarquico uma medida de similaridade baseada na soma de quadrados entre 2 agrupamentos para todas as variáveis. Não entrarei em detalhes neste trabalho sobre definições e conceitos das técnicas aplicadas.
Um dendrograma é uma forma de representação gráfica dos agrupamentos formados (clusteres) e os níveis de similaridade entre eles.
Obs.: Há outros modelos e técnicas para clusterização (métodos hierárquicos, não hierárquicos e de mistura de distribuições) que podem ser utilizados. Foquei nos que eu tinha conhecimento e aptidão para utilizar os recursos do R.
# removendo termos esparços da matriz
rst_Tweets <- removeSparseTerms(DTM_tweets, 0.97)
# A função dist computa e retorna uma matriz com as medidas de distância entre as variáveis. Se trata de uma análise de cluster *não supervisionada*.
distancia <- dist(t(rst_Tweets), method = "euclidian")
# Crio um cluster hierarquico para análise, utilizando o método de agrupamento Ward D.
dendrograma <- hclust(d = distancia, method = "ward.D")
fviz_dend(
dendrograma,
k = 10,
color_labels_by_k = TRUE,
cex = 0.8,
lwd = 0.2,
rect = TRUE,
horiz = TRUE,
rect_fill = TRUE,
main = "Dendrograma dos termos dos Tweets Coletados",
xlab = "Agrupamento",
ylab = "Distância"
)
## Warning: The `<scale>` argument of `guides()` cannot be `FALSE`. Use "none" instead as
## of ggplot2 3.3.4.
## ℹ The deprecated feature was likely used in the factoextra package.
## Please report the issue at <https://github.com/kassambara/factoextra/issues>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
É possível observar no dendrograma acima os agrupamentos formados (clustering) e seus níveis de relação. Os termos destacados apresentam proximidade, ou seja, nos textos tweetados as palavras destacadas aparecem em sua grande parte associadas.
Conseguimos verificar abaixo o grau de associação da palavra “inscrições”, presente no dendrograma acima.
DTM_tweets %>% findAssocs("inscrições", 0.2)
## $inscrições
## terminam sextafeira encerram mil saiba
## 0.78 0.67 0.33 0.27 0.26
## universidades participar
## 0.23 0.21
É possível observar um grau elevado da associação da palavra “inscrições” com “terminam”, certamente indicando tweets com menções ao término das inscrições. Outras relações correspondentes em menor grau com as palavras “sextafeira”, “encerram”, “saiba”, “mil”, “participar”, “universidades”.
A modelagem de tópicos é uma abordagem de mineração de textos (text mining) com o objetivo de analisar os padrões em um texto e classificar esses padrões em tópicos que possam determinar a predominância de um estilo, conteúdo ou tema.
A modelagem de tópicos ou Topic Modeling é um método de classificação não supervisionada para descoberta de tópicos em textos por meio da análise de seus termos (palavras) e suas respectivas relações.
A Modelagem de tópicos é um assunto denso (e muito interessante) com inúmeras técnicas que passam pela estatística e linguística, com a interpretabilidade dos tópicos. Neste trabalho, vou direto ao ponto, dando foco na identificação dos tópicos.
Nesta seção vou identificar a quantidade estimada de tópicos para o conjunto de tweets armazenados na matriz (DTM - Document Term Matrix). Para estimar,
Para geração do modelo utilizarei a função LDA do pacote topicmodels. O LDA (Latent Dirichlet allocation) é um dos algoritmos mais usados para modelagem de tópicos. Podemos entender que cada documento é uma mistura de tópicos e cada tópico é uma mistura de palavras. O método matemático LDA é utilizado para encontrar as associações entre essas misturas de palavras com cada tópico enquanto determina os tópicos presentes em cada documento. Há várias implementações do algoritmo LDA - Latent Dirichlet allocation. Aqui, vou utilizar aqui o método Gibbs com a métrica Griffiths2004.
Para se ter um valor mais preciso pode-se utilizar a combinação de várias métricas, como: “Griffiths2004”, “CaoJuan2009”, “Arun2010”, “Deveaud2014”. Contudo, este é um processo que consome muito recurso computacional e para gerar todos esses cáclulos no conjunto de dados inteiro seria algo em torno de 6 horas, levando em consideração 8 núcleos de processamento.
k_topicos <- FindTopicsNumber(
DTM_tweets,
topics = seq(2, 40, by = 2),
metrics = c("Griffiths2004"),
method = "Gibbs",
control = list(seed = 123),
mc.cores = NA,
return_models = FALSE
)
FindTopicsNumber_plot(k_topicos)
É possível observar acima o valor indicado de 16 tópicos presentes em nosso conjunto de tweets, o que significa que diante de todo este conjunto, a presença de determinados termos e sua respectiva frequência, podem ser segmentados em 16 tópicos.
Como se trata de uma coleta de tweets e não um conjunto restrito de pessoas conversando sobre um tema específico, o valor de 16 tópicos aparenta ser adequado.
Abaixo, realizo o processamento do vetor e faço a estimativa da estrutura de tópicos obtendo os parâmetros de um modelo ajustado.
# realizo o processamento do vetor
tp_Textos_text_corpus <- textProcessor(
Textos_text_corpus,
metadata = NULL,
wordLengths = c(4, Inf),
stem = FALSE,
language = "pt",
verbose = FALSE
)
meta <- tp_Textos_text_corpus$meta
vocab <- tp_Textos_text_corpus$vocab
docs <- tp_Textos_text_corpus$documents
stm_Textos_text_corpus <- stm(
documents = docs,
vocab = vocab,
data = meta,
K = 16,
max.em.its = 100,
seed = 123,
verbose = FALSE
)
Agora, exibo a formação da estrutura de tópicos em suas respectivas proporções
plot.STM(stm_Textos_text_corpus,
n = 5,
main = "Tópicos"
)
Com isso, observa-se que todo o conjunto de tweets coletados podem ser relacionados aos 16 tópicos por meio das 5 principais palavras mostradas para cada um deles acima.
Resumidamente, a análise de sentimentos é uma técnica de processamento de linguagem natural (PLN) que utiliza algoritmos, técnicas e modelos matemáticos para extrair informações e insights a partir de textos publicados em sistemas, documentos, redes sociais, dentre outras fontes, no intuito de identificar e classificar emoções presentes nos textos.
Esses sentimentos identificados são utilizados pelas empresas e organizações para entender a opinião e percepção das pessoas em relação a um produto, serviço, marca, ou até mesmo a atitudes e eventos desencadeados por uma entidade ou pessoa. Sua aplicabilidade é bastante extensa e compreende nichos como marketing, política, saúde, dentre outros.
Como exemplo, os sentimentos identificados nas manifestações de usuários sobre determinada ação de governo, pode gerar insumos para melhorias de políticas públicas.
O Syuzhet foi utilizado neste trabalho, pois apresenta recursos robustos para acesso e extração de sentimentos desenvolvidos pelo NPL group da Universidade de Stanford.
sentimentos_nrc <- get_nrc_sentiment(df_SISU$full_text, language = "portuguese", lowercase = TRUE)
## Warning: `spread_()` was deprecated in tidyr 1.2.0.
## ℹ Please use `spread()` instead.
## ℹ The deprecated feature was likely used in the syuzhet package.
## Please report the issue to the authors.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
Separando o conjunto de tweets que apresentam sentimento de raiva
sentRaiva <- which(sentimentos_nrc$anger > 0)
tail(df_SISU$full_text[sentRaiva])
## [1] "caralho de sisu n aguento mais a nota de corte subindo"
## [2] "Meu deus eu não aguento mais a ansiedade desse carai de sisu, eu só queria uma resposta logo independente da que seja"
## [3] "Acho que o sisu hoje vai bater recorde nessa brincadeirinha de calcular as notas de cortes https://t.co/BidseAgERd"
## [4] "Sisu vai me matar ainda hoje"
## [5] "obg sisu, você colabora muito com a minha ansiedade"
## [6] "Se o sisu demorar pra mostrar a parcial da nota corte de propósito eu juro que vou me vingar nos índices de sui"
Separando o conjunto de tweets que apresentam sentimento de satisfação
sentSatisfacao <- which(sentimentos_nrc$joy > 0)
tail(df_SISU$full_text[sentSatisfacao])
## [1] "FECHA SISU PELO AMOR DE DEUS"
## [2] "boa sorte a todos que tiveram uma ótima paciência com o sisu, se vocês aguentaram a lerdeza do site por 9 dias, vocês estão prontos pra tudo"
## [3] "sei quem esteve cmg ou teve o mínimo de consideração de me desejar boa sorte nesse Sisu 😊"
## [4] "sisu amor atualize viu, tenho que ir dormir pois trabalho amanhã de manhã cedo"
## [5] "Atualiza sisu pelo amor de Deus"
## [6] "Vamo sisu atualiza o site aí camarada https://t.co/Mg79oYEMYe"
# Traduzindo o vetor de sentimentos
sentimentos_traduzidos <- c("raiva", "antecipação", "aversão", "medo", "satisfação", "tristeza", "surpreza", "confiança", "negativo", "positivo")
# Armazenando no vetor numérico os valores calculados dos respectivos sentimentos
valor <- colSums(sentimentos_nrc[,])
# Convertendo em um dataframe para manupulação
valor_df <- data.frame(valor)
# Realizando a transposição do dataframe
valor_df2 <- cbind(sentimento = row.names(valor_df), valor_df, row.names = NULL)
# Realizando a associação dos valores aos títulos traduzidos
valor_df2$sentimento <- sentimentos_traduzidos
Análise da polaridade extraída dos tweets durante o período de finalização das inscrições e divulgação dos resultados do SISU (Sistema de Seleção Unificada).
# Gráfico com as polaridades
ggplot(valor_df2[9:10,]) +
aes(x = sentimento, y = valor, color = sentimento, fill = sentimento, label = valor) +
geom_col() +
geom_text(nudge_y = 25) +
labs(
x = "Polaridade",
y = "Valor",
title = "Polaridade dos Tweets",
subtitle = "Período da coleta: 24/02/2023 à 04/03/2023",
caption = "Twitter API v2"
) +
ggthemes::theme_hc() +
theme(
plot.title = element_text(size = 16L, face = "bold"),
axis.text.x = element_text(size = 11), axis.title.x = element_text(face = "bold"),
axis.text.y = element_text(size =11), axis.title.y = element_text(face = "bold")
)
Obs.: Foram desconsiderados aqueles com polaridade neutra, ou seja, os índices de positividade e negatividae se anulavam.
Análise dos sentimentos extraídos dos tweets durante o período de finalização das inscrições e divulgação dos resultados do SISU (Sistema de Seleção Unificada).
# Gráfico com os sentimentos
ggplot(valor_df2[1:8,]) +
aes(x = sentimento, y = valor, color = sentimento, fill = sentimento, label = valor) +
geom_col() +
geom_text(nudge_y = 15) +
labs(
x = "Sentimento",
y = "Valor",
title = "Sentimento dos Tweets",
subtitle = "Período da coleta: 24/02/2023 à 04/03/2023",
caption = "Twitter API v2"
) +
theme(
plot.title = element_text(size = 16L, face = "bold"),
axis.text.x = element_text(size = 11), axis.title.x = element_text(face = "bold"),
axis.text.y = element_text(size =11), axis.title.y = element_text(face = "bold")
)
Os dados analisados neste trabalho apresentaram um panorama com as características da interação do público em um recorte específico de tempo compatível com um período bem sensível do processo do SISU. As características apuradas inicialmente neste trabalho visam indicar o meio de interação, o período, a localidade, além de outros aspectos como o poder de propagação e popularidade das manifestações feitas pelos usuários do Twitter. Em uma segunda parte, apresento a modelagem de tópicos e a análise de sentimentos como mecanismos para o entendimento da essência do conteúdo coletado.
Sem fazer nenhum juízo de valor sobre os resultados apresentados, os recursos e métodos aplicados aqui neste trabalho demonstram a potencialidade desse segmento da análise de redes sociais, que evidentemente desempenha um papel crucial na compreensão da percepção e preferência das pessoas, refletindo a forma em que elas interagem socialmente no meio digital, apresentando seus interesses, suas opiniões e traduzindo seu comportamento.
O vocabulário Brasileiro apresenta peculiaridades que torna a análise de sentimentos mais complexa do que a análise de sentimentos em língua inglesa, além do volume de léxicos disponíveis para trabalho em português ser muito inferior ao inglês. O tom irônico, jocoso, bem como a presença massiva de emojis (representações gráficas de sentimentos), também são um desafio para o trabalho de preparação e processamento da linguagem natural. Pesquisas na área estão avançando e novos recursos de PLN estão surgindo, aumentando o arcabouço instrumental para os projetos na área.
Silge, J., & Robinson, D. (2017). Text Mining with R: A Tidy Approach (1st ed.). O’Reilly Media. https://www.tidytextmining.com/index.html
Yihui Xie, J. J. Allaire, Garrett Grolemund (2023).R Markdown: The Definitive Guide. https://bookdown.org/yihui/rmarkdown/
Roger D. Peng (2022). R Programming for Data Science https://bookdown.org/rdpeng/rprogdatascience/
Wickham H., & Grolemund G. (2017). R for Data Science. (2nd ed.). O’Reilly Media. https://r4ds.had.co.nz/index.html
Saif M. Mohammad - Website. 2023. Sentiment and Emotion Lexicons. http://saifmohammad.com/WebPages/lexicons.html
Twitter Community. 2023. https://twittercommunity.com/