Na ciência da computação chamamos uma sequência de caracteres de string. Para o desenvolvimento de análises automatizadas de conteúdo, é necessário saber como processar esse tipo especial de dado (o texto como dado)3. Nesse sentido, três coisas são importantes de serem lembradas aqui:
É possível utilizar toda a versatilidade de estruturas de dados no R (vetores, matrizes, listas, data.frame, etc.) para processar sequências de caracteres. Como trabalhar com strings no R, portanto?
Para declarar uma string, utilizamos aspas simples ' ou aspas dupla ""“. Vejamos o caso dos dois vetores abaixo, ambos recebendo a letra”a". # Vetores de caracteres caracter1 <- "a" caracter2 <- 'A' class(caracter1) ## [1] "character" ## [1] "character"Ambos são da classe character.
O R diferencia letras maiúsculas de letras minúsculas. Se compararmos os dois objetos criados acima, temos: ## [1] FALSE
# string txt <- "uma string é uma sequência de caracteres" txt <- 'também pode ser utilizada com aspas simples' txt <- "no caso de aspas dupla, usa-se 'aspas simples' na string" txt <- 'no caso de aspas simples, usa-se "aspas dupla" na string' txt <- "para usar \"aspas dupla\" na string é necessário usar \\" cat(txt) ## para usar "aspas dupla" na string é necessário usar \O R armazena a sequência de caracteres conforme ela é apresentada. Porém, é possível fazer uso de caracteres especiais para que o computador interprete e apresente o texto de forma adequada. Como vimos acima, o objeto txt armazena a string conforme foi redigida, mas com o uso da função cat() podemos apresentá-lo de forma adequada. Perceba a diferença entre o resultado e a sequência de caracteres que, de fato, foi armazenada no objeto txt.
É possível declarar um vetor de caracteres vazio. # vetor de caracteres com 5 strings vazias palmeiras <- character(5) palmeiras ## [1] "" "" "" "" ""Vejamos seu tamanho: length(palmeiras) # verificando o tamanho do vetor ## [1] 5Vemos que o objeto palmeiras possui 5 elementos, todos sem qualquer conteúdo, mas da classe character. Será que é possível inserir conteúdo em elementos específicos do vetor? Vejamos: # incluindo string no primeiro e terceiro elementos do vetor palmeiras[1] <- "Quando surge o alviverde imponente" palmeiras[3] <- "Sabe bem o que vem pela frente" palmeiras ## [1] "Quando surge o alviverde imponente" "" ## [3] "Sabe bem o que vem pela frente" "" ## [5] ""Ótimo! Significa que podemos ter um vetor no com o Hino do Palmeiras, sendo cada um de seus elementos um verso dessa bela poesia. E seria possível ter um vetor cujos elementos fossem os hinos (sequências de caracteres/strings) de todos os times do país? Sim!
Um vetor com uma string vazia é diferente de um vetor sem strings # Atenção: str_vazia <- "" # string vazia char_vazio <- character(0) # caracter vazio length(str_vazia) ## [1] 1 ## [1] 0
É importante saber como o R processa o texto como dado (character) em conjunto com outros formatos. frase <- "Campeonatos Brasileiros vencidos pelo Palmeiras." is.numeric(frase) ## [1] FALSE ## [1] TRUEAcima verificamos que a classe do objeto frase é de tipo character. quantidade <- 5 + 5 quantidade ## [1] 10 ## [1] TRUE ## [1] FALSEAcima verificamos que a classe do objeto quantidade é de tipo numeric. Seria possível converter de um tipo para outro? # convertendo quantidade quantidade <- as.character(quantidade) quantidade ## [1] "10" ## [1] TRUESim! Veja que agora o valor 10 aparece entre aspas, pois o objeto quantidade foi convertido para a classe character. E se um vetor possuir números e caracteres em diferentes elementos, como o R interpreta a classe desse vetor? # vetor com números e strings brasileiros <- c(10, "Campeonatos Brasileiros vencidos pelo Palmeiras.") brasileiros ## [1] "10" ## [2] "Campeonatos Brasileiros vencidos pelo Palmeiras." ## [1] "character"Perceba que o vetor é declarado com o número 10 no primeiro elemento e uma string no segundo elemento. Contudo, o R adota um critério de coerção de dados para que o vetor seja da classe character. Por isso, o número 10 é automaticamente convertido como caracter. O R segue duas regras básicas de coerção de tipos de dados:
No R matrizes são estruturas de dados que suportam apenas um tipo de classe de dados. Logo, assim como no caso do vetor visto anteriormente, ao constatar a presenção de alguma entrada de classe character automaticamente todos os elementos da matriz são convertidos. # Matrizes ---- m <- rbind(c(1:5), letters[1:5]) m ## [,1] [,2] [,3] [,4] [,5] ## [1,] "1" "2" "3" "4" "5" ## [2,] "a" "b" "c" "d" "e" ## [1] "matrix" "array"
data.frames são as estruturas de dados mais utilizadas no R. Sua versatilidade permite ter no mesmo objeto dados de classes diferentes num formato de matriz (matriz de dados). Vejamos: # Data Frames ---- df1 <- data.frame(numeros = 1:5, letras = letters[1:5]) str(df1) ## 'data.frame': 5 obs. of 2 variables: ## $ numeros: int 1 2 3 4 5 ## $ letras : chr "a" "b" "c" "d" ...Como padrão da função data.frame() strings são transformadas em fatores. Para manter strings como caracteres deve-se usar o argumento: stringsAsFactors = FALSE. df1 <- data.frame(numeros = 1:5, letras = letters[1:5], stringsAsFactors = FALSE) str(df1) ## 'data.frame': 5 obs. of 2 variables: ## $ numeros: int 1 2 3 4 5 ## $ letras : chr "a" "b" "c" "d" ...
Das estruturas de objetos mais populares no R, listas são as mais complexas. Sua grande vantagem em relação às demais estruturas é permitir uma organização hierárquica dos dados independente de sua classe e tamanho. Vejamos um exemplo: # Listas ---- # listas contemplam qualquer tipo de estrutura de dados ls <- list(1:10, letters[1:5], rnorm(5), m) ls ## [[1]] ## [1] 1 2 3 4 5 6 7 8 9 10 ## ## [[2]] ## [1] "a" "b" "c" "d" "e" ## ## [[3]] ## [1] -1.07932202 -0.92801368 -0.10882420 0.11014096 -0.08281621 ## ## [[4]] ## [,1] [,2] [,3] [,4] [,5] ## [1,] "1" "2" "3" "4" "5" ## [2,] "a" "b" "c" "d" "e"No exemplo acima, o objeto ls é composto por quatro elementos que contêm, cada um, diferentes tamanhos e diferentes estruturas de dados.
A função nchar() é um forma ágil e fácil de se obter o número de caracteres de uma string ou de strings de um vetor. nchar(c("Quantos", "caracteres?")) ## [1] 7 11nchar("Quantos caracteres?") ## [1] 19No exemplo acima, perceba que a função contabiliza o espaço entre palavras como caracter. Por isso, a soma do total de caracteres do primeiro caso (\(7 + 11 = 18\) caracteres) não é igual ao total de caracteres do segundo (\(19\) caracteres).
Sendo o R case sensitive, para o processamento do texto como dado, pode ser de interesse do pesquisador harmonizar o conteúdo sob análise com o objetivo de ter todos os caracteres em formato maiúsculo ou minúsculo. As funções toupper() e tolower() desempenham bem esse papel. tolower(c("TUdo eM MinúsCuLA", "ABCDE")) ## [1] "tudo em minúscula" "abcde"toupper(c("TUdo eM mAiúsCula", "ABCDE")) ## [1] "TUDO EM MAIÚSCULA" "ABCDE"
Operações com vetores de forma geral podem ser aplicadas a vetores com strings. Podemos, por exemplo, unir diferentes vetores. # União vec1 <- c("algumas", "palavras", "aleatória", "aqui") vec2 <- c("e", "algumas", "palavras", "ali") union(vec1, vec2) ## [1] "algumas" "palavras" "aleatória" "aqui" "e" "ali"Verificar a intersecção entre dois vetores. # Intersecção intersect(vec1, vec2) ## [1] "algumas" "palavras"Verificar a diferença entre dois vetores. # Diferença setdiff(vec1, vec2) ## [1] "aleatória" "aqui"E a igualdade de elementos entre dois vetores. No caso, entre o vetor vec1 e ele mesmo. # Igualdade identical(vec1, vec1) ## [1] TRUE
Outra operação básica de interesse é a verificação se um elemento (no caso, uma sequência de caracteres) está contido num objeto. Vamos verificar abaixo se a sequência “aqui” está contida no vetor vec1 através do operador %in%. # Elemento contido em ---- elem <- "aqui" elem %in% vec1 ## [1] TRUEÉ importante destacar que o exemplo acima é uma operação básica de vetores no R e não exclusiva para sequência de caracteres. Nesse sentido, ela apenas checa se no vetor vec1 há algum elemento idêntico ao padrão “aqui.” Mais adiante verificaremos como identificar a presença de sequências de caracteres no interior de outras strings sem que tenham de ser idênticas.
É possível ordenar um vetor de strings em ordem alfabética ou em sentido oposto como no exemplo abaixo. Tal versatilidade pode ser útil para o ordenamento de uma matriz de dados completa com base numa variável de nomes, por exemplo. # Ordenando ---- sort(vec1, decreasing = TRUE) ## [1] "palavras" "aqui" "algumas" "aleatória"
O pacote stringr integra uma coleção de pacotes projetados para a ciência de dados, o tidyverse. Combinado ao pacote stringi, você terá acesso a praticamente todas as possíveis funções necessárias para o processamento de strings em mais alto nível. Existem quatro famílias principais de funções no stringr:
Além do que veremos neste material, é altamente recomendável a consulta ao capítulo sobre strings do R for Data Science. # carregando pacote ---- library(stringr)
str_length("O Palmeiras é o time da virada, o Palmeiras é o time do amor.") ## [1] 61
# vetor de strings txt <- c("O Palmeiras é o time da virada", "o Palmeiras é o time do amor.") Selecionando o terceiro caracter. # identificando terceira letra str_sub(txt, 3, 3) ## [1] "P" "P"Selecionando do segundo caracter de trás pra frente. ## [1] " Palmeiras é o time da virad" " Palmeiras é o time do amor"
str_sub(txt, 3, 11) <- "PALMEIRAS" txt ## [1] "O PALMEIRAS é o time da virada" "o PALMEIRAS é o time do amor."Preencher uma string em tamanho fixo. str_pad(txt, 50) # por padrão: left ## [1] " O PALMEIRAS é o time da virada" ## [2] " o PALMEIRAS é o time do amor."Remove espaço extra. txt <- str_pad(txt, 50) # por padrão: left str_trim(txt) ## [1] "O PALMEIRAS é o time da virada" "o PALMEIRAS é o time do amor."
str_sub(txt, start = 3, end = 11) ## [1] " " " "É possível fazer o recorte usando índices de trás pra frente. str_sub(txt, start = -14, end = -1) ## [1] "time da virada" " time do amor."Extração de palavras. ## [1] "" "" ## [1] "virada" "amor."
Até aqui você viu funções básicas e intermediárias para o processamento de sequências de caracteres no R. Para avançar, é necessário aprender o uso de expressões regulares ( Regular Expressions ). Como definido no livro Handling Strings with R, uma expressão regular é um conjunto de símbolos que descreve um padrão de texto. Mais formalmente, uma expressão regular é um padrão que descreve um conjunto de cadeias de caracteres. Como o termo “expressão regular” é bastante longo, a maioria das pessoas usa a palavra regex para se referir à área. Entre outras tarefas, o uso de expressões regulares pode ajudá-lo a (Wickham and Grolemund 2017):
No entanto, é preciso ter atenção, pois o uso de expressões regulares pode se tornar uma tarefa realmente complexa. Veja esta discussão do StackOverflow a respeito de seu uso para identificação de endereços de e-mail, por exemplo. Dada a complexidade que a área pode assumir, vamos verificar o uso das regex em algumas funções do pacote stringr com base nesse tutorial. Junto a ele, é recomendável que a leitura atenta do ?regex no R.
Por padrão, expressões regulares buscam por correspondência em qualquer parte de uma sequência de caracteres. Porém, é extremamente útil poder ancorar a busca pela correspondência no início ou no final de uma string. Podemos usar:
txt <- c("O Palmeiras é o time da virada", "o Palmeiras é o time do amor.") str_detect(txt, "^O") ## [1] TRUE FALSE ## [1] FALSE TRUE
Wickham, Hadley, and Garrett Grolemund. 2017. R for Data Science: Import, Tidy, Transform, Visualize, and Model Data. 1 edition. Sebastopol, CA: O’Reilly Media. Page 2
Davi Moreira, Mônica Rocabado Uma das tarefas mais importantes para a análise de conteúdo consiste na sua própria busca e aquisição. O R nos ajuda nessa tarefa a partir de distintas estratégias. A seguir apresento aquelas nas quais o uso de técnicas computacionais e programação potencializa o alcance e escala de acervos a serem utilizados para pesquisas.
Caso tenha realizado surveys com perguntas abertas ou possua conteúdo de texto organizado em formato de documento ou tabulado é possível utilizar o R para analisá-las. Para arquivos em formato excel pode-se usar o pacote readxl com a função read_excel, funciona de forma similar ao exemplo em de leitura de arquivos em .csv. Para leitura de arquivos .txt, .csv, entre outros, recomenda-se utilizar o pacote readr. Vejamos um exemplo:
A Câmara dos Vereadores de São Paulo publica dados de sua atividade no portal SPLegis. Entre as informações disponíveis, é possível obter relatórios dos projetos com o conteúdo de todas as ementas através do download de um arquivo em formato .xlsx! library(readxl) arquivo_excel <- read_excel("Emendas Apresentadas.xlsx")
Antes de achar que os dados de um arquivo .pdf ou .doc são um obstáculo para a abordagem do texto como dado, diferentes estratégias podem ser adotadas para sua obtenção e processamento. Com o uso do pacote textreadr, por exemplo, de forma simples pode-se transformar o conteúdo do arquivo .pdf num arquivo .txt. Vejamos o conteúdo do discurso de posse do ex-presidente Luis Inácio Lula da Silva realizado em 01 de janeiro de 2003 no Congresso Nacional: library(textreadr) # lendo arquivo .pdf txt <- read_document("https://raw.githubusercontent.com/davi-moreira/txt4cs/master/data/lula-pronunciamento-posse-cd-2003.pdf") # salvando como .txt writeLines(txt, "lula-pronunciamento-posse-cd-2003.txt")
O Webscraping consiste na possibilidade de uso de programação para raspagem de dados da web, ou seja a obtenção de conteúdo presente na web. Nesse sentido, suponha que ao invés de montar uma equipe que irá acessar páginas na web para coletar seu conteúdo, você desenvolverá um programa específico para realizar essa tarefa com foco sobre os objetivos de sua pesquisa.
Há diversos pacotes para raspagem de dados com o R. Abaixo segue um lista com os principais. Para referências sobre seu uso, consulte os links indicados, este tutorial sobre o rvest e este capitulo sobre web scraping. Como o site Curso-R destaca, esses pacotes não são suficientes para acessar todo tipo de conteúdo da web. Páginas com conteúdo produzido na linguagem javascript, por exemplo, precisam de outras ferramentas para acesso a seu conteúdo. Nesses casos, é necessário “simular” um navegador que acessa a página web e realiza consultas. Uma das melhores ferramentas para isso é o selenium, abaixo indicado.
O processo de raspagem dos dados consiste nas seguintes etapas:
Toda página na internet possui um código-fonte - muitas vezes em html - que indica e cria o conteúdo de forma visual para página. Ao clicar na página desejada com o botão direito do mouse e selecionar “código fonte” ou digitar CRTL + U, pode-se visualizá-lo. Vejamos um exemplo:
Ao acessar o código fonte dessa página do portal da Câmara dos Deputados, você consegue visualizar o código .html que produz toda visualização, incluindo o conteúdo do discurso proferido pelo então deputado Jair Bolsonaro em 17 de Agosto de 20105.
Se é possível visualizar o conteúdo, é possível obtê-lo de forma automatizada. Vamos, portanto, obter o conteúdo do discurso proferido pelo então deputado Jair Bolsonaro em 17 de Agosto de 20106. Vamos utilizar as Etapas anteriormente apresentadas: De uma só vez, conseguimos cumprir as Etapas 1 e 2 com o código abaixo: Etapa 1: Conhecer detalhadamente o caminho para acesso aos dados: no nosso exemplo, o código fonte do endereço virtual que apresenta o discurso também nos apresenta o conteúdo publicado. Etapa 2: Armazenar todos os caminhos de acesso aos dados de forma amigável ao programa: nesse exemplo, trata-se de apenas um endereço que armazenamos no objeto link. # carregando pacotes ---- library(tidyverse) library(rvest) library(httr) library(xml2) # definindo o endereço da web link <- "https://www.camara.leg.br/internet/SitaqWeb/TextoHTML.asp?etapa=5&nuSessao=174.4.53.O&nuQuarto=56&nuOrador=2&nuInsercao=0&dtHorarioQuarto=11:46&sgFaseSessao=BC&Data=17/08/2010&txApelido=JAIR%20BOLSONARO,%20PP-RJ&txFaseSessao=Breves%20Comunica%C3%A7%C3%B5es&txTipoSessao=Extraordin%C3%A1ria%20-%20CD&dtHoraQuarto=11:46&txEtapa=" Etapa 3: Obter os dados: Podemos facilmente obter o código fonte de um endereço na internet com o uso da função readLines. Aplicamos, portanto, a função no objeto link e atribuímos seu resultado ao objeto conteudo. # obtem o codigo fonte conteudo <- readLines(link) Veja que o objeto conteudo é um vetor cujos elementos são cada uma das linhas presentes no código fonte da página. Isso significa que não precisamos mais do acesso à internet ou do próprio endereço para processar o conteúdo obtido uma vez que já está retido no seu ambiente de trabalho no R. Nessa etapa, pode ser conveniente salvar o objeto conteudo em seu formato bruto para posterior tratamento. Etapa 4: Processar os dados obtidos: Para finalizar nossa tarefa, uma rápida análise do objeto conteudo (código fonte da página que publicou o discurso), nos mostra que o elemento 328 do vetor apresenta o conteúdo de interesse. Veja que, nesse caso, a análise do próprio código fonte da página da Câmara dos Deputados apresenta o conteúdo do discurso na linha 328 do código html. Para processar os dados obtidos, vamos selecionar apenas o elemento 328 e assim concluímos nossa missão. conteudo <- conteudo[328]
Os Web services são utilizados para disponibilizar serviços interativos na Web, podendo ser acessados por outras aplicações. O objetivo dos Web Services é a comunicação de aplicações através da Internet. Um dos motivos que tornam os Web Services atrativos para a obtenção de dados e conteúdo é o fato deste serviço ser desenvolvido com base em tecnologias standards, em particular XML e HTTP (Hypertext Transfer Protocol).
A Câmara dos Deputados do Brasil possui um excelente serviço de transparência. Estimulado pela iniciativa do Laboratório Hacker, foi desenvolvido o Web service da Câmara dos Deputados. Após a realização da Primeira Maratona Hacker da Câmara dos Deputados em 2013, quando fora desenvolvido o Projeto Retórica Parlamentar, o Web Service da CD passou a disponibilizar os discursos proferidos pelos deputados federais em plenário.
Os ataques terroistas de 11 de setembro de 2001 chocaram o mundo e os deputados federais brasileiros não ficaram em silêncio diante de fato tão relevante. Nossa tarefa será a de obter os dados e o conteúdo dos discursos proferidos nesse dia terrível. Em virtude da estrutura de disponibilização dos dados no WebService da Câmara dos Deputados, nossa tarefa será dividida em duas subtarefas:
Importante ressaltar que essa etapa só é possível tendo realizado o método acima de ListarDiscursosPlenário, devido exigência do próprio WebService. bdDados <- bd bd <- data.frame() # data frame que recebera os pronunciamentos for( i in 1:dim(bdDados)[1]){ link <- paste("https://www.camara.leg.br/SitCamaraWS/SessoesReunioes.asmx/obterInteiroTeorDiscursosPlenario?", "codSessao=", bdDados$codigoSessao[i], "&numOrador=", bdDados$numOrador[i], "&numQuarto=", bdDados$numQuarto[i], "&numInsercao=", bdDados$numInsercao[i], sep = "") print(link) response <- GET(link) data <- xmlParse(response, encoding = "UTF-8") ls <- xmlToList(data) bdTemp <- data.frame(nome = ls$nome, partido = ls$partido, uf = ls$uf, horaInicioDiscurso = ls$horaInicioDiscurso, discursoRTFBase64 = ls$discursoRTFBase64) bd <- rbind(bd, bdTemp) Sys.sleep(.5) } Obtivemos os discursos em seu inteiro teor! No entanto, os dados estão em formato RTF codificado em Base64. Precisamos transformá-los em formato de texto plano para possibilitar sua leitura. Para tal vamos utilizar o pacote txt4cs que possui a função decode_rtfPTBRA. devtools::install_github("davi-moreira/txt4cs-pkg", force = TRUE) library(txt4cs) bd$discursoRTFBase64 <- as.character(bd$discursoRTFBase64) bd$discursoPlainTxt <- vector(mode = "character", length = dim(bd)[1]) for (i in 1:dim(bd)[1]){ bd$discursoPlainTxt[i] <- decode_rtfPTBRA(bd$discursoRTFBase64[i]) # print( bd$discursoPlainTxt) }
Utilizando o código acima, obtenha as falas proferidas do dia da autorização do processo de impeachement da Presidenta Dilma Vana Rousseff na Câmara dos Deputados, ocorrido em 17 de abril de 2016. Salve os dados em formato .rda.
Além do conteúdo diretamente publicado numa página web, pode ser de interesse fazer o download de arquivos disponíveis. Em especial, no caso brasileiro, muitos órgãos públicos publicam relatórios em formato .pdf. O obstáculo proporcionado pelo formato do arquivo e o modo como o conteúdo é disponibilizado pode ser superado com o uso da linguagem R. Como exemplo, vamos conferir o caso do Tribunal de Contas do Estado de Pernambuco, que anualmente disponibiliza relatórios de gestão.
Para atingir nosso objetivo, vamos utilizar as Etapas anteriormente apresentadas: Etapa 1: Conhecer detalhadamente o caminho para acesso aos dados: A página do TCE-PE apresenta os relatórios publicados anualmente. library(rvest) library(XML) library(xml2) link_tce <- "https://www.tce.pe.gov.br/internet/index.php/relatorios-de-gestao-fiscal-2" Etapa 2: Armazenar todos os caminhos de acesso aos dados de forma amigável ao programa: Aqui selecionamos exatamente os endereços de download de cada um dos arquivos publicados. Todos os .pdf são um link dentro do código fonte da página, iremos, portanto, obter os links desses relatórios. Para tanto, podemos observar que o código fonte possui um padrão que identifica os Relatórios de Desempenho anual: rdg. Iremos utilizá-lo para obter somente os documentos que possuam esse padrão. link_relatorios <- link_tce %>% read_html %>% # função que irá ler o código fonte escrito em html html_nodes("a") %>% # nó presente no código fonte antes do pdf html_attr("href") # atributro do nó # obtenção dos links do relatório através do padrão `rgd` link_relatorios <- link_relatorios[grep("rdg", link_relatorios)] Etapa 3: Obter os dados: A obtenção dos dados se refere justamente ao download do material para armazenamento local. Logo, definimos o diretório onde os arquivos serão salvos e fazemos uso da função download.file. Como obtivemos mais de um link, para mais de um relatório, no código abaixo apresentamos como obter o primeiro arquivo .pdf presente em link_relatorios[1]. download.file(link_relatorios[1], destfile = "seu_diretorio/nome_do_arquivo.pdf", mode = "wb") Etapa 4: Processar os dados obtidos: O processo é similar à leitura de conteúdo em arquivo .pdf explicada na seção ??. # pacotes library(textreadr) library(here) # # lendo arquivo .pdf rdg2018 <- read_document("nome_arquivo.pdf") # salvando como .txt writeLines(rdg2018, "meu_arquivo.txt")
Existem diversos pacotes que possibilitam a captura de informação do Twitter, sendo possível obter tweets ou timelines somente de usuários públicos, ou seja que não possuem um perfil privado. Como exemplo, vamos utilizar o pacote rtweet para obter dados dos tweets da timeline da Deputada Federal Tabata Amaral. Vale ressaltar que o Twitter exige que para obtenção dos dados você possua uma conta no Twitter e autorize o app rstats2twitter no popup que surgirá no seu browser ao utilizar alguma das funções no console do R, isso ocorrerá somente na primeira vez de uso, criando um token que será salvo para próximas sessões. library(rtweet) tabata_timeline <- get_timeline( user = "tabataamaralsp", n = 30) Além do uso de obtenção de hashtags é possível buscar termos que estão sendo tweetados ou hashtags através do search_tweets que segue lógica similar ao get_timeline.
No caso de textos em imagem é possível utilizar o optical character recognition (OCR). OCR é o processo de encontrar e reconhecer texto dentro de imagens, por exemplo, de uma captura de tela, texto digitalizado, etc. A imagem abaixo tem um texto de exemplo:
Com o pacote Tesseract e o uso da Interface de Programação de Aplicativos (API) do Google é possível capturar seu conteúdo, podendo ser uma imagem presente no seu computador ou da web. Para realizá-lo em português é necessário instalar o acervo de treinamento em português com o seguinte comando tesseract_download('por'). No caso, vamos utilizar a imagem do Tweet Fake atribuído ao Presidente Bolsonaro para obter seu conteúdo:
library(tesseract) # obtendo treinamento na ligua portuguesa tesseract_download('por') por <- tesseract("por") # alocando treinamento na ligua portuguesa # obtendo texto da imagem text <- tesseract::ocr("https://raw.githubusercontent.com/davi-moreira/txt4cs/master/data/tweet_bolsonaro_cp.png", engine = por) # resultado cat(text) Segue o resultado: ## Jair M. Bolsonaro & 8 a Qjairbolsonaro Vie . S O que é ciência política? 09:26 - 06/03/2019 - Twitter for iPhone
Nos últimos anos, diversas aplicações comerciais passaram a ofertar a possibilidade de transcrição de áudios e vídeos. Acompanhando esse processo, iniciativas foram implementadas na liguagem R, dentre elas o pacote googleLanguageR, da Google e o pacote aws.transcribe, da Amazon. De forma geral, esses pacotes utilizam APIs dessas grandes companhias de tecnologia com alguma limitação para uso gratuito, sendo necessário cadastro em sua plataforma e credenciamento de cartão de crédito. Para se ter um exemplo, vamos explorar o uso do pacote googleLanguageR. install.packages("googleLanguageR") Para obter uma chave de API que vai possibilitar o uso do pacote você precisa criar um conta no Google cloud e habilitá-la com o seu cartão de crédito. Vale ressaltar que é uma assinatura gratuita, mas caso ultrapasse o limite da cota gratuita você será cobrado. Após criar sua conta há uma diversidade de APIs que podem ser ativadas. O pacote googleLanguageR foi pensando para três funcionalidades principais:
Nosso foco aqui é na transcrição de áudio para texto8. Para usar a API você precisa estar logado na sua conta, entrar no console, acessar a página do Google Cloud Speech API e habilitá-la. Com a API já habilitada, é necessário criar uma credencial do serviço. Para isso, na página da API do Google Speech já habilitada vá na opção “credenciais.” em seguida em “criar credenciais.” Após concluída essa etapa, é necessário criar uma “chave de conta de serviço” e baixar o arquivo JSON. O arquivo JSON contém a chave que permite o uso da aplicação. Para prosseguir é necessário carregá-lo com a função gl_auth, conforme abaixo: library(googleLanguageR) gl_auth("arquivo.json") No nosso exemplo, vamos utilizar o áudio da propaganda de campanha do então candidato a Deputado Federal pelo PR, o candidato Tiririca.
Antes do uso da função para transcrição, recomenda-se realizar a etapa abaixo para unir os canais do som em um formato mono através do pacote tuneR e salvar a leitura do áudio em formato .csv. library(tuneR) # Baixe o som: audio <- "https://raw.githubusercontent.com/davi-moreira/txt4cs/master/data/tiririca_campanha.wav" #Leitura do som: audio_load <- readWave("audio") audio_load <- mono(audio_load, which = c("left", "right", "both")) writeWave(audio_load, "tiririca_campanha.csv", extensible = F) Em seguida, selecione o arquivo que deseja transcrever. Lembre-se que a cota gratuita é de 60 minutos por mês e o formato de áudio padrão é wav. A transcrição do áudio é realizada com a função gl_speech, identificando o código da língua e a frequência em Hertz9. audio_load <- "https://raw.githubusercontent.com/davi-moreira/txt4cs/master/data/tiririca_campanha.csv" transcricao <- gl_speech(audio_load, sampleRateHertz = 44100L, languageCode = "pt-BR") O função gl_speech retorna uma lista com dois elementos: transcript e timings. O primeiro com a transcrição e o segunda mostrando as palavras por segundo em seu início e fim. Abaixo vemos o resultado da transcrição: transcricao$transcript$transcript ## [1] "Adivinha quem está falando do vídeo você já te enviar Joel o Tiririca candidato a deputado federal Não esquece peguei vocês enganei vocês vocês pessoal fosse outra pessoa sou eu agastado 2222"Page 3
Davi Moreira, Mônica Rocabado A análise de conteúdo só é possível através da transformação do texto bruto em estruturas de dados convenientes para análise. Esta etapa é fundamental e deve ser feita com cuidado para evitar erros. Temos basicamente três estruturas num texto:
A análise do conteúdo do texto como dado exige versatilidade na transformação entre estruturas. Os pacotes que veremos permitem essa versatilidade. Para este capítulo utilizaremos especialmente os pacotes tidytext e quanteda, ambos estruturais para análise de conteúdo. Uma comparação entre os pacotes pode ser encontrada aqui. # carregando pacotes ---- library(tidytext) library(quanteda) library(tidyverse)
Usar os princípios do tidytext é uma maneira poderosa de tornar o processamento de dados mais ágil e eficaz. Conforme Wickham (2014), os dados organizados têm uma estrutura específica:
Assim, o formato de “texto arrumado” segue a mesma estrutura apresentada, na qual cada linha/observação possui uma unidade de texto significativa, também chamada por token, estes organizadas em uma coluna/variável. O token, portanto, é uma unidade de texto significativa, podendo ser uma única palavra, um conjunto de palavras, uma frase ou um parágrafo. Para obte-lo se deve realizar o processo de tokenização, em que se divide o texto em tokens, como veremos asseguir.
Vejamos um trecho de “Canção do Exílio”, poesia romântica de Gonçalves Dias escrita em 1843. No caso, estamos criando um vetor. text <- c("Minha terra tem palmeiras", "Onde canta o Sabiá", "As aves, que aqui, gorjeiam", "Não gorjeiam como lá") Para transformar o vetor de strings em formato tidytext, precisamos criar um data.frame, através da função tibble. Abaixo, estamos declarando o nome das colunas e o valor contido nelas, por exemplo text é a coluna que contém cada observação do vetor text que criamos anteriormente.
Um objeto tibble é uma classe moderna de data.frames dentro do R, disponível nos pacotes dplyr e tibble, que possui um método de impressão conveniente, não converte strings em fatores e não usa nomes de linhas. Tibbles são ótimos para uso com funções, pacotes e ferramentas tidy. Contudo, nosso objeto tibble ainda não está coerente com a definição de tidytext que apresentamos. Temos que converter nosso objeto em outro que atenda a condição one-token-per-document-per-row. Logo, cada token unigram (cada palavra) deve ser um valor indicado por verso. A função unnest_tokens presente no pacote tidytext realiza este processo de tokenização. Abaixo, a coluna “word” irá conter uma palavra por linha através da coluna text que contém nosso texto. text_token <- text_df %>% unnest_tokens(word, text)
Vamos utilizar como exemplo prático os discursos proferidos pelos deputados federais na sessão de Impeachment da Presidenta Dilma Rousseff em Abril de 2016. Os discursos proferidos podem ser carregados através do pacote txt4cs que acompanha esse livro. No objeto impeachment_dilma, cada linha apresenta um discurso, podendo haver mais de um discurso por deputado. # carregando pacotes ---- library(dplyr) library(stringr) library(tidytext) library(ggplot2) library(forcats) devtools::install_github("davi-moreira/txt4cs-pkg", force = T) library(txt4cs) library(here) impeachment_dilma <- txt4cs::impeachment_dilma
Agora iremos tokenizar os discursos no formato tidy. A função unnest_tokens permite a tokenização, deve-se indicar dois argumentos principais para seu funcionamento: i) o nome da coluna que será criada, no exemplo vamos criar a coluna word; ii) e indicar em qual coluna se deseja realizar a tokenização, no caso vamos usar a variável text. O padrão da função é a divisão do texto por uma palavra, ou unigram, sendo possível sua alteração caso se queira um tamanho maior de n-grams. tidy_impeachment <- impeachment_dilma %>% unnest_tokens(word, text) Vejamos os 100 primeiros registros da base em formato tidy:
Como é possível notar, nossa nova base apresenta uma série de informações/registros que talvez não sejam úteis. Temos artigos, preposições e outros elementos dos discursos proferidos que não agregram valor a depender da análise. Na abordagem do texto como dado, estes elementos são chamados de stopwords. Podemos remover esses elementos de nossa base de dados utilizando o pacote quanteda e a lista de stopwords criada pelo projeto Snowball. Para remover as stopwords vamos utilizar a função anti_join. #stopwords library(quanteda) stop_w <- tibble(word = stopwords(source = "stopwords-iso", language = "pt")) #retirar do corpus as stopwords tidy_impeachment <- tidy_impeachment %>% anti_join(stop_w) Além das stopwords, por padrão, ao disponibilizar os discursos, o Departamento de Taquigrafia da Câmara dos Deputados (DETAQ) inclui as legendas partidárias no corpo do texto proferido. Também podemos optar por remover estes tokens com o uso da função anti_join. # obtendo siglas dos partidos partido <- impeachment_dilma$partido %>% tibble() %>% distinct() %>% rename("word" = ".") %>% mutate(word = str_to_lower(word)) #retirar do corpus as stopwords tidy_impeachment <- tidy_impeachment %>% anti_join(partido) Vejamos novamente os 100 primeiros registros da base em formato tidy:
Com a base no formato tidy podemos iniciar algumas análises como, por exemplo, a contagem da frequência de palavras ou tokens: tidy_impeachment %>% count(word, sort = TRUE)
Também pode-se visualizar as frequências calculadas: tidy_impeachment %>% count(word, sort = TRUE) %>% mutate(word = fct_reorder(word, n)) %>% slice(1:20) %>% ggplot(aes(word, n)) + geom_col() + coord_flip() + labs(x="") Com a base de dados em formato tidy, podemos utilizar os meta-dados dos documentos em nossas análises. É possível, por exemplo, comparar o uso de palavras por diferentes autores. Utilizando a votação de abertura do processo de impeachment da ex-presidenta Dilma Rousseff, vamos comparar o uso de palavras por deputados do PT, PSOL e do PSDB através dos seguintes passos:
impeachment_tk <- impeachment_dilma %>% # 1. Separar os `tokens` dos deputados federais de cada partido escolhido filter(partido %in% c("PT", "PSDB", "PSOL")) %>% # 2. Transformar o banco de dados em formato `tidy` tibble() %>% unnest_tokens(word, text) %>% anti_join(stop_w) %>% # 3. Calcular a frequência e proporção dos termos utilizados por partido mutate(word = str_extract(word, "[a-z']+")) %>% count(partido, word) %>% group_by(partido) %>% mutate(proportion = n / sum(n)) %>% select(-n) %>% # 4. Adotando o PT como referência, organizar a base de dados pivot_wider(names_from = partido, values_from = proportion) %>% pivot_longer(cols = `PSOL`:`PSDB`, names_to = "partido", values_to = "proportion") # 5. Desenvolver nosso gráfico impeachment_tk %>% ggplot(aes(x = proportion, y = `PT`, color = abs(`PT` - proportion))) + geom_abline(color = "gray40", lty = 2) + geom_jitter(alpha = 0.1, size = 2.5, width = 0.3, height = 0.3) + geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5) + scale_x_log10(labels = scales::percent_format()) + scale_y_log10(labels = scales::percent_format()) + scale_color_gradient(limits = c(0, 0.001), low = "darkslategray4", high = "gray7") + facet_wrap(~partido, ncol = 2) + theme(legend.position="none", axis.text.x=element_blank(), axis.ticks.x=element_blank(), axis.text.y=element_blank(), axis.ticks.y=element_blank()) + labs(y = "PT", x = NULL) Com o PT como como referência no eixo Y e facet pelos partidos PSDB e PSOL no eixo X, quanto mais próximo um termo do eixo Y, mais ele foi relativamente utilizado pelos deputados do PT e quanto mais próximo ao eixo X, mais ele foi utilizado pelos deputados do PSDB ou PSOL. Podemos quantificar quão diferente é o vocabulário através de um simples teste de correlação. cor.test(data = impeachment_tk[impeachment_tk$partido == "PSDB",], ~ proportion + `PT`)
O pacote quanteda permite trabalhar com Tokens, Corpus e DFMs através do seguinte fluxograma: É possível criar um corpus a partir de diferentes fontes de dados:
Utilizando a base de dados com as falas proferidas no dia da aprovação do impeachment da Presidenta Dilma Rousseff pela Câmara dos Deputados, nesta seção focaremos em como construir um corpus a partir de uma matriz de dados. A base de dados é a mesma utiliada no item anterior, podendo ser carregada através do pacote txt4cs # carregando pacotes ---- library(quanteda) library(readtext) library(stringr) library(txt4cs) impeachment_dilma <- txt4cs::impeachment_dilma Inicialmente devemos transformar o data.frame em um corpus, utilizando a função corpus()do pacote quanteda corp <- corpus(impeachment_dilma) summary(corp, 5)
A função corpus_reshape() permite alterar a unidade de texto entre documentos, parágrafos e frases. Os textos podem ser restaurados para a unidade original mesmo que o corpus seja modificado por outras funções. No exemplo abaixo podemos mudar o corpus para formato de sentença. Segundo a função corpus_reshape do pacote quanteda, uma sentença termina no ponto final. corp_sent <- corpus_reshape(corp, to = 'sentences') summary(corp_sent, 5) corp_sent[11:12] A qualquer momento, podemos voltar para a estrutura original ao nível do documento: # restaurando documentos originais ---- corp_documents <- corpus_reshape(corp_sent, to = 'documents') summary(corp_documents, 5) corp_documents[2]
A função tokens(), como já vimos, segmenta o texto em unidades de textos significativas. Esta função no pacote quanteda remove espaços em branco e demais separadores de texto automaticamente. Para tanto, é necessário escrever o argumento como exemplificado abaixo na removação automática de pontuação^(Consulte a documentação da função para mais detalhes: ?tokens). Usaremos o corpus gerado do item anterior para nossos exemplos. toks <- tokens(corp, remove_punct = TRUE) #tokenizando e removendo a pontuação head(toks[[1]], 10)
Através da função kwic() do pacote quanteda é possível identificar o contexto imediato de um termo ou conjunto de palavras-chaves. No exemplo abaixo estamos buscando o contexto de palavras relacionadas a "golp*": kw <- kwic(toks, pattern = 'golp*') Vejamos os 30 primeiros registros do resultado:
Também é possível buscar mais de um termo por linha de código, assim como selecionar a quantidade de caracteres que apareçam com o termo procurado. kw <- kwic(toks, pattern = c("pela", "pelo"), window = 7) Vejamos os 30 primeiros registros do resultado:
Também é possível encontrar o contexto no qual frases ocorrem. Para tanto, você deve apresentar a sentença dentro da função phrase(): # caso de uma frase inteira ---- kw_frase <- kwic(toks, pattern = phrase('não vai ter golp*')) Vejamos os primeiros registros do resultado:
Apresentamos que a função tokens no pacote quanteda apenas remove separadores de texto e outros caracteres não textuais automaticamente se expressados no código. Para retirada de stopwords pode se fazer uso das funções tokens_select e tokens_remove, que são equivalentes.
# Tokens sem pontuações e sem números toks <- tokens(corp, remove_punct = T, remove_numbers = T)
# Removendo as stopwords toks_nostop <- tokens_select(toks, pattern = stopwords('pt'), selection = 'remove')
# Removendo palavras específicas toks_nosel <- tokens_remove(toks, pattern = c('SR', 'PRESIDENTE', 'Esclarecimentos'))
Até aqui, adotamos diferentes procedimentos que permitem “limpar” o conteúdo sob análise e, consequentemente, diminuir a dimensionalidade (número de tokens) do acervo. Contudo, como apontado por (Izumi and Moreira 2018) podemos supor que no nosso corpus existam as seguintes palavras únicas: “trabalho,” “trabalhador,” “trabalhista.” Ao fazer a contagem de frequência, encontraríamos cada uma delas repetida apenas uma vez. Conhecendo as palavras do acervo, o pesquisador poderia se perguntar se não há a possibilidade de contabilizar tokens relacionados ao radical trabalh de forma única, totalizando uma frequência igual a 3. Diante do desafio proposto, apesar de seus diferentes signficados, cada uma dessas palavras pode ser reduzida ao seu radical, trabalh. Essa solução dá ao pequisador informações suficientes para sua análise e reduz o n de três palavras únicas para uma palavra que tem a soma das frequências anteriores. Portanto, para garantir que palavras que variam apenas na flexão, número ou conjugação sejam consideradas iguais, reduzindo o número de dimensões contido no acervo, Por meio da adaptação do algoritmo de XXX Porter (1980) XXX para o português já desenvolvida por diferentes projetos (Snowball e NILC-USP), é possível garantir que palavras que variam apenas na flexão, número ou conjugação sejam consideradas iguais, reduzindo o número de dimensões contido no acervo. Por esse processo são obtidos os stems das palavras11. Chamamos esse procedimento de Stemming. Como é possível perceber, ao realizar este procedimento estamos transformando nosso acervo em uma sacola de palavras (bag of words), numa estrutura na qual a ordem de suas ocorrências já não tem grande importância. O pacote quanteda possui a opção de stemming em português que usa o projeto Snowball através da função tokens_wordstem(). No exemplo abaixo vamos utilizar o objeto toks já produzido anteriormente. token_stem <- tokens_wordstem(toks,language = "pt")
O método de tokenização apresentado nos itens anteriores não respeita a ordem em que as palavras foram escritas. No entanto, para algumas análises essa ordem é importa. Para garantir que a ordem de palavras esteja presente no processo de tokenização, podemos criar tokens com N-grams. Assim, geramos tokens de qualquer tamanho e respeitando a ordem em que aparecem no texto. Através da função tokens_ngrams() é possível aplicar esse método. Com tokens_ngram() você pode definir o tamanho que deseja que seus tokens sejam criados, tanto especificando um n-gram único, ou estabelecendo um limite de tamanho de n-grams que o próprio R irá identificar no texto. No exemplo abaixo vamos usar o objeto corp criado anteriormente. # criando tokens toks <- tokens(corp, remove_punct = T, remove_numbers = T) # criando n-grams toks_ngram <- tokens_ngrams(toks, n = 2:4)
Além de definir n-grams, você pode ter o interesse de garantir que alguns termos, nomes ou sequências de palavras sejam considerados como uni-grams. Para esse objetivo, a função tokens_compound() pode ser utilizada para gerar n-grams de forma seletiva, especificando o termo de análise desejada. # Gerar n-grams específicas toks_neg_bigram <- tokens_compound(toks, pattern = phrase('não *')) # Selecionar as n-grams geradas toks_neg_bigram_select <- tokens_select(toks_neg_bigram, pattern = phrase('não_*'))
Como ainda veremos nesse livro, parcela relevante dos modelos e abordagens de análise do texto como dado se valem da possibilidade de redução de dimensionalidade e da transformação do corpus numa sacola de palavras (bag of words). Mesmo diante da perda de informação e nuances do conteúdo em análise, os modelos que veremos nos próximos capítulos apresentam resultados sólidos e substantivos que potencializam a capacidade humana de lidar e interpretar um volume de documentos em escala antes inimaginável. De forma geral, para avançar com métodos que utilizarão a sacola de palavras (bag of words), precisamos estruturar nosso corpus em uma DFM (Document Feature Matrix) ou DTM (Document Term Matrix). Uma DFM é uma vetorização do texto, em que cada linha representa um documento (ou a unidade de agregação do texto que você definir) e cada coluna um termo.No pacote quanteda, a função dfm() permite construir uma matriz de documentos e termos/palavras/stems/tokens/features (DFM) a partir de um objeto de tokens. # Construindo um corpus, obtendo tokens e processando dados toks <- tokens(corp, remove_punct = TRUE, remove_numbers = T) toks <- tokens_select(toks, pattern = stopwords('pt'), selection = 'remove') toks <- tokens_remove(toks, pattern = c('sr', 'presidente', 'esclarecimentos', 'quero', 'ser', 'srs', 'é', 'aqui')) #Construindo um DFM dfmat <- dfm(toks)
Você pode obter a quantidade de documentos e termos utilizando ndoc e nfeat Abaixo vemos que temos rndoc(dfmat)` documentos. ndoc(dfmat) # numero de documentos Observamos que temos 6387 features. nfeat(dfmat) # numero de features É possível obter o nome dos documentos e das features com docnames e featnames. docnames(dfmat) # nomes dos documentos featnames(dfmat) # nomes das features topfeatures(dfmat, 10) # features mais frequentes Também é fácil consultar quais as features mais frequentes com a função topfeatures(). topfeatures(dfmat, 10) # features mais frequentes
Você pode selecionar as features que tiveram uma determinada frequência desejada para manter na DFM através da função dfm_trim(). No exemplo abaixo, features com menos de 1% de frequência nos documentos do corpus serão removidas. Perceba que este procedimento reduz bastante o número de features da DFM, de 6387 features ficamos com 762 features. dfmat_docfreq <- dfm_trim(dfmat, min_docfreq = 0.01, docfreq_type = "prop") nfeat(dfmat_docfreq)
É possível utilizar os meta-dados do corpus para agrupar os documentos em uma DFM através da função dfm_group(). Fazendo isso, ela irá somar os valores referentes a cada linha agrupando seu resultado de acordo com o argumento groups indicado na função. Vamos ver alguns exemplos a seguir:
# Por autor dfmat_autor <- dfm_group(dfmat_docfreq, groups = nomeOrador) ndoc(dfmat_autor)
# Por partido ---- dfmat_party <- dfm_group(dfmat_docfreq, groups = partido) ndoc(dfmat_party)
Também é possível utilizar os meta-dados do corpus selecionar linhas específicas da DFM. No exemplo abaixo, usamos a função dfm_subset() e o operador %in% para selecionar apenas as linhas referentes ao Partido dos Trabalhadores (PT). Como resultado, temos uma DFM com 1 linhas. # selecionando partidos dfmat_party_pt <- dfm_subset(dfmat_party, subset = partido %in% c("PT")) ndoc(dfmat_party_pt)
Vimos anteriormente que é possível reduzir a dimensionalidade do acervo através do processo de Stemming após “tokenizar” o nosso corpus. Tal procedimento, no entanto, pode ser desenvolvido diretamente na DFM potencializando nossa sacola de palavras (bag of words) Diante de um documento tenha em sua composição as seguintes palavras únicas: “trabalho,” “trabalhador,” “trabalhista.” Podemos reduzí-las a seu radical: trabalh. Assim, reduzindo o n de três palavras únicas para um stem que tem a soma das frequências anteriores. O pacote quanteda possui a opção de stemming da DFM em português que usa o projeto Snowball através da função dfm_wordstem(). Vamos aplicar o stemming para produzir uma nova dfm. Contudo, vamos aproveitar o exemplo abaixo para agregar mais valor ao processamentos dos dados e combinar as diferentes possbilidades que vimos até aqui. Have fun! impeachment_dfm <- impeachment_dilma %>% mutate(text = stri_trans_general(text, "Latin-ASCII")) %>% mutate(text = str_remove_all(text, "[[:digit:]]")) %>% corpus(docid_field = "doc_id", text_field = "text") %>% tokens(remove_punct = TRUE) %>% tokens_tolower() %>% tokens_remove(stopwords(source = "stopwords-iso", language = "pt"), min_nchar = 2) %>% tokens_wordstem(language = "pt") %>% dfm() %>% dfm_select(pattern = c("sr", "total", "deput", "vot", "president", "bet", "mansur", "palm"), selection = "remove") %>% dfm_trim(min_docfreq = 0.01, docfreq_type = "prop") impeachment_dfm ndoc(impeachment_dfm) # numero de documentos nfeat(impeachment_dfm) # numero de features head(docnames(impeachment_dfm), 20) # ids dos documentos head(featnames(impeachment_dfm), 20) # algumas features topfeatures(impeachment_dfm, 10) # features mais frequentes
Nossa nova dfm possui 555 documentos (linhas) e 682 features (colunas). As 10 features mais frequentes são: # features mais frequentes topfeatures(impeachment_dfm, 10)
Uma FCM (Feature Co-occurrence Matrix) é uma matriz de co-ocorrência de termos. Ou seja, apresenta a co-ocorrencia de features dentro de um contexto definido previamente, retornando linhas e colunas com os termos e os valores da contagem de co-ocorrencia entre os termos. O contexto pode ser um documento ou um conjunto de documentos. Pode-se construir uma FCM a partir de uma DFM ou um objeto de tokens usando a função fcm(). Por meio de um FCM é possível retornar as palavras que ocorrem mais frequentemente com topfeatures(), assim como visualizar uma rede semântica com a função textplot_network(), por exemplo. feat <- names(topfeatures(fcmat, 50)) fcmat_select <- fcm_select(fcmat, pattern = feat, selection = "keep") Estabelecemos o tamanho da rede selecionando o DFM segundo as topfeatures criadas no objeto acima. Para plotar o gráfico das principais features utilizamos textplot_network, estabelecendo uma frequência mínima. library(quanteda.textplots) size <- log(colSums(dfm_select(dfmat, feat, selection = "keep"))) set.seed(144) textplot_network(fcmat_select, min_freq = 0.95, vertex_size = size / max(size) * 3)
Izumi, Mauricio Yoshida, and D. C. Moreira. 2018. “O Texto Como Dado: Desafios e Oportunidades Para as Ciências Sociais.” REVISTA BRASILEIRA DE INFORMAÇÃO BIBLIOGRÁFICA EM CIÊNCIAS SOCIAIS - BIB 2 (86): 138–74. Page 4
Davi Moreira, Mônica Rocabado \(~\) Já vimos como abordar algumas análises básicas. Vamos agora consolidar o conteúdo.
No dia 29 de maio de 2021 a população brasileira realizou o maior protesto registrado no período da pandemia da Covid-19. A data marcada como 29M é simbólica por representar a repulsa de grande parcela da população ao modo como o então governo do Presidente Jair Bolsonaro lidava com a pandemia. Os protestos ganharam o mundo e receberam em diferentes partes do globo. Vamos iniciar este capítulo com a análise de 1000 tweets publicados com o termo “Bolsonaro” no dia 29 de maio de 2021. Os dados do Twitter foram obtidos através do pacote rtweet conforme apresentado no Capítulo 5. Para reproduzir essa análise você pode utilizar a base de dados presente no pacote txt4cs. A especificação do type indica que queremos tweets recentes e populares em nossa análise. library(rtweet) tweets <- search_tweets("Bolsonaro", n = 1000, type = "mixed", include_rts = FALSE) Utilizando o pacote quanteda, vamos verificar a frequência de hashtags usando select = "#*". Para tal, devemos processar nossos dados conforme indicado no Capítulo 6: transformar a base em um corpus, depois em tokens, remover pontuações, e criar em uma DFM selecionando apenasas hashtags. library(quanteda) library(quanteda.textstats) #transformando em um corpus corp_tweets <- corpus(tweets) #transformando em tokens toks_tweets <- tokens(corp_tweets, remove_punct = TRUE) #criando uma DFM com as hashtags dfmat_tweets <- dfm(toks_tweets, select = "#*") Com a dfmat_tweets iremos obter a frequência das features através da função textstat_frequency do pacote quanteda. No exemplo abaixo estamos especificando as 20 topfeatures conforme o agrupamento por idioma (lang) do tweet. Lembrando que para verificar as variáveis disponíveis no objeto DFM você pode utilizar a função docvars. Com este resultado estaremos obtendo as 20 principais hashtags e o o número de documentos no qual cada feature ocorre (docfreq). No caso, cada tweet é um documento, sendo, portanto, 1000 documentos. tstat_freq <- textstat_frequency(dfmat_tweets, n = 20, groups = lang)
Com a base de dados processada, podemos visualizar as hashtags mais frequentes. dfmat_tweets %>% textstat_frequency(n = 20) %>% ggplot(aes(x = reorder(feature, frequency), y = frequency)) + geom_point() + coord_flip() + labs(x = NULL, y = "Frequência") + theme_minimal()
Uma forma comum da visualização de frequência na análise de texto é a nuvem de palavras. Para isso vamos aplicar nossa DFM na função textplot_wordcloud() do pacote quanteda library(quanteda.textplots) set.seed(132) textplot_wordcloud(dfmat_tweets, max_words = 100) Podemos aprimorar a visualização da nuvem de palavras para comparar grupos. Primeiro, criamos uma nova variável ao nível do documento que atribui faixas de influência através do total de seguidores que o usuário possui. Para isso, criamos a nova variável influencia dentro do corp_tweets e estabelecemos uma condição do que consideramos influente. Tudo o que não corresponder ao critério será considerado “não influente,” no caso estamos utilizando como parâmetro perfis com mais de 1000 seguidores. docvars(corp_tweets, "influencia") <- factor( case_when(docvars(corp_tweets, "followers_count") > 1000 ~ "Influente", TRUE ~ "Não Influente")) Com isto feito, agrupamos a DFM de hashtags: # criando DFM com as hashtags dfmat_tweets <- dfm(corp_tweets, select = "#*") # agrupando DFM dfmat_corp_language <- dfm_group(dfmat_tweets, groups = influencia) Criamos a worcloud, possibilitando a comparação entre grupos indicando TRUE no argumento comparison. library(quanteda.textplots) set.seed(132) textplot_wordcloud(dfmat_corp_language, comparison = TRUE, max_words = 200)
Usando a dfm obtida com as falas proferidas no dia do impeachment da Presidenta Dilma, repita o exemplo acima comparando as falas dos deputados do PT, do PCdoB, do PSB e do PSOL com as dos demais partidos. A base esta presente no pacote txt4cs.
Uma questão central na mineração de texto e processamento de linguagem natural é: como quantificar o assunto de um documento? As possibilidades de respostas são diversas. Entre elas, está a possibilidade de utilizar a análise da frequência das palavras que compõem o documento. Logo, uma primeira medida seria a frequência de um termo (tf) em um documento. Contudo, há palavras que ocorrem muitas vezes talvez não sejam importantes. Podemos removê-las antes da análise, mas é possível que algumas dessas palavras sejam mais importantes em alguns documentos do que em outros. Por isso, uma alternativa seria examinar a frequência de inversa de um termo no documento (idf), o que diminui o peso de palavras comumente usadas e aumenta o peso de palavras que não são muito usadas em uma coleção de documentos. Combinando as duas alternativas, calcula-se o tf-idf de um termo. Em outras palavras: a frequência de um termo ajustada pela frequência com que é usado no acervo. tf-idf: A estatística tf-idf destina-se a medir a importância de uma palavra para um documento em uma coleção de documentos (corpus), por exemplo, para um romance em uma coleção de romances, para um site em uma coleção de sites, um discurso numa coleção de discursos, etc. O método resulta na frequência das palavras mais “relevantes” entre documentos. Quanto mais perto de 1, mais relevante é a palavra. Vamos ver um exemplo usando os votos proferidos na aprovação do impeachment da ex-presidenta Dilma Rousseff e presente no pacote txt4cs. Através do pacote tidytext, vamos transformar a base em formato token. Em seguida, vamos contar a quantidade de termos utilizados por partido e selecionar apenas PT, PSDB, PSOL e PMDB. O processo de contagem de termos é essencial para função tf-idf. library(tidytext) library(tidyverse) impeachment_words <- impeachment_dilma %>% unnest_tokens(word, text) %>% count(partido, word, sort = TRUE) %>% filter(partido %in% c("PT", "PSDB", "PSOL", "PMDB")) Obtemos o valor do tf-idf através da função bind_tf_idf do pacote tidytext. Note que é relevante definir a base de comparação dos documentos, no caso, partido. Dessa forma iremos obter as palavras mais relevantes para cada partido. plot_impeachment <- impeachment_words %>% bind_tf_idf(word, partido, n) %>% arrange(desc(tf_idf)) Para produzir uma visualização, transformamos os termos e partidos que estão em chr em fator (fct). plot_impeachment <- plot_impeachment %>% mutate(word = factor(word, levels = rev(unique(word)))) %>% mutate(partido = factor(partido, levels = unique(plot_impeachment$partido))) Com os dados tratados, podemos visualizar graficamente quais os 10 termos mais relevantes e utilizados por cada partido nos textos selecionados: plot_impeachment %>% group_by(partido) %>% top_n(10, tf_idf) %>% ungroup() %>% mutate(word = reorder(word, tf_idf)) %>% ggplot(aes(word, tf_idf, fill = partido)) + geom_col(show.legend = FALSE) + labs(x = NULL, y = "tf-idf") + facet_wrap(~partido, ncol = 2, scales = "free") + coord_flip()
Repita a análise tf-idf retirando os nomes dos parlamentares do conteúdo do voto proferido.
Interessados em visualizar as relações entre as palavras simultaneamente, através de n-grams podemos organizar as palavras em uma rede. O pacote igraph possui muitas funções poderosas para processar e analisar redes. Uma maneira de criar um objeto igraph a partir de um data.frame é a função graph_from_data_frame(). No exemplo iremos utilizar os votos proferidos na aprovação do processo de impeachment da ex-presidenta Dilma Rousseff (impeachment_dilma) disponível no pacote txt4cs que acompanha esse livro. library(igraph) library(ggraph) Iremos transformar essa base de dados com o pacote tidytext em n-grams de dois termos. impeachment_bigrams <- impeachment_dilma %>% unnest_tokens(bigram, text, token = "ngrams", n = 2) Como visto no capítulo anterior, para trabalhar com n-grams no tidytext temos que separar as palavras para podermos filtrar as stop_words, utilizaremos a base stop_words presente no pacote tidytext. bigrams_separated <- impeachment_bigrams %>% separate(bigram, c("word1", "word2"), sep = " ") stop_w <- tibble(word = stopwords(source = "stopwords-iso", language = "pt")) bigrams_filtered <- bigrams_separated %>% filter(!word1 %in% stop_w$word) %>% filter(!word2 %in% stop_w$word) Precisamos então contar quantas vezes os termos aparecem juntos para realizar nossa rede de termos. bigram_counts <- bigrams_filtered %>% count(word1, word2, sort = TRUE) Escolhemos os termos que tenham uma frequência conjunta maior que 30 vezes, e através da função graph_from_data_frame transformamos a data.frame em formato igraph library(igraph) bigram_graph <- bigram_counts %>% filter(n > 30) %>% graph_from_data_frame() Estabelecemos uma ordem aleatória e geramos nossa rede de relações. Para isso vamos precisar do pacote ggraph. library(ggraph) set.seed(2017) ggraph(bigram_graph, layout = "fr") + geom_edge_link() + geom_node_point() + geom_node_text(aes(label = name), vjust = 1, hjust = 1)
Utilize o corpus da aprovação do impeachment da Presidenta Dilma Rousseff e aplique a análise de redes de bigramas. A rede para PT e PSDB é diferente?
Podemos querer examinar a correlação entre palavras e verificar com que frequência elas aparecem juntas em relação à frequência com que aparecem separadas. Para tanto, vamos analisar o coeficiente \(\phi\), uma medida comum para correlação binária. O foco do coeficiente \(\phi\) é o quão mais provável é a palavra \(X\) e a palavra \(Y\) aparecem juntas em relação a aparecerem separadas. Sua interpretação é similar à correlação linear de Pearson em que 1 significa uma correlação linear perfeitamente positiva e -1 uma correlação linear perfeitamente negativa, caso 0 não há correlação. A função pairwise_cor() do pacote widyr nos permite encontrar o coeficiente \(\phi\) entre as palavras com base na frequência com que aparecem. Vamos utilizar o corpus com os votos proferidos no processo de abertura do impeachment contra a presidenta Dilma Rousseff. Seguindo nosso fluxo de trabalho, inicialmente vamos utilizar o pacote tidytext para preparar nossos dados para análise. library(tidytext) library(tidyverse) # separando stop words e palavras selecionadas para posterior remoção stop_w <- tibble(word = stopwords(source = "stopwords-iso", language = "pt")) sel_w <- tibble(word = c("sr", "deputado", "v.exa", "deputada")) # criando objeto com tokens impeachment_words <- impeachment_dilma %>% mutate(text = str_remove_all(text, "[[:digit:]]")) %>% unnest_tokens(word, text) %>% anti_join(stop_w) %>% anti_join(sel_w) Agrupamos as palavras e filtramos aquelas que possuem uma frequência maior ou igual a 20. Ao final computamos a correlação pareada com a função pairwise_cor(). library(widyr) word_cors <- impeachment_words %>% group_by(word) %>% filter(n() >= 20) %>% pairwise_cor(word, partido, sort = TRUE) Podemos visualizar a correlação pareada, focando em palavras de maior interesse, por exemplo: “deus” e “família.” Para tanto, basta filtrar a primeira coluna e assim avaliarmos as principais correlações pareadas existentes. word_cors %>% filter(item1 %in% c("deus", "família")) %>% group_by(item1) %>% top_n(10) %>% ungroup() %>% mutate(item2 = reorder(item2, correlation)) %>% ggplot(aes(item2, correlation)) + geom_bar(stat = "identity") + facet_wrap(~ item1, scales = "free") + coord_flip()
Utilize o corpus da aprovação do impeachment da Presidenta Dilma Rousseff e aplique a análise vista apenas para o PT e o PSDB.
A diversidade lexical pode ser obtida através de várias medidas com base no número de tokens exclusivos e no comprimento de um documento. Pode ser utilizada para analisar as habilidades lingüísticas de oradores ou autores, ou a complexidade das ideias expressas. Com a função textstat_lexdiv() do pacote quanteda, a mensuração padrão é a Type-Token Ratio (TTR), mas pode-se optar por outros métodos de cálculo. Essa medida divide o número de “tipos” pelo número total de tokens. Logo, quanto mais próximo a 1, maior a complexidade lexical. Vamos utilizar o corpus com os votos proferidos no processo de abertura do impeachment contra a presidenta Dilma Rousseff. Seguindo nosso fluxo de trabalho, devemos preparar nossa base para obter uma DFM. Com o objetivo de comparar votos com tamanho similar, vamos contar o número de palavras de cada um e manter na DFM somente aqueles incluídos no intervalo to segundo e terceiro quartís. library(stringi) library(stringr) library(quanteda) library(quanteda.textstats) impeachment_dfm <- impeachment_dilma %>% mutate(text = stri_trans_general(text, "Latin-ASCII")) %>% mutate(text = str_remove_all(text, "[[:digit:]]")) %>% corpus(docid_field = "doc_id", text_field = "text") %>% tokens(remove_punct = TRUE) %>% tokens_tolower() %>% tokens_remove(stopwords(source = "stopwords-iso", language = "pt"), min_nchar = 2) %>% tokens_wordstem(language = "pt") %>% dfm() Com a DFM pronta, podemos calcular a diversidade lexical através da função tstat_lexdiv(). Em seguida, vamos incluir o resultado da mensuração TTR em nossa base de dados original. tstat_lexdiv <- textstat_lexdiv(impeachment_dfm) impeachment_dilma_lexdiv_sel <- impeachment_dilma %>% mutate(doc_id = as.character(doc_id)) %>% left_join(tstat_lexdiv, by = c("doc_id" = "document")) %>% tibble() %>% arrange(desc(TTR)) Pronto! Vamos conhecer uma amostra aleatória de cinco discursos com maior diversidade lexical:
Agora, vamos conhecer os cinco discursos com menor diversidade lexical:
Com simples processamento dos dados, podemos comparar a diversidade lexical (TTR) de deputados selecionados: impeachment_dilma_lexdiv_sel %>% filter(nomeOrador %in% c("PR. MARCO FELICIANO", "PAULO TEIXEIRA", "ONYX LORENZONI", "JAIR BOLSONARO", "JEAN WYLLYS ", "TIRIRICA", "GLAUBER BRAGA")) %>% arrange(TTR) %>% mutate(nomeOrador = factor(nomeOrador, levels = nomeOrador)) %>% ggplot(aes(nomeOrador, TTR)) + geom_bar(stat = "identity", width = .7) + theme_bw() + coord_flip() + theme(axis.title.x = element_blank(), axis.title.y = element_blank())
Na sua área de pesquisa/trabalho, em quais aspectos pode ser relevante a complexidade de um acervo? Pense, por exemplo, no caso de campanhas políticas, discursos presidenciais ou votos de ministros do STF. A complexidade/diversidade do vocabulário utilizado importa?
Calcule a diversidade lexical dos votos proferidos, compute a mediana por partido e apresente uma visualização que os compare.
A função textstat_dist() calcula semelhanças e distâncias entre documentos ou features por diversas medidas. Conjutamente com a função, iremos utilizar a função as.dist() que retorna uma matriz de distância. Afim de obter um cluster hierárquico de análise, iremos também utilizar a função hclust(). Vamos utilizar o corpus com os votos proferidos no processo de abertura do impeachment contra a presidenta Dilma Rousseff. Seguindo nosso fluxo de trabalho, devemos preparar nossa base para obter uma DFM. impeachment_dfm_group <- impeachment_dilma %>% mutate(text = stri_trans_general(text, "Latin-ASCII")) %>% mutate(text = str_remove_all(text, "[[:digit:]]")) %>% corpus(docid_field = "doc_id", text_field = "text") %>% tokens(remove_punct = TRUE) %>% tokens_tolower() %>% tokens_remove(stopwords(source = "stopwords-iso", language = "pt"), min_nchar = 2) %>% tokens_wordstem(language = "pt") %>% dfm() %>% dfm_select(pattern = c("sr", "total", "deput", "vot", "president", "bet", "mansur", "palm"), selection = "remove") %>% dfm_trim(min_docfreq = 0.01, docfreq_type = "prop") %>% dfm_group(partido) tstat_dist <- as.dist(textstat_dist(impeachment_dfm_group)) clust <- hclust(tstat_dist) plot(clust, xlab = "Distance", ylab = NULL)
Segundo o implementador original da metodologia (WordSmith), Keyness é uma pontuação de associação para identificar palavras frequentes em documentos em um grupo de referência e de destino. O algorítmo identifica palavras-chave em uma base, comparando padrões de freqüência. Uma palavra é dita “chave” se:
Keyness positivo e negativo: Uma palavra que é positivamente chave ocorre mais frequentemente do que seria esperado por acaso em comparação com o corpus de referência. Uma palavra que é negativamente chave ocorre com menos frequência do que seria esperado por acaso em comparação com o corpus de referência. Usando a função textstat_keyness(), você pode comparar frequências de palavras entre documentos de destino e de referência. Como exemplo, vamos replicar o tutorial do pacote quanteda. Analisaremos um corpus de notícias publicadas pelo jornal The Guardian. Os documentos de destino são artigos de notícias publicados em 2016 e os documentos de referência são os publicados em 2012-2015. Caso não tenha, recomendo instalar o pacote quanteda.corpora para obter os dados para o exemplo através de devtools::install_github("quanteda/quanteda.corpora") library(quanteda) require(quanteda.textstats) require(quanteda.textplots) require(quanteda.corpora) library(lubridate) corp_news <- download("data_corpus_guardian") Após baixar os dados, vamos processá-los para obter uma DFM: toks_news <- tokens(corp_news, remove_punct = TRUE) dfmat_news <- dfm(toks_news, remove = stopwords('en')) Com a DFM, podemos utilizar a função textstat_keyness() que irá verificar a frequência relativa entre o target (documentos de destino) e os documentos de referência. tstat_key <- textstat_keyness(dfmat_news, target = year(docvars(dfmat_news, 'date')) >= 2016) attr(tstat_key, 'documents') <- c('2016', '2012-2015') textplot_keyness(tstat_key) O gráfico acima nos informa que os termos chave em target (2016) são trump, 2016, clinton, sanders, cruz, eu, brexit, em relação aos termos de referência (2012-2015) que são captions, yesterday, aest, clegg, miliband. Percebe-se que os termos chave positivos retratam a eleição presidencial de 2016 nos EUA. Já os termos chave negativos, que ocorrem com menor frequência em 2016 e maior frequência entre 2012 e 2015, se referem às disputas políticas entre Clegg e Miliband no Reino Unido. |