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.
remotes::install_github("andersonheri/acR")
install.packages("ellmer")
# Configurar chave de API no .Renviron
usethis::edit_r_environ()
# Adicione: GROQ_API_KEY=sua_chaveO 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"
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"
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.
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"
)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)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 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, ...
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
Onde olhar. Olhe a última coluna,
confidence_score, e procure valores abaixo de 1,000. Comk_consistency = 3L, 1,000 = as três rodadas escolheram a mesma categoria; 0,667 = duas escolheram, uma discordou.O que este exemplo mostra. 8 dos 9 documentos têm 1,000. A exceção é
pl2858_favor(0,667), classificado comopopulista: “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.Em palavras simples. O modelo teve dúvida justamente no texto que um leitor humano também acharia ambíguo.
Cuidado. Confiança alta não garante acerto: as três rodadas podem errar do mesmo jeito. Documentos como
pl2858_favorsã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.
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
Onde olhar. Uma linha por métrica. Olhe
Estimativa(de 0 a 1), o intervaloICe aInterpretacao, 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).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.
Em palavras simples. Concordância perfeita nesta amostra, o esperado para textos construídos com posições inequívocas.
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.
# 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.
# 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")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.