Análise do Mercado de Trabalho Brasileiro

Desemprego, Composição Ocupacional, Exposição à IA e Prêmio Salarial da Educação

Author

José Victor, Giovanna Parada, Matheus Torres, Pedro Kawamoto, Maria Castellucci

Published

October 10, 2025

Introdução

Este relatório apresenta uma análise abrangente do mercado de trabalho brasileiro utilizando dados da PNAD Contínua (Pesquisa Nacional por Amostra de Domicílios Contínua) do IBGE.

Objetivos da Análise

  1. Taxa de Desocupação: Evolução temporal geral e por grupos demográficos
  2. Composição Ocupacional: Distribuição dos trabalhadores por grandes grupos ocupacionais (CBO)
  3. Exposição à IA: Percentual de trabalhadores expostos à Inteligência Artificial Generativa
  4. Prêmio Salarial da Educação: Retorno econômico de cada ano adicional de escolaridade

Configuração e Preparação dos Dados

Mostrar código
suppressPackageStartupMessages({
  library(fst)
  library(data.table)
  library(survey)
  library(future.apply)
  library(progressr)
  library(purrr)
  library(stringr)
  library(dplyr)
  library(tidyr)
  library(openxlsx)
  library(ggplot2)
  library(knitr)
  library(kableExtra)
  library(readxl)
})

# Configurações de performance
fst::threads_fst(max(1L, parallel::detectCores() - 1L))  # FST multithread
plan(multisession, workers = max(1L, parallel::detectCores() - 1L))  # Paralelização obrigatória

# Configurações
setwd("C:/Users/vjs20/Downloads/Projetos 3")
options(survey.lonely.psu = "adjust")

cat("🚀 Configuração paralela:", future::nbrOfWorkers(), "workers\n")
🚀 Configuração paralela: 15 workers
Mostrar código
# Verificar se o diretório FST existe
fst_dir <- file.path(getwd(), "pnadc_fst")
if (!dir.exists(fst_dir) || length(list.files(fst_dir, pattern = "\\.fst$")) == 0) {
  stop("❌ Diretório FST não encontrado ou vazio!\n", 
       "🔧 Execute primeiro: source('Converter_PNADC_para_FST.R')")
}
cat("✅ Diretório FST encontrado:", fst_dir, "\n")
✅ Diretório FST encontrado: C:/Users/vjs20/Downloads/Projetos 3/pnadc_fst 
Mostrar código
cat("🚀 Configuração paralela:", future::nbrOfWorkers(), "workers\n")
🚀 Configuração paralela: 15 workers

Leitura da Tabela de Exposição à IA

Mostrar código
# Carregar e otimizar dados de exposição à IA
df_exposicao_ia <- readxl::read_excel(
  path = "Códigos da OIT e quanto vai ser afetado.xlsx",
  sheet = 1,  # Sheet1
  col_types = c("text", "numeric", "text", "numeric", "numeric")
) %>%
  mutate(
    cbo4 = str_pad(as.character(`4-digit code`), width = 4, side = "left", pad = "0"),
    # Usar diretamente a coluna Exposure como faixa_exposicao (já categórica)
    faixa_exposicao = as.character(Exposure)
  ) %>%
  select(cbo4, Exposure, faixa_exposicao)

# Converter para data.table otimizado
setDT(df_exposicao_ia)
setkey(df_exposicao_ia, cbo4)  # Índice para lookups rápidos
cat("📊 Base IA otimizada:", nrow(df_exposicao_ia), "CBOs com índice\n")
📊 Base IA otimizada: 427 CBOs com índice
Mostrar código
cat("🎯 Categorias encontradas:", paste(unique(df_exposicao_ia$faixa_exposicao), collapse = ", "), "\n")
🎯 Categorias encontradas: Not Exposed, Minimal Exposure, Gradient 2, Gradient 1, Gradient 3, Gradient 4 
Mostrar código
out_dir <- file.path(getwd(), "output_pnadc_ocupacao")
dir.create(out_dir, showWarnings = FALSE, recursive = TRUE)

xls_out <- file.path(out_dir, sprintf(
  "PNADC_desemprego_ocupacao_%s.xlsx",
  format(Sys.time(), "%Y%m%d_%H%M")
))

Parse do Layout SAS

Este relatório utiliza arquivos FST (Fast Serialization of Data Frames) ao invés dos arquivos TXT originais.

Vantagens: - ⚡ 10-50x mais rápido que ler arquivos TXT de largura fixa - 💾 Arquivos menores (compressão automática) - 🎯 Tipos de dados preservados (não precisa re-harmonizar) - 🔧 Leitura paralela automática

Primeira execução: Execute Converter_PNADC_para_FST.R para converter os arquivos TXT → FST.

✓ Layout SAS (referência): 420 variáveis identificadas

Funções Utilitárias

Mostrar código
clean_cbo4 <- function(x) {
  x_chr <- as.character(x)
  x_chr <- stringr::str_replace_all(x_chr, "[^0-9]", "")
  x_chr <- ifelse(nchar(x_chr) >= 4, substr(x_chr, 1, 4), x_chr)
  x_chr <- stringr::str_pad(x_chr, 4, pad = "0")
  x_chr[!stringr::str_detect(x_chr, "[1-9]")] <- NA_character_
  x_chr
}

get_cbo_grande_grupo <- function(cbo4) {
  primeiro_digito <- substr(cbo4, 1, 1)
  case_when(
    primeiro_digito == "0" ~ "0-Militares",
    primeiro_digito == "1" ~ "1-Diretores/Gerentes",
    primeiro_digito == "2" ~ "2-Profissionais",
    primeiro_digito == "3" ~ "3-Técnicos",
    primeiro_digito == "4" ~ "4-Administrativos",
    primeiro_digito == "5" ~ "5-Serviços/Vendedores",
    primeiro_digito == "6" ~ "6-Agropecuários",
    primeiro_digito == "7" ~ "7-Produção Industrial",
    primeiro_digito == "8" ~ "8-Operadores",
    primeiro_digito == "9" ~ "9-Serviços Elementares",
    TRUE ~ NA_character_
  )
}

faixa_idade <- function(x) {
  cut(x, breaks = c(14, 24, 54, 150), include.lowest = TRUE,
      labels = c("15-24", "25-54", "55+"))
}

ord_periodo <- function(x) {
  if (length(x) == 0) return(factor())
  parts <- do.call(rbind, strsplit(x, "T", fixed = TRUE))
  o <- order(as.integer(parts[, 1]), as.integer(parts[, 2]))
  factor(x, levels = unique(x[o]), ordered = TRUE)
}

Processamento dos Dados

Mostrar código
# Encontrar arquivos FST
pattern_fst <- "\\.fst$"
arquivos <- list.files(fst_dir, pattern = pattern_fst, full.names = TRUE)

cat("📁 Arquivos FST encontrados:", length(arquivos), "\n")
📁 Arquivos FST encontrados: 30 
Mostrar código
cat("🚀 Processamento paralelo com", future::nbrOfWorkers(), "workers\n")
🚀 Processamento paralelo com 15 workers
Mostrar código
cat("⚡ FST multithread ativo\n\n")
⚡ FST multithread ativo
Mostrar código
# Colunas necessárias (verificadas nos arquivos FST)
cols_needed <- c(
  "Ano", "Trimestre", "UF", "UPA", "Estrato", "V1028",
  "V2007", "V2009", "V2010", "V4010", "V4041", 
  "VD3004", "VD3005", "VD4001", "VD4002", "VD4016"
)
cat("📊 Colunas configuradas:", length(cols_needed), "variáveis\n")
📊 Colunas configuradas: 16 variáveis
Mostrar código
# Função de faixa etária vetorizada
faixa_idade_fast <- function(x) {
  cut(x,
      breaks = c(-Inf, 17, 24, 34, 44, 59, Inf),
      labels = c("14-17", "18-24", "25-34", "35-44", "45-59", "60+"),
      right = TRUE)
}
cat("✅ Funções auxiliares carregadas\n")
✅ Funções auxiliares carregadas
Mostrar código
# Definir função de processamento otimizada
processa_arquivo <- function(path) {
  # Configurar opção para estratos com UPA única
  options(survey.lonely.psu = "adjust")
  
  # Leitura completa do FST (já otimizado com apenas colunas necessárias)
  dt <- read_fst(path, as.data.table = TRUE)
  
  # Derivações in-place (sem cópias grandes)
  dt[, `:=`(
    anos_estudo = VD3005,
    in_forca = VD4002 %in% c(1L, 2L),
    ocupado = as.integer(VD4002 == 1L),
    desocupado = as.integer(VD4002 == 2L),
    tri_num = suppressWarnings(as.integer(Trimestre)),
    sexo = factor(V2007, levels = c(1,2), labels = c("Homem","Mulher")),
    faixa_id = faixa_idade_fast(V2009)
  )]
  
  # Limpar códigos CBO
  dt[, V4010_clean := clean_cbo4(V4010)]
  dt[, V4041_clean := clean_cbo4(V4041)]
  
  # Criar variáveis que dependem dos códigos limpos
  dt[, `:=`(
    cbo4_emp = fifelse(VD4002 == 1L, V4010_clean, NA_character_),
    ultima_ocupacao = fifelse(VD4002 == 1L, V4010_clean,
                         fifelse(VD4002 == 2L, V4041_clean, NA_character_))
  )]
  
  # Criar período usando tri_num já criado
  dt[, periodo := sprintf("%dT%d", Ano, tri_num)]
  
  # Grande grupo usando função existente
  dt[, grande_grupo := get_cbo_grande_grupo(ultima_ocupacao)]
  
  # Exposição IA - lookup otimizado (igual ao arquivo original)
  # Garantir que V4010 seja character para o match
  dt[, V4010_clean := clean_cbo4(V4010)]
  
  # Fazer join usando merge (mais robusto que o lookup por índice)
  exposicao_subset <- df_exposicao_ia[, .(cbo4, Exposure, faixa_exposicao)]
  dt <- merge(dt, exposicao_subset, by.x = "V4010_clean", by.y = "cbo4", all.x = TRUE)
  
  # Renomear colunas
  setnames(dt, c("Exposure", "faixa_exposicao"), c("categoria_exp_ia", "faixa_exp_ia"))
  
  # Garantir que ocupações não encontradas fiquem como "Sem informação" (igual ao original)
  dt[is.na(faixa_exp_ia) & !is.na(V4010_clean), faixa_exp_ia := "Sem informação"]
  
  periodo_atual <- dt$periodo[1L]
  
  # Designs amostrais com tratamento de erro
  des_per <- tryCatch({
    svydesign(ids = ~UPA, strata = ~Estrato, weights = ~V1028,
             data = dt[in_forca == TRUE], nest = TRUE)
  }, error = function(e) {
    # Se houver erro, tentar sem nest ou com opções diferentes
    tryCatch({
      svydesign(ids = ~UPA, strata = ~Estrato, weights = ~V1028,
               data = dt[in_forca == TRUE], nest = FALSE)
    }, error = function(e2) {
      # Como último recurso, design sem estratificação
      svydesign(ids = ~1, weights = ~V1028, data = dt[in_forca == TRUE])
    })
  })
  
  des_ocupados <- tryCatch({
    svydesign(ids = ~UPA, strata = ~Estrato, weights = ~V1028,
             data = dt[ocupado == 1L], nest = TRUE)
  }, error = function(e) {
    tryCatch({
      svydesign(ids = ~UPA, strata = ~Estrato, weights = ~V1028,
               data = dt[ocupado == 1L], nest = FALSE)
    }, error = function(e2) {
      svydesign(ids = ~1, weights = ~V1028, data = dt[ocupado == 1L])
    })
  })
  
  # Métricas com tratamento de erro
  res_g <- svymean(~desocupado, design = des_per, na.rm = TRUE)
  
  res_s <- svyby(~desocupado, ~sexo, design = des_per, svymean,
                 na.rm = TRUE, drop.empty.groups = TRUE)
  
  res_i <- svyby(~desocupado, ~faixa_id, design = des_per, svymean,
                 na.rm = TRUE, drop.empty.groups = TRUE)
  
  # Ocupação
  dist_grupo <- try(
    svyby(~ocupado, ~grande_grupo, design = des_ocupados, svytotal,
          na.rm = TRUE, drop.empty.groups = TRUE),
    silent = TRUE
  )
  total_ocu <- try(svytotal(~ocupado, design = des_ocupados, na.rm = TRUE), silent = TRUE)
  total_ocu_num <- if (inherits(total_ocu, "try-error")) NA_real_ else as.numeric(coef(total_ocu))
  
  # Ocupação x Sexo
  dist_ocu_s <- try(
    svyby(~ocupado, ~grande_grupo + sexo, design = des_ocupados, svytotal,
          na.rm = TRUE, drop.empty.groups = TRUE),
    silent = TRUE
  )
  
  # Exposição IA
  dist_exp <- try(
    svyby(~ocupado, ~faixa_exp_ia, design = des_ocupados, svytotal,
          na.rm = TRUE, drop.empty.groups = TRUE),
    silent = TRUE
  )
  
  # Prêmio salarial
  dt_premio <- dt[!is.na(VD4016) & VD4016 > 0 & !is.na(anos_estudo) & !is.na(V1028)]
  out_m1 <- out_m2 <- out_m3 <- NULL
  
  if (nrow(dt_premio) > 100) {
    # Model 1
    model1 <- try(lm(log(VD4016) ~ anos_estudo, data = dt_premio), silent = TRUE)
    if (!inherits(model1, "try-error")) {
      s <- summary(model1)$coefficients["anos_estudo", ]
      out_m1 <- data.table(periodo = periodo_atual, modelo = "Model1_sem_pesos",
                           coeficiente = s[["Estimate"]], erro_padrao = s[["Std. Error"]],
                           t_valor = s[["t value"]], p_valor = s[["Pr(>|t|)"]],
                           n_obs = nrow(dt_premio))
    }
    
    # Model 2 e 3 com tratamento de erro
    desenho_premio <- tryCatch({
      svydesign(ids = ~UPA, strata = ~Estrato, weights = ~V1028,
               data = dt_premio, nest = TRUE)
    }, error = function(e) {
      tryCatch({
        svydesign(ids = ~UPA, strata = ~Estrato, weights = ~V1028,
                 data = dt_premio, nest = FALSE)
      }, error = function(e2) {
        svydesign(ids = ~1, weights = ~V1028, data = dt_premio)
      })
    })
    
    model2 <- try(svyglm(log(VD4016) ~ anos_estudo, design = desenho_premio), silent = TRUE)
    if (!inherits(model2, "try-error")) {
      s <- summary(model2)$coefficients["anos_estudo", ]
      out_m2 <- data.table(periodo = periodo_atual, modelo = "Model2_com_pesos",
                           coeficiente = s[["Estimate"]], erro_padrao = s[["Std. Error"]],
                           t_valor = s[["t value"]], p_valor = s[["Pr(>|t|)"]],
                           n_obs = nrow(dt_premio))
    }
    
    dt3 <- dt_premio[!is.na(V2007) & !is.na(V2010) & !is.na(UF)]
    if (nrow(dt3) > 100) {
      desenho3 <- tryCatch({
        svydesign(ids = ~UPA, strata = ~Estrato, weights = ~V1028, data = dt3, nest = TRUE)
      }, error = function(e) {
        tryCatch({
          svydesign(ids = ~UPA, strata = ~Estrato, weights = ~V1028, data = dt3, nest = FALSE)
        }, error = function(e2) {
          svydesign(ids = ~1, weights = ~V1028, data = dt3)
        })
      })
      model3 <- try(svyglm(
        log(VD4016) ~ anos_estudo + V2009 + factor(V2007) + factor(V2010) + factor(UF),
        design = desenho3
      ), silent = TRUE)
      if (!inherits(model3, "try-error")) {
        s <- summary(model3)$coefficients["anos_estudo", ]
        out_m3 <- data.table(periodo = periodo_atual, modelo = "Model3_com_controles",
                             coeficiente = s[["Estimate"]], erro_padrao = s[["Std. Error"]],
                             t_valor = s[["t value"]], p_valor = s[["Pr(>|t|)"]],
                             n_obs = nrow(dt3))
      }
    }
  }
  
  list(
    tabs_geral = data.table(periodo = periodo_atual,
                            taxa_desocupacao = 100 * as.numeric(coef(res_g)),
                            se = 100 * as.numeric(SE(res_g))),
    tabs_sexo = data.table(periodo = periodo_atual,
                           sexo = as.character(res_s$sexo),
                           taxa = 100 * as.numeric(res_s$desocupado),
                           se = 100 * as.numeric(SE(res_s))),
    tabs_idade = data.table(periodo = periodo_atual,
                            faixa_id = as.character(res_i$faixa_id),
                            taxa = 100 * as.numeric(res_i$desocupado),
                            se = 100 * as.numeric(SE(res_i))),
    tabs_ocupacao_abs = if (!inherits(dist_grupo, "try-error") && !is.null(dist_grupo))
      data.table(periodo = periodo_atual,
                 grande_grupo = as.character(dist_grupo$grande_grupo),
                 n_ocupados = as.numeric(dist_grupo$ocupado),
                 se_ocupados = as.numeric(SE(dist_grupo)),
                 pct_total = 100 * as.numeric(dist_grupo$ocupado) / total_ocu_num) else NULL,
    tabs_ocupacao_sexo = if (!inherits(dist_ocu_s, "try-error") && !is.null(dist_ocu_s))
      data.table(periodo = periodo_atual,
                 grande_grupo = as.character(dist_ocu_s$grande_grupo),
                 sexo = as.character(dist_ocu_s$sexo),
                 n_ocupados = as.numeric(dist_ocu_s$ocupado),
                 se_ocupados = as.numeric(SE(dist_ocu_s))) else NULL,
    tabs_exposicao_ia = if (!inherits(dist_exp, "try-error") && !is.null(dist_exp))
      data.table(periodo = periodo_atual,
                 faixa_exposicao = as.character(dist_exp$faixa_exp_ia),
                 n_ocupados = as.numeric(dist_exp$ocupado),
                 se_ocupados = as.numeric(SE(dist_exp)),
                 pct_total = if (is.finite(total_ocu_num)) 100 * as.numeric(dist_exp$ocupado) / total_ocu_num else NA_real_) else NULL,
    m1 = out_m1, m2 = out_m2, m3 = out_m3
  )
}
cat("✅ Função de processamento definida\n")
✅ Função de processamento definida
Mostrar código
# Executar processamento paralelo
tempo_total_inicio <- Sys.time()

cat("🚀 Iniciando processamento paralelo com", future::nbrOfWorkers(), "workers...\n")
🚀 Iniciando processamento paralelo com 15 workers...
Mostrar código
# Usar with_progress sem handlers globais
resultados <- with_progress({
  p <- progressor(along = arquivos)
  
  future_lapply(arquivos, function(arquivo) {
    p(sprintf("Processando %s", basename(arquivo)))
    processa_arquivo(arquivo)
  }, future.seed = TRUE)
})

tempo_total <- as.numeric(difftime(Sys.time(), tempo_total_inicio, units = "mins"))
cat(sprintf("\n✅ Processamento concluído em %.2f minutos!\n", tempo_total))

✅ Processamento concluído em 22.41 minutos!
Mostrar código
cat(sprintf("⚡ Velocidade: %.1f segundos por arquivo\n", (tempo_total * 60) / length(arquivos)))
⚡ Velocidade: 44.8 segundos por arquivo
Mostrar código
# Consolidação rápida com rbindlist
cat("\n📊 Consolidando resultados...\n")

📊 Consolidando resultados...
Mostrar código
tabs_geral <- rbindlist(map(resultados, "tabs_geral"), fill = TRUE)
tabs_sexo <- rbindlist(map(resultados, "tabs_sexo"), fill = TRUE)
tabs_idade <- rbindlist(map(resultados, "tabs_idade"), fill = TRUE)
tabs_ocupacao_abs <- rbindlist(map(resultados, "tabs_ocupacao_abs"), fill = TRUE)
tabs_ocupacao_sexo <- rbindlist(map(resultados, "tabs_ocupacao_sexo"), fill = TRUE)
tabs_exposicao_ia <- rbindlist(map(resultados, "tabs_exposicao_ia"), fill = TRUE)
resultados_premio_model1 <- rbindlist(map(resultados, "m1"), fill = TRUE)
resultados_premio_model2 <- rbindlist(map(resultados, "m2"), fill = TRUE)
resultados_premio_model3 <- rbindlist(map(resultados, "m3"), fill = TRUE)

# Limpeza de memória
rm(resultados)
gc()
          used  (Mb) gc trigger  (Mb) max used  (Mb)
Ncells 2346908 125.4    4393877 234.7  4393877 234.7
Vcells 4145712  31.7    8388608  64.0  8388253  64.0
Mostrar código
# Converter para tibbles (compatibilidade com resto do código)
tabs_geral <- as_tibble(tabs_geral)
tabs_sexo <- as_tibble(tabs_sexo)
tabs_idade <- as_tibble(tabs_idade)
tabs_ocupacao_abs <- as_tibble(tabs_ocupacao_abs)
tabs_ocupacao_sexo <- as_tibble(tabs_ocupacao_sexo)
tabs_exposicao_ia <- as_tibble(tabs_exposicao_ia)
resultados_premio_model1 <- as_tibble(resultados_premio_model1)
resultados_premio_model2 <- as_tibble(resultados_premio_model2)
resultados_premio_model3 <- as_tibble(resultados_premio_model3)

cat("✅ Dados consolidados com sucesso!\n")
✅ Dados consolidados com sucesso!

Consolidação dos Resultados

Mostrar código
# Os dados já estão consolidados da paralelização!
# Apenas ordenar períodos
tab_tx_geral_periodo <- tabs_geral %>%
  mutate(periodo = ord_periodo(periodo)) %>%
  arrange(periodo)

tab_tx_periodo_sexo <- tabs_sexo %>%
  mutate(periodo = ord_periodo(periodo)) %>%
  arrange(periodo, sexo)

tab_tx_periodo_idade <- tabs_idade %>%
  mutate(periodo = ord_periodo(periodo)) %>%
  arrange(periodo, faixa_id)

tab_ocupacao_composicao <- tabs_ocupacao_abs %>%
  mutate(periodo = ord_periodo(periodo)) %>%
  arrange(periodo, grande_grupo)

tab_tx_periodo_ocu_sexo <- tabs_ocupacao_sexo %>%
  mutate(periodo = ord_periodo(periodo)) %>%
  arrange(periodo, grande_grupo, sexo)

tab_exposicao_ia <- tabs_exposicao_ia %>%
  mutate(periodo = ord_periodo(periodo)) %>%
  arrange(periodo, faixa_exposicao)

# Prêmio salarial
tab_premio_model1 <- resultados_premio_model1 %>%
  mutate(periodo = ord_periodo(periodo)) %>%
  arrange(periodo)

tab_premio_model2 <- resultados_premio_model2 %>%
  mutate(periodo = ord_periodo(periodo)) %>%
  arrange(periodo)

tab_premio_model3 <- resultados_premio_model3 %>%
  mutate(periodo = ord_periodo(periodo)) %>%
  arrange(periodo)

tab_premio_completa <- bind_rows(tab_premio_model1, tab_premio_model2, tab_premio_model3) %>%
  arrange(periodo, modelo)

cat("📊 Tabelas finais criadas com sucesso!\n")
📊 Tabelas finais criadas com sucesso!

Resultados: Taxa de Desocupação

Evolução Temporal Geral

Mostrar código
p_geral <- ggplot(tab_tx_geral_periodo, aes(x = periodo, y = taxa_desocupacao, group = 1)) +
  geom_line(color = "steelblue", linewidth = 1) +
  geom_point(color = "steelblue", size = 2) +
  labs(
    title = "Taxa de Desocupação (%) — Geral",
    x = "Período (Ano-Trimestre)", 
    y = "Taxa de Desocupação (%)"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggsave(file.path(out_dir, "tx_desocupacao_geral_por_periodo.png"), p_geral, width = 10, height = 5, dpi = 150)
p_geral

Taxa de desocupação ao longo do tempo

Tabela: Últimos 5 Períodos

Mostrar código
tab_tx_geral_periodo %>%
  arrange(desc(periodo)) %>%
  head(5) %>%
  mutate(
    taxa_desocupacao = sprintf("%.2f%%", taxa_desocupacao),
    se = sprintf("%.2f", se)
  ) %>%
  kable(
    col.names = c("Período", "Taxa (%)", "Erro Padrão"),
    align = "lcc"
  ) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Período Taxa (%) Erro Padrão
2025T2 5.76% 0.08
2025T1 7.00% 0.09
2024T4 6.16% 0.08
2024T3 6.35% 0.09
2024T2 6.89% 0.09

Por Sexo

Mostrar código
p_sexo <- ggplot(tab_tx_periodo_sexo, aes(x = periodo, y = taxa, color = sexo, group = sexo)) +
  geom_line(linewidth = 1) +
  geom_point(size = 2) +
  labs(
    title = "Taxa de Desocupação (%) — por Sexo",
    x = "Período (Ano-Trimestre)", 
    y = "Taxa (%)", 
    color = "Sexo"
  ) +
  scale_color_manual(values = c("Homem" = "#2E86AB", "Mulher" = "#A23B72")) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggsave(file.path(out_dir, "tx_desocupacao_por_periodo_sexo.png"), p_sexo, width = 10, height = 5, dpi = 150)
p_sexo

Taxa de desocupação por sexo

Por Faixa de Idade

Mostrar código
p_idade <- ggplot(tab_tx_periodo_idade, aes(x = periodo, y = taxa, color = faixa_id, group = faixa_id)) +
  geom_line(linewidth = 1) +
  geom_point(size = 2) +
  labs(
    title = "Taxa de Desocupação (%) — por Faixa de Idade",
    x = "Período (Ano-Trimestre)", 
    y = "Taxa (%)", 
    color = "Faixa de Idade"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggsave(file.path(out_dir, "tx_desocupacao_por_periodo_idade.png"), p_idade, width = 10, height = 5, dpi = 150)
p_idade

Taxa de desocupação por faixa de idade

Resultados: Composição Ocupacional

Evolução por Grande Grupo (Valores Absolutos)

Mostrar código
if (nrow(tab_ocupacao_composicao) > 0) {
  p_abs <- ggplot(tab_ocupacao_composicao, aes(
    x = periodo, y = n_ocupados / 1000000,
    color = grande_grupo, group = grande_grupo
  )) +
    geom_line(linewidth = 0.8) +
    geom_point(size = 1.5) +
    labs(
      title = "Evolução do Número de Ocupados por Grande Grupo Ocupacional",
      subtitle = "Valores em milhões de pessoas",
      x = "Período (Ano-Trimestre)", 
      y = "Milhões de ocupados", 
      color = "Grande Grupo"
    ) +
    theme_minimal() +
    theme(
      axis.text.x = element_text(angle = 45, hjust = 1),
      legend.position = "bottom"
    ) +
    guides(color = guide_legend(ncol = 2))
  
  ggsave(file.path(out_dir, "evolucao_ocupados_absoluto.png"), p_abs, width = 14, height = 7, dpi = 150)
  print(p_abs)
}

Número de ocupados por grande grupo ocupacional em milhões

Participação Percentual

Mostrar código
if (nrow(tab_ocupacao_composicao) > 0) {
  p_pct <- ggplot(tab_ocupacao_composicao, aes(
    x = periodo, y = pct_total,
    color = grande_grupo, group = grande_grupo
  )) +
    geom_line(linewidth = 0.8) +
    geom_point(size = 1.5) +
    labs(
      title = "Participação (%) de cada Ocupação no Total de Ocupados",
      subtitle = "Evolução da composição ocupacional ao longo do tempo",
      x = "Período (Ano-Trimestre)", 
      y = "% do total de ocupados", 
      color = "Grande Grupo"
    ) +
    theme_minimal() +
    theme(
      axis.text.x = element_text(angle = 45, hjust = 1),
      legend.position = "bottom"
    ) +
    guides(color = guide_legend(ncol = 2))
  
  ggsave(file.path(out_dir, "participacao_ocupacao_percentual.png"), p_pct, width = 14, height = 7, dpi = 150)
  print(p_pct)
}

Participação percentual de cada ocupação no total

Gráfico de Área Empilhada

Mostrar código
if (nrow(tab_ocupacao_composicao) > 0) {
  p_area <- ggplot(tab_ocupacao_composicao, aes(
    x = periodo, y = pct_total,
    fill = grande_grupo, group = grande_grupo
  )) +
    geom_area(position = "stack", alpha = 0.7) +
    labs(
      title = "Composição dos Ocupados por Grande Grupo Ocupacional",
      subtitle = "Distribuição percentual ao longo do tempo",
      x = "Período (Ano-Trimestre)", 
      y = "% do total", 
      fill = "Grande Grupo"
    ) +
    scale_y_continuous(labels = scales::percent_format(scale = 1)) +
    theme_minimal() +
    theme(
      axis.text.x = element_text(angle = 45, hjust = 1),
      legend.position = "bottom"
    ) +
    guides(fill = guide_legend(ncol = 2))
  
  ggsave(file.path(out_dir, "composicao_ocupacao_area.png"), p_area, width = 14, height = 7, dpi = 150)
  print(p_area)
}

Composição percentual dos ocupados (área empilhada)

Heatmap de Composição

Mostrar código
if (nrow(tab_ocupacao_composicao) > 0) {
  p_heatmap <- ggplot(tab_ocupacao_composicao, aes(x = periodo, y = grande_grupo, fill = pct_total)) +
    geom_tile(color = "white") +
    geom_text(aes(label = sprintf("%.1f%%", pct_total)), size = 2.5, color = "black") +
    scale_fill_gradient2(
      low = "lightblue", mid = "yellow", high = "darkred",
      midpoint = median(tab_ocupacao_composicao$pct_total, na.rm = TRUE),
      name = "% do total"
    ) +
    labs(
      title = "Heatmap: Participação de cada Ocupação no Total de Ocupados",
      x = "Período", 
      y = "Grande Grupo Ocupacional"
    ) +
    theme_minimal() +
    theme(
      axis.text.x = element_text(angle = 45, hjust = 1),
      axis.text.y = element_text(size = 8)
    )
  
  ggsave(file.path(out_dir, "heatmap_composicao_ocupacao.png"), p_heatmap, width = 14, height = 8, dpi = 150)
  print(p_heatmap)
}

Heatmap da participação ocupacional

Ocupação por Sexo (Facetado)

Mostrar código
if (nrow(tab_tx_periodo_ocu_sexo) > 0) {
  p_ocu_sexo <- ggplot(
    tab_tx_periodo_ocu_sexo,
    aes(x = periodo, y = n_ocupados / 1000000, color = sexo, group = sexo)
  ) +
    geom_line(linewidth = 0.7) +
    geom_point(size = 1) +
    facet_wrap(~grande_grupo, ncol = 3, scales = "free_y") +
    labs(
      title = "Número de Ocupados por Grande Grupo e Sexo",
      subtitle = "Valores em milhões",
      x = "Período", 
      y = "Milhões", 
      color = "Sexo"
    ) +
    scale_color_manual(values = c("Homem" = "#2E86AB", "Mulher" = "#A23B72")) +
    theme_minimal() +
    theme(
      axis.text.x = element_text(angle = 45, hjust = 1, size = 7),
      strip.text = element_text(size = 8)
    )
  
  ggsave(file.path(out_dir, "ocupados_por_grupo_sexo.png"), p_ocu_sexo, width = 16, height = 12, dpi = 150)
  print(p_ocu_sexo)
}

Número de ocupados por grande grupo e sexo

Resultados: Exposição à Inteligência Artificial

Evolução por Nível de Exposição (Valores Absolutos)

Mostrar código
if (nrow(tab_exposicao_ia) > 0) {
  p_ia_abs <- ggplot(tab_exposicao_ia, aes(
    x = periodo, y = n_ocupados / 1000000,
    color = faixa_exposicao, group = faixa_exposicao
  )) +
    geom_line(linewidth = 1) +
    geom_point(size = 2) +
    labs(
      title = "Evolução dos Ocupados por Nível de Exposição à IA",
      subtitle = "Valores em milhões de pessoas",
      x = "Período (Ano-Trimestre)", 
      y = "Milhões de ocupados",
      color = "Nível de Exposição"
    ) +
    theme_minimal() +
    theme(
      axis.text.x = element_text(angle = 45, hjust = 1),
      legend.position = "bottom"
    )
  
  ggsave(file.path(out_dir, "exposicao_ia_absoluto.png"), p_ia_abs, width = 14, height = 7, dpi = 150)
  print(p_ia_abs)
}

Número de ocupados por nível de exposição à IA

Participação Percentual

Mostrar código
if (nrow(tab_exposicao_ia) > 0) {
  p_ia_pct <- ggplot(tab_exposicao_ia, aes(
    x = periodo, y = pct_total,
    color = faixa_exposicao, group = faixa_exposicao
  )) +
    geom_line(linewidth = 1) +
    geom_point(size = 2) +
    labs(
      title = "Participação (%) dos Ocupados por Nível de Exposição à IA",
      subtitle = "% do total de ocupados em cada faixa de exposição",
      x = "Período (Ano-Trimestre)", 
      y = "% do total",
      color = "Nível de Exposição"
    ) +
    theme_minimal() +
    theme(
      axis.text.x = element_text(angle = 45, hjust = 1),
      legend.position = "bottom"
    )
  
  ggsave(file.path(out_dir, "exposicao_ia_percentual.png"), p_ia_pct, width = 14, height = 7, dpi = 150)
  print(p_ia_pct)
}

Participação percentual por nível de exposição à IA

Composição (Área Empilhada)

Mostrar código
if (nrow(tab_exposicao_ia) > 0) {
  p_ia_area <- ggplot(tab_exposicao_ia, aes(
    x = periodo, y = pct_total,
    fill = faixa_exposicao, group = faixa_exposicao
  )) +
    geom_area(position = "stack", alpha = 0.8) +
    labs(
      title = "Composição dos Ocupados por Exposição à IA Generativa",
      subtitle = "Distribuição percentual ao longo do tempo",
      x = "Período (Ano-Trimestre)", 
      y = "% do total",
      fill = "Nível de Exposição"
    ) +
    scale_y_continuous(labels = scales::percent_format(scale = 1)) +
    theme_minimal() +
    theme(
      axis.text.x = element_text(angle = 45, hjust = 1),
      legend.position = "bottom"
    )
  
  ggsave(file.path(out_dir, "exposicao_ia_area.png"), p_ia_area, width = 14, height = 7, dpi = 150)
  print(p_ia_area)
}

Composição dos ocupados por exposição à IA (área empilhada)

Distribuição no Último Período (Pizza)

Mostrar código
if (nrow(tab_exposicao_ia) > 0) {
  tab_ia_ultimo <- tab_exposicao_ia %>%
    filter(periodo == max(periodo))
  
  p_ia_pizza <- ggplot(tab_ia_ultimo, aes(x = "", y = pct_total, fill = faixa_exposicao)) +
    geom_bar(stat = "identity", width = 1) +
    coord_polar("y", start = 0) +
    geom_text(aes(label = sprintf("%.1f%%", pct_total)),
              position = position_stack(vjust = 0.5)) +
    labs(
      title = sprintf("Distribuição dos Ocupados por Exposição à IA (%s)", 
                      as.character(max(tab_exposicao_ia$periodo))),
      subtitle = "Último período disponível",
      fill = "Nível de Exposição"
    ) +
    theme_void() +
    theme(legend.position = "right")
  
  ggsave(file.path(out_dir, "exposicao_ia_pizza_atual.png"), p_ia_pizza, width = 10, height = 7, dpi = 150)
  print(p_ia_pizza)
}

Distribuição dos ocupados por exposição à IA no último período

Resumo Estatístico: Exposição à IA

Mostrar código
if (nrow(tab_exposicao_ia) > 0) {
  tab_ia_resumo <- tab_exposicao_ia %>%
    filter(periodo == max(periodo)) %>%
    arrange(desc(pct_total)) %>%
    mutate(
      n_milhoes = n_ocupados / 1000000,
      pct_fmt = sprintf("%.1f%%", pct_total)
    ) %>%
    select(faixa_exposicao, pct_fmt, n_milhoes)
  
  tab_ia_resumo %>%
    kable(
      col.names = c("Nível de Exposição", "% do Total", "Milhões de Trabalhadores"),
      align = "lcc",
      caption = paste("Distribuição dos Ocupados por Exposição à IA -", max(tab_exposicao_ia$periodo))
    ) %>%
    kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
}
Distribuição dos Ocupados por Exposição à IA - 2025T2
Nível de Exposição % do Total Milhões de Trabalhadores
Not Exposed 53.5% 54.723661
Minimal Exposure 15.2% 15.579730
Gradient 2 10.3% 10.551354
Gradient 1 8.9% 9.113421
Gradient 4 5.4% 5.479040
Gradient 3 4.8% 4.883045
Sem informação 1.9% 1.977728
ImportantTrabalhadores Altamente Expostos à IA
Mostrar código
if (nrow(tab_exposicao_ia) > 0) {
  expostos <- tab_exposicao_ia %>%
    filter(periodo == max(periodo)) %>%
    filter(faixa_exposicao %in% c("3-Média-Alta (50-75%)", "4-Alta (75-100%)")) %>%
    summarise(
      total_pct = sum(pct_total),
      total_n = sum(n_ocupados)
    )
  
  if (nrow(expostos) > 0 && expostos$total_pct > 0) {
    cat(sprintf(
      "**%.1f%%** dos trabalhadores brasileiros (**%.2f milhões de pessoas**) estão em ocupações com exposição média-alta ou alta à IA Generativa (>50%%).",
      expostos$total_pct, 
      expostos$total_n / 1000000
    ))
  }
}

Resultados: Prêmio Salarial da Educação

Comparação entre Modelos

Mostrar código
if (nrow(tab_premio_completa) > 0) {
  p_premio_comparacao <- ggplot(tab_premio_completa, aes(
    x = periodo, y = coeficiente,
    color = modelo, group = modelo
  )) +
    geom_line(linewidth = 1) +
    geom_point(size = 2) +
    geom_ribbon(aes(
      ymin = coeficiente - 1.96 * erro_padrao,
      ymax = coeficiente + 1.96 * erro_padrao,
      fill = modelo
    ), alpha = 0.2, color = NA) +
    labs(
      title = "Evolução do Prêmio Salarial da Educação",
      subtitle = "Retorno salarial por ano adicional de escolaridade (coeficiente log-salário)",
      x = "Período (Ano-Trimestre)",
      y = "Coeficiente (log-salário)",
      color = "Modelo",
      fill = "Modelo"
    ) +
    theme_minimal() +
    theme(
      axis.text.x = element_text(angle = 45, hjust = 1),
      legend.position = "bottom"
    )
  
  ggsave(file.path(out_dir, "premio_salarial_evolucao_comparacao.png"), 
         p_premio_comparacao, width = 14, height = 7, dpi = 150)
  print(p_premio_comparacao)
}

Evolução do prêmio salarial da educação - comparação entre modelos

Modelo Completo (com Controles)

Mostrar código
if (nrow(tab_premio_model3) > 0) {
  p_premio_model3 <- ggplot(tab_premio_model3, aes(x = periodo, y = coeficiente, group = 1)) +
    geom_line(linewidth = 1.2, color = "steelblue") +
    geom_point(size = 3, color = "steelblue") +
    geom_ribbon(
      aes(
        ymin = coeficiente - 1.96 * erro_padrao,
        ymax = coeficiente + 1.96 * erro_padrao
      ),
      alpha = 0.3, fill = "steelblue"
    ) +
    labs(
      title = "Prêmio Salarial da Educação (Modelo Completo)",
      subtitle = "Retorno por ano adicional de escolaridade - controles: idade, sexo, raça e UF",
      x = "Período (Ano-Trimestre)",
      y = "Coeficiente (log-salário)",
      caption = "Intervalo de confiança: 95%. Um coeficiente de 0.10 = ~10% de retorno salarial"
    ) +
    theme_minimal() +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))
  
  ggsave(file.path(out_dir, "premio_salarial_model3_evolucao.png"), 
         p_premio_model3, width = 14, height = 7, dpi = 150)
  print(p_premio_model3)
}

Prêmio salarial da educação - Modelo 3 (com controles)

Resumo Estatístico: Prêmio Salarial

Mostrar código
if (nrow(tab_premio_completa) > 0) {
  resumo_premio <- tab_premio_completa %>%
    group_by(modelo) %>%
    summarise(
      media_coef = mean(coeficiente, na.rm = TRUE),
      min_coef = min(coeficiente, na.rm = TRUE),
      max_coef = max(coeficiente, na.rm = TRUE),
      n_periodos = n()
    ) %>%
    mutate(
      media_pct = sprintf("%.1f%%", media_coef * 100),
      min_pct = sprintf("%.1f%%", min_coef * 100),
      max_pct = sprintf("%.1f%%", max_coef * 100)
    ) %>%
    select(modelo, media_pct, min_pct, max_pct, n_periodos)
  
  resumo_premio %>%
    kable(
      col.names = c("Modelo", "Média", "Mínimo", "Máximo", "Períodos"),
      align = "lcccc",
      caption = "Retorno Salarial por Ano Adicional de Escolaridade"
    ) %>%
    kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
}
Retorno Salarial por Ano Adicional de Escolaridade
Modelo Média Mínimo Máximo Períodos
Model1_sem_pesos 9.8% 9.1% 10.5% 30
Model2_com_pesos 10.3% 9.7% 10.8% 30
Model3_com_controles 11.2% 10.6% 11.8% 30
NoteInterpretação dos Modelos
  • Model 1 (sem pesos): Regressão simples OLS. Não representa a população brasileira, apenas a amostra.
  • Model 2 (com pesos): Regressão ponderada usando o desenho amostral complexo da PNAD. Representa a população brasileira.
  • Model 3 (com controles): Modelo completo com controles para idade, sexo, raça e UF. Isola o efeito da educação removendo o efeito de outras variáveis.

Um coeficiente de 0.10 significa aproximadamente 10% de aumento salarial para cada ano adicional de escolaridade.

Mostrar código
if (nrow(tab_premio_model3) > 0) {
  ultimo_coef <- tab_premio_model3 %>%
    filter(periodo == max(periodo)) %>%
    pull(coeficiente)
  ultimo_periodo <- max(tab_premio_model3$periodo)
  
  cat(sprintf(
    "\n**Último período analisado (%s)**:\n\nRetorno salarial da educação (Model 3): **%.1f%%** por ano adicional de estudo.\n",
    as.character(ultimo_periodo), 
    ultimo_coef * 100
  ))
}

**Último período analisado (2025T2)**:

Retorno salarial da educação (Model 3): **11.0%** por ano adicional de estudo.

Exportação dos Resultados

Mostrar código
# Criar Excel com todas as tabelas
wb <- openxlsx::createWorkbook()

openxlsx::addWorksheet(wb, "Desocupacao_Geral")
openxlsx::writeData(wb, "Desocupacao_Geral", tab_tx_geral_periodo)

openxlsx::addWorksheet(wb, "Desocupacao_Sexo")
openxlsx::writeData(wb, "Desocupacao_Sexo", tab_tx_periodo_sexo)

openxlsx::addWorksheet(wb, "Desocupacao_Idade")
openxlsx::writeData(wb, "Desocupacao_Idade", tab_tx_periodo_idade)

openxlsx::addWorksheet(wb, "Composicao_Ocupacao")
openxlsx::writeData(wb, "Composicao_Ocupacao", tab_ocupacao_composicao)

openxlsx::addWorksheet(wb, "Ocupacao_x_Sexo")
openxlsx::writeData(wb, "Ocupacao_x_Sexo", tab_tx_periodo_ocu_sexo)

openxlsx::addWorksheet(wb, "Exposicao_IA")
openxlsx::writeData(wb, "Exposicao_IA", tab_exposicao_ia)

if (nrow(tab_premio_completa) > 0) {
  openxlsx::addWorksheet(wb, "Premio_Salarial_Todos")
  openxlsx::writeData(wb, "Premio_Salarial_Todos", tab_premio_completa)
}

if (nrow(tab_premio_model1) > 0) {
  openxlsx::addWorksheet(wb, "Premio_Model1")
  openxlsx::writeData(wb, "Premio_Model1", tab_premio_model1)
}

if (nrow(tab_premio_model2) > 0) {
  openxlsx::addWorksheet(wb, "Premio_Model2")
  openxlsx::writeData(wb, "Premio_Model2", tab_premio_model2)
}

if (nrow(tab_premio_model3) > 0) {
  openxlsx::addWorksheet(wb, "Premio_Model3")
  openxlsx::writeData(wb, "Premio_Model3", tab_premio_model3)
}

openxlsx::saveWorkbook(wb, xls_out, overwrite = TRUE)

cat("\n✅ **Arquivo Excel salvo:**", basename(xls_out), "\n")

✅ **Arquivo Excel salvo:** PNADC_desemprego_ocupacao_20251010_0408.xlsx 
Mostrar código
cat("📁 **Pasta de saída:**", out_dir, "\n")
📁 **Pasta de saída:** C:/Users/vjs20/Downloads/Projetos 3/output_pnadc_ocupacao 

Resumo Executivo

Mostrar código
cat("\n## Estatísticas Principais\n\n")

## Estatísticas Principais
Mostrar código
cat("- **Períodos processados:**", length(unique(tab_tx_geral_periodo$periodo)), "\n")
- **Períodos processados:** 30 
Mostrar código
cat("- **Arquivos analisados:**", length(arquivos), "trimestres da PNAD Contínua\n")
- **Arquivos analisados:** 30 trimestres da PNAD Contínua
Mostrar código
cat("- **Grandes grupos ocupacionais:**", length(unique(tab_ocupacao_composicao$grande_grupo)), "\n")
- **Grandes grupos ocupacionais:** 10 
Mostrar código
if (nrow(tab_exposicao_ia) > 0) {
  cat("- **Categorias de exposição à IA:**", length(unique(tab_exposicao_ia$faixa_exposicao)), "\n")
}
- **Categorias de exposição à IA:** 7 
Mostrar código
cat("\n## Gráficos Gerados\n\n")

## Gráficos Gerados
Mostrar código
cat("1. Taxa de desocupação geral\n")
1. Taxa de desocupação geral
Mostrar código
cat("2. Taxa de desocupação por sexo\n")
2. Taxa de desocupação por sexo
Mostrar código
cat("3. Taxa de desocupação por idade\n")
3. Taxa de desocupação por idade
Mostrar código
cat("4. Evolução do número de ocupados por grupo (absoluto)\n")
4. Evolução do número de ocupados por grupo (absoluto)
Mostrar código
cat("5. Participação % de cada ocupação no total\n")
5. Participação % de cada ocupação no total
Mostrar código
cat("6. Gráfico de área empilhada (composição ocupacional)\n")
6. Gráfico de área empilhada (composição ocupacional)
Mostrar código
cat("7. Heatmap de composição ocupacional\n")
7. Heatmap de composição ocupacional
Mostrar código
cat("8. Facet por grupo e sexo\n")
8. Facet por grupo e sexo
Mostrar código
cat("9-12. Exposição à IA (absoluto, %, área, pizza)\n")
9-12. Exposição à IA (absoluto, %, área, pizza)
Mostrar código
cat("13-14. Prêmio salarial (comparação e modelo 3)\n")
13-14. Prêmio salarial (comparação e modelo 3)
TipArquivos de Saída

Todos os gráficos e tabelas foram salvos em: C:/Users/vjs20/Downloads/Projetos 3/output_pnadc_ocupacao

  • Gráficos em formato PNG (150 dpi)
  • Tabelas em Excel com múltiplas abas
  • Este relatório HTML interativo

Notas Metodológicas

Fonte dos Dados

  • PNAD Contínua (Pesquisa Nacional por Amostra de Domicílios Contínua) - IBGE
  • Microdados trimestrais em formato de largura fixa (FWF)
  • Desenho amostral complexo: estratificado, conglomerado em múltiplos estágios

Ponderação

Todos os cálculos de desemprego, composição ocupacional e prêmio salarial utilizam: - Pesos calibrados (V1028) para representar a população brasileira - Estratos e UPAs para variância correta - Opção nest=TRUE para ajustar estrutura hierárquica

Classificação Ocupacional

  • CBO 2002 (Classificação Brasileira de Ocupações)
  • Agregação em 10 grandes grupos pelo primeiro dígito
  • Match com códigos OIT para exposição à IA

Limitações

  • Desempregados frequentemente não possuem código CBO (usamos ocupação anterior quando disponível)
  • Exposição à IA baseada em classificação internacional (OIT), pode não refletir realidade brasileira
  • Prêmio salarial calculado apenas para pessoas com renda positiva declarada

Relatório gerado em: 10/10/2025 às 04:30