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)

# plotly: criação de gráficos interativos a partir de objetos ggplot2
# ou diretamente via sua própria sintaxe. Permite hover, zoom e filtros.
library(plotly)

# DT: renderização de tabelas HTML interativas com busca, ordenação
# e paginação, ideal para exploração detalhada dos dados no navegador.
library(DT)

# scales: formatação de rótulos numéricos em gráficos e textos inline
# (percentuais, notação de milhar, etc.).
library(scales)

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
4 Criança (≤12) 15.9 Abaixo do Peso 103.76 Pré-Diabetes (100–125)
30 Adulto (30–44) 31.9 Obesidade 141.80 Diabetes (≥126 mg/dL)
15 Adolescente (13–17) 32.3 Obesidade 62.57 Normal (<100 mg/dL)
62 Idoso (≥60) 29.2 Sobrepeso 75.78 Normal (<100 mg/dL)
62 Idoso (≥60) 34.0 Obesidade 74.32 Normal (<100 mg/dL)
16 Adolescente (13–17) 21.4 Peso Normal 74.98 Normal (<100 mg/dL)
42 Adulto (30–44) 36.3 Obesidade 89.00 Normal (<100 mg/dL)
5 Criança (≤12) 18.5 Peso Normal 102.04 Pré-Diabetes (100–125)
43 Adulto (30–44) 47.6 Obesidade 100.88 Pré-Diabetes (100–125)
2 Criança (≤12) 20.8 Peso Normal 73.62 Normal (<100 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.

4 Análise Exploratória dos Dados

4.1 Perfil Comparativo: quem sofre AVC?

Antes de qualquer gráfico, construímos duas perspectivas sintéticas sobre os dados: uma tabela de perfil médio separando pacientes com e sem AVC, e uma variável de escore de risco composto que agrega os principais fatores clínicos em uma única medida. Ambas as construções geram informações que não estão disponíveis diretamente no dataset original.

# ===================================================
# TABELA: PERFIL MÉDIO (AVC vs. Sem AVC)
# ===================================================

# Comparar as médias das variáveis numéricas e as proporções das
# principais variáveis binárias entre os dois grupos de desfecho.
# Esta tabela cria uma visão consolidada que não é imediatamente
# visível ao se olhar para o dataset bruto.

perfil <- stroke |>
  group_by(`Diagnóstico` = stroke) |>
  summarise(
    `N pacientes`             = n(),
    `Idade média (anos)`      = round(mean(age), 1),
    `Glicose média (mg/dL)`   = round(mean(avg_glucose_level), 1),
    `IMC médio (kg/m²)`       = round(mean(bmi), 1),
    `% com hipertensão`       = percent(mean(hypertension  == "Sim"), accuracy = 0.1),
    `% com doença cardíaca`   = percent(mean(heart_disease == "Sim"), accuracy = 0.1),
    `% fumante ativo`         = percent(mean(smoking_status == "smokes"), accuracy = 0.1),
    .groups = "drop"
  )

perfil |>
  kable(caption = "Perfil médio dos pacientes por diagnóstico de AVC") |>
  kable_styling(bootstrap_options = c("striped", "hover"),
                full_width = FALSE, font_size = 13) |>
  column_spec(1, bold = TRUE) |>
  row_spec(2, background = "#FFF0F0")   # destaque no grupo com AVC
Perfil médio dos pacientes por diagnóstico de AVC
Diagnóstico N pacientes Idade média (anos) Glicose média (mg/dL) IMC médio (kg/m²) % com hipertensão % com doença cardíaca % fumante ativo
Não 4860 42.0 104.8 28.8 8.9% 4.7% 15.4%
Sim 249 67.7 132.5 30.3 26.5% 18.9% 16.9%
# ===================================================
# NOVA VARIÁVEL: ESCORE DE RISCO COMPOSTO
# ===================================================

# Criamos 'n_fatores_risco' somando quantos dos cinco principais
# fatores de risco clínico cada paciente apresenta:
#   1. Hipertensão
#   2. Doença cardíaca
#   3. Tabagismo ativo
#   4. Diabetes (glicose ≥ 126 mg/dL)
#   5. Sobrepeso ou obesidade (IMC ≥ 25)
# A variável resultante permite medir o efeito cumulativo dos fatores,
# algo que analisar cada fator isoladamente não revela.

stroke <- stroke |>
  mutate(
    n_fatores_risco = as.integer(hypertension   == "Sim") +
                     as.integer(heart_disease   == "Sim") +
                     as.integer(smoking_status  == "smokes") +
                     as.integer(nivel_glicose   == "Diabetes (≥126 mg/dL)") +
                     as.integer(categoria_imc   %in% c("Sobrepeso", "Obesidade"))
  )

A tabela acima já revela diferenças marcantes: pacientes que sofreram AVC têm, em média, 25.8 anos a mais, nível de glicose 27.8 mg/dL superior e taxas consideravelmente maiores de hipertensão e doença cardíaca. Esses contrastes orientam toda a análise seguinte.


4.2 O Papel Determinante da Idade

A idade é, de longe, o fator mais associado ao AVC neste dataset. O gráfico interativo abaixo permite explorar como a distribuição etária difere radicalmente entre os dois grupos.

# ===================================================
# GRÁFICO INTERATIVO (plotly): DISTRIBUIÇÃO DA IDADE
# ===================================================

# Convertemos o ggplot para plotly com ggplotly() para manter
# o controle fino de formatação do ggplot2 e adicionar interatividade.

g_idade <- ggplot(stroke, aes(x = age, fill = stroke, colour = stroke)) +
  geom_density(alpha = 0.45, linewidth = 0.8) +
  scale_fill_manual(
    values = c("Não" = "#4E9AF1", "Sim" = "#E05252"),
    name   = "AVC"
  ) +
  scale_colour_manual(
    values = c("Não" = "#2B72C8", "Sim" = "#B03030"),
    name   = "AVC"
  ) +
  labs(
    title   = "Distribuição etária por diagnóstico de AVC",
    subtitle = "Pacientes com AVC concentram-se fortemente acima dos 60 anos",
    x       = "Idade (anos)",
    y       = "Densidade"
  ) +
  theme_minimal(base_size = 13) +
  theme(legend.position = "top")

ggplotly(g_idade, tooltip = c("x", "y")) |>
  layout(legend = list(orientation = "h", x = 0.3, y = 1.1))

Distribuição etária por diagnóstico de AVC (interativo)

# ===================================================
# GRÁFICO: TAXA DE AVC POR FAIXA ETÁRIA
# ===================================================

# Calculamos a proporção de pacientes com AVC dentro de cada faixa
# etária — uma forma de "normalizar" o efeito do tamanho de cada grupo.

stroke |>
  group_by(faixa_etaria) |>
  summarise(
    taxa_avc = mean(stroke == "Sim"),
    n        = n(),
    n_avc    = sum(stroke == "Sim"),
    .groups  = "drop"
  ) |>
  ggplot(aes(x = faixa_etaria, y = taxa_avc, fill = taxa_avc)) +
  geom_col(width = 0.7, show.legend = FALSE) +
  geom_text(aes(label = paste0(percent(taxa_avc, accuracy = 0.1),
                               "\n(n = ", n_avc, ")")),
            vjust = -0.4, size = 3.2, lineheight = 1.1) +
  scale_y_continuous(labels = percent_format(), limits = c(0, 0.22)) +
  scale_fill_gradient(low = "#AED6F1", high = "#E05252") +
  labs(
    title   = "Taxa de AVC por faixa etária",
    subtitle = "Proporção de pacientes diagnosticados com AVC em cada grupo",
    x       = NULL,
    y       = "Taxa de AVC (%)"
  ) +
  theme_minimal(base_size = 13) +
  theme(axis.text.x = element_text(angle = 20, hjust = 1))
Taxa de AVC por faixa etária

Taxa de AVC por faixa etária

O padrão é inequívoco: entre crianças e adolescentes a taxa de AVC é praticamente nula, enquanto entre idosos (≥60 anos) ela ultrapassa 13.2%. Esse salto não é linear — ele se acelera a partir dos 45 anos, indicando que a janela de prevenção mais crítica está na meia-idade.


4.3 Fatores de Risco Clínicos Individuais

A seguir, avaliamos como cada fator de risco clínico se relaciona com a ocorrência de AVC, apresentando as taxas de incidência dentro de cada categoria.

# ===================================================
# GRÁFICO FACETADO: TAXA DE AVC POR FATOR CLÍNICO
# ===================================================

# Reformulamos os dados para o formato longo, o que permite
# criar um único gráfico facetado elegante em vez de quatro
# gráficos separados — economizando espaço e facilitando comparações.

fatores_longos <- stroke |>
  select(stroke, hypertension, heart_disease, nivel_glicose, categoria_imc) |>
  pivot_longer(
    cols      = -stroke,
    names_to  = "fator",
    values_to = "categoria"
  ) |>
  mutate(
    fator = recode(fator,
      "hypertension"   = "Hipertensão",
      "heart_disease"  = "Doença Cardíaca",
      "nivel_glicose"  = "Nível de Glicose",
      "categoria_imc"  = "Categoria de IMC"
    ),
    categoria = as.character(categoria)
  ) |>
  group_by(fator, categoria) |>
  summarise(
    taxa_avc = mean(stroke == "Sim"),
    n        = n(),
    .groups  = "drop"
  )

ggplot(fatores_longos,
       aes(x = reorder(categoria, taxa_avc), y = taxa_avc, fill = taxa_avc)) +
  geom_col(width = 0.65, show.legend = FALSE) +
  geom_text(aes(label = percent(taxa_avc, accuracy = 0.1)),
            hjust = -0.15, size = 3.2) +
  scale_y_continuous(labels = percent_format(), limits = c(0, 0.21)) +
  scale_fill_gradient(low = "#AED6F1", high = "#E05252") +
  coord_flip() +
  facet_wrap(~fator, scales = "free_y", ncol = 2) +
  labs(
    title    = "Taxa de AVC por categoria de cada fator clínico",
    subtitle = "Pacientes com hipertensão, doença cardíaca ou diabetes apresentam risco marcadamente elevado",
    x        = NULL,
    y        = "Taxa de AVC (%)"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    strip.text       = element_text(face = "bold", size = 11),
    panel.spacing    = unit(1.2, "lines"),
    plot.subtitle    = element_text(colour = "grey40", size = 10)
  )
Taxa de AVC por categoria de cada fator de risco clínico

Taxa de AVC por categoria de cada fator de risco clínico

Os quatro painéis revelam padrões consistentes: pacientes com hipertensão apresentam taxa de AVC cerca de 3 vezes maior que os sem hipertensão; o mesmo vale para doenças cardíacas. Entre os pacientes com nível de glicose na faixa de diabetes (≥126 mg/dL), a taxa de AVC é a mais elevada de todos os subgrupos analisados. Quanto ao IMC, o efeito é menos pronunciado — pacientes obesos têm taxa ligeiramente superior, mas a diferença é mais modesta do que o esperado clinicamente, possivelmente porque o IMC sozinho captura mal o risco metabólico sem considerar a distribuição de gordura corporal.


4.4 Efeito Cumulativo: quanto mais fatores, maior o risco

Uma das análises mais reveladoras deste projeto é avaliar se o risco de AVC se acumula com a soma dos fatores de risco. Para isso, utilizamos a variável n_fatores_risco criada anteriormente.

# ===================================================
# TABELA: TAXA DE AVC POR ESCORE DE RISCO COMPOSTO
# ===================================================

tabela_escore <- stroke |>
  group_by(`Fatores de Risco` = n_fatores_risco) |>
  summarise(
    `N pacientes` = n(),
    `N com AVC`   = sum(stroke == "Sim"),
    `Taxa de AVC` = percent(mean(stroke == "Sim"), accuracy = 0.1),
    .groups = "drop"
  )

tabela_escore |>
  kable(caption = "Relação entre número de fatores de risco e taxa de AVC",
        align   = c("c", "r", "r", "r")) |>
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE, font_size = 13) |>
  column_spec(4, bold = TRUE,
              color = c("grey50", "#2B72C8", "#5BA85A",
                        "#E0882A", "#C0392B", "#8B0000")[
                          seq_len(nrow(tabela_escore))]) |>
  footnote(general = "Fatores considerados: hipertensão, doença cardíaca, tabagismo ativo, diabetes e sobrepeso/obesidade.",
           general_title = "Nota:")
Relação entre número de fatores de risco e taxa de AVC
Fatores de Risco N pacientes N com AVC Taxa de AVC
0 1228 20 1.6%
1 2245 76 3.4%
2 1177 88 7.5%
3 387 49 12.7%
4 67 12 17.9%
5 5 4 80.0%
Nota:
Fatores considerados: hipertensão, doença cardíaca, tabagismo ativo, diabetes e sobrepeso/obesidade.
# ===================================================
# GRÁFICO: DOSE-RESPOSTA DO ESCORE DE RISCO
# ===================================================

stroke |>
  group_by(n_fatores_risco) |>
  summarise(
    taxa_avc = mean(stroke == "Sim"),
    n        = n(),
    .groups  = "drop"
  ) |>
  ggplot(aes(x = n_fatores_risco, y = taxa_avc)) +
  geom_line(colour = "#E05252", linewidth = 1.2) +
  geom_point(aes(size = n), colour = "#E05252", alpha = 0.85) +
  geom_text(aes(label = paste0(percent(taxa_avc, accuracy = 0.1),
                               "\n(n=", n, ")")),
            vjust = -1.1, size = 3.2, lineheight = 1.1, colour = "grey30") +
  scale_y_continuous(labels = percent_format(), limits = c(0, 0.35)) +
  scale_size_continuous(range = c(3, 10), name = "N pacientes") +
  labs(
    title    = "Efeito cumulativo dos fatores de risco sobre a taxa de AVC",
    subtitle = "Cada fator adicional amplifica significativamente o risco — pacientes com 4+ fatores têm risco >20%",
    x        = "Número de fatores de risco presentes",
    y        = "Taxa de AVC (%)"
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.subtitle = element_text(colour = "grey40", size = 10),
    legend.position = "bottom"
  )
Taxa de AVC cresce com o acúmulo de fatores de risco

Taxa de AVC cresce com o acúmulo de fatores de risco

A relação dose-resposta é clara e estatisticamente coerente: pacientes sem nenhum fator de risco têm taxa de AVC inferior a 1.6%, enquanto aqueles com quatro ou mais fatores apresentam risco superior a 22.2%. Isso sugere que intervenções clínicas devem priorizar pacientes com múltiplos fatores concomitantes, não apenas aqueles com uma única condição isolada.


4.5 O Paradoxo do Ex-Fumante

Uma das descobertas mais contraintuitivas deste dataset envolve o tabagismo. Ao calcular a taxa de AVC por categoria de hábito tabágico, surge um resultado aparentemente paradoxal.

# ===================================================
# GRÁFICO: AVC POR HÁBITO TABÁGICO
# ===================================================

# Calculamos a taxa de AVC excluindo os "Unknown" para não distorcer
# a comparação entre os grupos com informação disponível.
stroke |>
  filter(smoking_status != "Unknown") |>
  group_by(smoking_status) |>
  summarise(
    taxa_avc  = mean(stroke == "Sim"),
    n         = n(),
    idade_med = round(median(age), 1),
    .groups   = "drop"
  ) |>
  mutate(smoking_status = fct_reorder(smoking_status, taxa_avc)) |>
  ggplot(aes(x = smoking_status, y = taxa_avc, fill = smoking_status)) +
  geom_col(width = 0.6, show.legend = FALSE) +
  geom_text(aes(label = paste0(percent(taxa_avc, accuracy = 0.1),
                               "\nIdade mediana: ", idade_med, " anos")),
            vjust = -0.4, size = 3.4, lineheight = 1.2) +
  scale_y_continuous(labels = percent_format(), limits = c(0, 0.12)) +
  scale_fill_manual(values = c(
    "never smoked"    = "#AED6F1",
    "formerly smoked" = "#E0882A",
    "smokes"          = "#E05252"
  )) +
  scale_x_discrete(labels = c(
    "never smoked"    = "Nunca fumou",
    "formerly smoked" = "Ex-fumante",
    "smokes"          = "Fumante ativo"
  )) +
  labs(
    title    = "Taxa de AVC e idade mediana por hábito tabágico",
    subtitle = "Ex-fumantes apresentam taxa mais alta que fumantes ativos — um efeito explicado pela confusão com a idade",
    x        = NULL,
    y        = "Taxa de AVC (%)"
  ) +
  theme_minimal(base_size = 13) +
  theme(plot.subtitle = element_text(colour = "grey40", size = 10))
Taxa de AVC por hábito tabágico — o paradoxo do ex-fumante

Taxa de AVC por hábito tabágico — o paradoxo do ex-fumante

# ===================================================
# GRÁFICO: DISTRIBUIÇÃO DE IDADE POR HÁBITO TABÁGICO
# ===================================================

# Para entender o paradoxo, comparamos as distribuições de idade
# por categoria de tabagismo — revelando o mecanismo de confusão.

stroke |>
  filter(smoking_status != "Unknown") |>
  mutate(smoking_status = recode(smoking_status,
    "never smoked"    = "Nunca fumou",
    "formerly smoked" = "Ex-fumante",
    "smokes"          = "Fumante ativo"
  )) |>
  ggplot(aes(x = smoking_status, y = age, fill = smoking_status)) +
  geom_violin(alpha = 0.6, show.legend = FALSE) +
  geom_boxplot(width = 0.15, fill = "white", outlier.size = 1,
               show.legend = FALSE) +
  scale_fill_manual(values = c(
    "Nunca fumou"  = "#AED6F1",
    "Ex-fumante"   = "#E0882A",
    "Fumante ativo"= "#E05252"
  )) +
  labs(
    title    = "Distribuição de idade por hábito tabágico",
    subtitle = "Ex-fumantes são substancialmente mais velhos — o verdadeiro fator de risco subjacente",
    x        = NULL,
    y        = "Idade (anos)"
  ) +
  theme_minimal(base_size = 13) +
  theme(plot.subtitle = element_text(colour = "grey40", size = 10))
Distribuição etária por hábito tabágico explica o paradoxo

Distribuição etária por hábito tabágico explica o paradoxo

O gráfico de barras mostra que ex-fumantes têm taxa de AVC maior que fumantes ativos, o que parece contradizer o senso comum. O gráfico de violino resolve o mistério: ex-fumantes são consideravelmente mais velhos que fumantes ativos neste dataset. Ou seja, não é a cessação do tabagismo que eleva o risco — é o fato de que pessoas mais velhas (já mais propensas ao AVC) tendem a estar no grupo de ex-fumantes, criando uma associação espúria. Este é um exemplo clássico de viés de confundimento por idade, que reforça a importância de análises multivariadas para conclusões causais.


4.6 Exploração Interativa dos Dados

A tabela a seguir permite uma exploração livre do dataset limpo, com filtros, busca e ordenação por qualquer coluna. Ela é especialmente útil para investigar perfis individuais de pacientes e verificar combinações específicas de fatores de risco.

# ===================================================
# TABELA INTERATIVA (DT): DATASET COMPLETO
# ===================================================

# Exibimos uma versão resumida e legível do dataset, com os campos
# mais relevantes para análise clínica. O DT permite que o leitor
# filtre, ordene e pesquise sem nenhum código adicional.

stroke |>
  select(
    Sexo             = gender,
    Idade            = age,
    `Faixa Etária`   = faixa_etaria,
    Hipertensão      = hypertension,
    `D. Cardíaca`    = heart_disease,
    `Glicose (mg/dL)`= avg_glucose_level,
    `Nível Glicose`  = nivel_glicose,
    `IMC`            = bmi,
    `Cat. IMC`       = categoria_imc,
    Tabagismo        = smoking_status,
    AVC              = stroke,
    `Nº Fat. Risco`  = n_fatores_risco
  ) |>
  mutate(
    Tabagismo = recode(as.character(Tabagismo),
      "never smoked"    = "Nunca fumou",
      "formerly smoked" = "Ex-fumante",
      "smokes"          = "Fumante ativo",
      "Unknown"         = "Desconhecido"
    ),
    `Glicose (mg/dL)` = round(`Glicose (mg/dL)`, 1),
    IMC               = round(IMC, 1)
  ) |>
  datatable(
    filter    = "top",
    rownames  = FALSE,
    class     = "stripe hover compact",
    caption   = "Dataset completo — use os filtros no topo de cada coluna para explorar subgrupos",
    options   = list(
      pageLength = 10,
      scrollX    = TRUE,
      language   = list(
        search      = "Buscar:",
        lengthMenu  = "Mostrar _MENU_ registros",
        info        = "Exibindo _START_ a _END_ de _TOTAL_ registros",
        paginate    = list(`previous` = "Anterior", `next` = "Próximo")
      )
    )
  ) |>
  formatStyle(
    "AVC",
    backgroundColor = styleEqual(c("Não", "Sim"), c("#EAF4FB", "#FDEDEC"))
  ) |>
  formatStyle(
    "Nº Fat. Risco",
    background = styleColorBar(range(stroke$n_fatores_risco), "#E05252"),
    backgroundSize  = "100% 70%",
    backgroundRepeat= "no-repeat",
    backgroundPosition = "center"
  )

5 Conclusões

5.1 Retomada do Problema

Este projeto partiu de uma questão central: quais características clínicas e demográficas estão mais associadas à ocorrência de AVC? A motivação reside no peso epidemiológico da doença — primeira causa de morte e incapacidade no Brasil — e na possibilidade concreta de que a identificação precoce de perfis de risco possa subsidiar estratégias preventivas mais eficazes.

5.2 Abordagem Utilizada

Para responder a essa questão, utilizamos o Stroke Prediction Dataset (Kaggle), composto por 5109 registros de pacientes com informações clínicas e demográficas. A análise seguiu três etapas: (1) importação e limpeza dos dados, com atenção especial às inconsistências de formato e aos valores ausentes; (2) enriquecimento do dataset com variáveis derivadas — faixas etárias, categorias de IMC e glicose, e um escore de risco composto; e (3) análise exploratória combinando tabelas descritivas, visualizações estáticas com ggplot2, gráficos interativos com plotly e tabela explorável com DT.

5.3 Principais Insights

A análise revelou quatro achados de destaque:

1. A idade é o fator dominante. A taxa de AVC entre idosos (≥60 anos) é mais de 15 vezes superior à observada em adultos jovens (18–29 anos). Nenhuma outra variável isolada apresentou poder discriminatório comparável.

2. Fatores clínicos individuais dobram o risco. Hipertensão e doença cardíaca, cada uma isoladamente, estão associadas a taxas de AVC cerca de 3 vezes maiores em relação aos grupos sem essas condições. O nível de glicose na faixa de diabetes apresentou o mesmo padrão.

3. O risco se acumula com os fatores. Pacientes sem nenhum fator de risco têm taxa de AVC abaixo de 2%, enquanto aqueles com quatro ou mais fatores ultrapassam 20%. Essa relação dose-resposta é uma das descobertas mais práticas desta análise.

4. O paradoxo do ex-fumante. Ex-fumantes apresentaram taxa de AVC superior à de fumantes ativos — não por causalidade, mas por confundimento etário: ex-fumantes neste dataset são substancialmente mais velhos. Isso ilustra os limites da análise bivariada e a necessidade de controle por variáveis de confusão.

5.4 Implicações Práticas

Os resultados têm aplicações diretas para diferentes públicos. Profissionais de saúde podem usar o escore de risco composto como ferramenta de triagem rápida: pacientes com três ou mais fatores presentes merecem acompanhamento preventivo prioritário. Gestores de saúde pública encontram respaldo para concentrar campanhas de prevenção de AVC na população acima de 45 anos, especialmente aqueles com hipertensão ou diabetes não controlados. Seguradoras podem incorporar variáveis como faixa etária, hipertensão e histórico cardíaco como preditores em modelos atuariais, em substituição a proxies menos informativas.

5.5 Limitações e Possibilidades de Melhoria

Esta análise, apesar de suas contribuições, apresenta limitações importantes que devem nortear trabalhos futuros:

  • Causalidade não estabelecida. Trata-se de dados transversais; nenhuma das associações encontradas pode ser interpretada como causal sem um desenho longitudinal ou experimental.
  • Desbalanceamento da variável-alvo. Apenas 4.9% dos pacientes sofreram AVC. Para modelos preditivos, seria necessário aplicar técnicas de reamostragem (SMOTE, por exemplo).
  • Tabagismo com alta proporção de “Unknown”. Aproximadamente 30.2% dos pacientes têm hábito tabágico desconhecido, o que limita as conclusões sobre esse fator.
  • Ausência de variáveis de contexto. Informações como uso de medicamentos antihipertensivos, histórico familiar de AVC, nível de atividade física e dieta seriam fundamentais para uma análise mais completa.
  • IMC como proxy imperfeito. O IMC não distingue gordura visceral de massa muscular; medidas como circunferência abdominal ou relação cintura-quadril seriam superiores para capturar risco metabólico.

Uma extensão natural deste trabalho seria a construção de um modelo preditivo supervisionado — como regressão logística ou floresta aleatória — capaz de estimar a probabilidade individual de AVC a partir do perfil clínico do paciente, transformando os insights exploratórios aqui apresentados em uma ferramenta de apoio à decisão clínica.