1 Introdução

A análise de bases de dados muito grandes pode ser desafiadora mesmo quando o computador utilizado é relativamente potente com processador moderno e memória RAM abundante (entre 32GB e 64GB, por exemplo). Como o upgrade contínuo de hardware é inviável e nem todos pesquisadores tem sua a disposição servidores de grande capacidade é necessário entender técnicas para lidar com tais bases.

Uma primeira abordagem seria repartir a base original, lendo apenas parte das variáveis disponíveis e/ou parte das observações e então analisar os dados em lotes. Contudo essa estratégia limita bastante as análises que podem ser feitas, uma vez que comparações entre diferentes subconjuntos ou entre um subconjunto e toda a base não podem ser feitas diretamente (comparações entre estados ou entre um estado e o país, por exemplo). Adicionalmente, a análise em subconjuntos da base eleva o risco da ocorrência de erros e inconsistências.

Esta publicação mostra como ler uma base de dados retangular grande (em formato csv ou txt) em R de forma rápida, como checar eventuais erros de leitura, e como salvá-la em um formato (parquet) no qual as consultas possam ser feitas sem que todos os dados sejam carregados na memória. Isso é especialmente útil para bases que sejam maiores que a memória RAM disponível. Para tanto são utilizados dados públicos, não identificados da Relação Anual de Informações Sociais (RAIS) disponibilizados pelo Ministério do Trabalho e Emprego (MTE) como exemplo.

2 A Relação Anual de Informações Sociais

A Relação Anual de Informações Sociais (RAIS) é um registro administrativo mantido pelo governo brasileiro para subsidiar a gestão das políticas públicas para o mercado de trabalho. A RAIS foi criada pelo decreto 76.900 de 23 de dezembro de 19751, com o objetivo de obter informações de empregados e empregadores do setor formal que participassem do Programa de Integração Social (PIS) - setor privado - ou do Programa de Formação do Patrimônio do Servidor Público (PASEP)2 - setor público. Embora as leis que criaram esses programas continuem vigentes, eles tomaram nova forma após a Constituição Federal de 1988 e a legislação subsequente do Fundo de Amparo ao Trabalhador (FAT). Essas mudanças resultaram na criação do benefício do abono salarial (art. 239, §3º CF) que muitas vezes ainda é referido pela população como “benefício do PIS” ou “benefício do PASEP”. Além do abono salarial, os dados da RAIS auxiliam no acompanhamento de diversas obrigações dos empregadores, como a quota de empregados pessoa com deficiência3 e a quota de aprendizes4.

Anualmente os empregadores enviam ao Ministério do Trabalho um conjunto de informações de cada estabelecimento de suas empresas e dos empregados que atuam nesses estabelecimentos. O envio de informações já foi feito em formulários de papel, disquetes, e pela internet usando um sistema próprio (GDRAIS). Atualmente, as informações da RAIS são obtidas pelo eSocial5, não sendo necessário o envio de outras informações6.

Em geral, no segundo semestre de cada ano é divulgada a RAIS do ano anterior. Esses dados permitem uma avaliação detalhada do comportamento do mercado de trabalho formal naquele ano e, considerando dados de diferentes anos, uma análise da evolução da economia formal como um todo. Além dos resultados agregados, desde 1985 estão disponíveis os microdados da RAIS, no que se convencionou chamar de RAIS estatística7.

A cobertura quase censitária da RAIS8 e a disponibilidade dos dados a tornaram uma fonte essencial para a compreensão da economia brasileira.

Os microdados permitem analises socioeconômicas diversas, a avaliação de teorias e hipóteses, assim como a avaliação de políticas públicas. Em sua ausência essas atividades só poderiam ser realizadas com dados amostrais de pesquisas domiciliares (ou junto as empresas), que nem sempre são representativas no nível de interesse (município ou setor, por exemplo).

Além dos dados públicos, tratados nesta publicação, os dados com a identificação de trabalhadores e empresas podem ser obtidos sob certas condições9 e por instituições específicas10. Tais dados permitem a concatenação das observações em diferentes anos e, assim, a análise longitudinal do mercado de trabalho.

Um dos maiores obstáculos ao uso da RAIS é justamente o tamanho dessa base de dados, devido ao número de observações e variáveis. Nessa publicação utilizaremos os dados da RAIS relativa ao ano de 2021 (RAIS 2021) como exemplo. Esses dados são disponibilizados em sete arquivos txt11, um referente aos estabelecimentos (empresas), com 2,04GB , e seis relativos aos vínculos empregatícios (trabalhadores) que totalizam 32GB 12.

O tamanho dessa base é um problema relevante para o uso de softwares que funcionam com a base toda na memória RAM (Stata e R, por exemplo). Softwares que mantém a base em disco (como o SAS, por exemplo, ou um banco de dados SQL) em geral não tem dificuldades, embora o tempo de processamento possa ser grande. O tamanho da RAIS muitas vezes obriga a restrição do número de variáveis consideradas na análise ou então da cobertura geográfica considerada, considerando apenas alguns estados ou uma grande região (i.e. analisando apenas parte das observações).

2.1 Download dos microdados

Os microdados da RAIS são disponibilizados pelo Ministério do Trabalho e Emprego (MTE) por meio do Programa de Disseminação das Estatísticas do Trabalho (PDET). Esses microdados são acessados por uma conexão file transfer protocol, contudo os navegadores de internet mais populares (Mozilla Firefox e Google Chrome) deixaram de suportar esse protocolo há algumas versões. Há duas alternativas fáceis para contornar esse problema:

  • Utilizando o Windows Explorer: abra o Windows Explorer e cole o endereço ftp://ftp.mtps.gov.br/pdet/microdados/ na barra de endereços, como se fosse um caminho de disco no seu computador. Os arquivos disponíveis no servidor ftp estarão visíveis e você poderá baixá-los como se estivesse copiando os arquivos de uma pasta para outra na sua máquina.

Exemplo de ftp no Windows Explorer \(~\)

  • Utilizando o Filezilla: Você pode instalar o Filezilla, após isso é só acessar a opção Gerenciador de sites... na aba Arquivo. Nessa nova janela é necessário apenas colar o endereço ftp no campo Host:, como na imagem abaixo, e clicar em Conectar.

Exemplo de ftp no Windows Explorer \(~\)

Para o download de poucos arquivos (a RAIS de um ano, por exemplo) a primeira opção é viável e evita a instalação do Filezilla. Para o download de múltiplos arquivos (mais de uma edição da RAIS ou arquivos do CAGED, que são mensais e estão disponíveis no mesmo servidor ftp) o Filezilla é mais adequado por permitir um gerenciamento mais detalhado dos downloads (mostrando os arquivos locais e os do servidor ftp) e permitir parada e retomada downloads.

Para os exercícios seguintes serão utilizados os microdados da RAIS 2021 que devem ter sido baixados e descompactados. No servidor ftp também está disponível um arquivo xls que descreve as variáveis e suas codificações.

Utilizaremos o pacote fs para listar os arquivos que devem ser lidos (.txt) e posteriormente os que forem gravados (.parquet) nos respectivos diretórios.

## Obtém a lista de arquivos txt no diretório
arquivos <- fs::dir_info("E:/Data/RAIS/2021/7z", glob = "*.txt")

print(arquivos[,c(1,3)])
## # A tibble: 7 × 2
##   path                                                       size
##   <fs::path>                                          <fs::bytes>
## 1 E:/Data/RAIS/2021/7z/RAIS_ESTAB_PUB.txt                   2.04G
## 2 E:/Data/RAIS/2021/7z/RAIS_VINC_PUB_CENTRO_OESTE.txt       2.93G
## 3 E:/Data/RAIS/2021/7z/RAIS_VINC_PUB_MG_ES_RJ.txt           6.56G
## 4 E:/Data/RAIS/2021/7z/RAIS_VINC_PUB_NORDESTE.txt           5.54G
## 5 E:/Data/RAIS/2021/7z/RAIS_VINC_PUB_NORTE.txt              1.81G
## 6 E:/Data/RAIS/2021/7z/RAIS_VINC_PUB_SP.txt                 9.19G
## 7 E:/Data/RAIS/2021/7z/RAIS_VINC_PUB_SUL.txt                6.02G

Os microdados da RAIS 2021 relativos aos vínculos empregatícios são disponibilizados em seis arquivos, que são organizados de acordo com a região geográfica e/ou estado das observações. O arquivo relativo à região norte é o menor e o com os registros do estado de São Paulo o maior. Seguiremos no exemplo lendo os dados da região norte (RAIS_VINC_PUB_NORTE.txt), mas o mesmo procedimento pode ser feito para os outros arquivos.

3 Lendo os microdados: o pacote vroom

As dificuldades de se trabalhar com grandes bases de dados se iniciam antes da análise em si. A leitura inicial dos arquivos brutos dessas bases (em txt ou csv, por exemplo) pode ser bastante lenta. Adicionalmente, é comum que a leitura precise ser refeita algumas vezes até se ter certeza que todos os campos/variáveis foram lidos corretamente e para que possíveis problemas possam ser identificados (mais de uma observação por linha ou um delimitador diferente do esperado, por exemplo), o que multiplica o tempo despendido na leitura inicial dos dados.

Os microdados da RAIS não apresentam problemas na maioria dos anos, mas realizar a sua leitura utilizando as funções padrão (como read.csv()) seria um processo bastante lento. Há algumas opções para leitura com performance melhor, sendo o pacote vroom de Hester et al. (2023) uma das alternativas mais rápidas.

suppressMessages(library("vroom"))

A leitura inicial de uma nova base deve considerar apenas parte das observações (linhas) para identificar os problemas mais óbvios sem incorrer no tempo total de leitura. Neste caso consideramos apenas as dez primeiras observações (n_max = 10). Sabemos que a primeira linha do arquivo contém o nome das variáveis (col_names = TRUE), que as variáveis são delimitadas por ponto e vírgula (delim = ";"), e que a codificação dos caracteres segue o padrão latin1.

RAIS_NO_2021 <- vroom("E:/Data/RAIS/2021/7z/RAIS_VINC_PUB_NORTE.txt",
                      delim = ";",
                      col_names = TRUE,
                      n_max = 10,
                      locale = locale(encoding="latin1"))
## New names:
## Rows: 10 Columns: 60
## ── Column specification
## ──────────────────────────────────────────────────────── Delimiter: ";" chr
## (38): Bairros SP, Bairros Fortaleza, Bairros RJ, Motivo Desligamento, Di... dbl
## (21): Causa Afastamento 1, Causa Afastamento 2, Causa Afastamento 3, CBO... num
## (1): Tempo Emprego
## ℹ Use `spec()` to retrieve the full column specification for this data. ℹ
## Specify the column types or set `show_col_types = FALSE` to quiet this message.
## • `Tipo Estab` -> `Tipo Estab...42`
## • `Tipo Estab` -> `Tipo Estab...43`

A mensagem da leitura das dez primeiras linhas mostra que existem 60 variáveis (colunas) no arquivo. Entres essas havia duas variáveis com o mesmo nome (Tipo Estab nas colunas 42 e 43) que foram renomeadas para Tipo Estab...42 e Tipo Estab...43. Podemos verificar o nome de todas as variáveis na RAIS 2021 usando a função colnames().

# Mostra o nome das variáveis lidos do arquivo
colnames(RAIS_NO_2021)
##  [1] "Bairros SP"              "Bairros Fortaleza"      
##  [3] "Bairros RJ"              "Causa Afastamento 1"    
##  [5] "Causa Afastamento 2"     "Causa Afastamento 3"    
##  [7] "Motivo Desligamento"     "CBO Ocupação 2002"      
##  [9] "CNAE 2.0 Classe"         "CNAE 95 Classe"         
## [11] "Distritos SP"            "Vínculo Ativo 31/12"    
## [13] "Faixa Etária"            "Faixa Hora Contrat"     
## [15] "Faixa Remun Dezem (SM)"  "Faixa Remun Média (SM)" 
## [17] "Faixa Tempo Emprego"     "Escolaridade após 2005" 
## [19] "Qtd Hora Contr"          "Idade"                  
## [21] "Ind CEI Vinculado"       "Ind Simples"            
## [23] "Mês Admissão"            "Mês Desligamento"       
## [25] "Mun Trab"                "Município"              
## [27] "Nacionalidade"           "Natureza Jurídica"      
## [29] "Ind Portador Defic"      "Qtd Dias Afastamento"   
## [31] "Raça Cor"                "Regiões Adm DF"         
## [33] "Vl Remun Dezembro Nom"   "Vl Remun Dezembro (SM)" 
## [35] "Vl Remun Média Nom"      "Vl Remun Média (SM)"    
## [37] "CNAE 2.0 Subclasse"      "Sexo Trabalhador"       
## [39] "Tamanho Estabelecimento" "Tempo Emprego"          
## [41] "Tipo Admissão"           "Tipo Estab...42"        
## [43] "Tipo Estab...43"         "Tipo Defic"             
## [45] "Tipo Vínculo"            "IBGE Subsetor"          
## [47] "Vl Rem Janeiro SC"       "Vl Rem Fevereiro SC"    
## [49] "Vl Rem Março SC"         "Vl Rem Abril SC"        
## [51] "Vl Rem Maio SC"          "Vl Rem Junho SC"        
## [53] "Vl Rem Julho SC"         "Vl Rem Agosto SC"       
## [55] "Vl Rem Setembro SC"      "Vl Rem Outubro SC"      
## [57] "Vl Rem Novembro SC"      "Ano Chegada Brasil"     
## [59] "Ind Trab Intermitente"   "Ind Trab Parcial"

3.1 A função problems()

Eu sei que tem problemas na América do Sul
Eu sei que tem problemas na Europa e na Ásia
Eu sei que tem problemas na África e na Oceania
Eu sei que tem problemas no Rio Grande do Sul
Mas resolver os problemas do mundo é coisa de vagabundo
Resolver os problemas do mundo é coisa de vagabundo

Problemas - Os Replicantes

vroom will only fail to parse a file if the file is invalid in a way that is unrecoverable. However there are a number of non-fatal problems that you might want to know about. You can retrieve a data frame of these problems with this function.

Além do desempenho em velocidade de leitura o pacote vroom também traz uma função de diagnóstico bastante útil para avaliar se o arquivo bruto foi carregado adequadamente. Trata-se da função problems().

A função problems() toma como argumento um tibble gerado pelo vroom() e retorna outro tibble indicando a linha em que ocorreu o problema e algumas informações como o número de colunas esperado e o número de colunas encontrado, assim como discrepâncias dos dados em relação aos formatos de leitura (as.character(), as.Date(), por exemplo) quando eles são especificados.

Como já mencionado, a geração dos microdados da RAIS é cuidadosa e na maioria dos anos não são encontrados problemas, o que faz com que a função problems() gere um tibble com zero linhas.

# Consulta se o vroom registrou problemas na leitura
problems(RAIS_NO_2021)
## # A tibble: 0 × 5
## # ℹ 5 variables: row <int>, col <int>, expected <chr>, actual <chr>, file <chr>

Para ilustrar a utilidade dessa função vamos considerar outro arquivo de microdados, também disponibilizado pelo MTE, mas esse referente às Guia de Recolhimento da Contribuição Sindical Urbana (GRCSU). Considerando a base de guias de 2006, lemos os dados utilizando a função vroom() e em seguida aplicamos a função problems() ao tibble resultante:

CSU_2006 <- vroom(arquivos_CSU[1],
                  skip = 1,
                  delim = ";",
                  col_names =  c("UF", "CATEGORIA", "ENTIDADE", "CNAE", "CONTRIBUINTE", 
                                 "DT_ARRECADACAO", "VR_ARRECADADO", "VR_MTE", "VAR_DES"),
                  col_types = cols("UF" = col_character(), 
                                   "CATEGORIA" = col_character(),
                                   "ENTIDADE"  =col_character(), 
                                   "CNAE" = col_integer(),
                                   "CONTRIBUINTE" = col_character(), 
                                   "DT_ARRECADACAO" = col_date(format = "%d.%m.%Y"), 
                                   "VR_ARRECADADO" = col_number(), 
                                   "VR_MTE" = col_number(), 
                                   "VAR_DES" = col_character()),
                  locale = locale(decimal_mark = ",", 
                                  grouping_mark = "."))

problems(CSU_2006)
## Warning: One or more parsing issues, call `problems()` on your data frame for details,
## e.g.:
##   dat <- vroom(...)
##   problems(dat)
## # A tibble: 11 × 5
##        row   col expected  actual     file 
##      <int> <int> <chr>     <chr>      <chr>
##  1   60913    17 9 columns 17 columns ""   
##  2 1370358    17 9 columns 17 columns ""   
##  3 1691243    17 9 columns 17 columns ""   
##  4 2929481    17 9 columns 17 columns ""   
##  5 3370565    17 9 columns 17 columns ""   
##  6 3560509    17 9 columns 17 columns ""   
##  7 3748702    17 9 columns 17 columns ""   
##  8 3940812    17 9 columns 17 columns ""   
##  9 4101286    17 9 columns 17 columns ""   
## 10 4308305    17 9 columns 17 columns ""   
## 11 4483160    17 9 columns 17 columns ""

Nesse caso temos que em 11 linhas eram esperadas 9 colunas, mas foram lidas 17 colunas. A última variável especificada na leitura (VAR_DES = “Variável desconhecida”) é uma variável que deveria estar em branco para todas observações e que só é gerada por cada linha se encerrar com o delimitador (;). Investigando as linhas especificadas na coluna row (60913, 1370358, …) descobrimos que nessas linhas estão registradas duas observações no lugar de uma (8 + 1 + 8 = 17). Nesse caso, a solução é dividir essas linhas após a leitura para que cada linha corresponda a uma observação apenas. Outros problemas podem demandar soluções mais complexas.

3.2 Leitura do csv utilizando o vroom()

Finalmente passamos a leitura dos dados de fato. Utilizando os nomes identificados na leitura inicial e a opção col_select = podemos selecionar as variáveis que serão lidas. Como o objetivo é ler “a base toda”, são excluídas apenas as variáveis relativas aos bairros de algumas cidades, por serem pouco uteis, e as variáveis correspondentes a remuneração em termos de salário mínimo, que podem ser obtidas das outras variáveis de remuneração.

A opção col_types = indica o tipo de variável que está sendo lida. Nessa base existem apenas três tipos: variáveis numéricas discretas, que se referem a alguma codificação como a CNAE (col_integer()); variáveis contínuas, como o valor da remuneração (col_double()); e variáveis lógicas indicando se algo ocorre ou não (col_logical()).

RAIS_NO_2021 <- vroom("E:/Data/RAIS/2021/7z/RAIS_VINC_PUB_NORTE.txt",
                      delim = ";",
                      locale=locale(encoding="latin1", decimal_mark = ","),
                      col_names = TRUE,
                      col_select = c("Causa Afastamento 1",
                                     "Causa Afastamento 2",
                                     "Causa Afastamento 3",
                                     "Motivo Desligamento",
                                     "CBO Ocupação 2002",  
                                     "CNAE 2.0 Classe",
                                     "CNAE 95 Classe",
                                     "Vínculo Ativo 31/12",
                                     "Faixa Etária",
                                     "Faixa Hora Contrat",
                                     "Faixa Tempo Emprego",
                                     "Escolaridade após 2005", 
                                     "Qtd Hora Contr",
                                     "Idade", 
                                     "Ind CEI Vinculado", 
                                     "Ind Simples",
                                     "Mês Admissão",  
                                     "Mês Desligamento",  
                                     "Mun Trab", 
                                     "Município",
                                     "Nacionalidade",
                                     "Natureza Jurídica",  
                                     "Ind Portador Defic", 
                                     "Qtd Dias Afastamento",
                                     "Raça Cor", 
                                     "Vl Remun Dezembro Nom",  
                                     "Vl Remun Média Nom", 
                                     "CNAE 2.0 Subclasse", 
                                     "Sexo Trabalhador",
                                     "Tamanho Estabelecimento",
                                     "Tempo Emprego", 
                                     "Tipo Admissão", 
                                     "Tipo Estab...42",
                                     "Tipo Estab...43", 
                                     "Tipo Defic", 
                                     "Tipo Vínculo", 
                                     "IBGE Subsetor", 
                                     "Vl Rem Janeiro SC",  
                                     "Vl Rem Fevereiro SC", 
                                     "Vl Rem Março SC", 
                                     "Vl Rem Abril SC", 
                                     "Vl Rem Maio SC", 
                                     "Vl Rem Junho SC", 
                                     "Vl Rem Julho SC", 
                                     "Vl Rem Agosto SC",  
                                     "Vl Rem Setembro SC", 
                                     "Vl Rem Outubro SC",  
                                     "Vl Rem Novembro SC",
                                     "Ano Chegada Brasil", 
                                     "Ind Trab Intermitente",
                                     "Ind Trab Parcial"  
                                     ),
                      col_types =  cols("Causa Afastamento 1" = col_integer(),
                                        "Causa Afastamento 2" = col_integer(),
                                        "Causa Afastamento 3" = col_integer(),
                                        "Motivo Desligamento" = col_integer(),
                                        "CBO Ocupação 2002" = col_integer(),
                                        "CNAE 2.0 Classe" = col_integer(),
                                        "CNAE 95 Classe" = col_integer(),
                                        "Vínculo Ativo 31/12" = col_logical(),
                                        "Faixa Etária" = col_integer(),
                                        "Faixa Hora Contrat" = col_integer(),
                                        "Faixa Tempo Emprego" = col_integer(),
                                        "Escolaridade após 2005" = col_integer(),
                                        "Qtd Hora Contr" = col_integer(),
                                        "Idade" = col_integer(),
                                        "Ind CEI Vinculado" = col_logical(),
                                        "Ind Simples" = col_logical(),
                                        "Mês Admissão" = col_integer(),
                                        "Mês Desligamento" = col_integer(),
                                        "Mun Trab" = col_integer(),
                                        "Município" = col_integer(),
                                        "Nacionalidade" = col_integer(),
                                        "Natureza Jurídica" = col_integer(),
                                        "Ind Portador Defic" = col_logical(),
                                        "Qtd Dias Afastamento" = col_integer(),
                                        "Raça Cor" = col_integer(),
                                        "Vl Remun Dezembro Nom" = col_double(),
                                        "Vl Remun Média Nom" = col_double(),
                                        "CNAE 2.0 Subclasse" = col_integer(),
                                        "Sexo Trabalhador" = col_integer(),
                                        "Tamanho Estabelecimento" = col_integer(),
                                        "Tempo Emprego" = col_double(),
                                        "Tipo Admissão" = col_integer(),
                                        "Tipo Estab...42" = col_integer(),
                                        "Tipo Estab...43" = col_character(),
                                        "Tipo Defic" = col_integer(),
                                        "Tipo Vínculo" = col_integer(),
                                        "IBGE Subsetor" = col_integer(),
                                        "Vl Rem Janeiro SC" = col_double(),
                                        "Vl Rem Fevereiro SC" = col_double(),
                                        "Vl Rem Março SC" = col_double(),
                                        "Vl Rem Abril SC" = col_double(),
                                        "Vl Rem Maio SC" = col_double(),
                                        "Vl Rem Junho SC" = col_double(),
                                        "Vl Rem Julho SC" = col_double(),
                                        "Vl Rem Agosto SC" = col_double(),
                                        "Vl Rem Setembro SC" = col_double(),
                                        "Vl Rem Outubro SC" = col_double(),
                                        "Vl Rem Novembro SC" = col_double(),
                                        "Ano Chegada Brasil" = col_integer(),
                                        "Ind Trab Intermitente" = col_logical(),
                                        "Ind Trab Parcial" = col_logical()
                                        )
                      ) 
## New names:
## • `Tipo Estab` -> `Tipo Estab...42`
## • `Tipo Estab` -> `Tipo Estab...43`

Para as análises é mais conveniente que as variáveis possuam nomes mais curtos, por essa razão elas são renomeadas abaixo. É conveniente que os mesmos nomes sejam mantidos caso se vá trabalhar com dados de outros anos, isto é, que as variáveis tenham os mesmos nomes nos dados da RAIS 2020, 2021, e 2022, por exemplo. Note que a função problems() não funcionará após alguma outra mudança nesse objeto (além dos nomes das variáveis), pois ela não reconhecerá mais o tibble como tendo sido gerado pelo vroom().

colnames(RAIS_NO_2021) <- c('afast1',
                            'afast2',
                            'afast3',
                            'mot_des',
                            'cbo_2002',
                            'cnae20',
                            'cnaeE95',
                            'va_3112',
                            'fx_etaria',
                            'fx_horas',
                            'fx_tempo',
                            'escolaridade',
                            'horas_contr',
                            'idade',
                            'cei',
                            'simples',
                            'adm_mes',
                            'adm_des',
                            'muni_trab',
                            'muni_estab',
                            'nacion',
                            'nat_jud',
                            'deficiente',
                            'afast_dias',
                            'raca_cor',
                            'rem_dez',
                            'rem_media',
                            'cnae20s',
                            'sexo',
                            'tam_estab',
                            'tempo',
                            'adm_tipo',
                            'estab_tipo1',
                            'estab_tipo2',
                            'deficiecia_tipo',
                            'tipo_vinc',
                            'ibge_sub',
                            'rem_jan',
                            'rem_fev',
                            'rem_mar',
                            'rem_abr',
                            'rem_mai',
                            'rem_jun',
                            'rem_jul',
                            'rem_ago',
                            'rem_set',
                            'rem_out',
                            'rem_nov',
                            'ano_br',
                            'intermitente',
                            'parcial')

Não há uma variável pronta para identificar as UFs nesta base, contudo ela pode ser criada a partir dos códigos de município. Isso é feito antes de considerarmos salvar a base:

RAIS_NO_2021 <- RAIS_NO_2021 %>% 
    # Cria uma variável de UF
    dplyr::mutate( UF = trunc(muni_estab/10000) )

3.3 Dividindo a leitura de um mesmo csv

Dependendo do tamanho arquivo e do hardware em uso, mesmo a leitura de um único arquivo pode ser problemática devido à base de dados a ser maior que a memória RAM disponível. Uma solução possível neste caso é ler o arquivo em partes no lugar de lê-lo todo de uma vez. Se esse for o caminho a ser seguido deve-se ter especial cuidado na atribuição dos nomes das variáveis, sendo melhor incluir esses nomes no código do que capturá-los da primeira linha do arquivo13.

A comparação da memória RAM disponível no sistema com o tamanho do arquivo a ser lido sugere o número de partes em que o arquivo precisará ser dividido. A divisão pode então ser feita considerando o número de linhas do arquivo bruto utilizando as opções comandos skip e n_max do vroom.

No nosso exemplo, o arquivo RAIS_VINC_PUB_NORTE.txt tem 1.81GB. Vamos assumir que o hardware disponível seja capaz de lidar com um terço desse arquivo com tranquilidade, assim teríamos que ler esse arquivo em três partes. Recorrendo ao pacote R.utils podemos contar o número de linhas de um arquivo txt ou csv sem ter que o abrir. Usando o pacote tictoc verifica-se que mesmo a contagem de linhas é um processo relativamente lento para arquivos grandes.

tictoc::tic()
R.utils::countLines("E:/Data/RAIS/2021/7z/RAIS_VINC_PUB_NORTE.txt")
## [1] 3977464
## attr(,"lastLineHasNewline")
## [1] TRUE
tictoc::toc()
## 21.89 sec elapsed

Com o número de linhas conhecido, cada parte do arquivo seria lida e salva em parquet. Removendo os objetos da memória se repetiria o processo com as demais partes. O código abaixo ilustra como a leitura em partes seria feita.

# Leitura da primeira parte do csv
RAIS_NO_2021_parte1 <- vroom("E:/Data/RAIS/2021/7z/RAIS_VINC_PUB_NORTE.txt",
                             delim = ";",
                             locale=locale(encoding="latin1", decimal_mark = ","),
                             col_names = c(
                                            ..............
                                           ),
                             col_select = c(
                                            ..............
                                            ),
                             col_types =  cols(
                                               ..............
                                               )
                             # Pula a primeira linha, que tem o nome das variáveis
                             skip = 1,
                             # Lê até a linha 1.325.821
                             n_max = 1325821
                             )

# Salvar e limpar a memória

# Leitura da segunda parte do csv
RAIS_NO_2021_parte2 <- vroom("E:/Data/RAIS/2021/7z/RAIS_VINC_PUB_NORTE.txt",
                             delim = ";",
                             locale=locale(encoding="latin1", decimal_mark = ","),
                             col_names = c(
                                            ..............
                                           ),
                             col_select = c(
                                            ..............
                                            ),
                             col_types =  cols(
                                               ..............
                                               )
                             # Pula as primeiras 1.325.821 linhas
                             skip = 1325821,
                             # Lê até a linha 2.651.642
                             n_max = 2651642
                             )

# Salvar e limpar a memória

# Leitura da terceira parte do csv
RAIS_NO_2021_parte3 <- vroom("E:/Data/RAIS/2021/7z/RAIS_VINC_PUB_NORTE.txt",
                             delim = ";",
                             locale=locale(encoding="latin1", decimal_mark = ","),
                             col_names = c(
                                            ..............
                                           ),
                             col_select = c(
                                            ..............
                                            ),
                             col_types =  cols(
                                               ..............
                                               )
                             # Pula as primeiras 2.651.642 linhas
                             skip = 2651642
                             # Não é necessário definir um n_max aqui, já que
                             # o arquivo será lido até o fim
                             )

# Salvar e limpar a memória

Os procedimentos de checagem dos dados (com a função problems) e a mudança no nome das variáveis pode ser feito da mesma forma que com o arquivo lido em uma parte apenas. A divisão de um arquivo grande em partes menores normalmente acarretaria nos problemas citados na introdução, contudo, salvando as partes em parquet em um mesmo diretório será possível analisá-los conjuntamente como veremos a seguir.

No restante desse texto vamos assumir que cada um dos seis arquivos de vínculos empregatícios da RAIS 2021 tenha sido lido sem divisão.

4 Gravando os microdados: o pacote arrow

Uma vez que os dados tenham sido lidos eles já podem ser analisados, mas idealmente eles devem ser salvos em algum formato que ofereça vantagens em relação ao formato bruto (csv ou txt) tanto para evitar uma nova leitura quanto para otimizar a velocidade das análises e/ou o tamanho em disco. A escolha feita aqui é pelo formato Apache Parquet que além da velocidade de processamento, por sua organização “por colunas”, também resulta em um arquivo de tamanho reduzido em disco pelas possibilidades de compressão.

A seção seguinte descreve a motivação por trás da representação colunar em disco (columnar representation in-disk), mas ela não é essencial para as demais e pode ser pulada caso você não se interesse saber como esse formato funciona.

4.1 Linhas e colunas: por que se importar?

Quando dados são armazenados na memória ou no disco de um computador eles são armazenados e lidos de forma unidimensional (ou linear, numa sequência números binários). Assim, quando estamos gravando ou lendo um array multidimensional, como uma tabela, é necessária uma definição sobre como (em que ordem) as diferentes dimensões serão transferidas para o padrão unidimensional do armazenamento dos dados.

Para arrays bidimensionais como as tabelas há duas opções principais a row-major order (orientalção por observação ou por linha) e a column-major order (orientação por variável ou por coluna).

\[ \begin{bmatrix} \color{red}{a_{0,0}} & \color{lightblue}{a_{0,1}} & \color{green}{a_{0,2}} \\ \color{red}{a_{1,0}} & \color{lightblue}{a_{1,1}} & \color{green}{a_{1,2}} \\ \color{red}{a_{2,0}} & \color{lightblue}{a_{2,1}} & \color{green}{a_{2,2}} \\ \end{bmatrix} \Rightarrow \underbrace{ \begin{bmatrix} \color{red}{a_{0,0}} & \color{lightblue}{a_{0,1}} & \color{green}{a_{0,2}} & \color{red}{a_{1,0}} & \color{lightblue}{a_{1,1}} & \color{green}{a_{1,2}} & \color{red}{a_{2,0}} & \color{lightblue}{a_{2,1}} & \color{green}{a_{2,2}} \\ \end{bmatrix}}_{\text{Row-major order}} \]

\[ \begin{bmatrix} \color{red}{a_{0,0}} & \color{lightblue}{a_{0,1}} & \color{green}{a_{0,2}} \\ \color{red}{a_{1,0}} & \color{lightblue}{a_{1,1}} & \color{green}{a_{1,2}} \\ \color{red}{a_{2,0}} & \color{lightblue}{a_{2,1}} & \color{green}{a_{2,2}} \\ \end{bmatrix} \Rightarrow \underbrace{ \begin{bmatrix} \color{red}{a_{0,0}} & \color{red}{a_{1,0}} & \color{red}{a_{2,0}} & \color{lightblue}{a_{0,1}} & \color{lightblue}{a_{1,1}} & \color{lightblue}{a_{2,1}} & \color{green}{a_{0,2}} & \color{green}{a_{1,2}} & \color{green}{a_{2,2}} \\ \end{bmatrix}}_{\text{Column-major order}} \]

A diferença dessas duas formas de armazenamento é a spatial locality resultante, isto é, qual informação está contígua a qual na memória. Na row-major order temos que as informações de um mesmo registro são contíguas, enquanto na column-major order temos que as informações de uma mesma variável são contíguas.

Historicamente a maioria dos sistemas optou pelo formato de armazenamento em linhas porque a maioria das aplicações envolvia a gravação e leitura de todas as informações de uma ou mais observações. Nesse contexto, um armazenamento colunar é ineficiente. Com o tempo, as aplicações passaram a envolver cálculos que utilizavam apenas algumas variáveis de um grande número (ou de todas) as observações. Nesse contexto, o armazenamento colunar é mais eficiente e o acesso aos dados demanda mais tempo, Abadi (2017).14

Essas opções de organização se relacionam a otimização das consultas (query optimization) que são possíveis de fazer. Se a aplicação principal envolve recuperar registros (observações) a organização por linhas facilita a abordagem de “predicate pushdown” que visa reduzir a quantidade de observações lidas e carregadas na memória ao explorar os metadados de um arquivo (um índice em um banco SQL, por exemplo) antes de ler os registros individuais de fato. Se a aplicação envolve apenas cálculos de algumas variáveis, sem necessidade de recuperar todas as variáveis dos registros a organização por colunas favorece a estratégia “projection pushdown”, que seleciona apenas as colunas necessárias antes de ler os registros.

The columnar data formats are a popular choice for fast analytics workloads. As opposed to row-oriented storage, columnar storage can significantly reduce the amount of data fetched from disk by allowing access to only the columns that are relevant for the particular query or workload. Moreover, columnar storage combined with efficient encoding and compression techniques can drastically reduce the storage requirements without sacrificing query performance.

Floratou (2018)

Nesse contexto, a opção para deixar nossos dados em formato colunar na memória e no disco são o Apache Arrow e o Apache Parquet, respectivamente, sendo que ambos são integrados ao R ao pelo pacote arrow, Richardson et al. (2023).

suppressMessages(library("arrow"))

Apache Arrow defines a language-independent columnar memory format for flat and hierarchical data, organized for efficient analytic operations on modern hardware like CPUs and GPUs. The Arrow memory format also supports zero-copy reads for lightning-fast data access without serialization overhead.

Apache Arrow

Apache Parquet is an open source, column-oriented data file format designed for efficient data storage and retrieval. It provides efficient data compression and encoding schemes with enhanced performance to handle complex data in bulk. Parquet is available in multiple languages including Java, C++, Python, etc…

Databricks - What is Parquet?

Parquet and Arrow are complementary technologies, and they make some different design tradeoffs. In particular, Parquet is a storage format designed for maximum space efficiency, whereas Arrow is an in-memory format intended for operation by vectorized computational kernels.

Arrow and Parquet Part 1: Primitive Types and Nullability

A peculiaridade do formato parquet é que ele não é um formato colunar de fato, mas sim um formato híbrido que armazena sequencialmente grupos de colunas (column chunks):

\[ \begin{bmatrix} \color{red}{a_{0,0}} & \color{lightblue}{a_{0,1}} & \color{green}{a_{0,2}} \\ \color{red}{a_{1,0}} & \color{lightblue}{a_{1,1}} & \color{green}{a_{1,2}} \\ \color{red}{a_{2,0}} & \color{lightblue}{a_{2,1}} & \color{green}{a_{2,2}} \\ \end{bmatrix} \Rightarrow \underbrace{ \begin{bmatrix} \color{red}{a_{0,0}} & \color{red}{a_{1,0}} & \color{lightblue}{a_{0,1}} & \color{lightblue}{a_{1,1}} & \color{green}{a_{0,2}} & \color{green}{a_{1,2}} & \color{red}{a_{2,0}} & \color{lightblue}{a_{2,1}} & \color{green}{a_{2,2}} \\ \end{bmatrix}}_{\text{Formato de armazenamento híbrido}} \]

Assim, um arquivo parquet é composto por um ou mais grupos de linhas (row groups). Cada grupo de linha contém um fragmento de coluna (column chunk) para cada variável/coluna. Os fragmentos de coluna são divididos em páginas (pages). Adicionalmente, há uma estrutura de metadados no arquivo correspondente a essas divisões.

Formato de arquivo \(~\)

Devido a essa organização interna, um conjunto de arquivos parquet salvos em um mesmo diretório pode ser tratado como constituindo uma mesma base de dados (parquet dataset) no ambiente Apache Arrow. Ao se procurar uma informação específica (observações de um estado, por exemplo) são lidos os metadados dos arquivos individuais, uma vez que os arquivos contendo essa informação são localizados, se passa a leitura dos metadados das outras estruturas o que permite a leitura apenas dos dados de interesse aumentando a velocidade do processo.

\[ \text{Diretório} \rightarrow \text{Arquivos .parquet} \rightarrow \text{Row Groups} \rightarrow \text{Column chunks} \rightarrow \text{Data Page} \]

É isso que possibilita a divisão de um arquivo muito grande em pedaços menores sem prejudicar as análises, por exemplo. Uma base de dados em parquet pode ser composta por um caminho para um diretório que contenha um ou mais arquivos parquet, no caso de os arquivos serem salvos sem particionamento. Ou então o caminho de uma pasta contendo subdiretórios que correspondem ao particionamento da base original segundo alguma(s) de sua(s) variáveis.

4.2 Criando um Parquet dataset

Como já mencionado, o formato parquet reduz bastante o tamanho do arquivo em disco e essa compactação pode ser por si só um bom motivador do uso deste formato. Nesse caso um arquivo salvo pode ser carregado como em outros formatos recuperando o objeto original que é carregado na memória RAM, por exemplo.

objeto <- arrow::read_parquet("E:/diretorio/arquivo.parquet")

Contudo, nossa motivação aqui é economizar o uso da memória RAM e, para tanto, estamos preocupados com a criação de “parquet datasets”. Há ao menos duas opções possíveis para isso, uma é salvar os arquivos correspondentes a cada txt lido em um mesmo diretório. A outra é salvar cada arquivo no mesmo diretório, mas com particionamento, o que resultará numa estrutura mais complexa de subdiretórios no disco. O particionamento demanda (bastante) mais tempo para se salvar os arquivos e faz com que a compactação não seja tão eficiente. Contudo, se ele for feito corretamente o tempo gasto no particionamento será mais que compensado pelo tempo economizado nas análises posteriores. O restante da seção é baseado no capítulo de arrow do excelente Wickham, Çetinkaya-rundel, and Grolemund (2023).

4.2.1 Salvando parquet sem particionamento (arquivo único)

A forma mais direta de se salvar um objeto em parquet é utilizar a função write_parquet() do pacote arrow. A opção pelo tipo de compactação gzip se deve ao objetivo de minimizar o tamanho em disco, mas é possivel salvar os arquivos com outros modos de compactação que possibilitam consultas mais rápidas, mas em arquivos maiores.

Não é necessário ordenar a base antes de ela ser salva, mas dado a organização interna dos arquivos parquet (na indexação feita pelos metadados) e pela forma de compactação (baseada em valores semelhantes das colunas) optamos por ordenar a base por duas variáveis antes de salvá-la. Isso terá impactos no tamanho em disco e também no tempo de consulta.

RAIS_NO_2021 %>% 
  # Ordena a base
  dplyr::arrange(UF, cnae20) %>% 
  write_parquet(
              sink = "E:/Data/RAIS/2021/parquet/completa/RAIS2021N.parquet",
              compression = "gzip",
              compression_level = 9) %>% 
  system.time()
## Warning: One or more parsing issues, call `problems()` on your data frame for details,
## e.g.:
##   dat <- vroom(...)
##   problems(dat)
##   usuário   sistema decorrido 
##     53.06      0.28     52.90

Fazendo isso para cada arquivo original da RAIS de vínculos empregatícios obtemos o seguinte conjunto de arquivos:

## Obtém a lista de arquivos parquet no diretório
arquivos_pq <- fs::dir_info("E:/Data/RAIS/2021/parquet/completa", glob = "*.parquet")

print(arquivos_pq[,c(1,3)])
## # A tibble: 6 × 2
##   path                                                         size
##   <fs::path>                                            <fs::bytes>
## 1 E:/Data/RAIS/2021/parquet/completa/RAIS2021CO.parquet        240M
## 2 E:/Data/RAIS/2021/parquet/completa/RAIS2021MG.parquet        546M
## 3 E:/Data/RAIS/2021/parquet/completa/RAIS2021N.parquet         142M
## 4 E:/Data/RAIS/2021/parquet/completa/RAIS2021NE.parquet        428M
## 5 E:/Data/RAIS/2021/parquet/completa/RAIS2021S.parquet         526M
## 6 E:/Data/RAIS/2021/parquet/completa/RAIS2021SP.parquet        800M

Uma observação importante é que o formato parquet não suporta objetos do tipo lista, idealmente o objeto a ser salvo é uma base de dados retangular. Tentar salvar uma lista usando arrow::write_parquet() resultará numa mensagem de erro.

4.2.2 Salvando parquet com particionamento (multiplos arquivos)

So hurry up and wait

But what’s worth waiting for?

Hurry Up And Wait - Stereophonics

Conhecendo as queries mais frequentes que serão feitas um uma base o ideal seria salvar a base particionando os arquivos de acordo com essas queries, que serão mais rápidas no arquivo particionado. Para isso se utiliza a função write_dataset() do pacote arrow em conjunto com a função group_by() do pacote dplyr.

There is no such thing as a free lunch

O particionamento da base resultará numa estrutura de subdiretórios mais complexa e tanto a criação desses subdiretórios quanto a gravação dos diferentes arquivos parquet farão com que salvar uma base particionada demande bem mais tempo do que salvar uma não particionada.

Adicionalmente, a grande redução do tamanho em disco dos arquivos parquet em relação aos originais se deve ao uso de dicionários e outras técnicas que se valem da semelhança dos dados em cada coluna. Ao se particionar uma base a divisão em diferentes arquivos reduzirá essas possibilidades de compactação, além de se multiplicar o volume de metadados. Assim, é esperado que uma base particionada ocupe mais espaço em disco que uma não particionada.

No nosso exemplo as consultas mais frequentes serão por unidade da federação (UF) e classe da Classificação Nacional de Atividade Econômica (CNAE).

Dada a estrutura interna dos arquivos parquet e a forma de particionamento, melhores resultados em termos de compactação e tempo de acesso serão obtidas se esses arquivos forem ordenados de acordo com as variáveis de particionamento (usando arrange()). Depois as observações são agrupadas por UF e classe da CNAE e só então a função write_dataset() é utilizada:

RAIS_NO_2021 %>% 
  # Indica as variáveis para particionamento
  dplyr::arrange(UF, cnae20) %>% 
  dplyr::group_by(UF, cnae20) %>% 
  # Grava o objeto em arquivos parquet seguindo o particionamento do group_by
  write_dataset(path = "E:/Data/RAIS/2021/parquet/particionada", 
                format = "parquet",
                compression = "gzip",
                compression_level = 9) %>% 
  system.time()
##   usuário   sistema decorrido 
##     54.70      4.80     78.19

Abaixo, podemos ver que o particionamento segundo as variáveis listadas no group_by() gera uma estrutura de diretórios pelos valores dessas variáveis.

Essa divisão em diretórios de acordo com os valores de uma (ou mais) de suas colunas é derivado de outra tecnologia da Apache, o Apache Hive, e por isso é referido como hive-style directory naming. Isso pode ser alterado (hive_style = FALSE), mas fica mais difícil para o analista observar o resultado em disco da opção de particionamento.

## Obtém a lista de arquivos parquet no diretório
arquivos_pq_part <- fs::dir_info("E:/Data/RAIS/2021/parquet/particionada", 
                                 recurse = TRUE,
                                 glob = "*.parquet")

print(arquivos_pq_part[,c(1,3)])
## # A tibble: 14,911 × 2
##    path                                                                     size
##    <fs::path>                                                             <fs::>
##  1 …Data/RAIS/2021/parquet/particionada/UF=11/cnae20=10112/part-0.parquet 769.2K
##  2 …Data/RAIS/2021/parquet/particionada/UF=11/cnae20=10121/part-0.parquet  84.9K
##  3 …Data/RAIS/2021/parquet/particionada/UF=11/cnae20=10139/part-0.parquet    56K
##  4 …Data/RAIS/2021/parquet/particionada/UF=11/cnae20=10201/part-0.parquet  30.8K
##  5 …Data/RAIS/2021/parquet/particionada/UF=11/cnae20=10317/part-0.parquet    29K
##  6 …Data/RAIS/2021/parquet/particionada/UF=11/cnae20=10325/part-0.parquet  18.6K
##  7 …Data/RAIS/2021/parquet/particionada/UF=11/cnae20=10333/part-0.parquet  20.5K
##  8 …Data/RAIS/2021/parquet/particionada/UF=11/cnae20=10414/part-0.parquet  27.2K
##  9 …Data/RAIS/2021/parquet/particionada/UF=11/cnae20=10511/part-0.parquet  74.3K
## 10 …Data/RAIS/2021/parquet/particionada/UF=11/cnae20=10520/part-0.parquet 136.1K
## # ℹ 14,901 more rows

Nota-se que salvar o objeto particionado levou bem mais tempo que salvá-lo em um único arquivo. Adicionalmente, a opção de particionamento da base pela CNAE dentro das UFs pode ser considerada “ruim” já pelos resultados acima (por resultar em muitos arquivos muito pequenos). Uma opção melhor possivelmente seria particioná-la apenas por UF. Mas para fins de ilustração seguiremos no exemplo com essa opção de particionamento.

Abaixo mostramos o espaço ocupado em disco do diretório com os arquivos originais, os arquivos parquet sem particionamento e os arquivos parquet salvos com particionamente sem ordenar as observações e ordenando as observações.

Formato Número de arquivos Tamanho em disco
txt 6 32GB
parquet - sem particionamento (sem ordenamento) 6 6,87GB
parquet - sem particionamento (com ordenamento) 6 2,63GB
parquet - com particionamento (sem ordenamento) 14292 16,9GB
parquet - com particionamento (com ordenamento) 14911 3,24GB

5 Consultando a base

Com os arquivos criados da seção anterior já temos a grande vantagem da base de dados estar ocupando um espaço bem menor em disco (em relação ao formato original), mas esse não é nosso principal objetivo. Lendo e salvando os demais arquivos da RAIS 2021 teremos os dados de todos os vínculos empregatícios do Brasil em 2021 salvos em formato parquet e poderemos fazer consultas a essa base de dados completa sem ter que carregá-la totalmente na memória.

Para tanto é necessário criar um objeto no R que indica onde a base de dados está. No nosso caso vamos criar dois objetos, um para a base composta por um arquivo parquet correspondente a cada arquivo txt de vínculos original e outra, particionada, é composta por múltiplos subdiretórios e arquivos para cada arquivo txt lido.

# Parquet dataset sem particionamento
RAIS2021 <- arrow::open_dataset("E:/Data/RAIS/2021/parquet/completa/", 
                         format = "parquet" )

# Parquet dataset com particionamento
RAIS2021_part <- arrow::open_dataset("E:/Data/RAIS/2021/parquet/particionada/", 
                              format = "parquet",
                              partitioning = arrow::hive_partition() )
                              # partitioning = c("UF", "cnae20"))

Note que as bases não são carregadas na memória e esses objetos apenas “apontam” para os respectivos diretórios.

Como primeiro exercício vamos tabular o número de vínculos empregatícios em atividades de impressão (CNAEs 1811-3, 1812-1, e 1813-0), ativos em 31 de dezembro de 2021, por UF em cada uma dessas bases. Isso serve de ilustração do ganho de desempenho devido ao particionamento.

RAIS2021 %>% 
  # Vínculo ativo em 31/12 e exclui os estatutários
  filter( va_3112 == 1 & 
            (! tipo_vinc %in% c(30,31,35)) &
            cnae20 %in% c(18113, 18121, 18130) ) %>%
  select(UF) %>%
  group_by(UF) %>% 
  count() %>% 
  collect() %>% 
  system.time()
##   usuário   sistema decorrido 
##      1.67      0.08      0.84
RAIS2021_part %>% 
  # Vínculo ativo em 31/12 e exclui os estatutários
  filter( va_3112 == 1 & 
            (! tipo_vinc %in% c(30,31,35)) &
            cnae20 %in% c(18113, 18121, 18130) ) %>%
  select(UF) %>%
  group_by(UF) %>% 
  count() %>% 
  collect() %>% 
  system.time()
##   usuário   sistema decorrido 
##      0.25      0.01      0.17

No exemplo acima fica claro que uma query relacionada as variáveis de particionamento é mais rápida com a base particionada. Se queries semelhantes forem feitas múltiplas vezes o tempo inicial de se salvar a base será mais que compensado.

Como um segundo exercício vamos replicar um cálculo simples apresentado em Stivali (2023). Esse texto afirma (p. 24) que as ocupações de auxiliar de escritório e de assistente administrativo correspondiam a 61,8% dos aprendizes em 2021. Note que nenhuma das variáveis envolvidas nesse cálculo está relacionada ao particionamento implementado.

## Consulta a base sem particionamento
tictoc::tic()
prop1 <- RAIS2021 %>% 
  filter(va_3112 == 1, 
         cbo_2002 %in% CBO_SIT$CBO, 
         tipo_vinc == 55) %>%
  group_by(cbo_2002) %>%
  summarise(total_oc = n()) %>%
  arrange(desc(total_oc)) %>%
  collect()
tictoc::toc()
## 0.92 sec elapsed
prop1 <- prop1 %>%
  mutate(freq = total_oc / sum(total_oc))

print(prop1[c(1,2),c(1,3)])
## # A tibble: 2 × 2
##   cbo_2002  freq
##      <int> <dbl>
## 1   411005 0.417
## 2   411010 0.202
## Consulta a base com particionamento
tictoc::tic()
prop1 <- RAIS2021_part %>% 
  filter(va_3112 == 1, 
         cbo_2002 %in% CBO_SIT$CBO, 
         tipo_vinc == 55) %>%
  group_by(cbo_2002) %>%
  summarise(total_oc = n()) %>%
  arrange(desc(total_oc)) %>%
  collect() 
tictoc::toc()
## 4.78 sec elapsed
prop1 <- prop1 %>%
  mutate(freq = total_oc / sum(total_oc))

print(prop1[c(1,2),c(1,3)])
## # A tibble: 2 × 2
##   cbo_2002  freq
##      <int> <dbl>
## 1   411005 0.417
## 2   411010 0.202

Em ambos os casos obtemos o mesmo número da publicação (felizmente!), mas dessa vez a consulta a base particionada demorou muito mais!

Isso se deve pelo particionamento resultar em muitos arquivos muito pequenos, como já apontado acima. Isso faz com que muitas operações de leitura tenham que ser feitas para se localizar informações das variáveis não incluidas no particionamento. Isto é, o particionamento “excessivo” funciona para acelerar as consultas que dependem das variáveis de particionamento, mas tornam as outras consultas mais lentas. Há alguns números “tamanhos ótimos” dos arquivos parquet na internet, mas nenhum desse me pareceu bem fundamentado.

No caso da RAIS particionar por CNAE certamente é uma má ideia, mas por UF e vínculo ativo em dezembro do ano de referência, por exemplo, não resultarão em muitos arquivos e acelerarão as consultas.

6 Comentários finais

Essa publicação tentou ser ao mesmo tempo um tutorial para o uso dos microdados da RAIS em “ambientes computacionais modestos” e também para o uso dos pacotes vroom e arrow com qualquer base grande.

Como ilustrado na última seção, o particionamento pode melhorar o desempenho das consultas a base, mas se feito de forma equivocada ele pode deixar uma consulta específica muito eficiente piorando qualquer outra que se deseje fazer.

Referências bibliográficas

Abadi, Daniel. 2017. DBMS Musings: Apache Arrow Vs. Parquet and ORC: Do We Really Need a Third Apache Project for Columnar Data Representation?” DBMS Musings. https://dbmsmusings.blogspot.com/2017/10/apache-arrow-vs-parquet-and-orc-do-we.html.
Almeida, Mariana Eugenio, Tamille Sales Dias, Rosângela Jardim de Farias, Augusto Veras Soares Martinez Albuquerque, Sergio Luiz Rodrigues Torres, and Luis Felipe Batista de Oliveira. 2020. “Substituição Da Captação Dos Dados Do CAGED Pelo e-Social.” Boletim Mercado de Trabalho - Conjuntura e Análise, no. 69 (July): 81–94.
BRASIL. 2020. “Substituição Da Captação Dos Dados Do Caged Pelo eSocial.” Brasília, DF: Ministério da Economia; Secretaria Especial de Previdência e Trabalho; Secretaria de Trabalho. http://pdet.mte.gov.br/images/Novo_CAGED/Nota%20t%C3%A9cnica%20substitui%C3%A7%C3%A3o%20CAGED_26_05.pdf.
Floratou, Avrilia. 2018. “Columnar Storage Formats.” In Encyclopedia of Big Data Technologies, edited by Sherif Sakr and Albert Zomaya, 1–6. Cham: Springer International Publishing. https://doi.org/10.1007/978-3-319-63962-8_248-1.
Hester, Jim, Hadley Wickham, Jennifer Bryan, Shelby Bearrows, https://github com/mandreyel/ (mio library), Jukka Jylänki (grisu3 implementation), Mikkel Jørgensen (grisu3 implementation), Posit Software, and PBC. 2023. “Vroom: Read and Write Rectangular Text Data Quickly.” https://cran.r-project.org/web/packages/vroom/index.html.
MTP, BRASIL -. 2022. “Relação Anual de Informações Sociais, Ano-Base 2021.” Nota {Técnica}. Brasília, DF: Ministério do Trabalho e Previdência - Secretaria de Trabalho. http://pdet.mte.gov.br/images/RAIS/2021/Nota_Técnica_RAIS_2021.pdf.
Richardson, Neal, Ian Cook, Nic Crane, Dewey Dunnington, Romain François, Jonathan Keane, Dragoș Moldovan-Grünfeld, et al. 2023. “Arrow: Integration to ’Apache’ ’Arrow’.” https://cran.r-project.org/web/packages/arrow/index.html.
Stivali, Matheus. 2023. “Estrutura Ocupacional Da Aprendizagem: Investimento Em Capital Humano Ou Custo Trabalhista?” Radar: Tecnologia, Produção e Comércio Exterior 72. https://repositorio.ipea.gov.br/bitstream/11058/11903/1/Radar_n72_Art4_Estrutura_ocupacional.pdf.
Wickham, Hadley, Mine Çetinkaya-rundel, and Garrett Grolemund. 2023. R for Data Science: Import, Tidy, Transform, Visualize, and Model Data. 2nd edition. Beijing Boston Farnham Sebastopol Tokyo: O’Reilly Media.

  1. Substituído pelo Decreto nº 10.854, de 10 de dezembro de 2021)↩︎

  2. Definidos, respectivamente, na Lei Complementar nº 7, de 7 de setembro de 1970, e na Lei Complementar nº 8, de 3 de dezembro de 1970↩︎

  3. Lei nº 8.213, de 19 de dezembro de 1991.↩︎

  4. Lei nº 10.097, de 19 de dezembro de 2000.↩︎

  5. Sistema de Escrituração Digital das Obrigações Fiscais, Previdenciárias e Trabalhistas, instituído pelo Decreto nº 8.373, de 11 de dezembro de 2014.↩︎

  6. Ver Almeida et al. (2020), BRASIL (2020), e MTP (2022). Grande número de prefeituras ainda não aderiu ao eSocial embora elas já o devessem ter feito. Em 2023 elas ainda usaram o GDRAIS para informar os dados referentes a 2022, a princípio isso não deve ocorrer em 2024.↩︎

  7. A RAIS estatística diverge da “RAIS bruta” usada pelos sistemas do governo por ter passado por um processo de crítica e limpeza dos dados além de não conter todas as variáveis exigidas pela legislação.↩︎

  8. Apenas alguns contratos de trabalho formais não eram obrigados a informar a RAIS pelo decreto 76.900/1978, mas todos são obrigados a informar o eSocial. Por razões de consistência com a série histórica a divulgação manteve os mesmos tipos de contrato e empregadores considerados anteriormente, mas futuramente é possível que uma versão ainda mais abrangente seja divulgada. Da mesma forma, a RAIS poderá ter uma frequência maior que a anual, uma vez que os dados do eSocial são informados mensalmente.↩︎

  9. Formalização de um acordo de cooperação técnica entre a instituição e o MTE e assinatura de termo de compromisso de sigilo por parte dos analistas que terão acesso aos dados.↩︎

  10. Sindicatos e associações patronais, além de universidades e outras instituições de pesquisa↩︎

  11. Até 2017 os dados eram disponibilizados em 28 arquivos, um arquivo para os estabelecimentos e um arquivo de vínculos empregatícios para cada unidade da federação.↩︎

  12. Uma empresa pode ter mais de um estabelecimento, e os dados da RAIS devem ser informados por estabelecimento. Com os dados identificados, através da codificação do CNPJ, é possível identificar os diferentes estabelecimentos de uma mesma empresa. De forma semelhante, um mesmo trabalhador pode ter mais de um emprego, com empregadores diferentes. Nesse caso um mesmo CPF apareceria duas vezes ou mais na RAIS de dado ano. Como não estamos trabalhando com os dados identificados aqui esse tipo de análise não é possível e não retornaremos a esse assunto.↩︎

  13. Temos os dois exemplos nos blocos de código acima, capturamos o nome das variáveis no próprio arquivo no caso da RAIS e incluímos eles no código no caso da Guia de Contribuição Sindical.↩︎

  14. O SAS utiliza a organização row-major order, enquanto MatLab, Octave, Julia, R utilizam column-major order. https://en.wikipedia.org/wiki/Row-_and_column-major_order↩︎