Fatores de Risco para AVC: Uma Análise Exploratória

Stroke Prediction Dataset — Projeto Final


1 Introdução

1.1 Declaração do Problema

O Acidente Vascular Cerebral (AVC) é uma das principais causas de morte e incapacidade permanente no mundo. De acordo com a Organização Mundial da Saúde (OMS), o AVC é responsável por aproximadamente 11% de todas as mortes globais, sendo a segunda maior causa de óbito em nível mundial. No Brasil, o cenário é igualmente preocupante: o AVC representa a primeira causa de morte e incapacidade no país, gerando um enorme custo humano, social e econômico.

O problema central que este projeto aborda é: quais características clínicas e demográficas estão mais associadas à ocorrência de AVC? Compreender os perfis de risco é fundamental para que profissionais de saúde possam realizar triagens preventivas mais eficazes, identificando pacientes que demandam atenção prioritária antes que o evento ocorra.

A relevância deste problema vai além do âmbito clínico. Gestores de saúde pública precisam de dados para alocar recursos preventivos de maneira mais inteligente; seguradoras de saúde podem ajustar apólices com base em perfis de risco; e a própria população pode tomar decisões mais informadas sobre seu estilo de vida ao entender quais fatores são modificáveis.

1.2 Abordagem Adotada

Para abordar esta questão, utilizamos o Stroke Prediction Dataset, disponível publicamente na plataforma Kaggle. O conjunto de dados contém informações clínicas e demográficas de 5.110 pacientes, incluindo variáveis como idade, presença de hipertensão, doenças cardíacas, nível médio de glicose, índice de massa corporal (IMC) e hábito de fumar, além do diagnóstico de AVC.

A metodologia empregada segue três etapas principais:

  1. Preparação dos dados: importação, limpeza e transformação do conjunto de dados bruto em um formato adequado para análise;
  2. Análise exploratória: investigação das distribuições individuais das variáveis e das relações entre os fatores de risco e a ocorrência de AVC;
  3. Síntese de insights: extração de padrões e conclusões que possam orientar decisões práticas na área da saúde.

1.3 Técnica Proposta

A análise é conduzida inteiramente em R, utilizando o ecossistema tidyverse para manipulação e visualização dos dados. Serão empregadas as seguintes técnicas:

  • Análise univariada: distribuição de cada variável individualmente, com tabelas de frequência para variáveis categóricas e estatísticas descritivas para variáveis numéricas.
  • Análise bivariada: comparação das distribuições das variáveis explicativas entre os grupos com e sem AVC, por meio de gráficos e tabelas cruzadas.
  • Criação de variáveis derivadas: construção de faixas etárias, categorias de IMC e níveis de glicose, enriquecendo a análise com novas perspectivas clínicas relevantes.
  • Visualizações interativas: uso de pacotes como plotly para permitir exploração dinâmica dos dados.

Esta abordagem combinada de estatística descritiva com visualização é suficiente para revelar padrões importantes sem a necessidade de modelos preditivos complexos, tornando os resultados acessíveis a um público mais amplo.

1.4 Utilidade para os Clientes

Os resultados desta análise têm aplicações práticas diretas para diferentes públicos:

  • Profissionais de saúde: identificar quais combinações de fatores de risco merecem atenção clínica prioritária, orientando protocolos de rastreamento e prevenção.
  • Gestores de saúde pública: compreender o perfil epidemiológico da população em risco, subsidiando políticas de prevenção e campanhas de conscientização.
  • Seguradoras de saúde: refinar modelos atuariais ao incorporar variáveis clínicas que demonstrarem forte associação com AVC.
  • Pesquisadores e estudantes de saúde: dispor de uma análise exploratória documentada e reproduzível que pode servir de ponto de partida para estudos mais aprofundados, incluindo modelagem preditiva.

2 Pacotes Requeridos

Antes de iniciar a análise, carregamos todos os pacotes necessários. A instalação pode ser feita com install.packages("nome_do_pacote") para cada um deles. O pacote rmdformats é necessário para a renderização deste documento no formato readthedown; para instalá-lo, utilize install.packages("rmdformats").

# ===================================================
# CARREGAMENTO DOS PACOTES
# ===================================================

# tidyverse: coleção de pacotes para ciência de dados em R.
# Inclui: dplyr (manipulação), ggplot2 (visualização),
# readr (importação), tidyr (transformação), forcats
# (fatores), stringr (strings) e purrr (programação funcional).
library(tidyverse)

# janitor: limpeza de dados e padronização de nomes de colunas.
# Utilizado principalmente para uniformizar os nomes das variáveis
# (snake_case) e para identificar entradas duplicadas.
library(janitor)

# skimr: geração de resumos estatísticos compactos e informativos,
# especialmente útil para uma visão geral rápida do dataset.
library(skimr)

# knitr: integração entre código R e o documento final,
# permitindo a criação de tabelas formatadas com kable().
library(knitr)

# kableExtra: extensão do kable() para formatação avançada
# de tabelas HTML, com suporte a cores, agrupamentos e estilos.
library(kableExtra)

# naniar: análise e visualização de dados faltantes (NA).
# Permite entender a estrutura dos valores ausentes no dataset.
library(naniar)

3 Preparação dos Dados

3.1 Fonte dos Dados

O conjunto de dados utilizado neste projeto é o Stroke Prediction Dataset, disponível publicamente na plataforma Kaggle no seguinte endereço:

🔗 https://www.kaggle.com/datasets/jawairia123/stroke-prediction-dataset

O arquivo pode ser baixado diretamente da página acima e deve ser salvo no diretório de trabalho do R antes de executar este documento. O nome do arquivo é healthcare-dataset-stroke-data.csv.

# ===================================================
# IMPORTAÇÃO DOS DADOS BRUTOS
# ===================================================

# Leitura do CSV com read_csv() do pacote readr (tidyverse).
# Utilizamos read_csv() em vez de read.csv() pois ele infere
# os tipos de dados de forma mais inteligente e retorna um tibble.
#
# ATENÇÃO: os valores ausentes da coluna 'bmi' estão armazenados
# no CSV como a string literal "N/A" (e não como célula vazia).
# Sem o argumento 'na', o readr não reconheceria "N/A" como ausente
# e leria a coluna inteira como caractere, causando erros nas etapas
# seguintes. O argumento 'na' amplia a lista de símbolos reconhecidos
# como valores ausentes para incluir "N/A".
stroke_bruto <- read_csv(
  "healthcare-dataset-stroke-data.csv",
  na = c("", "NA", "N/A")   # reconhece "N/A" como valor ausente
)

# Visualizando as primeiras linhas para uma inspeção inicial
glimpse(stroke_bruto)
Rows: 5,110
Columns: 12
$ id                <dbl> 9046, 51676, 31112, 60182, 1665, 56669, 53882, 10434…
$ gender            <chr> "Male", "Female", "Male", "Female", "Female", "Male"…
$ age               <dbl> 67, 61, 80, 49, 79, 81, 74, 69, 59, 78, 81, 61, 54, …
$ hypertension      <dbl> 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1…
$ heart_disease     <dbl> 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0…
$ ever_married      <chr> "Yes", "Yes", "Yes", "Yes", "Yes", "Yes", "Yes", "No…
$ work_type         <chr> "Private", "Self-employed", "Private", "Private", "S…
$ Residence_type    <chr> "Urban", "Rural", "Rural", "Urban", "Rural", "Urban"…
$ avg_glucose_level <dbl> 228.69, 202.21, 105.92, 171.23, 174.12, 186.21, 70.0…
$ bmi               <dbl> 36.6, NA, 32.5, 34.4, 24.0, 29.0, 27.4, 22.8, NA, 24…
$ smoking_status    <chr> "formerly smoked", "never smoked", "never smoked", "…
$ stroke            <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…

3.2 Descrição dos Dados Originais

O dataset foi publicado originalmente com o objetivo de treinar modelos de aprendizado de máquina para predição de AVC, sendo amplamente utilizado em benchmarks de classificação binária na comunidade de ciência de dados. Os dados foram coletados de registros médicos de múltiplos pacientes e disponibilizados de forma anonimizada.

O conjunto de dados original possui 5110 observações e 12 variáveis, descritas na tabela a seguir:

# ===================================================
# TABELA DESCRITIVA DAS VARIÁVEIS ORIGINAIS
# ===================================================

# Construímos manualmente a tabela de metadados para que
# a descrição seja informativa e contextualizada.
tibble(
  Variável    = names(stroke_bruto),
  Tipo        = map_chr(stroke_bruto, ~ class(.x)[1]),
  Descrição   = c(
    "Identificador único do paciente",
    "Sexo biológico: 'Male', 'Female' ou 'Other'",
    "Idade em anos (contínua)",
    "Presença de hipertensão: 0 = Não, 1 = Sim",
    "Presença de doença cardíaca: 0 = Não, 1 = Sim",
    "Estado civil: 'Yes' ou 'No'",
    "Tipo de ocupação: Private, Self-employed, Govt_job, children, Never_worked",
    "Tipo de residência: 'Urban' ou 'Rural'",
    "Nível médio de glicose no sangue (mg/dL)",
    "Índice de Massa Corporal (IMC)",
    "Hábito de fumar: 'formerly smoked', 'never smoked', 'smokes', 'Unknown'",
    "Diagnóstico de AVC: 0 = Não, 1 = Sim (variável-alvo)"
  ),
  `Valores Ausentes` = map_chr(stroke_bruto, ~ as.character(sum(is.na(.x))))
) |>
  kable(caption = "Descrição das variáveis do dataset original") |>
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width        = FALSE,
    font_size         = 13
  ) |>
  row_spec(12, bold = TRUE, background = "#FFF3CD") # destaque na variável-alvo
Descrição das variáveis do dataset original
Variável Tipo Descrição Valores Ausentes
id numeric Identificador único do paciente 0
gender character Sexo biológico: ‘Male’, ‘Female’ ou ‘Other’ 0
age numeric Idade em anos (contínua) 0
hypertension numeric Presença de hipertensão: 0 = Não, 1 = Sim 0
heart_disease numeric Presença de doença cardíaca: 0 = Não, 1 = Sim 0
ever_married character Estado civil: ‘Yes’ ou ‘No’ 0
work_type character Tipo de ocupação: Private, Self-employed, Govt_job, children, Never_worked 0
Residence_type character Tipo de residência: ‘Urban’ ou ‘Rural’ 0
avg_glucose_level numeric Nível médio de glicose no sangue (mg/dL) 0
bmi numeric Índice de Massa Corporal (IMC) 201
smoking_status character Hábito de fumar: ‘formerly smoked’, ‘never smoked’, ‘smokes’, ‘Unknown’ 0
stroke numeric Diagnóstico de AVC: 0 = Não, 1 = Sim (variável-alvo) 0

Algumas peculiaridades importantes identificadas nos dados originais:

  • Representação dos valores ausentes: os valores faltantes da variável bmi estão armazenados no CSV como a string literal "N/A" — e não como célula em branco (padrão mais comum). Isso exigiu configuração explícita durante a importação, pois o read_csv() não reconhece "N/A" como ausente por padrão, lendo a coluna inteira como texto caso esse detalhe seja ignorado.
  • Proporção de ausentes em bmi: as observações sem IMC representam 3.9% do total, sugerindo que o dado não foi coletado para parte dos pacientes — o que será tratado por imputação.
  • Categoria “Unknown” em smoking_status: não se trata de um NA técnico, mas de uma categoria explícita que indica que o histórico tabágico do paciente não foi informado. Esta distinção é importante na etapa de limpeza.
  • Inconsistência no nome da coluna: Residence_type começa com letra maiúscula, ao contrário de todas as demais variáveis — uma irregularidade que será corrigida.
  • Categoria rara em gender: existe apenas 1 observação com gender == "Other", o que a torna estatisticamente inviável para análises comparativas.
  • work_type == "children": esta categoria está escrita em letras minúsculas, diferentemente das demais, o que indica uma inconsistência de digitação.
  • Variável-alvo desbalanceada: apenas 249 pacientes (4.9%) sofreram AVC, refletindo a prevalência real na população geral mas exigindo atenção nas análises.

3.3 Importação e Limpeza dos Dados

Nesta seção realizamos todas as transformações necessárias para converter o dataset bruto em um conjunto de dados limpo e pronto para análise. Cada etapa é explicada antes do código correspondente.

3.3.1 Etapa 1 — Padronização dos nomes das colunas

O pacote janitor oferece a função clean_names(), que converte todos os nomes de colunas para o padrão snake_case (minúsculas, palavras separadas por _). Isso resolve a inconsistência de Residence_type e garante uniformidade.

# Padronizar nomes das colunas para snake_case
stroke <- stroke_bruto |>
  clean_names()

# Verificar se os nomes foram corrigidos
names(stroke)
 [1] "id"                "gender"            "age"              
 [4] "hypertension"      "heart_disease"     "ever_married"     
 [7] "work_type"         "residence_type"    "avg_glucose_level"
[10] "bmi"               "smoking_status"    "stroke"           

3.3.2 Etapa 2 — Remoção da coluna identificadora

A variável id é apenas um identificador único de cada paciente, sem qualquer informação analítica. Mantê-la poderia gerar confusão em análises de correlação ou resumos, portanto é removida.

# Remover a coluna 'id' — não tem valor analítico
stroke <- stroke |>
  select(-id)

3.3.3 Etapa 3 — Remoção da categoria de gênero “Other”

Como identificamos na etapa anterior, há apenas 1 observação com gender == "Other". Uma única observação não permite nenhuma inferência comparativa e pode distorcer análises agregadas por gênero. Portanto, essa linha é removida.

# Verificar a contagem antes da remoção
stroke |> count(gender, name = "n_antes")
# Remover a observação com gender == "Other"
stroke <- stroke |>
  filter(gender != "Other")

# Confirmar remoção
stroke |> count(gender, name = "n_depois")

3.3.4 Etapa 4 — Correção da capitalização em work_type

A categoria "children" está grafada em letras minúsculas, diferente das demais ("Private", "Self-employed", etc.). Corrigimos isso para manter consistência.

# Verificar os valores únicos antes
stroke |> count(work_type)
# Corrigir: "children" -> "Children"
stroke <- stroke |>
  mutate(work_type = if_else(work_type == "children", "Children", work_type))

# Confirmar correção
stroke |> count(work_type)

3.3.5 Etapa 5 — Conversão de variáveis para fatores

As variáveis binárias (0/1) e as categóricas de texto devem ser convertidas para o tipo factor em R. Isso é fundamental para que funções de análise e visualização as tratem corretamente. Aproveitamos a conversão para aplicar rótulos descritivos em português nas variáveis binárias.

stroke <- stroke |>
  mutate(
    # Variáveis binárias: recodificar 0/1 com rótulos descritivos
    hypertension  = factor(hypertension,
                           levels = c(0, 1),
                           labels = c("Não", "Sim")),
    heart_disease = factor(heart_disease,
                           levels = c(0, 1),
                           labels = c("Não", "Sim")),
    stroke        = factor(stroke,
                           levels = c(0, 1),
                           labels = c("Não", "Sim")),

    # Variáveis de texto: converter para fator
    gender         = factor(gender),
    ever_married   = factor(ever_married,
                            levels = c("No", "Yes"),
                            labels = c("Não", "Sim")),
    work_type      = factor(work_type),
    residence_type = factor(residence_type),

    # smoking_status: "Unknown" é mantido como nível de fator
    # (não é convertido para NA porque representa uma categoria
    # real — pacientes que não informaram o hábito tabágico).
    smoking_status = factor(smoking_status,
                            levels = c("never smoked", "formerly smoked",
                                       "smokes", "Unknown"))
  )

# Verificar os tipos após conversão
glimpse(stroke)
Rows: 5,109
Columns: 11
$ gender            <fct> Male, Female, Male, Female, Female, Male, Male, Fema…
$ age               <dbl> 67, 61, 80, 49, 79, 81, 74, 69, 59, 78, 81, 61, 54, …
$ hypertension      <fct> Não, Não, Não, Não, Sim, Não, Sim, Não, Não, Não, Si…
$ heart_disease     <fct> Sim, Não, Sim, Não, Não, Não, Sim, Não, Não, Não, Nã…
$ ever_married      <fct> Sim, Sim, Sim, Sim, Sim, Sim, Sim, Não, Sim, Sim, Si…
$ work_type         <fct> Private, Self-employed, Private, Private, Self-emplo…
$ residence_type    <fct> Urban, Rural, Rural, Urban, Rural, Urban, Rural, Urb…
$ avg_glucose_level <dbl> 228.69, 202.21, 105.92, 171.23, 174.12, 186.21, 70.0…
$ bmi               <dbl> 36.6, NA, 32.5, 34.4, 24.0, 29.0, 27.4, 22.8, NA, 24…
$ smoking_status    <fct> formerly smoked, never smoked, never smoked, smokes,…
$ stroke            <fct> Sim, Sim, Sim, Sim, Sim, Sim, Sim, Sim, Sim, Sim, Si…

3.3.6 Etapa 6 — Tratamento dos valores ausentes no IMC

Como constatado anteriormente, 201 observações não possuem valor de IMC. Antes de decidir o método de imputação, visualizamos o padrão de ausência:

# Visualizar o padrão de valores faltantes
vis_miss(stroke) +
  labs(title = "Mapa de valores ausentes",
       subtitle = "Apenas a variável 'bmi' possui valores faltantes") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))
Padrão de valores ausentes no dataset

Padrão de valores ausentes no dataset

Os dados faltantes em bmi estão distribuídos de forma relativamente aleatória (faltam 3.9% dos valores). Para a imputação, optamos por substituir cada valor ausente pela mediana do IMC do grupo correspondente de faixa etária e gênero. Esta estratégia é preferível à mediana global porque preserva as variações naturais de IMC que existem entre crianças, adultos jovens e idosos, além de respeitar as diferenças fisiológicas entre homens e mulheres.

# Passo 6a: criar faixa etária temporária para guiar a imputação
stroke <- stroke |>
  mutate(
    faixa_etaria_temp = cut(
      age,
      breaks = c(-Inf, 12, 17, 29, 44, 59, Inf),
      labels = c("Criança", "Adolescente", "Jovem Adulto",
                 "Adulto", "Meia-Idade", "Idoso"),
      right  = TRUE
    )
  )

# Passo 6b: imputar bmi pela mediana do grupo (faixa etária + gênero)
stroke <- stroke |>
  group_by(faixa_etaria_temp, gender) |>
  mutate(
    bmi = if_else(is.na(bmi),
                  median(bmi, na.rm = TRUE),
                  bmi)
  ) |>
  ungroup()

# Confirmar que não há mais NAs em bmi
cat("Valores ausentes em bmi após imputação:", sum(is.na(stroke$bmi)), "\n")
Valores ausentes em bmi após imputação: 0 

3.3.7 Etapa 7 — Criação de variáveis derivadas

Com os dados limpos, criamos três novas variáveis que enriquecem a análise com perspectivas clínicas relevantes.

7a. Faixa Etária — segmentação da idade em categorias clínicas, substituindo a variável temporária utilizada na imputação.

7b. Categoria de IMC — classificação do IMC segundo os critérios da OMS, permitindo comparações padronizadas entre grupos.

7c. Nível de Glicose — categorização clínica do nível médio de glicose com base nos limiares utilizados para diagnóstico de pré-diabetes e diabetes (segundo a American Diabetes Association).

stroke <- stroke |>
  # Remover a coluna temporária de faixa etária
  select(-faixa_etaria_temp) |>
  mutate(
    # 7a: Faixa etária (variável definitiva)
    faixa_etaria = cut(
      age,
      breaks = c(-Inf, 12, 17, 29, 44, 59, Inf),
      labels = c("Criança (≤12)",
                 "Adolescente (13–17)",
                 "Jovem Adulto (18–29)",
                 "Adulto (30–44)",
                 "Meia-Idade (45–59)",
                 "Idoso (≥60)"),
      right = TRUE
    ),

    # 7b: Categoria de IMC (classificação OMS)
    categoria_imc = case_when(
      bmi < 18.5              ~ "Abaixo do Peso",
      bmi >= 18.5 & bmi < 25  ~ "Peso Normal",
      bmi >= 25   & bmi < 30  ~ "Sobrepeso",
      bmi >= 30               ~ "Obesidade"
    ),
    categoria_imc = factor(
      categoria_imc,
      levels = c("Abaixo do Peso", "Peso Normal", "Sobrepeso", "Obesidade")
    ),

    # 7c: Nível de glicose (limiares ADA: <100 normal, 100-125 pré-diabetes,
    #     >=126 diabetes)
    nivel_glicose = case_when(
      avg_glucose_level < 100               ~ "Normal (<100 mg/dL)",
      avg_glucose_level >= 100 & avg_glucose_level < 126 ~ "Pré-Diabetes (100–125)",
      avg_glucose_level >= 126              ~ "Diabetes (≥126 mg/dL)"
    ),
    nivel_glicose = factor(
      nivel_glicose,
      levels = c("Normal (<100 mg/dL)", "Pré-Diabetes (100–125)",
                 "Diabetes (≥126 mg/dL)")
    )
  )

# Verificar a criação das novas variáveis
stroke |>
  select(age, faixa_etaria, bmi, categoria_imc,
         avg_glucose_level, nivel_glicose) |>
  slice_sample(n = 10) |>
  kable(caption = "Amostra das variáveis derivadas criadas") |>
  kable_styling(bootstrap_options = c("striped", "hover"),
                full_width = FALSE, font_size = 12)
Amostra das variáveis derivadas criadas
age faixa_etaria bmi categoria_imc avg_glucose_level nivel_glicose
1 Criança (≤12) 15.1 Abaixo do Peso 123.21 Pré-Diabetes (100–125)
56 Meia-Idade (45–59) 24.8 Peso Normal 112.62 Pré-Diabetes (100–125)
40 Adulto (30–44) 24.1 Peso Normal 106.76 Pré-Diabetes (100–125)
30 Adulto (30–44) 29.7 Sobrepeso 118.62 Pré-Diabetes (100–125)
60 Idoso (≥60) 28.5 Sobrepeso 100.20 Pré-Diabetes (100–125)
20 Jovem Adulto (18–29) 45.9 Obesidade 100.80 Pré-Diabetes (100–125)
57 Meia-Idade (45–59) 31.0 Obesidade 62.20 Normal (<100 mg/dL)
53 Meia-Idade (45–59) 55.2 Obesidade 87.03 Normal (<100 mg/dL)
48 Meia-Idade (45–59) 38.7 Obesidade 216.70 Diabetes (≥126 mg/dL)
60 Idoso (≥60) 37.3 Obesidade 153.48 Diabetes (≥126 mg/dL)

3.4 Conjunto de Dados Final

Após todas as etapas de limpeza e transformação, o dataset final possui 5109 observações e 14 variáveis (as 11 originais — após remoção de id — mais as 3 variáveis derivadas criadas).

# Visualizar a estrutura final do dataset limpo
glimpse(stroke)
Rows: 5,109
Columns: 14
$ gender            <fct> Male, Female, Male, Female, Female, Male, Male, Fema…
$ age               <dbl> 67, 61, 80, 49, 79, 81, 74, 69, 59, 78, 81, 61, 54, …
$ hypertension      <fct> Não, Não, Não, Não, Sim, Não, Sim, Não, Não, Não, Si…
$ heart_disease     <fct> Sim, Não, Sim, Não, Não, Não, Sim, Não, Não, Não, Nã…
$ ever_married      <fct> Sim, Sim, Sim, Sim, Sim, Sim, Sim, Não, Sim, Sim, Si…
$ work_type         <fct> Private, Self-employed, Private, Private, Self-emplo…
$ residence_type    <fct> Urban, Rural, Rural, Urban, Rural, Urban, Rural, Urb…
$ avg_glucose_level <dbl> 228.69, 202.21, 105.92, 171.23, 174.12, 186.21, 70.0…
$ bmi               <dbl> 36.60, 29.20, 32.50, 34.40, 24.00, 29.00, 27.40, 22.…
$ smoking_status    <fct> formerly smoked, never smoked, never smoked, smokes,…
$ stroke            <fct> Sim, Sim, Sim, Sim, Sim, Sim, Sim, Sim, Sim, Sim, Si…
$ faixa_etaria      <fct> Idoso (≥60), Idoso (≥60), Idoso (≥60), Meia-Idade (4…
$ categoria_imc     <fct> Obesidade, Sobrepeso, Obesidade, Obesidade, Peso Nor…
$ nivel_glicose     <fct> Diabetes (≥126 mg/dL), Diabetes (≥126 mg/dL), Pré-Di…

A seguir, exibimos as 10 primeiras linhas do dataset final de forma condensada:

# Exibir amostra do dataset final (as 10 primeiras linhas)
stroke |>
  head(10) |>
  kable(caption = "Primeiras 10 linhas do dataset limpo") |>
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = TRUE, font_size = 11) |>
  scroll_box(width = "100%")
Primeiras 10 linhas do dataset limpo
gender age hypertension heart_disease ever_married work_type residence_type avg_glucose_level bmi smoking_status stroke faixa_etaria categoria_imc nivel_glicose
Male 67 Não Sim Sim Private Urban 228.69 36.60 formerly smoked Sim Idoso (≥60) Obesidade Diabetes (≥126 mg/dL)
Female 61 Não Não Sim Self-employed Rural 202.21 29.20 never smoked Sim Idoso (≥60) Sobrepeso Diabetes (≥126 mg/dL)
Male 80 Não Sim Sim Private Rural 105.92 32.50 never smoked Sim Idoso (≥60) Obesidade Pré-Diabetes (100–125)
Female 49 Não Não Sim Private Urban 171.23 34.40 smokes Sim Meia-Idade (45–59) Obesidade Diabetes (≥126 mg/dL)
Female 79 Sim Não Sim Self-employed Rural 174.12 24.00 never smoked Sim Idoso (≥60) Peso Normal Diabetes (≥126 mg/dL)
Male 81 Não Não Sim Private Urban 186.21 29.00 formerly smoked Sim Idoso (≥60) Sobrepeso Diabetes (≥126 mg/dL)
Male 74 Sim Sim Sim Private Rural 70.09 27.40 never smoked Sim Idoso (≥60) Sobrepeso Normal (<100 mg/dL)
Female 69 Não Não Não Private Urban 94.39 22.80 never smoked Sim Idoso (≥60) Peso Normal Normal (<100 mg/dL)
Female 59 Não Não Sim Private Rural 76.15 29.75 Unknown Sim Meia-Idade (45–59) Sobrepeso Normal (<100 mg/dL)
Female 78 Não Não Sim Private Urban 58.57 24.20 Unknown Sim Idoso (≥60) Peso Normal Normal (<100 mg/dL)

3.5 Resumo das Variáveis de Interesse

A tabela a seguir consolida as informações descritivas de todas as variáveis do dataset limpo. Para variáveis numéricas, apresentamos a média, mediana, desvio padrão e amplitude. Para variáveis categóricas, apresentamos o número de categorias e a categoria mais frequente.

# ===================================================
# TABELA RESUMO CONSOLIDADA DAS VARIÁVEIS
# ===================================================

# --- Variáveis numéricas ---
resumo_num <- stroke |>
  select(age, avg_glucose_level, bmi) |>
  pivot_longer(everything(), names_to = "Variável", values_to = "valor") |>
  group_by(Variável) |>
  summarise(
    Tipo      = "Numérica",
    # first() extrai o valor escalar do grupo — necessário porque dentro
    # de summarise() a coluna agrupadora ainda é um vetor (todos iguais),
    # e case_when() sobre um vetor devolveria N linhas em vez de 1.
    Descrição = case_when(
      first(Variável) == "age"               ~ "Idade (anos)",
      first(Variável) == "avg_glucose_level" ~ "Glicose média (mg/dL)",
      first(Variável) == "bmi"               ~ "IMC (kg/m²)"
    ),
    Resumo = sprintf(
      "Média: %.1f | Mediana: %.1f | DP: %.1f | Min–Max: %.1f–%.1f",
      mean(valor), median(valor), sd(valor), min(valor), max(valor)
    ),
    `N Ausentes` = "0",
    .groups = "drop"
  )

# --- Variáveis categóricas ---
resumo_cat <- stroke |>
  select(gender, hypertension, heart_disease, ever_married,
         work_type, residence_type, smoking_status, stroke,
         faixa_etaria, categoria_imc, nivel_glicose) |>
  pivot_longer(everything(), names_to = "Variável", values_to = "valor") |>
  group_by(Variável) |>
  summarise(
    Tipo      = "Categórica",
    Descrição = case_when(
      first(Variável) == "gender"         ~ "Sexo biológico",
      first(Variável) == "hypertension"   ~ "Hipertensão (Sim/Não)",
      first(Variável) == "heart_disease"  ~ "Doença cardíaca (Sim/Não)",
      first(Variável) == "ever_married"   ~ "Já foi casado(a) (Sim/Não)",
      first(Variável) == "work_type"      ~ "Tipo de ocupação",
      first(Variável) == "residence_type" ~ "Tipo de residência",
      first(Variável) == "smoking_status" ~ "Hábito tabágico",
      first(Variável) == "stroke"         ~ "AVC (Sim/Não) — variável-alvo",
      first(Variável) == "faixa_etaria"   ~ "Faixa etária (derivada)",
      first(Variável) == "categoria_imc"  ~ "Categoria de IMC — OMS (derivada)",
      first(Variável) == "nivel_glicose"  ~ "Nível de glicose clínico (derivada)"
    ),
    Resumo = sprintf(
      "%d categorias | Mais frequente: '%s' (%s)",
      n_distinct(valor),
      names(sort(table(valor), decreasing = TRUE))[1],
      scales::percent(
        max(table(valor)) / sum(!is.na(valor)),
        accuracy = 0.1
      )
    ),
    `N Ausentes` = as.character(sum(is.na(valor))),
    .groups = "drop"
  )

# Combinar e exibir
bind_rows(resumo_num, resumo_cat) |>
  arrange(Tipo, Variável) |>
  kable(caption = "Resumo consolidado das variáveis do dataset limpo") |>
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width        = TRUE,
    font_size         = 12
  ) |>
  row_spec(which(bind_rows(resumo_num, resumo_cat) |>
                   arrange(Tipo, Variável) |>
                   pull(Variável) == "stroke"),
           bold = TRUE, background = "#FFF3CD") |>
  pack_rows("Variáveis Categóricas", 1, 11) |>
  pack_rows("Variáveis Numéricas",  12, 14)
Resumo consolidado das variáveis do dataset limpo
Variável Tipo Descrição Resumo N Ausentes
Variáveis Categóricas
categoria_imc Categórica Categoria de IMC — OMS (derivada) 4 categorias &#124; Mais frequente: ‘Obesidade’ (38.5%) 0
ever_married Categórica Já foi casado(a) (Sim/Não) 2 categorias &#124; Mais frequente: ‘Sim’ (65.6%) 0
faixa_etaria Categórica Faixa etária (derivada) 6 categorias &#124; Mais frequente: ‘Idoso (≥60)’ (26.9%) 0
gender Categórica Sexo biológico 2 categorias &#124; Mais frequente: ‘Female’ (58.6%) 0
heart_disease Categórica Doença cardíaca (Sim/Não) 2 categorias &#124; Mais frequente: ‘Não’ (94.6%) 0
hypertension Categórica Hipertensão (Sim/Não) 2 categorias &#124; Mais frequente: ‘Não’ (90.3%) 0
nivel_glicose Categórica Nível de glicose clínico (derivada) 3 categorias &#124; Mais frequente: ‘Normal (<100 mg/dL)’ (61.3%) 0
residence_type Categórica Tipo de residência 2 categorias &#124; Mais frequente: ‘Urban’ (50.8%) 0
smoking_status Categórica Hábito tabágico 4 categorias &#124; Mais frequente: ‘never smoked’ (37.0%) 0
stroke Categórica AVC (Sim/Não) — variável-alvo 2 categorias &#124; Mais frequente: ‘Não’ (95.1%) 0
work_type Categórica Tipo de ocupação 5 categorias &#124; Mais frequente: ‘Private’ (57.2%) 0
Variáveis Numéricas
age Numérica Idade (anos) Média: 43.2 &#124; Mediana: 45.0 &#124; DP: 22.6 &#124; Min–Max: 0.1–82.0 0
avg_glucose_level Numérica Glicose média (mg/dL) Média: 106.1 &#124; Mediana: 91.9 &#124; DP: 45.3 &#124; Min–Max: 55.1–271.7 0
bmi Numérica IMC (kg/m²) Média: 28.9 &#124; Mediana: 28.3 &#124; DP: 7.7 &#124; Min–Max: 10.3–97.6 0

Destaques do resumo:

  • O dataset limpo está completamente livre de valores ausentes após a imputação do IMC.
  • A variável-alvo stroke é altamente desbalanceada: apenas 4.9% dos pacientes sofreram AVC. Isso é importante e será considerado na etapa de análise exploratória.
  • A idade varia de 0.08 a 82 anos, incluindo crianças — a faixa etária criada auxilia na segmentação dessas análises.
  • Cerca de 30.2% dos pacientes possuem hábito tabágico desconhecido (“Unknown”), o que deve ser levado em conta ao interpretar qualquer análise envolvendo tabagismo. ```