library(tidyverse)
## Warning: replacing previous import by 'tidyr::%>%' when loading 'broom'
## Warning: replacing previous import by 'tidyr::gather' when loading 'broom'
## Warning: replacing previous import by 'tidyr::spread' when loading 'broom'
## ── Attaching packages ────────────────────────────────── tidyverse 1.2.1 ──
## ✔ ggplot2 2.2.1     ✔ purrr   0.2.4
## ✔ tibble  1.3.4     ✔ dplyr   0.7.4
## ✔ tidyr   0.7.2     ✔ stringr 1.2.0
## ✔ readr   1.1.1     ✔ forcats 0.2.0
## ── Conflicts ───────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
library(here)
## here() starts at /home/marianne-linhares/reclamacoes-do-gf
library(modelr)
library(broom)
## 
## Attaching package: 'broom'
## The following object is masked from 'package:modelr':
## 
##     bootstrap
theme_set(theme_bw())

O governo recebe um grande número de reclamações e poderia gerenciar melhor seus recursos se soubesse o quão insatisfeitas as pessoas estão em relação a um certo órgão. Existem métodos para análise de sentimento aplicáveis, mas não sabemos se esses métodos são eficazes neste contexto.

Pergunta

Quão eficazes são métodos de análise de sentimento baseados em léxicos para estimar a insatisfação manifestada em reclamações recebidas pelo governo federal?

Resumo da nossa abordagem

Para analisar a eficiência de léxicos em determinar insatisfação de uma reclamação do https://www.reclameaqui.com.br em relação ao Governo federal, comparamos o resultado dos mesmos com a avaliação de insatisfação dada por seres humanos.

EDA

Realizamos uma EDA básica que pode ser vista ao longo dos notebooks, buscando responder perguntas como: quantas palavras os léxicos conseguem de fato identificar nas reclamações?

Calculando a insatisfação

Para calcular a insatisfação pelo léxico tentamos várias abordagens:

  1. Somatório simples das palavras identificadas pelo léxico.
  2. Somatório ponderado sendo palavras que são adjetivos consideradas mais importantes (peso 2 vezes maior).
  3. Somatório simples / número de palavras identificadas pelo léxico.
  4. Somatório ponderado / número de palavras identificadas pelo léxico.

A implementação dessas abordagens e alguns outros tratamentos foram feitos no arquivo calcula-sentimentos.Rmd que pode ser acessado no nosso repositório git: https://github.com/mari-linhares/reclamacoes-do-gf.

Como nesta análise não obtivemos grandes diferenças, mantivemos apenas a abordagem 1 e omitimos os resultados das demais análises para simplificação (porém essas podem ser obtidas no repositório git).

Normalizando a saída dos léxicos

Para comparar os resultados dos léxicos, que consistia de números positivos e negativos, com a nota de insatisfação dada por pessoas, que estava numa escala entre 0 e 5, precisamos normalizar os dados dos léxicos. Para tal, utilizamos duas abordagens:

  1. Fazer com que o menor resultado obtido de um léxico equivalesse a 5 e o maior a 0, com os valores intermediários seguindo a mesma proporção

  2. Fazer com que o primeiro quartil e os valores abaixo dele equivalessem a 5, o terceiro quartil e os valores acima dele equivalessem a 0, com os valores intermediários seguindo a mesma proporção.

A primeira abordagem se mostrou mais coerente então a maior parte das análises foi feita com ela.

Regressão

Após conferir e entender alguns dados sobre a resposta dos léxicos, calculamos os erros de cada combinação de léxico e forma de normalizar. Com este erro, geramos regressões lineares com:

  • Erro ← Léxico
  • Erro ← Léxico + N° Palavras
  • Erro ← Léxico + N° Palavras + Órgão
  • Erro ← N° Palavras + N° Adjetivos + Nº de palavras conhecidas pelo léxico (uma regressão para cada léxico)

Conclusão

Ao final, conseguimos observar que o léxico não parece influenciar no erro, mas em alguns modelos o número de palavras que uma reclamação possui e alguns órgãos parecem ter uma certa relação (embora o número de palavras tenha um efeito pouco relevante).

Utilizando uma definição de acerto para os léxicos de que o léxico acertou se o erro foi menor que 1, obtivemos um acerto de 61% para o léxico sent e 58% para o léxico op30.

Carregando os dados

reclamacoes_raw = read_csv(here("data/reclamacoes-raw/reclamacoes-raw.csv"))
## Parsed with column specification:
## cols(
##   orgao = col_character(),
##   link = col_character(),
##   titulo = col_character(),
##   reclamacao = col_character()
## )
avaliacoes_raw = read_csv(here("data/avaliacoes/avaliacoes-20180222.csv"))
## Parsed with column specification:
## cols(
##   mat_avaliador = col_integer(),
##   id_reclamação = col_integer(),
##   insatisfação = col_double()
## )
sentimentos = read_csv(here("data/sentimentos/sentimento.csv"))
## Parsed with column specification:
## cols(
##   id = col_integer(),
##   sentimento_op30 = col_integer(),
##   palavras_op30 = col_integer(),
##   sentimento_sent = col_integer(),
##   palavras_sent = col_integer(),
##   palavras = col_integer(),
##   quant_adj_op30 = col_integer(),
##   quant_adj_sent = col_integer()
## )
# sentimentos_ponderados = read_csv(here("data/sentimentos/sentimento_ponderado.csv"))

# adicionando id a reclamacoes, comprimento da reclamacao e nome do orgao
reclamacoes_raw = reclamacoes_raw %>% 
    mutate(id = 1:n(), 
           comprimento_reclamacao = str_length(reclamacao), 
           nome_orgao = str_split(link, "/") %>% map_chr(~ .[[5]]))

reclamacoes_l tem um formato long em vez de wide (explicado aqui).

avaliacoes = avaliacoes_raw %>% 
    group_by(id_reclamação) %>% 
    summarise(insatisfação = median(insatisfação), 
              avaliadores = n())

reclamacoes = reclamacoes_raw %>% 
    inner_join(avaliacoes, by = c("id" = "id_reclamação")) %>% 
    left_join(sentimentos, by = "id")

reclamacoes_l = reclamacoes %>%  
    # select(-palavras_op30, -palavras_sent) %>% 
    gather(key = "lexico", 
           value = "polaridade", 
           sentimento_op30, sentimento_sent)

Removendo reclamações em que pelo menos um dos léxicos não reconhece nenhuma palavra.

reclamacoes_l = subset(reclamacoes_l, palavras_op30>0 & palavras_sent>0)
reclamacoes = subset(reclamacoes, palavras_op30>0 & palavras_sent>0)

Converte polaridades para escala 0-5

Antes de converter é importante saber alguns valores chaves para cada léxico, como o mínimo, máximo e os quartis.

summary(reclamacoes)
##     orgao               link              titulo         
##  Length:155         Length:155         Length:155        
##  Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character  
##                                                          
##                                                          
##                                                          
##   reclamacao              id        comprimento_reclamacao
##  Length:155         Min.   :  2.0   Min.   : 111          
##  Class :character   1st Qu.: 50.5   1st Qu.: 434          
##  Mode  :character   Median :127.0   Median : 635          
##                     Mean   :133.9   Mean   :1064          
##                     3rd Qu.:205.5   3rd Qu.:1228          
##                     Max.   :318.0   Max.   :9598          
##   nome_orgao         insatisfação    avaliadores    sentimento_op30
##  Length:155         Min.   :0.750   Min.   :1.000   Min.   :-17.0  
##  Class :character   1st Qu.:2.000   1st Qu.:1.000   1st Qu.: -1.0  
##  Mode  :character   Median :3.000   Median :1.000   Median :  1.0  
##                     Mean   :2.876   Mean   :1.594   Mean   :  1.2  
##                     3rd Qu.:4.000   3rd Qu.:2.000   3rd Qu.:  3.0  
##                     Max.   :5.000   Max.   :5.000   Max.   : 28.0  
##  palavras_op30    sentimento_sent    palavras_sent       palavras     
##  Min.   :  1.00   Min.   :-16.0000   Min.   : 1.000   Min.   :  16.0  
##  1st Qu.:  7.00   1st Qu.: -1.5000   1st Qu.: 2.000   1st Qu.:  68.5  
##  Median : 13.00   Median :  0.0000   Median : 3.000   Median : 106.0  
##  Mean   : 21.71   Mean   : -0.1226   Mean   : 4.735   Mean   : 176.6  
##  3rd Qu.: 25.50   3rd Qu.:  1.0000   3rd Qu.: 5.000   3rd Qu.: 200.5  
##  Max.   :190.00   Max.   : 18.0000   Max.   :45.000   Max.   :1593.0  
##  quant_adj_op30   quant_adj_sent  
##  Min.   :  0.00   Min.   : 0.000  
##  1st Qu.:  5.00   1st Qu.: 1.000  
##  Median :  8.00   Median : 2.000  
##  Mean   : 14.41   Mean   : 3.168  
##  3rd Qu.: 17.00   3rd Qu.: 4.000  
##  Max.   :135.00   Max.   :32.000
min_op30 = min(reclamacoes$sentimento_op30)
max_op30 = max(reclamacoes$sentimento_op30)
first_quantile_op30 = quantile(reclamacoes$sentimento_op30, 0.25, na.rm=T)
third_quantile_op30 = quantile(reclamacoes$sentimento_op30, 0.75, na.rm=T)

min_sent = min(reclamacoes$sentimento_sent)
max_sent = max(reclamacoes$sentimento_sent)
first_quantile_sent = quantile(reclamacoes$sentimento_sent, 0.25, na.rm=T)
third_quantile_sent = quantile(reclamacoes$sentimento_sent, 0.75, na.rm=T)

Normalizando da maneira usual entre 0 e 5.

Agora iremos calcular a polaridade_normalizada_usal usando a estratégia mais simples, normalizando os valores entre 0 e 5 de maneira usual.

 polarizeResultUsual <- function(Min, Max){
     range = Max - Min
     arr = (reclamacoes_l$polaridade - Min)/range
     normalized = 5 - ((arr*5) -0)  # invertendo 0-> 5 e 5->0
     return(normalized)
 }

# reclamacoes long
reclamacoes_l$polaridade_normalizada_usual <-        ifelse(reclamacoes_l$lexico=="sentimento_op30", polarizeResultUsual(min_op30, max_op30), polarizeResultUsual(min_sent, max_sent))

# reclamacoes op30
polarizeResultUsual2 <- function(Min, Max){
     range = Max - Min
     arr = (reclamacoes$sentimento_op30 - Min)/range
     normalized = 5 - ((arr*5) -0)  # invertendo 0-> 5 e 5->0
     return(normalized)
 }
reclamacoes$sentimento_op30_usual <- polarizeResultUsual2(min_op30, max_op30)

# reclamacoes sent
polarizeResultUsual3 <- function(Min, Max){
     range = Max - Min
     arr = (reclamacoes$sentimento_sent - Min)/range
     normalized = 5 - ((arr*5) -0)  # invertendo 0-> 5 e 5->0
     return(normalized)
 }
reclamacoes$sentimento_sent_usual <- polarizeResultUsual3(min_sent, max_sent)

Normalizando utilizando os quartis

Como os resultados dos léxicos possuem muitos valores extremos, criaremos a polaridade_normalizada_quartil que ao invés de considerar os mínimos e máximos, considera os primeiros e terceiros quartis.

polarizeResultQuartil <- function(Min, Max){
     range = Max - Min
     arr = (reclamacoes_l$polaridade - Min)/range
     normalized = 5 - ((arr*5) -0)
     normalized <- ifelse(arr<0, 5, normalized)
     normalized <- ifelse(arr>1, 0, normalized)
     return(normalized)
 }

# reclamacoes long
reclamacoes_l$polaridade_normalizada_quartil <- ifelse(reclamacoes_l$lexico=="sentimento_op30", polarizeResultQuartil(first_quantile_op30, third_quantile_op30), polarizeResultQuartil(first_quantile_sent, third_quantile_sent))

# reclamacoes op30
polarizeResultQuartil2 <- function(Min, Max){
     range = Max - Min
     arr = (reclamacoes$sentimento_op30 - Min)/range
     normalized = 5 - ((arr*5) -0)
     normalized <- ifelse(arr<0, 5, normalized)
     normalized <- ifelse(arr>1, 0, normalized)
     return(normalized)
 }

reclamacoes$sentimento_op30_quartil <- polarizeResultQuartil2(first_quantile_op30, third_quantile_op30)

# reclamacoes sent
polarizeResultQuartil3 <- function(Min, Max){
     range = Max - Min
     arr = (reclamacoes$sentimento_sent - Min)/range
     normalized = 5 - ((arr*5) -0)
     normalized <- ifelse(arr<0, 5, normalized)
     normalized <- ifelse(arr>1, 0, normalized)
     return(normalized)
 }

reclamacoes$sentimento_sent_quartil <- polarizeResultQuartil3(first_quantile_sent, third_quantile_sent)

Calculando o erro por reclamação

reclamacoes_l = reclamacoes_l %>%
    mutate(erro_usual = (insatisfação - polaridade_normalizada_usual)**2,
           erro_quartil = (insatisfação - polaridade_normalizada_quartil)**2)

EDA - Verificando os resultados

Comparando os valores dos léxicos

reclamacoes %>% 
    ggplot(aes(x = sentimento_op30, y = sentimento_sent)) + 
    geom_abline(slope = 1, intercept = 0, color = "grey") + 
    geom_count(alpha = .7) 

Comparando os valores dos léxicos normalizados

Normalização usual

reclamacoes %>% 
    ggplot(aes(x = sentimento_op30_usual, y = sentimento_sent_usual)) + 
    geom_abline(slope = 1, intercept = 0, color = "grey") + 
    geom_count(alpha = .7) 

Normalização utilizando os quartis

reclamacoes %>% 
    ggplot(aes(x = sentimento_op30_quartil, y = sentimento_sent_quartil)) + 
    geom_abline(slope = 1, intercept = 0, color = "grey") + 
    geom_count(alpha = .7) 

Erro e comparação das normalizações

Usual

reclamacoes_l %>% 
    ggplot(aes(x = insatisfação, y = polaridade_normalizada_usual, group = insatisfação)) + 
    geom_jitter(alpha = .7)  + 
    facet_wrap(~ lexico)

reclamacoes_l %>% 
    ggplot(aes(x = insatisfação, y = erro_usual, group = insatisfação)) + 
    geom_jitter(alpha = .5)  +
    # geom_boxplo() + 
    facet_wrap(~ lexico)

Quartil

reclamacoes_l %>% 
    ggplot(aes(x = insatisfação, y = polaridade_normalizada_quartil, group = insatisfação)) + 
    geom_jitter(alpha = .7)  + 
    facet_wrap(~ lexico)

reclamacoes_l %>% 
    ggplot(aes(x = insatisfação, y = erro_quartil, group = insatisfação)) + 
    geom_jitter(alpha = .5)  +
    # geom_boxplo() + 
    facet_wrap(~ lexico)

Há relação entre o léxico e a precisão/erro?

Aqui iremos construir vários modelos buscando entender a relação entre o léxico (e outras variáveis independentes) e o erro.

Considerando apenas o léxico

model_erro_lex = lm(erro_usual ~ factor(lexico), data=reclamacoes_l)
tidy(model_erro_lex, conf.int = TRUE, conf.level = 0.95)
glance(model_erro_lex)

Regressão simples foi utilizada para analisar se o léxico tem uma associação significativa com o erro na estimativa de instatisfação da reclamação. Os resultados da regressão indicam que um modelo com os preditores no formato Erro = X1.léxico explicam basicamente nada da variância da variável de resposta (R² = 0.0008613828).

  • léxico: medida como fator (0 ou 1) tem uma relação não significativa com o erro (b = [-0.664799; 0.3888652], IC com 95%). Não podemos afirmar se um léxico é “melhor” que o outro para estes dados usando este modelo.

Pelos resultados do p.value, cujo alpha está bem acima de 0.05, e do intervalo de confiança, que inclue 0, não parece haver relação entre o léxico e o erro.

Considerando o léxico e o número de palavras

model_erro_lex = lm(erro_usual ~ factor(lexico) + palavras, data=reclamacoes_l)
tidy(model_erro_lex, conf.int = TRUE, conf.level = 0.95)
glance(model_erro_lex)

Regressão múltipla foi utilizada para analisar se o léxico e o número de palavras têm uma associação significativa com o erro na estimativa de instatisfação da reclamação. Os resultados da regressão indicam que um modelo com os preditores no formato Erro = X1.léxico + X2.palavras explicam ~18% da variância da variável de resposta (R² = 0.1793).

  • léxico: medida como fator (0 ou 1) tem uma relação não significativa com o erro (b = [-0.616199478 0.340265738], IC com 95%). Não podemos afirmar se um léxico é “melhor” que o outro para estes dados usando este modelo.
  • palavras: medida como unidade tem uma relação significativa com o erro (b = [0.003588207; 0.005864052], IC com 95%). Adicionar mais uma palavra ao texto faz com que o erro cresça ligeiramente, mais especificamente cerca de 0.005.

Considerando o léxico, o número de palavras e o órgão

model_erro_lex = lm(erro_usual ~ factor(lexico) + palavras + factor(nome_orgao), data=reclamacoes_l)
tidy(model_erro_lex, conf.int = TRUE, conf.level = 0.95)
glance(model_erro_lex)

Regressão múltipla foi utilizada para analisar se o léxico, o número de palavras e o órgão têm uma associação significativa com o erro na estimativa de instatisfação da reclamação. Os resultados da regressão indicam que um modelo com os preditores no formato Erro = X1.léxico + X2.palavras + X3.órgão1 + X4.órgão2 + … + XN.órgãoN explicam ~26% da variância da variável de resposta (R² = 0.2638).

  • léxico: medida como fator (0 ou 1) tem uma relação não significativa com o erro (b = [-0.602515183; 0.326581443], IC com 95%). Não podemos afirmar se um léxico é “melhor” que o outro para estes dados usando este modelo.

  • palavras: medida como unidade tem uma relação significativa com o erro (b = [0.003598035; 0.006104555], IC com 95%). Adicionar mais uma palavra ao texto faz com que o erro cresça ligeiramente, mais especificamente cerca de 0.005.

  • órgãos: para cada órgão foi criada uma variável medida como fator (0 ou 1), como são muitas variáveis vamos comentar apenas as que tem relação significativa.
    • inss-ministerio-da-previdencia-social: tem uma relação significativa positiva com o erro (b = [0.251654415; 3.477687808], IC com 95%). Ser uma reclamação relacionada ao inss produz uma mudança de ~1.84 no erro.
    • ministerio-da-saude: tem uma relação significativa positiva com o erro (b = [1.203197276; 4.796453197], IC com 95%). Ser uma reclamação relacionada ao inss produz uma mudança de ~3 no erro.
    • ministerio-do-planejamento: tem uma relação significativa positiva com o erro (b = [0.022871807; 3.102570180], IC com 95%). Ser uma reclamação relacionada ao inss produz uma mudança de ~1.56 no erro.
    • serpro-servico-federal-de-processamento-de-dados: tem uma relação significativa positiva com o erro (b = [0.169012336; 3.337198225], IC com 95%). Ser uma reclamação relacionada ao inss produz uma mudança de ~1.75 no erro.

Considerando o número de palavras, quantidade de palavras conhecidas pelo léxico e quantidade de adjetivos nas reclamações

Para está regressão iremos considerar cada léxico separadamente a fim de estudar os efeitos do número de palavras (palavras), quantidade de palavras conhecidas pelo léxico na reclamação (palavras_) e quantidade de adjetivos nas reclamações de acordo com os léxicos (quant_adj_).

Léxico op30

lexico_op30 = filter(reclamacoes_l, lexico=="sentimento_op30")
model_erro_lex_adj_op30 = lm(erro_usual ~ quant_adj_op30 + palavras + palavras_op30, data=lexico_op30)
tidy(model_erro_lex_adj_op30, conf.int=T, conf.level=0.95)
glance(model_erro_lex_adj_op30)

O resultado explica ~22% da variância da variável de resposta (R²=0.2248284).

  • quant_adj_op30: medida como unidade; tem uma relação não significativa com o erro (b = [-0.205082803; 0.07024863], IC com 95%).
  • palavras: medida como unidade; tem uma relação significativa positiva com o erro (b = [0.003913831; 0.01925003], IC com 95%). De acordo com o modelo a cada palavra adicionada o erro aumenta ~0.012.
  • palavras_op30: medida como unidade; tem uma relação não significativa com o erro (b = [-0.113361807; 0.11256429], IC com 95%).

Léxico sent

lexico_sent = filter(reclamacoes_l, lexico=="sentimento_sent")
model_erro_lex_adj_sent = lm(erro_usual ~  quant_adj_sent + palavras + palavras_sent, data=lexico_sent)
tidy(model_erro_lex_adj_sent, conf.int=T, conf.level=0.95)
glance(model_erro_lex_adj_sent)

O resultado explica ~19% da variância da variável de resposta (R²=0.1994397).

  • quant_adj_sent: diferentemente dos resultados do léxico op30, a quantidade de adjetivos tem uma relação significativa positiva com o erro (b = [0.110148620; 0.526963598], IC com 95%). Temos que a cada adjetivo a mais na reclamação o valor do erro deve aumentar por volta de ~0.31.
  • palavras: medida como unidade; tem uma relação significativa positiva com o erro (b = [0.000176915; 0.006112659], IC com 95%). De acordo com o modelo a cada palavra adicionada o erro aumenta ~0.003.
  • palavras_sent: medida como unidade; tem uma relação significativa negativa com o erro (b = [-0.380057430; -0.025314061], IC com 95%). Esse resultado é interessante pois indica que se adicionarmos uma palavra conhecida (do léxico) a reclamação o erro tende a diminuir em ~0.2 unidades.

Podemos Utilizar os léxicos para avaliar insatisfação humana em uma reclamação?

Para responder tal pergunta, definimos que um léxico “acerta” o nível de insatisfação quando seu erro é igual ou menor que 1. Com isso, calculamos a porcentagem de acerto de cada léxico com forma de normalização.

total_op30 = reclamacoes_l %>%
    filter(lexico == "sentimento_op30") %>%
    count()
hits_usual_op30 = reclamacoes_l %>%
    filter(lexico == "sentimento_op30") %>%
    filter(erro_usual <= 1) %>%
    count()
hits_quart_op30 = reclamacoes_l %>%
    filter(lexico == "sentimento_op30") %>%
    filter(erro_quartil <= 1) %>%
    count()

total_sent = reclamacoes_l %>%
    filter(lexico == "sentimento_sent") %>%
    count()
hits_usual_sent = reclamacoes_l %>%
    filter(lexico == "sentimento_sent") %>%
    filter(erro_usual <= 1) %>%
    count()
hits_quart_sent = reclamacoes_l %>%
    filter(lexico == "sentimento_sent") %>%
    filter(erro_quartil <= 1) %>%
    count()

sprintf("Porcentagem de hits OP30 (Usual): %f", hits_usual_op30/total_op30)
## [1] "Porcentagem de hits OP30 (Usual): 0.580645"
sprintf("Porcentagem de hits OP30 (Quartil): %f", hits_quart_op30/total_op30)
## [1] "Porcentagem de hits OP30 (Quartil): 0.296774"
sprintf("Porcentagem de hits Sent (Usual): %f", hits_usual_sent/total_sent)
## [1] "Porcentagem de hits Sent (Usual): 0.580645"
sprintf("Porcentagem de hits Sent (Quartil): %f", hits_quart_sent/total_sent)
## [1] "Porcentagem de hits Sent (Quartil): 0.348387"

Com tais resultados concluímos duas coisas: A primeira que a utilização da normalização usual foi bem melhor que as dos quartis para os nossos dados. A segunda coisa foi que o léxico acerta o nível de insatisfação em cerca de 60% das reclamações, acertando cerca de 3 a cada 5 reclamações.

obs: por algum motivo no knit hits sent = hits op30, porem rodando localmente vimos que hits sent = 0.610687. Devido ao tempo não conseguimos investigar a fundo o motivo da diferença.

Porcentagem de palavras identificadas pelos léxicos

No gráfico abaixo verificamos um histograma da porcentagem de palavras identificadas pelos léxicos.

Lexico sent

ggplot(data=lexico_op30, aes(lexico_sent$palavras_sent/palavras)) + 
  geom_histogram(bins = 30, color="darkblue", fill="lightblue") +
  labs(x="Porcentagem de palavras identificadas pelo lexico", y="Reclamações com esse número") + scale_y_continuous(breaks=seq(0, 250, 10)) +
  scale_x_continuous(breaks=seq(0, 0.5, 0.05))

Lexico op30

ggplot(data=lexico_sent, aes(lexico_op30$palavras_op30/palavras)) + 
  geom_histogram(bins = 30, color="darkblue", fill="lightblue") +
  labs(x="Porcentagem de palavras identificadas pelo lexico", y="Reclamações com esse número") + scale_y_continuous(breaks=seq(0, 250, 10)) +
  scale_x_continuous(breaks=seq(0, 0.5, 0.05))

A partir dos gráficos acima observamos que o léxico op30 para a maioria das reclamações identifica cerca de 15% das palavras, já o ĺéxico sent na maioria das vezes identifica apenas 5% das palavras, mas mesmo assim ambos obtiveram uma taxa de acerto bastante similar e os modelos não identificaram os léxicos como sendo significativos para o erro.