<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Paixão por Dados</title>
    <link>http://www.sillasgonzaga.com/tags/text-mining/index.xml</link>
    <description>Recent content on Paixão por Dados</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <copyright>&amp;copy; 2016. All rights reserved.</copyright>
    <atom:link href="http://www.sillasgonzaga.com/tags/text-mining/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Topic Modeling: Um algoritmo consegue entender sobre o que fala a youtuber Nathalia Arcuri?</title>
      <link>http://www.sillasgonzaga.com/post/topic-modeling-nathalia-arcuri/</link>
      <pubDate>Sat, 14 Apr 2018 00:00:00 +0000</pubDate>
      
      <guid>http://www.sillasgonzaga.com/post/topic-modeling-nathalia-arcuri/</guid>
      <description>&lt;p&gt;No meu &lt;a href=&#34;http://www.sillasgonzaga.com/post/literaturaBR-01/&#34;&gt;último post&lt;/a&gt; sobre Mineração de Texto, usei algumas ferramentas do R para analisar textos clássicos da literatura brasileira. Desta vez, o foco da análise será algo mais contemporâneo: uma youtuber. Mais precisamente, a &lt;a href=&#34;http://mepoupenaweb.uol.com.br/sobre-a-nath/&#34;&gt;Nathalia Arcuri&lt;/a&gt;, responsável por um dos principais canais de educação financeira, o &lt;a href=&#34;https://www.youtube.com/channel/UC8mDF5mWNGE-Kpfcvnn0bUg/&#34;&gt;Me Poupe&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Além do objeto da análise, a abordagem aqui também é diferente: vou mostrar como Topic Modeling pode ser usado para descobrir temas gerais em um conjunto de dados textuais.&lt;/p&gt;
&lt;p&gt;Assim, este post se dedica ao seguinte problema de pesquisa: é possível identificar, por meio de um algoritmo de inteligência artificial, temas gerais que uma youtuber com mais de 300 vídeos publicados fala sobre?&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;library(reticulate)
reticulate::use_python(&amp;quot;/home/sillas/anaconda3/bin/python&amp;quot;, required = TRUE)
library(lexiconPT)
library(tidytext)
library(tidyverse)
library(magrittr)
library(stm)
library(tm)
library(ggridges)
library(formattable)
options(scipen = 999)&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&#34;coleta-dos-dados&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Coleta dos dados&lt;/h2&gt;
&lt;p&gt;Para analisar o conteúdo de vídeos de youtube, precisamos das legendas dos vídeos. Alguns (bem poucos) canais produzem suas próprias legendas manualmente, mas a grande maioria, como o Me Poupe, o canal da Nathalia Arcuri, não o faz. Sendo assim, precisamos nós mesmo produzir essas legendas.&lt;/p&gt;
&lt;p&gt;Isso seria uma tarefa muito árdua, mas felizmente o próprio Youtube tem seu próprio serviço de inteligência artifical de reconhecimento de fala, que cria automaticamente legendas para um vídeo. Apesar de algumas vezes as legendas produzidas pelo algoritmo do Youtube não serem muito fiéis, elas serão usadas como dados brutos para a modelagem por tópicos. Os resultados apresentados no post mostram que essas legendas automáticas podem sim serem usadas para fins de estudo.&lt;/p&gt;
&lt;p&gt;Para coletar as legendas dos vídeo, eu uso um utilitário de linha de comando chamado &lt;a href=&#34;https://rg3.github.io/youtube-dl/&#34;&gt;&lt;code&gt;youtube-dl&lt;/code&gt;&lt;/a&gt;, que é bem simples de usar. No código abaixo, que mistura R com shell script, eu mostro como montar uma query para baixar as legendas do vídeo em arquivos de texto cujos nomes contem alguns metadados do vídeo, descritos na variável &lt;code&gt;fields_raw&lt;/code&gt;. Caso você deseje realizar este mesmo estudo com outro youtuber, basta atribuir a url do canal na variável &lt;code&gt;channel_url&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;fields_raw &amp;lt;- c(&amp;quot;id&amp;quot;, &amp;quot;title&amp;quot;, &amp;quot;alt_title&amp;quot;, &amp;quot;creator&amp;quot;, &amp;quot;release_date&amp;quot;,
                &amp;quot;timestamp&amp;quot;, &amp;quot;upload_date&amp;quot;, &amp;quot;duration&amp;quot;, &amp;quot;view_count&amp;quot;,
                &amp;quot;like_count&amp;quot;, &amp;quot;dislike_count&amp;quot;, &amp;quot;comment_count&amp;quot;)

fields &amp;lt;- fields_raw %&amp;gt;% 
  map_chr(~paste0(&amp;quot;%(&amp;quot;, ., &amp;quot;)s&amp;quot;)) %&amp;gt;% 
  # usar &amp;amp;&amp;amp;&amp;amp; como separador de fields
  paste0(collapse = &amp;quot;&amp;amp;&amp;amp;&amp;amp;&amp;quot;) %&amp;gt;% 
  # acrescentar aspas no inicio e no final do string
  paste0(&amp;#39;&amp;quot;&amp;#39;, ., &amp;#39;&amp;quot;&amp;#39;)

# canal do me poupe
channel_url &amp;lt;- &amp;quot;https://www.youtube.com/channel/UC8mDF5mWNGE-Kpfcvnn0bUg&amp;quot;

# montar query (comando) do youtube-dl
cmd_ytdl &amp;lt;- str_glue(&amp;quot;youtube-dl -o {fields} -i -v -w --skip-download --write-auto-sub --sub-lang pt {channel_url}&amp;quot;)

# acrescentar diretorio
pasta_captions &amp;lt;- &amp;quot;/home/sillas/R/Projetos/paixaopordados-blogdown/data/mepoupe&amp;quot;
fs::dir_create(pasta_captions)
cmd &amp;lt;- str_glue(&amp;quot;cd {pasta_captions} &amp;amp;&amp;amp; {cmd_ytdl}&amp;quot;)

# imprimir comando para ver como ficou
cat(cmd)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## cd /home/sillas/R/Projetos/paixaopordados-blogdown/data/mepoupe &amp;amp;&amp;amp; youtube-dl -o &amp;quot;%(id)s&amp;amp;&amp;amp;&amp;amp;%(title)s&amp;amp;&amp;amp;&amp;amp;%(alt_title)s&amp;amp;&amp;amp;&amp;amp;%(creator)s&amp;amp;&amp;amp;&amp;amp;%(release_date)s&amp;amp;&amp;amp;&amp;amp;%(timestamp)s&amp;amp;&amp;amp;&amp;amp;%(upload_date)s&amp;amp;&amp;amp;&amp;amp;%(duration)s&amp;amp;&amp;amp;&amp;amp;%(view_count)s&amp;amp;&amp;amp;&amp;amp;%(like_count)s&amp;amp;&amp;amp;&amp;amp;%(dislike_count)s&amp;amp;&amp;amp;&amp;amp;%(comment_count)s&amp;quot; -i -v -w --skip-download --write-auto-sub --sub-lang pt https://www.youtube.com/channel/UC8mDF5mWNGE-Kpfcvnn0bUg&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Portanto, temos o comando para baixar as legendas dos vídeos na pasta indicada. Para rodar o comando, basta a variável &lt;code&gt;cmd&lt;/code&gt; como argumento da função &lt;code&gt;system()&lt;/code&gt; ou o copiar e colar no terminal.&lt;/p&gt;
&lt;p&gt;Uma amostra dos arquivos baixados:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;dir(pasta_captions)[1:3]&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## [1] &amp;quot;039orzgCUt0&amp;amp;&amp;amp;&amp;amp;10 DILEMAS MAIS FREQUENTES SOBRE TESOURO DIRETO! Tire as dúvidas e invista!&amp;amp;&amp;amp;&amp;amp;NA&amp;amp;&amp;amp;&amp;amp;NA&amp;amp;&amp;amp;&amp;amp;NA&amp;amp;&amp;amp;&amp;amp;NA&amp;amp;&amp;amp;&amp;amp;20170424&amp;amp;&amp;amp;&amp;amp;642&amp;amp;&amp;amp;&amp;amp;272848&amp;amp;&amp;amp;&amp;amp;33970&amp;amp;&amp;amp;&amp;amp;137&amp;amp;&amp;amp;&amp;amp;NA.pt.vtt&amp;quot;                 
## [2] &amp;quot;0bqZrSaitDo&amp;amp;&amp;amp;&amp;amp;PLANILHA DE INDEPENDÊNCIA FINANCEIRA! Aprenda a usar! Série especial_ Office 365 #AjudaaNath&amp;amp;&amp;amp;&amp;amp;NA&amp;amp;&amp;amp;&amp;amp;NA&amp;amp;&amp;amp;&amp;amp;NA&amp;amp;&amp;amp;&amp;amp;NA&amp;amp;&amp;amp;&amp;amp;20170328&amp;amp;&amp;amp;&amp;amp;988&amp;amp;&amp;amp;&amp;amp;260474&amp;amp;&amp;amp;&amp;amp;18439&amp;amp;&amp;amp;&amp;amp;206&amp;amp;&amp;amp;&amp;amp;NA.pt.vtt&amp;quot;
## [3] &amp;quot;0eSSqsHSr2A&amp;amp;&amp;amp;&amp;amp;Série empreendedorismo na veia Ep 3_ EXPERIENTES TAMBÉM FALHAM!&amp;amp;&amp;amp;&amp;amp;NA&amp;amp;&amp;amp;&amp;amp;NA&amp;amp;&amp;amp;&amp;amp;NA&amp;amp;&amp;amp;&amp;amp;NA&amp;amp;&amp;amp;&amp;amp;20161225&amp;amp;&amp;amp;&amp;amp;282&amp;amp;&amp;amp;&amp;amp;32361&amp;amp;&amp;amp;&amp;amp;2307&amp;amp;&amp;amp;&amp;amp;21&amp;amp;&amp;amp;&amp;amp;NA.pt.vtt&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&#34;limpeza-dos-dados&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Limpeza dos dados&lt;/h2&gt;
&lt;p&gt;O Youtube tem um formato próprio de arquivos para legendas chamado vtt. Veja como são os arquivos de texto baixados na etapa anterior:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;arquivos_captions &amp;lt;- dir(pasta_captions, pattern = &amp;#39;*.vtt&amp;#39;, full.names = TRUE)
amostra &amp;lt;- arquivos_captions[1]

read_lines(amostra)[1:30]&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;##  [1] &amp;quot;WEBVTT&amp;quot;                                                                                                                                                                                                    
##  [2] &amp;quot;Kind: captions&amp;quot;                                                                                                                                                                                            
##  [3] &amp;quot;Language: pt&amp;quot;                                                                                                                                                                                              
##  [4] &amp;quot;Style:&amp;quot;                                                                                                                                                                                                    
##  [5] &amp;quot;::cue(c.colorCCCCCC) { color: rgb(204,204,204);&amp;quot;                                                                                                                                                           
##  [6] &amp;quot; }&amp;quot;                                                                                                                                                                                                        
##  [7] &amp;quot;::cue(c.colorE5E5E5) { color: rgb(229,229,229);&amp;quot;                                                                                                                                                           
##  [8] &amp;quot; }&amp;quot;                                                                                                                                                                                                        
##  [9] &amp;quot;##&amp;quot;                                                                                                                                                                                                        
## [10] &amp;quot;&amp;quot;                                                                                                                                                                                                          
## [11] &amp;quot;00:00:00.000 --&amp;gt; 00:00:02.419 align:start position:0%&amp;quot;                                                                                                                                                     
## [12] &amp;quot; &amp;quot;                                                                                                                                                                                                         
## [13] &amp;quot;olha&amp;lt;00:00:00.329&amp;gt;&amp;lt;c&amp;gt; o&amp;lt;/c&amp;gt;&amp;lt;00:00:00.420&amp;gt;&amp;lt;c&amp;gt; data&amp;lt;/c&amp;gt;&amp;lt;c.colorE5E5E5&amp;gt;&amp;lt;00:00:00.840&amp;gt;&amp;lt;c&amp;gt; mtow&amp;lt;/c&amp;gt;&amp;lt;00:00:01.260&amp;gt;&amp;lt;c&amp;gt; que&amp;lt;/c&amp;gt;&amp;lt;00:00:01.350&amp;gt;&amp;lt;c&amp;gt; apurou&amp;lt;/c&amp;gt;&amp;lt;00:00:01.709&amp;gt;&amp;lt;c&amp;gt; que&amp;lt;/c&amp;gt;&amp;lt;00:00:01.979&amp;gt;&amp;lt;c&amp;gt; esta&amp;lt;/c&amp;gt;&amp;lt;/c&amp;gt;&amp;quot;
## [14] &amp;quot;&amp;quot;                                                                                                                                                                                                          
## [15] &amp;quot;00:00:02.419 --&amp;gt; 00:00:02.429 align:start position:0%&amp;quot;                                                                                                                                                     
## [16] &amp;quot;olha o data&amp;lt;c.colorE5E5E5&amp;gt; mtow que apurou que esta&amp;quot;                                                                                                                                                       
## [17] &amp;quot; &amp;lt;/c&amp;gt;&amp;quot;                                                                                                                                                                                                     
## [18] &amp;quot;&amp;quot;                                                                                                                                                                                                          
## [19] &amp;quot;00:00:02.429 --&amp;gt; 00:00:04.640 align:start position:0%&amp;quot;                                                                                                                                                     
## [20] &amp;quot;olha o data&amp;lt;c.colorE5E5E5&amp;gt; mtow que apurou que esta&amp;lt;/c&amp;gt;&amp;quot;                                                                                                                                                   
## [21] &amp;quot;&amp;lt;c.colorCCCCCC&amp;gt;pergunta&amp;lt;/c&amp;gt;&amp;lt;c.colorE5E5E5&amp;gt;&amp;lt;00:00:03.210&amp;gt;&amp;lt;c&amp;gt; este&amp;lt;/c&amp;gt;&amp;lt;00:00:03.389&amp;gt;&amp;lt;c&amp;gt; dilema&amp;lt;/c&amp;gt;&amp;lt;00:00:03.780&amp;gt;&amp;lt;c&amp;gt; é&amp;lt;/c&amp;gt;&amp;lt;00:00:04.170&amp;gt;&amp;lt;c&amp;gt; o&amp;lt;/c&amp;gt;&amp;lt;00:00:04.259&amp;gt;&amp;lt;c&amp;gt; mais&amp;lt;/c&amp;gt;&amp;lt;/c&amp;gt;&amp;quot;                              
## [22] &amp;quot;&amp;quot;                                                                                                                                                                                                          
## [23] &amp;quot;00:00:04.640 --&amp;gt; 00:00:04.650 align:start position:0%&amp;quot;                                                                                                                                                     
## [24] &amp;quot;&amp;lt;c.colorCCCCCC&amp;gt;pergunta&amp;lt;/c&amp;gt;&amp;lt;c.colorE5E5E5&amp;gt; este dilema é o mais&amp;quot;                                                                                                                                           
## [25] &amp;quot; &amp;lt;/c&amp;gt;&amp;quot;                                                                                                                                                                                                     
## [26] &amp;quot;&amp;quot;                                                                                                                                                                                                          
## [27] &amp;quot;00:00:04.650 --&amp;gt; 00:00:06.650 align:start position:0%&amp;quot;                                                                                                                                                     
## [28] &amp;quot;&amp;lt;c.colorCCCCCC&amp;gt;pergunta&amp;lt;/c&amp;gt;&amp;lt;c.colorE5E5E5&amp;gt; este dilema é o mais&amp;lt;/c&amp;gt;&amp;quot;                                                                                                                                       
## [29] &amp;quot;&amp;lt;c.colorE5E5E5&amp;gt;freqüente&amp;lt;00:00:05.190&amp;gt;&amp;lt;c&amp;gt; de&amp;lt;/c&amp;gt;&amp;lt;00:00:05.310&amp;gt;&amp;lt;c&amp;gt; tudo&amp;lt;/c&amp;gt;&amp;lt;00:00:05.700&amp;gt;&amp;lt;c&amp;gt; o&amp;lt;/c&amp;gt;&amp;lt;00:00:05.790&amp;gt;&amp;lt;c&amp;gt; que&amp;lt;/c&amp;gt;&amp;lt;/c&amp;gt;&amp;lt;c.colorCCCCCC&amp;gt;&amp;lt;00:00:05.970&amp;gt;&amp;lt;c&amp;gt; investiu&amp;lt;/c&amp;gt;&amp;lt;00:00:06.509&amp;gt;&amp;lt;c&amp;gt; uma&amp;lt;/c&amp;gt;&amp;lt;/c&amp;gt;&amp;quot;  
## [30] &amp;quot;&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Temos vários problemas de dados não estruturados aí, como timestamps, formatação de estilo como cor e alinhamento e repetição de frases em diferentes linhas (note como as frases nas linhas 24 e 28 são as mesmas). Entretanto, limpar esses dados é mais simples que se imagina. Segue o passo-a-passo:&lt;/p&gt;
&lt;p&gt;Começando pelo mais crítico, vamos remover toda a formatação de legendas do texto, deixando apenas as frases. Para isso, uso um módulo Python (sim, eu me rendi, esta é a primeira vez que uso Python no blog) chamado &lt;code&gt;webvtt&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&#34;python&#34;&gt;&lt;code&gt;from webvtt import WebVTT
def caption_to_vector(file):
  x = WebVTT().read(file)
  txt = [caption.text for caption in x]
  return(txt)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vejam com essa função faz todo o trabalho bruto de limpar os metadados da legenda:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;x &amp;lt;- amostra %&amp;gt;% caption_to_vector()
x[1:20]&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;##  [1] &amp;quot; \nolha o data mtow que apurou que esta&amp;quot;                                      
##  [2] &amp;quot;olha o data mtow que apurou que esta\n &amp;quot;                                      
##  [3] &amp;quot;olha o data mtow que apurou que esta\npergunta este dilema é o mais&amp;quot;          
##  [4] &amp;quot;pergunta este dilema é o mais\n &amp;quot;                                             
##  [5] &amp;quot;pergunta este dilema é o mais\nfreqüente de tudo o que investiu uma&amp;quot;          
##  [6] &amp;quot;freqüente de tudo o que investiu uma\n &amp;quot;                                      
##  [7] &amp;quot;freqüente de tudo o que investiu uma\nvez só ou possa investir todo mês&amp;quot;      
##  [8] &amp;quot;vez só ou possa investir todo mês\n &amp;quot;                                         
##  [9] &amp;quot;vez só ou possa investir todo mês\nfilho olha esse aqui se não ficar nos&amp;quot;     
## [10] &amp;quot;filho olha esse aqui se não ficar nos\n &amp;quot;                                     
## [11] &amp;quot;filho olha esse aqui se não ficar nos\nvídeos em alta do yuan subir é&amp;quot;        
## [12] &amp;quot;vídeos em alta do yuan subir é\n &amp;quot;                                            
## [13] &amp;quot;vídeos em alta do yuan subir é\nmarmelada hoje vou falar para você&amp;quot;           
## [14] &amp;quot;marmelada hoje vou falar para você\n &amp;quot;                                        
## [15] &amp;quot;marmelada hoje vou falar para você\nquais são os dez maiores dilemas de&amp;quot;      
## [16] &amp;quot;quais são os dez maiores dilemas de\n &amp;quot;                                       
## [17] &amp;quot;quais são os dez maiores dilemas de\nquem quer investir no tesouro direto mas&amp;quot;
## [18] &amp;quot;quem quer investir no tesouro direto mas\n &amp;quot;                                  
## [19] &amp;quot;quem quer investir no tesouro direto mas\nainda não sabe muito bem como fazer&amp;quot;
## [20] &amp;quot;ainda não sabe muito bem como fazer\n &amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ainda temos alguns problemas, como as linhas repetidas, mas isso é o de menos. Resolvemos todos os problemas de limpeza de dados com a função abaixo:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;limpar_caption &amp;lt;- function(arquivo){
  caption_raw &amp;lt;- caption_to_vector(arquivo)
  n &amp;lt;- length(caption_raw)
  # remover \n das linhas com exceção da ultima
  caption &amp;lt;- c(stringr::str_remove_all(caption_raw[-n], &amp;quot;[\n].*&amp;quot;),
               caption_raw[n])
  # remover duplicatas
  caption &amp;lt;- unique(caption)
  # remover acentos
  caption &amp;lt;- iconv(caption, from = &amp;quot;UTF-8&amp;quot;, to = &amp;quot;ASCII//TRANSLIT&amp;quot;)
  # juntar todo o vector em um so
  caption &amp;lt;- paste0(caption, collapse = &amp;quot;\n&amp;quot;)
  caption
}

# exemplo
# amostra %&amp;gt;% limpar_caption()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Além disso, precisamos também extrair os metadados dos vídeos salvos nos nomes dos arquivos:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# funcao para extrair metadados do video baseado no titulo
extrair_metadados &amp;lt;- function(arquivo, pasta = pasta_captions,
                              fields = fields_raw){
  mat &amp;lt;- str_split(arquivo, &amp;quot;&amp;amp;{3}&amp;quot;, simplify = TRUE)
  # substituir elemento da primeira coluna por id (remover pasta do nome)
  mat[1,1] &amp;lt;- mat[1,1] %&amp;gt;% str_remove_all(pasta) %&amp;gt;% str_remove_all(&amp;quot;/&amp;quot;)
  
  # renomear colunas
  cols &amp;lt;- fields[1:ncol(mat)]
  colnames(mat) &amp;lt;- cols
  as.tibble(mat)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finalmente, a função abaixo cria um dataframe com as colunas de metadados e de legenda, que chamo de caption:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# funcao para juntar tudo num dataframe so
caption_to_df &amp;lt;- function(arquivo, ...){
  
  caption &amp;lt;- limpar_caption(arquivo)
  meta &amp;lt;- extrair_metadados(arquivo, ...)
  meta &amp;lt;- meta %&amp;gt;% mutate(caption = caption)
  
  meta
}

### gerar dataframe para todos os videos
df &amp;lt;- arquivos_captions %&amp;gt;% 
  map_df(caption_to_df) %&amp;gt;% 
  # remover coluna que nao uso
  select(-comment_count) %&amp;gt;% 
  # converter a classe de algumas colunas
  mutate(upload_date = lubridate::ymd(upload_date)) %&amp;gt;% 
  mutate_at(vars(duration:dislike_count), as.numeric)

# vendo como ficou
glimpse(df)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Observations: 319
## Variables: 12
## $ id            &amp;lt;chr&amp;gt; &amp;quot;039orzgCUt0&amp;quot;, &amp;quot;0bqZrSaitDo&amp;quot;, &amp;quot;0eSSqsHSr2A&amp;quot;, &amp;quot;0G...
## $ title         &amp;lt;chr&amp;gt; &amp;quot;10 DILEMAS MAIS FREQUENTES SOBRE TESOURO DIRETO...
## $ alt_title     &amp;lt;chr&amp;gt; &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, ...
## $ creator       &amp;lt;chr&amp;gt; &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, ...
## $ release_date  &amp;lt;chr&amp;gt; &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, ...
## $ timestamp     &amp;lt;chr&amp;gt; &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, &amp;quot;NA&amp;quot;, ...
## $ upload_date   &amp;lt;date&amp;gt; 2017-04-24, 2017-03-28, 2016-12-25, 2016-02-15,...
## $ duration      &amp;lt;dbl&amp;gt; 642, 988, 282, 440, 670, 647, 574, 396, 615, 614...
## $ view_count    &amp;lt;dbl&amp;gt; 272848, 260474, 32361, 242366, 262462, 53146, 11...
## $ like_count    &amp;lt;dbl&amp;gt; 33970, 18439, 2307, 13163, 14439, 3868, 9369, 17...
## $ dislike_count &amp;lt;dbl&amp;gt; 137, 206, 21, 371, 453, 89, 110, 176, 87, 149, 1...
## $ caption       &amp;lt;chr&amp;gt; &amp;quot; \nolha o data mtow que apurou que esta\npergun...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&#34;topic-modeling&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Topic Modeling&lt;/h2&gt;
&lt;p&gt;Antes de partir para o código aplicado a Topic Modeling, uma brevíssima introdução teórica.&lt;/p&gt;
&lt;p&gt;No contexto de &lt;a href=&#34;https://en.wikipedia.org/wiki/Text_mining&#34;&gt;Text Mining&lt;/a&gt;, um &lt;strong&gt;tópico&lt;/strong&gt; &lt;span class=&#34;math inline&#34;&gt;\(T\)&lt;/span&gt; é definido como um conjunto de &lt;strong&gt;palavras&lt;/strong&gt; ou tokens (&lt;span class=&#34;math inline&#34;&gt;\(w_1, w_2, ... w_n\)&lt;/span&gt;) onde cada palavra possui uma probabilidade de pertencer a um tópico, e um &lt;strong&gt;documento&lt;/strong&gt; é definido como um conjunto de tópicos, onde cada um possui uma proporção de presença dentro do documento. A soma da proporção de cada tópico dentro de um documento é igual a 1, assim como a soma das probabilidades de cada palavra para um dado tópico, que também é igual a 1. Nesta análise, um documento corresponde a cada vídeo lançado no canal Me Poupe.&lt;/p&gt;
&lt;p&gt;Topic Modeling, portanto, é definido como uma ténica não-supervisionada de Machine Learning que busca que identica clusteres ou grupos de palavras que ocorrem juntas, descobrindo assim tópicos abstratos que ocorrem em um conjunto de documentos. O objetivo é descobrir subestruturas semânticas dentro de um conjunto de textos.&lt;/p&gt;
&lt;p&gt;Existem alguns algoritmos de Topic Modeling, muitos deles presentes no pacote &lt;code&gt;topicmodels&lt;/code&gt;. Apenas devido a preferência pessoal, vou usar neste post o algoritmo &lt;strong&gt;Structural Topic Models&lt;/strong&gt; (STM), presente no pacote &lt;code&gt;stm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A qualidade de um tópico encontrado por um algoritmo pode ser medida por algumas métricas, como &lt;strong&gt;exclusividade&lt;/strong&gt; e &lt;strong&gt;coerência semântica&lt;/strong&gt;, cuja ideia é que, em um modelo de tópicos que é semanticamente coerente, as palavras que são mais prováveis de pertencer a um tópico devem ocorrer dentro de um mesmo documento. A formulação matemática dessas métricas é razoavelmente mais complicadas que essas explicações. Caso você deseja conhecer essas métricas com mais detalhes, confira as referências no final do post.&lt;/p&gt;
&lt;div id=&#34;pre-processamento&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Pré-processamento&lt;/h3&gt;
&lt;p&gt;Mesmo após o processo de limpeza, é necessário realizar alguns pré-processamentos antes de aplicar o algoritmo de Topic Modeling.&lt;/p&gt;
&lt;p&gt;Um dos procedimentos necessários é a remoção de stopwords, que são palavras que ocorrem tão frequentemente que não acrescentam nenhum valor semântico a um texto, como “e”, “vai”, “lá”, etc. O pacote &lt;code&gt;tm&lt;/code&gt; fornece uma lista de stopwords em vários idiomas, incluindo Português.&lt;/p&gt;
&lt;p&gt;O código abaixo retorna as palavras mais faladas nos vídeos do Me Poupe, após a remoção de stopwords:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# stopwords da lingua portuguesa sem acento
sw_pt_tm &amp;lt;- tm::stopwords(&amp;quot;pt&amp;quot;) %&amp;gt;% iconv(from = &amp;quot;UTF-8&amp;quot;, to = &amp;quot;ASCII//TRANSLIT&amp;quot;)

# criar dataframe com uma linha por palavra
df_palavra &amp;lt;- df %&amp;gt;% 
  unnest_tokens(palavra, caption) %&amp;gt;% 
  # filtrar fora stopword
  filter(!palavra %in% sw_pt_tm)

df_palavra %&amp;gt;% 
  count(palavra) %&amp;gt;% 
  arrange(desc(n)) %&amp;gt;% 
  head(20) %&amp;gt;% 
  formattable()&lt;/code&gt;&lt;/pre&gt;
&lt;table class=&#34;table table-condensed&#34;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&#34;text-align:right;&#34;&gt;
palavra
&lt;/th&gt;
&lt;th style=&#34;text-align:right;&#34;&gt;
n
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
vai
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
5467
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
gente
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
3297
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
porque
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
3077
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
aqui
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
3047
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
pra
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
3001
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
dinheiro
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
2731
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
fazer
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
2373
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
entao
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
2256
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
pode
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
2119
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
la
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
2097
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
ter
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
1645
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
ai
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
1614
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
agora
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
1520
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
ser
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
1516
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
pessoas
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
1354
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
canal
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
1334
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
vou
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
1334
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
hoje
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
1319
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
video
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
1267
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
tudo
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
1243
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Como muitas dessas palavras não possuem um grande valor semântico para a separação de tópicos, podemos as acrescentar à lista de stopwords.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;minhas_sw &amp;lt;- c(&amp;quot;vai&amp;quot;, &amp;quot;porque&amp;quot;, &amp;quot;vou&amp;quot;, &amp;quot;ai&amp;quot;, &amp;quot;pra&amp;quot;, &amp;quot;entao&amp;quot;)
sw_pt &amp;lt;- c(minhas_sw, sw_pt_tm)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Partimos então para o processamento de textos com as funções do pacote &lt;code&gt;stm&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;proc &amp;lt;- stm::textProcessor(df$caption, metadata = df, language = &amp;quot;portuguese&amp;quot;,
                           customstopwords = sw_pt)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Building corpus... 
## Converting to Lower Case... 
## Removing punctuation... 
## Removing stopwords... 
## Remove Custom Stopwords...
## Removing numbers... 
## Stemming... 
## Creating Output...&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;out &amp;lt;- stm::prepDocuments(proc$documents, proc$vocab, proc$meta,
                          lower.thresh = 10)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Removing 14665 of 16857 terms (33357 of 129408 tokens) due to frequency 
## Your corpus now has 319 documents, 2192 terms and 96051 tokens.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;criacao-do-modelo-de-topicos-e-interpretacao-dos-resultados&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Criação do modelo de tópicos e interpretação dos resultados&lt;/h2&gt;
&lt;p&gt;A quantidade de tópicos &lt;span class=&#34;math inline&#34;&gt;\(K\)&lt;/span&gt; que desejamos extrair dos textos é, na verdade, escolhida pelo ser humano. Não há uma regra precisa que defina o número ótimo de clusteres. No entanto, é possível usar a função &lt;code&gt;stm::searchK&lt;/code&gt; para rodar o STM para diferentes valores do parâmetro &lt;code&gt;K&lt;/code&gt; para encontrar o valor que otimiza a exclusividade e a coerência semântica do modelo.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;storage &amp;lt;- stm::searchK(out$documents, out$vocab, K = c(3:15),
                          data = out$meta)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Após uma inspeção manual, decidi usar &lt;span class=&#34;math inline&#34;&gt;\(K = 10\)&lt;/span&gt; para encontrar 10 tópicos sobre os quais a Nathalia Arcuri mais fala.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;fit &amp;lt;- stm(
  documents = out$documents, vocab = out$vocab, data = out$meta,  K = 10,
  max.em.its = 75, init.type = &amp;quot;Spectral&amp;quot;, verbose = FALSE
  )&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Após a construção do modelo, a melhor forma de visualizar os resultados é por meio de um gráfico:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;plot(fit, &amp;quot;summary&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2018-04-14-topic-modeling-nathalia-arcuri_files/figure-html/unnamed-chunk-10-1.png&#34; width=&#34;864&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Ou mesmo por texto:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;stm::labelTopics(fit)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Topic 1 Top Words:
##       Highest Prob: gent, aqui, coisa, fazer, pode, assim, casa 
##       FREX: roupa, cabelo, peca, barato, economizar, comer, loja 
##       Lift: energia, cor, tratamento, sapato, ovo, armario, compro 
##       Score: energia, tratamento, ovo, cabelo, pared, loja, dica 
## Topic 2 Top Words:
##       Highest Prob: gent, aqui, pessoa, pergunta, agora, vamo, fazer 
##       FREX: milhao, hashtag, galera, pergunta, poup, job, inscrito 
##       Lift: bilhao, premio, jornalista, seguidor, contrata, hashtag, orgulho 
##       Score: bilhao, live, job, milhao, premio, hashtag, conteudo 
## Topic 3 Top Words:
##       Highest Prob: dinheiro, reai, ano, aqui, fazer, video, mil 
##       FREX: meta, planilha, tarefa, viver, aposentadoria, reai, poupar 
##       Lift: vitoria, offic, aposentar, caderno, planilha, metodo, produtividad 
##       Score: vitoria, caderno, meta, offic, planilha, joaquina, metodo 
## Topic 4 Top Words:
##       Highest Prob: dinheiro, coisa, mulher, vida, pessoa, pode, ser 
##       FREX: mulher, mae, present, crianca, casamento, maravilha, pai 
##       Lift: gostam, casamento, adulto, maravilha, honesto, casar, present 
##       Score: gostam, casamento, natal, present, maravilha, adulto, objetivo 
## Topic 5 Top Words:
##       Highest Prob: gent, cidad, aqui, rio, janeiro, fazer, candidato 
##       FREX: cidad, candidato, prefeitura, janeiro, rio, prefeito, municipio 
##       Lift: arrecadou, evasiva, habitacao, municipio, prefeitura, promovida, tocar 
##       Score: municipio, tocar, candidato, prefeitura, habitacao, corrupcao, prefeito 
## Topic 6 Top Words:
##       Highest Prob: tesouro, dinheiro, taxa, fundo, direto, aqui, investimento 
##       FREX: tesouro, selic, fundo, direto, taxa, titulo, corretora 
##       Lift: digit, garantidor, imobiliario, ipc, ipc-, cdi, tesouro 
##       Score: digit, tesouro, selic, imobiliario, corretora, taxa, rentabilidad 
## Topic 7 Top Words:
##       Highest Prob: empresa, pessoa, aqui, pode, fazer, dinheiro, valor 
##       FREX: aco, aplicativo, empresa, aluguel, mercado, bolsa, acao 
##       Lift: placa, aco, lucro, bolo, alugar, documento, moeda 
##       Score: placa, aco, investidor, alugar, bolo, empresa, lucro 
## Topic 8 Top Words:
##       Highest Prob: ponto, cartao, gent, credito, aqui, pode, fazer 
##       FREX: multiplus, ponto, acumular, multiplo, cartao, black, site 
##       Lift: milha, anuidad, multiplus, multiplo, passagen, acumular, acumula 
##       Score: multiplus, anuidad, multiplo, ponto, black, acumular, cartao 
## Topic 9 Top Words:
##       Highest Prob: divida, dinheiro, pagar, carro, credito, cartao, pode 
##       FREX: divida, carro, parcela, pagar, juro, credito, ajuda 
##       Lift: chequ, financiado, quitar, devendo, divida, parcelar, parcela 
##       Score: chequ, divida, credito, cartao, financiamento, parcela, consorcio 
## Topic 10 Top Words:
##       Highest Prob: pessoa, gent, fazer, ser, aqui, dinheiro, ter 
##       FREX: ingl, empreendedor, curso, faculdad, sucesso, segredo, negocio 
##       Lift: encontrei, mestr, ingl, atividad, consequencia, tecnico, milionaria 
##       Score: encontrei, live, faculdad, empreendedor, conteudo, ingl, chefe&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Na tabela acima, existe para cada tópico uma lista de palavras-chave, de acordo com uma métrica de associação. As métricas mais interessantes a serem olhadas são &lt;strong&gt;Highest prob&lt;/strong&gt;, que é basicamente uma contagem de cada palavra no tópico, e &lt;strong&gt;FREX&lt;/strong&gt;, que é combina frequência e exclusividade para identificar palavras que mais representam o tópico.&lt;/p&gt;
&lt;p&gt;Assim, a interpretação dos tópicos pode ser feita desta maneira:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tópico 1: Dicas de economia doméstica, como que para reduzir despesas em casa;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Tópico 2: Genérico;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Tópico 3: Planejamento Financeiro, com temas como planilhas e aposentadoria;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Tópico 4: Vídeos focados para mulheres ou famílias;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Tópico 5: Política;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Tópico 6: Renda Fixa (este ficou bem claro);&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Tópico 7: Dificil saber. Talvez renda variável;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Tópico 8: Cartão de cŕedito e temas afins, como programas de milhas;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Tópico 9: Dívidas e despesas;&lt;/li&gt;
&lt;li&gt;Tópico 10: Empreendedorimo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;É possível representar visualmente a diferença entre um par de tópicos, como o 6 e o 9:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;plot(fit, &amp;quot;perspective&amp;quot;, topics = c(9, 6))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2018-04-14-topic-modeling-nathalia-arcuri_files/figure-html/unnamed-chunk-12-1.png&#34; width=&#34;864&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Percebe-se que as palavras “dívida” e “tesouro” separam bem esses dois tópicos.&lt;/p&gt;
&lt;div id=&#34;atribuicao-de-topicos-a-videos&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Atribuição de tópicos a vídeos&lt;/h3&gt;
&lt;p&gt;Um dos possíveis produtos da análise de Topic Modeling é atribuir a cada documento (ou vídeo) um tópico, de acordo com a proporção de cada tópico nele.&lt;/p&gt;
&lt;p&gt;O objeto &lt;code&gt;fit&lt;/code&gt; possui internamente um elemento que é uma matriz &lt;span class=&#34;math inline&#34;&gt;\(N \times K\)&lt;/span&gt;, onde N é o número de documentos. Assim, para cada vídeo, existe um porcentual de participação de cada tópico, cuja soma é igual a 1.&lt;/p&gt;
&lt;p&gt;Vejamos essa matriz para os cinco primeiros vídeos:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;head(fit$theta)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;##             [,1]        [,2]        [,3]         [,4]         [,5]
## [1,] 0.000273920 0.003253428 0.004040834 0.0009959664 0.0001388210
## [2,] 0.018310800 0.030741680 0.708967915 0.0118358411 0.0006963748
## [3,] 0.070843633 0.041732621 0.013845361 0.0304166112 0.0839133871
## [4,] 0.008194601 0.002381194 0.008008110 0.0033322595 0.0018488345
## [5,] 0.010782705 0.026464694 0.010263833 0.0477243086 0.1095574639
## [6,] 0.006023939 0.872040010 0.005714869 0.0059672704 0.0481083479
##             [,6]        [,7]         [,8]        [,9]        [,10]
## [1,] 0.985509536 0.002881037 0.0001246733 0.002037421 0.0007443629
## [2,] 0.183050228 0.005301315 0.0292175603 0.008811973 0.0030663133
## [3,] 0.010378165 0.176350442 0.0412429834 0.262069287 0.2692075093
## [4,] 0.001888864 0.106774265 0.8546355488 0.011153510 0.0017828124
## [5,] 0.047119628 0.504319587 0.0113556114 0.156215296 0.0761968712
## [6,] 0.019619518 0.011139023 0.0019899886 0.006333920 0.0230631152&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Observando a primeira linha, nota-se que existe um claro predomínio do Tópico 6, que representa 98% da distribuição de tópicos. Qual é esse vídeo?&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df %&amp;gt;% 
  filter(row_number() == 1) %&amp;gt;% 
  select(id, title)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## # A tibble: 1 x 2
##   id          title                                                       
##   &amp;lt;chr&amp;gt;       &amp;lt;chr&amp;gt;                                                       
## 1 039orzgCUt0 10 DILEMAS MAIS FREQUENTES SOBRE TESOURO DIRETO! Tire as dú…&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vemos que o algoritmo acertou na mosca, pois de fato o vídeo é sobre Renda Fixa.&lt;/p&gt;
&lt;p&gt;A qual tópico pertence o vídeo mais assistido do Me Poupe?&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# id do video Meu Primeiro Milhao
ind &amp;lt;- which(df$id == &amp;quot;4jaWDfTbytA&amp;quot;)
t(fit$theta[ind,])&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;##            [,1]       [,2]      [,3]       [,4]         [,5]        [,6]
## [1,] 0.05483404 0.03399528 0.5101044 0.01345342 0.0007212358 0.003021568
##             [,7]        [,8]        [,9]     [,10]
## [1,] 0.003167582 0.001051847 0.003699136 0.3759515&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Os tópicos predominantes são 3 (Planejamento financeiro), com probabilidade de 51% e 10 (empreendedorismo), com 38%. Faz sentido, porque neste vídeo a Nathalia conta sua história como empreendedora para atingir seu primeiro milhão por meio de muito planejamento financeiro (não tô forçando, eu vi o vídeo).&lt;/p&gt;
&lt;p&gt;Vamos então, para cada vídeo, extrair seu tópico predominante e contar a frequência de videos por tópico:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;nomes_topicos &amp;lt;- c(&amp;quot;economia_domestica&amp;quot;, &amp;quot;generico&amp;quot;, &amp;quot;plan_financeiro&amp;quot;,
                   &amp;quot;mulher_familia&amp;quot;, &amp;quot;politica&amp;quot;, &amp;quot;renda_fixa&amp;quot;, &amp;quot;renda_variavel&amp;quot;,
                   &amp;quot;cartao_de_credito&amp;quot;, &amp;quot;dividas&amp;quot;, &amp;quot;empreendedorismo&amp;quot;)
# extrair a maior probabilidade pra cada video
maior_prob &amp;lt;- apply(fit$theta, 1, max)
# extrair o nome do topico com a maior probabilidade
topico_video &amp;lt;- nomes_topicos[apply(fit$theta, 1, which.max)]

# acrescentar esses dados no dataframe principal
df_topico &amp;lt;- df %&amp;gt;% 
  mutate(maior_prob = maior_prob,
         topico = topico_video)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;roxo &amp;lt;- &amp;quot;mediumpurple4&amp;quot;

# grafico da quantidade de videos por topico
df_topico %&amp;gt;% 
  count(topico) %&amp;gt;% 
  # classificar em ordem decrescente
  mutate(topico = forcats::fct_reorder(topico, n)) %&amp;gt;% 
  ggplot(aes(x = topico, y = n)) + 
  geom_col(fill = roxo) +
  theme_minimal() + 
  labs(x = NULL, y = &amp;quot;Vídeos&amp;quot;,
       title = &amp;quot;Quantidade de vídeos por tópico&amp;quot;) +
  coord_flip()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2018-04-14-topic-modeling-nathalia-arcuri_files/figure-html/unnamed-chunk-17-1.png&#34; width=&#34;864&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Os tópicos sobre os quais a Nathalia mais fala são Renda Fixa, Economia Doméstica e Dívidas. Quem acompanha seu canal não vai ficar surpreso com estes resultados, o que é um índicio da qualidade do modelo construído neste post.&lt;/p&gt;
&lt;p&gt;Também é possível extrair os vídeos chaves de cada tópico, isto é, os vídeos com a maior probabilidade para cada tópico:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df_topico %&amp;gt;% 
  group_by(topico) %&amp;gt;% 
  filter(maior_prob == max(maior_prob)) %&amp;gt;% 
  select(id, title, topico, maior_prob) %&amp;gt;% 
  knitr::kable()&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr class=&#34;header&#34;&gt;
&lt;th align=&#34;left&#34;&gt;id&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;title&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;topico&lt;/th&gt;
&lt;th align=&#34;right&#34;&gt;maior_prob&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td align=&#34;left&#34;&gt;_alsKxI3-TI&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;NATH AO VIVO! COMO VIAJAR SEM COLOCAR A MÃO NO BOLSO! Dicas preciosas dos viciados por pontos!&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;cartao_de_credito&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;0.9906875&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td align=&#34;left&#34;&gt;eMQZRHoIgtQ&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;COMO EU TIRO AS METAS DO PAPEL! Técnica simples pra juntar mais dinheiro do que nunca!&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;plan_financeiro&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;0.9773071&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td align=&#34;left&#34;&gt;FHqHW8-xvLQ&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;Dia das crianças - COMO FALAR QUE A GRANA TÁ CURTA&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;mulher_familia&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;0.9717965&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td align=&#34;left&#34;&gt;iA9Iqx2uByo&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;Entrevista - Carlos Osório - Que Candidato é esse&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;politica&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;0.9907593&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td align=&#34;left&#34;&gt;kddZm50Gluw&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;Perguntas e respostas #5 - Cartão de crédito e FIM DE NAMORO!&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;dividas&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;0.9629534&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td align=&#34;left&#34;&gt;liIQENCBlF0&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;SE VOCÊ NÃO ENTENDER ISSO, NUNCA VAI INVESTIR NA BOLSA!_ Renda variável do jeito mais simples&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;renda_variavel&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;0.9695903&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td align=&#34;left&#34;&gt;R0AQyTIvcvI&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;O que é SELIC E CDI Entenda isso HOJE e pare de PERDER DINHEIRO! _ SÉRIE INVESTIDORES INICIANTES&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;renda_fixa&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;0.9899924&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td align=&#34;left&#34;&gt;SfG934AzjYU&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;COMO EU COMPRO ROUPAS DUAS VEZES AO ANO! Episódio final _ A negociação!&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;economia_domestica&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;0.9877744&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td align=&#34;left&#34;&gt;WtFV03Suheg&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;A FINAL DA CONTRATAÇÃO QUE VOCÊ RESPEITA! Quem a Nath vai contratar (AGORA GRAVADO)&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;generico&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;0.9605720&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td align=&#34;left&#34;&gt;Zla1-t3aOZw&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;3 ESCOLHAS QUE ENRIQUECERAM O CERBASI _ meu guru&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;empreendedorismo&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;0.9151230&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Eu, pessoalmente, estou bastante satisfeito com os resultados. A modelagem de tópicos funcionou muito bem.&lt;/p&gt;
&lt;p&gt;Vamos estão analisar algumas estatísticas básicas para cada tópico: quais têm os vídeos mais longos? Quais são os mais populares?&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;meu_grafico &amp;lt;- function(df, var){
  quo_var &amp;lt;- enquo(var)

  mediana_geral &amp;lt;- df %&amp;gt;% 
    summarise_at(vars(!!quo_var), median, na.rm = TRUE) %&amp;gt;% 
    pull()
  
  # obter grafico
  p &amp;lt;- df %&amp;gt;% 
    group_by(topico) %&amp;gt;% 
    summarise(m = median(!!quo_var, na.rm = TRUE)) %&amp;gt;% 
    ggplot(aes(x = topico, y = m)) +
    geom_col(fill = roxo) +
    geom_hline(yintercept = mediana_geral, linetype = &amp;quot;dashed&amp;quot;) +
    theme_minimal() +
    coord_flip() +
    labs(x =  NULL, y = NULL)
  
  p
}
  
meu_grafico(df_topico, duration) + ggtitle(&amp;quot;Média da duração dos vídeos por tópico do Me Poupe&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2018-04-14-topic-modeling-nathalia-arcuri_files/figure-html/graficos-1.png&#34; width=&#34;864&#34; /&gt;&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;meu_grafico(df_topico, view_count) + ggtitle(&amp;quot;Média de views por tópico do Me Poupe&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2018-04-14-topic-modeling-nathalia-arcuri_files/figure-html/graficos-2.png&#34; width=&#34;864&#34; /&gt;&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;meu_grafico(df_topico, like_count) + ggtitle(&amp;quot;Média de likes por tópico do Me Poupe&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2018-04-14-topic-modeling-nathalia-arcuri_files/figure-html/graficos-3.png&#34; width=&#34;864&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Com estes gráficos, aprendemos que os vídeos sobre renda fixa, planejamento financeiro e empreendedorismo são os mais populares entre os “me poupeiros” (como a Nathalia chama quem acompanha o canal), pois possuem mais visualizações e curtidas. Outro fato interessante é que os vídeos categorizados como política são, por assim dizer, improdutivos, pois, apesar de serem os mais longos, possuem muitos poucos views. Existem alguns possíveis tipos de viés que poderiam explicar este dado, como o temporal.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;analise-de-sentimento&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Análise de Sentimento&lt;/h3&gt;
&lt;p&gt;Finalmente, vamos aplicar uma abordagem de &lt;a href=&#34;http://sillasgonzaga.com/post/o-sensacionalista-e-text-mining/&#34;&gt;análise de sentimento&lt;/a&gt; para analisar se os tópicos apresentam diferentes polaridades entre si. Quais tópicos são mais negativos?&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;data(&amp;quot;sentiLex_lem_PT02&amp;quot;)
dict &amp;lt;- unique(sentiLex_lem_PT02)

# criacao do dataframe de sentimento por topico
df_sent &amp;lt;- df %&amp;gt;% 
  unnest_tokens(palavra, caption) %&amp;gt;% 
  inner_join(sentiLex_lem_PT02, by = c(&amp;quot;palavra&amp;quot; = &amp;quot;term&amp;quot;)) %&amp;gt;% 
  group_by(id) %&amp;gt;% 
  summarise(
    sentimento_soma = sum(polarity),
    sentimento_media = mean(polarity)
  )

# acrescentar ao dafaframe principal
df_topico_sent &amp;lt;- inner_join(df_topico, df_sent, by = &amp;quot;id&amp;quot;) 


# calcular sentimento geral dos videos
sent_medio_geral &amp;lt;- median(df_topico_sent$sentimento_media)

df_topico_sent %&amp;gt;% 
  mutate(topico = forcats::fct_reorder(topico, sentimento_media, median)) %&amp;gt;% 
  ggplot(aes(x = sentimento_media, y = topico)) +
  geom_density_ridges(fill = roxo) +
  geom_vline(xintercept = sent_medio_geral, linetype = &amp;quot;dashed&amp;quot;) +
  theme_minimal() +
  labs(x = &amp;quot;Sentimento&amp;quot;, y = NULL,
       title = &amp;quot;Distribuição dos sentimentos por tópico do Me Poupe&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Picking joint bandwidth of 0.0756&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2018-04-14-topic-modeling-nathalia-arcuri_files/figure-html/analise_sent-1.png&#34; width=&#34;864&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Em novamente o que eu considero um acerto da metodologia, os vídeos de dívidas apresentaram polaridades mais baixas, ou sejas, um nível de sentimento mais negativo.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;conclusao&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;É relativamente difícil avaliar a acurácia e precisão de um algoritmo de topic modeling por essa técnica ser não-supervisionada, ou seja, os dados não possuírem um output com a resposta correta.&lt;/p&gt;
&lt;p&gt;Contudo, os resultados apresentados neste post mostram que é possível sim usar esse tipo de técnica, com um certo auxílio de interpretação humana, para categorizar um conjunto de vídeos sem ser necessário os assistir ou mesmo sem saber seus títulos.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;referencias&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Referências&lt;/h2&gt;
&lt;p&gt;Antes das referências formais, gostaria de indicar o post do &lt;a href=&#34;http://curso-r.com/blog/2018/02/23/2018-02-21-2cents/&#34;&gt;Júlio Trecenti&lt;/a&gt;, que é um outro exemplo de uso criativo de análise de dados para youtubers.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Topic_model&#34; class=&#34;uri&#34;&gt;https://en.wikipedia.org/wiki/Topic_model&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Mimno, D., Wallach, H. M., Talley, E., Leenders, M., &amp;amp; McCallum, A. (2011, July). “Optimizing semantic coherence in topic models.” In Proceedings of the Conference on Empirical Methods in Natural Language Processing (pp. 262-272). Association for Computational Linguistics. Chicago&lt;/p&gt;
&lt;p&gt;Roberts, M., Stewart, B., Tingley, D., Lucas, C., Leder-Luis, J., Gadarian, S., Albertson, B., et al. (2014). “Structural topic models for open ended survey responses.” American Journal of Political Science, 58(4), 1064-1082&lt;/p&gt;
&lt;p&gt;Margaret E. Roberts, Brandon M. Stewart and Dustin Tingley (2018). stm: R Package for Structural Topic Models. URL &lt;a href=&#34;http://www.structuraltopicmodel.com&#34; class=&#34;uri&#34;&gt;http://www.structuraltopicmodel.com&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Anunciando o lançamento de literaturaBR</title>
      <link>http://www.sillasgonzaga.com/post/literaturaBR-01/</link>
      <pubDate>Tue, 21 Nov 2017 00:00:00 +0000</pubDate>
      
      <guid>http://www.sillasgonzaga.com/post/literaturaBR-01/</guid>
      <description>&lt;div id=&#34;paixao-por-dados-de-cara-nova&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Paixão por Dados de cara nova!&lt;/h2&gt;
&lt;p&gt;O blog está de cara nova! O &lt;a href=&#34;www.sillasgonzaga.github.io&#34;&gt;endereço antigo&lt;/a&gt; do blog começou a apresentar alguns bugs bem chatos, então tomei a decisão de finalmente migrar para uma nova plataforma, utilizando o pacote &lt;code&gt;blogdown&lt;/code&gt;, a mesma que o pessoal do Curso-R usa no site deles. Para comemorar essa migração, anuncio o lançamento do meu terceiro pacote R: o &lt;code&gt;literaturaBR&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;literaturabr-o-mais-novo-pacote-da-comunidade-r-brasil&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;literaturaBR, o mais novo pacote da comunidade R Brasil&lt;/h2&gt;
&lt;p&gt;Após lançar o pacote &lt;a href=&#34;https://github.com/sillasgonzaga/lexiconPT&#34;&gt;&lt;code&gt;lexiconPT&lt;/code&gt;&lt;/a&gt;, senti que a carência de datasets textuais na língua portuguesa poderia restringir seu potencial de alcance de desenvolvedores e cientistas de dados interessados em usar os léxicos para fazer análise de sentimento. Apesar de ter feito um post mostrando seu uso em &lt;a href=&#34;http://www.sillasgonzaga.com/post/o-sensacionalista-e-text-mining/&#34;&gt;dados obtidos do Facebook&lt;/a&gt;, eu admito que é complicado ter que fazer web scraping de algum site toda vez que se deseja praticar ou ensinar mineração de texto com textos em Português.&lt;/p&gt;
&lt;p&gt;Esse problema me inspirou a desenvolver mais um pacote R para facilitar pequenas demonstrações de Text Mining. Assim como a Julia Silge criou o &lt;a href=&#34;https://github.com/juliasilge/janeaustenr&#34;&gt;&lt;code&gt;janeaustenr&lt;/code&gt;&lt;/a&gt; com livros clássicos da Jane Austen, eu criei o &lt;a href=&#34;https://github.com/sillasgonzaga/literaturaBR&#34;&gt;&lt;code&gt;literaturaBR&lt;/code&gt;&lt;/a&gt;, um &lt;em&gt;data package&lt;/em&gt; criado para disponibilizar livros clássicos da literatura brasileira já prontos para serem importados e manuseados no R. Para saber quais livros estão disponíveis na versão atual do pacote e outras informações úteis, visite seu repositório.&lt;/p&gt;
&lt;p&gt;Este post se destina a apresentar algumas exemplos simples de tarefas de Text Mining, como:&lt;br /&gt;
* Análise de Sentimento;&lt;br /&gt;
* Complexidade Léxica;&lt;br /&gt;
* Analise de ocorrência de palavras em específico.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;introducao&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Introdução&lt;/h2&gt;
&lt;p&gt;Os pacotes usados neste post são:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;library(literaturaBR) # meio obvio
library(tidytext) # excelente pacote de text mining
library(tidyverse) # &amp;lt;3
library(stringr) # indispensavel para manipulacao de texto
library(quanteda) # otimas funcoes para analise quantitativa de texto
library(qdap) # similar ao quanteda, embo ra eu nao me lembre exatamente se eu o uso neste post
library(forcats) # manipulacao de fatores
library(ggthemes) # temas para o ggplot2
library(lexiconPT)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vamos então importar os datasets presentes no &lt;code&gt;literaturaBR&lt;/code&gt; na data de hoje e os transformar em um dataset só:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;data(&amp;quot;memorias_de_um_sargento_de_milicias&amp;quot;)
data(&amp;quot;memorias_postumas_bras_cubas&amp;quot;)
data(&amp;quot;alienista&amp;quot;)
data(&amp;quot;escrava_isaura&amp;quot;)
data(&amp;quot;ateneu&amp;quot;)

df &amp;lt;- bind_rows(memorias_de_um_sargento_de_milicias,
                memorias_postumas_bras_cubas,
                alienista,
                escrava_isaura,
                ateneu)


# Olhando a estrutura do dataframe
glimpse(df)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Observations: 5,149
## Variables: 5
## $ book_name        &amp;lt;chr&amp;gt; &amp;quot;Memórias de um Sargento de Milícias&amp;quot;, &amp;quot;Memór...
## $ chapter_name     &amp;lt;chr&amp;gt; &amp;quot;Capítulo 1 - Origem, nascimento e batismo&amp;quot;, ...
## $ url              &amp;lt;chr&amp;gt; &amp;quot;https://pt.wikisource.org/wiki/Mem%C3%B3rias...
## $ paragraph_number &amp;lt;int&amp;gt; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, ...
## $ text             &amp;lt;chr&amp;gt; &amp;quot;Era no tempo do rei.&amp;quot;, &amp;quot;Uma das quatro esqui...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Todos&lt;/strong&gt; os datasets fornecidos pelo &lt;code&gt;literaturaBR&lt;/code&gt; possuem a mesma estrutura, onde cada linha corresponde a um parágrafo de um livro e contêm 5 variáveis:&lt;br /&gt;
* &lt;code&gt;book_name&lt;/code&gt;: Nome original do livro;&lt;br /&gt;
* &lt;code&gt;chapter_name&lt;/code&gt;: Nome original do capítulo do livro do parágrafo;&lt;br /&gt;
* &lt;code&gt;url&lt;/code&gt;: Link para artigo do Wikisouce de onde o capítulo do parágrafo foi extraído;&lt;br /&gt;
* &lt;code&gt;paragraph_number&lt;/code&gt;: Ordem do parágrafo em seu capítulo;&lt;br /&gt;
* &lt;code&gt;text&lt;/code&gt;: Texto do parágrafo. Contem acentos e pontuação.&lt;/p&gt;
&lt;p&gt;Um rápido adendo: os nomes das colunas está em inglês porque só tomei a decisão de escrever toda a documentação do pacote em Português meio tardiamente.&lt;/p&gt;
&lt;div id=&#34;analise-basica&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Análise básica&lt;/h3&gt;
&lt;p&gt;Para usar (parte) das funções do pacote &lt;code&gt;quanteda&lt;/code&gt;, precisamos converter o dataframe dos livros em um objeto do tipo &lt;code&gt;corpus&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df_corpus &amp;lt;- df %&amp;gt;% 
  # agrupar por livro
  group_by(book_name) %&amp;gt;% 
  # formatar o dataframe para que so tenha uma linha por livro
  summarise(text = paste0(text, sep = &amp;quot;&amp;quot;, collapse = &amp;quot;. &amp;quot;))

dim(df_corpus)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## [1] 5 2&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;meu_corpus &amp;lt;- quanteda::corpus(df_corpus$text, docnames = df_corpus$book_name)
summary(meu_corpus)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Corpus consisting of 5 documents:
## 
##                                 Text Types Tokens Sentences
##                     A escrava Isaura  9349  64929      1751
##  Memórias de um Sargento de Milícias  8703  71391      2287
##      Memórias Póstumas de Brás Cubas 11058  74223      3199
##                          O Alienista  4387  19977       770
##                             O Ateneu 15341  73063      3551
## 
## Source:  /home/sillas/R/Projetos/paixaopordados-blogdown/content/post/* on x86_64 by sillas
## Created: Wed Nov 22 20:34:35 2017
## Notes:&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Como vemos, a função &lt;code&gt;quanteda::corpus()&lt;/code&gt; identificou a quantidade de &lt;em&gt;Types&lt;/em&gt; (número de palavras distintas em um corpus), &lt;em&gt;Tokens&lt;/em&gt; (número total de palavras em um corpus) e &lt;em&gt;Sentences&lt;/em&gt; (frases) em cada livro.&lt;/p&gt;
&lt;p&gt;Vamos então criar uma &lt;em&gt;document-feature matrix&lt;/em&gt; a partir desse corpus criado, tomando o cuidade de remover pontuações e stopwords:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;corpus_dfm &amp;lt;- dfm(meu_corpus, remove_punct = TRUE,
                  remove = quanteda::stopwords(&amp;quot;portuguese&amp;quot;),
                  groups = df_corpus$book_name)

# Analisando as 15 palavras mais comuns no geral por livro
dfm_sort(corpus_dfm)[, 1:15]&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Document-feature matrix of: 5 documents, 15 features (5.33% sparse).
## 5 x 15 sparse Matrix of class &amp;quot;dfm&amp;quot;
##                                      features
## docs                                    é casa tempo ainda tudo bem todos
##   A escrava Isaura                    424   98    81   121   78 175    78
##   Memórias de um Sargento de Milícias 305  193   207   131  192 126   139
##   Memórias Póstumas de Brás Cubas     492  107   108    89   98  65    69
##   O Alienista                          88  108    20    29   26  10    37
##   O Ateneu                            199   51    56    97   56  56    91
##                                      features
## docs                                  tão ser havia   d leonardo coisa
##   A escrava Isaura                    137  96    56  20        0    35
##   Memórias de um Sargento de Milícias  63  75   153 194      366   128
##   Memórias Póstumas de Brás Cubas      95  97    53  83        0   140
##   O Alienista                          31  25     7  44        0    23
##   O Ateneu                             47  78   101  26        0    38
##                                      features
## docs                                  disse dia
##   A escrava Isaura                       67  41
##   Memórias de um Sargento de Milícias   119 128
##   Memórias Póstumas de Brás Cubas       130  89
##   O Alienista                            30  21
##   O Ateneu                               11  77&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A função &lt;code&gt;dfm_sort()&lt;/code&gt; retorna a ocorrência de cada palavra (também chamado de token ou feature) em cada livro. Para pesquisar a ocorrência de alguma palavra específica nos documentos, use a função &lt;code&gt;dfm_select()&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# ocorrencias da palavra amor
dfm_select(corpus_dfm, &amp;quot;amor&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Document-feature matrix of: 5 documents, 1 feature (0% sparse).
## 5 x 1 sparse Matrix of class &amp;quot;dfm&amp;quot;
##                                      features
## docs                                  amor
##   A escrava Isaura                      71
##   Memórias de um Sargento de Milícias   30
##   Memórias Póstumas de Brás Cubas       53
##   O Alienista                            3
##   O Ateneu                              45&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Curioso para saber o contexto em que essa palavra aparece? Você pode usar a função &lt;code&gt;kwic()&lt;/code&gt; para isso:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# usar a função head() para o output nao ficar mt grande
kwic(meu_corpus, &amp;quot;amor&amp;quot;) %&amp;gt;% head()&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;##                                                              
##  [A escrava Isaura, 2011]    , bem pode conquistar o | amor |
##  [A escrava Isaura, 5090]    não se havia casado por | amor |
##  [A escrava Isaura, 5173]     o mais cego e violento | amor |
##  [A escrava Isaura, 6555] pudesse obter também o teu | amor |
##  [A escrava Isaura, 7215]          Ora, senhor, pelo | amor |
##  [A escrava Isaura, 7686]  disputar com o senhor por | amor |
##                         
##  de algum guapo mocetão,
##  , sentimento esse a que
##  , que de dia em        
##  !... És                
##  de Deus!..             
##  de uma escrava..&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Para saber as palavras mais usadas, dentre os vários métodos possíveis para isso, pode-se usar a função &lt;code&gt;topfeatures()&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;topfeatures(corpus_dfm, groups = df_corpus$book_name)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## $`A escrava Isaura`
##       é  isaura  senhor leôncio  álvaro escrava     bem     tão     pai 
##     424     344     243     205     188     180     175     137     127 
##   ainda 
##     121 
## 
## $`Memórias de um Sargento de Milícias`
## leonardo        é    maria  comadre    tempo    porém        d     casa 
##      366      305      231      209      207      205      194      193 
##     tudo    major 
##      192      179 
## 
## $`Memórias Póstumas de Brás Cubas`
##        é virgília    coisa    olhos    disse     nada    outro    outra 
##      492      199      140      138      130      125      122      116 
##     vida   porque 
##      116      113 
## 
## $`O Alienista`
##      casa alienista         é     verde bacamarte  barbeiro    câmara 
##       108       106        88        77        59        54        52 
##   itaguaí     simão  evarista 
##        49        49        45 
## 
## $`O Ateneu`
##         é     sobre aristarco   diretor     havia     ainda    ateneu 
##       199       167       148       104       101        97        93 
##     todos       ser      dois 
##        91        78        77&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&#34;analise-comparativa-entre-os-livros&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Análise comparativa entre os livros&lt;/h3&gt;
&lt;p&gt;Algo interessante a se fazer é quantificar a similaridade e a dissimilaridade ou distância entre os livros. As funções &lt;code&gt;textstat_simil&lt;/code&gt; e &lt;code&gt;textstat_dist&lt;/code&gt; implementam diversas técnicas e algoritmos para isso. Sugiro ler a documentação completa das funções e ler as referências indicadas para conhecer melhor os métodos de cálculo.&lt;/p&gt;
&lt;p&gt;Vamos então calcular a similaridade entre os livros presentes no pacote:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# normalizar os livros pelo seu tamanho
corpus_dfm_norm &amp;lt;- dfm_weight(corpus_dfm, &amp;quot;relfreq&amp;quot;)
corpus_simil &amp;lt;- textstat_simil(corpus_dfm_norm, method = &amp;quot;correlation&amp;quot;,
                               margin = &amp;quot;documents&amp;quot;, upper = TRUE,
                               diag = FALSE)
# ver os resultados individualmente para cada livro
round(corpus_simil, 3)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;##                                     A escrava Isaura
## A escrava Isaura                                    
## Memórias de um Sargento de Milícias            0.524
## Memórias Póstumas de Brás Cubas                0.625
## O Alienista                                    0.431
## O Ateneu                                       0.512
##                                     Memórias de um Sargento de Milícias
## A escrava Isaura                                                  0.524
## Memórias de um Sargento de Milícias                                    
## Memórias Póstumas de Brás Cubas                                   0.642
## O Alienista                                                       0.502
## O Ateneu                                                          0.550
##                                     Memórias Póstumas de Brás Cubas
## A escrava Isaura                                              0.625
## Memórias de um Sargento de Milícias                           0.642
## Memórias Póstumas de Brás Cubas                                    
## O Alienista                                                   0.585
## O Ateneu                                                      0.631
##                                     O Alienista O Ateneu
## A escrava Isaura                          0.431    0.512
## Memórias de um Sargento de Milícias       0.502    0.550
## Memórias Póstumas de Brás Cubas           0.585    0.631
## O Alienista                                        0.450
## O Ateneu                                  0.450&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alguns resultados são bem interessantes. O Alienista é mais diferente de todos, tendo uma correlação superior a 0,51 apenas com o livro Memórias Póstumas de Brás Cubas, coincidentemente ou não ambos do mesmo autor. A maior correlação pertence aos livros Memórias Póstumas de Brás Cubas e Memórias de um Sargento de Milícias.&lt;/p&gt;
&lt;p&gt;Passemos então para a análise da dissimilaridade ou distância entre os livros, com o auxílio de um dendograma:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;corpus_dist &amp;lt;- textstat_dist(corpus_dfm_norm, method = &amp;quot;euclidean&amp;quot;,
                               margin = &amp;quot;documents&amp;quot;, upper = TRUE,
                               diag = FALSE)
# ver os resultados individualmente para cada livro
plot(hclust(corpus_dist))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2017-11-21-literaturaBR-01_files/figure-html/dist-1.png&#34; width=&#34;672&#34; /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;analise-de-sentimento&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Análise de Sentimento&lt;/h3&gt;
&lt;p&gt;Como já publiquei no blog um post sobre Análise de Sentimento, não vou me alongar em repetir conceitos sobre o tema. Segue o código comentado:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Criar um dataframe em que cada linha corresponda a uma unica palavra
df.token &amp;lt;- df %&amp;gt;%
  unnest_tokens(term, text)

glimpse(df.token)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Observations: 252,299
## Variables: 5
## $ book_name        &amp;lt;chr&amp;gt; &amp;quot;Memórias de um Sargento de Milícias&amp;quot;, &amp;quot;Memór...
## $ chapter_name     &amp;lt;chr&amp;gt; &amp;quot;Capítulo 1 - Origem, nascimento e batismo&amp;quot;, ...
## $ url              &amp;lt;chr&amp;gt; &amp;quot;https://pt.wikisource.org/wiki/Mem%C3%B3rias...
## $ paragraph_number &amp;lt;int&amp;gt; 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, ...
## $ term             &amp;lt;chr&amp;gt; &amp;quot;era&amp;quot;, &amp;quot;no&amp;quot;, &amp;quot;tempo&amp;quot;, &amp;quot;do&amp;quot;, &amp;quot;rei&amp;quot;, &amp;quot;uma&amp;quot;, &amp;quot;da...&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# importar lexico de sentimentos
data(&amp;quot;oplexicon_v3.0&amp;quot;)
df.token &amp;lt;- df.token %&amp;gt;%
  inner_join(oplexicon_v3.0, by = &amp;quot;term&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Um pergunta muito interessante a se fazer é se o sentimento varia ao longo dos capítulos dos livros. Os livros ficam mais (ou menos) positivos a medida em que se aproximam do final?&lt;/p&gt;
&lt;p&gt;Para isso, primeiro precisamos normalizar os livros, visto que eles apresentam tamanhos e quantidades de capítulos diferentes.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# extrair capitulos de cada livro
df_chapter_number &amp;lt;- df.token %&amp;gt;%
  distinct(book_name, chapter_name) %&amp;gt;%
  group_by(book_name) %&amp;gt;%
  # normalizar capitulo de acordo com sua posicao no livro
  mutate(chapter_number_norm = row_number()/max(row_number()))

glimpse(df_chapter_number)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Observations: 252
## Variables: 3
## $ book_name           &amp;lt;chr&amp;gt; &amp;quot;Memórias de um Sargento de Milícias&amp;quot;, &amp;quot;Me...
## $ chapter_name        &amp;lt;chr&amp;gt; &amp;quot;Capítulo 1 - Origem, nascimento e batismo...
## $ chapter_number_norm &amp;lt;dbl&amp;gt; 0.02083333, 0.04166667, 0.06250000, 0.0833...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agora calculamos o sentimento de cada capítulo, que corresponde à soma da polaridade de suas palavras:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df.sentiment &amp;lt;- df.token %&amp;gt;%
  # calcular sentimento por capitulo
  group_by(book_name, chapter_name) %&amp;gt;%
  summarise(polarity = sum(polarity, na.rm = TRUE)) %&amp;gt;%
  ungroup() %&amp;gt;%
  # retornar posicao relativa (ou normalizada) do capitulo de cada livro
  left_join(df_chapter_number) %&amp;gt;%
  arrange(book_name, chapter_number_norm)

# grafico
df.sentiment %&amp;gt;%
  ggplot(aes(x = chapter_number_norm, y = polarity)) +
    geom_line() +
    facet_wrap(~ book_name, ncol = 5, labeller = label_wrap_gen(20)) +
    labs(x = &amp;quot;Posição relativa no livro&amp;quot;, y = &amp;quot;Sentimento&amp;quot;) +
    theme_bw()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2017-11-21-literaturaBR-01_files/figure-html/sentimento%20por%20capitulo-1.png&#34; width=&#34;768&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Os resultados são muito interessantes: De acordo com esse método, &lt;strong&gt;A Escrava Isaura&lt;/strong&gt; e &lt;strong&gt;O Ateneu&lt;/strong&gt; são verdadeiras montanhas-russas de emoções, enquanto que livros os “dois Memórias” são mais estáveis. &lt;strong&gt;O Alienista&lt;/strong&gt; apresenta um sentimento que tem uma tendência decrescente até a metade e crescente após ela.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;complexidade-lexica-e-ocorrencia-de-palavras&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Complexidade léxica e ocorrência de palavras&lt;/h3&gt;
&lt;p&gt;A função &lt;code&gt;textstat_lexdiv&lt;/code&gt; traz várias métricas de complexidade e diversidade léxicas. Novamente, recomendo a leitura de sua documentação para conhecer as métricas disponíveis.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# aplicando a funcao no objeto sem stopwords e pontuação
lexdiv &amp;lt;- textstat_lexdiv(corpus_dfm, measure = &amp;quot;TTR&amp;quot;)
lexdiv&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;##                    A escrava Isaura Memórias de um Sargento de Milícias 
##                           0.3062233                           0.2517707 
##     Memórias Póstumas de Brás Cubas                         O Alienista 
##                           0.3139269                           0.4291334 
##                            O Ateneu 
##                           0.4134223&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;#grafico
lexdiv %&amp;gt;% 
  as.data.frame() %&amp;gt;% 
  magrittr::set_colnames(&amp;quot;TTR&amp;quot;) %&amp;gt;% 
  tibble::rownames_to_column(&amp;quot;livro&amp;quot;) %&amp;gt;% 
  mutate(livro = forcats::fct_reorder(livro, TTR)) %&amp;gt;% 
  ggplot(aes(x = livro, y = TTR)) + 
    geom_col(fill = &amp;quot;cadetblue4&amp;quot;) +
    coord_flip() + 
    labs(x = NULL, y = &amp;quot;TTR&amp;quot;) +
    theme_minimal()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2017-11-21-literaturaBR-01_files/figure-html/plot%20ttr-1.png&#34; width=&#34;672&#34; /&gt;&lt;/p&gt;
&lt;p&gt;O livro que apresenta a maior diversidade léxica, de acordo com a métrica &lt;em&gt;Type-Token Ratio&lt;/em&gt; (TTR), é &lt;strong&gt;O Alienista&lt;/strong&gt;. &lt;strong&gt;Memórias de um Sargento de Milícias&lt;/strong&gt; vem bem atrás dos demais.&lt;/p&gt;
&lt;p&gt;De todas os gráficos que apresentei neste post, o mais legal vem a seguir. É possível plotar a ocorrência de uma determinada palavra ao longo dos livros analisados. Por exemplo, a ocorrência da palavra amor é uniformemente distribuída nos livros?&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;kwic(meu_corpus, &amp;quot;amor&amp;quot;) %&amp;gt;% textplot_xray(scale = &amp;quot;relative&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2017-11-21-literaturaBR-01_files/figure-html/plot%20xray-1.png&#34; width=&#34;672&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Com o gráfico acima, é possível ver que a palavra amor aparece, no livro Ateneu, muito mais frequentemente na primeira metade do que na segunda. Nos demais livros, a ocorrência da palavra é mais uniforme.&lt;/p&gt;
&lt;p&gt;Outro exemplo interessante surge com a busca da palavra fogo. Quem já leu O Ateneu já consegue imaginar como serão os resultados:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;kwic(meu_corpus, &amp;quot;fogo&amp;quot;) %&amp;gt;% textplot_xray(scale = &amp;quot;relative&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2017-11-21-literaturaBR-01_files/figure-html/unnamed-chunk-1-1.png&#34; width=&#34;672&#34; /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;conclusao&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;Eu realmente torço para que o &lt;code&gt;literaturaBR&lt;/code&gt; e o &lt;code&gt;lexiconPT&lt;/code&gt; atinjam seus potenciais e sejam dois grandes marcos em pesquisas em Text Mining com textos em português. Toda sugestão ou pedido de melhorias serão muitíssimos bem-vindos.&lt;/p&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Mineração de textos em notícias de G1: O que diferencia notícias sobre Rio de Janeiro e São Paulo?</title>
      <link>http://www.sillasgonzaga.com/post/analise-g1-01/</link>
      <pubDate>Sun, 15 Oct 2017 00:00:00 +0000</pubDate>
      
      <guid>http://www.sillasgonzaga.com/post/analise-g1-01/</guid>
      <description>&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;library(rvest)
library(tidyverse)
library(magrittr)
library(stringr)
library(Rfacebook)
library(tidytext)
library(tm)&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&#34;motivacao-para-o-post&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Motivação para o post&lt;/h2&gt;
&lt;p&gt;Apesar de hoje em dia eu morar no Rio de Janeiro, morei e vivi (quase) a vida toda em Aracaju, a capital do menor estado do Brasil. Devido à irrelevância que a cidade tem (desculpa mas é verdade) no cenário político e econômico do país, era (e ainda é) muito raro ver qualquer notícia em um veículo de audiência nacional (como o Jornal Nacional ou a homepage do G1 ou Estadão) relacionada a Aracaju ou a Sergipe que não seja desgraça ou por um acontecimento inusitadamente ruim. Voltei a notar isso recentemente, quando saiu na homepage do G1 uma notícia de Sergipe sobre o &lt;a href=&#34;https://g1.globo.com/se/sergipe/noticia/camarote-desaba-durante-show-de-ivete-sangalo-em-aracaju.ghtml?utm_source=facebook&amp;amp;utm_medium=social&amp;amp;utm_campaign=g1&#34;&gt;desabamento de um camarote durante a Odonto Fantasy&lt;/a&gt;, uma das maiores festas a fantasia do Brasil.&lt;/p&gt;
&lt;p&gt;Como bom entusiasta por Data Science, decidi não me ater ao senso comum de que Sergipe só tem destaque nacional por motivos de desgraça e sujei as mãos: é possível testar a hipótese de que certos temas são mais associados a algumas cidades que outras? O que destaca as notícias sobre estados menores que saem na homepage do G1 comparadas com as de estados maiores?&lt;/p&gt;
&lt;p&gt;Consigo pensar em pelo menos dois (possivelmente existem mais) métodos para realizar essa análise: além da análise de frequência mencionada acima (a palavra X está mais associada a cidade Y1 do que a cidade Y2), poderia ser aplicada uma análise de sentimento para distinguir o sentimento de notícias relacionadas a Aracaju em comparação com o de cidades maiores. Neste post, o foco será na primeira alternativa.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;coleta-dos-dados&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Coleta dos dados&lt;/h2&gt;
&lt;p&gt;Vejam que fui enfático sobre a notícia em si ter saído na homepage do G1 ou não. Ou seja, não basta ela ter saído na página local do estado (como a de &lt;a href=&#34;http://g1.globo.com/se/&#34;&gt;Sergipe&lt;/a&gt;). O primeiro problema é então ter um histórico de notícias que saíram na primeira página, o que não existe em nenhum lugar público na Internet. Não bastaria entrar na página de cada estado e coletar as notícias. Nesta análise, é fundamental ter a certeza de que pessoas de todo o Brasil puderam ter acesso fácil ao artigo.&lt;/p&gt;
&lt;p&gt;Para resolver o problema, utilizei o pressuposto de que, se a notícia foi postada na &lt;a href=&#34;https://www.facebook.com/g1&#34;&gt;página do G1 no Facebook&lt;/a&gt;, é porque ela saiu na home. Não sei o quanto que isso é verdade, mas acredito que seja uma hipótese válida.&lt;/p&gt;
&lt;p&gt;Para extrair os links postados na página do G1 no Facebook, utilizamos o pacote &lt;code&gt;Rfacebook&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;token &amp;lt;- readRDS(&amp;quot;/home/sillas/R/facebook_token.Rds&amp;quot;)
g1 &amp;lt;- Rfacebook::getPage(&amp;quot;180562885329138&amp;quot;, token = token, n = 5000)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O intervalo pesquisado é:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;range(sort(g1$created_time))&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## [1] &amp;quot;2006-09-18T07:00:00+0000&amp;quot; &amp;quot;2017-10-14T20:46:01+0000&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;g1_links &amp;lt;- g1$link&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nem todos os links postados pela página redirecionam ao G1: alguns são de vídeos postados no próprio Facebook. Por isso, é necessário limpar os dados:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# remover links de videos do facebook
g1_links &amp;lt;- g1_links[!str_detect(g1_links, &amp;quot;www.facebook.com&amp;quot;)]

# ver links mais comuns
str_replace_all(g1_links, &amp;quot;http://|https://&amp;quot;, &amp;quot;&amp;quot;) %&amp;gt;% str_replace_all(&amp;quot;/.*&amp;quot;, &amp;quot;&amp;quot;) %&amp;gt;% table()&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## .
##                 bit.ly   fantastico.globo.com           g1.globo.com 
##                      8                      1                    717 
##                 glo.bo globoesporte.globo.com        media.giphy.com 
##                    895                      8                      1 
##          s2.glbimg.com 
##                      1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ainda existem alguns links meio nebulosos. Vamos nos ater aos que são do G1 (&lt;em&gt;g1.globo.com&lt;/em&gt;) e do encurtador de URLs da Globo.com (&lt;em&gt;glo.bo&lt;/em&gt;). Para estes últimos, como precisamos extrair o estado da notícia da URL (de &lt;a href=&#34;https://g1.globo.com/se/sergipe/noticia/camarote-desaba-durante-show-de-ivete-sangalo-em-aracaju.ghtml&#34; class=&#34;uri&#34;&gt;https://g1.globo.com/se/sergipe/noticia/camarote-desaba-durante-show-de-ivete-sangalo-em-aracaju.ghtml&lt;/a&gt; precisamos extrair &lt;strong&gt;“se”&lt;/strong&gt;, a sigla de Sergipe), uso uma função postada no meu Gist para converter uma URL encurtada em normal:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# manter apenas links glo.bo e g1.globo.com
g1_links &amp;lt;- g1_links[str_detect(g1_links, &amp;quot;g1.globo.com|glo.bo&amp;quot;) &amp;amp; !is.na(g1_links)]
# carregar funcao para transformar url encurtada
devtools::source_gist(&amp;quot;2e92f880811f460c7edd0a622563a17a&amp;quot;, filename = &amp;quot;getLongURL.R&amp;quot;)
# converter links glo.bo para urls inteiras
ind &amp;lt;- str_which(g1_links, &amp;quot;glo.bo&amp;quot;)
getLongURL.curl.possibly &amp;lt;- possibly(getLongURL.curl, otherwise = NA)
novos_links &amp;lt;- g1_links[ind] %&amp;gt;% map_chr(getLongURL.curl.possibly)
g1_links[ind] &amp;lt;- novos_links
# manter apenas links glo.bo e g1.globo.com (para remover links de outros sites da globo, como o Globoesporte.com)
g1_links &amp;lt;- g1_links[str_detect(g1_links, &amp;quot;g1.globo.com|glo.bo&amp;quot;) &amp;amp; !is.na(g1_links)]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tendo os links corrigidos, podemos então criar uma função que extraia o corpo da notícia do link. Como o foco deste post não é o Web Scraping em si e sim os dados resultantes dele, não vou descrever muito o que a função abaixo faz. Em caso de dúvidas, escreva nos comentários do post ou entre em contato comigo que eu responderei com grande prazer.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# criar função
extrair_g1 &amp;lt;- function(url_g1){
  # exemplo de url:
  # &amp;quot;https://g1.globo.com/rio-de-janeiro/noticia/imagens-mostram-acao-de-criminosos-durante-assalto-na-zona-norte-do-rio.ghtml&amp;quot;
  if (!str_detect(url_g1, &amp;quot;glo.bo&amp;quot;) &amp;amp; !str_detect(url_g1, &amp;quot;g1.globo&amp;quot;)){
    warning(paste0(&amp;quot;Link inválido: &amp;quot;, url_g1))
    return(NA)
  }
  
  x &amp;lt;- url_g1 %&amp;gt;% read_html() 
  # extrair sessao da noticia
  css_nome_caderno &amp;lt;- &amp;quot;.header-editoria--link&amp;quot;
  caderno &amp;lt;- x %&amp;gt;% html_nodes(css_nome_caderno) %&amp;gt;% html_text()
  if (length(caderno) == 0) caderno &amp;lt;- NA
  # extrair corpo da noticia
  css_corpo_a &amp;lt;- &amp;quot;.content-text__container&amp;quot;
  css_corpo_b &amp;lt;- &amp;quot;#materia-letra p&amp;quot;
  css_corpo_c &amp;lt;- &amp;quot;.post-content p&amp;quot;
  
  corpo_noticia &amp;lt;- x %&amp;gt;% 
    html_nodes(css_corpo_a) %&amp;gt;% 
    html_text()
  # se o css selector nao funcionou, use outro:
  if (length(corpo_noticia) == 0) {
    corpo_noticia &amp;lt;- x %&amp;gt;% 
      html_nodes(css_corpo_b) %&amp;gt;% 
      html_text()
  } 
  
  if (length(corpo_noticia) == 0) {
    corpo_noticia &amp;lt;- x %&amp;gt;% 
      html_nodes(css_corpo_c) %&amp;gt;% 
      html_text()
  } 
  # colapsar corpo da noticia em um vetor de elemento unico
  corpo_noticia &amp;lt;- paste0(corpo_noticia, collapse = &amp;quot;. &amp;quot;)
  
  
  tibble(url = url_g1, caderno = caderno, corpo_noticia = corpo_noticia)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# agora sim extrair dados do g1:
df_g1 &amp;lt;- g1_links %&amp;gt;% map_df(extrair_g1)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# extrair caderno da url da noticia
df_g1$documento  &amp;lt;- df_g1$url %&amp;gt;% 
  str_replace_all(&amp;quot;https&amp;quot;, &amp;quot;http&amp;quot;) %&amp;gt;% 
  str_replace_all(&amp;quot;http://g1.globo.com/&amp;quot;, &amp;quot;&amp;quot;) %&amp;gt;% 
  str_replace_all(&amp;quot;/.*&amp;quot;, &amp;quot;&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Uma amostra mínima dos dados coletados:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;str(df_g1[1,])&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Classes &amp;#39;tbl_df&amp;#39;, &amp;#39;tbl&amp;#39; and &amp;#39;data.frame&amp;#39;:    1 obs. of  4 variables:
##  $ url          : chr &amp;quot;https://g1.globo.com/politica/operacao-lava-jato/noticia/occhi-tinha-meta-mensal-de-propina-para-distribuir-a-p&amp;quot;| __truncated__
##  $ caderno      : chr &amp;quot;Operação Lava Jato&amp;quot;
##  $ corpo_noticia: chr &amp;quot; O doleiro Lúcio Funaro afirmou em seu acordo de delação premiada com a Procuradoria Geral da República (PGR) q&amp;quot;| __truncated__
##  $ documento    : chr &amp;quot;politica&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agora podemos partir para a análise. Mas antes, um pouco de contexto teórico:&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;tf-idf&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;tf-idf&lt;/h2&gt;
&lt;p&gt;O &lt;strong&gt;Fator de Frequência Inversa no Documento&lt;/strong&gt; ou &lt;em&gt;term’s inverse document frequency&lt;/em&gt; (tf-idf) é uma técnica de mineração de texto que calcula a frequência de um determinado elemento textual (como uma palavra ou um n-gram) em um documento, diminuindo o peso de palavras comumente usadas em vários contextos diferentes (como “a”, “o” e “de”) e aumentando o peso de palavras que não são usadas em outros documentos da coleção.&lt;/p&gt;
&lt;p&gt;No glossário de mineração de texto, documento nada mais é do que uma coleção que categoriza um conjunto de textos. Pode ser um livro (inteiro ou apenas um ou mais capítulos), a letra de uma música, um poema, uma notícia, um conjunto de notícias, etc. No nosso contexto, definimos como documento as notícias do G1 coletadas nessa amostra sobre um determinado tema, como Rio de Janeiro, São Paulo, Política ou Economia. Daí o nome da última coluna.&lt;/p&gt;
&lt;p&gt;O livro online &lt;a href=&#34;http://tidytextmining.com/tfidf.html&#34;&gt;Text Mining with R&lt;/a&gt; é uma ótima referência para tf-idf, tanto teórica quanto prática.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;resultados&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Resultados&lt;/h2&gt;
&lt;p&gt;Quais são os documentos mais comuns?&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df_g1$documento %&amp;gt;% table() %&amp;gt;% sort() %&amp;gt;% rev()&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## .
##                   mundo               sao-paulo          rio-de-janeiro 
##                     205                     158                     118 
##                pop-arte              tecnologia                economia 
##                     110                     102                      99 
##                politica                      sp         ciencia-e-saude 
##                      95                      73                      60 
##         planeta-bizarro                bemestar                  musica 
##                      37                      30                      29 
##        distrito-federal                      mg                      pr 
##                      28                      27                      26 
##                  carros   vestibular-e-educacao                   goias 
##                      24                      21                      21 
##                  brasil                educacao             rock-in-rio 
##                      21                      18                      17 
##                natureza          espirito-santo                      sc 
##                      17                      15                      13 
##                      rs           resumo-do-dia            minas-gerais 
##                      13                      13                      13 
##             mato-grosso          11-de-setembro                   bahia 
##                      13                      13                      12 
##                vc-no-g1                  parana           agenda-do-dia 
##                      10                      10                       9 
##                      pi         bienal-do-livro                      rj 
##                       8                       8                       7 
##                     swu              pernambuco                      pb 
##                       6                       6                       6 
##      mato-grosso-do-sul                loterias                      rn 
##                       6                       6                       5 
##                      to                      rr                      ro 
##                       4                       4                       4 
##           revolta-arabe                 paraiba                      pa 
##                       4                       4                       4 
##              fantastico        dia-das-criancas                      ac 
##                       4                       4                       4 
##        turismo-e-viagem       rihanna-no-brasil                   platb 
##                       3                       3                       3 
## justin-bieber-no-brasil              e-ou-nao-e                   ceara 
##                       3                       3                       3 
##                  videos                      se                      ma 
##                       2                       2                       2 
##                 loteria                      ap                      am 
##                       2                       2                       2 
##                      al                    spfw          reveillon-2012 
##                       2                       1                       1 
##    monitor-da-violencia   especial-publicitario               especiais 
##                       1                       1                       1 
##            dia-dos-pais       dia-dos-namorados     concursos-e-emprego 
##                       1                       1                       1 
##                amazonas 
##                       1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As sessões do G1 que mais marcam presença na homepage são, sem muita surpresa, &lt;a href=&#34;http://g1.globo.com/mundo/&#34;&gt;Mundo&lt;/a&gt;, &lt;a href=&#34;http://g1.globo.com/sao-paulo/&#34;&gt;São Paulo&lt;/a&gt; e &lt;a href=&#34;http://g1.globo.com/rio-de-janeiro/&#34;&gt;Rio de Janeiro&lt;/a&gt;. Você pode ter percebido que existe tanto um documento chamado “sao-paulo” como “sp”, assim como “rio-de-janeiro” e “rj”. Isso acontece porque o G1, para os maiores estados, tem sessões separadas para a capital e para o resto das cidades.&lt;/p&gt;
&lt;p&gt;Os dois primeiros objetos de estudo são as cidades mais famosas do Brasil: Rio de Janeiro e São Paulo. O passo-a-passo da aplicação do tf-idf para essas duas cidades é descrito abaixo:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;docs &amp;lt;- c(&amp;quot;rio-de-janeiro&amp;quot;, &amp;quot;sao-paulo&amp;quot;)

df_filtro  &amp;lt;- df_g1 %&amp;gt;%
  #mutate(caderno_url = if_else(caderno %in% vetor_cadernos, &amp;quot;Selecionado&amp;quot;, &amp;quot;Outros&amp;quot;))
  filter(documento %in% docs)

temp &amp;lt;- df_filtro %&amp;gt;% 
  # separar cada palavra do corpo da notícia em uma linha diferente, sem converter para minusculo
  unnest_tokens(palavra, corpo_noticia, token = &amp;quot;ngrams&amp;quot;, n = 1, to_lower = FALSE) %&amp;gt;% 
  # remover as duplicatas, isto é, as palavras que aparecem mais de uma vez em uma mesma noticia
  distinct(url, palavra, .keep_all = TRUE) %&amp;gt;% 
  # contar a ocorrencia de cada palavra em cada doc
  count(documento, palavra) %&amp;gt;% 
  tidytext::bind_tf_idf(term = palavra, document = documento, n = n) %&amp;gt;%  
  # ordenar as palavras com menor tf_idf por documento
  group_by(documento) %&amp;gt;% 
  arrange(desc(tf_idf)) %&amp;gt;% 
  mutate(palavra = factor(palavra, levels = rev(unique(palavra)))) %&amp;gt;% 
  #filtrar as 20 de maior destaque
  filter(row_number() &amp;lt;= 20) %&amp;gt;% 
  ungroup() %&amp;gt;% 
  arrange(desc(tf_idf)) %&amp;gt;% 
  mutate(palavra = factor(palavra, levels = rev(unique(palavra))))
    

# construir grafico
temp %&amp;gt;% 
  ggplot(aes(palavra, tf_idf, fill = documento)) +
      geom_col(show.legend = FALSE) +
      geom_text(aes(label = n), hjust = 1.3) +
      labs(x = NULL, y = &amp;quot;tf-idf&amp;quot;,
           title = &amp;quot;Termos mais frequentes associados a apenas uma das cidades&amp;quot;,
           caption = &amp;quot;Fonte: Mineração de texto aplicada em notícias do G1.&amp;quot;) +
      facet_wrap(~documento, ncol = 2, scales = &amp;quot;free&amp;quot;) +
      coord_flip()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2017-10-15-analise-g1-01_files/figure-html/unnamed-chunk-13-1.png&#34; width=&#34;864&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Algumas observações sobre os resultados precisam ser feitas:&lt;br /&gt;
* No Rio de Janeiro, aparecem alguns termos meio estranhos como Envie e VC. Isso acontece porque, durante um período, no final das reportagens, o G1 escrevia algo como “Envie VC também uma reportagem para o G1RJ pelo Whatsapp ou pelo Viber”.&lt;br /&gt;
* Alguns nomes próprios se destacam, o que é natural, visto que Dória é prefeito de SP e Cabral um ex-governador do RJ. Em mineração de texto, esse tipo de dado é chamado de Entidade.&lt;/p&gt;
&lt;p&gt;Para limpar os resultados no segundo caso, são removidos os substantitos próprios. A maneira que eu bolei para isso talvez não seja a mais científica ou linguisticamente correta possível, mas aparenta ter funcionado. Ela se baseia em remover toda palavra que não é igual se escrita toda em minúsculo ou se escrita toda em maiúsculo. Por exemplo:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;eh_substantivo_comum &amp;lt;- function(x) {x == str_to_lower(x) | x == str_to_upper(x)}
c(eh_substantivo_comum(&amp;quot;tiroteio&amp;quot;), eh_substantivo_comum(&amp;quot;RJTV&amp;quot;), eh_substantivo_comum(&amp;quot;Doria&amp;quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## [1]  TRUE  TRUE FALSE&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Aproveitando o embalo da limpeza de dados, os termos mencionados acima como “Envie” e “VC” também serão removidos. Aplicando esta metodologia:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;temp &amp;lt;- df_filtro %&amp;gt;% 
  unnest_tokens(palavra, corpo_noticia, token = &amp;quot;ngrams&amp;quot;, n = 1, to_lower = FALSE) %&amp;gt;% 
  # aplicar regra do substantivo comum
  filter(eh_substantivo_comum(palavra)) %&amp;gt;% 
  # remover termos estranhos
  filter(!palavra %in% c(&amp;quot;VC&amp;quot;, &amp;quot;RJTV&amp;quot;)) %&amp;gt;% 
  distinct(url, palavra, .keep_all = TRUE) %&amp;gt;% 
  count(documento, palavra) %&amp;gt;% 
  tidytext::bind_tf_idf(term = palavra, document = documento, n = n) %&amp;gt;%  
  group_by(documento) %&amp;gt;% 
  arrange(desc(tf_idf)) %&amp;gt;% 
  mutate(palavra = factor(palavra, levels = rev(unique(palavra)))) %&amp;gt;% 
  filter(row_number() &amp;lt;= 20) %&amp;gt;% 
  ungroup() %&amp;gt;% 
  arrange(desc(tf_idf)) %&amp;gt;% 
  mutate(palavra = factor(palavra, levels = rev(unique(palavra))))
    

# construir grafico
temp %&amp;gt;% 
  ggplot(aes(palavra, tf_idf, fill = documento)) +
      geom_col(show.legend = FALSE) +
      geom_text(aes(label = n), hjust = 1.3) +
      labs(x = NULL, y = &amp;quot;tf-idf&amp;quot;,
           title = &amp;quot;Termos mais frequentes associados a apenas uma das cidades&amp;quot;,
           caption = &amp;quot;Fonte: Mineração de texto aplicada em notícias do G1.&amp;quot;) +
      facet_wrap(~documento, ncol = 2, scales = &amp;quot;free&amp;quot;) +
      coord_flip()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2017-10-15-analise-g1-01_files/figure-html/unnamed-chunk-15-1.png&#34; width=&#34;864&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Agora é possível notar resultados mais interessantes: termos relacionados ao tráfico de drogas como &lt;strong&gt;tiroteio&lt;/strong&gt;, &lt;strong&gt;rival&lt;/strong&gt;, &lt;strong&gt;UPP&lt;/strong&gt; e &lt;strong&gt;propinas&lt;/strong&gt; possui uma frequência relativa muito maior em notícias sobre a cidade do Rio de Janeiro que na capital paulista. Por outro lado, notícias sobre &lt;strong&gt;estupro&lt;/strong&gt; e &lt;strong&gt;abuso&lt;/strong&gt; parecem ser mais comuns na terra onde biscoito é chamado de bolacha.&lt;/p&gt;
&lt;p&gt;Também é possível realizar essa mesma análise por expressões compostas por mais de uma palavra, que são os chamados n-grams, onde n é a quantidade de termos. Analisando os 2-grams:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;temp &amp;lt;- df_filtro %&amp;gt;% 
  unnest_tokens(palavra, corpo_noticia, token = &amp;quot;ngrams&amp;quot;, n = 2, to_lower = FALSE) %&amp;gt;% 
  # aplicar regra do substantivo comum
  filter(eh_substantivo_comum(palavra)) %&amp;gt;% 
  distinct(url, palavra, .keep_all = TRUE) %&amp;gt;% 
  count(documento, palavra) %&amp;gt;% 
  tidytext::bind_tf_idf(term = palavra, document = documento, n = n) %&amp;gt;%  
  group_by(documento) %&amp;gt;% 
  arrange(desc(tf_idf)) %&amp;gt;% 
  mutate(palavra = factor(palavra, levels = rev(unique(palavra)))) %&amp;gt;% 
  filter(row_number() &amp;lt;= 20) %&amp;gt;% 
  ungroup() %&amp;gt;% 
  arrange(desc(tf_idf)) %&amp;gt;% 
  mutate(palavra = factor(palavra, levels = rev(unique(palavra))))
    

# construir grafico
temp %&amp;gt;% 
  ggplot(aes(palavra, tf_idf, fill = documento)) +
      geom_col(show.legend = FALSE) +
      geom_text(aes(label = n), hjust = 1.3) +
      labs(x = NULL, y = &amp;quot;tf-idf&amp;quot;) +
      facet_wrap(~documento, ncol = 2, scales = &amp;quot;free&amp;quot;) +
      coord_flip()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2017-10-15-analise-g1-01_files/figure-html/unnamed-chunk-16-1.png&#34; width=&#34;864&#34; /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;e-sergipe&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;E Sergipe?&lt;/h2&gt;
&lt;p&gt;Conforme foi mostrado um pouco acima, existem apenas 2 notícias de Sergipe na amostra coletada. O Sudeste é o grande monopolizador da primeira página do G1. Mesmo se aplicássemos o tf-idf comparando o Rio de Janeiro com Sergipe, os resultados seriam enviesados devido à baixíssima quantidade de dados do último. Por isso, Norte e Nordeste são agregadas como uma:&lt;/p&gt;
&lt;p&gt;Vamos então criar mais uma coluna para identificar a região do Brasil a qual a notícia se refere:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df_g1 %&amp;lt;&amp;gt;% 
  mutate(regiao = case_when(
    documento %in% c(&amp;quot;ac&amp;quot;, &amp;quot;am&amp;quot;, &amp;quot;amazonas&amp;quot;, &amp;quot;ap&amp;quot;, &amp;quot;pa&amp;quot;, &amp;quot;ro&amp;quot;, &amp;quot;rr&amp;quot;) ~ &amp;quot;Norte&amp;quot;,
    documento %in% c (&amp;quot;al&amp;quot;, &amp;quot;bahia&amp;quot;, &amp;quot;ceara&amp;quot;, &amp;quot;ma&amp;quot;, &amp;quot;paraiba&amp;quot;, &amp;quot;pb&amp;quot;, &amp;quot;pernambuco&amp;quot;, &amp;quot;pi&amp;quot;, &amp;quot;rn&amp;quot;, &amp;quot;se&amp;quot;, &amp;quot;to&amp;quot;) ~ &amp;quot;Nordeste&amp;quot;,
    documento %in% c(&amp;quot;distrito-federal&amp;quot;, &amp;quot;goias&amp;quot;, &amp;quot;mato-grosso&amp;quot;, &amp;quot;mato-grosso-do-sul&amp;quot;) ~ &amp;quot;Centro-Oeste&amp;quot;,
    documento %in% c(&amp;quot;espirito&amp;quot;, &amp;quot;mg&amp;quot;, &amp;quot;minas-gerais&amp;quot;, &amp;quot;rio-de-janeiro&amp;quot;, &amp;quot;rj&amp;quot;, &amp;quot;sao-paulo&amp;quot;, &amp;quot;sp&amp;quot;) ~ &amp;quot;Sudeste&amp;quot;,
    documento %in% c(&amp;quot;parana&amp;quot;, &amp;quot;rs&amp;quot;, &amp;quot;sc&amp;quot;) ~ &amp;quot;Sul&amp;quot;,
    #caderno_url %in% &amp;quot;mundo&amp;quot; ~ &amp;quot;Mundo&amp;quot;,
    TRUE ~ NA_character_
  ))

df_g1$regiao[df_g1$regiao %in% c(&amp;quot;Norte&amp;quot;, &amp;quot;Nordeste&amp;quot;)] &amp;lt;- &amp;quot;Norte-Nordeste&amp;quot;

# regioes mais comuns:
df_g1$regiao %&amp;gt;% table() %&amp;gt;% sort() %&amp;gt;% rev()&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## .
##        Sudeste Norte-Nordeste   Centro-Oeste            Sul 
##            396             75             68             36&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Para não ter de repetir o código que constrói os gráficos acima, salvei a função &lt;a href=&#34;&#34;&gt;neste Gist&lt;/a&gt;, com direito a algumas parametrizações, como a opção de remover ou não substantivos próprios:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;id_gist &amp;lt;- &amp;quot;2d626f33ee1b635a0aa3beeda31ae720&amp;quot;
devtools::source_gist(id_gist, filename = &amp;quot;grafico_g1.R&amp;quot;)

df_g1 %&amp;gt;% 
  mutate(documento = regiao) %&amp;gt;% 
  filter(!is.na(documento)) %&amp;gt;% 
  grafico_tfidf(n_grams = 1, remover_nomes_proprios = TRUE, agregar_por_noticia = TRUE)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2017-10-15-analise-g1-01_files/figure-html/unnamed-chunk-18-1.png&#34; width=&#34;864&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Mesmo agregando os dados por região, o desbalanceamento da quantidade de notícias por região prejudicou os resultados. Seria necessário coletar muito mais notícias para que os resultados fossem mais interessantes.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;bonus&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Bônus&lt;/h2&gt;
&lt;p&gt;A análise não precisa se restringir a regiões demográficas. Podemos, por exemplo, comparar notícias dos cadernos de Política e Ciência:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df_g1 %&amp;gt;% 
  filter(documento %in% c(&amp;quot;politica&amp;quot;, &amp;quot;ciencia-e-saude&amp;quot;)) %&amp;gt;% 
  grafico_tfidf(n_grams = 1, remover_nomes_proprios = TRUE, agregar_por_noticia = TRUE)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2017-10-15-analise-g1-01_files/figure-html/unnamed-chunk-19-1.png&#34; width=&#34;864&#34; /&gt;&lt;/p&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>O Sensacionalista e Text Mining: Análise de sentimento usando o lexiconPT</title>
      <link>http://www.sillasgonzaga.com/post/o-sensacionalista-e-text-mining/</link>
      <pubDate>Sat, 23 Sep 2017 00:00:00 +0000</pubDate>
      
      <guid>http://www.sillasgonzaga.com/post/o-sensacionalista-e-text-mining/</guid>
      <description>&lt;p&gt;De volta à ativa no blog!&lt;/p&gt;
&lt;p&gt;Recentemente, quando precisei fazer pela primeira vez algum tipo de análise em cima de textos (o chamado Text Mining ou Mineração de Texto) em Português, senti falta de ter um acesso fácil a um léxico na linguagem. O R já tem a sua disposição vários recursos para quem quer fazer Text Mining em inglês, como os pacotes &lt;code&gt;tokenizer&lt;/code&gt;, &lt;code&gt;tidytext&lt;/code&gt;, &lt;code&gt;tm&lt;/code&gt; e &lt;code&gt;lexicon&lt;/code&gt;, além de vários blog posts sobre Sentiment Analysis que você encontra no R-bloggers. Contudo, existe uma séria escassez de material de referência na língua portuguesa.&lt;/p&gt;
&lt;p&gt;O pacote &lt;a href=&#34;https://github.com/sillasgonzaga/lexiconPT&#34;&gt;&lt;code&gt;lexiconPT&lt;/code&gt;&lt;/a&gt;, que eu lancei em 20/09/2017 no Github (e em breve no CRAN também) nasceu para resolver parte desse problema. Até o momento, o &lt;code&gt;lexiconPT&lt;/code&gt; possui três datasets de léxicos: o OpLexicon (versões 2.1 e 3.0) e o SentiLex-PT02. Não pretendo (nem tenho competência para tal, pois sou iniciante em Text Mining - sem falsa modéstia) destrinchar como cada um deles funciona e em quê eles diferem. Para isso, sugiro ler as referências citadas na documentação dos próprios datasets (ex.: &lt;code&gt;help(&amp;quot;oplexicon_v2.1&amp;quot;)&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Mas ter o léxico em mãos só resolve parte dos problemas: ainda faltam os textos em si para serem analisados. Algumas ideias de datasets poderiam ser notícias, letras de músicas, livros (tem vários em Domínio Público), tweets, etc. Para demonstrar um simples uso do pacote, eu decidi por analisar comentários feitos por usuários na página do &lt;a href=&#34;https://www.facebook.com/sensacionalista/&#34;&gt;Sensacionalista&lt;/a&gt;, uma das mais populares do Facebook. A coleta dos dados foi relativamente fácil graças ao pacote &lt;code&gt;Rfacebook&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Com o pacote &lt;code&gt;lexiconPT&lt;/code&gt;, podemos responder a perguntas como:&lt;br /&gt;
* Os comentários no Sensacionalista são mais negativos ou positivos?&lt;br /&gt;
* Qual termo está mais associado a comentários negativos? PT ou PSDB? Temer ou Dilma? Bolsonaro ou Lula?&lt;br /&gt;
* Qual o comentário feito por um usuário mais negativo da história do Sensacionalista (dentro da amostra coletada)? E qual o mais positivo?&lt;/p&gt;
&lt;p&gt;Vamos ao código.&lt;/p&gt;
&lt;div id=&#34;coleta-dos-dados&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Coleta dos dados&lt;/h2&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;library(Rfacebook) # usado para extrair dados do facebook
library(tidyverse) # pq nao da pra viver sem
library(ggExtra)
library(magrittr) # &amp;lt;3
library(lubridate)
library(stringr) # essencial para trabalhar com textos
library(tidytext) # um dos melhores pacotes para text mining
library(lexiconPT)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Este post só foi possível graças ao &lt;code&gt;Rfacebook&lt;/code&gt;. Para aprender como ele funciona, leia a documentação presente em seu &lt;a href=&#34;https://github.com/pablobarbera/Rfacebook&#34;&gt;repo&lt;/a&gt; no Github. Para este primeiro, primeiro usei a função &lt;code&gt;getPage()&lt;/code&gt; para extrair as últimas 5000 publicações do Sensacionalista.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# token que eu gerei com minha API key.
# Essa parte vc obviamente nao vai conseguir reproduzir.
# leia o README do Rfacebook para saber como obter seu token
fb_token &amp;lt;- readRDS(&amp;quot;/home/sillas/R/data/facebook_token.Rds&amp;quot;) 

# demora cerca de 10 min pra rodar:
pg &amp;lt;- getPage(&amp;quot;sensacionalista&amp;quot;, fb_token, n = 5000)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;É necessário corrigir o encoding do corpo da publicação para o R parar de reclamar sobre isso:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# corrigir encoding do texto do post
pg$message %&amp;lt;&amp;gt;% iconv(to = &amp;quot;ASCII//TRANSLIT&amp;quot;)
# remover emojis
pg$message %&amp;lt;&amp;gt;% iconv(sub=&amp;quot;&amp;quot;, &amp;#39;UTF-8&amp;#39;, &amp;#39;ASCII&amp;#39;)
# visualizar dataframe
glimpse(pg)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Observations: 4,666
## Variables: 11
## $ from_id        &amp;lt;chr&amp;gt; &amp;quot;108175739225302&amp;quot;, &amp;quot;108175739225302&amp;quot;, &amp;quot;10817573...
## $ from_name      &amp;lt;chr&amp;gt; &amp;quot;Sensacionalista&amp;quot;, &amp;quot;Sensacionalista&amp;quot;, &amp;quot;Sensacio...
## $ message        &amp;lt;chr&amp;gt; &amp;quot;Apos liminar da Justica do DF permitir o trata...
## $ created_time   &amp;lt;chr&amp;gt; &amp;quot;2017-09-18T18:58:53+0000&amp;quot;, &amp;quot;2017-09-18T17:25:0...
## $ type           &amp;lt;chr&amp;gt; &amp;quot;link&amp;quot;, &amp;quot;link&amp;quot;, &amp;quot;link&amp;quot;, &amp;quot;link&amp;quot;, &amp;quot;video&amp;quot;, &amp;quot;link&amp;quot;...
## $ link           &amp;lt;chr&amp;gt; &amp;quot;http://www.sensacionalista.com.br/2017/09/18/l...
## $ id             &amp;lt;chr&amp;gt; &amp;quot;108175739225302_1638598099516384&amp;quot;, &amp;quot;1081757392...
## $ story          &amp;lt;chr&amp;gt; NA, NA, NA, NA, &amp;quot;Sensacionalista shared Gshow -...
## $ likes_count    &amp;lt;dbl&amp;gt; 10172, 2162, 2503, 5793, 4676, 2585, 821, 766, ...
## $ comments_count &amp;lt;dbl&amp;gt; 230, 164, 285, 221, 329, 104, 32, 58, 493, 586,...
## $ shares_count   &amp;lt;dbl&amp;gt; 2290, 453, 410, 2751, 0, 930, 26, 36, 900, 92, ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Só esse dataset por si só já renderia (e renderá) análises interessantes, mas vou as deixar para um futuro post para não deixar este aqui grande demais.&lt;/p&gt;
&lt;p&gt;A coluna &lt;code&gt;id&lt;/code&gt; é a que usaremos como referência como input na função &lt;code&gt;getPost()&lt;/code&gt; para extrair os comentários dos usuários na publicação. Infelizmente, a API do Facebook apresenta uma certa instabilidade para requests de dados muito grandes. Em várias tentativas que fiz, o máximo de dados que consegui extrair foram 200 comentários de 500 publicações da página. Portanto, vou usar esses parâmetros:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# roda em cerca de 8 minutos:
df_posts &amp;lt;- pg$id[1:500] %&amp;gt;% map(getPost, fb_token, n = 200, comments = TRUE, likes = FALSE,
                            reactions = FALSE)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A função &lt;code&gt;getPost()&lt;/code&gt; fornece o seguinte output:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;str(df_posts)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## List of 500
##  $ :List of 2
##   ..$ post    :&amp;#39;data.frame&amp;#39;: 1 obs. of  10 variables:
##   .. ..$ from_id       : chr &amp;quot;108175739225302&amp;quot;
##   .. ..$ from_name     : chr &amp;quot;Sensacionalista&amp;quot;
##   .. ..$ message       : chr &amp;quot;Após liminar da Justiça do DF permitir o tratamento da homossexualidade como doença&amp;quot;
##   .. ..$ created_time  : chr &amp;quot;2017-09-18T18:58:53+0000&amp;quot;
##   .. ..$ type          : chr &amp;quot;link&amp;quot;
##   .. ..$ link          : chr &amp;quot;http://www.sensacionalista.com.br/2017/09/18/liminar-que-chancela-cura-gay-permite-tratar-justica-brasileira-como-doenca/&amp;quot;
##   .. ..$ id            : chr &amp;quot;108175739225302_1638598099516384&amp;quot;
##   .. ..$ likes_count   : num 11340
##   .. ..$ comments_count: num 248
##   .. ..$ shares_count  : num 2590
##   ..$ comments:&amp;#39;data.frame&amp;#39;: 200 obs. of  7 variables:
##   .. ..$ from_id       : chr [1:200] &amp;quot;1437254732976789&amp;quot; &amp;quot;1445900342154501&amp;quot; &amp;quot;10209941046899919&amp;quot; &amp;quot;173469923210786&amp;quot; ...
##   .. ..$ from_name     : chr [1:200] &amp;quot;Sérgio Henrique Reis&amp;quot; &amp;quot;Renata Gil&amp;quot; &amp;quot;Lucas Ferreira&amp;quot; &amp;quot;Leonardo Wesley&amp;quot; ...
##   .. ..$ message       :&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Error in strtrim(encodeString(object, quote = &amp;quot;\&amp;quot;&amp;quot;, na.encode = FALSE), : string multibyte inválida em &amp;#39;&amp;lt;a0&amp;gt;&amp;lt;be&amp;gt;\xed&amp;lt;b4&amp;gt;\u0094  Ahh do Distrito Federal.... Quando Juscelino Kubitschek fundou Brasília, será que ele tinha a mínima ideia do que sairia de lá?&amp;quot;&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Para extrair os dataframes relativos aos comentários e aos metadados das publicações, o &lt;code&gt;purrr&lt;/code&gt; é uma mão na roda:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df_comments &amp;lt;- df_posts %&amp;gt;% map_df(&amp;quot;comments&amp;quot;)
df_posts &amp;lt;- df_posts %&amp;gt;% map_df(&amp;quot;post&amp;quot;)
# repetir procedimento de consertar o encoding
df_comments$message %&amp;lt;&amp;gt;% iconv(to = &amp;quot;ASCII//TRANSLIT&amp;quot;) %&amp;gt;% iconv(sub=&amp;quot;&amp;quot;, &amp;#39;UTF-8&amp;#39;, &amp;#39;ASCII&amp;#39;)
df_posts$message %&amp;lt;&amp;gt;% iconv(to = &amp;quot;ASCII//TRANSLIT&amp;quot;) %&amp;gt;% iconv(sub=&amp;quot;&amp;quot;, &amp;#39;UTF-8&amp;#39;, &amp;#39;ASCII&amp;#39;)
# por questoes de anonimizacao, vou remover os dados pessoais referentes aos usuarios
df_comments %&amp;lt;&amp;gt;% select(-from_id, -from_name)

# olhar estrutura dos dataframes
str(df_comments)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## &amp;#39;data.frame&amp;#39;:    72100 obs. of  5 variables:
##  $ message       : chr  &amp;quot;Entao pode faltar no servico, ligar pro chefe(a), levar atestado e dizer que acordou com vontade de chupar rola?&amp;quot; &amp;quot;Nunca o sensacionalista foi tao verdadeiro. Pq a justica brasileira se demonstrou uma verdadeira praga na socie&amp;quot;| __truncated__ &amp;quot;O unico que precisa de tratamento e o sr juiz que autorizou.&amp;quot; &amp;quot;Isso viola todas os acordos internacionais e uma aberracao contra qqr liberdade individual e humana. Logo essa liminar cai.&amp;quot; ...
##  $ created_time  : chr  &amp;quot;2017-09-18T19:01:21+0000&amp;quot; &amp;quot;2017-09-18T19:03:57+0000&amp;quot; &amp;quot;2017-09-18T19:01:26+0000&amp;quot; &amp;quot;2017-09-18T19:00:54+0000&amp;quot; ...
##  $ likes_count   : num  575 392 270 227 310 145 113 73 38 50 ...
##  $ comments_count: num  58 5 4 3 12 16 10 0 0 8 ...
##  $ id            : chr  &amp;quot;1638598099516384_1638600206182840&amp;quot; &amp;quot;1638598099516384_1638602159515978&amp;quot; &amp;quot;1638598099516384_1638600266182834&amp;quot; &amp;quot;1638598099516384_1638599869516207&amp;quot; ...&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;str(df_posts)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## &amp;#39;data.frame&amp;#39;:    500 obs. of  10 variables:
##  $ from_id       : chr  &amp;quot;108175739225302&amp;quot; &amp;quot;108175739225302&amp;quot; &amp;quot;108175739225302&amp;quot; &amp;quot;108175739225302&amp;quot; ...
##  $ from_name     : chr  &amp;quot;Sensacionalista&amp;quot; &amp;quot;Sensacionalista&amp;quot; &amp;quot;Sensacionalista&amp;quot; &amp;quot;Sensacionalista&amp;quot; ...
##  $ message       : chr  &amp;quot;Apos liminar da Justica do DF permitir o tratamento da homossexualidade como doenca&amp;quot; &amp;quot;Ja recebeu encomendas de quarteis&amp;quot; &amp;quot;Aparelho esta sendo oferecido por importadores por ate 6 mil reais&amp;quot; &amp;quot;Temer desembarcou nos Estados Unidos para jantar com Trump e participacao na Assembleia Geral da ONU&amp;quot; ...
##  $ created_time  : chr  &amp;quot;2017-09-18T18:58:53+0000&amp;quot; &amp;quot;2017-09-18T17:25:00+0000&amp;quot; &amp;quot;2017-09-18T17:05:41+0000&amp;quot; &amp;quot;2017-09-18T17:00:02+0000&amp;quot; ...
##  $ type          : chr  &amp;quot;link&amp;quot; &amp;quot;link&amp;quot; &amp;quot;link&amp;quot; &amp;quot;link&amp;quot; ...
##  $ link          : chr  &amp;quot;http://www.sensacionalista.com.br/2017/09/18/liminar-que-chancela-cura-gay-permite-tratar-justica-brasileira-como-doenca/&amp;quot; &amp;quot;http://www.sensacionalista.com.br/2017/09/18/fabricante-de-pau-de-arara-comemora-falta-de-resposta-a-general-qu&amp;quot;| __truncated__ &amp;quot;http://www.sensacionalista.com.br/2017/09/18/empresa-lanca-servico-de-escolta-armada-para-quem-comprar-o-iphone-x/&amp;quot; &amp;quot;http://www.sensacionalista.com.br/2017/09/18/coreia-do-norte-nega-ter-lancado-temer-nos-eua/&amp;quot; ...
##  $ id            : chr  &amp;quot;108175739225302_1638598099516384&amp;quot; &amp;quot;108175739225302_1638510679525126&amp;quot; &amp;quot;108175739225302_1638503796192481&amp;quot; &amp;quot;108175739225302_1638456466197214&amp;quot; ...
##  $ likes_count   : num  11340 2271 2826 6361 4990 ...
##  $ comments_count: num  248 167 315 242 347 106 32 58 494 590 ...
##  $ shares_count  : num  2590 478 441 3008 0 ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Só pode ser trollagem da API do Facebook retornar aquela mensagem logo no topo do dataframe, mas enfim.&lt;/p&gt;
&lt;p&gt;O dataframe &lt;code&gt;df_comments&lt;/code&gt;, o objeto da nossa análise, não possui alguns dados que serão valiosos para a análise, como o link para o artigo no site do Sensacionalista. Por isso, vamos um &lt;code&gt;left_join&lt;/code&gt; com o &lt;code&gt;df_posts&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Percebeu que nas colunas &lt;code&gt;df_comments$id&lt;/code&gt; e &lt;code&gt;df_posts$id&lt;/code&gt; existe um traço separando dois conjuntos numéricos? Por alguma razão que beira a imbecilidade, não é possível combinar imediatamente essas duas colunas para formatar um dataframe só com o &lt;code&gt;left_join&lt;/code&gt;. A sintaxe de identificação do Facebook funciona assim: O post é identificado &lt;code&gt;IDPAGINA_IDPUBLICAÇÃO&lt;/code&gt; e o comentário na publicação é identificado como &lt;code&gt;IDPUBLICAÇÃO_IDCOMENTÁRIO&lt;/code&gt;. Ou seja, para poder juntar os dois dataframes, vamos ter que combinar a sequência númerica à esquerda do underline (acho que esse traço tem algum outro nome, mas não me lembro no momento) em &lt;code&gt;df_comments$id&lt;/code&gt; e à direita em &lt;code&gt;df_posts$id&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# consertar colunas de id: no df_comments, deixar à esquerda do underline. no df_posts, deixar à direita.
df_comments$id_post_real &amp;lt;- df_comments$id
df_comments$id %&amp;lt;&amp;gt;% str_replace_all(&amp;quot;_.*&amp;quot;, &amp;quot;&amp;quot;)
df_posts$id %&amp;lt;&amp;gt;% str_replace_all(&amp;quot;.*_&amp;quot;, &amp;quot;&amp;quot;)

# juntar as duas tabelas
df_posts %&amp;lt;&amp;gt;% dplyr::select(id, post_message = message, horario_post = created_time,
                     type, post_comments_count = comments_count, post_link = link,
                     post_type = type, post_likes_count = likes_count)
df_comments %&amp;lt;&amp;gt;% rename(horario_comentario = created_time)

df_comments %&amp;lt;&amp;gt;% left_join(df_posts, by = &amp;quot;id&amp;quot;)
# remover NAs (nao sao muitos casos)
df_comments %&amp;lt;&amp;gt;% filter(!is.na(horario_post))
# converter colunas de horario
df_comments$horario_comentario %&amp;lt;&amp;gt;% str_sub(1, 19) %&amp;gt;% str_replace_all(&amp;quot;T&amp;quot;, &amp;quot;&amp;quot;) %&amp;gt;% ymd_hms()
df_comments$horario_post %&amp;lt;&amp;gt;% str_sub(1, 19) %&amp;gt;% str_replace_all(&amp;quot;T&amp;quot;, &amp;quot;&amp;quot;) %&amp;gt;% ymd_hms()
# Como ficou:
glimpse(df_comments)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Observations: 71,891
## Variables: 12
## $ message             &amp;lt;chr&amp;gt; &amp;quot;Entao pode faltar no servico, ligar pro c...
## $ horario_comentario  &amp;lt;dttm&amp;gt; 2017-09-18 19:01:21, 2017-09-18 19:03:57,...
## $ likes_count         &amp;lt;dbl&amp;gt; 575, 392, 270, 227, 310, 145, 113, 73, 38,...
## $ comments_count      &amp;lt;dbl&amp;gt; 58, 5, 4, 3, 12, 16, 10, 0, 0, 8, 69, 3, 0...
## $ id                  &amp;lt;chr&amp;gt; &amp;quot;1638598099516384&amp;quot;, &amp;quot;1638598099516384&amp;quot;, &amp;quot;1...
## $ id_post_real        &amp;lt;chr&amp;gt; &amp;quot;1638598099516384_1638600206182840&amp;quot;, &amp;quot;1638...
## $ post_message        &amp;lt;chr&amp;gt; &amp;quot;Apos liminar da Justica do DF permitir o ...
## $ horario_post        &amp;lt;dttm&amp;gt; 2017-09-18 18:58:53, 2017-09-18 18:58:53,...
## $ post_type           &amp;lt;chr&amp;gt; &amp;quot;link&amp;quot;, &amp;quot;link&amp;quot;, &amp;quot;link&amp;quot;, &amp;quot;link&amp;quot;, &amp;quot;link&amp;quot;, &amp;quot;l...
## $ post_comments_count &amp;lt;dbl&amp;gt; 248, 248, 248, 248, 248, 248, 248, 248, 24...
## $ post_link           &amp;lt;chr&amp;gt; &amp;quot;http://www.sensacionalista.com.br/2017/09...
## $ post_likes_count    &amp;lt;dbl&amp;gt; 11340, 11340, 11340, 11340, 11340, 11340, ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&#34;uso-do-lexiconpt&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Uso do lexiconPT&lt;/h2&gt;
&lt;p&gt;Agora temos o dataset em mãos para usar o &lt;code&gt;lexiconPT&lt;/code&gt;. Acho muito importante ressaltar que Text Mining é uma atividade razoavelmente complexa que envolve uma extensa etapa de limpeza e tratamento de dados, como remover (ou não) acentos e corrigir palavras com letras duplicadas (trist&lt;em&gt;ee&lt;/em&gt;) ou erros gramaticais (infelismente). Para fins de simplicidade, não vou me ater muito a esses detalhes e pular direto para a utilização dos léxicos portugueses e apresentação dos resultados.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# carregar datasets
data(&amp;quot;oplexicon_v3.0&amp;quot;)
data(&amp;quot;sentiLex_lem_PT02&amp;quot;)

op30 &amp;lt;- oplexicon_v3.0
sent &amp;lt;- sentiLex_lem_PT02

glimpse(op30)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Observations: 32,191
## Variables: 4
## $ term              &amp;lt;chr&amp;gt; &amp;quot;=[&amp;quot;, &amp;quot;=@&amp;quot;, &amp;quot;=p&amp;quot;, &amp;quot;=P&amp;quot;, &amp;quot;=x&amp;quot;, &amp;quot;=d&amp;quot;, &amp;quot;=D&amp;quot;, &amp;quot;;...
## $ type              &amp;lt;chr&amp;gt; &amp;quot;emot&amp;quot;, &amp;quot;emot&amp;quot;, &amp;quot;emot&amp;quot;, &amp;quot;emot&amp;quot;, &amp;quot;emot&amp;quot;, &amp;quot;emo...
## $ polarity          &amp;lt;int&amp;gt; -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, ...
## $ polarity_revision &amp;lt;chr&amp;gt; &amp;quot;A&amp;quot;, &amp;quot;A&amp;quot;, &amp;quot;A&amp;quot;, &amp;quot;A&amp;quot;, &amp;quot;A&amp;quot;, &amp;quot;A&amp;quot;, &amp;quot;A&amp;quot;, &amp;quot;A&amp;quot;, &amp;quot;A&amp;quot;,...&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;glimpse(sent)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## Observations: 7,014
## Variables: 5
## $ term                    &amp;lt;chr&amp;gt; &amp;quot;a-vontade&amp;quot;, &amp;quot;abafado&amp;quot;, &amp;quot;abafante&amp;quot;, &amp;quot;a...
## $ grammar_category        &amp;lt;chr&amp;gt; &amp;quot;N&amp;quot;, &amp;quot;Adj&amp;quot;, &amp;quot;Adj&amp;quot;, &amp;quot;Adj&amp;quot;, &amp;quot;Adj&amp;quot;, &amp;quot;Adj&amp;quot;...
## $ polarity                &amp;lt;dbl&amp;gt; 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1...
## $ polarity_target         &amp;lt;chr&amp;gt; &amp;quot;N0&amp;quot;, &amp;quot;N0&amp;quot;, &amp;quot;N0&amp;quot;, &amp;quot;N0&amp;quot;, &amp;quot;N0&amp;quot;, &amp;quot;N0&amp;quot;, &amp;quot;N...
## $ polarity_classification &amp;lt;chr&amp;gt; &amp;quot;MAN&amp;quot;, &amp;quot;JALC&amp;quot;, &amp;quot;MAN&amp;quot;, &amp;quot;JALC&amp;quot;, &amp;quot;JALC&amp;quot;, ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ambos os datasets possuem colunas de polaridade de sentimento, que é a que usaremos para quantificar o quão negativo ou positivo é um comentário.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# criar ID unica para cada comentario
df_comments %&amp;lt;&amp;gt;% mutate(comment_id = row_number())
# usar funçao do tidytext para criar uma linha para cada palavra de um comentario
df_comments_unnested &amp;lt;- df_comments %&amp;gt;% unnest_tokens(term, message)

df_comments_unnested %&amp;gt;%
  select(comment_id, term) %&amp;gt;%
  head(20)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;##      comment_id     term
## 1             1    entao
## 1.1           1     pode
## 1.2           1   faltar
## 1.3           1       no
## 1.4           1  servico
## 1.5           1    ligar
## 1.6           1      pro
## 1.7           1    chefe
## 1.8           1        a
## 1.9           1    levar
## 1.10          1 atestado
## 1.11          1        e
## 1.12          1    dizer
## 1.13          1      que
## 1.14          1  acordou
## 1.15          1      com
## 1.16          1  vontade
## 1.17          1       de
## 1.18          1   chupar
## 1.19          1     rola&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;De novo esse comentário… &lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Enfim, veja que foi criada uma linha para cada palavra presetnte no comentário. Será essa nova coluna &lt;code&gt;term&lt;/code&gt; que usaremos como referência para quantificar o sentimento de um comentário.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df_comments_unnested %&amp;gt;% 
  left_join(op30, by = &amp;quot;term&amp;quot;) %&amp;gt;% 
  left_join(sent %&amp;gt;% select(term, lex_polarity = polarity), by = &amp;quot;term&amp;quot;) %&amp;gt;% 
  select(comment_id, term, polarity, lex_polarity) %&amp;gt;% 
  head(10)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;##    comment_id    term polarity lex_polarity
## 1           1   entao       NA           NA
## 2           1    pode       NA           NA
## 3           1  faltar        1           NA
## 4           1      no       NA           NA
## 5           1 servico       NA           NA
## 6           1   ligar       -1           NA
## 7           1     pro       NA           NA
## 8           1   chefe       NA           NA
## 9           1       a       NA           NA
## 10          1   levar       -1           NA&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A amostra acima mostra que nem toads as palavras possuem uma polaridade registrada nos léxicos. Não só isso, mas algumas palavras (como &lt;strong&gt;faltar&lt;/strong&gt;, &lt;strong&gt;ligar&lt;/strong&gt; e &lt;strong&gt;levar&lt;/strong&gt;) estão presentes no OpLexicon mas não no SentiLex. A polaridade 1 em faltar significa que, de acordo com esse léxico, a palavra está associada a comentários positivos. Saber essa diferença é fundamental, pois a escolha do léxico pode ter uma grande influência nos resultados da análise.&lt;/p&gt;
&lt;p&gt;Vamos então manter no dataframe apenas as palavras que possuem polaridade tanto no OpLexicon como no SentiLex:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df_comments_unnested &amp;lt;- df_comments_unnested %&amp;gt;% 
  inner_join(op30, by = &amp;quot;term&amp;quot;) %&amp;gt;% 
  inner_join(sent %&amp;gt;% select(term, lex_polarity = polarity), by = &amp;quot;term&amp;quot;) %&amp;gt;% 
  group_by(comment_id) %&amp;gt;% 
  summarise(
    comment_sentiment_op = sum(polarity),
    comment_sentiment_lex = sum(lex_polarity),
    n_words = n()
    ) %&amp;gt;% 
  ungroup() %&amp;gt;% 
  rowwise() %&amp;gt;% 
  mutate(
    most_neg = min(comment_sentiment_lex, comment_sentiment_op),
    most_pos = max(comment_sentiment_lex, comment_sentiment_op)
  )

head(df_comments_unnested)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## # A tibble: 6 x 6
##   comment_id comment_sentiment_op comment_sentiment_lex n_words most_neg
##        &amp;lt;int&amp;gt;                &amp;lt;int&amp;gt;                 &amp;lt;dbl&amp;gt;   &amp;lt;int&amp;gt;    &amp;lt;dbl&amp;gt;
## 1          2                    0                     0       2        0
## 2          7                   -2                    -3       3       -3
## 3          8                    1                     1       2        1
## 4          9                    0                     0       3        0
## 5         11                   -1                    -1       3       -1
## 6         12                   -3                    -3       3       -3
## # ... with 1 more variables: most_pos &amp;lt;dbl&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&#34;apresentacao-dos-resultados&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Apresentação dos resultados&lt;/h2&gt;
&lt;p&gt;O gráfico de pontos abaixo mostra a distribuição de polaridade para cada léxico:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df_comments_unnested %&amp;gt;% 
  ggplot(aes(x = comment_sentiment_op, y = comment_sentiment_lex)) +
    geom_point(aes(color = n_words)) + 
    scale_color_continuous(low = &amp;quot;green&amp;quot;, high = &amp;quot;red&amp;quot;) +
    labs(x = &amp;quot;Polaridade no OpLexicon&amp;quot;, y = &amp;quot;Polaridade no SentiLex&amp;quot;) +
    #geom_smooth(method = &amp;quot;lm&amp;quot;) +
    geom_vline(xintercept = 0, linetype = &amp;quot;dashed&amp;quot;) +
    geom_hline(yintercept = 0, linetype = &amp;quot;dashed&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2017-09-23-o-sensacionalista-e-text-mining-analise-de-sentimento-usando-o-lexiconpt_files/figure-html/unnamed-chunk-14-1.png&#34; width=&#34;672&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Existem pelo menos três outliers nos dados, todos causados pela grande quantidade de palavras do comentário, o que pode ser um indício de que simplesmente somar a polaridade de cada palavra do comentário pode não ser um bom método. Outra informação revelada pelo gráfico é que existem palavras que possuem sentimentos diferentes de acordo com o léxico usado. Ter isso em mente é fundamental para a análise.&lt;/p&gt;
&lt;p&gt;Após remover os outliers, já é possível descobrir quais os comentários mais positivos e mais negativos da amostra coletada:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df_comments_unnested %&amp;lt;&amp;gt;% filter(between(comment_sentiment_op, -10, 10))

# comentario mais positivo da historia do sensacionalista
most_pos &amp;lt;- which.max(df_comments_unnested$most_pos)
most_neg &amp;lt;- which.min(df_comments_unnested$most_neg)

# mais positivo
cat(df_comments$message[df_comments$comment_id == df_comments_unnested$comment_id[most_pos]])&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## O mundo a esquerda e sempre melhor. A musica, o pincel, a pena e a talha. Sempre esteve para a esquerda. As grandes mentes criativas, os grandes pensadores, os humanistas, os geniais. Tudo que e original e belo e oriundo da esquerda.
## E tendencia natural do ser humano, ao passo q alcanca um minimo de consciencia critica do mundo sempre tender a esquerda.
## Todavia o mundo tambem precisa de mentes computacionais, a esse papel a direita tem seu valor. A direita funciona bem quando o objetivo e produzir o fisico, o tangivel e operacional. Embora isso tambem pudesse ser feito por maquinas, robos, ou mesmo por animais adestrados.
## A exemplo, os EUA, onde o cidadao comum e um ser vegetativo, destinado a operar, produzir e consumir. Sao seres incapazes de formular um pensamento critico e original. Uma nacao que por forca do capital tem seu vies ideologico voltado pra direita. Ainda q por vezes elejam um presidente com vies de esquerda, nunca irao evoluir sua consciencia. Sera sempre uma nacao de dementes e ignorantes.
## Nao por acaso que o capital, na sua forma economica ou politica sempre se poe sobre a direita quando tem por objetivo o ganho, financeiro ou de poder. E sobre a direita q se faz os movimentos de massa imbecilizada, pois como seres roboticos sao facilmente levados aonde se quer levar.
## A esquerda liga-se ao mundo das ideias, ao pensamento critico, a modelagem do ser humano como ser consciente. A esquerda e a construcao do pensamento, e o
## individuo pensante e critico, tudo que evolui e eleva o ser humano a um patamar de consciencia superior, provem da esquerda.
## Nacoes capitalistas, ainda q direitista, mas q sua massa possui em sua construcao um vies ideologico de esquerda serao sempre nacoes ricas financeiramente e ricas de estado de consciencia critica. Observa se isto em paises europeus, onde o
## capital existe por forca do consumo, mas coexiste com o estado de bem estar social, com a beleza das artes e com tudo aquilo e natural da esquerda, enquanto consciencia e beleza.
## A direita tem por tendencia natural, o simples, o computacional e robotizado. O argumento da direita e sempre algoritmo, linear e raso. A direita sera sempre um universo de seres ocos, massivos e imbecilizados.
## A.L.C.&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# mais negativo
cat(df_comments$message[df_comments$comment_id == df_comments_unnested$comment_id[most_neg]])&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## No ultimo domingo foi ao ar o programa do Silvio Santos, onde Maisa foi ridiculamente feita de idiota por duas pessoas na frente do Brasil todo. Onde mesmo ela pedindo pra parar, nao pararam. Mas eu to aqui apenas pra falar a verdade sobre uma dessas pessoas: DUDU CAMARGO. 
## Muitos o conhecem como o cara chato que apresenta o jornal da manha, mas eu o conheco como o EX ABUSIVO QUE FEZ COISAS IMPERDOAVEIS. 
## Tudo comecou quando nos conhecemos na escola, quando ele fez de tudo pra me conquistar, e eu era bobo. Cai. Comecamos a namorar e era tudo incrivel e flores, mesmo com o preconceito, era bom ter alguem com quem contar e gostar. 
## As coisas foram andando, e mesmo com todas as pessoas dizendo pra eu nao continuar esse relacionamento, eu continuei. Meus professores, meus amigos, e ate gente desconhecida tentaram abrir meus olhos. Mas ja era tarde. Eu estava apaixonado! Ia fazer de tudo pra ficar com ele!
## O terror comeca. 
## Estava tudo bem, ate ele comecar a ser agressivo e babaca. Gritava, ficava de cara feia, falava merda, e eu tambem, mas ele foi ao extremo. Ele estava vidrado em ser um astro da tv que esqueceu que tinha um relacionamento e comecou a me tratar como lixo, ate que ele me machucou feio, arranhou meu braco e deixou uma cicatriz em uma de nossas brigas. Nao sou santo, claro que tentei revidar para me defender. Mas apenas UMA vez.
## As brigas e abusos tornaram-se serios, ele cada dia era mais estourado, e nao so comigo, com todos, ate com a familia, que por sinal sempre o apoiou. Mas ele sempre queria ser mais que todos.
## Nao contente em 1, ele bateu 2, 3, 4... Varias vezes. Ja cheguei em casa com o rosto machucado e tive que mentir pra minha mae, mas ela nao era e nem e burra, sabia o que tinha acontecido. 
## Na escola, todos perguntavam e eu mentia, achando que estava tudo bem, mas nao estava. Eu estava tao apaixonado que nao conseguia sair daquele pesadelo.
## Outro ponto: ABUSO SEXUAL. Isso e bem serio, e se fosse brincadeira, eu nao estaria me expondo assim. Em um relacionamento voce consequentemente transa com a pessoa, e no comeco era bom, era normal. Mas depois das agressoes, eu fiquei tao em choque que inventava que estava doente para nao ter que fazer nada com ele. E ele reclamava, e bla bla bla, e dizia que sexo nao era nada, mas era. Ate que ele comecou a falar coisas horriveis para mim, comecou ficar tao frustrado que comecou a forcar coisas dizendo que se eu nao fizesse, era pra eu ir embora e nunca mais voltar, e que se eu voltasse, ele me trataria pior do que ja estava tratando. Mas eu fiquei, nao sei o porque, mas fiquei, com todo o medo e receio. 
## Toda vez que eu queria conversar com ele sobre algo, ele ia direto me mandando ficar quieto e me chamava pra fazer coisas... Quando ele nao estava no instagram ou implorando para alguma aparicao na tv. 
## Eu estava louco e assustado, a escola chamou minha mae pra conversar pois estavam com medo de que eu fizesse algo comigo mesmo, meu psicologico estava destruido e eu fiquei com depressao. 
## Algumas pessoas vao dizer que eu quem quis, ja que nao fui embora, mas nao e bem assim. 
## Nao tenho tudo registrado, - e acho que nem preciso - mas eu vivi, as pessoas viram e tentaram me ajudar, mas eu recusei, com medo. 
## Passei muitas coisas boas no comeco, mas depois eu vi o monstro que ele era/e. Ele pode se fazer de santo na tv, mas muita gente sabe quem ele realmente e. E eu sei o que eu sofri. 
## Vao dizer que e tudo mentira, e que eu quero fama. Talvez que sou louco, mas felizmente tenho testemunhas e nao tenho mais medo de me esconder. Depois de domingo, ele provou ser a pessoa mais falsa e artificial que existe. Engessado, segundo a Maisa. 
## Nao e porque ele e uma pessoa publica que pode fazer o que quer sem pagar. Sei das consequencias que posso ter, mas nada tira a paz que sinto sabendo que tenho pessoas incriveis que me apoiaram e apoiam ate hoje. 
## Espero que entendam e ajudem as pessoas que precisam. Se voce sabe alguem que esteja nessa vida, ajude-a a sair... Nao importa quem seja. Chega de abuso!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Por incrível que pareça, tanto o comentário mais positivo quanto o mais negativo falam sobre a esquerda.&lt;/p&gt;
&lt;p&gt;Para prosseguir com a análise, usaremos o léxico OpLexicon para a análise de sentimento:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df_comments %&amp;lt;&amp;gt;% inner_join(
  df_comments_unnested %&amp;gt;% select(comment_id, sentiment = comment_sentiment_op),
  by = &amp;quot;comment_id&amp;quot;
  )
# criar coluna de data (variavel da classe Date)
df_comments$data &amp;lt;- as.Date(df_comments$horario_post)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agora sim podemos demonstrar uma visualização de uma análise básica de sentimento: Como tem sido o sentimento geral dos comentários no Sensacionalista ao longo do tempo?&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df_comments_wide &amp;lt;- df_comments %&amp;gt;% 
  # filtrar fora palavras neutras
  filter(sentiment != 0) %&amp;gt;% 
  # converter numerico para categorico
  mutate(sentiment = ifelse(sentiment &amp;lt; 0, &amp;quot;negativo&amp;quot;, &amp;quot;positivo&amp;quot;)) %&amp;gt;% 
  # agrupar os dados
  count(data, post_link, post_type, sentiment) %&amp;gt;% 
  # converter para formato wide
  spread(sentiment, n, fill = 0) %&amp;gt;% 
  mutate(sentimento = positivo - negativo) %&amp;gt;% 
  ungroup() %&amp;gt;% 
  arrange(data)

head(df_comments_wide) %&amp;gt;% knitr::kable()&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr class=&#34;header&#34;&gt;
&lt;th align=&#34;left&#34;&gt;data&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;post_link&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;post_type&lt;/th&gt;
&lt;th align=&#34;right&#34;&gt;negativo&lt;/th&gt;
&lt;th align=&#34;right&#34;&gt;positivo&lt;/th&gt;
&lt;th align=&#34;right&#34;&gt;sentimento&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td align=&#34;left&#34;&gt;2017-04-13&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;a href=&#34;https://www.facebook.com/sensacionalista/photos/a.187587037950838.39557.108175739225302/1460990897277106/?type=3&#34; class=&#34;uri&#34;&gt;https://www.facebook.com/sensacionalista/photos/a.187587037950838.39557.108175739225302/1460990897277106/?type=3&lt;/a&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;photo&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;7&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;17&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td align=&#34;left&#34;&gt;2017-04-13&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;a href=&#34;http://www.sensacionalista.com.br/2017/04/13/temer-lula-e-fhc-articulam-pacto-de-nao-rir-de-brasileiro-que-desfez-amizade-no-facebook-por-politica/&#34; class=&#34;uri&#34;&gt;http://www.sensacionalista.com.br/2017/04/13/temer-lula-e-fhc-articulam-pacto-de-nao-rir-de-brasileiro-que-desfez-amizade-no-facebook-por-politica/&lt;/a&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;link&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;32&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;34&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td align=&#34;left&#34;&gt;2017-04-14&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;a href=&#34;https://www.facebook.com/sensacionalista/photos/a.187587037950838.39557.108175739225302/1462221330487396/?type=3&#34; class=&#34;uri&#34;&gt;https://www.facebook.com/sensacionalista/photos/a.187587037950838.39557.108175739225302/1462221330487396/?type=3&lt;/a&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;photo&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;42&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;22&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;-20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td align=&#34;left&#34;&gt;2017-04-15&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;a href=&#34;https://www.facebook.com/sensacionalista/photos/a.187587037950838.39557.108175739225302/1463589740350555/?type=3&#34; class=&#34;uri&#34;&gt;https://www.facebook.com/sensacionalista/photos/a.187587037950838.39557.108175739225302/1463589740350555/?type=3&lt;/a&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;photo&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;7&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;27&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td align=&#34;left&#34;&gt;2017-04-16&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;a href=&#34;https://www.sensacionalista.com.br/2016/03/25/laja-jato-diz-que-lula-comprou-ovo-da-kopenhagen-em-nome-de-amigo/&#34; class=&#34;uri&#34;&gt;https://www.sensacionalista.com.br/2016/03/25/laja-jato-diz-que-lula-comprou-ovo-da-kopenhagen-em-nome-de-amigo/&lt;/a&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;link&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;23&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;35&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td align=&#34;left&#34;&gt;2017-04-16&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;a href=&#34;http://www.sensacionalista.com.br/2017/04/10/as-18-melhores-coisas-com-sentimentos-a-nova-obsessao-da-internet/&#34; class=&#34;uri&#34;&gt;http://www.sensacionalista.com.br/2017/04/10/as-18-melhores-coisas-com-sentimentos-a-nova-obsessao-da-internet/&lt;/a&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;link&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;17&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;16&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Por exemplo, o primeiro link coletado na amostra, uma foto, teve 9 palavras contadas como negativas e 13 como positivas. O score geral dos comentários nessa publicação foi 13 - 9 = 4.&lt;/p&gt;
&lt;p&gt;Qual a publicação do Sensacionalista com maior nível de “positividade”? E o de “negatividade”?&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df_comments_wide %&amp;gt;% 
  arrange(sentimento) %&amp;gt;% 
  filter(row_number() == 1 | row_number() == nrow(df_comments_wide)) %&amp;gt;% 
   knitr::kable()&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr class=&#34;header&#34;&gt;
&lt;th align=&#34;left&#34;&gt;data&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;post_link&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;post_type&lt;/th&gt;
&lt;th align=&#34;right&#34;&gt;negativo&lt;/th&gt;
&lt;th align=&#34;right&#34;&gt;positivo&lt;/th&gt;
&lt;th align=&#34;right&#34;&gt;sentimento&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td align=&#34;left&#34;&gt;2017-09-11&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;a href=&#34;http://www.sensacionalista.com.br/2017/09/11/projeto-do-mbl-pretende-vestir-obras-de-arte-em-museus-ao-redor-do-mundo/&#34; class=&#34;uri&#34;&gt;http://www.sensacionalista.com.br/2017/09/11/projeto-do-mbl-pretende-vestir-obras-de-arte-em-museus-ao-redor-do-mundo/&lt;/a&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;link&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;95&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;39&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;-56&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td align=&#34;left&#34;&gt;2017-06-05&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;a href=&#34;http://www.sensacionalista.com.br/2017/06/05/festa-se-nada-der-certo-em-colegio-debocha-de-garis-e-faxineiras-e-mostra-que-ja-deu-tudo-errado/&#34; class=&#34;uri&#34;&gt;http://www.sensacionalista.com.br/2017/06/05/festa-se-nada-der-certo-em-colegio-debocha-de-garis-e-faxineiras-e-mostra-que-ja-deu-tudo-errado/&lt;/a&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;link&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;29&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;86&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;57&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A publicação que mais recebeu comentários negativos (não tenho certeza se é essa a interpretação mais correta dos resultados, mas enfim) é um link sobre o MBL, enquanto o mais positivo é sobre o famoso caso do “E se der errado”.&lt;/p&gt;
&lt;p&gt;O gráfico abaixo mostra a evolução do sentimento dos comentários nas publicações do Sensacionalista ao longo do tempo:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;df_comments_wide %&amp;gt;% 
  mutate(index = row_number()) %&amp;gt;% 
  ggplot(aes(x = index, y = sentimento)) +
    geom_col(aes(fill = post_type)) +
    scale_y_continuous(breaks = seq(-60, 60, 20), limits = c(-60, 60)) +
    labs(x = &amp;quot;Índice da publicação&amp;quot;, y = &amp;quot;Sentimento&amp;quot;,
         fill = NULL, title = &amp;quot;Evolução do sentimento em publicações do Sensacionalista&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2017-09-23-o-sensacionalista-e-text-mining-analise-de-sentimento-usando-o-lexiconpt_files/figure-html/unnamed-chunk-19-1.png&#34; width=&#34;672&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Uma possível interpretação do gráfico é que a série temporal não possui uma clara tendência, apesar de os picos de negatividade serem bem mais frequentes que os de positividade.&lt;/p&gt;
&lt;p&gt;Outra análise que dá para fazer é investigar o nível de sentimento de comentários associados a determinadas palavras. Por exemplo, o quão negativo costuma ser um comentário quando ele menciona a palavra &lt;strong&gt;bolsonaro&lt;/strong&gt;?&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# qual o sentimento mais associado a palavras em especifico
df_comments %&amp;gt;% 
  mutate(
    temer = str_detect(str_to_lower(message), &amp;quot;temer&amp;quot;),
    lula = str_detect(str_to_lower(message), &amp;quot;lula&amp;quot;),
    pmdb = str_detect(str_to_lower(message), &amp;quot;pmdb&amp;quot;),
    psdb = str_detect(str_to_lower(message), &amp;quot;psdb&amp;quot;),
    pt = str_detect(str_to_lower(message), &amp;quot;pt&amp;quot;),
    dilma = str_detect(str_to_lower(message), &amp;quot;dilma&amp;quot;),
    doria = str_detect(str_to_lower(message), &amp;quot;doria&amp;quot;),
    governo = str_detect(str_to_lower(message), &amp;quot;governo&amp;quot;),
    bolsonaro = str_detect(str_to_lower(message), &amp;quot;bolsonaro&amp;quot;)
  ) %&amp;gt;% 
  gather(termo, eh_presente, temer:bolsonaro) %&amp;gt;% 
  filter(eh_presente) %&amp;gt;% 
  group_by(termo) %&amp;gt;% 
  summarise(sentiment = mean(sentiment)) %&amp;gt;% 
  ggplot(aes(x = termo, y = sentiment)) + 
    geom_col(fill = &amp;quot;#C10534&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;http://www.sillasgonzaga.com/post/2017-09-23-o-sensacionalista-e-text-mining-analise-de-sentimento-usando-o-lexiconpt_files/figure-html/unnamed-chunk-20-1.png&#34; width=&#34;672&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Temer e Dilma, os dois presidentes com os piores níveis de popularidade de República, estarem associados a comentários positivos é bem surpreendente. Na verdade, isso ocorre porque a própria palavra &lt;strong&gt;temer&lt;/strong&gt; possui polaridade positiva. Para consultar a polaridade de uma palavra nos datasets presentes no &lt;code&gt;lexiconPT&lt;/code&gt;, use a função &lt;code&gt;lexiconPT::get_word_sentiment()&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;get_word_sentiment(&amp;quot;temer&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## $oplexicon_v2.1
##        term type polarity
## 28711 temer   vb        1
## 
## $oplexicon_v3.0
##        term type polarity polarity_revision
## 30160 temer   vb        1                 A
## 
## $sentilex
##       term grammar_category polarity polarity_target
## 6546 temer                V       -1           N0:N1
##      polarity_classification
## 6546                     MAN&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&#34;conclusao-e-chamada-para-futuros-trabalhos&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Conclusão e chamada para futuros trabalhos&lt;/h2&gt;
&lt;p&gt;O pacote &lt;code&gt;lexiconPT&lt;/code&gt;, apesar de simples, tem um enorme potencial para enriquecer o conteúdo de Text Mining em Português na comunidade brasileira de R. O exemplo dado nesse post pode ser considerado deveras simplório. Muitas etapas foram puladas ou desconsideradas com o intuito de fornecer a você uma rápida introdução às possibilidades criadas pelo pacote. Espero que o leitor deste post tenha se sentido motivado a fazer suas próprias análises de sentimento. As possibilidade são incontáveis.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;referencias&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Referências&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://tidytextmining.com/&#34;&gt;Text Mining with R - A Tidy Approach&lt;/a&gt;: Livro online gratuito sobre Text Mining feito pela autora do pacote &lt;code&gt;tidytext&lt;/code&gt;;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://blog.eighty20.co.za//package%20exploration/2017/06/12/sentiment-blog-post/&#34;&gt;Single Word Analysis of Early 19th Century Poetry Using tidytext&lt;/a&gt;;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/leobarone/FLS6397/blob/master/tutorials/tutorial11.Rmd&#34;&gt;Texto no R&lt;/a&gt;;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://ctlente.com/pt/trump-colbert/&#34;&gt;A Fixação de Colbert&lt;/a&gt;;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://leg.ufpr.br/~walmes/ensino/mintex/&#34;&gt;Mineração de Texto - Prof. Walmes M. Zeviani&lt;/a&gt;;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/juliasilge/tidytext&#34;&gt;Pacote tidytext&lt;/a&gt;;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://juliasilge.com/blog/text-mining-stack-overflow/&#34;&gt;Text Mining of Stack Overflow Questions&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/juliasilge/women-in-film&#34;&gt;Women in film&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</description>
    </item>
    
  </channel>
</rss>