# --- Importação da Base de Dados de Processos ---
# A função read_xlsx() do pacote 'readxl' é utilizada para ler um arquivo do Excel (.xlsx).
# O primeiro argumento é o caminho para o arquivo "raw_processos.xlsx".
# O argumento 'na = "*NI*"' é crucial: ele instrui o R a tratar todas as células
# que contêm o texto "*NI*" (Não Informado) como valores ausentes (NA - Not Available).
# O resultado, um dataframe (ou tibble), é armazenado no objeto 'raw_processos'.
<- readxl::read_xlsx("DATA/RAW/raw_processos.xlsx", na = "*NI*")
raw_processos
# --- Importação da Base de Dados de Decisões ---
# Aqui, importamos o arquivo de decisões.
<- readxl::read_xlsx("DATA/RAW/raw_decisoes.xlsx",
raw_decisoes # O argumento 'na' é usado novamente para padronizar o tratamento de dados ausentes.
na = "*NI*",
# O argumento 'col_types' permite especificar o tipo de dado para cada coluna,
# o que evita erros de interpretação automática do R.
# "guess": deixa o 'readxl' adivinhar o tipo da coluna.
# "date": força a coluna a ser lida como um tipo de dado de data.
# A ordem no vetor corresponde à ordem das colunas no arquivo Excel.
col_types = c("guess", "date", "guess", "guess", "guess", "guess")
)
# --- Importação da Base de Dados de Legitimados ---
# Importa o terceiro arquivo de dados, referente aos legitimados.
<- readxl::read_xlsx("DATA/RAW/raw_legitimados.xlsx",
raw_legitimados # Novamente, define "*NI*" como o indicador de valor ausente.
na = "*NI*"
)
Apêndice A — Apêndice A - Como os dados foram coletados?
Os dados foram coletados do portal Corte Aberta do STF, painéis estatísticos de controle concentrado que podem ser acessados aqui. Foram baixados três tabelas do portal e armazenados na pasta DATA/RAW/
.
Uma vez baixados os dados, eles foram importados utilizando a função readxl::read_xlsx()
:
Após importar os dados, salvei eles em formato .rds
para leitura mais posterior mais rápida:
# --- Salvando os Dataframes Brutos em Formato RDS ---
# A função saveRDS() salva um único objeto R em um arquivo.
# O formato .rds é um formato binário nativo do R, otimizado para velocidade e fidelidade.
# Aqui, estamos salvando o dataframe 'raw_processos', que foi lido do Excel,
# em um arquivo chamado "raw_processos.rds". Isso permite que, em sessões futuras,
# possamos carregar este objeto diretamente com a função readRDS(), o que é
# significativamente mais rápido do que ler e processar o arquivo .xlsx novamente.
saveRDS(raw_processos, "DATA/RAW/raw_processos.rds")
# O mesmo processo é repetido para o dataframe 'raw_decisoes'.
saveRDS(raw_decisoes, "DATA/RAW/raw_decisoes.rds")
# Finalmente, o dataframe 'raw_legitimados' também é salvo em formato .rds.
saveRDS(raw_legitimados, "DATA/RAW/raw_legitimados.rds")
O segundo conjunto de dados foram obtidos a partir da raspagem de dados no portal do STF. Primeiro, extraiu-se os dados sobre decisões monocráticas e acórdãos, depois, com base na tabela de acórdãos, foram baixados os arquivos em .pdf
dos acórdãos. Para isso, foi utilizado o pacote decJ
.
# --- Extração de Decisões Monocráticas ---
# Utiliza a função 'stf_jurisprudencia'
# para extrair dados da jurisprudência do STF.
<- decJ::stf_jurisprudencia(
raw_monocraticas # Define o filtro para buscar apenas processos da classe "ADPF".
classe = "ADPF",
# Especifica que a busca deve ser feita na base de 'decisoes', que corresponde às decisões monocráticas.
base = 'decisoes',
# Define a quantidade máxima de registros a serem extraídos como 3000.
# A função internamente lidará com a paginação para coletar este volume de dados.
quantidade = 3000
)
# --- Extração de Acórdãos ---
# Realiza uma segunda extração, desta vez focada nos acórdãos (decisões colegiadas).
<- decJ::stf_jurisprudencia(
raw_acordaos # Mantém o filtro para a classe "ADPF".
classe = "ADPF",
# Altera a base da busca para "acordaos".
base = "acordaos",
# Solicita também um máximo de 3000 registros.
quantidade = 3000
)
# --- Salvamento dos Dados Brutos Coletados ---
# Salva o dataframe 'raw_monocraticas', que contém os dados extraídos da API,
# em um arquivo .rds.
saveRDS(raw_monocraticas, "DATA/RAW/raw_monocraticas.rds")
# Salva o dataframe 'raw_acordaos' no mesmo formato .rds.
saveRDS(raw_acordaos, "DATA/RAW/raw_acordaos.rds")
A função decJ::stf_jurisprudencia()
funciona da seguinte forma:
# Definição da função 'stf_jurisprudencia'.
# Esta função foi projetada para buscar dados na API de jurisprudência do STF.
# Parâmetros:
# busca: Termo de busca livre (palavras-chave). Padrão é NULL.
# classe: Classe processual específica (e.g., "ADPF"). Padrão é NULL.
# base: A base de dados a ser consultada ("acordaos" ou "decisoes"). Padrão é "acordaos".
# quantidade: O número total de resultados desejados. Padrão é 25.
= function(busca = NULL, classe = NULL, base = c("acordaos", "decisoes"), quantidade = 25){
stf_jurisprudencia
# Define um cabeçalho (header) para a requisição HTTP, especificando um "User-Agent".
# Isso simula o acesso a partir de um navegador web, uma prática recomendada para
# evitar que a requisição seja bloqueada por sistemas de segurança do servidor.
<- httr::add_headers("User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.51")
header
# Inicia uma estrutura condicional para montar o corpo (body) da requisição
# com base nos parâmetros fornecidos pelo usuário.
if (!is.null(busca) & is.null(classe)) {
# Cenário 1: A busca é feita por palavra-chave ('busca'), mas não por classe.
# Assume-se que 'busca_jurisprudencia' é um objeto (lista) pré-definido que contém a estrutura da consulta.
<- busca_jurisprudencia
body # Insere o termo de busca do usuário na estrutura da consulta.
$query$bool$filter[[1]]$query_string$query <- busca
body# Define a base de dados a ser consultada (acórdãos ou decisões).
$post_filter$bool$must[[1]]$term$base <- base
bodyelse if (is.null(busca) & !is.null(classe)) {
} # Cenário 2: A busca é feita por classe, mas não por palavra-chave.
# Assume-se que 'busca_classe' é um objeto (lista) pré-definido.
<- busca_classe
body # Insere a classe processual na estrutura da consulta.
$query$bool$filter$query_string$query <- classe
body# Define a base de dados a ser consultada.
$post_filter$bool$must$term$base <- base
bodyelse if ((!is.null(busca) & !is.null(classe))) {
} # Cenário 3: O usuário forneceu tanto 'busca' quanto 'classe'.
# A função emite um alerta de erro informando a limitação atual.
::cli_alert_danger("Essa funcao so funciona com busca por palavras chaves OU por classe. Ainda estamos desenvolvendo uma forma de trabalhar com as duas buscas juntas.")
cli# Retorna NULL, interrompendo a execução da função.
return(NULL)
}
# Calcula o número de iterações necessárias para obter a quantidade desejada de resultados.
# A API provavelmente tem um limite de resultados por página (aqui estimado em 250).
# A função ceiling() arredonda para cima, garantindo que todos os resultados sejam coletados.
<- ceiling(quantidade / 250)
num_iteracoes
# Define o tamanho da página ('size') para cada requisição.
if (quantidade > 250) {
# Se a quantidade desejada for maior que 250, o tamanho da página é fixado em 250.
$size <- 250
bodyelse {
} # Caso contrário, o tamanho da página é a própria quantidade desejada.
$size <- quantidade
body
}
# Utiliza a função map_dfr do pacote 'purrr' para iterar e coletar os dados.
# 'map_dfr' aplica uma função a cada elemento de uma sequência (1:num_iteracoes)
# e une os resultados em um único dataframe.
::map_dfr(1:num_iteracoes, purrr::slowly(~{
purrr# A função slowly() é usada para introduzir um atraso entre as requisições,
# uma prática essencial de web scraping para não sobrecarregar o servidor da API.
# Define o ponto de partida ('from') para a paginação. Para a página 'n', começa em (n-1)*250.
$from <- (.x - 1) * 250
body
# Realiza a requisição HTTP do tipo POST para a URL da API do STF.
<- httr::POST(
htmlSTF "https://jurisprudencia.stf.jus.br/api/search/search",
body = body, # O corpo da requisição, contendo a consulta.
encode = "json", # Codifica o corpo da requisição como JSON.
# O cabeçalho definido anteriormente.
header
)
# Extrai o conteúdo da resposta HTTP como texto e o converte de formato JSON para uma lista R.
<- jsonlite::fromJSON(httr::content(htmlSTF, "text"))
getContent
# Navega pela estrutura aninhada da lista para extrair os dados de interesse,
# que estão localizados no campo '_source'.
<- getContent$result$hits$hits$`_source`
dados rate = purrr::rate_delay(5)), # Define o atraso para 5 segundos entre cada chamada à API.
}, # Adiciona uma barra de progresso para fornecer feedback visual ao usuário durante a extração.
.progress = list(format = "Extraindo {cli::pb_bar} {cli::pb_elapsed}"))
}
Estas então foram as formas de coletar os dados brutos. Abaixo podemos observar a estrutura dessas tabelas:
Tabela | Nº Colunas | Nº Linhas | Nº de Dados |
---|---|---|---|
raw_processos | 32 | 8355 | 267360 |
raw_decisoes | 6 | 17734 | 106404 |
raw_legitimados | 3 | 16687 | 50061 |
raw_acordaos | 57 | 632 | 36024 |
raw_monocraticas | 37 | 1571 | 58127 |
Uma vez obtidos os dados brutos, foi preciso fazer uma faxina inicial. Primeiro, limpei o nome das variáveis:
# --- Higienização dos Nomes das Colunas ---
# A função clean_names() do pacote 'janitor' é aplicada ao dataframe 'raw_processos'.
# Esta função converte todos os nomes de colunas para um formato padronizado,
# geralmente "snake_case" (e.g., "Data de Autuação" se torna "data_de_autuacao").
# Isso remove espaços, caracteres especiais, acentos e letras maiúsculas,
# o que previne erros e facilita a referência às colunas no código.
# O novo dataframe com os nomes limpos é armazenado em 'clean_processos'.
<- janitor::clean_names(raw_processos)
clean_processos
# O mesmo processo de limpeza de nomes de colunas é aplicado ao dataframe 'raw_decisoes'.
<- janitor::clean_names(raw_decisoes)
clean_decisoes
# O processo é repetido para o dataframe 'raw_legitimados'.
<- janitor::clean_names(raw_legitimados)
clean_legitimados
# O dataframe 'raw_acordaos', obtido via API, também tem seus nomes de colunas padronizados.
<- janitor::clean_names(raw_acordaos)
clean_acordaos
# Finalmente, o dataframe 'raw_monocraticas' passa pelo mesmo procedimento de higienização.
<- janitor::clean_names(raw_monocraticas) clean_monocraticas
Após a limpeza dos nomes das variáveis de todas as tabelas, passei a limpar tabelas específicas. Começando pela tabela de processos (clean_processos
).
# O fluxo de limpeza e transformação inicia com o dataframe 'clean_processos'.
<- clean_processos |>
clean_processos # 1. Seleção de Variáveis:
# A função select() é utilizada para reter apenas as colunas de interesse para a análise,
# descartando informações irrelevantes e simplificando o dataframe.
::select(
dplyr
processo, link_processo, relator_atual, ramo_do_direito, assunto_relacionado,
data_autuacao, data_transito_julgado, data_baixa, em_tramitacao,
tem_rito_art_12, legislacao|>
)
# 2. Conversão e Padronização de Datas:
# A função mutate() é usada para modificar colunas existentes.
# Aqui, estamos convertendo colunas de texto ou numéricas para o formato de data padrão do R.
::mutate(
dplyr# A função ymd() do pacote 'lubridate' interpreta e converte para o formato Ano-Mês-Dia.
# As datas são primeiro convertidas para o tipo 'Date' para garantir consistência.
data_autuacao = lubridate::ymd(as.Date(data_autuacao)),
data_transito_julgado = lubridate::ymd(as.Date(data_transito_julgado)),
# Para 'data_baixa', especifica-se o formato original ("%d/%m/%Y") para garantir a correta interpretação.
data_baixa = lubridate::ymd(as.Date(data_baixa, format = "%d/%m/%Y"))
|>
)
# 3. Divisão de Colunas (String Splitting):
# A função separate() do pacote 'tidyr' divide o conteúdo de uma coluna em várias.
::separate(
tidyr# A coluna a ser dividida.
processo, into = c("classe", "numero"), # Os nomes das novas colunas.
sep = "\\s" # O separador, aqui definido como qualquer espaço em branco ("\\s").
|>
)
# 4. Normalização de Dados (Wide to Long):
# A função separate_rows() transforma uma célula com múltiplos valores em múltiplas linhas.
::separate_rows(
tidyr# A coluna a ser transformada.
assunto_relacionado, # Cada assunto, que estava separado por um pipe "|", agora ocupará sua própria linha,
# replicando as informações das outras colunas. Isso é fundamental para análises de frequência.
sep = "\\|"
|>
)
# O mesmo processo de normalização é aplicado à coluna 'legislacao'.
::separate_rows(
tidyr
legislacao,# O separador aqui é "\\r", que representa um "retorno de carro" (carriage return),
# um caractere de nova linha comum em alguns sistemas.
sep = "\\r"
)
# 5. Salvamento do Dataframe Processado:
# Salva o dataframe final, limpo e estruturado, em um arquivo .rds.
saveRDS(clean_processos, "DATA/CLEAN/clean_processos.rds")
A segunda tabela que apliquei a faxina foi a tabela que contém as decisões (clean_decisoes
). A tabela criada considera apenas as decisões cujo resultado foi procedente, improcedente, procedente em parte ou prejudicado. Posteriormente, as ações que tiverem “em tramitação” igual a “não” na tabela de processos e não retornarem esses resultados, serão consideradas sem resolução de mérito.
# O fluxo de limpeza e transformação inicia com o dataframe 'clean_decisoes'.
<- clean_decisoes |>
clean_decisoes # Utiliza a função select() do pacote 'dplyr' para remover a coluna 'observacao'.
# O sinal de menos (-) antes do nome da coluna indica que ela deve ser descartada.
::select(-observacao) |>
dplyr
# Utiliza a função mutate() para modificar a coluna 'data'.
# A função ymd() do pacote 'lubridate' é aplicada para converter a coluna para o tipo de dado 'Date',
# garantindo que as datas sejam interpretadas corretamente no formato Ano-Mês-Dia.
::mutate(data = lubridate::ymd(as.Date(data))) |>
dplyr
# Utiliza a função separate() do pacote 'tidyr' para dividir a coluna 'processo'
# em duas novas colunas: "classe" e "numero".
::separate(
tidyr
processo,into = c("classe", "numero"),
# A separação ocorre no primeiro caractere de espaço em branco ("\\s") encontrado.
sep = "\\s"
|>
)
# Utiliza novamente a função mutate() para padronizar o texto da coluna 'descricao'.
# A função str_to_lower() do pacote 'stringr' converte todo o texto para letras minúsculas,
# o que é crucial para garantir a consistência em buscas e filtros textuais.
::mutate(descricao = stringr::str_to_lower(descricao))
dplyr
# Salva o dataframe 'clean_decisoes', agora limpo e estruturado, em um arquivo .rds.
saveRDS(clean_decisoes, "DATA/CLEAN/clean_decisoes.rds")
A teceira tabela envolve os legitimados (clean_legitimados
):
# O fluxo de limpeza e transformação inicia com o dataframe 'clean_legitimados'.
<- clean_legitimados |>
clean_legitimados # Utiliza a função mutate() para modificar colunas.
# A função across() do pacote 'dplyr' é usada para aplicar a mesma operação
# a múltiplas colunas de uma só vez, tornando o código mais conciso.
# Aqui, a função customizada 'decJ::utilitario_remover_acentos' é aplicada
# às colunas "legitimado_polo_ativo" e "legitimado_polo_passivo".
# Este passo é crucial para a padronização textual, pois remove acentos e
# caracteres diacríticos, facilitando buscas, filtros e junções (joins) de dados.
::mutate(dplyr::across(c("legitimado_polo_ativo", "legitimado_polo_passivo"), decJ::utilitario_remover_acentos)) |>
dplyr
# Utiliza a função separate() do pacote 'tidyr' para dividir a coluna 'processo'
# em duas novas colunas: "classe" e "numero".
::separate(
tidyr
processo,into = c("classe", "numero"),
# A separação é feita com base no primeiro caractere de espaço em branco ("\\s") encontrado.
sep = "\\s"
)
# Salva o dataframe 'clean_legitimados', agora limpo e estruturado, em um arquivo .rds.
saveRDS(clean_legitimados, "DATA/CLEAN/clean_legitimados.rds")
A limpeza da tabela com as decisões monocráticas e os acórdãos envolveu, basicamente, selecionar as colunas de interesse.
# --- Limpeza do Dataframe de Decisões Monocráticas ---
# O fluxo inicia com o dataframe 'clean_monocraticas', que contém os dados brutos da API.
<- clean_monocraticas |>
clean_monocraticas # A função select() do pacote 'dplyr' é utilizada para selecionar e renomear colunas em um único passo.
# A sintaxe 'novo_nome = nome_antigo' é usada para renomear a coluna durante a seleção.
::select(
dplyr# Mantém a coluna 'id' com o mesmo nome.
id, uf = procedencia_geografica_uf_sigla, # Seleciona 'procedencia_geografica_uf_sigla' e a renomeia para 'uf'.
numero = processo_numero, # Seleciona 'processo_numero' e a renomeia para 'numero'.
data = julgamento_data, # Seleciona 'julgamento_data' e a renomeia para 'data'.
texto = decisao_texto # Seleciona 'decisao_texto' e a renomeia para 'texto'.
)
# --- Limpeza do Dataframe de Acórdãos ---
# O mesmo processo é aplicado ao dataframe 'clean_acordaos'.
<- clean_acordaos |>
clean_acordaos # Seleciona e renomeia as colunas de interesse para criar um dataframe mais conciso e com nomes padronizados.
::select(
dplyr# Mantém a coluna 'id'.
id, uf = procedencia_geografica_uf_sigla, # Renomeia a coluna de UF.
numero = processo_numero, # Renomeia a coluna do número do processo.
data = julgamento_data, # Renomeia a coluna da data de julgamento.
texto = ementa_texto, # Seleciona o texto da ementa e o renomeia para 'texto'.
url = inteiro_teor_url # Seleciona a URL do inteiro teor e a renomeia para 'url'.
)
# --- Salvamento dos Dataframes Limpos ---
# Salva o dataframe 'clean_monocraticas', agora limpo e organizado, em um arquivo .rds.
saveRDS(clean_monocraticas, "DATA/CLEAN/clean_monocraticas.rds")
# Salva o dataframe 'clean_acordaos' no mesmo formato.
saveRDS(clean_acordaos, "DATA/CLEAN/clean_acordaos.rds")
Serão feitas limpezas específicas dos dados a depender da análise. Essas limpezas estarão descritas no momento da análise no capítulo respectivo.