Implementar el modelo de árbol de clasificación con datos relacionados a una condición de salud de las personas para predecir anomalías de corazón y evaluar la exactitud del modelo mediante la matriz de confusión.
Se cargan librerías y se descargan los datos: https://raw.githubusercontent.com/rpizarrog/Analisis-Inteligente-de-datos/main/datos/heart_2020_cleaned.csv
Los datos están relacionados con aspectos médicos y son valores numéricos de varias variables que caracterizan el estado de salud de 319,795 personas.
Se construye un modelo supervisado basado en el algoritmo de árbol de clasificación para resolver la tarea de clasificación binaria e identificar si una persona padece del corazón o no.
Se construyen datos de entrenamiento y validación al 80% y 20% cada uno.
Se desarrollan los modelos en Python de:
Regresión Logística binaria
Árbol de Clasificación tipo class
K Means
SVM Lineal
SVM Polinomial
SVM Radial
Los modelo se aceptan si tienen un valor de exactitud (accuracy) por encima del 70%..
Los árboles de clasificación son el subtipo de árboles de predicción que se aplica cuando la variable respuesta dependiente es de tipo categórica o cualitativa y que tiene un significado conforme o de acuerdo a una etiqueta.[@amat_rodrigo_arboles_2017]
[@amat_rodrigo_arboles_2020]
La etiqueta puede ser ‘BUENO’ o ‘MALO’; ‘0’ o ‘1’; ‘ALTO’ O ‘BAJO’; ‘ENFERMO, ’NO ENFERMO’; entre otros ejemplos.
Algunas librerías son nuevas, hay que instalarlas desde R, aquí se indican cuáles librerías y con comentario dado que ya se instalaron previamente.
# library(reticulate)
# py_install("statsmodels")
# Tratamiento de datos
import pandas as pd
import numpy as np
import statsmodels.api as sm
# Estadísticas
import scipy
from scipy import stats
# Para partir datos entrenamiento y validación
from sklearn.model_selection import train_test_split
# Modelo de Clasificación
from sklearn.metrics import classification_report
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import plot_tree
from sklearn.tree import export_graphviz
from sklearn.tree import export_text
from sklearn.model_selection import GridSearchCV
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.metrics import accuracy_score
from sklearn.metrics import confusion_matrix
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score
# Gráficos
import matplotlib.pyplot as plt
import seaborn as sb
Se cargan datos del enlace URL, se observan los primeros y últimos registros del conjunto de datos.
datos = pd.read_csv("https://raw.githubusercontent.com/rpizarrog/Analisis-Inteligente-de-datos/main/datos/heart_2020_cleaned.csv")
datos
## HeartDisease BMI Smoking ... Asthma KidneyDisease SkinCancer
## 0 No 16.60 Yes ... Yes No Yes
## 1 No 20.34 No ... No No No
## 2 No 26.58 Yes ... Yes No No
## 3 No 24.21 No ... No No Yes
## 4 No 23.71 No ... No No No
## ... ... ... ... ... ... ... ...
## 319790 Yes 27.41 Yes ... Yes No No
## 319791 No 29.84 Yes ... Yes No No
## 319792 No 24.24 No ... No No No
## 319793 No 32.81 No ... No No No
## 319794 No 46.56 No ... No No No
##
## [319795 rows x 18 columns]
Son 319795 observaciones y 18 variables
print("Observaciones y variables: ", datos.shape)
## Observaciones y variables: (319795, 18)
print("Columnas y tipo de dato")
# datos.columns
## Columnas y tipo de dato
datos.dtypes
## HeartDisease object
## BMI float64
## Smoking object
## AlcoholDrinking object
## Stroke object
## PhysicalHealth float64
## MentalHealth float64
## DiffWalking object
## Sex object
## AgeCategory object
## Race object
## Diabetic object
## PhysicalActivity object
## GenHealth object
## SleepTime float64
## Asthma object
## KidneyDisease object
## SkinCancer object
## dtype: object
Hay 292422 casos sin daño al corazón y el resto que si tienen daño 27373.
frecuencia = (datos.groupby("HeartDisease").agg(frecuencia=("HeartDisease","count")).reset_index())
frecuencia
## HeartDisease frecuencia
## 0 No 292422
## 1 Yes 27373
fig, ax = plt.subplots()
# Colores
bar_labels = ['No', 'Yes']
bar_colors = ['tab:blue', 'tab:red']
#frecuencia['frecuencia'].plot(kind="bar")
ax.bar(frecuencia['HeartDisease'], frecuencia['frecuencia'], label=bar_labels, color=bar_colors)
## <BarContainer object of 2 artists>
ax.set_ylabel('Frecuencia')
ax.set_title('Daños al Corazón')
ax.legend(title='Daño')
plt.show()
# plt.gcf().clear()
Histograma únicamente de las variables numéricas del conjunto de datos ‘BMI’, ‘PhysicalHealth’, ‘MentalHealth’, ‘SleepTime’.
datos[['BMI', 'PhysicalHealth', 'MentalHealth', 'SleepTime']].hist()
## array([[<AxesSubplot: title={'center': 'BMI'}>,
## <AxesSubplot: title={'center': 'PhysicalHealth'}>],
## [<AxesSubplot: title={'center': 'MentalHealth'}>,
## <AxesSubplot: title={'center': 'SleepTime'}>]], dtype=object)
Crear variable llamada HeartDisease01 que se utilizará en el modelo de Regresión Logística tendrá valores 0 de para ‘No’ daño y 1 para si hay daño (‘Yes’).
datos['HeartDisease01'] = np.where(datos ['HeartDisease']== "Yes", 1, 0)
Quitar la variable HeartDisease que ya tiene variable transformada a HeartDisease01
datos = datos.drop("HeartDisease", axis='columns')
Quedaron las columnas:
datos.columns.values
## array(['BMI', 'Smoking', 'AlcoholDrinking', 'Stroke', 'PhysicalHealth',
## 'MentalHealth', 'DiffWalking', 'Sex', 'AgeCategory', 'Race',
## 'Diabetic', 'PhysicalActivity', 'GenHealth', 'SleepTime', 'Asthma',
## 'KidneyDisease', 'SkinCancer', 'HeartDisease01'], dtype=object)
Todas las variables de entrada o variables independientes:
“BMI”: Indice de masa corporal con valores entre 12.02 y 94.85.
“Smoking”: Si la persona es fumadora o no con valores categóritos de ‘Yes’ o ‘No’.
“AlcoholDrinking” : Si consume alcohol o no, con valores categóricos de ‘Yes’ o ‘No’.
“Stroke”: Si padece alguna anomalía cerebrovascular, apoplejia o algo similar, con valores categóricos de ‘Yes’ o ‘No’.
“PhysicalHealth” Estado físico en lo general con valores entre 0 y 30.
“MentalHealth”. Estado mental en lo general con valores entre 0 y 30.
“DiffWalking” . Que si se le dificulta caminar o tiene algún padecimiento al caminar, con valores categóritoc de ‘Yes’ o ‘No’.
“Sex”: Género de la persona, con valores de ‘Female’ y ‘Male’ para distinguir al género femenino y masculino respectivamente.
“AgeCategory”: Una clasificación de la edad de la persona de entre 18 y 80 años. La primera categoría con un rango de edad entre 18-24, a partir de 25 con rangos de 5 en 5 hasta la clase de 75-80 y una última categoría mayores de 80 años.
“Race”. Raza u origen de la persona con valores categóricos de ‘American Indian/Alaskan Native’, ’Asian’,’Black’, ’Hispanic’, ’Other’ y’White’.
“Diabetic”. Si padece o ha padecido de diabetes en cuatro condiciones siendo Yes y No para si o no: ‘No’, ‘borderline diabetes’ condición antes de detectarse diabetes tipo 2, ‘Yes’, y ‘Yes (during pregnancy)’ durante embarazo.
“PhysicalActivity” que si realiza actividad física, con valores categóricos de ‘Yes’ o ‘No’.
“GenHealth”: EStado general de salud de la persona con valores categóricos de ‘Excellent’, ‘Very good’, ‘Good’, ‘Fair’ y ‘Poor’ con significado en español de excelente, muy buena, buena, regular y pobre o deficiente.
“SleepTime”: valor numérico de las horas de sueño u horas que duerme la persona con valores en un rango entre 1 y 24.
“Asthma”: si padece de asma o no, con valores categóricos de ‘Yes’ o ‘No’.
“KidneyDisease”: si tiene algún padecimiento en los riñones, con valores categóricos de ‘Yes’ o ‘No’.
“SkinCancer”: si padece algún tipo de cáncer de piel, con valores categóricos de ‘Yes’ o ‘No’.
La variable de interés como dependiente o variable de salida es la de daño al corazón (HeartDisease), con valores categóricos de ‘Yes’ o ‘No’ , ahora la variable HeartDisease01 con valores ‘1’ o ‘0’.
Nuevamente la descripción de variables y ahora son 319795 observaciones y 18 variables
print("Observaciones y variables: ", datos.shape)
## Observaciones y variables: (319795, 18)
print("Columnas y tipo de dato")
# datos.columns
## Columnas y tipo de dato
datos.dtypes
## BMI float64
## Smoking object
## AlcoholDrinking object
## Stroke object
## PhysicalHealth float64
## MentalHealth float64
## DiffWalking object
## Sex object
## AgeCategory object
## Race object
## Diabetic object
## PhysicalActivity object
## GenHealth object
## SleepTime float64
## Asthma object
## KidneyDisease object
## SkinCancer object
## HeartDisease01 int32
## dtype: object
Para construir el modelo, se requiere variables de tipo numérica, aún se tienen en las variables independiente variables de tipo categóricas u object en Python:
Las variables que son categóricas: ‘Smoking’, ‘AlcoholDrinking’, ‘Stroke’, ‘DiffWalking’, ‘Sex’, ‘AgeCategory’, ‘Race’, ‘Diabetic’, ‘PhysicalActivity’, ‘GenHealth’, ‘Asthma’, ‘KidneyDisease’, ‘SkinCancer’.
Con estas variables, crear variables Dummys y construir un conjunto de datos que incluye las variable dummis.
El método de la librería de Pandas llamado get_dummies() convierte los datos categóricos en variables indicadoras o ficticias.
datos_dummis = pd.get_dummies(datos, drop_first = True)
datos_dummis
## BMI PhysicalHealth ... KidneyDisease_Yes SkinCancer_Yes
## 0 16.60 3.0 ... 0 1
## 1 20.34 0.0 ... 0 0
## 2 26.58 20.0 ... 0 0
## 3 24.21 0.0 ... 0 1
## 4 23.71 28.0 ... 0 0
## ... ... ... ... ... ...
## 319790 27.41 7.0 ... 0 0
## 319791 29.84 0.0 ... 0 0
## 319792 24.24 0.0 ... 0 0
## 319793 32.81 0.0 ... 0 0
## 319794 46.56 0.0 ... 0 0
##
## [319795 rows x 38 columns]
Asi queda el conjunto de datos preparado llamado datos_dummis
datos_dummis.dtypes
## BMI float64
## PhysicalHealth float64
## MentalHealth float64
## SleepTime float64
## HeartDisease01 int32
## Smoking_Yes uint8
## AlcoholDrinking_Yes uint8
## Stroke_Yes uint8
## DiffWalking_Yes uint8
## Sex_Male uint8
## AgeCategory_25-29 uint8
## AgeCategory_30-34 uint8
## AgeCategory_35-39 uint8
## AgeCategory_40-44 uint8
## AgeCategory_45-49 uint8
## AgeCategory_50-54 uint8
## AgeCategory_55-59 uint8
## AgeCategory_60-64 uint8
## AgeCategory_65-69 uint8
## AgeCategory_70-74 uint8
## AgeCategory_75-79 uint8
## AgeCategory_80 or older uint8
## Race_Asian uint8
## Race_Black uint8
## Race_Hispanic uint8
## Race_Other uint8
## Race_White uint8
## Diabetic_No, borderline diabetes uint8
## Diabetic_Yes uint8
## Diabetic_Yes (during pregnancy) uint8
## PhysicalActivity_Yes uint8
## GenHealth_Fair uint8
## GenHealth_Good uint8
## GenHealth_Poor uint8
## GenHealth_Very good uint8
## Asthma_Yes uint8
## KidneyDisease_Yes uint8
## SkinCancer_Yes uint8
## dtype: object
Datos de entrenamiento al 80% de los datos y 20% los datos de validación. Semilla 1264
X_entrena, X_valida, Y_entrena, Y_valida = train_test_split(datos_dummis.drop(columns = "HeartDisease01"), datos_dummis['HeartDisease01'],train_size = 0.80, random_state = 1264)
Se crea un conjunto de datos de validación con 255836 registros y 37 variables.
X_entrena
## BMI PhysicalHealth ... KidneyDisease_Yes SkinCancer_Yes
## 236711 20.18 0.0 ... 0 0
## 274160 21.26 2.0 ... 0 0
## 75292 24.03 0.0 ... 0 0
## 40376 24.03 0.0 ... 0 0
## 20446 35.90 0.0 ... 0 0
## ... ... ... ... ... ...
## 190176 21.95 0.0 ... 0 0
## 303569 33.29 3.0 ... 0 0
## 214186 39.33 0.0 ... 0 0
## 193218 23.75 0.0 ... 0 0
## 762 32.60 0.0 ... 0 0
##
## [255836 rows x 37 columns]
Se crea un conjunto de datos de validación con 63959 registros y 37 variables.
X_valida
## BMI PhysicalHealth ... KidneyDisease_Yes SkinCancer_Yes
## 261532 21.41 0.0 ... 0 0
## 246306 19.97 0.0 ... 0 0
## 296746 31.57 0.0 ... 0 0
## 302593 37.66 0.0 ... 0 0
## 16727 34.87 0.0 ... 0 0
## ... ... ... ... ... ...
## 59431 29.12 20.0 ... 0 0
## 151420 20.12 0.0 ... 0 0
## 239395 28.19 3.0 ... 0 0
## 154423 24.96 2.0 ... 0 1
## 178926 19.77 1.0 ... 0 0
##
## [63959 rows x 37 columns]
Se crea el modelo de árbol de clasificación con datos de entrenamiento
modelo_ac = DecisionTreeClassifier(
max_depth = 5,
criterion = 'gini',
random_state = 1264
)
modelo_ac.fit(X_entrena, Y_entrena)
DecisionTreeClassifier(max_depth=5, random_state=1264)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
DecisionTreeClassifier(max_depth=5, random_state=1264)
fig, ax = plt.subplots(figsize=(13, 6))
print(f"Profundidad del árbol: {modelo_ac.get_depth()}")
## Profundidad del árbol: 5
print(f"Número de nodos terminales: {modelo_ac.get_n_leaves()}")
## Número de nodos terminales: 32
plot = plot_tree(
decision_tree = modelo_ac,
#feature_names = modelo_ac.tolist(),
class_names = 'Daño al corazón',
filled = True,
impurity = False,
fontsize = 7,
ax = ax
)
texto_modelo = export_text(
decision_tree = modelo_ac,
feature_names = list(datos_dummis.drop(columns = "HeartDisease01").columns)
)
print(texto_modelo)
## |--- DiffWalking_Yes <= 0.50
## | |--- Stroke_Yes <= 0.50
## | | |--- Diabetic_Yes <= 0.50
## | | | |--- AgeCategory_80 or older <= 0.50
## | | | | |--- AgeCategory_75-79 <= 0.50
## | | | | | |--- class: 0
## | | | | |--- AgeCategory_75-79 > 0.50
## | | | | | |--- class: 0
## | | | |--- AgeCategory_80 or older > 0.50
## | | | | |--- Sex_Male <= 0.50
## | | | | | |--- class: 0
## | | | | |--- Sex_Male > 0.50
## | | | | | |--- class: 0
## | | |--- Diabetic_Yes > 0.50
## | | | |--- Sex_Male <= 0.50
## | | | | |--- KidneyDisease_Yes <= 0.50
## | | | | | |--- class: 0
## | | | | |--- KidneyDisease_Yes > 0.50
## | | | | | |--- class: 0
## | | | |--- Sex_Male > 0.50
## | | | | |--- KidneyDisease_Yes <= 0.50
## | | | | | |--- class: 0
## | | | | |--- KidneyDisease_Yes > 0.50
## | | | | | |--- class: 0
## | |--- Stroke_Yes > 0.50
## | | |--- Diabetic_Yes <= 0.50
## | | | |--- GenHealth_Fair <= 0.50
## | | | | |--- GenHealth_Poor <= 0.50
## | | | | | |--- class: 0
## | | | | |--- GenHealth_Poor > 0.50
## | | | | | |--- class: 0
## | | | |--- GenHealth_Fair > 0.50
## | | | | |--- MentalHealth <= 1.50
## | | | | | |--- class: 0
## | | | | |--- MentalHealth > 1.50
## | | | | | |--- class: 0
## | | |--- Diabetic_Yes > 0.50
## | | | |--- PhysicalHealth <= 6.50
## | | | | |--- GenHealth_Fair <= 0.50
## | | | | | |--- class: 0
## | | | | |--- GenHealth_Fair > 0.50
## | | | | | |--- class: 0
## | | | |--- PhysicalHealth > 6.50
## | | | | |--- BMI <= 37.38
## | | | | | |--- class: 1
## | | | | |--- BMI > 37.38
## | | | | | |--- class: 0
## |--- DiffWalking_Yes > 0.50
## | |--- Stroke_Yes <= 0.50
## | | |--- KidneyDisease_Yes <= 0.50
## | | | |--- GenHealth_Poor <= 0.50
## | | | | |--- Diabetic_Yes <= 0.50
## | | | | | |--- class: 0
## | | | | |--- Diabetic_Yes > 0.50
## | | | | | |--- class: 0
## | | | |--- GenHealth_Poor > 0.50
## | | | | |--- Diabetic_Yes <= 0.50
## | | | | | |--- class: 0
## | | | | |--- Diabetic_Yes > 0.50
## | | | | | |--- class: 0
## | | |--- KidneyDisease_Yes > 0.50
## | | | |--- GenHealth_Poor <= 0.50
## | | | | |--- Diabetic_Yes <= 0.50
## | | | | | |--- class: 0
## | | | | |--- Diabetic_Yes > 0.50
## | | | | | |--- class: 0
## | | | |--- GenHealth_Poor > 0.50
## | | | | |--- SkinCancer_Yes <= 0.50
## | | | | | |--- class: 0
## | | | | |--- SkinCancer_Yes > 0.50
## | | | | | |--- class: 1
## | |--- Stroke_Yes > 0.50
## | | |--- GenHealth_Poor <= 0.50
## | | | |--- Sex_Male <= 0.50
## | | | | |--- KidneyDisease_Yes <= 0.50
## | | | | | |--- class: 0
## | | | | |--- KidneyDisease_Yes > 0.50
## | | | | | |--- class: 0
## | | | |--- Sex_Male > 0.50
## | | | | |--- Diabetic_Yes <= 0.50
## | | | | | |--- class: 0
## | | | | |--- Diabetic_Yes > 0.50
## | | | | | |--- class: 1
## | | |--- GenHealth_Poor > 0.50
## | | | |--- KidneyDisease_Yes <= 0.50
## | | | | |--- Sex_Male <= 0.50
## | | | | | |--- class: 0
## | | | | |--- Sex_Male > 0.50
## | | | | | |--- class: 1
## | | | |--- KidneyDisease_Yes > 0.50
## | | | | |--- Sex_Male <= 0.50
## | | | | | |--- class: 1
## | | | | |--- Sex_Male > 0.50
## | | | | | |--- class: 1
Se construyen predicciones con los datos de validación.
predicciones = modelo_ac.predict(X_valida)
print(predicciones)
## [0 0 0 ... 0 0 0]
comparaciones = pd.DataFrame(X_valida)
comparaciones = comparaciones.assign(HeartDisease_Real = Y_valida)
comparaciones = comparaciones.assign(HeartDisease_Pred = predicciones.flatten().tolist())
print(comparaciones)
## BMI PhysicalHealth ... HeartDisease_Real HeartDisease_Pred
## 261532 21.41 0.0 ... 0 0
## 246306 19.97 0.0 ... 0 0
## 296746 31.57 0.0 ... 0 0
## 302593 37.66 0.0 ... 0 0
## 16727 34.87 0.0 ... 1 0
## ... ... ... ... ... ...
## 59431 29.12 20.0 ... 1 0
## 151420 20.12 0.0 ... 0 0
## 239395 28.19 3.0 ... 0 0
## 154423 24.96 2.0 ... 0 0
## 178926 19.77 1.0 ... 0 0
##
## [63959 rows x 39 columns]
Se evalúa el modelo con la matriz de confusión
print(confusion_matrix(comparaciones['HeartDisease_Real'], comparaciones['HeartDisease_Pred']))
## [[58271 176]
## [ 5269 243]]
matriz = confusion_matrix(comparaciones['HeartDisease_Real'], comparaciones['HeartDisease_Pred'])
print(classification_report(comparaciones['HeartDisease_Real'], comparaciones['HeartDisease_Pred']))
## precision recall f1-score support
##
## 0 0.92 1.00 0.96 58447
## 1 0.58 0.04 0.08 5512
##
## accuracy 0.91 63959
## macro avg 0.75 0.52 0.52 63959
## weighted avg 0.89 0.91 0.88 63959
accuracy = accuracy_score(
y_true = comparaciones['HeartDisease_Real'],
y_pred = comparaciones['HeartDisease_Pred'],
normalize = True
)
print(f"El accuracy de test es: {100 * accuracy} %")
## El accuracy de test es: 91.48673368876938 %
Se crea un registro de una persona con ciertas condiciones de salud a partir de un diccionario.
# Se crea un diccionario
registro = {'BMI': 38, 'PhysicalHealth': 2, 'MentalHealth': 5, 'SleepTime' : 12, 'Smoking_Yes' : 1, 'AlcoholDrinking_Yes' : 1, 'Stroke_Yes' : 1, 'DiffWalking_Yes': 1, 'Sex_Male': 1,
'AgeCategory_25-29' : 0, 'AgeCategory_30-34' : 0,
'AgeCategory_35-39' : 0, 'AgeCategory_40-44' : 0,
'AgeCategory_45-49' : 0, 'AgeCategory_50-54' : 0,
'AgeCategory_55-59' : 0, 'AgeCategory_60-64' : 0,
'AgeCategory_65-69' : 0, 'AgeCategory_70-74': 1,
'AgeCategory_75-79' : 0, 'AgeCategory_80 or older' : 0, 'Race_Asian' : 0, 'Race_Black' : 1, 'Race_Hispanic' : 0,
'Race_Other' : 0, 'Race_White' : 0,
'Diabetic_No, borderline diabetes' : 0, 'Diabetic_Yes' : 1,
'Diabetic_Yes (during pregnancy)' : 0,
'PhysicalActivity_Yes' : 0, 'GenHealth_Fair' : 1,
'GenHealth_Good' : 0, 'GenHealth_Poor' : 0,
'GenHealth_Very good' : 0, 'Asthma_Yes' : 1, 'KidneyDisease_Yes':1, 'SkinCancer_Yes': 0}
persona = pd.DataFrame()
persona = persona.append(registro, ignore_index=True)
## <string>:1: FutureWarning: The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.
persona
## BMI PhysicalHealth ... KidneyDisease_Yes SkinCancer_Yes
## 0 38 2 ... 1 0
##
## [1 rows x 37 columns]
Se hace la predicción en términos de clasificación de la persona con estos valores para saber si tiene o no daño en el corazón:
prediccion = modelo_ac.predict(persona)
print(prediccion)
## [1]
La predicción en términos de clasificación de la persona con las características proporcionadas es que está enfermo o tiene daño del corazón.
Al igual que con el modelo de regresion logistica binaria, con el seed “1264” existe una mayor numero de Falsos Negativos (5155) que de Falsos Positivos (340), a pesar de que tenga un rendimiendo de 91% de Accuarcy este modelo, al ser un tema de salud, en este contexto es de mucha gravedad que haya mas inclinacion de error en Falsos Negativos ya que vidas correran peligro a diferencia si sale algun Falso Positivo, en donde con mas estudios solo sera como una noticia de mal gusto. Por lo que valdria la pena buscar la mejora de este modelo o alternativa.
Habiendo realizado predicciones con los datos de validación, se tiene un valor de aproximadamente del \(91.68 \%\) aproximadamente el 92% de exactitud en el modelo de árbol de regresión.
El modelo se aprueba dado que la métrica era igual o superior del \(70%\).
Habrá que compararlo contra otro modelo de clasificación.