--- title: "Introdução ao acR: pipeline integrado de análise de conteúdo" output: rmarkdown::html_vignette: toc: true toc_depth: 2 vignette: > %\VignetteIndexEntry{Introdução ao acR: pipeline integrado de análise de conteúdo} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 4, fig.align = "center", dpi = 120 ) ``` Esta vignette percorre o pipeline completo do `acR` em um exemplo pequeno mas realista (discursos parlamentares favoráveis e contrários a uma reforma), mostrando o *output* efetivo de cada etapa. Se você prefere um tour de 5 minutos com só o essencial, veja o **[Quickstart](quickstart.html)**. ```{r setup} library(acR) ``` ```{r lexico, include=FALSE} # ac_sentiment() baixa o OpLexicon (GitHub) na primeira execução e guarda # em cache. Sem acesso a ele, os trechos que dependem do léxico não rodam, # em vez de interromper a vignette com erro. tem_lexico <- tryCatch({ suppressMessages(ac_sentiment(ac_corpus(data.frame(text = "bom")))) TRUE }, error = function(e) FALSE) ``` ```{r aviso-lexico, echo=FALSE, results="asis", eval=!tem_lexico} cat( "> **Aviso:** o léxico OpLexicon não pôde ser baixado ao gerar esta", "> página (sem acesso à internet), então os trechos que dependem dele", "> não foram executados. Rode a vignette com internet para ver as saídas.", sep = "\n" ) ``` ## O objeto central: `ac_corpus` Tudo no `acR` gira em torno do `ac_corpus`, um `tibble` com colunas padronizadas `doc_id` e `text` que carrega quaisquer metadados adicionais. As demais funções aceitam esse objeto diretamente. ```{r corpus} df <- data.frame( id = paste0("d", 1:8), texto = c( "Sou favoravel a reforma tributária: simplifica o sistema e reduz distorcoes.", "Voto contra: essa reforma vai destruir o setor produtivo brasileiro.", "Apoio a proposta com forte adesao, ha ganhos claros de eficiência arrecadatória.", "Rejeito o texto: transfere renda das familias para grandes corporacoes.", "Defendo a reforma que corrige as distorcoes historicas do nosso sistema.", "Somos contrários: o projeto beneficia apenas os mais ricos do pais.", "Voto sim, precisamos modernizar urgentemente a estrutura tributária.", "Voto não, os pequenos empresários serao os grandes prejudicados." ), partido = c("PT","PL","PT","PL","PT","PL","PT","PL"), posicao = c("favor","contra","favor","contra","favor","contra","favor","contra"), stringsAsFactors = FALSE ) corpus <- ac_corpus(df, text = texto, docid = id, meta = c(partido, posicao)) corpus ``` ## 1. Frequências e termos salientes Antes de contar, precisamos **remover stopwords**. Sem essa etapa, os termos mais frequentes seriam `o`, `a`, `de`, `que`: carregam pouca informação temática e mascaram os padrões reais do corpus. ```{r contagem} corpus_limpo <- ac_clean(corpus, remove_stopwords = "pt") # Frequência global freq <- ac_count(corpus_limpo) ac_top_terms(freq, n = 8) ``` Agora quebrando por lado (`favor` vs `contra`): ```{r contagem-grupo} freq_lado <- ac_count(corpus_limpo, by = "posicao") ac_top_terms(freq_lado, n = 5, by = "posicao") ``` ## 2. Termos distintivos com *keyness* Quais palavras aparecem **muito mais** em um grupo do que no outro? A métrica `chi2` (χ²) mede essa distintividade. ```{r keyness} key <- ac_keyness(freq_lado, group = "posicao", target = "favor") head(key, 6) ``` Visualização compacta dos termos mais distintivos: ```{r plot-keyness} if (requireNamespace("ggplot2", quietly = TRUE)) { ac_plot_keyness(key, n = 6) } ``` > **Como ler este gráfico** > > 1. **Onde olhar.** Cada barra é uma palavra. Barra para a **direita** > (verde-azulada) = mais usada nos discursos `favor`; para a > **esquerda** (vermelha) = mais usada nos `contra`. Quanto mais > comprida, maior a diferença entre os lados. Confira os números na > tabela `key` logo acima: `n_target` (vezes em `favor`) e > `n_reference` (vezes em `contra`). > > 2. **O que este exemplo mostra.** Do lado favorável, as três barras > mais longas são *distorcoes*, *sistema* e *tributária* (2 vezes em > `favor`, nenhuma em `contra`). Do lado contrário, a barra mais > longa de todo o gráfico é *grandes* (0 contra 2: "grandes > corporacoes", "grandes prejudicados"). As outras barras, mais > curtas e todas iguais, são palavras que aparecem uma vez só. > > 3. **Em palavras simples.** Quem defende a reforma fala do sistema > tributário e de corrigir distorções; quem é contra fala de quem > ganha e quem perde (os "grandes"). > > 4. **Cuidado.** Com 8 discursos curtos, uma diferença de 2 contra 0 é > pouca evidência: leia o gráfico como pista para o que ler nos > textos, não como resultado estatístico (Dunning, 1993). Quando > várias palavras empatam (as barras curtas), o gráfico mostra as > primeiras em ordem alfabética: a escolha entre elas não tem > significado. ## 3. Sentimento por documento (OpLexicon) Escore agregado de polaridade de cada documento, usando o léxico OpLexicon (Souza e Vieira, 2012): ```{r sentimento, eval=tem_lexico} sent <- ac_sentiment(corpus) sent ``` Visualizando por documento: ```{r plot-sentimento, eval=tem_lexico} if (requireNamespace("ggplot2", quietly = TRUE)) { ac_plot_sentiment(sent) } ``` > **Como ler este gráfico** > > 1. **Onde olhar.** Cada barra é um discurso; o comprimento é o escore > (palavras positivas menos negativas do léxico OpLexicon). Para a > direita (verde) = positivo; para a esquerda (laranja) = negativo; > sem barra = zero. Compare com a posição declarada de cada discurso, > que você conhece pela tabela do corpus. > > 2. **O que este exemplo mostra.** `d3` tem o maior escore (+2: "forte" > e "claros"). O caso que mais ensina é `d2`, um discurso **contra** > ("Voto contra: essa reforma vai destruir o setor produtivo") que > sai **positivo**: o léxico conta "produtivo" como +1 e não tem > nenhuma palavra negativa para "destruir". `d6` (contra) sai zero > porque "ricos" conta +1 e anula "contrários" (−1). > > 3. **Em palavras simples.** O léxico não lê a frase, ele soma palavras > soltas. Quando a opinião está num verbo ("destruir") ou quando uma > palavra positiva aparece num contexto negativo ("setor produtivo" > sendo destruído), o escore erra a direção. > > 4. **Cuidado.** O escore é relativo ao léxico, não uma medida de "quão > positivo é o texto" (Taboada et al., 2011). Neste corpus, até > "tributária" conta como +1, um erro de domínio. Antes de usar o > escore, compare-o com uma amostra lida por humanos; a > `vignette("sentimento")` mostra esses erros em detalhe. ## 4. Escolher um modelo LLM Antes de codificar qualitativamente, o `acR` recomenda modelos com base em custo, idioma e tipo de tarefa. Consulta 100 % *offline*: ```{r modelos} ac_qual_recommend_model(task = "coding", budget = "medium", lang = "pt", n = 3) ``` Base: Gilardi, Alizadeh e Kubli (2023) e Törnberg (2023). ## 5. Codificação qualitativa com LLM Esta etapa exige chave de API (`ANTHROPIC_API_KEY`, `GROQ_API_KEY`, etc.) e por isso não roda na construção da vignette. Estrutura mínima: ```{r coding, eval = FALSE} codebook <- ac_qual_codebook( name = "posicionamento", instructions = "Classifique o posicionamento sobre a reforma tributária.", categories = list( favor = list( definition = "Manifestação favorável à reforma.", examples_pos = "Sou favoravel a reforma, simplifica o sistema." ), contra = list( definition = "Manifestação contrária à reforma.", examples_pos = "Voto contra, vai destruir o setor produtivo." ) ) ) codificado <- ac_qual_code( corpus = corpus, codebook = codebook, model = "anthropic/claude-sonnet-4-5" ) ``` `codificado` é um tibble com `doc_id`, `categoria`, `confidence_score` (via *self-consistency*) e `reasoning`. ## 6. Validação humana e confiabilidade ```{r validacao-code, eval = FALSE} # Amostra aleatória (representativa): é ela que estima a confiabilidade # do corpus. Amostrar só os casos incertos subestimaria a concordância. amostra <- ac_qual_sample(codificado, n = 50, strategy = "random") # Exporta planilha para revisão humana ac_qual_export_for_review(amostra, path = "revisao.xlsx", corpus = corpus) # Após preencher, reimporta e calcula IRR humano <- ac_qual_import_human("revisao.xlsx") ac_qual_reliability(llm = codificado, human = humano) ``` Um exemplo do formato do output com dados sintéticos (equivalente ao que sai com codificação real): ```{r validacao-demo} llm_sim <- tibble::tibble( doc_id = paste0("d", 1:8), categoria = c("favor","contra","favor","contra","favor","contra","favor","contra") ) humano_sim <- tibble::tibble( doc_id = paste0("d", 1:8), categoria = c("favor","contra","favor","contra","favor","favor","favor","contra") # 1 discordância em 8 casos -> ~87.5% de concordância ) ac_qual_reliability(llm = llm_sim, human = humano_sim, bootstrap = 50) ``` As métricas incluem *percent agreement*, *alpha* de Krippendorff, AC1 de Gwet e F1 macro, com IC via *bootstrap* e interpretação segundo Landis e Koch (1977) e Gwet (2014). ## Próximos passos * **[Codificação com LLMs](qualitativo-llm.html)**: codebook completo, `ellmer`, *self-consistency*, tradução, fusão. * **[Análise de proposições](analise-proposicoes.html)**: pipeline real de ponta-a-ponta em texto legislativo brasileiro. * **[LDA](lda.html)**: modelagem de tópicos. * **[Sentimento](sentimento.html)**: pipeline detalhado com OpLexicon. ## Referências DUNNING, T. Accurate methods for the statistics of surprise and coincidence. *Computational Linguistics*, v. 19, n. 1, p. 61-74, 1993. GILARDI, F.; ALIZADEH, M.; KUBLI, M. ChatGPT outperforms crowd workers for text-annotation tasks. *PNAS*, v. 120, n. 30, 2023. GWET, K. L. *Handbook of inter-rater reliability*. 4. ed. Gaithersburg: Advanced Analytics, 2014. KRIPPENDORFF, K. *Content analysis: an introduction to its methodology*. 4. ed. Thousand Oaks: 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. SOUZA, M.; VIEIRA, R. Sentiment analysis on Twitter with Portuguese language. *STIL/SBC*, 2012. TABOADA, M. et al. Lexicon-based methods for sentiment analysis. *Computational Linguistics*, v. 37, n. 2, p. 267-307, 2011. TÖRNBERG, P. ChatGPT-4 outperforms experts and crowd workers in annotating political Twitter messages with zero-shot learning. *PLOS ONE*, v. 18, n. 4, 2023.