--- title: "Full Pipeline with DuckDB" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Full Pipeline with DuckDB} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ``` This guide demonstrates the full `datacaged` pipeline: from checking the HuggingFace repository to analytical queries in DuckDB with `dplyr` and SQL. ## 1. Check availability before downloading Antes de iniciar qualquer download, check that the HuggingFace repository is accessible and which competencies are available. ```{r status} library(datacaged) # Verifica se o repositório HuggingFace está online e mede a latência caged_status() #> OK Repositório HuggingFace online #> ℹ Latência: 312 ms #> ℹ Dataset: https://huggingface.co/datasets/alexsandroprado/caged ``` ```{r ftp-files} # List the 12 most recent competencies available in Novo CAGED caged_hf_files() # Lista todas as competências do Legacy CAGED caged_hf_files(type = "antigo", n = Inf) # Lista competências do CAGED Adjustments caged_hf_files(type = "ajustes", n = 24) # Capture result for programmatic use disponivel <- caged_hf_files(n = 1, verbose = FALSE) cat("Competência mais recente:", disponivel$competencia, "\n") ``` ## 2. Download microdata The `caged_download()` function manages local cache automatically: already-downloaded files are not re-downloaded. It returns a `manifest` — a data.frame with the status of each file. ```{r download-novo} # Baixa Novo CAGED de 2023 inteiro (MOV + FOR + EXC por competência) manifest <- caged_download( years = 2023, months = seq_len(12L), destdir = "~/dados/caged_cache" # omitir para usar cache padrão ) # Inspect o manifest dplyr::count(manifest, type, status) #> # A tibble: 4 × 3 #> type status n #> #> 1 EXC baixado 12 #> 2 FOR baixado 12 #> 3 MOV baixado 12 #> 4 MOV cache 0 # Files available for processing manifest |> dplyr::filter(status %in% c("baixado", "cache")) |> dplyr::select(type, competencia, nome_arquivo, arquivo) ``` ```{r download-antigo} # Baixa Legacy CAGED — arquivo nacional único por competência manifest_ant <- caged_download(years = 2018:2019, months = seq_len(12L)) # Download adjustments for the same period too caged_adjustments_load( years = 2018:2019, months = seq_len(12L), db_path = "caged_historico.duckdb" ) ``` ## 3. Parse files `caged_parse()` extracts the `.7z`, reads the `.txt` and returns a normalised tibble. ```{r parse-individual} # Parse a single file df_jan <- caged_parse("~/dados/caged_cache/NOVO_CAGED/2023/202301/CAGEDMOV202301.7z") # Inspect dplyr::glimpse(df_jan) #> Rows: 3,665,155 #> Columns: 29 #> $ competenciamov 202301, 202301, ... #> $ uf 11, 11, ... #> $ municipio 1100015, ... #> $ saldomovimentacao 1, -1, ... #> $ salario 1412.00, 2800.50, ... # Parse multiple files of the same type (devem ser todos MOV, ou todos FOR, etc.) arquivos_mov <- list.files( "~/dados/caged_cache/NOVO_CAGED/2023", pattern = "CAGEDMOV", recursive = TRUE, full.names = TRUE ) df_mov_2023 <- caged_parse_batch(arquivos_mov) ``` ## 4. Write to DuckDB ```{r gravar} # Write the parsed tibble to the database caged_to_duckdb(df_mov_2023, db_path = "caged.duckdb") # Re-runs are safe: competencies already in the database are skipped caged_to_duckdb(df_mov_2023, db_path = "caged.duckdb") #> ℹ 12 competências já no banco — pulando. # To overwrite (e.g. after correcting data): caged_to_duckdb(df_mov_2023, db_path = "caged.duckdb", overwrite_competencies = TRUE) # Write to a specific table without auto-detection caged_to_duckdb(df_jan, db_path = "caged.duckdb", table = "caged_mov") ``` ## 5. Full pipeline in a single command For the most common case — download everything and write to the database — use `caged_load()`: ```{r pipeline-completo} library(datacaged) # ── Novo CAGED 2022-2023 ───────────────────────────────────────────────────── caged_load( years = 2022:2023, months = seq_len(12L), db_path = "caged.duckdb" ) # ── Legacy CAGED 2015-2019 ──────────────────────────────────────────────────── caged_load( years = 2015:2019, months = seq_len(12L), db_path = "caged.duckdb" ) # ── CAGED Adjustments 2015-2019 ─────────────────────────────────────────────────── caged_adjustments_load( years = 2015:2019, months = seq_len(12L), db_path = "caged.duckdb" ) # ── Check the resulting database ───────────────────────────────────────────── caged_info("caged.duckdb") #> ── caged.duckdb ────────────────────────────────────────────────────────── #> Tamanho do arquivo: 4.2 GB #> ── Tabelas ─────────────────────────────────────────────────────────────── #> * "caged_mov" Registros: 43,981,860 Competências: 202201 – 202312 #> * "caged_for" Registros: 1,093,176 Competências: 202201 – 202312 #> * "caged_exc" Registros: 94,800 Competências: 202201 – 202312 #> * "caged_antigo" Registros: 28,450,000 Competências: 201501 – 201912 #> * "caged_ajustes" Registros: 342,000 Competências: 201501 – 201912 ``` ## 6. Queries with dplyr ```{r conexao} library(dplyr) con <- caged_connect("caged.duckdb") ``` ```{r consultas-dplyr} # ── Available tables ───────────────────────────────────────────────────────── DBI::dbListTables(con) # ── Lazy reference (does not load into memory) ─────────────────────────────── mov <- tbl(con, "caged_mov") # ── Monthly balance ─────────────────────────────────────────────────────────── mov |> group_by(competenciamov) |> summarise( admissoes = sum(saldomovimentacao == 1, na.rm = TRUE), desligamentos = sum(saldomovimentacao == -1, na.rm = TRUE), saldo = sum(saldomovimentacao, na.rm = TRUE) ) |> arrange(competenciamov) |> collect() # ── Balance by state and sector ────────────────────────────────────────────── mov |> filter(competenciamov >= 202301) |> group_by(uf, secao) |> summarise(saldo = sum(saldomovimentacao, na.rm = TRUE)) |> arrange(desc(saldo)) |> collect() # ── Average wage by education level and gender ─────────────────────────────── mov |> filter(!is.na(salario), salario > 0) |> group_by(graudeinstrucao, sexo) |> summarise( salario_medio = mean(salario, na.rm = TRUE), n = n() ) |> collect() # ── Top 10 municipalities by hires ─────────────────────────────────────────── mov |> filter(saldomovimentacao == 1) |> group_by(municipio) |> summarise(admissoes = n()) |> slice_max(admissoes, n = 10) |> collect() ``` ## 7. Direct SQL queries ```{r consultas-sql} # Net balance by CNAE section and month DBI::dbGetQuery(con, " SELECT competenciamov AS competencia, secao, SUM(CASE WHEN saldomovimentacao = 1 THEN 1 ELSE 0 END) AS admissoes, SUM(CASE WHEN saldomovimentacao = -1 THEN 1 ELSE 0 END) AS desligamentos, SUM(saldomovimentacao) AS saldo, ROUND(AVG(salario), 2) AS salario_medio FROM caged_mov WHERE competenciamov >= 202301 GROUP BY competenciamov, secao ORDER BY competenciamov, saldo DESC ") # Age distribution by range and movement type DBI::dbGetQuery(con, " SELECT CASE WHEN idade < 25 THEN 'Até 24 anos' WHEN idade BETWEEN 25 AND 34 THEN '25-34 anos' WHEN idade BETWEEN 35 AND 44 THEN '35-44 anos' WHEN idade BETWEEN 45 AND 54 THEN '45-54 anos' ELSE '55 anos ou mais' END AS faixa_etaria, SUM(CASE WHEN saldomovimentacao = 1 THEN 1 ELSE 0 END) AS admissoes, SUM(CASE WHEN saldomovimentacao = -1 THEN 1 ELSE 0 END) AS desligamentos FROM caged_mov GROUP BY faixa_etaria ORDER BY faixa_etaria ") ``` ## 8. Historical series: joining Novo CAGED and legacy CAGED To build long series that cross the 2020 break point, normalise columns before stacking. ```{r serie-historica} # Novo CAGED — colunas a normalizar novo <- tbl(con, "caged_mov") |> select( competencia = competenciamov, uf, saldo = saldomovimentacao, salario, sexo, idade, escolaridade = graudeinstrucao ) |> mutate(serie = "novo") # Legacy CAGED — já tem coluna competencia e saldomovimentacao antigo <- tbl(con, "caged_antigo") |> select( competencia, uf, saldo = saldomovimentacao, salario, sexo, idade, escolaridade ) |> mutate(serie = "antigo") # Empilha e agrega serie_hist <- bind_rows( novo |> group_by(competencia, serie) |> summarise(saldo = sum(saldo, na.rm = TRUE)), antigo |> group_by(competencia, serie) |> summarise(saldo = sum(saldo, na.rm = TRUE)) ) |> collect() |> arrange(competencia) ``` ## 9. Export results ```{r exportar} library(dplyr) # Export to CSV resultado <- tbl(con, "caged_mov") |> group_by(competenciamov, uf) |> summarise(saldo = sum(saldomovimentacao, na.rm = TRUE)) |> collect() readr::write_csv(resultado, "saldo_uf_mensal.csv") # Export all database tables to Parquet (native DuckDB, much faster) caged_to_parquet("caged.duckdb", output_dir = "~/exports") # Export only caged_mov partitioned by state caged_to_parquet( "caged.duckdb", output_dir = "~/exports", tables = "caged_mov", partition_by = "uf" ) # Close connection DBI::dbDisconnect(con, shutdown = TRUE) ``` ## 10. Incremental update Use `caged_update()` to keep the database up to date without re-downloading everything. The function queries the database to find the maximum competency and downloads only what is more recent on HuggingFace. ```{r update} # Update Novo CAGED with months not yet in the database caged_update(db_path = "caged.duckdb") # Update all series caged_update( db_path = "caged.duckdb", series = c("novo", "antigo", "ajustes") ) ``` ## 11. Best practices - **Always close the connection** with `DBI::dbDisconnect(con, shutdown = TRUE)` when done. - **Use `tbl()` before `collect()`**: DuckDB executes as much as possible before bringing data into R. - **`select()` early**: bring only the necessary columns to save memory. - **Safe re-runs**: `caged_load()` detects already-written competencies and skips them automatically. - **Local cache**: `.7z` files are stored in `tools::R_user_dir("datacaged", "cache")` and reused between sessions. - **Large volumes**: the pipeline processes one competency at a time to avoid running out of RAM.