Projeto Machine Learning — Previsão de AVC

Pandas Couple
12 min readMay 21, 2021

--

O AVC é o entupimento ou rompimento dos vasos que levam sangue ao cérebro, provocando a paralisia da região afetada no cérebro. Também é chamado de acidente vascular cerebral, derrame cerebral ou Acidente Vascular Encefálico (AVE).

O acidente vascular cerebral (AVC) é a doença que mais mata no Brasil e a que mais causa incapacidade no mundo: cerca de 70% das pessoas que sofrem um derrame não retorna ao trabalho depois do acidente vascular cerebral e 50% ficam dependentes de outras pessoas no dia a dia. Apesar desses números preocupantes, muita gente ainda têm dúvidas sobre o assunto e desconhece as principais causas, sintomas e maneiras de prevenir essa enfermidade.

O AVC acontece quando o suprimento de sangue que vai para o cérebro é interrompido ou drasticamente reduzido, privando as cédulas de oxigênio e de nutrientes. Ou, então, quando um vaso sanguíneo se rompe, causando uma hemorragia cerebral. Entre as causas dessas ocorrências, estão a malformação arterial cerebral (aneurisma), hipertensão arterial, cardiopatia, trombo embolia (bloqueio da artéria pulmonar).

Existem dois tipos de AVC: O AVC isquêmico e o AVC hemorrágico.

Neste trabalho vamos montar um modelo de previsão para ajudar os profissionais da saúde a detectarem pacientes que possam vir a sofrer um AVC e desse modo tentar salva-los a tempo de não sofrerem consequências mais graves.

Aquisição dos Dados

Os dados deste trabalho foram adquiridos do Kaggle. Lá você encontra esse e outros datasets muito interessantes para fazer projetos como esse ou até participar de competições entre cientistas de dados.

Informação das variáveis:

  • id: identificador;
  • gender: “Masculino”, “Feminino” ou “outro”;
  • age: idade do paciente;
  • hypertension: 0 se o paciente não tem hipertensão, 1 se o paciente tem hipertensão;
  • heart_disease: 0 se o paciente não tem doenças cardíacas, 1 se o paciente tem doença cardíaca;
  • ever_married: nunca se casou, “não” ou “sim”;
  • work_type: tipo de trabalho, “crianças”, “governo”, “nunca trabalhou”, “privado” or “autônomo”;
  • Residence_type: tipo da residência, “Rural” ou “Urbana”;
  • avg_glucose_level: nível médio de glicose no sangue;
  • bmi: índice de massa corporal;
  • smoking_status: relação com o cigarro, “já fumou”, “nunca fumou”, “fuma” ou “desconhecido”;
  • stroke: 1 se o paciente teve um AVC ou 0 se não teve um AVC;
  • Note: “Unknown” na variável ‘smoking_status’ significa que essa informação não é conhecida.

O objetivo principal do trabalho é: Montar um modelo de machine learning que faça previsões para pacientes que possam ter um AVC.

Análise exploratória dos dados

Nesta primeira etapa do trabalho, o objetivo principal é fazer uma análise completa de toda a informação que conseguirmos extrair do conjunto de dados em questão, como por exemplo fazer diversas análises estatísticas; Verificar outliers e valores ausentes que possam estar presentes; Verificar a correlação entre as variáveis; Analisar gráficos, entre outros.

Após ter as informações que precisamos, vamos partir para uma segunda etapa que é a de limpeza e tratamento dos dados, cujo os detalhes serão abordados posteriormente.

Para tornar a análise exploratória simples de entender vamos dividir as tarefas em tópicos e ir detalhando uma a uma.

  • Verificar as 05 primeiras entradas do dataset:

Note que apenas com esse comando, conseguimos ter uma visão geral do nosso dataset, identificando nossas variáveis (colunas) e nossas entradas (linhas) e assim já conseguindo obter alguns insights de como proceder com esse dataset.

  • Verificar os tipos das variáveis e tamanho do dataset:

Com esse comando, conseguimos ver que o nosso dataset possui 5510 entradas e 12 colunas. Conseguimos perceber os tipos das variáveis (int, float ou object) e já obter alguns insights, pois, vamos precisar trocar os tipos das variáveis ‘object’ para ‘int’ na etapa de preparação e limpeza dos dados.

  • Verificar o resumo estatístico do dataframe:

Com esse comando conseguimos ver uma distribuição estatística completa do nosso dataset, como a média, desvio padrão, os valores mínimos e máximos, e as distribuições dos valores nos quartis. Esse dataset não parece ter nenhum outlier, pois, não vemos valores tão discrepantes. Vamos plotar um gráfico boxplot para termos outra visão e tentar detectar algum outlier.

  • Gráfico boxplot:

Com esse boxplots, conseguimos corroborar a nossa ideia anterior de que esse dataset não possuía nenhum outlier, realmente não há nenhum ponto com valor discrepante que poderia vir a diminuir o desempenho do nosso modelo.

Vamos agora verificar as entradas únicas de cada coluna para obter algum insight.

  • Analisar as entradas das variáveis:

Com esse comando conseguimos ver quantas entradas diferentes cada coluna possui. As colunas ‘age’, ‘bmi’, ‘avg_glucose_level’ e ‘id’ possuem valores numéricos contínuos e por isso elas possuem essa grande quantidade de valores nas entradas. Já as variáveis ‘hypertension’, ‘heart_disease’, ‘ever_married’, ‘residence_type’, ‘stroke’, ‘gender’, ‘smoking_status’ e ‘work_type’ possuem valores numéricos discretos e por isso essa pequena quantidade de valores.

Conseguimos já obter alguns insights como por exemplo, alterar as colunas que possuem valores discretos e normalizar as colunas que possuem valores contínuos. Na etapa de preparação e limpeza dos dados vamos falar melhor sobre isso.

Vamos agora verificar a porcentagem de valores ausentes.

  • Verificar a porcentagem de valores ausentes nas colunas:

Como podemos ver, o dataset se encontra bem preenchido. Vamos precisar apenas preencher os valores ausentes da variável ‘bmi’ na etapa de preparação e limpeza dos dados. O restante não possui valores ausentes e assim não irão precisar de nenhuma intervenção.

Vamos verificar agora como está o balanceamento da nossa variável alvo.

  • Verificação do balanceamento da variável alvo:

Como podemos ver nosso dataset se encontra muito desbalanceado, pois, temos muitos exemplo da classe ‘0’ e poucos exemplos da classe ‘1’, quando formos testar nosso modelo, isso pode causar problemas no seu desempenho, pode ser que o modelo fique muito bom em prever as classes ‘0’ e não consiga ter bom desempenho em prever as classes ‘1’. Na etapa de preparação e limpeza iremos balancear esse dataset para corrigir esse problema.

Vamos agora ver a correlação entre as variáveis.

  • Analisar a correlação entre as variáveis:

Note que a correlação das outras variáveis com a variável alvo ‘stroke’ é pequena. A que apresentou uma maior correlação foi a variável ‘age’. Quando formos preparar os dados antes de criar nosso modelo de machine learning, vamos fazer algumas alterações a fim de tentar melhorar o desempenho do modelo, como por exemplo, trocar as variáveis do tipo ‘object’ para variáveis do tipo numéricas; Normalizar as variáveis ‘bmi’ e ‘avg_glucose_level’; Balancear a variável alvo, entre outros.

Após tudo isso vamos plotar uma nova matriz de correlação para verificar o que mudou.

Agora vamos entrar na segunda etapa que é a etapa de preparação e limpeza dos dados.

Preparação e limpeza dos dados

Nessa segunda etapa, vamos fazer um trabalho de preparar os dados (por exemplo, padronizar/normalizar e balancear nossos dados, entre outros) e também um trabalho de limpeza dos dados (excluir variáveis irrelevantes, preencher valores ausentes nas entradas, entre outros).

Vamos fazer como na primeira etapa, ir fazendo tópico a tópico tudo o que será necessário para deixar nosso dataframe pronto para alimentar nosso modelo de machine learning na etapa posterior.

Vamos começar criando uma cópia do nosso dataframe

  • Criar uma cópia do dataframe
  • Eliminar as colunas que não irão ter utilidade para nosso modelo

Como vimos na análise exploratória, no tópico da correlação entre as variáveis, a variável id não apresentava nenhuma correlação relevante com as outras variáveis, já que ela é apenas um identificador do paciente, podendo assim ser eliminada sem nenhum prejuízo ao desempenho do modelo, pelo contrário, isso será benéfico ao modelo.

Vamos agora verificar como ficou nosso dataframe sem a coluna ‘id’

  • Verificar as 05 primeiras entradas

Vamos agora eliminar os valores ausentes do nosso dataset. Os valores ausentes são os que vem com uma entrada representadas por ‘NaN’. Conforme vimos na análise exploratória, a única variável que apresentada valores ausentes é a ‘bmi’. Vamos então substituir esses valores pela média ou pela mediana de seus valores (nesse caso a média e a mediana possuem valores próximos, geralmente é mais utilizado a mediana, pois, a mesma é menos sensível a presença de outliers que podem diminuir o desempenho do nosso modelo).

Vamos optar por substituir pela mediana.

  • Substituindo os valores ausentes

Conforme vimos acima, agora todo o nosso dataframe está completo sem nenhum valor ausente.

Vamos agora alterar as variáveis categóricas para numéricas, bem como, separa-las em valores discretos para melhor desempenho do modelo

  • Alterar as variáveis categóricas e separa-las em valores discretos

Veja como ficou nosso novo dataframe que será utilizado para alimentar nosso modelo de machine learning na próxima etapa.

  • Verificar os tipos das variáveis

Observe que agora, todas as variáveis foram definidas para numéricas.

Nossos dados estão praticamente prontos para alimentarem o nosso modelo de machine learning que será feito na próxima etapa. Ainda nessa etapa iremos fazer uma padronização e balanceamento das nossas variáveis, porém, antes, quero já montar um modelo bem simples antes de fazer essas etapas para obter os resultados do mesmo e assim termos condições de comparar os resultados entre esse modelo mais simples e o modelo final que será feito na etapa posterior.

Vamos utilizar a cross-validation para quantificar esse desempenho para depois compararmos com o modelo final e ver o quanto melhorou. Essa costuma ser uma boa prática para modelos de machine learning, pois, dessa forma, você tem um modelo base para comparar com quaisquer outros que virem em etapas posteriores.

Após treinarmos e quantificarmos esse modelo mais simples, ainda nessa etapa, iremos fazer uma série de testes com várias possibilidades de padronização e balanceamento para escolher a técnica que terá um melhor desempenho para o modelo.

E por fim, com a técnica de padronização e balanceamento escolhidas, iremos escolher o nosso modelo final.

Vamos inicialmente utilizar apenas uma técnica simples de padronização para evitar erros muito grandes no nosso modelo.

Modelo base — regressão logística

Vamos utilizar para esse primeiro modelo base a regressão logística, pois, é um modelo simples e rápido de ser executado. Para medir o desempenho do modelo vamos utilizar aqui o F1-score, pois, ele engloba em sua fórmula tanto a métrica de Precisão quanto a métrica do Recall, e portanto, para obtermos um F1-score satisfatório ambas as métricas de precisão e recall também precisam ser satisfatórias.

Não iremos utilizar a Acurácia aqui, pois, como o dataset está desbalanceado, e caso nosso modelo não conseguir prever nenhuma classe ‘1’, a acurácia continuaria com um valor alto, mascarando então nossos resultados, diferentemente do que acontece com as outras métricas.

Observe que a nossa métrica de avaliação, o F1-score, deu extremamente baixa. Provavelmente, por conta do nossos dados não terem sido balanceados (lembre-se que temos muitos exemplos da classe ‘0’ e poucos exemplos da classe ‘1’), nosso modelo não consegue fazer nenhuma previsão correta para os casos em que ‘y = 1’ e isso implica num F1-score baixo (nesse caso o valor mínimo).

Vamos agora executar uma série de testes balanceando e padronizando nossos dados utilizando algumas técnicas existentes.

Teste 1 — StandardScaler + RandomUnderSampling

Note como nosso F1-score melhorou absurdamente quando balanceamos esse conjunto de dados, ou seja, nossas suposições anteriores estavam corretas!

Vamos continuar executando mais alguns testes com outras técnicas de balanceamento e padronização para tentarmos alcançar desempenhos ainda melhores.

Teste 2 — StandardScaler + SMOTE

Utilizando um balanceamento com o SMOTE (que é uma técnica de Oversampling e não Undersampling como foi utilizado no teste 1), já houve um aumento do nosso F1-score. Vamos continuar com os testes.

Teste 3 — MinMaxScaler + RandomUnderSampler

Obtivemos o mesmo desempenho do teste 1. Vamos para um último teste.

Teste 4 — MinMaxScaler + SMOTE

Todos deram resultados semelhantes. Apesar do teste 4 ter dado um desempenho superior, irei seguir com o teste 1, pois, acredito que o balanceamento com o SMOTE pode causar um overfitting no nosso modelo.

Distribuição após balanceamento com SMOTE

Modelo de Machine Learning

Nessa terceira e última etapa, vamos utilizar o cross-validation junto com as parametrizações do teste 2, feito na etapa anterior, para escolher o modelo que irá alcançar o melhor desempenho para então finalizarmos nosso trabalho tunando seus hyperparâmetros para tentar alcançar um desempenho ainda superior.

Vamos adotar os seguintes modelos:

  • Random forest;
  • Decision tree;
  • Stochastic Gradient Descent;
  • SVC;
  • Regressão Logística;
  • XGB
  • LightGBM.

Finalmente, o modelo que atingiu melhor desempenho foi o SVC, porém, vou seguir com o Random Forest, pois, acredito que tunando seus hyperparâmetros ele possa vir a ter um desempenho melhor que o SVC ou a regressão logística, que também deu um valor alto.

Depois, para finalizar, vamos expor nosso modelo aos dados de teste para avaliar seu desempenho com dados que ele nunca viu antes, e, assim analisar se nosso modelo realmente está generalizando para dados que ele nunca teve contato.

Vamos lá!

Modelo final — Random Forest

Tentaremos aqui otimizar os seguintes hyperparâmetros:

  • n_estimators = número de árvores na previsão;
  • max_features = número máximo de recursos considerados para dividir um nó;
  • max_depth = número máximo de níveis em cada árvore de decisão;
  • min_samples_split = número mínimo de pontos de dados colocados em um nó antes que o nó seja dividido;
  • min_samples_leaf = número mínimo de pontos de dados permitidos em um nó folha;
  • bootstrap = método para amostragem de pontos de dados (com ou sem substituição);
Melhor resultado após o tunning dos hyperparâmetros acima

Conseguimos melhorar significativamente o nosso desempenho!

Vamos agora, finalmente, expor nosso modelo aos dados de teste e avaliar seu desempenho.

Verificação final com os dados de teste

Vamos analisar o resultado: Em primeiro lugar, gostaria de dizer que cada tipo de problema tem sua interpretação particular, vamos portanto, analisar somente este caso.

O valor principal que é o verdadeiro positivo (VP) apresentou um desempenho muito bom de 89%, ou seja, nosso modelo está prevendo os casos de VP 89% das vezes corretamente. O seu valor complementar que é o falso negativo, apresentou um desempenho de 11% o que é ruim, pois, nosso modelo previu que o paciente não irá ter AVC quando na verdade ele irá ter um AVC 11% das vezes. O ideal seria que esse 11% fosse a 0, mas sabemos que na prática todos os modelos apresentam algum tipo de erro e esses 11% são a minoria, portanto nosso modelo está bem “melhor” do que “pior”.

O segundo valor principal que é o verdadeiro negativo (VN) também apresentou um desempenho bom de 70%, ou seja, nosso modelo está prevendo os casos de VN 70% das vezes corretamente. O seu valor complementar que é o falso positivo, está alto, com um valor de 30% (o que não é interessante), porém, isso não é tão ruim quanto os casos de falso negativo, pois, o modelo detecta um AVC quando na verdade o paciente não tem AVC, ou seja, na prática irá ficar “tudo bem”.

Conclusão

Neste trabalho passamos por diversas etapas essenciais para qualquer pessoa que trabalhe com dados. Começamos com uma introdução para situar o problema, depois, adquirimos os dados, após isso começamos uma análise exploratória importantíssima para chegarmos onde queríamos, depois fizemos toda uma preparação e limpeza dos dados que eu, particularmente, julgo ser a etapa mais importante devido o seu nível de dificuldade e importância para um bom funcionamento do modelo de Machine Learning e enfim criamos nosso modelo final e tunamos os seus hyperparâmetros para expor ele aos testes finais tentando buscar sempre um melhor desempenho e, por fim, tirar nossas conclusões.

É possível, claro, fazer ainda mais melhorias com mais testes de pré-processamento e também de tunning de hyperparâmetros de outros modelos e até utilizar modelos mais robustos como por exemplo uma rede neural.

Vou deixar isso para uma próxima oportunidade! Espero ter ajudado a melhorar seu conhecimento!

Obrigado por nos acompanhar até aqui!

--

--

Pandas Couple
Pandas Couple

Written by Pandas Couple

Casal de Cientistas de Dados, contribuindo para a comunidade de Data Science.

Responses (1)