Índice de Gini

Remuneração dos Servidores Públicos Federais

Carreiras
Remuneração
Funcionalismo Público
Índice de Gini
Desigualdade Salarial
Servidores Públicos Federais
SIAPE
Autor
Afiliação

CGINF - Coordenação Geral de Informações Gerenciais

DIGID - Diretoria de Governança e Inteligência de dados

Data de Publicação

17 de abril de 2025

Resumo

Este tutorial aborda o cálculo e a visualização do Índice de Gini, uma medida estatística amplamente utilizada para avaliar a desigualdade na distribuição de renda em populações ou regiões.

1 Introdução

A desigualdade salarial entre os servidores públicos federais é um tema relevante para o debate sobre justiça distributiva e eficiência na gestão pública. Este relatório analisa a distribuição dos rendimentos utilizando o Índice de Gini, uma métrica amplamente utilizada para avaliar desigualdade de renda.

O Índice de Gini permite medir a dispersão dos rendimentos dentro do funcionalismo, possibilitando uma análise mais detalhada da concentração salarial entre diferentes órgãos e carreiras. Compreender esse indicador é essencial para subsidiar políticas de gestão de pessoal e avaliar o impacto das estruturas salariais na administração pública.

Essa métrica fornece um valor numérico que varia de 0 a 1, onde 0 representa igualdade perfeita (todas as pessoas possuem a mesma renda) e 1 representa desigualdade máxima (uma única pessoa detém toda a renda, enquanto as demais não possuem nenhuma) (Hoffmann 2006).

Ao calcular o Índice de Gini para a remuneração dos servidores públicos, é possível obter uma compreensão clara da distribuição salarial no setor público federal. Esse índice é frequentemente utilizado por economistas, pesquisadores e gestores para medir e comparar a desigualdade entre órgãos, carreiras e ao longo do tempo. A partir dessa análise, é possível identificar padrões de concentração salarial e avaliar o impacto de políticas de reajuste, progressão e reestruturação de carreiras.

Dentre os aspectos analisáveis com o Índice de Gini na remuneração dos servidores públicos federais, destacam-se:

  • Disparidades salariais entre diferentes órgãos e carreiras;
  • Impacto de políticas de valorização profissional na redução da desigualdade;
  • Efeitos da progressividade ou regressividade dos planos de carreira;
  • Comparação da desigualdade salarial ao longo do tempo e em diferentes períodos de ajuste fiscal.

2 Cálculo do Índice de Gini

O Índice de Gini é calculado da seguinte maneira:

\[ G = \frac{\sum_{i=1}^{n} \sum_{j=1}^{n} |x_i - x_j|}{2n^2 \bar{x}} \]

Onde:
- ( G ) é o Índice de Gini.
- ( n ) é o número de observações na amostra.
- ( x_i ) são os valores ordenados da variável de renda.
- ( x_j ) são os valores ordenados da mesma variável de renda.
- ( || ) representa o valor absoluto.
- ( {x} ) é a média da variável de renda.

3 Análise dos Dados

Para ilustrar a aplicação do Índice de Gini na remuneração dos servidores públicos federais, utilizaremos dados do Sistema Integrado de Administração de Pessoal (SIAPE). Esses dados contêm informações sobre a remuneração de servidores do Poder Executivo Federal.

Código
# carregar a base de dados em excel e compilar todas as abas em um único arquivo


# Carregando as bibliotecas necessárias
library(readxl)
library(dplyr)
library(purrr)
library(tidyr)
library(ggplot2)
library(ggtext)
library(ggdist)
library(glue)
library(patchwork)
library(camcorder)
library(gt)

library(ggstatsplot)
library(plotly)

#gg_record(dir = "tidytuesday-temp", device = "png", width = 10, height = 8, units = "in", dpi = 320)

# Define o caminho do arquivo
file_path <- "data/BaseDados_EstudoDesigualdades.xlsx"

# Obtém os nomes de todas as abas no Excel
sheet_names <- excel_sheets(file_path)

# excluir abas que não serão utilizadas 
# aba FORA 

sheet_names <- sheet_names[!sheet_names %in% c("FORA")]

# Lê todas as abas e combina em uma única base usando purrr::map
BaseDados_Unica <- sheet_names %>%
  map_df(~ read_excel(file_path, sheet = .x) %>%
           mutate(sheet_name = .x)) # Adiciona o nome da aba como coluna (opcional)

# preencher valores NA na coluna escolaridade_cargo com "Não Informado"



# filtrar apenas campos que possuem servidores

df_gini <- BaseDados_Unica |> janitor::clean_names() |> 
  replace_na(list(escolaridade_cargo = "Sem informação")) |>
  rename( "2026" = ano_2026,  "2023"= mai_23 ) |>
  pivot_longer(`2023`:`2026`, names_to = "Ano", values_to = "remun") |>
  filter(qtd > 0) 

df_gini %>%
    DT::datatable(rownames = FALSE, filter = "top",
                  options = list(pageLength = 5, scrollX = TRUE)) %>%
    DT::formatStyle(
        columns = names(df_gini), 
        target = "row",
        lineHeight = "12px",  # Reduz a altura das linhas
        fontSize = "12px"  # Reduz o tamanho da fonte
    )

Por Subgrupo de Carreiras

Código
# Expandir a base de dados com base na quantidade de observações
df_expandido <- df_gini %>%
  uncount(qtd)

# Calculo do indice de gini por ano
 df_gini_subgrupo <- df_expandido |> 
  filter(Ano == "2026") |> 
  group_by(subgrupo) |>  
  mutate(
    total_remun = sum(remun, na.rm = TRUE),
    gini_index = ifelse(total_remun > 0, reldist::gini(remun), NA)
  ) |> 
  ungroup()
 
 df_gini_subgrupo <- df_gini_subgrupo %>% arrange(desc(gini_index))
 df_gini_subgrupo$subgrupo <- factor(df_gini_subgrupo$subgrupo, levels = unique(df_gini_subgrupo$subgrupo))
 
 
 ano <- unique(df_gini_subgrupo$Ano)
 mean_remun <- mean(df_gini_subgrupo$remun, na.rm = TRUE)
 median_remun <- median(df_gini_subgrupo$remun, na.rm = TRUE)
 bg_color <- "grey97"
 font_family <- "Fira Sans"
 
 
 # Formatar {mean_remun} para moeda brasileira R$ e {median_remun} para moeda brasileira R$ com o pacate scales formato brasil
 
 library(scales)
 
 # Criando um formatador para moeda brasileira
 format_brl <- label_number(big.mark = ".", decimal.mark = ",", prefix = "R$ ", , accuracy = 0.01)
 
 plot_subtitle <- glue("A remuneração média é de {format_brl(mean_remun)} e a mediana é de {format_brl(median_remun)}.
                       <br>")
 
 
 # Gráfico com o boxplot e outras informações
 
 p <- df_gini_subgrupo%>% 
   ggplot(aes(subgrupo, remun)) +
 stat_halfeye(fill_type = "segments", alpha = 0.3, scale = 6) +
   geom_boxplot(alpha = 0.1, outlier.shape = NA, colour = "grey" ) +
  stat_interval(  ) +
   stat_summary(geom = "point", fun = median) +
   annotate("text", x =11, y = 0, label = "(\U00F8 Índice de Gini)",
            family = "Fira Sans", size = 3, hjust = 0.5) +
   stat_summary(
     aes(y = gini_index),
     geom = "text",
     fun.data = function(x) {
       data.frame(
         y = 0,
         label = sprintf("(%s)", scales::number(mean(ifelse(x > 0, x, NA), na.rm = TRUE), accuracy = 0.010)))},
     family = font_family, size = 2.5
   ) +
   geom_hline(yintercept = median_remun, col = "grey30", lty = "dashed") +
   annotate("text", x = 11, y = median_remun + 50, label = "Remuneração Mediana Geral",
            family = "Fira Sans", size = 3, hjust = 0)  +
   scale_x_discrete(labels = toupper, expand = c(0, 1)) +
   scale_y_continuous(breaks = seq(2500, 40000, 2500)) +
   scale_color_manual(values = MetBrewer::met.brewer("VanGogh3")) +
   coord_flip(ylim = c(0, 55000), clip = "off") +
   guides(col = "none") +
   labs(
     title = toupper(glue("Índice de Gini por subgrupo de servidores públicos no ano de {ano}.")),
     subtitle = plot_subtitle,
     caption = "Fonte: SIAPE",
     x = NULL,
     y = "Remuneração (R$)"
   ) +
   theme_minimal(base_family = font_family, base_size = 10) +
   theme(
     plot.background = element_rect(color = NA, fill = bg_color),
     panel.grid = element_blank(),
     panel.grid.major.x = element_line(linewidth = 0.1, color = "grey75"),
     plot.title = element_text(family = "Fira Sans SemiBold"),
     plot.title.position = "plot",
     plot.subtitle = element_textbox_simple(
       margin = margin(t = 4, b = 4), size = 8),
     plot.caption = element_textbox_simple(
       margin = margin(t = 12), size = 7
     ),
     plot.caption.position = "plot",
     axis.text.y = element_text(hjust = 0, margin = margin(r = 1.5), family = "Fira Sans SemiBold"),
     plot.margin = margin(4, 14, 4, 4),
    axis.text.x = element_text(angle = 45, hjust = 1)
   )
 

 # p
rent_title_words = readr::read_csv("data/rent_title_words.csv")
# create the dataframe for the legend (inside plot)
df_for_legend <- rent_title_words %>% 
  filter(word == "beautiful")

p_legend <- df_for_legend %>% 
  ggplot(aes(word, price)) +
  stat_halfeye(fill_type = "segments", alpha = 0.3) +
  stat_interval() +
  stat_summary(geom = "point", fun = median) +
  annotate(
    "richtext",
    x = c(0.8, 0.8, 0.8, 1.4, 1.8),
    y = c(1000, 5000, 3000, 2400, 4000),
    label = c("50 % dos servidores<br>recebem abaixo desse valor", "95 % dos servidores", 
              "80 % dos servidores", "Mediana", "Distribuição<br>da remuneração"),
    fill = NA, label.size = 0, family = font_family, size = 2, vjust = 1,
  ) +
  geom_curve(
    data = data.frame(
      x = c(0.9, 0.80, 0.80, 1.225, 1.8),
      xend = c(0.95, 0.95, 0.95, 1.075, 1.8),
      y = c(1000, 5000, 3000, 2300, 3800),
      yend = c(1800, 5000, 3000, 2100, 2500)),
    aes(x = x, xend = xend, y = y, yend = yend),
    stat = "unique", curvature = 0.2, size = 0.2, color = "grey12",
    arrow = arrow(angle = 20, length = unit(1, "mm"))
  ) +
  scale_color_manual(values = MetBrewer::met.brewer("VanGogh3")) +
  coord_flip(xlim = c(0.75, 1.3), ylim = c(0, 6000), expand = TRUE) +
  guides(color = "none") +
  labs(title = "Legenda") +
  theme_void(base_family = font_family) +
  theme(plot.title = element_text(family = "Fira Sans SemiBold", size = 9,
                                  hjust = 0.075),
        plot.background = element_rect(color = "grey30", size = 0.1, fill = bg_color))

# p_legend
# Insert the custom legend into the plot
#p + inset_element(p_legend, l = 0.6, r = 1.0,  t = 0.86, b = 0.6, clip = FALSE)

knitr::include_graphics("tidytuesday-temp/2025_01_31_10_04_47.190668.png")

Código
knitr::include_graphics("tidytuesday-temp/2025_01_31_14_44_04.771656.png")

Por Escolaridade

Código
# Expandir a base de dados com base na quantidade de observações
df_expandido <- df_gini %>%
  uncount(qtd)

# Calculo do indice de gini por ano
 df_gini_subgrupo <- df_expandido |> 
  filter(Ano == "2026") |> 
    group_by(escolaridade_cargo) |>  
  mutate(
    total_remun = sum(remun, na.rm = TRUE),
    gini_index = ifelse(total_remun > 0, reldist::gini(remun), NA)
  ) |> 
  ungroup()
 
 
 
 df_gini_subgrupo <- df_gini_subgrupo %>% arrange(desc(gini_index))
 df_gini_subgrupo$escolaridade_cargo <- factor(df_gini_subgrupo$escolaridade_cargo, levels = unique(df_gini_subgrupo$escolaridade_cargo))
 
 
 ano <- unique(df_gini_subgrupo$Ano)
 mean_remun <- mean(df_gini_subgrupo$remun, na.rm = TRUE)
 median_remun <- median(df_gini_subgrupo$remun, na.rm = TRUE)
 bg_color <- "grey97"
 font_family <- "Fira Sans"
 
 
 # Formatar {mean_remun} para moeda brasileira R$ e {median_remun} para moeda brasileira R$ com o pacate scales formato brasil
 
 library(scales)
 
 # Criando um formatador para moeda brasileira
 format_brl <- label_number(big.mark = ".", decimal.mark = ",", prefix = "R$ ", , accuracy = 0.01)
 
 plot_subtitle <- glue("A remuneração média é de {format_brl(mean_remun)} e a mediana é de {format_brl(median_remun)}.
                       <br>")
 
 
 # Gráfico com o boxplot e outras informações
 
 p <- df_gini_subgrupo%>% 
   ggplot(aes(escolaridade_cargo, remun)) +
 stat_halfeye(fill_type = "segments", alpha = 0.3, scale = 6) +
   geom_boxplot(alpha = 0.1, outlier.shape = NA, colour = "grey" ) +
  stat_interval(  ) +
   stat_summary(geom = "point", fun = median) +
   annotate("text", x =5, y = 0, label = "(\U00F8 Índice de Gini)",
            family = "Fira Sans", size = 3, hjust = 0.5) +
   stat_summary(
     aes(y = gini_index),
     geom = "text",
     fun.data = function(x) {
       data.frame(
         y = 0,
         label = sprintf("(%s)", scales::number(mean(ifelse(x > 0, x, NA), na.rm = TRUE), accuracy = 0.010)))},
     family = font_family, size = 2.5
   ) +
   geom_hline(yintercept = median_remun, col = "grey30", lty = "dashed") +
   annotate("text", x = 5, y = median_remun + 50, label = "Remuneração Mediana Geral",
            family = "Fira Sans", size = 3, hjust = 0)  +
   scale_x_discrete(labels = toupper, expand = c(0, 1)) +
   scale_y_continuous(breaks = seq(2500, 40000, 2500)) +
   scale_color_manual(values = MetBrewer::met.brewer("VanGogh3")) +
   coord_flip(ylim = c(0, 40000), clip = "off") +
   guides(col = "none") +
   labs(
     title = toupper(glue("Índice de Gini por subgrupo de servidores públicos no ano de {ano}.")),
     subtitle = plot_subtitle,
     caption = "Fonte: SIAPE",
     x = NULL,
     y = "Remuneração (R$)"
   ) +
   theme_minimal(base_family = font_family, base_size = 10) +
   theme(
     plot.background = element_rect(color = NA, fill = bg_color),
     panel.grid = element_blank(),
     panel.grid.major.x = element_line(linewidth = 0.1, color = "grey75"),
     plot.title = element_text(family = "Fira Sans SemiBold"),
     plot.title.position = "plot",
     plot.subtitle = element_textbox_simple(
       margin = margin(t = 4, b = 4), size = 8),
     plot.caption = element_textbox_simple(
       margin = margin(t = 12), size = 7
     ),
     plot.caption.position = "plot",
     axis.text.y = element_text(hjust = 0, margin = margin(r = 1.5), family = "Fira Sans SemiBold"),
     plot.margin = margin(4, 14, 4, 4),
    axis.text.x = element_text(angle = 45, hjust = 1)
   )
 
# p
rent_title_words = readr::read_csv("data/rent_title_words.csv")
# create the dataframe for the legend (inside plot)
df_for_legend <- rent_title_words %>% 
  filter(word == "beautiful")

p_legend <- df_for_legend %>% 
  ggplot(aes(word, price)) +
  stat_halfeye(fill_type = "segments", alpha = 0.3) +
  stat_interval() +
  stat_summary(geom = "point", fun = median) +
  annotate(
    "richtext",
    x = c(0.8, 0.8, 0.8, 1.4, 1.8),
    y = c(1000, 5000, 3000, 2400, 4000),
    label = c("50 % dos servidores<br>recebem abaixo desse valor", "95 % dos servidores", 
              "80 % dos servidores", "Mediana", "Distribuição<br>da remuneração"),
    fill = NA, label.size = 0, family = font_family, size = 2, vjust = 1,
  ) +
  geom_curve(
    data = data.frame(
      x = c(0.9, 0.80, 0.80, 1.225, 1.8),
      xend = c(0.95, 0.95, 0.95, 1.075, 1.8),
      y = c(1000, 5000, 3000, 2300, 3800),
      yend = c(1800, 5000, 3000, 2100, 2500)),
    aes(x = x, xend = xend, y = y, yend = yend),
    stat = "unique", curvature = 0.2, size = 0.2, color = "grey12",
    arrow = arrow(angle = 20, length = unit(1, "mm"))
  ) +
  scale_color_manual(values = MetBrewer::met.brewer("VanGogh3")) +
  coord_flip(xlim = c(0.75, 1.3), ylim = c(0, 6000), expand = TRUE) +
  guides(color = "none") +
  labs(title = "Legenda") +
  theme_void(base_family = font_family) +
  theme(plot.title = element_text(family = "Fira Sans SemiBold", size = 9,
                                  hjust = 0.075),
        plot.background = element_rect(color = "grey30", size = 0.1, fill = bg_color))

# p_legend
# Insert the custom legend into the plot
#p + inset_element(p_legend, l = 0.6, r = 1.0,  t = 0.56, b = 0.4, clip = TRUE)

knitr::include_graphics("tidytuesday-temp/2025_02_03_18_36_37.816252.png")

Código
knitr::include_graphics("tidytuesday-temp/2025_02_03_18_38_37.843374.png")

Por Órgão

O Índice de Gini também pode ser calculado para diferentes órgãos do Poder Executivo Federal, permitindo uma análise mais detalhada da desigualdade salarial entre instituições. A seguir, apresentamos um gráfico demostrando a evolução do Índice de Gini por órgão no ano de 2023 e 2026.

Observa-se que em 2026, em azul, o Índice de Gini apresentou uma redução em relação a 2023, em vermelho, para a maioria dos órgãos. Nos casos em que o Índice de Gini não apresentou redução, o gráfico apresenta apenas o indice em 2026

Código
# Expandir a base de dados com base na quantidade de observações
df_expandido <- df_gini %>%
  uncount(qtd)

# Calculo do indice de gini por ano
df_gini_orgao <- df_expandido |> 
  #filter(Ano == "2023") |> 
  group_by(orgao_area, Ano) |>  
  summarise(
    total_remun = sum(remun, na.rm = TRUE),
    gini_index = ifelse(total_remun > 0, reldist::gini(remun), NA)
  ) |> 
  select(-total_remun) |>
  ungroup() |> 
  mutate(gini_index = round(gini_index, 2))
  


# pivotar wide 

df_gini_orgao_wide <- df_gini_orgao |> 
  pivot_wider(names_from = Ano, values_from = gini_index) |> 
  mutate(gap = `2023` - `2026`) |> 
  group_by(orgao_area) |>
  mutate(
    max=max(`2023`, `2026`)) |>
  ungroup() 
 

# Reordenando a variável orgao_area com base no gap em ordem decrescente

df_long_i <- df_gini_orgao_wide |> 
  arrange(desc(abs(gap))) |> 
 
  mutate(labels=forcats::fct_reorder(orgao_area , abs(gap))) |>
  ungroup()
 

df_long <- df_long_i %>% select(-c(orgao_area))%>% 
  pivot_longer(`2023`: `2026`, names_to = "name", values_to = "value") 

library(forcats)
library(scales)

nudge_value = 0.2

p_main <- df_long %>%
  ggplot(aes(x = value, y = labels)) +
  
  # Linhas entre os pontos
  geom_line(aes(group = labels), color = "gray70", linewidth = 2) +
  
  # Pontos com cores personalizadas
  geom_point(aes(color = name), size = 3) +
  
  # Rótulos para os valores
  geom_text(aes(label = label_number(accuracy = 0.001)(value), color = name),
            size = 3,
            fontface = "bold",
            nudge_x = if_else(df_long$value == df_long$max, 0.02, -0.02),
            hjust = if_else(df_long$value == df_long$max, 0, 1)) +
  
  # Adicionando legenda personalizada ao lado direito do gráfico
  geom_text(aes(label = name, color = name),
            data = df_long %>% filter(gap == max(gap)),
            nudge_y = 1,
            fontface = "bold",
            size = 3.5) +
  
  # Estilizando tema
  theme_light(base_size = 12) + 
  theme(legend.position = "none",
        axis.text.y = element_text(color = "black", size = 8),
        axis.text.x = element_text(color = "#666666", size = 8),
        axis.title = element_blank(),
        panel.grid.major.y = element_line(color = "gray90", linetype = "dashed"),
        panel.grid.minor = element_blank()) +
  
  # Título e legendas
  labs(
    title = "Índice de Gini por Órgão (2023 e 2026)",
    caption = "Fonte: SIAPE",
  ) +
  
  # Ajustando cores
  scale_color_manual(values = c("#BF2F24", "#436685")) +
  
  # Ajustando limites do gráfico
  coord_cartesian(ylim = c(0, 50)) 

knitr::include_graphics("tidytuesday-temp/2025_02_03_14_45_21.663065.png")

Código
set.seed(123)

p <- ggdotplotstats(
  data       = df_long |> filter(name == "2023"),
  y          = labels,
  x          = value,
  test.value = 55,
  type       = "robust",
  title      = "Indice de Gini por Órgão",
  xlab       = "Indice de Gini",
  ylab       = "Órgão",
  group      = name,  # Diferencia os anos por cor
  ggtheme    = theme_minimal(8)
) 

ggplotly(p)
Código
set.seed(123)

p <- ggdotplotstats(
  data       = df_long |> filter(name == "2026"),
  y          = labels,
  x          = value,
  test.value = 55,
  type       = "robust",
  title      = "Indice de Gini por Órgão",
  xlab       = "Indice de Gini",
  ylab       = "Órgão",
  group      = name,  # Diferencia os anos por cor
  ggtheme    = theme_minimal(8)
) 

ggplotly(p)

4 Estatísticas descritivas da remuneração dos servidores públicos federais

Código
library(plotly)
library(ggplot2)

# Gráfico de barras para 'subgrupo', separado por 'ano'
p1 <- ggplot(df_expandido, aes(x = subgrupo)) +
  geom_bar(fill = "steelblue") +
  theme_minimal() +
  labs(title = "Distribuição por Subgrupo ", x = "Subgrupo", y = "Contagem") 
  #facet_wrap(~ Ano) # Separa os gráficos por ano

ggplotly(p1) # Transforma em um gráfico interativo

5 Descritivas por ano

Código
library(gtsummary)
library(dplyr)

# Criar tabelas separadas para cada ano
tabela_2023 <- df_expandido %>%
  filter(Ano == "2023") %>%
  select(subgrupo, remun) %>%
  tbl_summary(by = subgrupo) 
#  modify_caption("**Ano 2023**")

tabela_2026 <- df_expandido %>%
  filter(Ano == "2026") %>%
  select(subgrupo, remun) %>%
  tbl_summary(by = subgrupo)
 # modify_caption("**Ano 2026**")

# Empilhar as tabelas verticalmente
tabela_empilhada <- tbl_stack(
  list(tabela_2023, tabela_2026),
  group_header = c("Ano 2023", "Ano 2026") # Adiciona título para cada tabela
)

# Exibir a tabela empilhada
tabela_empilhada
Characteristic AG REGIONAIS
N = 4621
AGENCIAS
N = 10,7361
Ambiental
N = 12,7691
C&T
N = 19,4581
EDUCAÇÃO
N = 281,4801
GESTÃO
N = 33,1241
INFRAESTRUTURA
N = 3,9731
PGPE-PST
N = 64,1351
POLICIAS
N = 30,3261
PREVIDENCIARIAS
N = 23,3021
Ano 2023
remun 13,633 (6,977, 14,458) 19,757 (10,347, 22,387) 12,157 (7,424, 15,580) 12,248 (8,972, 16,969) 9,407 (5,282, 14,468) 29,761 (19,541, 29,833) 13,042 (9,794, 18,138) 4,920 (4,743, 4,920) 16,641 (12,745, 20,330) 9,918 (9,553, 13,311)
Ano 2026
remun 15,603 (7,986, 16,547) 24,981 (14,543, 28,354) 15,289 (8,081, 18,290) 15,502 (11,488, 20,974) 10,981 (6,435, 16,877) 35,424 (24,641, 36,694) 15,478 (12,560, 23,167) 5,781 (5,558, 5,781) 19,617 (14,710, 25,250) 11,677 (11,243, 16,397)
1 Median (Q1, Q3)

6 Demais estatísticas descritivas

Código
library(dplyr)
library(scales)
library(gt)

gerar_resumo <- function(df, ano) {
  df %>%
    filter(Ano == ano) %>%
    group_by(subgrupo) %>%
    summarise(
      mean = label_dollar(big.mark = ".", decimal.mark = ",", prefix = "R$ ")(mean(remun, na.rm = TRUE)),
      sd = label_dollar(big.mark = ".", decimal.mark = ",", prefix = "R$ ")(sd(remun, na.rm = TRUE)),
      min = label_dollar(big.mark = ".", decimal.mark = ",", prefix = "R$ ")(min(remun, na.rm = TRUE)),
      max = label_dollar(big.mark = ".", decimal.mark = ",", prefix = "R$ ")(max(remun, na.rm = TRUE)),
      median = label_dollar(big.mark = ".", decimal.mark = ",", prefix = "R$ ")(median(remun, na.rm = TRUE)),
      cv = sd(remun, na.rm = TRUE) / mean(remun, na.rm = TRUE),
      n = n()
    )
}

gerar_tabela <- function(df_summary) {
  df_summary %>%
    gt() %>%
    tab_header(
      title = "Estatísticas Descritivas por Subgrupo"
    ) %>%
    tab_spanner(
      label = "Estatísticas",
      columns = c(mean, sd, min, max, median, cv)
    ) %>%
    cols_label(
      mean = "Média",
      sd = "Desvio Padrão",
      min = "Mínimo",
      max = "Máximo",
      median = "Mediana",
      cv = "Coeficiente de Variação",
      n = "Total de Observações"
    ) %>%
    fmt_currency(
      columns = vars(mean, sd, min, max, median),
      currency = "BRL",
      decimals = 2
    ) %>%
    fmt_number(
      columns = vars(cv),
      decimals = 2
    ) |> 
    tab_options(
      table.font.size = px(12)
    )
}

# Exemplo de uso:
df_summary <- gerar_resumo(df_expandido, "2023")
gerar_tabela(df_summary)
Estatísticas Descritivas por Subgrupo
subgrupo
Estatísticas
Total de Observações
Média Desvio Padrão Mínimo Máximo Mediana Coeficiente de Variação
AG REGIONAIS R$ 11.840,72 R$ 4.219,21 R$ 5.655,53 R$ 18.583,74 R$ 13.632,72 0.36 462
AGENCIAS R$ 16.592,39 R$ 6.025,08 R$ 2.865,44 R$ 25.718,98 R$ 19.756,72 0.36 10736
Ambiental R$ 12.055,63 R$ 5.569,30 R$ 3.164,93 R$ 22.210,10 R$ 12.156,86 0.46 12769
C&T R$ 13.041,26 R$ 4.592,01 R$ 2.712,70 R$ 21.758,55 R$ 12.247,70 0.35 19458
EDUCAÇÃO R$ 10.785,90 R$ 6.024,69 R$ 693,22 R$ 22.377,72 R$ 9.406,75 0.56 281480
GESTÃO R$ 26.611,26 R$ 6.205,90 R$ 6.789,73 R$ 32.760,95 R$ 29.761,03 0.23 33124
INFRAESTRUTURA R$ 14.265,28 R$ 4.298,76 R$ 6.861,38 R$ 22.552,44 R$ 13.042,18 0.30 3973
PGPE-PST R$ 5.476,14 R$ 1.791,27 R$ 2.369,86 R$ 14.371,42 R$ 4.920,30 0.33 64135
POLICIAS R$ 16.597,78 R$ 6.774,10 R$ 3.579,79 R$ 33.721,23 R$ 16.641,32 0.41 30326
PREVIDENCIARIAS R$ 11.631,20 R$ 4.488,61 R$ 3.066,01 R$ 22.625,14 R$ 9.918,12 0.39 23302
Código
df_summary <- gerar_resumo(df_expandido, "2026")
gerar_tabela(df_summary)
Estatísticas Descritivas por Subgrupo
subgrupo
Estatísticas
Total de Observações
Média Desvio Padrão Mínimo Máximo Mediana Coeficiente de Variação
AG REGIONAIS R$ 13.551,70 R$ 4.828,89 R$ 6.472,76 R$ 21.269,09 R$ 15.602,66 0.36 462
AGENCIAS R$ 21.679,42 R$ 7.106,18 R$ 3.947,94 R$ 32.657,66 R$ 24.980,77 0.33 10736
Ambiental R$ 14.338,16 R$ 6.317,05 R$ 3.622,26 R$ 25.551,39 R$ 15.289,36 0.44 12769
C&T R$ 15.858,45 R$ 5.665,60 R$ 3.104,69 R$ 25.571,24 R$ 15.501,68 0.36 19458
EDUCAÇÃO R$ 12.696,25 R$ 6.905,63 R$ 1.694,42 R$ 26.326,81 R$ 10.980,93 0.54 281480
GESTÃO R$ 32.619,43 R$ 7.801,71 R$ 7.770,85 R$ 41.260,95 R$ 35.423,96 0.24 33124
INFRAESTRUTURA R$ 17.625,93 R$ 5.465,72 R$ 8.911,38 R$ 26.580 R$ 15.477,79 0.31 3973
PGPE-PST R$ 6.390,28 R$ 2.099,64 R$ 2.712,31 R$ 16.909,97 R$ 5.780,60 0.33 64135
POLICIAS R$ 20.263,79 R$ 8.390,96 R$ 4.097,07 R$ 41.350 R$ 19.617,37 0.41 30326
PREVIDENCIARIAS R$ 13.814,94 R$ 5.383,02 R$ 3.520,26 R$ 26.696,57 R$ 11.676,78 0.39 23302

7 Comparação de anos em uma única tabela

Código
library(dplyr)
library(scales)
library(gt)

gerar_resumo_duplo <- function(df, ano1, ano2) {
  df_summary_ano1 <- df %>%
    filter(Ano == ano1) %>%
    group_by(subgrupo) %>%
    summarise(
      mean = mean(remun, na.rm = TRUE),
      sd = sd(remun, na.rm = TRUE),
      min = min(remun, na.rm = TRUE),
      max = max(remun, na.rm = TRUE),
      median = median(remun, na.rm = TRUE),
      cv = sd(remun, na.rm = TRUE) / mean(remun, na.rm = TRUE),
      n = n(),
      .groups = "drop"
    )

  df_summary_ano2 <- df %>%
    filter(Ano == ano2) %>%
    group_by(subgrupo) %>%
    summarise(
      mean = mean(remun, na.rm = TRUE),
      sd = sd(remun, na.rm = TRUE),
      min = min(remun, na.rm = TRUE),
      max = max(remun, na.rm = TRUE),
      median = median(remun, na.rm = TRUE),
      cv = sd(remun, na.rm = TRUE) / mean(remun, na.rm = TRUE),
      n = n(),
      .groups = "drop"
    )

  # Juntar os dois dataframes pela coluna 'subgrupo'
  df_summary <- df_summary_ano1 %>%
    inner_join(df_summary_ano2, by = "subgrupo", suffix = c(paste0("_", ano1), paste0("_", ano2))) %>%
    mutate(
      mean = paste0(
        label_dollar(big.mark = ".", decimal.mark = ",", prefix = "R$ ")(mean_2023),
        " -> ",
        label_dollar(big.mark = ".", decimal.mark = ",", prefix = "R$ ")(mean_2026)
      ),
      sd = paste0(
        label_dollar(big.mark = ".", decimal.mark = ",", prefix = "R$ ")(sd_2023),
        " -> ",
        label_dollar(big.mark = ".", decimal.mark = ",", prefix = "R$ ")(sd_2026)
      ),
      min = paste0(
        label_dollar(big.mark = ".", decimal.mark = ",", prefix = "R$ ")(min_2023),
        " -> ",
        label_dollar(big.mark = ".", decimal.mark = ",", prefix = "R$ ")(min_2026)
      ),
      max = paste0(
        label_dollar(big.mark = ".", decimal.mark = ",", prefix = "R$ ")(max_2023),
        " -> ",
        label_dollar(big.mark = ".", decimal.mark = ",", prefix = "R$ ")(max_2026)
      ),
      median = paste0(
        label_dollar(big.mark = ".", decimal.mark = ",", prefix = "R$ ")(median_2023),
        " -> ",
        label_dollar(big.mark = ".", decimal.mark = ",", prefix = "R$ ")(median_2026)
      ),
      cv = paste0(round(cv_2023, 2), " -> ", round(cv_2026, 2)),
      n = paste0(n_2023, " -> ", n_2026)
    ) %>%
    select(subgrupo, mean, sd, min, max, median, cv, n)

  return(df_summary)
}

gerar_tabela <- function(df_summary) {
  df_summary %>%
    gt() %>%
    tab_header(
      title = "Estatísticas Descritivas por Subgrupo (Comparação de Anos)"
    ) %>%
    tab_spanner(
      label = "Estatísticas (2023 -> 2026)",
      columns = c(mean, sd, min, max, median, cv)
    ) %>%
    cols_label(
      mean = "Média",
      sd = "Desvio Padrão",
      min = "Mínimo",
      max = "Máximo",
      median = "Mediana",
      cv = "Coeficiente de Variação",
      n = "Total de Observações"
    ) |> 
    tab_options(
      table.font.size = px(12)
    )
}

# Exemplo de uso:
df_summary <- gerar_resumo_duplo(df_expandido, "2023", "2026")
gerar_tabela(df_summary)
Estatísticas Descritivas por Subgrupo (Comparação de Anos)
subgrupo
Estatísticas (2023 -> 2026)
Total de Observações
Média Desvio Padrão Mínimo Máximo Mediana Coeficiente de Variação
AG REGIONAIS R$ 11.840,72 -> R$ 13.551,70 R$ 4.219,21 -> R$ 4.828,89 R$ 5.655,53 -> R$ 6.472,76 R$ 18.583,74 -> R$ 21.269,09 R$ 13.632,72 -> R$ 15.602,66 0.36 -> 0.36 462 -> 462
AGENCIAS R$ 16.592,39 -> R$ 21.679,42 R$ 6.025,08 -> R$ 7.106,18 R$ 2.865,44 -> R$ 3.947,94 R$ 25.718,98 -> R$ 32.657,66 R$ 19.756,72 -> R$ 24.980,77 0.36 -> 0.33 10736 -> 10736
Ambiental R$ 12.055,63 -> R$ 14.338,16 R$ 5.569,30 -> R$ 6.317,05 R$ 3.164,93 -> R$ 3.622,26 R$ 22.210,10 -> R$ 25.551,39 R$ 12.156,86 -> R$ 15.289,36 0.46 -> 0.44 12769 -> 12769
C&T R$ 13.041,26 -> R$ 15.858,45 R$ 4.592,01 -> R$ 5.665,60 R$ 2.712,70 -> R$ 3.104,69 R$ 21.758,55 -> R$ 25.571,24 R$ 12.247,70 -> R$ 15.501,68 0.35 -> 0.36 19458 -> 19458
EDUCAÇÃO R$ 10.785,90 -> R$ 12.696,25 R$ 6.024,69 -> R$ 6.905,63 R$ 693,22 -> R$ 1.694,42 R$ 22.377,72 -> R$ 26.326,81 R$ 9.406,75 -> R$ 10.980,93 0.56 -> 0.54 281480 -> 281480
GESTÃO R$ 26.611,26 -> R$ 32.619,43 R$ 6.205,90 -> R$ 7.801,71 R$ 6.789,73 -> R$ 7.770,85 R$ 32.760,95 -> R$ 41.260,95 R$ 29.761,03 -> R$ 35.423,96 0.23 -> 0.24 33124 -> 33124
INFRAESTRUTURA R$ 14.265,28 -> R$ 17.625,93 R$ 4.298,76 -> R$ 5.465,72 R$ 6.861,38 -> R$ 8.911,38 R$ 22.552,44 -> R$ 26.580,00 R$ 13.042,18 -> R$ 15.477,79 0.3 -> 0.31 3973 -> 3973
PGPE-PST R$ 5.476,14 -> R$ 6.390,28 R$ 1.791,27 -> R$ 2.099,64 R$ 2.369,86 -> R$ 2.712,31 R$ 14.371,42 -> R$ 16.909,97 R$ 4.920,30 -> R$ 5.780,60 0.33 -> 0.33 64135 -> 64135
POLICIAS R$ 16.597,78 -> R$ 20.263,79 R$ 6.774,10 -> R$ 8.390,96 R$ 3.579,79 -> R$ 4.097,07 R$ 33.721,23 -> R$ 41.350,00 R$ 16.641,32 -> R$ 19.617,37 0.41 -> 0.41 30326 -> 30326
PREVIDENCIARIAS R$ 11.631,20 -> R$ 13.814,94 R$ 4.488,61 -> R$ 5.383,02 R$ 3.066,01 -> R$ 3.520,26 R$ 22.625,14 -> R$ 26.696,57 R$ 9.918,12 -> R$ 11.676,78 0.39 -> 0.39 23302 -> 23302

Box Plot

Código
library(ggplot2)
library(scales)  # Para formatar como moeda

gerar_boxplot <- function(df, ano) {
  ggplot(df |> filter(Ano == ano), aes(x = subgrupo, y = remun)) +
    geom_boxplot(fill = "skyblue", color = "black", alpha = 0.5) +  # Boxplot
    stat_summary(  # Adiciona a média como ponto
      fun = mean,  
      geom = "point",  
      shape = 18,  # Formato do ponto (estrela)
      size = 3,  
      color = "red"
    ) +
    stat_summary(  # Adiciona o valor da média como texto formatado
      fun = mean,  
      geom = "text",  
      aes(label = scales::label_dollar(prefix = "R$ ", big.mark = ".", decimal.mark = ",")(..y..)),  
      vjust = -0.5,  
      color = "red",  
      size = 3.5  
    ) +
    scale_y_continuous(labels = label_dollar(prefix = "R$ ", big.mark = ".", decimal.mark = ",")) +  # Formata eixo Y
    labs(
      title = paste("Distribuição de Remuneração por Subgrupo -", ano),
      x = "Subgrupo",
      y = "Remuneração"
    ) +
    annotate(
      "text", x = 1, y = max(df$remun, na.rm = TRUE), 
      label = "Média da Remuneração", 
      color = "red", size = 4, hjust = 0
    ) +  # Anotação no gráfico
    theme_minimal() +
    theme(
      axis.text.x = element_text(angle = 45, hjust = 1, size = 10)  # Rotaciona e reduz a fonte do eixo X
    )
}

# Exemplo de uso:
gerar_boxplot(df_expandido, "2023")

Código
gerar_boxplot(df_expandido, "2026")

2023

Código
ggplot(df_expandido |> filter(Ano == "2023"), aes(x = remun)) +
  geom_histogram(binwidth = 500, fill = "lightgreen", alpha = 0.7) +
  facet_wrap(~subgrupo) +  # Facetando por subgrupo
  labs(
    title = "Distribuição da Remuneração por Subgrupo",
    x = "Remuneração",
    y = "Frequência"
  ) +
  theme_minimal()

2026

Código
ggplot(df_expandido |> filter(Ano == "2026"), aes(x = remun)) +
  geom_histogram(binwidth = 500, fill = "lightgreen", alpha = 0.7) +
  facet_wrap(~subgrupo) +  # Facetando por subgrupo
  labs(
    title = "Distribuição da Remuneração por Subgrupo",
    x = "Remuneração",
    y = "Frequência"
  ) +
  theme_minimal()

8 Comparação de médias entre grupos

2023

Código
# df_expandido |> filter(Ano == "2023") |> ggstatsplot::ggbetweenstats(
#   x = subgrupo,
#   y = remun,
#   title = "Distribuição de Remuneração por Subgrupo (2023)"
# )

2026

Código
# df_expandido |> filter(Ano == "2026") |> ggstatsplot::ggbetweenstats(
#   x = subgrupo,
#   y = remun,
#   title = "Distribuição de Remuneração por Subgrupo (2023)"
# )

9 Referências

Hoffmann, Rodolfo. 2006. Estatística para economistas. 1.ª ed. Cengage Learning.