Tag

mineração de texto Archives - Análise Macro

Detecção de plágio com NLP no R: o caso Decotelli

By | Data Science

Natural Language Processing (NLP) é um tópico interessante que vem sendo bastante explorado no mundo da ciência de dados. Aqui no blog já exploramos, por exemplo, a mineração de textos (text mining) das atas do COPOM (códigos disponíveis em R). No texto de hoje exploraremos mais o assunto aplicado ao problema de detecção de plágio entre dois ou mais textos, usando como exemplo um evento repercutido no ano passado envolvendo o ex-Ministro da Educação, Carlos Decotelli.

Para contextualizar, e para quem não teve conhecimento do evento em questão, em junho de 2020 houveram apontamentos de que a dissertação de mestrado do então Ministro da Educação, Carlos Decotelli, continha indícios de plágio. Muitas notícias foram vinculadas na época e após o “escândalo” o Ministro entregou uma carta de demissão.

Dados os atores envolvidos e repercussão deste evento, é natural aos cientistas de dados se perguntarem se o plágio pode ser verificado de forma mais empírica. Neste texto faremos um exercício aplicado de R para tentar responder essa questão, usando o pacote textreuse.

O pacote textreuse serve muito bem o propósito deste exercício, pois fornece um conjunto de funções para medir a similaridade entre documentos de texto e detectar passagens que foram reutilizadas. Para realizar essa mensuração existem algumas possibilidades, entre elas o Índice de Jaccard - também conhecido como coeficiente de similaridade de Jaccard - que oferece uma estatística para medir a similaridade entre conjuntos amostrais, expresso conforme abaixo:

O Índice de Jaccard é muito simples quando queremos identificar similaridade entre textos, sendo os coeficientes expressos como números entre 0 e 1. Dessa forma, quanto maior o número, mais semelhantes são os textos (conjuntos amostrais).

Agora vamos à prática!

Pacotes

Para reproduzir os código de R deste exercício, certifique de que tenha os seguintes pacotes instalados/carregados:


# Carregar pacotes
library(textreuse) # CRAN v0.1.5
library(magrittr) # CRAN v2.0.1
library(dplyr) # CRAN v1.0.7
library(stringr) # CRAN v1.4.0
library(ggplot2) # CRAN v3.3.5
library(ggtext) # CRAN v0.1.1
library(scales) # CRAN v1.1.1

Dados

Os dados utilizados são os documentos que queremos comparar a similaridade. O principal documento é a dissertação de mestrado de Carlos Decotelli, e os demais são documentos indicados por Thomas Conti de onde os trechos teriam sido copiados. Nos links à seguir você pode baixar os originais de cada documento:

  1. Dissertação Carlos Decotelli (2008): https://bibliotecadigital.fgv.br/dspace/handle/10438/3726
  2. Relatório CVM (2007) - The Internet Archive: http://web.archive.org/web/20200629224030/https://ri.banrisul.com.br/banrisul/web/arquivos/Banrisul_DFP2007b_port.pdf
  3. “A Abordagem Institucional na Administração” de Alexandre Rosa e Cláudia Coser (2003): https://twiki.ufba.br/twiki/bin/viewfile/PROGESP/ItemAcervo241?rev=&filename=Abordagem_institucional_na_administracao.pdf
  4. “Mudança e estratégia nas organizações” de Clóvis L. Machado-da-Silva et al. (1999): http://anpad.org.br/admin/pdf/enanpad1998-orgest-26.pdf

Para usar esses documentos no pacote textreuse precisamos transformá-los para o formato de texto (extensão .txt). Existem formas de fazer isso por meio de pacotes no R, no entanto, para simplificar eu usei ferramentas online que fazem esse serviço, tomando o cuidado de remover as páginas pré ou pós textuais (capa, sumário, referências, etc.) antes da conversão, já que o objetivo é comparar os textos do corpo dos documentos propriamente ditos.

Ferramenta online para converter de PDF para TXT: https://pdftotext.com/pt/

Após esse tratamento/conversão teremos 4 arquivos de texto para realizar a análise. Disponibilizei os arquivos neste link para quem se interessar em reproduzir. Na sequência importamos os arquivos para o R:


# Criar pasta temporária
temp_folder <- tempdir()

# Baixar arquivos txt (zipados)
download.file(
url = "https://analisemacro.com.br/download/34322/",
destfile = paste0(temp_folder, "\\decotelli.zip"),
mode = "wb" # talvez você precise mudar esse argumento, dependendo do seu sistema
)

# Descompactar
unzip(
zipfile = paste0(temp_folder, "\\decotelli.zip"),
exdir = paste0(temp_folder, "txts")
)

# Listar arquivos na pasta
list.files(paste0(temp_folder, "txts"), pattern = ".txt")

# [1] "clovis-p ginas-1-13.txt" "cvm-p ginas-23-62.txt"
# [3] "decotelli-p ginas-9-83.txt" "rosa-p ginas-1-13.txt"

# Importar arquivos (se arquivos não tiverem marcador "End of Line" será gerado um warning
corpus <- textreuse::TextReuseCorpus( # cria uma estrutura Corpus (coleção de documentos)
dir = paste0(temp_folder, "txts"), # caminho para pasta com arquivos txt
tokenizer = tokenize_ngrams, # função para separar textos em tokens
progress = FALSE # não mostrar barra de progresso
)

Se você tiver dificuldades em importar os dados, é possível explorar os exemplos das Vignettes do pacote também.

Agora vamos inspecionar o que temos no objeto corpus:


# Exibir resultado
corpus

# TextReuseCorpus
# Number of documents: 4
# hash_func : hash_string
# tokenizer : tokenize_ngrams

Verificando a classe do objeto:


# Classe
class(corpus)

# [1] "TextReuseCorpus" "Corpus"

Quais documentos/textos temos no objeto corpus?


# Documentos importados
names(corpus)

# [1] "clovis-p ginas-1-13" "cvm-p ginas-23-62" "decotelli-p ginas-9-83"
# [4] "rosa-p ginas-1-13"

E, similarmente, podemos acessar os elementos do objeto:


# Acessando texto da dissertação de mestrado
corpus[[3]]

# TextReuseTextDocument
# file : C:\Users\ferna\AppData\Local\Temp\Rtmp6rA1wGtxts/decotelli-p ginas-9-83.txt
# hash_func : hash_string
# id : decotelli-p ginas-9-83
# minhash_func :
# tokenizer : tokenize_ngrams
# content : 1 – INTRODUÇÃO
#
# A moeda é sem dúvida a principal invenção do homem na área das ciências sociais. Criada para
# facilitar as trocas, viabilizando a especialização do trabalho, a moeda é de s

Perceba que há problemas de encoding, que para este exercício iremos simplesmente ignorar.

Índice de Jaccard

Com os dados preparados, podemos obter a estatística do Índice de Jaccard para medir a similaridade entre os textos, usando a função jaccard_similarity(), que compara um texto A com um texto B. Queremos fazer isso já para os 4, portanto usamos a função auxiliar pairwise_compare() que reporta a estatística de cada documento comparando a todos os demais (todos os pares possíveis), dentro do objeto corpus:


# Coeficientes de similaridade de Jaccard
comparisons <- textreuse::pairwise_compare(
corpus = corpus, # objeto com textos importados
f = jaccard_similarity # função para comparar os textos
)

# Resultado
comparisons

# clovis-p ginas-1-13 cvm-p ginas-23-62
# clovis-p ginas-1-13 NA 0.01308411
# cvm-p ginas-23-62 NA NA
# decotelli-p ginas-9-83 NA NA
# rosa-p ginas-1-13 NA NA
# decotelli-p ginas-9-83 rosa-p ginas-1-13
# clovis-p ginas-1-13 0.04087225 0.02439457
# cvm-p ginas-23-62 0.19385437 0.01169428
# decotelli-p ginas-9-83 NA 0.03302505
# rosa-p ginas-1-13 NA NA

O resultado de destaque mostra que o texto da dissertação de mestrado de Carlos Decotelli (decotelli-p ginas-9-83) comparado com o texto do relatório da CVM (cvm-p ginas-23-62) obteve um coeficiente de 0.19 (o valor máximo vai até 1), bem superior aos demais. Esse resultado dialoga com o que foi indicado por Thomas Conti em junho de 2020.

Vale frisar que existem outros métodos de comparação e funcionalidades que o pacote oferece, consulte a documentação.

Podemos facilmente converter o resultado, que é uma matriz, para um data frame com a função pairwise_candidates():


# Converter para data frame
comparisons_df <- textreuse::pairwise_candidates(comparisons)
comparisons_df

# A tibble: 6 x 3
# a b score
# * <chr> <chr> <dbl>
# 1 clovis-p ginas-1-13 cvm-p ginas-23-62 0.0131
# 2 clovis-p ginas-1-13 decotelli-p ginas-9-83 0.0409
# 3 clovis-p ginas-1-13 rosa-p ginas-1-13 0.0244
# 4 cvm-p ginas-23-62 decotelli-p ginas-9-83 0.194
# 5 cvm-p ginas-23-62 rosa-p ginas-1-13 0.0117
# 6 decotelli-p ginas-9-83 rosa-p ginas-1-13 0.0330

Por fim, vamos criar uma visualização dos resultados:


comparisons_df %>%
dplyr::filter(
stringr::str_detect(string = a, "decotelli") |
stringr::str_detect(string = b, "decotelli")
) %>%
dplyr::mutate(a = c("Machado-da-Silva et al.", "Relatório CVM", "Rosa & Coser")) %>%
ggplot2::ggplot(ggplot2::aes(x = reorder(a, score), y = score, fill = a)) +
ggplot2::geom_col() +
ggplot2::geom_text(
ggplot2::aes(
label = format(round(score, 2), decimal.mark = ",", )
),
position = ggplot2::position_stack(vjust = .5),
color = "white",
fontface = "bold",
size = 5
) +
ggplot2::scale_y_continuous(labels = scales::number_format(decimal.mark = ",", accuracy = 0.01)) +
ggplot2::scale_fill_manual(NULL, values = c("#b22200", "#282f6b", "#224f20")) +
ggplot2::theme_light() +
ggplot2::labs(
title = "Caso Decotelli: houve plágio?",
subtitle = "Coeficiente de similaridade com dissertação de mestrado de Decotelli (2008)",
y = "Índice de Jaccard",
x = NULL,
caption = "<span style = 'font-size:10pt'>**Dados** dos textos originais disponíveis em *Decotelli (2008)*: bit.ly/3pk8oOe*, Rosa & Coser (2003)*: bit.ly/3pqGQa4, *Machado-da-Silva et al. (1999)*: bit.ly/2Xxsjh2 e *Relatório CVM (2007)*: bit.ly/3aW5Kpz<br>**Elaboração**: analisemacro.com.br</span>"
) +
ggplot2::theme(
legend.position = "none",
axis.text = ggtext::element_markdown(size = 12, face = "bold"),
axis.title = ggtext::element_markdown(size = 12, face = "bold"),
plot.title = ggtext::element_markdown(size = 30, face = "bold", colour = "#282f6b"),
plot.subtitle = ggtext::element_markdown(size = 16),
plot.caption = ggtext::element_textbox_simple(
size = 12,
lineheight = 1,
margin = ggplot2::margin(10, 5.5, 10, 5.5)
)
)

 

 

O que achou? O pacote textreuse é bastante intuitivo e de fácil uso, de modo que até quem não tem conhecimento aprofundado de estatística, NLP, text mining, etc. consegue fazer análises simples como essa. Espero que o exercício tenha ajudado!

Referências

Recomenda-se consultar a documentação dos pacotes utilizados para entendimento aprofundado sobre as funcionalidades.

  • Jure Leskovec, Anand Rajaraman, and Jeff Ullman, Mining of Massive Datasets (Cambridge University Press, 2011).

 

Análise das Atas do COPOM com text mining

By | Data Science, Política Monetária

Introdução

Mineração de texto, ou text mining, é um tópico muito interessante, pois há um potencial enorme de aplicações para obtenção de insights através dessa técnica envolvendo análise textual. Com a finalidade de demonstrar seu uso, neste post faremos uma breve e introdutória análise das atas do Comitê de Política Monetária - COPOM usando text mining com o auxílio do pacote tidytext.

As atas do COPOM são um caminho natural para qualquer economista em busca de uma fonte de dados para exercitar o text mining, já que a autoridade monetária realiza ajustes (mudanças) no texto a cada reunião, realizada a cada 45 dias. As atas são disponibilizadas publicamente neste link em arquivos PDFs (atualmente há 238 reuniões realizadas). Inicialmente vamos pegar o arquivo da última reunião publicada e importar os dados para o R, fazendo tratamentos e posterior análise. Por fim, vamos coletar os dados de todo o histórico de atas para refazer a análise de forma a incorporar o componente temporal.

Um ponto importante é que utilizaremos as versões em inglês das atas, apesar de o COPOM disponibilizar também em português. Isso se deve ao fato de que as ferramentas aqui utilizadas, para aplicar a técnica de text mining, funcionarem melhor com textos na língua inglesa.

Pacotes

Os pacotes utilizados neste exercício podem ser instalados/carregados com o gerenciador pacman, todos provenientes do CRAN:


# Instalar/carregar pacotes
if(!require("pacman")) install.packages("pacman")
pacman::p_load(
"tidytext",
"pdftools",
"stopwords",
"textdata",
"dplyr",
"tidyr",
"lubridate",
"magrittr",
"knitr",
"ggplot2",
"ggthemes",
"jsonlite",
"purrr",
"stringr",
"scales",
"forcats"
)

Text mining de uma ata do COPOM

Vamos importar a última ata do COPOM para o R. Primeiro criamos um objeto para armazenar o link para o arquivo e, na sequência, usamos a função pdf_text do pacote pdftools para ler cada página do arquivo PDF e transformar os dados em um vetor de caracteres de tamanho igual ao número de páginas.

# URL da última ata do COPOM
www <- "https://www.bcb.gov.br/content/copom/copomminutes/MINUTES%20238.pdf"
# Ler arquivo PDF convertendo para caracter
raw_copom_last <- pdftools::pdf_text(www)

Tratamento de dados

Um primeiro olhar sobre os dados mostrará que há uma série de caracteres especiais "\n" e "\r" que indicam quebras de linhas. Portanto, vamos tratar de forma a obter um objeto tibble com uma coluna (text) com os dados do texto de cada página da ata, outra coluna (meeting) indicando o mês da reunião e a última coluna (page) que informará a página referente ao texto.

# Tratamento de dados
copom_last_clean <- dplyr::tibble(
text = unlist(strsplit(raw_copom_last, "\r"))
) %>% 
dplyr::mutate(
meeting = "May 2021", 
page = dplyr::row_number(),
text = gsub("\n", "", text)
)

Text mining

Agora estamos com os dados quase prontos para uma análise! O que faremos agora é começar a aplicar algumas das técnicas de text mining provenientes do livro Text Mining with R escrito pela Julia Silge e David Robinson.

O primeiro passo é "tokenizar" nossos dados, de forma a obter "unidades do texto" que servirão para a análise. Isso facilitará para realizarmos a contagem de palavras frequentes, filtrar palavras em específico, etc. O processo de "tokenizar" é definido no livro como:

"A token is a meaningful unit of text, most often a word, that we are interested in using for further analysis, and tokenization is the process of splitting text into tokens."

Para utilizar a técnica, usamos a função unnest_tokens do pacote tidytext e na sequência realizamos a contagem das palavras encontradas:

# Text mining - Criar tokens
copom_last <- copom_last_clean %>% 
tidytext::unnest_tokens(word, text)
# Contar palavras
copom_last %>%
dplyr::count(word, sort = TRUE) %>% 
dplyr::slice_head(n = 6) %>% 
knitr::kable()


Como podemos observar, as palavras mais frequentes são palavras comuns como "the", "of", "in", etc. que não servirão muito para nossa análise. Essas palavras são chamadas de "stop words" no mundo do text mining. Vamos fazer uma tratativa para remover essas palavras usando como base o objeto stop_words proveniente do pacote tidytext e, antes, removemos também números que foram apontados como "palavras":

copom_last_sw <- copom_last %>%
# Remover palavras comuns (stop words)
dplyr::anti_join(stop_words)%>%
# Remover números
dplyr::mutate(word = gsub("[^A-Za-z ]", "", word)) %>%
# Contar palavras
dplyr::count(word, sort = TRUE) %>% 
dplyr::filter(word != "")
copom_last_sw %>% 
dplyr::slice_head(n = 6) %>% 
knitr::kable()

Agora sim temos os dados prontos para uma análise de sentimento!

Análise de sentimento

Nosso foco agora é usar ferramentas de análise para tirar informação desses dados, ou seja, queremos saber o que as palavras das atas do COPOM podem indicar com base na tentativa de apontarmos um "score" para cada uma delas, usando datasets e bibliotecas de "sentimento do texto" do tidytext para isso.

Vamos ver quais são as palavras negativas e positivas usadas com mais frequência. A função get_sentiments do tidytext fará isso pra gente, bastando apontar um lexicon, que pode ser "bing", "afinn", "loughran" ou "nrc".

# Obter análise de sentimento das palavras com base em uma biblioteca
copom_last_sw %>%
dplyr::inner_join(tidytext::get_sentiments("bing")) %>% 
dplyr::slice_head(n = 10) %>% 
knitr::kable()

Como resultado, "risks" é a palavra negativa que aparece mais vezes (13 no total) nessa ata do COPOM, e "recovery", uma palavra positiva, é usada 4 vezes no total. Um segundo olhar sobre esse resultado pode levantar uma suspeita, pois "recovery" é apontada como uma palavra positiva (recuperação), no entanto, é razoável supor que pode estar também associada com uma situação anterior negativa. Isso é um possível problema da análise que o leitor pode investigar.
Agora vamos explorar a análise graficamente:

# Análise de sentimento final da ata de Maio/2021
copom_sentiment <- copom_last %>%
dplyr::inner_join(tidytext::get_sentiments("bing")) %>%
dplyr::count(meeting, page, sentiment) %>%
tidyr::pivot_wider(
id_cols = c(meeting, page),
names_from = sentiment, 
values_from = n,
values_fill = 0
) %>%
dplyr::mutate(sentiment = positive - negative)
# Gerar gráfico
copom_sentiment %>% 
ggplot2::ggplot(ggplot2::aes(page, sentiment, fill = sentiment > 0)) +
ggplot2::geom_col(show.legend = FALSE) +
ggplot2::scale_fill_manual(values = c("#b22200", "#282f6b")) +
ggplot2::labs(
x = "Página da ata",
y = "Sentimento",
title = "Análise de sentimento da Ata do COPOM - Maio/2021",
subtitle = "Bing lexicon",
caption = paste0("Elaboração: analisemacro.com.br\nDados: ", www)
)

O gráfico conta uma história interessante. O texto começou negativo na página 3 (sobre atualização de conjuntura, cenário e riscos), mas depois foi "neutro" na página 4 (sentimento positivo - negativo = 0) - onde é discutido a condução da política monetária e -, por fim, na última página o texto fica positivo com a decisão da reunião do COPOM.

Vale enfatizar que a primeira página, referente a capa, não é considerada na análise. Já a página 2, da contracapa, possui igual número de palavras negativas e positivas.

Text mining com todas as atas do COPOM

Agora vamos aplicar a técnica apresentada acima para um conjunto maior de dados, desta vez vamos pegar todas as atas disponíveis no site do Banco Central do Brasil - BCB através deste link.

Vamos expandir nossa análise capturando o texto de cada Ata do COPOM desde sua 42ª reunião, totalizando 198 atas para nossa análise. Faremos a comparação da frequência relativa de palavras e tópicos e veremos como o sentimento (conforme explorado acima) varia entre os relatórios.

Dados

Infelizmente, os links para as atas em PDF seguem um padrão irregular, mas felizmente para você, preparei um web-scrapping que automatizará o processo de captura dos links e dados. O código a seguir coletará os arquivos PDF e os deixará prontos para a mineração com o tidytext.

# URL para página com JSON dos links das atas
www_all <- "https://www.bcb.gov.br/api/servico/sitebcb/copomminutes/ultimas?quantidade=2000&filtro="
# Raspagem de dados
raw_copom <- jsonlite::fromJSON(www_all)[["conteudo"]] %>% 
dplyr::as_tibble() %>% 
dplyr::select(meeting = "Titulo", url = "Url") %>% 
dplyr::mutate(url = paste0("https://www.bcb.gov.br", url)) %>% 
dplyr::mutate(text = purrr::map(url, pdftools::pdf_text))
# Tratamento de dados
copom_clean <- raw_copom %>% 
tidyr::unnest(text) %>% 
dplyr::filter(!meeting == "Changes in Copom meetings") %>% 
dplyr::group_by(meeting) %>%
dplyr::mutate(
page = dplyr::row_number(),
text = strsplit(text, "\r") %>% gsub("\n", "", .),
meeting = stringr::str_sub(meeting, 1, 3) %>% 
stringr::str_remove("[:alpha:]") %>% 
as.numeric()
) %>%
dplyr::ungroup() %>% 
tidyr::unnest(text) %>% 
dplyr::arrange(meeting)

Estatística básica dos dados

Vamos ver o que conseguimos obter calculando algumas estatísticas básicas dos textos.

Número de palavras por ata

# Frequência de palavras por ata
copom_words <- copom_clean %>%
tidytext::unnest_tokens(word, text) %>%
dplyr::count(meeting, word, sort = TRUE) %>%
dplyr::ungroup()
# Total de palavras por ata
total_words <- copom_words %>% 
dplyr::group_by(meeting) %>% 
dplyr::summarize(total = sum(n))
# Gerar gráfico
total_words %>% 
ggplot2::ggplot(ggplot2::aes(x = meeting, y = total)) +
ggplot2::geom_line(color = "#282f6b", size = 0.8)+
ggplot2::geom_point(
shape = 21, 
fill = "white", 
color = "#282f6b", 
size = 1.6, 
stroke = 1.1
) +
ggplot2::scale_y_continuous(labels = scales::number_format()) +
ggplot2::labs(
x = "Reunião (nº da ata)", 
y = "Número de palavras",
title = "Número de palavras nas atas do COPOM",
subtitle = "42ª até 238ª reunião",
caption = "Elaboração: analisemacro.com.br\nDados: BCB"
)

Percebe-se algumas mudanças ao longo do tempo, especialmente entre as reuniões número 180 e 181, onde houve remoção considerável de seções do comunicado na gestão Tombini. No mesmo sentido, de redução do total de palavras por comunicado, o início da gestão de Illan Goldfajn marcou mudança no layout e estrutura das seções da ata, alterações essas que permanecem em vigor até hoje.

A redução do tamanho dos comunicados, de forma geral, é certamente um ponto interessante que merece investigação em se tratando de qualidade de comunicação da política monetária.

Sobre o que os diretores discutiram nas reuniões?

Vamos compilar uma lista das palavras usadas com maior frequência em cada ata. Como antes, vamos omitir as "stop words".

# Palavras por ata
copom_text <- copom_clean %>% 
dplyr::select(meeting, page, text) %>%
tidytext::unnest_tokens(word, text)
# Gerar gráfico
copom_text %>% 
dplyr::mutate(word = gsub("[^A-Za-z ]", "", word)) %>%
dplyr::filter(word != "") %>%
dplyr::anti_join(stop_words) %>%
dplyr::group_by(meeting) %>%
dplyr::count(word, sort = TRUE) %>% 
dplyr::mutate(rank = dplyr::row_number()) %>%
dplyr::ungroup() %>% 
dplyr::arrange(rank, meeting) %>%
dplyr::filter(rank < 9, meeting > 230) %>% 
ggplot2::ggplot(ggplot2::aes(y = n, x = forcats::fct_reorder(word, n))) +
ggplot2::geom_col(fill = "#282f6b") +
ggplot2::facet_wrap(~meeting, scales = "free", ncol = 4) +
ggplot2::coord_flip() +
ggplot2::labs(
x = "",
y = "",
title = "Palavras mais frequentes nas atas do COPOM",
subtitle = "Excluídas palavras comuns (stop words) e números.",
caption = "Elaboração: analisemacro.com.br\nDados: BCB"
)


Como esperado, muitas discussões sobre inflação. Vamos tentar encontrar algo mais informativo com esses dados.

Conforme Silge e Robinson, podemos usar a função bind_tf_idf para juntar a frequência do termo (tf) e a frequência inversa do documento (idf) ao nosso conjunto de dados. Essa estatística diminuirá o peso em palavras muito comuns e aumentará o peso em palavras que só aparecem em algumas atas. Em essência, extrairemos o que há de especial em cada ata. As atas do COPOM sempre falarão muito sobre inflação e juros, e a estatística "tf-idf" pode nos dizer algo sobre o que é diferente em cada ata.

Também limparemos alguns termos adicionais que o pdftools captou (abreviações e palavras estranhas ou fragmentadas), aumentando nossa lista de "stop words".

# Stop words personalizadas
custom_stop_words <- dplyr::bind_rows(
dplyr::tibble(
word = c(
tolower(month.abb), 
tolower(month.name),
"one","two","three","four","five","six","seven","eight","nine","ten",
"eleven","twelve", "wkh", "ri", "lq", "month", "wr", "dqg",
"hdu", "jurzwk", "zlwk", "zlwk", "hfhpehu", "dqxdu", "kh", "sulfh", "dv",
"kh", "prqwk", "hdu", "shulrg", "dv", "jurzwk", "wkdw", "zdv", "iru", "dw",
"wkdw", "jrrgv", "xqh", "eloolrq", "eloolrq", "iluvw", "dq", "frqvxphu", 
"prqwk", "udwh", "sulo", "rq", "txduwhu", "vhfwru", "pandemic", 
"dffxpxodwhg", "hg", "kdyh", "sdqghg", "sulfhv", "rq", "sdqvlrq", 
"percent", "forvhg", "frpsduhg", "lqgh", "ryhpehu", "wklv", "kdv", "prqwkv",
"bcbgovbr", "banco", "head"
), 
lexicon = c("custom")
),
stop_words
)
# Palavras por ata
copom_text_refined <- copom_text %>% 
dplyr::mutate(word = gsub("[^A-Za-z ]", "", word)) %>%
dplyr::filter(word != "") %>%
dplyr::group_by(meeting) %>% 
dplyr::count(word, sort = TRUE) %>% 
tidytext::bind_tf_idf(word, meeting, n) %>% 
dplyr::arrange(desc(tf_idf))
# Gerar gráfico
copom_text_refined %>% 
dplyr::anti_join(custom_stop_words, by = "word") %>%
mutate(word = factor(word, levels = rev(unique(word)))) %>% 
dplyr::group_by(meeting) %>%
dplyr::mutate(id = dplyr::row_number()) %>%
dplyr::ungroup() %>% 
dplyr::filter(id < 9, meeting > 230) %>% 
ggplot2::ggplot(ggplot2::aes(y = tf_idf, x = word, fill = meeting)) +
ggplot2::geom_col(show.legend = FALSE) +
ggplot2::facet_wrap(~meeting, scales = "free", ncol = 4) +
ggplot2::coord_flip() +
ggplot2::labs(
x = "",
y = "tf-idf",
title = "Palavras mais distintas nas atas do COPOM",
subtitle = "Estatística tf-idf do tidytext",
caption = "Elaboração: analisemacro.com.br\nDados: BCB"
) +
ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1))

Esse gráfico já mostram uma história interessante dessa amostra que escolhemos. Podemos observar preocupações com o BREXIT, reformas e agenda econômica, o surgimento do termo "covid" a partir da ata nº 231 e subsequente adoção do forward guidance e, por fim, também como destaque, a mudança da política para uma "normalização parcial" (termos "partial" e "normalization").

Comparando o sentimento entre as atas
Como o sentimento variou entre as atas? Vamos usar a abordagem que usamos no início para a ata de maio/2021 e aplicá-la a cada ata.

# Análise de sentimento das atas
copom_sentiment_all <- copom_text %>%
dplyr::anti_join(stop_words) %>%
dplyr::inner_join(tidytext::get_sentiments("bing")) %>%
dplyr::count(meeting, page, sentiment) %>%
tidyr::pivot_wider(
id_cols = c(meeting, page),
names_from = sentiment, 
values_from = n,
values_fill = 0
) %>%
dplyr::mutate(sentiment = positive - negative)
# Gerar gráfico
copom_sentiment_all %>% 
dplyr::filter(meeting > 230) %>% 
ggplot2::ggplot(ggplot2::aes(page, sentiment, fill = sentiment > 0)) +
ggplot2::geom_col(show.legend = FALSE) +
ggplot2::scale_fill_manual(values = c("#b22200", "#282f6b")) +
ggplot2::facet_wrap(~meeting, ncol = 4, scales = "free_x") +
ggplot2::annotate("segment", x = -Inf, xend = Inf, y = -Inf, yend = -Inf)+
ggplot2::annotate("segment", x = -Inf, xend = -Inf, y = -Inf, yend = Inf) +
ggplot2::labs(
x = "Página da ata",
y = "Sentimento",
title = "Análise de sentimento das Ata do COPOM",
subtitle = "Bing lexicon, amostra das últimas 8 atas",
caption = "Elaboração: analisemacro.com.br\nDados: BCB"
)

O resultado mostra que o sentimento mudou consideravelmente no texto das atas da amostra das últimas 8 reuniões, com forte predominância "negativa" desde o advento da pandemia da Covid-19.

Conclusão

Isso é tudo por hoje. Essas técnicas são muito interessantes e promissoras, merecendo exploração aprofundada. Certamente o exercício aqui apresentado possui falhas e pontos de melhoria, mas serve como uma introdução ao tema. Espero que goste, caro leitor!

Possui interesse no tema de política monetária? A Análise Macro disponibiliza o curso de Teoria da Política Monetária dentro da temática de Central Banking. Aproveite!

_____________

Receba diretamente em seu e-mail gratuitamente nossas promoções especiais
e conteúdos exclusivos sobre Análise de Dados!

Assinar Gratuitamente