Skip to contents

Esta vignette percorre o módulo quantitativo do acR em cima de um corpus de discursos parlamentares fabricado para o exemplo. Cada etapa é apresentada com uma pergunta metodológica — por que faço isso? — antes do código e do gráfico.

O caminho é o clássico da análise de conteúdo estatística: corpus → limpeza → tokenização → frequências → distintividade → visualização.

Qual métrica responde qual pergunta?

O módulo quantitativo tem seis técnicas centrais. Aprender qual usar em cada situação vale mais que decorar sintaxe:

Pergunta Técnica Função
Que palavras dominam o corpus como um todo? Frequência bruta ac_count()ac_top_terms()
Que palavras são distintivas de cada documento vs. o resto? TF-IDF ac_tf_idf()ac_plot_tf_idf()
Que palavras separam um grupo do outro estatisticamente? Keyness ac_keyness()ac_plot_keyness()
Que palavras andam juntas (associação sintagmática)? Coocorrência ac_cooccurrence()ac_plot_cooccurrence()
Onde no texto um termo aparece com mais frequência? X-ray plot ac_plot_xray()
Panorama visual rápido para relatório ou apresentação Nuvem de palavras ac_wordcloud() (ggwordcloud)

Não é redundância. As três métricas de frequência respondem perguntas diferentes:

  • Frequência bruta conta ocorrências. É a primeira olhada, mas os termos no topo tendem a ser genéricos (mesmo depois de tirar stopwords).
  • TF-IDF penaliza termos que aparecem em muitos documentos: destaca o vocabulário próprio de cada texto. Sem grupo definido a priori.
  • Keyness compara grupos com um teste estatístico (χ² ou log-likelihood). Precisa de uma variável categórica no corpus.

Para descobrir tipologias emergentes sem categoria a priori, veja vignette("cluster") e vignette("lda") (não supervisionado). Para rotular com categorias teóricas, veja vignette("qualitativo-llm") (assistido).

1. Construir o corpus

O ac_corpus() padroniza qualquer data.frame em um objeto com colunas fixas doc_id e text, mantendo os metadados que você quiser preservar para comparações posteriores.

textos <- c(
  "O governo anunciou nova politica fiscal para reduzir o deficit publico.",
  "A oposicao critica o aumento dos gastos e da divida publica no orcamento.",
  "Parlamentares debatem a reforma tributaria e o imposto de renda das familias.",
  "O presidente vetou o projeto que ampliava beneficios sociais para os pobres.",
  "O Senado aprovou o marco legal para investimentos em infraestrutura logistica.",
  "Deputados votam pela reducao da carga tributaria para pequenas empresas.",
  "A politica monetaria do Banco Central eleva a taxa de juros para conter inflacao.",
  "A inflacao acima da meta preocupa economistas e o mercado financeiro.",
  "O relator defendeu emendas ao texto do orcamento com foco em saude e educacao.",
  "A comissao aprovou o projeto que amplia beneficios sociais no interior.",
  "O ministro afirmou que a reforma tributaria simplifica o sistema para empresas.",
  "Lideres da oposicao pediram vistas ao projeto na comissao de constituicao."
)

meta <- data.frame(
  text = textos,
  tema = c("fiscal","fiscal","tributario","social","infraestrutura",
           "tributario","monetario","monetario","fiscal","social",
           "tributario","fiscal"),
  stringsAsFactors = FALSE
)

corpus <- ac_corpus(meta)
corpus
#> 
#> ── Corpus acR ──────────────────────────────────────────────────────────────────
#>  Documentos: 12
#>  Metadados: 1 coluna
#>  Idioma: "pt"
#> 
#> # A tibble: 12 × 3
#>   doc_id text                                                              tema 
#>   <chr>  <chr>                                                             <chr>
#> 1 doc_1  O governo anunciou nova politica fiscal para reduzir o deficit p… fisc…
#> 2 doc_2  A oposicao critica o aumento dos gastos e da divida publica no o… fisc…
#> 3 doc_3  Parlamentares debatem a reforma tributaria e o imposto de renda … trib…
#> 4 doc_4  O presidente vetou o projeto que ampliava beneficios sociais par… soci…
#> 5 doc_5  O Senado aprovou o marco legal para investimentos em infraestrut… infr…
#> 6 doc_6  Deputados votam pela reducao da carga tributaria para pequenas e… trib…
#> # ℹ 6 more rows

2. Limpeza: por que remover stopwords?

Sem remoção de stopwords, os termos mais frequentes de qualquer corpus em português são artigos, preposições e conjunções (o, a, de, que, para…). Eles carregam pouquíssima informação temática e sufocam qualquer padrão real de vocabulário. A regra prática em análise de conteúdo (Sampaio & Lycarião, 2021) é sempre remover stopwords antes de contar frequências ou calcular distintividade.

O ac_clean() aceita o parâmetro remove_stopwords = "pt" para aplicar uma lista de stopwords portuguesas curada. Você pode adicionar seu próprio glossário via extra_stopwords.

corpus_limpo <- ac_clean(
  corpus,
  remove_stopwords = "pt",
  extra_stopwords  = c("pra", "pro")  # opcional: coloquiais
)
corpus_limpo
#> 
#> ── Corpus acR ──────────────────────────────────────────────────────────────────
#>  Documentos: 12
#>  Metadados: 1 coluna
#>  Idioma: "pt"
#> 
#> # A tibble: 12 × 3
#>   doc_id text                                                              tema 
#>   <chr>  <chr>                                                             <chr>
#> 1 doc_1  governo anunciou nova politica fiscal reduzir deficit publico     fisc…
#> 2 doc_2  oposicao critica aumento gastos divida publica orcamento          fisc…
#> 3 doc_3  parlamentares debatem reforma tributaria imposto renda familias   trib…
#> 4 doc_4  presidente vetou projeto ampliava beneficios sociais pobres       soci…
#> 5 doc_5  senado aprovou marco legal investimentos infraestrutura logistica infr…
#> 6 doc_6  deputados votam reducao carga tributaria pequenas empresas        trib…
#> # ℹ 6 more rows

3. Tokenização

ac_tokenize() quebra cada documento em unidades de análise. Com n = 1L são unigramas (palavras); com n = 2L, bigramas (“reforma tributária”). Bigramas são úteis quando você quer capturar expressões compostas que perdem sentido tokenizadas separadamente.

tokens <- ac_tokenize(corpus_limpo, n = 1L)
head(tokens, 12)
#> # A tibble: 12 × 3
#>    doc_id token_id token   
#>    <chr>     <int> <chr>   
#>  1 doc_1         1 governo 
#>  2 doc_1         2 anunciou
#>  3 doc_1         3 nova    
#>  4 doc_1         4 politica
#>  5 doc_1         5 fiscal  
#>  6 doc_1         6 reduzir 
#>  7 doc_1         7 deficit 
#>  8 doc_1         8 publico 
#>  9 doc_2         1 oposicao
#> 10 doc_2         2 critica 
#> 11 doc_2         3 aumento 
#> 12 doc_2         4 gastos

4. Frequência

Contagem de ocorrências por documento e depois agregação. ac_count() já retorna no formato tidy pronto para pipeline com dplyr e ggplot2.

contagem <- ac_count(corpus_limpo)
contagem
#> # A tibble: 88 × 3
#>    doc_id token        n
#>    <chr>  <chr>    <int>
#>  1 doc_1  anunciou     1
#>  2 doc_1  deficit      1
#>  3 doc_1  fiscal       1
#>  4 doc_1  governo      1
#>  5 doc_1  nova         1
#>  6 doc_1  politica     1
#>  7 doc_1  publico      1
#>  8 doc_1  reduzir      1
#>  9 doc_10 amplia       1
#> 10 doc_10 aprovou      1
#> # ℹ 78 more rows

5. Top termos: o vocabulário do corpus

O gráfico abaixo mostra as 15 palavras mais frequentes — agora, com stopwords removidas, é possível ler os temas dominantes: reforma, projeto, tributária, orçamento, etc.

top <- ac_top_terms(contagem, n = 15)
ac_plot_top_terms(top) +
  ggplot2::labs(
    title    = "Termos mais frequentes no corpus",
    subtitle = "Discursos legislativos - 12 documentos fabricados",
    caption  = "acR - vignette Analise Quantitativa"
  )

6. Nuvem de palavras

Uma nuvem funciona como panorama visual rápido, útil para relatórios e apresentações. Não substitui análise numérica: o tamanho da palavra é proporcional à frequência, mas a posição no plot é aleatória.

Desde a versão 0.3.1 o ac_wordcloud() prefere automaticamente o motor ggwordcloud (retorna um ggplot, com tipografia mais legível e layout consistente com o tema do pacote); se ele não estiver instalado, cai para o wordcloud clássico. Você pode forçar via backend = "ggwordcloud" ou backend = "wordcloud".

ac_wordcloud(contagem, max_words = 40, title = "Panorama do corpus")
#> Warning in wordcloud_boxes(data_points = points_valid_first, boxes = boxes, :
#> Some words could not fit on page. They have been removed.

6.1 Nuvem comparativa entre grupos

Quando o corpus tem uma variável de agrupamento (partido, período, tema, etc.), a nuvem comparativa mostra lado a lado os termos mais distintivos de cada grupo — pontuados por TF-IDF calculado tratando cada grupo como um “documento”. Aceita 2, 3 ou mais grupos.

# Nosso corpus tem 5 temas distintos — todos entram na comparativa:
ac_plot_wordcloud_comparative(
  corpus,
  group     = tema,
  max_words = 25,
  title     = "Termos distintivos por tema"
)
#> Warning in wordcloud_boxes(data_points = points_valid_first, boxes = boxes, :
#> Some words could not fit on page. They have been removed.
#> Warning in wordcloud_boxes(data_points = points_valid_first, boxes = boxes, :
#> Some words could not fit on page. They have been removed.
#> Warning in wordcloud_boxes(data_points = points_valid_first, boxes = boxes, :
#> Some words could not fit on page. They have been removed.
#> Warning in wordcloud_boxes(data_points = points_valid_first, boxes = boxes, :
#> Some words could not fit on page. They have been removed.
#> Warning in wordcloud_boxes(data_points = points_valid_first, boxes = boxes, :
#> Some words could not fit on page. They have been removed.

O layout usa ggwordcloud com facets quando disponível (backend = "auto"), e cai em geom_text com jitter reproduzível quando não. A semente do posicionamento é controlada por seed = 42L. Cores vêm de ac_palette() por padrão — uma por grupo.

7. TF-IDF: o que é distintivo em cada documento?

Frequência bruta favorece palavras que aparecem em quase todos os documentos (mesmo depois de stopwords). O TF-IDF (term frequency × inverse document frequency) penaliza termos comuns e destaca vocabulário próprio de cada grupo — útil quando o corpus é composto por textos com propostas ou temas distintos.

Com 12 discursos curtos e cada palavra aparecendo em 1–2 documentos, o TF-IDF por documento resulta em barras quase todas iguais (todo termo único vale o mesmo). O padrão fica mais legível agregando por tema: tratamos cada tema como um “documento” e o TF-IDF revela o vocabulário característico de cada eixo temático do corpus.

freq_tema  <- ac_count(corpus_limpo, by = "tema")
tfidf_tema <- ac_tf_idf(freq_tema, by = "tema")

ac_plot_tf_idf(tfidf_tema, by = "tema", n = 6) +
  ggplot2::labs(
    title    = "Termos mais distintivos por tema (TF-IDF)",
    subtitle = "Cada eixo temático como um documento; alta pontuação = termo característico do tema"
  )

8. Keyness: o que é distintivo entre grupos?

Enquanto TF-IDF compara documento vs. corpus, keyness compara grupos — e o critério que define o grupo é seu, não do pacote. Pode ser partido, período histórico, tema, região, autor, condição experimental, faixa etária, qualquer variável categórica no seu corpus. É a métrica clássica para a pergunta comparativa: “o que um grupo diz que o outro não diz?”.

Aqui comparamos os temas fiscal e tributário — quais palavras marcam cada lado?

freq_tema     <- ac_count(corpus_limpo, by = "tema")
freq_2grupos  <- freq_tema[freq_tema$tema %in% c("fiscal", "tributario"), ]

kn <- ac_keyness(freq_2grupos, group = "tema", target = "fiscal")

ac_plot_keyness(kn, n = 10) +
  ggplot2::labs(
    title    = "Palavras distintivas: 'fiscal' vs. 'tributario'",
    subtitle = "Barras positivas = distintivas do tema 'fiscal'"
  )

Interpretação: chi-quadrado alto indica sobre-representação estatística. Termos como deficit e orçamento marcam o eixo fiscal; carga e renda o tributário — resultado consistente com o corpus de exemplo.

9. Rede de co-ocorrência

Palavras que aparecem próximas com frequência formam agrupamentos semânticos. A rede de coocorrência mostra essas vizinhanças com base numa janela de tokens (aqui, 5 palavras à esquerda e à direita).

Útil para: identificar frames, mapear associações conceituais e encontrar combinações de palavras que definem sub-temas dentro do corpus.

cooc <- ac_cooccurrence(corpus_limpo, window = 5L, min_count = 2L)

if (requireNamespace("ggraph", quietly = TRUE)) {
  ac_plot_cooccurrence(cooc, top_n = 25) +
    ggplot2::labs(
      title    = "Rede de coocorrencia (janela = 5, freq minima = 2)",
      subtitle = "Arestas conectam termos que co-ocorrem no mesmo contexto"
    )
}

9.1 Trocando o peso: PMI em vez de contagem

A rede padrão pondera arestas pela contagem de coocorrências — termos frequentes tendem a dominar. O PMI (pointwise mutual information) mede o quanto duas palavras aparecem juntas acima do esperado por acaso; revela pares surpreendentes, mesmo que raros.

if (requireNamespace("ggraph", quietly = TRUE)) {
  ac_plot_cooccurrence(cooc, top_n = 25, weight = "pmi") +
    ggplot2::labs(
      title    = "Rede de coocorrencia ponderada por PMI",
      subtitle = "Destaca pares associados alem do esperado por acaso"
    )
}

9.2 Trocando o layout

O layout controla como os nós são posicionados no plano. O padrão "fr" (Fruchterman-Reingold) minimiza cruzamentos; "kk" (Kamada-Kawai) respeita distâncias de grafo; "circle" alinha tudo num anel — útil para corpora pequenos onde a topologia importa menos que a legibilidade.

if (requireNamespace("ggraph", quietly = TRUE)) {
  ac_plot_cooccurrence(cooc, top_n = 25, layout = "circle") +
    ggplot2::labs(
      title    = "Rede de coocorrencia — layout circular",
      subtitle = "Todas as palavras num anel; arestas mostram o grafo cru"
    )
}

9.3 Coocorrência como matriz (heatmap)

Quando a rede fica saturada de arestas, um heatmap das top-N palavras pode ser mais legível: linhas e colunas são termos, e a intensidade da célula é a coocorrência. Não requer ggraph.

top_tokens <- ac_top_terms(ac_count(corpus_limpo), n = 12)$token
cooc_mat   <- cooc[cooc$word1 %in% top_tokens & cooc$word2 %in% top_tokens, ]

ggplot2::ggplot(cooc_mat,
                ggplot2::aes(word1, word2, fill = cooc)) +
  ggplot2::geom_tile(color = "white") +
  ggplot2::scale_fill_gradient(low = "#F1F5F9", high = ac_palette(1)) +
  ggplot2::labs(
    title    = "Matriz de coocorrencia (top 12 termos)",
    subtitle = "Alternativa ao grafo quando a rede fica densa",
    x = NULL, y = NULL, fill = "cooc."
  ) +
  theme_ac() +
  ggplot2::theme(
    axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)
  )

Referências

  • Krippendorff, K. (2018). Content Analysis: An Introduction to Its Methodology. 4. ed. SAGE.
  • Sampaio, R. C.; Lycarião, D. (2021). Análise de conteúdo categorial: manual de aplicação. ENAP.
  • Silge, J.; Robinson, D. (2017). Text Mining with R. O’Reilly.