The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Casos de Uso: Análise do Mercado de Trabalho

library(datacaged)
library(dplyr)
library(ggplot2)
library(lubridate)
library(scales)

Esta vinheta apresenta análises prontas do mercado de trabalho formal usando o datacaged. Cada caso de uso é independente — você pode executar apenas o trecho de interesse.

Dados necessários (executar uma vez):

# Novo CAGED 2023
caged_load(years = 2023, months = seq_len(12L), db_path = "caged.duckdb")

# Para casos com série histórica, adicionar:
caged_load(years = 2020:2022, months = seq_len(12L), db_path = "caged.duckdb")
caged_load(years = 2015:2019, months = seq_len(12L), db_path = "caged.duckdb")
caged_adjustments_load(years = 2015:2019, months = seq_len(12L), db_path = "caged.duckdb")
con <- caged_connect("caged.duckdb")

Caso 1 — Saldo de empregos: admissões vs desligamentos

Análise do fluxo mensal de entrada e saída no mercado de trabalho formal.

fluxo <- tbl(con, "caged_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)
  ) |>
  collect() |>
  mutate(
    data = ym(as.character(competenciamov))
  ) |>
  arrange(data)
ggplot(fluxo, aes(x = data)) +
  geom_col(aes(y = admissoes),      fill = "#2196F3", alpha = 0.8) +
  geom_col(aes(y = -desligamentos), fill = "#F44336", alpha = 0.8) +
  geom_line(aes(y = saldo), color = "#212121", linewidth = 1.2) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "grey40") +
  scale_y_continuous(labels = label_number(scale = 1e-6, suffix = "M")) +
  scale_x_date(date_breaks = "2 months", date_labels = "%b/%y") +
  labs(
    title    = "Fluxo de Emprego Formal — Novo CAGED 2023",
    subtitle = "Barras azuis = admissões | barras vermelhas = desligamentos | linha = saldo",
    x        = NULL,
    y        = "Movimentações",
    caption  = "Fonte: MTE/CAGED via datacaged"
  ) +
  theme_minimal(base_size = 12) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Caso 2 — Saldo por setor econômico (CNAE)

Identifica quais setores geraram e destruíram mais empregos formais no período.

# Dicionário de seções da CNAE 2.0
cnae_secoes <- tibble::tribble(
  ~secao, ~descricao,
  "A",  "Agricultura",
  "B",  "Indústrias Extrativas",
  "C",  "Indústria de Transformação",
  "D",  "Eletricidade e Gás",
  "E",  "Água e Saneamento",
  "F",  "Construção",
  "G",  "Comércio",
  "H",  "Transporte",
  "I",  "Alojamento e Alimentação",
  "J",  "Informação e Comunicação",
  "K",  "Atividades Financeiras",
  "L",  "Atividades Imobiliárias",
  "M",  "Profissionais e Técnicas",
  "N",  "Atividades Administrativas",
  "O",  "Adm. Pública",
  "P",  "Educação",
  "Q",  "Saúde",
  "R",  "Artes e Cultura",
  "S",  "Outras Atividades",
  "T",  "Serviços Domésticos",
  "U",  "Organismos Internacionais"
)

setor <- tbl(con, "caged_mov") |>
  filter(!is.na(secao)) |>
  group_by(secao) |>
  summarise(saldo = sum(saldomovimentacao, na.rm = TRUE)) |>
  collect() |>
  left_join(cnae_secoes, by = "secao") |>
  mutate(
    descricao = coalesce(descricao, paste("Setor", secao)),
    cor       = if_else(saldo >= 0, "#2196F3", "#F44336")
  ) |>
  arrange(saldo)
ggplot(setor, aes(x = saldo, y = reorder(descricao, saldo), fill = cor)) +
  geom_col(show.legend = FALSE) +
  geom_vline(xintercept = 0, color = "grey30") +
  scale_fill_identity() +
  scale_x_continuous(labels = label_number(scale = 1e-3, suffix = "k")) +
  labs(
    title   = "Saldo de Empregos por Setor Econômico — 2023",
    x       = "Saldo (mil vínculos)",
    y       = NULL,
    caption = "Fonte: MTE/CAGED via datacaged"
  ) +
  theme_minimal(base_size = 11) +
  theme(panel.grid.major.y = element_blank())

Caso 3 — Mapa de calor: saldo por UF e mês

Visualização de qual UF e em qual mês o emprego formal foi mais aquecido.

# Dicionário de UFs (código IBGE -> sigla)
data("uf_codigos")

uf_mensal <- tbl(con, "caged_mov") |>
  group_by(uf, competenciamov) |>
  summarise(saldo = sum(saldomovimentacao, na.rm = TRUE)) |>
  collect() |>
  left_join(uf_codigos, by = c("uf" = "codigo")) |>
  filter(!is.na(sigla)) |>
  mutate(
    data = ym(as.character(competenciamov)),
    mes  = format(data, "%b")
  )
ggplot(uf_mensal, aes(x = mes, y = reorder(sigla, saldo), fill = saldo)) +
  geom_tile(color = "white", linewidth = 0.3) +
  scale_fill_gradient2(
    low      = "#F44336",
    mid      = "white",
    high     = "#2196F3",
    midpoint = 0,
    labels   = label_number(scale = 1e-3, suffix = "k")
  ) +
  labs(
    title   = "Saldo de Empregos por UF e Mês — 2023",
    x       = NULL,
    y       = NULL,
    fill    = "Saldo",
    caption = "Fonte: MTE/CAGED via datacaged"
  ) +
  theme_minimal(base_size = 10) +
  theme(
    axis.text.x    = element_text(angle = 45, hjust = 1),
    legend.position = "right"
  )

Caso 4 — Perfil do trabalhador admitido

Caracterização demográfica dos novos vínculos formais por sexo, escolaridade e raça/cor.

# Dicionários de categorias
grau_instrucao <- tibble::tribble(
  ~graudeinstrucao, ~escolaridade,
  1L, "Analfabeto",
  2L, "Fundamental incompleto",
  3L, "Fundamental completo",
  4L, "Médio incompleto",
  5L, "Médio completo",
  6L, "Superior incompleto",
  7L, "Superior completo",
  8L, "Mestrado",
  9L, "Doutorado"
)

raca_dic <- tibble::tribble(
  ~racacor, ~raca,
  1L, "Indígena",
  2L, "Branca",
  4L, "Preta",
  6L, "Amarela",
  8L, "Parda",
  9L, "Não informada"
)

# Admissões por escolaridade e sexo
perfil_escol <- tbl(con, "caged_mov") |>
  filter(saldomovimentacao == 1, !is.na(graudeinstrucao)) |>
  group_by(graudeinstrucao, sexo) |>
  summarise(n = n()) |>
  collect() |>
  left_join(grau_instrucao, by = "graudeinstrucao") |>
  mutate(
    sexo_label = if_else(sexo == 1, "Masculino", "Feminino"),
    escolaridade = factor(escolaridade, levels = grau_instrucao$escolaridade)
  )
ggplot(perfil_escol, aes(x = escolaridade, y = n, fill = sexo_label)) +
  geom_col(position = "dodge") +
  scale_y_continuous(labels = label_number(scale = 1e-3, suffix = "k")) +
  scale_fill_manual(values = c("Masculino" = "#1565C0", "Feminino" = "#AD1457")) +
  labs(
    title   = "Admissões por Escolaridade e Sexo — 2023",
    x       = NULL,
    y       = "Admissões (mil)",
    fill    = NULL,
    caption = "Fonte: MTE/CAGED via datacaged"
  ) +
  theme_minimal(base_size = 11) +
  theme(
    axis.text.x    = element_text(angle = 40, hjust = 1),
    legend.position = "top"
  )
# Admissões por raça/cor
perfil_raca <- tbl(con, "caged_mov") |>
  filter(saldomovimentacao == 1, !is.na(racacor)) |>
  group_by(racacor) |>
  summarise(n = n()) |>
  collect() |>
  left_join(raca_dic, by = "racacor") |>
  mutate(pct = n / sum(n))
ggplot(perfil_raca, aes(x = reorder(raca, n), y = n, fill = raca)) +
  geom_col(show.legend = FALSE) +
  geom_text(aes(label = percent(pct, accuracy = 0.1)), hjust = -0.1, size = 3.5) +
  coord_flip() +
  scale_y_continuous(
    labels = label_number(scale = 1e-6, suffix = "M"),
    expand = expansion(mult = c(0, 0.15))
  ) +
  scale_fill_brewer(palette = "Set2") +
  labs(
    title   = "Distribuição de Admissões por Raça/Cor — 2023",
    x       = NULL,
    y       = "Admissões",
    caption = "Fonte: MTE/CAGED via datacaged"
  ) +
  theme_minimal(base_size = 11)

Caso 5 — Distribuição salarial das admissões

Análise dos salários declarados nas novas contratações.

salarios <- tbl(con, "caged_mov") |>
  filter(
    saldomovimentacao == 1,
    !is.na(salario),
    salario > 0,
    salario < 30000   # remove outliers extremos
  ) |>
  select(salario, sexo, graudeinstrucao) |>
  collect() |>
  mutate(
    sexo_label = if_else(sexo == 1, "Masculino", "Feminino"),
    log_salario = log10(salario)
  )

# Percentis salariais
salarios |>
  group_by(sexo_label) |>
  summarise(
    p10    = quantile(salario, 0.10),
    mediana = median(salario),
    media   = mean(salario),
    p90    = quantile(salario, 0.90)
  )
ggplot(salarios, aes(x = salario, fill = sexo_label)) +
  geom_histogram(
    aes(y = after_stat(density)),
    bins     = 60,
    alpha    = 0.6,
    position = "identity"
  ) +
  geom_vline(
    data = salarios |>
      group_by(sexo_label) |>
      summarise(med = median(salario)),
    aes(xintercept = med, color = sexo_label),
    linewidth = 1.2, linetype = "dashed"
  ) +
  scale_x_continuous(
    labels = label_dollar(prefix = "R$", big.mark = ".", decimal.mark = ","),
    limits = c(0, 10000)
  ) +
  scale_fill_manual(values  = c("Masculino" = "#1565C0", "Feminino" = "#AD1457")) +
  scale_color_manual(values = c("Masculino" = "#0D47A1", "Feminino" = "#880E4F")) +
  labs(
    title    = "Distribuição Salarial das Admissões por Sexo — 2023",
    subtitle = "Linhas tracejadas indicam a mediana salarial de cada grupo",
    x        = "Salário de admissão (R$)",
    y        = "Densidade",
    fill     = NULL,
    color    = NULL,
    caption  = "Fonte: MTE/CAGED via datacaged"
  ) +
  theme_minimal(base_size = 11) +
  theme(legend.position = "top")

Caso 6 — Evolução do emprego formal: série 2015-2023

Construção de uma série histórica longa unindo CAGED antigo (até 2019) e Novo CAGED (2020 em diante).

# CAGED antigo: competencia + saldomovimentacao
antigo <- tbl(con, "caged_antigo") |>
  filter(!is.na(competencia)) |>
  group_by(competencia) |>
  summarise(saldo = sum(saldomovimentacao, na.rm = TRUE)) |>
  collect() |>
  mutate(serie = "CAGED Antigo")

# Ajustes: somados ao antigo para corrigir a série
ajustes <- tbl(con, "caged_ajustes") |>
  filter(!is.na(competencia)) |>
  group_by(competencia) |>
  summarise(saldo_ajuste = sum(saldomovimentacao, na.rm = TRUE)) |>
  collect()

antigo_corrigido <- left_join(antigo, ajustes, by = "competencia") |>
  mutate(
    saldo = saldo + coalesce(saldo_ajuste, 0L),
    serie = "CAGED Antigo (corrigido)"
  ) |>
  select(competencia, saldo, serie)

# Novo CAGED: competenciamov + saldomovimentacao
novo <- tbl(con, "caged_mov") |>
  group_by(competencia = competenciamov) |>
  summarise(saldo = sum(saldomovimentacao, na.rm = TRUE)) |>
  collect() |>
  mutate(serie = "Novo CAGED")

# União das séries
serie_longa <- bind_rows(antigo_corrigido, novo) |>
  filter(!is.na(competencia)) |>
  mutate(data = ym(as.character(competencia))) |>
  filter(data >= ym("201501")) |>
  arrange(data)
ggplot(serie_longa, aes(x = data, y = saldo, color = serie)) +
  geom_line(linewidth = 1) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "grey50") +
  annotate("rect",
    xmin = ym("202001"), xmax = ym("202012"),
    ymin = -Inf, ymax = Inf,
    alpha = 0.08, fill = "orange"
  ) +
  annotate("text",
    x = ym("202006"), y = Inf, vjust = 1.5,
    label = "Pandemia\nCOVID-19", size = 3, color = "darkorange"
  ) +
  scale_y_continuous(labels = label_number(scale = 1e-6, suffix = "M")) +
  scale_x_date(date_breaks = "1 year", date_labels = "%Y") +
  scale_color_manual(values = c(
    "CAGED Antigo (corrigido)" = "#795548",
    "Novo CAGED"               = "#1565C0"
  )) +
  labs(
    title    = "Saldo de Empregos Formais — Série Histórica 2015-2023",
    subtitle = "Antigo CAGED corrigido pelos Ajustes + Novo CAGED",
    x        = NULL,
    y        = "Saldo mensal",
    color    = NULL,
    caption  = "Fonte: MTE/CAGED via datacaged"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "top")

Caso 7 — Rotatividade por setor

Taxa de rotatividade (desligamentos / estoque estimado) por setor econômico.

# cnae_secoes também definido aqui para que o Caso 7 possa ser executado de forma independente
cnae_secoes <- tibble::tribble(
  ~secao, ~descricao,
  "A",  "Agricultura",       "B", "Indústrias Extrativas",
  "C",  "Ind. de Transformação", "D", "Eletricidade e Gás",
  "E",  "Água e Saneamento", "F", "Construção",
  "G",  "Comércio",          "H", "Transporte",
  "I",  "Alojamento e Alimentação", "J", "Informação e Comunicação",
  "K",  "Atividades Financeiras",   "L", "Atividades Imobiliárias",
  "M",  "Profissionais e Técnicas", "N", "Atividades Administrativas",
  "O",  "Adm. Pública",      "P", "Educação",
  "Q",  "Saúde",             "R", "Artes e Cultura",
  "S",  "Outras Atividades", "T", "Serviços Domésticos",
  "U",  "Organismos Internacionais"
)

rotatividade <- tbl(con, "caged_mov") |>
  filter(!is.na(secao)) |>
  group_by(secao, competenciamov) |>
  summarise(
    admissoes     = sum(saldomovimentacao ==  1, na.rm = TRUE),
    desligamentos = sum(saldomovimentacao == -1, na.rm = TRUE)
  ) |>
  collect() |>
  left_join(cnae_secoes, by = "secao") |>
  mutate(descricao = coalesce(descricao, paste("Setor", secao))) |>
  group_by(descricao) |>
  summarise(
    total_admissoes     = sum(admissoes),
    total_desligamentos = sum(desligamentos),
    rotatividade_pct    = total_desligamentos / (total_admissoes + total_desligamentos)
  ) |>
  arrange(desc(rotatividade_pct))
ggplot(
  rotatividade |> filter(!is.na(descricao)),
  aes(x = total_admissoes, y = total_desligamentos,
      size = rotatividade_pct, color = rotatividade_pct,
      label = descricao)
) +
  geom_point(alpha = 0.7) +
  geom_abline(slope = 1, intercept = 0, linetype = "dashed", color = "grey50") +
  ggrepel::geom_text_repel(size = 3, max.overlaps = 12) +
  scale_x_continuous(labels = label_number(scale = 1e-6, suffix = "M")) +
  scale_y_continuous(labels = label_number(scale = 1e-6, suffix = "M")) +
  scale_size_continuous(range = c(2, 10), guide = "none") +
  scale_color_gradient(low = "#2196F3", high = "#F44336", labels = percent) +
  labs(
    title    = "Rotatividade por Setor Econômico — 2023",
    subtitle = "Pontos acima da diagonal = mais desligamentos que admissões",
    x        = "Admissões",
    y        = "Desligamentos",
    color    = "Rotatividade",
    caption  = "Fonte: MTE/CAGED via datacaged"
  ) +
  theme_minimal(base_size = 11)

Caso 8 — Emprego formal por porte de estabelecimento

Análise de qual porte de empresa concentra mais geração de empregos.

porte_dic <- tibble::tribble(
  ~tamestabjan, ~porte,
  0L,  "Não informado",
  1L,  "Até 4 vínculos",
  2L,  "5 a 9",
  3L,  "10 a 19",
  4L,  "20 a 49",
  5L,  "50 a 99",
  6L,  "100 a 249",
  7L,  "250 a 499",
  8L,  "500 a 999",
  9L,  "1000 ou mais"
)

porte <- tbl(con, "caged_mov") |>
  filter(!is.na(tamestabjan)) |>
  group_by(tamestabjan) |>
  summarise(
    saldo       = sum(saldomovimentacao, na.rm = TRUE),
    admissoes   = sum(saldomovimentacao ==  1, na.rm = TRUE),
    desligamentos = sum(saldomovimentacao == -1, na.rm = TRUE)
  ) |>
  collect() |>
  left_join(porte_dic, by = "tamestabjan") |>
  mutate(porte = factor(porte, levels = porte_dic$porte))
ggplot(porte |> filter(!is.na(porte)), aes(x = porte)) +
  geom_col(aes(y = admissoes),       fill = "#1565C0", alpha = 0.8) +
  geom_col(aes(y = -desligamentos),  fill = "#B71C1C", alpha = 0.8) +
  geom_point(aes(y = saldo), color = "#212121", size = 3) +
  geom_hline(yintercept = 0, color = "grey30") +
  scale_y_continuous(labels = label_number(scale = 1e-6, suffix = "M")) +
  labs(
    title    = "Movimentação por Porte de Estabelecimento — 2023",
    subtitle = "Azul = admissões | Vermelho = desligamentos | Ponto = saldo",
    x        = "Faixa de tamanho (vínculos em jan/ano)",
    y        = "Movimentações",
    caption  = "Fonte: MTE/CAGED via datacaged"
  ) +
  theme_minimal(base_size = 11) +
  theme(axis.text.x = element_text(angle = 40, hjust = 1))

Caso 9 — Emprego formal nas regiões brasileiras

Comparação do mercado de trabalho formal entre as 5 regiões do Brasil.

data("uf_codigos")

regiao_mensal <- tbl(con, "caged_mov") |>
  group_by(uf, competenciamov) |>
  summarise(saldo = sum(saldomovimentacao, na.rm = TRUE)) |>
  collect() |>
  left_join(uf_codigos, by = c("uf" = "codigo")) |>
  filter(!is.na(regiao)) |>
  group_by(regiao, competenciamov) |>
  summarise(saldo = sum(saldo), .groups = "drop") |>
  mutate(data = ym(as.character(competenciamov)))
ggplot(regiao_mensal, aes(x = data, y = saldo, color = regiao, fill = regiao)) +
  geom_line(linewidth = 1) +
  geom_area(alpha = 0.1) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "grey40") +
  scale_y_continuous(labels = label_number(scale = 1e-3, suffix = "k")) +
  scale_x_date(date_breaks = "2 months", date_labels = "%b/%y") +
  scale_color_brewer(palette = "Set1") +
  scale_fill_brewer(palette  = "Set1") +
  facet_wrap(~regiao, scales = "free_y", ncol = 2) +
  labs(
    title   = "Saldo de Empregos Formais por Região — 2023",
    x       = NULL,
    y       = "Saldo (mil vínculos)",
    color   = NULL,
    fill    = NULL,
    caption = "Fonte: MTE/CAGED via datacaged"
  ) +
  theme_minimal(base_size = 11) +
  theme(
    legend.position  = "none",
    axis.text.x      = element_text(angle = 45, hjust = 1),
    strip.text       = element_text(face = "bold")
  )

Caso 10 — Painel executivo: resumo do mercado de trabalho

Conjunto de indicadores-chave em um painel consolidado.

# KPIs gerais do ano
kpis <- tbl(con, "caged_mov") |>
  summarise(
    admissoes     = sum(saldomovimentacao ==  1, na.rm = TRUE),
    desligamentos = sum(saldomovimentacao == -1, na.rm = TRUE),
    saldo         = sum(saldomovimentacao,       na.rm = TRUE),
    salario_medio = round(mean(salario[salario > 0], na.rm = TRUE), 2),
    pct_mulheres  = round(mean(sexo == 3, na.rm = TRUE) * 100, 1),
    pct_superior  = round(mean(graudeinstrucao >= 7, na.rm = TRUE) * 100, 1)
  ) |>
  collect()

cat(glue::glue("
=== PAINEL MERCADO DE TRABALHO FORMAL 2023 ===

Admissões:       {scales::number(kpis$admissoes,     big.mark = '.')}
Desligamentos:   {scales::number(kpis$desligamentos, big.mark = '.')}
Saldo:           {scales::number(kpis$saldo,         big.mark = '.')}
Salário médio:   R$ {scales::number(kpis$salario_medio, big.mark = '.', decimal.mark = ',')}
Mulheres (%):    {kpis$pct_mulheres}%
Com nível sup.:  {kpis$pct_superior}%
"))
# Exportar qualquer resultado para Parquet via caged_to_parquet()
caged_to_parquet(
  "caged.duckdb",
  output_dir = "~/exports_caged"
)

# Ou particionado por UF para análises regionais
caged_to_parquet(
  "caged.duckdb",
  output_dir   = "~/exports_caged",
  tables       = "caged_mov",
  partition_by = "uf"
)
DBI::dbDisconnect(con, shutdown = TRUE)

Nota sobre os dados: os dados do CAGED estão sujeitos a revisões pelo MTE. Para análises que exigem precisão histórica, considere incorporar também os ajustes via caged_adjustments_load().

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.