The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Análise de proposições legislativas

Contexto

Esta vignette demonstra o pipeline completo do acR aplicado à classificação do posicionamento de textos legislativos. O corpus cobre três proposições de alta saliência na 57ª legislatura brasileira: a jornada 6x1 (PLP 300/2023), a anistia dos atos de 8 de Janeiro (PL 2858/2022) e a Reforma Administrativa (PEC 32/2020).

O objetivo é classificar cada texto em uma categoria de posicionamento (Favorável, Contrário, Neutro/Técnico, Populista ou Ambíguo), calcular o grau de certeza via self-consistency e validar os resultados com revisão humana.


1. Instalar e configurar

remotes::install_github("andersonheri/acR")
install.packages("ellmer")

# Configurar chave de API no .Renviron
usethis::edit_r_environ()
# Adicione: GROQ_API_KEY=sua_chave

2. Corpus

O corpus é criado a partir de um data.frame com os textos e metadados. Cada proposição tem três documentos: a ementa técnica, um discurso favorável e um discurso contrário.

library(acR)
library(ellmer)
library(dplyr)

textos <- c(
  "Altera a CLT para instituir a escala 4x3, vedando a jornada 6x1.",
  "Conquista histórica: votaremos favoráveis com convicção.",
  "Gerará desemprego em massa nas micro e pequenas empresas.",
  "Concede anistia aos condenados pelos atos de 8 de janeiro de 2023.",
  "Presos políticos: votar pela anistia é votar pela democracia.",
  "Anistiar quem atacou o Congresso é uma afronta à democracia.",
  "Modifica a CF/88, extinguindo a estabilidade para novos servidores.",
  "Modernização necessária para aumentar eficiência e reduzir custos.",
  "Ataca direitos conquistados e abre espaço para perseguição política."
)

df <- data.frame(
  id    = c("plp300_ementa",  "plp300_favor",  "plp300_contra",
            "pl2858_ementa",  "pl2858_favor",  "pl2858_contra",
            "pec32_ementa",   "pec32_favor",   "pec32_contra"),
  texto = textos,
  tema  = c("6x1",     "6x1",     "6x1",
            "anistia", "anistia", "anistia",
            "reform",  "reform",  "reform"),
  tipo  = c("ementa",   "favoravel", "contrario",
            "ementa",   "favoravel", "contrario",
            "ementa",   "favoravel", "contrario"),
  stringsAsFactors = FALSE
)

corpus <- ac_corpus(df, text = texto, docid = id)
print(corpus)
## -- Corpus acR --
## * Documentos: 9
## * Metadados: 2 colunas (tema, tipo)
## * Idioma: "pt"

3. Codebook

O codebook define as cinco categorias de posicionamento com suas definições operacionais. A instrução orienta o modelo a usar Populista para textos com apelo emocional sem argumentação substantiva: uma distinção importante para a análise de discursos parlamentares brasileiros.

codebook <- ac_qual_codebook(
  name         = "posicionamento_proposicoes",
  instructions = paste(
    "Classifique o posicionamento do texto em relação à proposição.",
    "Use Populista para apelo emocional sem argumentação substantiva."
  ),
  categories = list(
    favoravel      = list(definition = "Apoio explícito à proposição com argumentação substantiva."),
    contrario      = list(definition = "Oposição à proposição com argumentação substantiva."),
    neutro_tecnico = list(definition = "Descrição jurídica ou técnica sem valoração explícita."),
    populista      = list(definition = "Apelo emocional sem evidência ou argumentação técnica."),
    ambiguo        = list(definition = "Posicionamento contraditório ou impossível de classificar.")
  ),
  mode = "manual"
)

print(codebook)
## -- Codebook acR: "posicionamento_proposicoes" --
## * Modo: "manual"
## * Categorias (5): "favoravel", "contrario", "neutro_tecnico",
##   "populista" and "ambiguo"
## * Multilabel: FALSE
## * Idioma: "pt"

3b. Refinamento do codebook

O acR oferece um conjunto de funções para refinar o codebook iterativamente antes de iniciar a classificação. Este passo é opcional mas recomendado para análises com categorias complexas ou sobrepostas.

Adicionar exemplos às categorias

Exemplos positivos e negativos melhoram a precisão da classificação, especialmente em categorias limítrofes como populista e ambiguo (Sampaio & Lycarião, 2021):

# Recriar com exemplos explícitos para as categorias mais difíceis
codebook <- ac_qual_codebook(
  name         = "posicionamento_proposicoes",
  instructions = paste(
    "Classifique o posicionamento do texto em relação à proposição.",
    "Use Populista para apelo emocional sem argumentação substantiva."
  ),
  categories = list(
    favoravel = list(
      definition   = "Apoio explícito à proposição com argumentação substantiva.",
      examples_pos = c("Esta reforma aumentará a produtividade e reduzirá custos."),
      examples_neg = c("Votaremos com o coração pela causa do povo.")
    ),
    contrario = list(
      definition   = "Oposição à proposição com argumentação substantiva.",
      examples_pos = c("Os dados mostram que esta medida aumentará o desemprego."),
      examples_neg = c("Esta proposta é uma vergonha nacional.")
    ),
    neutro_tecnico = list(
      definition   = "Descrição jurídica ou técnica sem valoração explícita.",
      examples_pos = c("Altera o art. 58 da CLT, estabelecendo nova escala."),
      examples_neg = c("Apoiamos esta proposta de modernização.")
    ),
    populista = list(
      definition   = "Apelo emocional sem evidência ou argumentação técnica.",
      examples_pos = c("Presos políticos: este é o momento da justiça!"),
      examples_neg = c("Estudos mostram que a medida reduz 15% dos custos."),
      weight       = 1.5  # categoria mais difícil: peso maior
    ),
    ambiguo = list(
      definition   = "Posicionamento contraditório ou impossível de classificar.",
      examples_pos = c("Apoio a reforma mas temo suas consequências sociais."),
      weight       = 1.2
    )
  ),
  mode = "manual"
)

Enriquecer com literatura via LLM

ac_qual_codebook_hybrid() re-ancora as definições em referências bibliográficas buscadas via LLM, preservando os exemplos manuais:

chat_obj <- chat_groq(
  model = "openai/gpt-oss-120b",
  echo  = "none"
)

codebook_enriquecido <- ac_qual_codebook_hybrid(
  codebook = codebook,
  chat     = chat_obj,
  n_refs   = 2L,
  lang     = "pt"
)

# Ver definição enriquecida da categoria populista
cat(codebook_enriquecido$categories$populista$definition)
cat("\nReferências:\n")
print(codebook_enriquecido$categories$populista$references)

Fundir com codebook de estilo retórico

Para análises multidimensionais, é possível fundir dois codebooks. Aqui adicionamos uma dimensão de estilo retórico ao codebook de posicionamento:

codebook_retorico <- ac_qual_codebook(
  name         = "estilo_retorico",
  instructions = "Identifique o estilo retórico dominante.",
  categories   = list(
    pathos = list(
      definition   = "Apelo emocional predominante.",
      examples_pos = c("Não podemos trair a esperança do povo brasileiro.")
    ),
    logos = list(
      definition   = "Apelo racional e argumentativo predominante.",
      examples_pos = c("Os dados do IBGE confirmam redução de 12% no emprego.")
    )
  )
)

# Fundir: posicionamento + estilo retórico
codebook_completo <- ac_qual_codebook_merge(
  cb1          = codebook_enriquecido,
  cb2          = codebook_retorico,
  name         = "posicionamento_e_estilo",
  on_conflict  = "rename_second",
  instructions = paste(
    "Classifique o posicionamento e o estilo retórico do texto.",
    "Use Populista para apelo emocional sem argumentação."
  )
)

Gerar system prompt e inspecionar histórico

# Gerar system prompt para uso com ellmer
prompt <- as_prompt(
  codebook_enriquecido,
  reasoning        = TRUE,
  reasoning_length = "short"
)

# Inspecionar todas as modificações feitas no codebook
ac_qual_codebook_history(codebook_enriquecido)
## -- Histórico: "posicionamento_proposicoes" --
## # A tibble: 1 x 3
##   timestamp           action detail
##   <chr>               <chr>  <chr>
## 1 2025-06-01 14:23:01 hybrid favoravel, contrario, neutro_tecnico, ...

Salvar o codebook para replicabilidade

ac_qual_save_codebook(
  codebook_enriquecido,
  path = "codebook_proposicoes_v1.yaml"
)

# Em outra sessão ou para outro pesquisador:
# codebook <- ac_qual_load_codebook("codebook_proposicoes_v1.yaml")

4. Classificação com LLM

O argumento chat = recebe qualquer objeto Chat do ellmer. Aqui usamos o Groq com openai/gpt-oss-120b, que oferece plano gratuito e latência baixa: ideal para corpora de tamanho médio como este.

chat_obj <- chat_groq(
  model = "openai/gpt-oss-120b",
  echo  = "none"
)

resultado <- ac_qual_code(
  corpus           = corpus,
  codebook         = codebook_enriquecido,
  chat             = chat_obj,
  confidence       = "total",
  k_consistency    = 3L,
  reasoning        = TRUE,
  reasoning_length = "short"
)

Os resultados esperados para este corpus, dado o conteúdo majoritariamente inequívoco dos textos, são classificações com confiança alta (≥ 0.80) na maioria dos documentos, com uma exceção deliberadamente ambígua:

## # A tibble: 9 × 5
##   doc_id          tema    tipo       categoria      confidence_score
##   <chr>           <chr>   <chr>      <chr>                     <dbl>
## 1 plp300_ementa   6x1     ementa     neutro_tecnico            1.000
## 2 plp300_favor    6x1     favoravel  favoravel                 1.000
## 3 plp300_contra   6x1     contrario  contrario                 1.000
## 4 pl2858_ementa   anistia ementa     neutro_tecnico            1.000
## 5 pl2858_favor    anistia favoravel  populista                 0.667
## 6 pl2858_contra   anistia contrario  contrario                 1.000
## 7 pec32_ementa    reform  ementa     neutro_tecnico            1.000
## 8 pec32_favor     reform  favoravel  favoravel                 1.000
## 9 pec32_contra    reform  contrario  contrario                 1.000

O texto pl2858_favor (“Presos políticos: votar pela anistia é votar pela democracia”) recebeu populista com confiança 0.667, caso limítrofe que ilustra a utilidade do self-consistency para identificar documentos ambíguos que merecem revisão humana prioritária.

Como ler esta tabela

  1. Onde olhar. Olhe a última coluna, confidence_score, e procure valores abaixo de 1,000. Com k_consistency = 3L, 1,000 = as três rodadas escolheram a mesma categoria; 0,667 = duas escolheram, uma discordou.

  2. O que este exemplo mostra. 8 dos 9 documentos têm 1,000. A exceção é pl2858_favor (0,667), classificado como populista: “Presos políticos: votar pela anistia é votar pela democracia” defende a proposta com um apelo à democracia em tom de slogan, e uma das três rodadas leu isso como discurso populista em vez de posição favorável.

  3. Em palavras simples. O modelo teve dúvida justamente no texto que um leitor humano também acharia ambíguo.

  4. Cuidado. Confiança alta não garante acerto: as três rodadas podem errar do mesmo jeito. Documentos como pl2858_favor são bons candidatos a uma rodada de diagnóstico do codebook (strategy = "uncertainty"), separada da amostra aleatória que a seção 5 usa para estimar a confiabilidade.


5. Validação humana

A amostra de validação é aleatória (strategy = "random", o padrão): para que a concordância medida nela represente o corpus inteiro, ela precisa ser representativa (Krippendorff, 2018, cap. 12). Priorizar os casos de menor confiança (strategy = "uncertainty") serve para diagnosticar erros do codebook, não para estimar confiabilidade.

# Amostra aleatória para estimar a confiabilidade
amostra <- ac_qual_sample(
  resultado,
  n        = 5,
  strategy = "random"
)

ac_qual_export_for_review(
  sample = amostra,
  path   = "revisao_proposicoes.xlsx",
  corpus = corpus
)

# Após preenchimento da coluna categoria_humano no Excel:
humano <- ac_qual_import_human(
  path    = "revisao_proposicoes.xlsx",
  cat_col = "categoria_humano",
  id_col  = "doc_id"
)

concordancia <- ac_qual_irr(
  gold      = humano,
  predicted = resultado,
  method    = "all",
  id_col    = "doc_id",
  cat_col   = "categoria"
)

print(concordancia)
## ── Confiabilidade inter-anotador (acR) ──
## • Documentos comparados: 5
## • Categorias: contrario, favoravel, neutro_tecnico
##
## Metrica                         Estimativa  IC              Interpretacao
## ---------------------------------------------------------------------------
## Percent Agreement               1.000       --              alta (>= 90%; Neuendorf, 2017)
## Cohen's Kappa (unweighted)      1.000       [1.000, 1.000]  quase perfeita (Landis & Koch, 1977)
## Fleiss' Kappa                   1.000       [1.000, 1.000]  quase perfeita (Landis & Koch, 1977)
## Krippendorff's Alpha (nominal)  1.000       [1.000, 1.000]  confiável (>= 0,800; Krippendorff, 2018)

Como ler esta tabela

  1. Onde olhar. Uma linha por métrica. Olhe Estimativa (de 0 a 1), o intervalo IC e a Interpretacao, que usa a referência própria de cada métrica: Landis & Koch (1977) para kappa, Krippendorff (2018) para o alfa, Neuendorf (2017) para o percentual. A matriz de confusão embaixo mostra onde humano e modelo discordaram (fora da diagonal).

  2. O que este exemplo mostra. Todas as métricas valem 1,000 e a matriz de confusão só tem números na diagonal: nos 5 documentos revisados, humano e modelo deram exatamente a mesma categoria.

  3. Em palavras simples. Concordância perfeita nesta amostra, o esperado para textos construídos com posições inequívocas.

  4. Cuidado. O IC de [1,000; 1,000] não significa certeza. Com concordância perfeita, toda reamostra bootstrap também é perfeita, e o método não consegue expressar a incerteza de uma amostra de 5 documentos (Efron & Tibshirani, 1993). Reporte kappa ou alfa como medida principal (o percentual não desconta o acaso) e use uma amostra bem maior antes de afirmar confiabilidade.

Concordância perfeita é esperada para este corpus, dado que os textos foram construídos com posicionamentos inequívocos. Em corpora reais com textos ambíguos, o critério mais usado em análise de conteúdo é o de Krippendorff (2018, sec. 12.4): alfa a partir de 0,800 para conclusões sem ressalvas e entre 0,667 e 0,800 só para conclusões provisórias. As faixas de Landis & Koch (1977) (“substancial” para kappa entre 0,61 e 0,80) são rótulos descritivos que os próprios autores chamam de arbitrários, não um critério de publicação. Com 5 documentos, como aqui, o intervalo de confiança é largo demais para qualquer conclusão: Neuendorf (2017) sugere ao menos 50 unidades na subamostra de confiabilidade.


6. Análise por tema e tipo

# Distribuição de categorias por tema
resultado |>
  count(tema, categoria) |>
  arrange(tema, desc(n))

# Verificar se ementas são sempre neutro_tecnico
resultado |>
  filter(tipo == "ementa") |>
  select(doc_id, tema, categoria, confidence_score)
## # A tibble: 3 × 4
##   doc_id         tema    categoria      confidence_score
##   <chr>          <chr>   <chr>                     <dbl>
## 1 plp300_ementa  6x1     neutro_tecnico                1
## 2 pl2858_ementa  anistia neutro_tecnico                1
## 3 pec32_ementa   reform  neutro_tecnico                1

As três ementas foram corretamente classificadas como neutro_tecnico com confiança máxima, resultado consistente com a natureza jurídica desses textos.


7. Exportar resultados

# CSV para análise posterior
ac_export(resultado, formato = "csv", arquivo = "proposicoes_codificadas.csv")

# Excel para compartilhamento
ac_export(resultado, formato = "xlsx", arquivo = "proposicoes_codificadas.xlsx")

# LaTeX para inclusão em artigo
ac_export(resultado, formato = "latex", arquivo = "proposicoes_codificadas.tex")

Referências

BARDIN, L. Análise de conteúdo. Edições 70, 2011.

EFRON, B.; TIBSHIRANI, R. J. An Introduction to the Bootstrap. New York: Chapman & Hall, 1993.

GILARDI, F.; ALIZADEH, M.; KUBLI, M. ChatGPT outperforms crowd workers for text-annotation tasks. PNAS, v. 120, n. 30, 2023.

KRIPPENDORFF, K. Content Analysis: An Introduction to Its Methodology. 4. ed. SAGE, 2018.

LANDIS, J. R.; KOCH, G. G. The measurement of observer agreement for categorical data. Biometrics, v. 33, n. 1, p. 159-174, 1977.

NEUENDORF, K. A. The Content Analysis Guidebook. 2. ed. Thousand Oaks: SAGE, 2017.

SAMPAIO, R. C.; LYCARIAO, D. Análise de conteúdo categorial: manual de aplicação. Brasília: ENAP, 2021.

WICKHAM, H. et al. ellmer: Chat with Large Language Models. Posit, 2025. Disponível em: https://ellmer.tidyverse.org.

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.