Abstract
Esta publicação mostra como ler os microdados de uma base de dados bastante grande de forma rápida, como checar a consistência dos dados lidos, e como salvar esses dados em um formato que possibilite sua análise sem que toda base seja totalmente carregada na memória RAM do computador. Como exemplo utilizamos os microdados da Relação Anual de Informações Sociais (RAIS) disponibilizados pelo Ministério do Trabalho e Emprego (MTE) - dados públicos, não identificados.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.
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).
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:
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. \(~\)
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. \(~\)
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.
vroomAs 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.
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().
## [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"
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
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.
## # 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.
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:
csvDependendo 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.
## [1] 3977464
## attr(,"lastLineHasNewline")
## [1] TRUE
## 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óriaOs 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.
arrowUma 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.
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).
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 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…
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.
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.
\(~\)
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.
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.
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).
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.
So hurry up and wait
But what’s worth waiting for?
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 |
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
## # 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
## # 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.
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.
Substituído pelo Decreto nº 10.854, de 10 de dezembro de 2021)↩︎
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↩︎
Lei nº 8.213, de 19 de dezembro de 1991.↩︎
Lei nº 10.097, de 19 de dezembro de 2000.↩︎
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.↩︎
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.↩︎
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.↩︎
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.↩︎
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.↩︎
Sindicatos e associações patronais, além de universidades e outras instituições de pesquisa↩︎
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.↩︎
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.↩︎
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.↩︎
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↩︎