Análise de riscos para o Burnout no ambiente de trabalho


1 Introdução

1.1 O problema

O burnout se caracteriza como um fenômeno ocupacional de esgotamento profissional reconhecido pela Organização Mundial da Saúde (OMS) desde 2019, tornando-se um dos temas mais discutidos dentro da saúde organizacional (World Health Organization (2019)). Em especial desde a pandemia de COVID-19, que mudou significativamente os padrões de trabalho, as discussões sobre o burnout se tornaram cada vez mais relevantes no que diz respeito à retenção de talentos, considerando que pesquisas apontam que o burnout é um fator importante na intenção de funcionários deixarem os seus empregos (Trinkenreich et al. (2023)).

Segundo estimativas de um estudo de referência do Instituto Americano de Estresse, o estresse ocupacional custa às empresas norte-americanas mais de US$ 300 bilhões por ano em fatores como rotatividade e falta de desempenho (American Institute of Stress (2024)). No Brasil, o burnout foi reconhecido na portaria GM/MS Nº 1.999 como doença ocupacional em 2022, impondo responsabilidades legais crescentes às empresas (Brasil. Ministério da Saúde (2023)).

Tais questões levantam a seguinte pergunta de pesquisa:

Quais fatores organizacionais e individuais estão mais associados ao risco de burnout, e como eles se relacionam no ambiente de trabalho?

Responder a esta pergunta se torna relevante para: equipes e gestores de RH, que precisam identificar perfis de risco antes que o funcionário solicite demissão; executivos que necessitam entender quais departamentos e cargos concentram maior esgotamento; e pesquisadores interessados nos fatores comportamentais e organizacionais do burnout em larga escala.

1.2 Abordagem e metodologia

Para responder à pergunta de pesquisa, foram conduzidas duas etapas de análise.

Na primeira etapa, o Synthetic Employee Dataset, disponibilizado publicamente no Hugging Face com 850.000 perfis sintéticos de funcionários, foi importado, limpo e organizado. O dataset contém variáveis numéricas (scores de estresse, carga de trabalho, desempenho), categóricas (departamento, cargo, arquétipo comportamental), booleanas (saída da empresa) e textuais (feedbacks). A limpeza incluiu padronização de nomes, conversão de tipos, tratamento de valores ausentes, desaninhamento de listas de habilidades e criação de variáveis derivadas, entre elas, um índice de engajamento e um score de desequilíbrio esforço-recompensa inspirado no modelo de Siegrist (1996).

Na segunda etapa, apresentada neste documento, conduz-se a análise exploratória dos dados (EDA). Primeiro examina-se a distribuição global do burnout; em seguida investiga-se como ele varia entre departamentos, níveis de cargo e perfis comportamentais; depois explora-se a relação entre burnout e as variáveis de esforço (horas extras, carga de trabalho) e recompensa (salário, satisfação); e por fim constrói-se um perfil do funcionário em risco severo de saída por burnout. Os resultados são apresentados por meio de gráficos estáticos, tabelas interativas (pacote DT) e um gráfico interativo (pacote plotly).


2 Pacotes necessários

library(tidyverse)   # Manipulação e visualização de dados (dplyr, ggplot2, etc.)
library(janitor)     # Padronização de nomes de colunas
library(arrow)       # Leitura de arquivos no formato Parquet
library(knitr)       # Geração de tabelas formatadas no relatório
library(kableExtra)  # Estilização de tabelas estáticas
library(skimr)       # Sumários estatísticos compactos
library(plotly)      # Gráficos interativos
library(DT)          # Tabelas interativas com busca e ordenação
library(jsonlite)    # Carregamento de dados em JSON

3 Preparação dos dados

3.1 Fonte e descrição

Os dados foram obtidos do repositório público do Hugging Face:

BrotherTony. (2025). Synthetic Employee Dataset: 800K+ Records for HR Analytics. Hugging Face. https://huggingface.co/datasets/BrotherTony/employee-burnout-turnover-prediction-800k

3.1.1 Contexto e propósito original

O dataset corresponde a um conjunto de dados sintéticos, construído com base em padrões observados em ambientes corporativos reais. Seu propósito original é servir como base para modelos preditivos de burnout e análises de contextos de RH.

3.1.2 Características do dataset bruto

Atributo Valor
Total de registros ~850.000
Número de variáveis 31
Formato original Parquet/Arquivo direto
Idioma Inglês
Licença CC-BY-NC-ND 4.0 (uso permitido em contexto não comercial e com atribuição)

3.1.3 Variáveis originais

Variáveis do dataset bruto
Variável Tipo Descrição
employee_id character Identificador único
role character Cargo (diversas variações)
job_level character Nível: Entry, Mid, Senior, Manager, Lead
department character Departamento (38 divisões)
tenure_months integer Tempo de empresa em meses
salary double Salário anual em USD
performance_score double Score de desempenho (0–1)
satisfaction_score double Score de satisfação (0–1)
workload_score double Carga de trabalho (0–1)
team_sentiment double Sentimento da equipe (0–1)
recent_feedback character Texto livre de avaliação do funcionário
communication_patterns character Estilo de comunicação baseado em persona
project_completion_rate double Taxa de conclusão de projetos (0–1)
overtime_hours double Horas extras
training_participation double Participação em treinamentos (0–1)
collaboration_score double Score de colaboração (0–1)
technical_skills list Habilidades técnicas por funcionário
soft_skills list Habilidades interpessoais por funcionário
email_sentiment double Sentimento do e-mail (0–1)
slack_activity double Atividade no Slack (0–1)
meeting_participation double Participação em reuniões (0–1)
goal_achievement_rate double Taxa de atingimento de metas (0–1)
stress_level double Nível de estresse (0–1)
burnout_risk double Risco de burnout (0–1)
left_company logical Saiu da empresa? (TRUE/FALSE)
turnover_reason character Motivo da saída (quando aplicável)
risk_factors_summary character Resumo do risco: assume apenas ‘Low Risk’ e ‘Severe Burnout Risk’ no dataset
turnover_probability_generated double Probabilidade de saída gerada por ML
persona_name character Arquétipo comportamental (12 tipos)
role_complexity_score double Complexidade do cargo (0–1)
career_progression_score double Progressão de carreira (0–1)

3.2 Importação dos dados

O dataset é distribuído no formato Parquet, um formato binário que preserva os tipos de cada coluna e ocupa muito menos espaço que um CSV equivalente. O pacote arrow permite ler arquivos Parquet diretamente de uma URL. O primeiro shard contém aproximadamente 95.000 registros com os 31 atributos originais. Nos primeiros testes, este arquivo foi importado. Contudo, para esta versão, importa-se o arquivo JSON com todos os registros disponibilizados na fonte (como citado acima, cerca de 850.000 observações).

url_parquet <- paste0(
  "https://huggingface.co/datasets/BrotherTony/",
  "employee-burnout-turnover-prediction-800k/",
  "resolve/refs%2Fconvert%2Fparquet/default/train/0000.parquet"
)

arquivo_local <- "0000.parquet"

if (!file.exists(arquivo_local)) {
  download.file(url_parquet, destfile = arquivo_local, mode = "wb")
}

#df_bruto <- read_parquet(arquivo_local) # Primeira possibilidade: importação de parte dos dados via Parquet

df_bruto <-fromJSON("synthetic-employee-dataset.json") # Importação completa dos dados

3.3 Etapas de limpeza e organização

3.3.1 Padronização dos nomes das variáveis

A função clean_names() do pacote janitor converte todos os nomes de colunas para snake_case, sem espaços ou caracteres especiais.

df <- clean_names(df_bruto)
names(df)
##  [1] "employee_id"                    "role"                          
##  [3] "job_level"                      "department"                    
##  [5] "tenure_months"                  "salary"                        
##  [7] "performance_score"              "satisfaction_score"            
##  [9] "workload_score"                 "team_sentiment"                
## [11] "recent_feedback"                "communication_patterns"        
## [13] "project_completion_rate"        "overtime_hours"                
## [15] "training_participation"         "collaboration_score"           
## [17] "technical_skills"               "soft_skills"                   
## [19] "email_sentiment"                "slack_activity"                
## [21] "meeting_participation"          "goal_achievement_rate"         
## [23] "stress_level"                   "burnout_risk"                  
## [25] "left_company"                   "turnover_reason"               
## [27] "risk_factors_summary"           "turnover_probability_generated"
## [29] "persona_name"                   "role_complexity_score"         
## [31] "career_progression_score"

3.3.2 Conversão de tipos de dados

job_level precisa se tornar um fator ordenado para permitir a comparação entre níveis, e tenure_months é convertido explicitamente para inteiro.

df$job_level <- factor(df$job_level,
                       levels  = c("Entry", "Mid", "Senior", "Manager", "Lead"),
                       ordered = TRUE)

df$tenure_months <- as.integer(df$tenure_months)

glimpse(df)
## Rows: 849,999
## Columns: 31
## $ employee_id                    <chr> "SYN_00000000", "SYN_00000001", "SYN_00…
## $ role                           <chr> " ", " Customer Success Manager", " Adm…
## $ job_level                      <ord> Mid, Manager, Entry, Manager, Mid, Mid,…
## $ department                     <chr> "Research & Development", "Research & D…
## $ tenure_months                  <int> 169, 54, 1, 31, 131, 34, 100, 77, 107, …
## $ salary                         <dbl> 79704.58, 29694.29, 62208.47, 236066.57…
## $ performance_score              <dbl> 0.6324821, 0.5385873, 0.6246560, 0.9593…
## $ satisfaction_score             <dbl> 0.6237460, 0.9825562, 0.7672001, 0.1858…
## $ workload_score                 <dbl> 0.7581168, 0.7884164, 0.6976166, 0.4931…
## $ team_sentiment                 <dbl> 0.6623349, 0.9346612, 0.8885591, 0.7321…
## $ recent_feedback                <chr> "Close to home and good shift time give…
## $ communication_patterns         <chr> "Exhibits communication style typical o…
## $ project_completion_rate        <dbl> 0.5241873, 0.5582056, 0.5668491, 0.7674…
## $ overtime_hours                 <dbl> 0.000000, 0.000000, 0.000000, 9.591680,…
## $ training_participation         <dbl> 0.04437344, 0.31485786, 0.74490049, 0.2…
## $ collaboration_score            <dbl> 0.4921307, 0.9813943, 0.7011382, 0.3396…
## $ technical_skills               <list> <"REST APIs", "Python (Pandas, NumPy, …
## $ soft_skills                    <list> <"Communication", "Creativity", "Teamw…
## $ email_sentiment                <dbl> 0.6328913, 1.0000000, 0.7580049, 0.2025…
## $ slack_activity                 <dbl> 0.4921307, 0.9813943, 0.7011382, 0.3396…
## $ meeting_participation          <dbl> 0.4921307, 0.9813943, 0.7011382, 0.3396…
## $ goal_achievement_rate          <dbl> 0.6324821, 0.5385873, 0.6246560, 0.9593…
## $ stress_level                   <dbl> 0.9089919, 0.3633211, 0.6643785, 1.0000…
## $ burnout_risk                   <dbl> 0.8666426, 0.2189959, 0.5415310, 1.0000…
## $ left_company                   <lgl> FALSE, FALSE, TRUE, FALSE, FALSE, FALSE…
## $ turnover_reason                <chr> "Not Applicable", "Not Applicable", "Pe…
## $ risk_factors_summary           <chr> "Severe Burnout Risk", "Low Risk", "Low…
## $ turnover_probability_generated <dbl> 0.2909790, 0.1560017, 0.2338970, 0.3516…
## $ persona_name                   <chr> "ChangeResistor", "NewEnthusiast", "New…
## $ role_complexity_score          <dbl> 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2,…
## $ career_progression_score       <dbl> 1.0000000, 1.0000000, 0.8364946, 1.0000…

3.3.3 Tratamento de valores ausentes e strings especiais

turnover_reason usa a string "Not Applicable" para funcionários ainda ativos. Para o contexto do R, converte-se para NA. Em adição, padroniza-se a capitalização de risk_factors_summary e remove-se espaços extras nas colunas de texto.

df$turnover_reason[df$turnover_reason == "Not Applicable"] <- NA
df$turnover_reason[df$turnover_reason == ""]               <- NA

df$risk_factors_summary <- str_to_title(trimws(df$risk_factors_summary))

df$risk_factors_summary <- factor(
  df$risk_factors_summary,
  levels  = c("Low Risk", "Medium Risk", "High Risk", "Severe Burnout Risk"),
  ordered = TRUE
)

df$persona_name           <- trimws(df$persona_name)
df$role                   <- trimws(df$role)
df$department             <- trimws(df$department)
df$communication_patterns <- trimws(df$communication_patterns)
df$recent_feedback        <- trimws(df$recent_feedback)

n_nas <- colSums(is.na(df))
n_nas <- n_nas[n_nas > 0]

if (length(n_nas) > 0) {
  na_tabela <- data.frame(
    Variável   = names(n_nas),
    N_ausentes = as.integer(n_nas)
  )
  kable(na_tabela,
        caption   = "Valores ausentes por variável (após tratamento)",
        col.names = c("Variável", "Nº de ausentes"),
        row.names = FALSE) |>
    kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
} else {
  cat("Nenhum valor ausente detectado.\n")
}
Valores ausentes por variável (após tratamento)
Variável Nº de ausentes
turnover_reason 604246
risk_factors_summary 44784

3.3.4 Processamento das colunas de habilidades

As colunas technical_skills e soft_skills contêm, em cada célula, uma lista de habilidades do funcionário. Contam-se as habilidades por funcionário e criam-se datasets expandidos para análise de frequência.

df$n_tech_skills <- sapply(df$technical_skills, length)
df$n_soft_skills <- sapply(df$soft_skills, length)

df_tech_skills <- df[, c("role", "job_level", "department",
                          "persona_name", "burnout_risk",
                          "left_company", "technical_skills")]
df_tech_skills <- tidyr::unnest(df_tech_skills, technical_skills)
names(df_tech_skills)[names(df_tech_skills) == "technical_skills"] <- "skill"
df_tech_skills$skill <- trimws(as.character(df_tech_skills$skill))

df_soft_skills <- df[, c("role", "job_level", "department",
                          "persona_name", "burnout_risk",
                          "left_company", "soft_skills")]
df_soft_skills <- tidyr::unnest(df_soft_skills, soft_skills)
names(df_soft_skills)[names(df_soft_skills) == "soft_skills"] <- "skill"
df_soft_skills$skill <- trimws(as.character(df_soft_skills$skill))

3.3.5 Criação de variáveis derivadas

Criam-se novas variáveis a partir das existentes. Em especial, desenvolve-se um score composto inspirado no modelo de desequilíbrio esforço-recompensa de Siegrist (1996), que identifica a combinação de alta demanda e baixa recompensa como base do esgotamento ocupacional. A operacionalização com pesos numéricos (0.4/0.3/0.3) é uma adaptação exploratória para as variáveis disponíveis no dataset, e não reproduz diretamente nenhuma fórmula original do autor.

High Effort (extrinsic: demands/obligations + intrinsic: need for control) <-> Low Reward (money + esteem + status control)

Em adição, cria-se uma nova variável burnout_level, à despeito da risk_factors_summary, a partir dos valores de burnout_risk. A razão disto é que, nesta última, a fonte só trouxe ocorrências de “Low Risk” e “Severe Burnout Risk”.

# 1. Faixa salarial
df$faixa_salarial <- cut(
  df$salary,
  breaks = c(0, 50000, 80000, 120000, 200000, Inf),
  labels = c("< 50K", "50K-80K", "80K-120K", "120K-200K", "> 200K"),
  right  = FALSE
)

# 2. Tempo de empresa em anos
df$tenure_anos <- df$tenure_months / 12

# 3. Grupos de tenure (fases de carreira)
df$grupo_tenure <- cut(
  df$tenure_anos,
  breaks = c(0, 1, 3, 5, 10, Inf),
  labels = c("< 1 ano", "1-3 anos", "3-5 anos", "5-10 anos", "> 10 anos"),
  right  = FALSE
)

# 4. Índice de engajamento: média entre satisfação, colaboração e sentimento da equipe
df$indice_engajamento <- (df$satisfaction_score +
                            df$collaboration_score +
                            df$team_sentiment) / 3

# 5. Score de desequilíbrio esforço-recompensa (inspirado em Siegrist, 1996)
df$score_desequilibrio <- (df$workload_score * 0.4) +
  (df$overtime_hours / max(df$overtime_hours, na.rm = TRUE) * 0.3) +
  ((1 - df$satisfaction_score) * 0.3)

# 6. Flag de alto risco de burnout (acima do percentil 75)
df$alto_risco_burnout <- df$burnout_risk >= quantile(df$burnout_risk, 0.75,
                                                      na.rm = TRUE)

# 7. Flag: saída associada ao burnout
df$saida_por_burnout <- df$left_company & df$alto_risco_burnout

# 8. Variável de nível de risco de burnout baseada no score contínuo
# A variável original risk_factors_summary assume apenas dois valores no dataset
# (Low Risk e Severe Burnout Risk), sem granularidade suficiente para análise.
# Cria-se aqui uma variável própria a partir de burnout_risk, um dado contínuo,
# dividida em quatro faixas.
df$burnout_level <- cut(
  df$burnout_risk,
  breaks = c(0, 0.3, 0.6, 0.8, 1.01),
  labels = c("Low", "Medium", "High", "Severe"),
  right  = FALSE,
  include.lowest = TRUE
)

df$burnout_level <- factor(df$burnout_level,
                           levels  = c("Low", "Medium", "High", "Severe"),
                           ordered = TRUE)

3.3.6 Remoção de colunas desnecessárias

df$technical_skills <- NULL
df$soft_skills      <- NULL
df$employee_id      <- NULL

cat(sprintf("Dataset limpo final: %d linhas × %d colunas\n", nrow(df), ncol(df)))
## Dataset limpo final: 849999 linhas × 38 colunas

3.3.7 Verificação de duplicatas

n_dup <- sum(duplicated(df))
cat(sprintf("Registros duplicados: %d\n", n_dup))
## Registros duplicados: 0
if (n_dup > 0) {
  df <- unique(df)
  cat(sprintf("Após remoção: %d linhas\n", nrow(df)))
}

3.4 Visão do dataset final

df[1:10, c("role", "job_level", "department", "tenure_anos",
            "stress_level", "burnout_risk", "satisfaction_score",
            "overtime_hours", "left_company",
            "risk_factors_summary", "burnout_level", "persona_name")] |>
  kable(caption = "Amostra do dataset limpo (10 primeiras observações)",
        digits = 3) |>
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                font_size = 11, full_width = TRUE) |>
  scroll_box(width = "100%", height = "380px")
Amostra do dataset limpo (10 primeiras observações)
role job_level department tenure_anos stress_level burnout_risk satisfaction_score overtime_hours left_company risk_factors_summary burnout_level persona_name
Mid Research & Development 14.083 0.909 0.867 0.624 0.000 FALSE Severe Burnout Risk Severe ChangeResistor
Customer Success Manager Manager Research & Development 4.500 0.363 0.219 0.983 0.000 FALSE Low Risk Low NewEnthusiast
Administrative Assistant Entry HR 0.083 0.664 0.542 0.767 0.000 TRUE Low Risk Medium NewEnthusiast
Senior Manager Manager Research & Development 2.583 1.000 1.000 0.186 9.592 FALSE Severe Burnout Risk Severe OverachievingSprinter
Anonymous Employee Mid Research & Development 10.917 0.723 0.615 0.567 0.000 FALSE Low Risk High SeasonedExpert
Anonymous Employee Mid Dairy 2.833 0.362 0.217 0.909 0.000 FALSE Low Risk Low QuietAchiever
Anonymous Employee Mid Procurement 8.333 0.457 0.309 0.895 0.000 FALSE Low Risk Medium QuietAchiever
Senior Business Analyst Senior Meats 6.417 0.599 0.464 0.708 0.000 FALSE Low Risk Medium SocialCatalyst
Senior Manager Manager Sales & Marketing 8.917 0.955 0.934 0.616 0.000 FALSE Severe Burnout Risk Severe StrugglingLearner
Sales Mid Procurement 14.083 0.401 0.254 1.000 0.000 FALSE Low Risk Low QuietAchiever

3.5 Sumário das variáveis de interesse

vars_foco <- c("stress_level", "burnout_risk", "satisfaction_score",
               "workload_score", "overtime_hours",
               "indice_engajamento", "score_desequilibrio")

resumo <- data.frame(
  Variável      = vars_foco,
  Mínimo        = round(sapply(df[vars_foco], min,    na.rm = TRUE), 3),
  Mediana       = round(sapply(df[vars_foco], median, na.rm = TRUE), 3),
  Média         = round(sapply(df[vars_foco], mean,   na.rm = TRUE), 3),
  Máximo        = round(sapply(df[vars_foco], max,    na.rm = TRUE), 3),
  Desvio_Padrao = round(sapply(df[vars_foco], sd,     na.rm = TRUE), 3),
  Pct_Ausentes  = round(sapply(df[vars_foco],
                               function(x) mean(is.na(x)) * 100), 1),
  row.names     = NULL
)

kable(resumo,
      caption   = "Sumário estatístico das variáveis contínuas de interesse",
      col.names = c("Variável", "Mínimo", "Mediana", "Média",
                    "Máximo", "Desvio Padrão", "% Ausentes")) |>
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) |>
  column_spec(1, bold = TRUE, monospace = TRUE)
Sumário estatístico das variáveis contínuas de interesse
Variável Mínimo Mediana Média Máximo Desvio Padrão % Ausentes
stress_level 0.000 0.942 0.791 1.000 0.265 0
burnout_risk 0.000 0.914 0.736 1.000 0.316 0
satisfaction_score 0.050 0.583 0.582 1.000 0.276 0
workload_score 0.007 0.614 0.599 1.000 0.200 0
overtime_hours 0.000 0.000 3.133 73.954 6.094 0
indice_engajamento 0.041 0.570 0.574 0.999 0.169 0
score_desequilibrio 0.013 0.377 0.378 0.693 0.107 0

Destaques do sumário:

  • O burnout_risk médio da amostra é 0.74 (escala 0–1).
  • 28.5% dos funcionários saíram da empresa.
  • Funcionários com nível de risco Severe representam 56.2% da amostra.
  • O máximo de overtime_hours é 73.9535736h.

3.5.1 Distribuição das variáveis categóricas principais

risco_tab <- as.data.frame(table(df$burnout_level))
risco_tab$Pct <- round(risco_tab$Freq / sum(risco_tab$Freq) * 100, 1)
names(risco_tab) <- c("Nível de Risco", "N", "%")

kable(risco_tab,
      caption   = "Distribuição por nível de risco de burnout",
      row.names = FALSE) |>
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Distribuição por nível de risco de burnout
Nível de Risco N %
Low 121311 14.3
Medium 153101 18.0
High 98040 11.5
Severe 477547 56.2
persona_tab <- as.data.frame(table(df$persona_name))
persona_tab <- persona_tab[order(-persona_tab$Freq), ]
persona_tab$Pct <- round(persona_tab$Freq / sum(persona_tab$Freq) * 100, 1)
names(persona_tab) <- c("Persona", "N", "%")

kable(persona_tab,
      caption   = "Distribuição por arquétipo comportamental (persona)",
      row.names = FALSE) |>
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Distribuição por arquétipo comportamental (persona)
Persona N %
StrugglingLearner 86714 10.2
CorporateNavigator 69639 8.2
SteadyEddy 69611 8.2
NewEnthusiast 69566 8.2
QuietAchiever 69525 8.2
9-to-5Clockwatcher 69434 8.2
ChangeResistor 69337 8.2
SeasonedExpert 69293 8.2
SocialCatalyst 69273 8.1
OverachievingSprinter 69243 8.1
BurntOutStar 69209 8.1
AmbitiousClimber 69155 8.1

4 Análise Exploratória dos Dados

A análise parte de uma pergunta central: Quais fatores organizacionais e individuais estão mais associados ao risco de burnout, e como eles se relacionam no ambiente de trabalho? Para responder, a exploração é organizada: (1) distribuição geral do risco de burnout; (2) variação por cargo e departamento; e (3) relação com esforço e recompensa.

4.1 Distribuição geral do risco de burnout

Inicialmente, busca-se entender como o risco de burnout se distribui na população listada. Como a variável original risk_factors_summary assume apenas dois valores no dataset (Low Risk e Severe Burnout Risk), utiliza-se a variável derivada burnout_level, criada a partir do score contínuo burnout_risk com quatro faixas: Low (< 0.3), Medium (0.3–0.6), High (0.6–0.8) e Severe (≥ 0.8), que permite uma leitura mais específica do risco.

risco_dist <- as.data.frame(table(df$burnout_level)) # Contar e calcular proporções por categoria de risco
risco_dist$pct <- risco_dist$Freq / sum(risco_dist$Freq) * 100
risco_dist$label <- paste0(round(risco_dist$pct, 1), "%")
names(risco_dist)[1] <- "categoria"

cores_risco <- c("Low"    = "#2ecc71", #Paleta para os riscos
                 "Medium" = "#f39c12",
                 "High"   = "#e74c3c",
                 "Severe" = "#7b241c")

p1 <- ggplot(risco_dist, aes(x = categoria, y = pct, fill = categoria)) +
  geom_col(width = 0.6, alpha = 0.9) +
  geom_text(aes(label = label), vjust = -0.5, size = 4, fontface = "bold") +
  scale_fill_manual(values = cores_risco) +
  scale_y_continuous(limits = c(0, 75),
                     labels = function(x) paste0(x, "%")) +
  labs(title    = "Distribuição dos funcionários por nível de risco de burnout",
       subtitle = paste0("Total de ", format(nrow(df), big.mark = "."),
                         " funcionários analisados"),
       x        = NULL,
       y        = "Proporção (%)") +
  theme_minimal(base_size = 12) +
  theme(legend.position = "none",
        axis.text.x     = element_text(face = "bold"))

print(p1)

As faixas Low e Medium concentram funcionários com burnout_risk abaixo de 0.6, enquanto High e Severe reúnem os casos de maior atenção. O dado mais relevante é que 67.7% dos funcionários estão em High ou Severe: ou seja, com risco de burnout acima de 0.6. Desses, 477.547 estão no nível mais crítico (Severe, burnout_risk ≥ 0.8), o que representa os casos de maior urgência de intervenção.

4.2 Burnout por nível hierárquico

Uma das perguntas mais práticas para gestores de RH pode ser traduzida em: o risco de burnout aumenta com o nível do cargo? O boxplot abaixo permite comparar a distribuição de burnout_risk entre os cinco níveis hierárquicos.

p2 <- ggplot(df, aes(x = job_level, y = burnout_risk, fill = job_level)) +
  geom_boxplot(outlier.size = 0.5, outlier.alpha = 0.3, alpha = 0.8) +
  scale_fill_brewer(palette = "Reds", direction = 1) +
  labs(title    = "Risco de burnout por nível hierárquico",
       subtitle = "Cada caixa representa 50% central dos funcionários naquele nível",
       x        = "Nível hierárquico",
       y        = "Risco de Burnout (0–1)",
       fill     = "Nível") +
  theme_minimal(base_size = 12) +
  theme(legend.position = "none")

print(p2)

# Tabela de médias por nível
nivel_resumo <- aggregate(burnout_risk ~ job_level, data = df, FUN = mean)
nivel_resumo$burnout_risk <- round(nivel_resumo$burnout_risk, 3)
nivel_resumo$n <- as.integer(table(df$job_level))
nivel_resumo$pct_alto_risco <- round(
  tapply(df$alto_risco_burnout, df$job_level, mean) * 100, 1)
names(nivel_resumo) <- c("Nível", "Burnout médio", "N", "% em alto risco")

kable(nivel_resumo,
      caption   = "Burnout médio e proporção em alto risco por nível hierárquico",
      row.names = FALSE) |>
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Burnout médio e proporção em alto risco por nível hierárquico
Nível Burnout médio N % em alto risco
Entry 0.740 101211 46.4
Mid 0.732 609887 45.0
Senior 0.739 41347 46.0
Manager 0.758 78219 48.8
Lead 0.758 19335 48.7

Contrariamente ao senso comum, os níveis hierárquicos superiores não concentram necessariamente o maior risco. Isso é coerente com a teoria de Siegrist: trabalhadores de entrada (Entry) podem enfrentar alta carga sem contrapartida adequada de estabilidade ou reconhecimento, enquanto líderes (Lead) têm maior controle sobre seu trabalho.

4.3 Os departamentos mais críticos

Qual departamento concentra os funcionários com maior risco? O gráfico abaixo apresenta os 15 departamentos com maior média de burnout, permitindo priorização de ações de RH.

# Calcular média por departamento e selecionar os 15 maiores
dept_media <- aggregate(burnout_risk ~ department, data = df, FUN = mean)
dept_media <- dept_media[order(-dept_media$burnout_risk), ]
dept_top15 <- dept_media[1:15, ]
dept_top15$department <- factor(dept_top15$department,
                                levels = rev(dept_top15$department))
dept_top15$burnout_risk <- round(dept_top15$burnout_risk, 3)

p3 <- ggplot(dept_top15, aes(x = department, y = burnout_risk, fill = burnout_risk)) +
  geom_col(alpha = 0.9) +
  geom_text(aes(label = burnout_risk), hjust = -0.15, size = 3) +
  scale_fill_gradient(low = "#f1948a", high = "#922b21") +
  coord_flip() +
  labs(title    = "Top 15 departamentos com maior risco de burnout",
       subtitle = "Média do score de burnout_risk por departamento",
       x        = NULL,
       y        = "Burnout médio",
       fill     = "Burnout") +
  theme_minimal(base_size = 11) +
  theme(legend.position = "none") +
  scale_y_continuous(limits = c(0, 0.8))

print(p3)

A tabela interativa abaixo permite explorar todos os departamentos:

dept_completo <- aggregate(burnout_risk ~ department, data = df, FUN = mean)
dept_completo$n <- as.integer(table(df$department))
dept_completo$pct_saida <- round(
  tapply(df$left_company, df$department, mean, na.rm = TRUE) * 100, 1)
dept_completo$burnout_risk <- round(dept_completo$burnout_risk, 3)
dept_completo <- dept_completo[order(-dept_completo$burnout_risk), ]
names(dept_completo) <- c("Departamento", "Burnout médio", "N", "% que saiu")

datatable(dept_completo,
          caption  = "Risco de burnout e rotatividade por departamento",
          rownames = FALSE,
          options  = list(pageLength = 10, language = list(
            search      = "Buscar:",
            lengthMenu  = "Mostrar _MENU_ registros",
            info        = "Mostrando _START_–_END_ de _TOTAL_",
            paginate    = list(previous = "Anterior", `next` = "Próximo")
          ))) |>
  formatStyle("Burnout médio",
              background = styleColorBar(c(0, 1), "#f1948a"),
              backgroundSize = "100% 90%",
              backgroundRepeat = "no-repeat",
              backgroundPosition = "center")

4.4 A relação entre esforço, recompensa e burnout

Abaixo, busca-se verificar se o desequilíbrio entre esforço (carga e horas extras) e recompensa (satisfação e salário) está de fato associado ao risco de burnout. O gráfico interativo abaixo cruza o score_desequilibrio com o burnout_risk, colorido pelo nível de risco classificado:

set.seed(42)
df_amostra <- df[sample(nrow(df), 3000), ] #Amostra

p4 <- plot_ly(
  data   = df_amostra,
  x      = ~score_desequilibrio,
  y      = ~burnout_risk,
  color  = ~burnout_level,
  colors = c("Low" = "#2ecc71", "Medium" = "#f39c12",
             "High" = "#e74c3c", "Severe" = "#7b241c"),
  type   = "scatter",
  mode   = "markers",
  marker = list(size = 5, opacity = 0.6),
  text   = ~paste0("Departamento: ", department,
                   "<br>Cargo: ", role,
                   "<br>Nível: ", job_level,
                   "<br>Desequilíbrio: ", round(score_desequilibrio, 2),
                   "<br>Burnout risk: ", round(burnout_risk, 2)),
  hoverinfo = "text"
) |>
  layout(
    title  = list(text = "Desequilíbrio esforço-recompensa vs. risco de burnout",
                  font = list(size = 14)),
    xaxis  = list(title = "Score de desequilíbrio (0–1)"),
    yaxis  = list(title = "Risco de burnout (0–1)"),
    legend = list(title = list(text = "Nível de risco"))
  )

p4

Os pontos classificados como Severe (vermelho escuro) concentram-se no quadrante superior direito. Nota-se que, quanto maior o risco de burnout, maior a probabilidade de cair em extremo ou em mínimo desequilíbrio, de acordo com os dados. As observações mais equilibradas são aquelas que apresentam menor risco de burnout, estando mais próximos da mediana dos valores de desequilíbrio. Passe o cursor sobre os pontos para identificar departamento, cargo e nível de cada funcionário.

4.5 Horas extras e burnout

O boxplot abaixo revela a distribuição: mediana, intervalo central e valores extremos em cada faixa de jornada extra.

df$faixa_overtime <- cut( # Criar faixas de horas extras
  df$overtime_hours,
  breaks = c(-Inf, 0, 15, 30, 50, Inf),
  labels = c("Sem extras", "1–15h", "16–30h", "31–50h", "> 50h")
)


n_faixas <- table(df$faixa_overtime) # Calcular N por faixa

p5 <- ggplot(df, aes(x = faixa_overtime, y = burnout_risk, fill = faixa_overtime)) +
  geom_boxplot(outlier.size  = 0.4,
               outlier.alpha = 0.2,
               alpha         = 0.8,
               width         = 0.55) +
  stat_summary(fun = mean, geom = "point",
               shape = 23, size = 3, fill = "white", color = "#2c3e50") +
  scale_fill_manual(values = c("Sem extras" = "#2ecc71",
                               "1–15h"      = "#f1c40f",
                               "16–30h"     = "#e67e22",
                               "31–50h"     = "#e74c3c",
                               "> 50h"      = "#7b241c")) +
  labs(title    = "Distribuição do risco de burnout por faixa de horas extras",
       subtitle = "Losango branco = média; linha central = mediana",
       x        = "Horas extras semanais",
       y        = "Risco de Burnout (0–1)") +
  theme_minimal(base_size = 12) +
  theme(legend.position = "none")

print(p5)

overtime_tab <- data.frame(
  Faixa        = levels(df$faixa_overtime),
  N            = as.integer(n_faixas),
  Mediana      = round(tapply(df$burnout_risk, df$faixa_overtime,
                               median, na.rm = TRUE), 3),
  Media        = round(tapply(df$burnout_risk, df$faixa_overtime,
                               mean, na.rm = TRUE), 3),
  Pct_alto     = round(tapply(df$alto_risco_burnout, df$faixa_overtime,
                               mean, na.rm = TRUE) * 100, 1),
  row.names    = NULL
)
names(overtime_tab) <- c("Faixa de horas extras", "N",
                         "Burnout mediano", "Burnout médio", "% em alto risco")

kable(overtime_tab,
      caption   = "Risco de burnout por faixa de horas extras",
      row.names = FALSE) |>
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Risco de burnout por faixa de horas extras
Faixa de horas extras N Burnout mediano Burnout médio % em alto risco
Sem extras 583707 0.751 0.668 36.7
1–15h 213253 1.000 0.884 65.2
16–30h 48433 1.000 0.883 64.9
31–50h 4462 1.000 0.883 65.1
> 50h 144 1.000 0.878 65.3

O boxplot revela que, a partir da ocorrência de horas extras, ocorre uma concentração maior de observações em níveis maiores de burnout, mantendo-se, contudo, de modo visualmente equilibrado a partir das 15h extras de trabalho.


5 Conclusões

5.1 O problema abordado

Este projeto investigou quais fatores organizacionais e individuais estão mais associados ao risco de burnout em ambientes de trabalho e como eles elevam a probabilidade de um funcionário deixar a empresa, uma questão com implicações diretas para a saúde ocupacional e para os custos organizacionais.

5.2 Como o problema foi abordado

Utilizou-se o Synthetic Employee Dataset (BrotherTony, 2025), com ~850.000 registros e 31 variáveis. Após importação via arquivo limpeza dos dados, conduziu-se uma análise exploratória organizada em quatro eixos: distribuição global do risco, variação por cargo e departamento e relação com esforço e recompensa.

5.3 Observações

Inicialmente, observa-se que o Burnout não respeita a hierarquia de modo linear. O risco é distribuído de maneira similar entre os níveis Entry a Lead, sugerindo que fatores de cargo específico (carga, autonomia, reconhecimento) importam mais que o nível em si. Com relação aos departamentos, os 15 com maior burnout médio apresentam scores expressivamente acima da média geral, indicando que contextos organizacionais específicos amplificam o risco. Em adição, o desequilíbrio esforço-recompensa pode se mostrar um preditor consistente, dado que a correlação entre score_desequilibrio e burnout_risk foi visível mesmo numa amostra aleatória de 3.000 pontos. Com respeito às horas extras dos funcionários, por fim,a distribuição de burnout sobe em mediana e em variabilidade à medida que a jornada extra aumenta, com os funcionários acima de 50h semanais concentrando grandes valores de risco.

5.4 Implicações

Os departamentos identificados como críticos e o limiar de horas extras podem ser levados como pontos de partida para intervenções focadas: monitoramento de carga, programas de suporte e revisão de remuneração nos grupos de alto risco. Já no âmbito executivo, a análise demonstra que burnout não é problema individual, mas organizacional. Departamentos específicos concentram risco, e a estrutura de esforço e recompensa (não apenas o salário) determina quem fica e quem sai. Em termos de peqsuisa, o score de desequilíbrio composto (inspirado em Siegrist, 1996) mostrou-se um possível preditor analítico útil quando outros dados diretos de escalas psicométricas não estão disponíveis.

5.5 Limitações e possíveis extensões

  1. Os dados são sintéticos, com base em distribuições reais, mas não representam nenhuma organização específica. Os padrões encontrados não devem ser generalizados sem validação em dados reais.
  2. O dataset não permite analisar a evolução do burnout ao longo do tempo. Um dataset temporal/longitudinal permitiria identificar trajetórias de risco e pontos de intervenção com maior precisão.
  3. Os pesos 0.4/0.3/0.3, pensados para o score de desequilíbrio, são arbitrários. Uma extensão natural seria estimar os pesos por regressão ou análise de componentes principais, tornando o score mais robusto.
  4. A coluna recent_feedback contém texto livre que não foi explorado. Uma análise de sentimentos sobre esses textos poderia ser de grande valor para enriquecer os achados qualitativos.

6 Referências

Licença dos dados: CC-BY-NC-ND 4.0. BrotherTony (2025), Hugging Face

American Institute of Stress. 2024. “What Is the True Cost of Work-Related Stress?” Yonkers: American Institute of Stress. https://www.stress.org/news/what-is-the-true-cost-of-work-related-stress.
Brasil. Ministério da Saúde. 2023. Portaria GM/MS Nº 1.999, de 27 de Novembro de 2023: Atualiza a Lista de Doenças Relacionadas ao Trabalho (LDRT). Portaria. Brasília, DF: Ministério da Saúde.
Siegrist, Johannes. 1996. “Adverse Health Effects of High-Effort/Low-Reward Conditions.” Journal of Occupational Health Psychology 1 (1): 27.
Trinkenreich, Bianca, Klaas-Jan Stol, Igor Steinmacher, Marco A Gerosa, Anita Sarma, Marcelo Lara, Michael Feathers, Nicholas Ross, and Kevin Bishop. 2023. “A Model for Understanding and Reducing Developer Burnout.” In 2023 IEEE/ACM 45th International Conference on Software Engineering: Software Engineering in Practice (ICSE-SEIP), 48–60. IEEE.
World Health Organization. 2019. “Burn-Out an ‘Occupational Phenomenon’: International Classification of Diseases.” Geneva: World Health Organization. https://www.who.int/news/item/28-05-2019-burn-out-an-occupational-phenomenon-international-classification-of-diseases.