Pré-processamento

Pré-processamento de dados é um conjunto de atividades que envolvem converter dados brutos em dados preparados, ou seja, em formatos úteis e eficientes. É um processo que compreende a preparação, organização e estruturação de dados.

Python
R

A segunda etapa no fluxo de trabalho do Jornalismo de Dados é a preparação dos dados antes da análise. Essa etapa, também conhecida como pré-processamento ou limpeza, é o momento em que os dados coletados são padronizados e formatados de modo que não tenha valores estranhos ou incorretos no conjunto de dados final.

Para isso, devo carregar os pacotes necessários e o arquivo gerado na etapa anterior:

library(tidyverse)
library(lubridate)
library(janitor)
library(stopwords)
library(tm) #removewords
library(tidytext)#unnest tokens
library(DT)
library(kableExtra)
df <- read_rds("dados/02_dados_raspagem_folha.rds") |> 
  clean_names() |> # padronizando as colunas
  glimpse() # Dando uma olhadinha nas colunas
Rows: 31,513
Columns: 9
$ pesquisa       <chr> "afrodescendente", "afrodescendente", "afrodescendente"…
$ x1             <dbl> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1…
$ datas          <chr> "26.nov.2022", "19.nov.2022", "7.nov.2022", "5.nov.2022…
$ horario        <chr> "13h00", "23h15", "13h21", "4h00", "4h00", "17h24", "4h…
$ data_formatada <chr> "2022 /11/26", "2022 /11/19", "2022 /11/7", "2022 /11/5…
$ editoria       <chr> "Folha de S.Paulo - Ilustrada", "Folha de S.Paulo - Mer…
$ titulos        <chr> "Na Flip, Teresa Cárdenas leva ancestralidade africana …
$ texto          <chr> "Este livro presta homenagem a essa herança, àquela for…
$ url            <chr> "https://www1.folha.uol.com.br/ilustrada/2022/11/na-fli…

Substituindo caracteres em colunas do dataframe

Algumas colunas de texto aparecem com quebras de linha. Vou remover essas marcações e forma:

## removendo quebras de linhas
df$titulos <- gsub("\n|\\t|  ", "", df$titulos)
df$editoria <- gsub("\n|\\t|  ", "", df$editoria)
df$texto <- gsub("\n|\\t|  ", "", df$texto)

## Formatando as datas
df$datas <- gsub(".jan.", "/01/", df$datas)
df$datas <- gsub(".fev.", "/02/", df$datas)
df$datas <- gsub(".mar.", "/03/", df$datas)
df$datas <- gsub(".abr.", "/04/", df$datas)
df$datas <- gsub(".mai.", "/05/", df$datas)
df$datas <- gsub(".jun.", "/06/", df$datas)
df$datas <- gsub(".jul.", "/07/", df$datas)
df$datas <- gsub(".ago.", "/08/", df$datas)
df$datas <- gsub(".set.", "/09/", df$datas)
df$datas <- gsub(".out.", "/10/", df$datas)
df$datas <- gsub(".nov.", "/11/", df$datas)
df$datas <- gsub(".dez.", "/12/", df$datas)
df$datas <- dmy(df$datas)


## Excluindo colunas

materias <- df
materias$data_formatada <- NULL
materias$pesquisa <- NULL
materias$x2 <- NULL
materias$horario <- NULL
materias$texto <- NULL
materias$edit <- substr(materias$editoria, 1, 16)


## Criando colunas titulo_limpo e editoria_limpo
materias<- materias |> 
  mutate(titulo_limpo = tolower(titulos)) |> 
  mutate(editoria_limpo = tolower(editoria))

## Removendo a pontuação das colunas titulo_limpo e editoria_limpo
materias$titulo_limpo <- gsub("[[:punct:]]", "", materias$titulo_limpo)
materias$editoria_limpo <- gsub("[[:punct:]]", "", materias$editoria_limpo)

## Removendo duplicatas e filtrando editorias
materias <- materias |> 
  filter(materias$edit == "Folha de S.Paulo")|>
  distinct(editoria, titulo_limpo, .keep_all = TRUE)|> 
  arrange(datas)
write_rds(materias, "dados/03_materias_sem_duplicatas.rds")
rm(df)
materias$titulo_tidy <- materias$titulo_limpo #duplicando a coluna pra manipular depois
materias$ano <- year(materias$datas)


materias <- materias |> 
  filter(editoria_limpo %in% c("folha de spaulo  cotidiano", 
                               "folha de spaulo  mundo",
                               "folha de spaulo  ilustrada", 
                               "folha de spaulo  ilustríssima",
                               "folha de spaulo  ilustrissima", 
                               "folha de spaulo  poder",
                               "folha de spaulo  mercado", 
                               "folha de spaulo  esporte",
                               "folha de spaulo  educação",
                               "folha de spaulo  equilíbrio e saúde",
                               "folha de spaulo  equilíbrio e saúde",
                               "folha de spaulo  equilíbrio e opinião"))

Serão análisados na próxima etapa 9900 itens.

Acrescentando mais palavras no conjunto de stopwords

Stopwords são palavras comuns que são frequentemente usadas em uma linguagem, mas que geralmente não contribuem muito para o significado do texto em que aparecem. Essas palavras incluem artigos, preposições, conjunções e outras palavras comuns, como “o”, “a”, “um”, “de”, “para”, “em”, “com”, “e”, entre outras. Em muitas tarefas de processamento de linguagem natural, como análise de sentimentos, classificação de texto e recuperação de informações, as stopwords são removidas do texto para reduzir o ruído e melhorar a precisão dos resultados.

stopwords_pt <- c(stopwords("pt"),"negro","negra","preto","preta","parda","pardo","negros","negras","pretos","pretas","pardas","pardos","afrodescendente","afrodescendentes","raciais","de","a", "o", "que", "e", "do","da","em", "um", "para", "é", "com", "não", "uma", "os", "no", "se", "na","por", "mais", "as", "dos", "como", "mas", "foi", "ao", "ele", "das", "tem", "à", "seu", "sua", "ou", "ser", "quando", "muito", "há", "nos", "já", "está", "eu", "também", "só", "pelo", "pela", "até", "isso", "ela", "entre", "era", "depois", "sem", "mesmo", "aos", "ter", "seus", "quem", "nas", "me", "esse", "eles", "estão", "você", "tinha", "foram", "essa", "num", "nem", "suas", "meu", "às", "minha", "têm", "numa", "pelos", "elas", "havia", "seja", "qual", "será", "nós", "tenho", "lhe", "deles", "essas", "esses", "pelas", "este", "fosse", "dele", "tu", "te", "vocês", "vos", "lhes", "meus", "minhas","teu", "tua","teus","tuas","nosso", "nossa","nossos","nossas","dela", "delas", "esta", "estes", "estas", "aquele", "aquela", "aqueles", "aquelas", "isto","aquilo","estou","está","estamos","estão","estive","esteve","estivemos","estiveram","estava","estávamos","estavam","estivera","estivéramos","esteja","estejamos","estejam","estivesse","estivéssemos","estivessem","estiver","estivermos","estiverem","hei","há","havemos","hão","houve","houvemos","houveram","houvera","houvéramos","haja","hajamos","hajam","houvesse","houvéssemos","houvessem","houver","houvermos","houverem","houverei","houverá","houveremos","houverão","houveria","houveríamos","houveriam","sou","somos","são","era","éramos","eram","fui","foi","fomos","foram","fora","fôramos","seja","sejamos","sejam","fosse","fôssemos","fossem","for","formos","forem","serei","será","seremos","serão","seria","seríamos","seriam","tenho","tem","temos","tém","tinha","tínhamos","tinham","tive","teve","tivemos","tiveram","tivera","tivéramos","tenha","tenhamos","tenham","tivesse","tivéssemos","tivessem","tiver","tivermos","tiverem","terei","terá", "teremos","terão", "teria","teríamos","teriam", "diz", "leia", "confira", "bergamo", "folha")

stopwords_final <- unique(stopwords_pt)
rm(stopwords_pt)

Criando os dataframes para análise

titulos_palavras

  • conjunto de dados para a análise de frequência de palavras
tidy_df <- materias %>% 
  mutate(titulo_tidy = titulo_limpo) |> 
  unnest_tokens(palavra, titulo_tidy, strip_numeric=TRUE)

Muitas palavras que não contribuem tanto para o contexto estão entre as mais frequentes porque ainda não foram retiradas as stopwords. O dataframe contando com as stopwords tem 109637 observações.

Removendo as stopwords, o dataframe resultante tem 71702 linhas

tidy_df$palavra <- removeWords(tidy_df$palavra, stopwords_final)
tidy_df <- subset(tidy_df, palavra != "")

head(tidy_df) #visualizando os resultados
# A tibble: 6 × 10
     x1 datas      editoria      titulos url   edit  titulo_limpo editoria_limpo
  <dbl> <date>     <chr>         <chr>   <chr> <chr> <chr>        <chr>         
1  7517 2013-01-02 Folha de S.P… Veread… http… Folh… vereadores … folha de spau…
2  7517 2013-01-02 Folha de S.P… Veread… http… Folh… vereadores … folha de spau…
3  7517 2013-01-02 Folha de S.P… Veread… http… Folh… vereadores … folha de spau…
4  7517 2013-01-02 Folha de S.P… Veread… http… Folh… vereadores … folha de spau…
5  7517 2013-01-02 Folha de S.P… Veread… http… Folh… vereadores … folha de spau…
6  7517 2013-01-02 Folha de S.P… Veread… http… Folh… vereadores … folha de spau…
# ℹ 2 more variables: ano <dbl>, palavra <chr>

Dicionário de Palavras Raciais

Este trecho lê um arquivo CSV chamado “dicionario_termos.csv”, que contém um dicionário de termos relacionados a raça e cor. Esse dicionário será usado para identificar palavras específicas durante a análise.

A definição de termos raciais foi feita da seguinte forma:

  1. Fiz alguns prompts no ChatGPT, como “liste 100 personalidades negras e justifique”, “me dê uma lista de termos relacionados a cor/raça negra”

  2. Em seguida, fiz uma limpeza “manual”: conferi se as justificativas estavam coerentes e acrescentei palavras faltantes.

  3. Depois pedi à ferramenta para classificar os termos em cinco categorias, inspirada no artigo Women in headlines. Aqui está o prompt e resposta: https://chat.openai.com/share/71fa5cb7-1958-4a1f-b8dd-6df933ada5a0

  4. Foi feita uma segunda análise se as classificações estavam coerentes. Corrigi alguns termos.

dicionario <- read_delim("dados/dicionario_termos_limpo.csv")

Verificar a presença de termos nos títulos

O código abaixo lida com a análise de texto e tem como objetivo agrupar palavras de um dicionário em categorias e, em seguida, verificar se essas palavras ou categorias estão presentes em títulos de algum conjunto de dados. Aqui está uma explicação passo a passo do que o código faz:

  1. Agrupar as palavras por categoria:
    • O código começa criando um novo dataframe chamado df_nested.
    • Ele usa o operador pipe (%>%) que é comum em pacotes como o dplyr para encadear operações. O dicionario parece ser um dataframe ou tibble que contém uma coluna chamada “palavra”.
    • O nest(palavra) agrupa as palavras do dicionário em listas aninhadas no dataframe df_nested, de forma que cada lista interna corresponda a uma categoria de palavras.
  2. Converter o dataframe em uma lista de listas:
    • O código cria uma variável chamada lista_palavras que extrai a coluna data do dataframe df_nested. Essa coluna agora contém listas de palavras agrupadas por categoria.
  3. Converter cada lista interna em uma lista:
    • A variável listas_palavras é criada usando a função map para percorrer a variável lista_palavras e converter cada lista interna em uma lista separada.
  4. Verificar a presença das palavras em títulos:
    • O código cria uma nova coluna chamada nova_coluna no dataframe titulos_palavras usando map_lgl. Ele percorre a coluna titulo_limpo do dataframe titulos_palavras e verifica se alguma das palavras ou categorias do dicionário está presente nos títulos. A função str_detect é usada para essa verificação, e o resultado é uma coluna de valores booleanos (TRUE ou FALSE) indicando se alguma palavra do dicionário está presente em cada título.
  5. Extrair palavras identificadas:
    • O código cria uma nova coluna chamada strings_identificadas no dataframe titulos_palavras. Ele percorre a coluna titulo_limpo e usa str_extract_all para extrair todas as palavras ou categorias do dicionário que estão presentes em cada título. O resultado é uma lista de palavras ou categorias para cada título.
  6. Remover palavras irrelevantes:
    • A variável palavra no dataframe titulos_palavras parece ser limpa, removendo palavras irrelevantes (stopwords) usando a função removeWords. As stopwords são frequentemente palavras comuns que podem não ser informativas para a análise de texto, como “a”, “de”, “para”, etc.
  7. Filtrar títulos com correspondências:
    • Finalmente, o código cria um novo dataframe chamado titulos_palavras_true usando o operador pipe (|>) para filtrar apenas as linhas em que a coluna nova_coluna tem o valor TRUE. Isso significa que ele está selecionando apenas os títulos que contêm palavras ou categorias do dicionário.

Portanto, o resultado final, armazenado em titulos_palavras_true, contém os títulos que contêm palavras ou categorias do dicionário, após a limpeza e verificação realizadas no código.

# Agrupar as palavras por categoria
df_nested <- dicionario %>%
  nest(palavra)

# Converter o dataframe em uma lista de listas
lista_palavras <- df_nested %>%
  pull(data)

# Converter cada lista interna em uma lista
listas_palavras <- lista_palavras %>%
  map(as.list)

tidy_df$nova_coluna <- map_lgl(tidy_df$titulo_limpo, function(x) any(str_detect(x, paste(unlist(listas_palavras), collapse = "|"))))
tidy_df$strings_identificadas <- map(tidy_df$titulo_limpo, function(x) unlist(str_extract_all(x, paste(unlist(listas_palavras), collapse = "|"))))

tidy_df$palavra <- removeWords(tidy_df$palavra, stopwords_final)
tidy_df <- subset(tidy_df, palavra != "")

tidy_df_true <- tidy_df |> 
  filter(nova_coluna==TRUE)

tidy_df_false <- tidy_df |> 
  filter(nova_coluna==FALSE)

write_rds(tidy_df_true, "dados/04_titulos_palavras.rds")
write_csv2(tidy_df_true, "dados/04_titulos_palavras.csv")

titulos_bigramas

  • Semelhante ao arquivo anterior, esse será utilizado na análise de bigramas
resultados <- materias %>%
  mutate(tem_termo = sapply(titulo_limpo, function(titulo_limpo) any(grepl(paste(dicionario$palavra, collapse = "|"), titulo_limpo))))

resultados_false <- resultados |> 
  filter(tem_termo==FALSE)

resultados_true <- resultados |> 
  filter(tem_termo==TRUE)

bigramas_com_materias <- resultados_true %>%
  select(datas, editoria_limpo, url, titulos, titulo_tidy) |> 
  unnest_tokens(bigram, titulo_tidy, token = "ngrams", n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% stopwords_final) %>% #uses the combined preset and custom list
  filter(!word2 %in% stopwords_final) |> 
  subset(word1 != "") |>
  subset(word2 != "") |> 
  mutate(bigrama = paste(word1, word2, sep = " "))  

bigramas_com_materias <- unique(bigramas_com_materias)

bigramas <- bigramas_com_materias %>% 
  dplyr::count(word1, word2, sort = TRUE)


write_rds(bigramas_com_materias, "dados/04_titulos_bigramas_com_materias.rds")
write_rds(bigramas, "dados/05_titulos_bigramas.rds")

Reconhecimento de entidades e análise pós-tagging

  • Arquivos criados para o reconhecimento de entidades nomeadas e análise pós-tagging

  • Esta etapa, diferentemente das outras, foi feita com Python e Excel

Os dados gerados foram limpos novamente, no Excel.

dados_ner <- read.csv2("dados/05_materias_pos_tagging_wide2.csv") |> 
  select("X","datas","editoria","titulos","url","edit","titulo_limpo","editoria_limpo","ano","palavra", "classe_gramatical") |> 
  filter(editoria_limpo %in% c("folha de spaulo  cotidiano", 
                               "folha de spaulo  mundo",
                               "folha de spaulo  ilustrada", 
                               "folha de spaulo  ilustríssima",
                               "folha de spaulo  ilustrissima", 
                               "folha de spaulo  poder",
                               "folha de spaulo  mercado", 
                               "folha de spaulo  esporte",
                               "folha de spaulo  educação",
                               "folha de spaulo  opinião",
                               "folha de spaulo  educação")) |>
  clean_names() |> 
  mutate(valor_limpo = tolower(palavra)) |> 
  subset(palavra != "")


dados_classe <- read.csv2("dados/05_materias_pos_tagging_wide2.csv") |> 
  select("X","datas","editoria","titulos","url","edit","titulo_limpo","editoria_limpo","ano","entidade", "tipo_entidade") |> 
  filter(editoria_limpo %in% c("folha de spaulo  cotidiano", 
                               "folha de spaulo  mundo",
                               "folha de spaulo  ilustrada", 
                               "folha de spaulo  ilustríssima",
                               "folha de spaulo  ilustrissima", 
                               "folha de spaulo  poder",
                               "folha de spaulo  mercado", 
                               "folha de spaulo  esporte",
                               "folha de spaulo  educação",
                               "folha de spaulo  opinião",
                               "folha de spaulo  educação")) |> 
  clean_names() |> 
  mutate(valor_limpo = tolower(entidade)) |> 
  subset(entidade != "")

## Backup
#write_csv2(dados_ner, "dados/08_materias_pos_tagging_classe_gramatical.csv")
write_csv2(dados_classe, "dados/08_materias_pos_tagging_entidades.csv")