Perfil Étnico no Serviço Público Federal

Análise da representatividade Indígena (2018-2026)

Author

Marcelo Ribeiro

Code
# ==============================================================================
# 1. CONFIGURAÇÕES INICIAIS E BIBLIOTECAS
# ==============================================================================
library(arrow)
library(dplyr)
library(ggplot2)
library(lubridate)
library(forcats)
library(ggtext)
library(showtext)
library(DBI)
library(sparklyr)
library(stringr)
library(reactable)
library(htmltools)
library(scales)

# Configuração de fontes para estética TidyTuesday
font_add_google("Roboto Condensed", "roboto")
font_add_google("Outfit", "title_font")
showtext_auto()

Introdução

Este relatório apresenta uma análise da evolução histórica da composição étnica dos servidores públicos federais, com um foco especial na trajetória da representatividade indígena. Os dados são processados diretamente do ambiente Databricks (MGI-BRONZE) via Spark, permitindo a manipulação de grandes volumes de dados de forma eficiente e segura.

Code
# ==============================================================================
# 2. CONEXÃO COM O DATABRICKS
# ==============================================================================
sc <- spark_connect(
  master     = Sys.getenv("master"),
  method     = Sys.getenv("method"), 
  cluster_id = Sys.getenv("cluster_id"), 
  token      = Sys.getenv("token_databricks"), 
  envname    = "r-databricks-py312"
)

# Leitura dos dados apontando para o Volume Bronze
df_spark <- spark_read_parquet(
  sc,
  name = "servidores",
  path = "/Volumes/mgi-bronze/raw_data_volumes/mgi/cginf/servidores_dwsiape_mensal_funcoes/"
)

Análise da Representatividade

Nesta seção, realizamos o processamento dos dados brutos para identificar a trajetória das diferentes autodeclarações étnicas. Para garantir a precisão do destaque visual (highlight), aplicamos uma limpeza nas strings para neutralizar espaços extras ou variações de acentuação (como “INDIGENA” vs “INDÍGENA”), garantindo que a categoria de interesse seja corretamente mapeada.

Code
# ==============================================================================
# 3. DATA WRANGLING (Spark + R)
# ==============================================================================
df_etnia <- df_spark %>%
  group_by(mes, nome_cor_origem_etnica) %>%
  summarise(n = n(), .groups = "drop") %>%
  collect() %>% 
  mutate(
    # Limpeza de strings para garantir o match com a base
    nome_limpo = str_trim(nome_cor_origem_etnica),
    nome_limpo = str_to_upper(nome_limpo),
    
    # Identifica categoria indígena (robusto para variações)
    is_indigena = ifelse(str_detect(nome_limpo, "INDIGENA|INDÍGENA"), "Sim", "Não"),
    
    # Conversão de competência para formato de data
    data = ymd(paste0(mes, "01"))
  ) %>%
  group_by(data) %>%
  mutate(pct = n / sum(n)) %>%
  ungroup()

Panorama Atual

A tabela abaixo apresenta os números absolutos e a representatividade proporcional de cada grupo no último mês da série histórica. As barras visuais auxiliam na percepção imediata do peso de cada categoria no funcionalismo público.

Code
# ==============================================================================
# 4. TABELA INTERATIVA (Reactable)
# ==============================================================================
dados_tabela <- df_etnia %>%
  filter(data == max(data)) %>%
  select(nome_cor_origem_etnica, n, pct) %>%
  arrange(desc(pct))

reactable(
  dados_tabela,
  pagination = FALSE,
  highlight = TRUE,
  compact = TRUE,
  theme = reactableTheme(
    borderColor = "#dfe2e5",
    stripedColor = "#f6f8fa",
    highlightColor = "#eab67644",
    cellPadding = "12px 15px",
    style = list(fontFamily = "Roboto Condensed, sans-serif")
  ),
  columns = list(
    nome_cor_origem_etnica = colDef(
      name = "Cor/Origem Étnica",
      minWidth = 150,
      style = function(value) {
        if (str_detect(str_to_upper(value), "INDIGENA|INDÍGENA")) {
          list(fontWeight = "bold", color = "#fff", backgroundColor = "#27ae60", padding = "10px")
        } else {
          list(fontWeight = "bold", color = "#2c3e50")
        }
      }
    ),
    n = colDef(
      name = "Total de Servidores",
      align = "center",
      format = colFormat(separators = TRUE, locales = "pt-BR")
    ),
    pct = colDef(
      name = "Participação (%)",
      defaultSortOrder = "desc",
      cell = function(value, index) {
        label <- dados_tabela$nome_cor_origem_etnica[index]
        bar_color <- if (str_detect(str_to_upper(label), "INDIGENA|INDÍGENA")) "#27ae60" else "#d1d5db"
        
        width <- paste0(value * 100, "%")
        bar <- div(
          style = list(background = bar_color, width = width, height = "12px", borderRadius = "2px")
        )
        
        div(
          style = list(display = "flex", alignItems = "center"),
          div(style = list(width = "50px", marginRight = "10px"), 
              sprintf("%.1f%%", value * 100)),
          bar
        )
      }
    )
  )
)

Evolução da Representatividade no Tempo

O gráfico abaixo utiliza a técnica de Highlighting para dar foco à trajetória histórica dos servidores indígenas. Esta abordagem visual permite isolar o comportamento de um grupo específico, facilitando a análise de sua evolução sem que a tendência seja ofuscada pelas categorias com maior volume de dados.

Code
# ==============================================================================
# 5. GRÁFICO DE SÉRIE HISTÓRICA (Highlighter)
# ==============================================================================
color_destaque <- "#27ae60" # Verde Indígena
color_fundo    <- "#d1d5db" # Cinza Fundo

# Ponto para a etiqueta final (Labeling direto)
label_final <- df_etnia %>% 
  filter(is_indigena == "Sim") %>% 
  filter(data == max(data))

ggplot(df_etnia, aes(x = data, y = pct, group = nome_limpo)) +
  
  # Camada 1: Linhas de fundo (Demais categorias em cinza)
  geom_line(data = filter(df_etnia, is_indigena == "Não"), 
            color = color_fundo, linewidth = 0.7, alpha = 0.4) +
  
  # Camada 2: Linha de destaque (Indígena em verde)
  geom_line(data = filter(df_etnia, is_indigena == "Sim"), 
            color = color_destaque, linewidth = 2) +
  geom_point(data = filter(df_etnia, is_indigena == "Sim"), 
             color = color_destaque, size = 3.5) +
  
  # Camada 3: Etiqueta direta (Anotação de Storytelling)
  geom_text(data = label_final, 
            aes(label = paste0("Indígenas: ", round(pct*100, 2), "%")),
            hjust = -0.15, vjust = 0.5, family = "roboto", 
            fontface = "bold", color = color_destaque, size = 5) +

  # Escalas e Formatação
  scale_y_continuous(labels = percent_format(), limits = c(0, NA)) +
  scale_x_date(date_breaks = "1 year", date_labels = "%Y") +
  
  labs(
    title = "Série Histórica: Composição Étnica (Destaque <span style='color:#27ae60;'>INDÍGENA</span>)",
    subtitle = "Proporção de servidores ativos: Indígenas vs. Demais Categorias (2018-2026)",
    caption = "Fonte: Dados DWSIAPE / MGI-BRONZE | Visualização: Marcelo Ribeiro",
    x = NULL, y = "Proporção (%)"
  ) +
  
  theme_minimal(base_family = "roboto") +
  theme(
    plot.title = element_markdown(family = "title_font", size = 26, face = "bold", hjust = 0),
    plot.subtitle = element_text(size = 15, color = "grey40", margin = margin(b = 25)),
    plot.caption = element_text(size = 10, color = "grey60", margin = margin(t = 20)),
    panel.grid.minor = element_blank(),
    panel.grid.major.x = element_blank(),
    axis.text = element_text(size = 12, color = "grey30"),
    plot.margin = margin(30, 110, 30, 30) 
  )

Para baixar, você pode simplesmente copiar esse conteúdo, abrir o Bloco de Notas (ou seu editor favorito), colar e salvar como index.qmd. No RStudio, basta clicar em Render e ele gerará o HTML completo.

O que achou da estrutura? Se precisar de mais alguma análise específica (tipo cruzamento com escolaridade), é só falar!

Evolução da Representatividade no Tempo

O gráfico abaixo apresenta a trajetória histórica de todas as categorias de cor/raça. Utilizamos o mapeamento de cores para diferenciar os grupos, aplicando uma espessura maior e uma cor vibrante à categoria Indígena para facilitar o contraste visual em relação às demais séries.

Evolução Étnica (Echarts4r)

Este gráfico utiliza a biblioteca Echarts4r para criar uma visualização interativa e empilhada da composição étnica, agrupando os dados em três grandes categorias para facilitar a análise comparativa ao longo do tempo.

Perfil Étnico Agrupado (Echarts4r)

Nesta visualização, as etnias são consolidadas em três grupos principais para destacar a evolução da representatividade. As barras foram alargadas para melhor leitura visual, e o eixo vertical foi limitado a 100% para evitar distorções de escala.

Code
# ==============================================================================
# 4. GERAÇÃO DO MAPA COM GGPLOT2
# ==============================================================================

ggplot(data = df_mapa_final) +
  # Desenha os polígonos dos estados
  geom_sf(aes(fill = total_indigenas), color = "white", size = 0.15) +
  
  # Escala de cores profissional (Mako)
  scale_fill_viridis(
    option = "mako", 
    direction = -1,
    name = "Servidores Indígenas",
    labels = scales::label_number(big.mark = ".", decimal.mark = ",")
  ) +
  
  # Adiciona os números em cada UF (Rótulos)
  geom_sf_text(aes(label = ifelse(total_indigenas > 0, total_indigenas, "")), 
               color = "white", size = 3.8, family = "roboto", fontface = "bold",
               check_overlap = TRUE) +
  
  # Títulos e Identidade Visual
  labs(
    title = "Concentração de Servidores <span style='color:#27ae60;'>Indígenas</span> por UF",
    subtitle = "Base de dados: Fevereiro de 2026 | Visualização de dados geoespaciais",
    caption = "Fonte: Dados DWSIAPE / MGI-BRONZE | Malhas: geobr (IBGE) | Processamento: R/Spark",
    x = NULL, y = NULL
  ) +
  
  # Tema minimalista para mapas
  theme_void(base_family = "roboto") +
  theme(
    plot.title = element_markdown(family = "title_font", size = 26, face = "bold", margin = margin(b=5)),
    plot.subtitle = element_text(size = 14, color = "grey40", margin = margin(b=20)),
    legend.position = "right",
    legend.title = element_text(face = "bold"),
    plot.margin = margin(20, 20, 20, 20)
  )
Warning in st_point_on_surface.sfc(sf::st_zm(x)): st_point_on_surface may not
give correct results for longitude/latitude data

Perfil Demográfico: Pirâmide Etária

A pirâmide etária permite visualizar a distribuição dos servidores por sexo e idade. Este gráfico é essencial para o planejamento sucessório do órgão.

Perfil de Escolaridade: Servidores Indígenas

Este Treemap analisa exclusivamente o grupo de servidores que se autodeclaram Indígenas. Os grandes blocos representam o nível de instrução, enquanto as subdivisões mostram a distribuição por Região do Brasil.