<jupyter_start><jupyter_text>Cargando los datos El primer paso consistirá en cargar las librerías necesarias (pandas, seaborn, matplotlib y scikit-learn).<jupyter_code>import pandas as pd
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import SVC
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score
from sklearn.model_selection import GridSearchCV<jupyter_output><empty_output><jupyter_text>Seguidamente, se procederá a cargar los datos a través del método de pandas "read_csv", renombrando la cabecera para hacerla más intuitiva.<jupyter_code>labels = [
    'edad',
    'sexo',
    'dolor_pecho',
    'presion_arterial_reposo',
    'colesterol',
    'azúcar_ayunas',
    'electro_reposo',
    'frecuencia_máxima',
    'angina_por_ejercicio',
    'depresión_ST_por_ejercicio',
    'pendiente_ST_max_ejercicio',
    'número_vasos',
    'talio',
    'resultado'
]
df = pd.read_csv('heart.csv', header=0, names = labels)
df.head(15)<jupyter_output><empty_output><jupyter_text>Análisis exploratorio preliminar A continuación, realizaremos un primer análisis exploratorio de los datos para comprobar con qué contamos.<jupyter_code>df.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
RangeIndex: 303 entries, 0 to 302
Data columns (total 14 columns):
 #   Column                      Non-Null Count  Dtype  
---  ------                      --------------  -----  
 0   edad                        303 non-null    int64  
 1   sexo                        303 non-null    int64  
 2   dolor_pecho                 303 non-null    int64  
 3   presion_arterial_reposo     303 non-null    int64  
 4   colesterol                  303 non-null    int64  
 5   azúcar_ayunas               303 non-null    int64  
 6   electro_reposo              303 non-null    int64  
 7   frecuencia_máxima           303 non-null    int64  
 8   angina_por_ejercicio        303 non-null    int64  
 9   depresión_ST_por_ejercicio  303 non-null    float64
 10  pendiente_ST_max_ejercicio  303 non-null    int64  
 11  número_vasos                303 non-null    int64  
 12  talio                       303 non-null    int64  
 13  resultado                   303 non[...]<jupyter_text>Se comprueba que contamos con 303 entradas y que para ninguna de las categorías hay elementos nulos (NaN)  Representaciones gráficas<jupyter_code>sns.countplot(x = 'resultado', data = df)
plt.xlabel("0 - Baja     1 - Alta")
plt.ylabel("Número de personas")
plt.title("Distribución total de probabilidad de sufrir infarto")
plt.show()
print('Alta probabilidad: ',(df['resultado']==1).sum())
print('Baja probabilidad: ',(df['resultado']==0).sum())<jupyter_output>Alta probabilidad:  165
Baja probabilidad:  138<jupyter_text>Con esta primera visualización se comprueba que, para la muestra de datos, 165 personas tienen altas probabilidades de sufrir un infarto frente a 138 que no.<jupyter_code>sns.countplot(x = 'resultado', hue = 'sexo', data = df)
plt.xlabel("0 - Baja     1 - Alta")
plt.ylabel("Número de personas")
plt.title("Probabilidad de sufrir infarto por género")
plt.legend(['Mujeres','Hombres'])
plt.show()<jupyter_output><empty_output><jupyter_text>Este gráfico parece indicarnos que, en relación a su género, las mujeres cuentan con más probabilidades de padecer un infarto que los hombres. Habría que comprobar si los datos están balanceados.<jupyter_code>print('Número total de mujeres: ', (df['sexo']==0).sum())
print('Número total de hombres: ', (df['sexo']==1).sum())<jupyter_output>Número total de mujeres:  96
Número total de hombres:  207<jupyter_text>Esta distribución se puede comprobar en la siguiente imagen:<jupyter_code>sns.countplot(x = 'sexo', data = df)
plt.xlabel("Sexo")
plt.ylabel("Número de personas")
plt.title("Distribución por sexo")
plt.legend(['Mujeres','Hombres'])
plt.show()
plt.figure(figsize=(8, 5))
sns.kdeplot(df['edad'],shade=True, color='g')
plt.show()<jupyter_output><empty_output><jupyter_text>Esta gráfica nos muestra el rango de edades de la muestra. Se comprueba que la mayor parte de las personas cuentan con una edad comprendida entre los 45 y los 65 años.<jupyter_code>df['dolor_pecho'].value_counts().plot(kind='bar',label='Dolor', color='lightblue')
plt.legend()
plt.xlabel('Escala de dolor')
plt.show()
print('Sin dolor (asintomático):                      ', (df['dolor_pecho']==0).sum())
print('Dolor de angina de pecho atípica:               ', (df['dolor_pecho']==2).sum())
print('Dolor de angina de pecho común:                 ', (df['dolor_pecho']==1).sum())
print('Dolor fuerte no compatible con angina de pecho: ', (df['dolor_pecho']==3).sum())<jupyter_output>Sin dolor (asintomático):                       143
Dolor de angina de pecho atípica:                87
Dolor de angina de pecho común:                  50
Dolor fuerte no compatible con angina de pecho:  23<jupyter_text>Esto nos indica que la mayoría de pacientes no sufre de dolor alguno en el pecho, seguido de 87 casos en los que el dolor se corresponde con una angina de pecho típica, 50 con un dolor medio compatible con anginas de pecho atípicas y 23 pacientes con un dolor fuerte no compatible con ninguna modalidad de angina de pecho<jupyter_code>plt.figure(figsize=(8, 5))
sns.histplot(df['presion_arterial_reposo'], color="red", kde= True, label='Presión sanguínea')
plt.legend()
plt.show()<jupyter_output><empty_output><jupyter_text>Esta imagen nos indica que los valores más habituales para la presión arterial en reposo están comprendidos entre 110 y 140 mm de Mercurio.<jupyter_code>plt.figure(figsize=(8, 5))
sns.kdeplot(df['colesterol'],color='black',shade=True,label='Colesterol')
plt.legend()
plt.show()<jupyter_output><empty_output><jupyter_text>Los valores más comunes de colesterol en sangre se distribuyen entre los 180 y 300 mg/dl.<jupyter_code>plt.figure(figsize=(8, 5))
sns.countplot(x='azúcar_ayunas', data = df, palette='YlOrBr')
plt.title('Azúcar en sangre de la población')
plt.xlabel('Azúcar > 120 mg/dl')
plt.show()<jupyter_output><empty_output><jupyter_text>Aquí se nos indica que la gran mayoría de personas tienen unos niveles de azúcar en sangre inferior a los 120 mg/dl. Cosa sorprendente viendo la cantidad de azúcares añadidos que contienen todas las comidas generalmente.<jupyter_code>plt.figure(figsize=(8, 11))
colores = ["#60D394","#FFD97D","#FF9B85"]
df['electro_reposo'].value_counts().plot(kind='pie',label='Electrocardiograma', colors=colores, autopct="%0.1f %%")
plt.legend(['onda ST-T anormal','normal','hipertrofia ventricular'], loc="best")
plt.xlabel('Electro en reposo')
plt.show()<jupyter_output><empty_output><jupyter_text>Este gráfico circular nos indica el porcentaje en los resultados de los electrocardiogramas efectuados. El 48.5% presentan un electro normal frente al 50.2% que tienen la onda ST-T anormal. Contaríamos con un 1.3% de pacientes que cuentan con hipertrofia ventricular izquierda.<jupyter_code>plt.figure(figsize=(8, 5))
sns.histplot(df['frecuencia_máxima'], color="purple", kde= True, label='Frecuencia máxima')
plt.legend(loc='upper left')
plt.show()<jupyter_output><empty_output><jupyter_text>En esta ocasión, podemos observar que la frecuencia máxima más común se encuentra en el intervalo entre las 130 y 180 pulsaciones por minuto. El máximo lo encontramos en 202 pulsaciones, donde muy pocos pacientes (sólo uno) llegan a alcanzar ese límite.<jupyter_code>print('Frecuencia cardíaca máxima:                     ', df['frecuencia_máxima'].max())
print('Nº de personas que alcanzan la frecuencia máxima: ',(df['frecuencia_máxima']==202).sum())
plt.figure(figsize=(8, 5))
df['angina_por_ejercicio'].value_counts().plot(kind='bar',label='Angina inducida', color = '#be9b22')
plt.legend()
plt.xlabel('Angina por ejercicio')
plt.show()
print('Anginas no provocadas por el ejercicio: ', (df['angina_por_ejercicio']==0).sum())
print('Anginas provocadas por el ejercicio:     ', (df['angina_por_ejercicio']==1).sum())<jupyter_output>Anginas no provocadas por el ejercicio:  204
Anginas provocadas por el ejercicio:      99<jupyter_text>La mayoría de anginas no son provocadas por la realización del ejercicio.<jupyter_code>plt.figure(figsize=(14, 5))
sns.kdeplot(df['depresión_ST_por_ejercicio'],shade=False,label='Depresión ST inducido por el ejercicio en relación al reposo')
plt.legend()
plt.xlabel('Depresión onda ST por el ejercicio')
plt.show()
plt.figure(figsize=(8, 5))
df['pendiente_ST_max_ejercicio'].value_counts().plot(kind='bar',label='Pendiente', color="brown")
plt.legend()
plt.xlabel('Pendiente del segmento ST en ejercicio máximo')
plt.show()
print('Nº de personas con pendiente ascendente: ',(df['pendiente_ST_max_ejercicio']==2).sum())
print('Nº de personas con pendiente descendente: ',(df['pendiente_ST_max_ejercicio']==0).sum())
print('Nº de personas con pendiente plana:      ',(df['pendiente_ST_max_ejercicio']==2).sum())
plt.figure(figsize=(8, 5))
df['número_vasos'].value_counts().plot(kind='bar',label='Vasos principales',color='#73a925')
plt.legend()
plt.xlabel('Número de vasos principales coloreados por fluoroscopia')
plt.show()<jupyter_output><empty_output><jupyter_text>Esto nos indica que la mayoría de casos no presentan coloración en las arterias principales debido a la fluoroscopia.  Análisis exploratorio (EDA) A continuación, realizaremos un primer análisis exploratorio de los datos para comprobar con qué contamos.<jupyter_code>df.shape<jupyter_output><empty_output><jupyter_text>Gracias a este método, comprobamos que el dataset, transformado en dataframe, está compuesto por 303 filas y 14 columnas o categorías.<jupyter_code>df.ndim<jupyter_output><empty_output><jupyter_text>Las dimensiones de que está formado son 2.<jupyter_code>df.size<jupyter_output><empty_output><jupyter_text>El tamaño o número total de datos es de 4.242 (nº de filas x nº de columnas).<jupyter_code>df.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
RangeIndex: 303 entries, 0 to 302
Data columns (total 14 columns):
 #   Column                      Non-Null Count  Dtype  
---  ------                      --------------  -----  
 0   edad                        303 non-null    int64  
 1   sexo                        303 non-null    int64  
 2   dolor_pecho                 303 non-null    int64  
 3   presion_arterial_reposo     303 non-null    int64  
 4   colesterol                  303 non-null    int64  
 5   azúcar_ayunas               303 non-null    int64  
 6   electro_reposo              303 non-null    int64  
 7   frecuencia_máxima           303 non-null    int64  
 8   angina_por_ejercicio        303 non-null    int64  
 9   depresión_ST_por_ejercicio  303 non-null    float64
 10  pendiente_ST_max_ejercicio  303 non-null    int64  
 11  número_vasos                303 non-null    int64  
 12  talio                       303 non-null    int64  
 13  resultado                   303 non[...]<jupyter_text>De esta forma comprobamos todas las categorías que conforman el dataframe (14 en total), presentándose 303 entradas donde no hay datos nulos. El tamaño en memoria es de 33.3 KB y podemos apreciar que la mayoría de los datos son de tipo integer (enteros).<jupyter_code>df.apply(lambda x: x.unique())<jupyter_output><empty_output><jupyter_text>Aquí podemos comprobar los valores únicos presentes para cada una de las variables. Como vemos, algunas de estas variables parecen ser categóricas (sexo, dolor_pecho, azucar_ayunas, electro_reposo, angina_por_ejercicio, pendiente_ST_max_ejercicio, número_vasos, talio y resultado).<jupyter_code>df.nunique(axis=0)<jupyter_output><empty_output><jupyter_text>En esta tabla, indicamos el número total de variables únicas presentes en cada categoría.<jupyter_code>df.describe()<jupyter_output><empty_output><jupyter_text>Aquí se muestran los estadísticos básicos de todas las categorías del dataframe. Otra manera sería analizar estos datos de forma independiente de uno en uno. Vamos a ver el ejemplo para la categoría "edad".<jupyter_code>df['edad'].mean()<jupyter_output><empty_output><jupyter_text>Nos indica que la edad media es de 54.36 años. Esto coincide con lo visto en la tabla superior obtenida a través del método .describe.<jupyter_code>df.edad.std()<jupyter_output><empty_output><jupyter_text>La desviación estándar es de 9.08, por lo que podemos asumir que los datos cuentan con cierta dispersión con respecto a la media, aunque podemos concluir que la mayor parte de los pacientes tienen una edad particularmente concentrada entorno a la misma. La mayoría deberán presentar una edad comprendida entre los 45 y los 63 años.<jupyter_code>df.edad.min()<jupyter_output><empty_output><jupyter_text>La edad mínima contenida en los datos (paciente más jóven).<jupyter_code>df['edad'].max()<jupyter_output><empty_output><jupyter_text>La edad máxima que podemos encontrar registrada.<jupyter_code>df.edad.quantile(0.25)<jupyter_output><empty_output><jupyter_text>En 47.5 encontramos el primer cuartil, por debajo de este número se concentran el 25% de los datos.<jupyter_code>df['edad'].quantile(0.75)<jupyter_output><empty_output><jupyter_text>Por encima de los 61 años, se concentra el 25% de pacientes de mayor edad. Como comprobamos a través del método .info(), no se presentan datos nulos pero de querer calcular el porcentaje sería con la siguiente expresión:<jupyter_code>(df.sexo.isna().sum())*100/(df.sexo.count())<jupyter_output><empty_output><jupyter_text>Otra forma de llegar al mismo resultado, sería:<jupyter_code>(df.sexo.isna().sum())*100/(len(df.edad))<jupyter_output><empty_output><jupyter_text>Como se puede observar, en todos los casos el porcentaje es del 0% dado que no hay datos faltantes en el dataframe.  Representaciones boxplot<jupyter_code>plt.figure(figsize = (14,5))
sns.boxplot(x = 'edad', data = df)
plt.show()<jupyter_output><empty_output><jupyter_text>Para la edad, no encontramos outliers.<jupyter_code>plt.figure(figsize = (14,5))
sns.boxplot(x = 'colesterol', data = df, color='g')
plt.show()<jupyter_output><empty_output><jupyter_text>Para la variable "colesterol", sí se encuentran outliers. En concreto, por encima de Q3 + 1.5 * Rango_intercuartílico.<jupyter_code>plt.figure(figsize = (14,5))
sns.boxplot(x = 'presion_arterial_reposo', data = df, color='#f17c67')
plt.show()<jupyter_output><empty_output><jupyter_text>Para la variable "presion_arterial_reposo", sí se encuentran outliers. En concreto, por encima de Q3 + 1.5 * Rango_intercuartílico.<jupyter_code>plt.figure(figsize = (14,5))
sns.boxplot(x = 'frecuencia_máxima', data = df, color='#67d8f1')
plt.show()<jupyter_output><empty_output><jupyter_text>Para la variable "frecuencia_máxima", sí se encuentran outliers. En concreto, por debajo de Q1 - 1.5 * Rango_intercuartílico.<jupyter_code>plt.figure(figsize = (14,5))
sns.boxplot(x = 'depresión_ST_por_ejercicio', data = df, color='#fffb00')
plt.show()<jupyter_output><empty_output><jupyter_text>Para la variable "depresión_ST_por_ejercicio", sí se encuentran outliers. En concreto, por encima de Q3 + 1.5 * Rango_intercuartílico.  Matriz de correlación A continuación, vamos a representar la matriz que correlaciona cada una de las variables del dataframe:<jupyter_code>plt.figure(figsize = (14,11))
sns.heatmap(df.corr(), annot = True)
plt.show()<jupyter_output><empty_output><jupyter_text>Donde se observa una correlación más pronunciada es entre los resultados con el dolor de pecho, la angina inducida por ejercicio y la depresión de la onda ST producida por el ejercicio. Sin embargo, en el enunciado se nos solicita realizar la matriz de correlación para las variables numéricas continuas. De este modo:<jupyter_code>df_var_con = df[['edad','presion_arterial_reposo','colesterol','frecuencia_máxima','depresión_ST_por_ejercicio']]
df_var_con.head()
df_var_con.dtypes
corr = df_var_con.corr().round(3)
corr
corr.style.background_gradient()<jupyter_output><empty_output><jupyter_text>La matriz de correlación de las variables continuas muestra que no existe una correlación muy pronunciada entre ellas. Donde se aprecia una mayor correlación es entre la presión arterial y la edad (0.279), el colesterol con la edad (0.214) y la depresión en la onda ST inducida por el ejercicio con la edad (0.210). En resumen, no se presentan valores lo suficientemente elevados como para considerar una correlación fuerte entre variables. El factor más determinante e influyente de entre todas las variables es la edad de los pacientes.  Preprocesado de los datos Sería buena práctica realizar algún procesado de los datos para facilitar la ingesta en el modelo de machine learning. En particular, parecería acertado escalar los datos para tratar los valores atípicos (outliers) que se presentan. En relación a las variables categóricas, sería necesario realizar la transformación de las mismas a variables dummy. Del mismo modo, siempre que exista colinealidad directa entre dichas variables dummy, habría que eliminar las que resultaran recurrentes. Un ejemplo de esto sería el sexo, si un paciente es hombre, por definición no podrá ser mujer, entonces el género quedaría totalmente definido con una única columna en lugar de con dos al llevarse a cabo la transformación a valores dummy (0 ó 1). Entediéndose la dimensión del Dataset como 2 (filas y columnas), no sería necesario ni posible reducir las dimensiones del mismo. Sin embargo, dado que parece que hay variables con mayor peso que otras, se podría estudiar el reducir aquellas categorías que fueran menos relevantes para el modelo (por ejemplo, aplicando feature importances).<jupyter_code>df[df.duplicated()]<jupyter_output><empty_output><jupyter_text>Revisando los posibles duplicados, comprobamos que contamos con una fila con información repetida. Procedemos a eliminarla:<jupyter_code>df.drop_duplicates(keep='first',inplace=True)<jupyter_output><empty_output><jupyter_text>Y comprobamos primeramente que, en efecto, tenemos una fila menos que inicialmente (302 filas) y que no contamos con duplicados:<jupyter_code>df.shape
df[df.duplicated()]<jupyter_output><empty_output><jupyter_text>Ahora debemos dividir los datos en x (variables predictorias) e y (la variable dependiente, a predecir).<jupyter_code>x = df.drop('resultado', axis=1)
x.head()
y = df[['resultado']]
y.head()<jupyter_output><empty_output><jupyter_text>Realizamos una lista con las variables categóricas (nos va a simplificar la transformación a variables dummy):<jupyter_code>var_cat = ['sexo','dolor_pecho','azúcar_ayunas','electro_reposo','angina_por_ejercicio','pendiente_ST_max_ejercicio','número_vasos','talio']
x_dummy = pd.get_dummies(x, columns = var_cat)
x_dummy.head()<jupyter_output><empty_output><jupyter_text>Pero como deseamos simplificar eliminando aquellas variables que presenten colinealidad directa, aplicamos el atributo drop_first = True, resultando:<jupyter_code>x_dummy = pd.get_dummies(x, columns = var_cat, drop_first = True)
x_dummy.head()<jupyter_output><empty_output><jupyter_text>Obsérvese como pasamos de contar con 30 columnas a 22. A continuación, deberemos dividir los datos x e y en datos de entrenamiento (train) y de testeo (test) respectivamente. Para ello, aplicamos el siguiente método contenido en scikit-learn:<jupyter_code>x_train, x_test, y_train, y_test = train_test_split(x_dummy, y, test_size = 0.2)<jupyter_output><empty_output><jupyter_text>Con estos pasos hemos preprocesado los datos, eliminando duplicados y transformando las variables categóricas a variables dummy interpretables por nuestros modelos predictivos. Adicionalmente, se han divido los datos en train y test.  Entrenando los modelos predictivos  Árboles de decisión En este primer apartado, estudiaremos las predicciones obtenidas a través del modelo de árboles de decisión. Instanciamos el modelo predictivo:<jupyter_code>dtc = DecisionTreeClassifier()<jupyter_output><empty_output><jupyter_text>Lo ajustamos con los datos de entrenamiento con que contamos:<jupyter_code>dtc.fit(x_train, y_train)<jupyter_output><empty_output><jupyter_text>Y realizamos las predicciones a través de los datos de testeo:<jupyter_code>predicciones_dtc = dtc.predict(x_test)
predicciones_dtc<jupyter_output><empty_output><jupyter_text>Ahora comprobaremos lo eficaz que resulta este modelo predictivo. Para ello haremos uso del reporte de clasificación:<jupyter_code>print(classification_report(y_test, predicciones_dtc))<jupyter_output>precision    recall  f1-score   support

           0       0.74      0.79      0.77        29
           1       0.80      0.75      0.77        32

    accuracy                           0.77        61
   macro avg       0.77      0.77      0.77        61
weighted avg       0.77      0.77      0.77        61<jupyter_text>Los valores de precisión obtenidos son algo bajos, contando con un 74% de acierto en los casos en que hay menos probabilidad de ataque al corazón (valor de la variable "resultado" = 0), y con un 80% de acierto para los supuestos en los que hay alta probabilidad de sufrir un infarto (valor de la variable "resultado" = 1). Vamos a ver cómo quedaría la matriz de confusión:<jupyter_code>print(confusion_matrix(y_test, predicciones_dtc))<jupyter_output>[[23  6]
 [ 8 24]]<jupyter_text>Se comprueba que se obtienen 23 verdaderos positivos y 24 verdaderos negativos.  Máquinas de soporte de vectores En este segundo apartado, estudiaremos las predicciones obtenidas a través del modelo de máquinas de soporte de vectores. Instanciamos el modelo predictivo:<jupyter_code>svm = SVC(kernel = 'linear')<jupyter_output><empty_output><jupyter_text>Lo ajustamos con los datos de entrenamiento con que contamos:<jupyter_code>svm.fit(x_train, y_train)<jupyter_output>C:\Users\usuario\anaconda3\envs\aepi\lib\site-packages\sklearn\utils\validation.py:63: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().
  return f(*args, **kwargs)<jupyter_text>Y realizamos las predicciones a través de los datos de testeo:<jupyter_code>predicciones_svm = svm.predict(x_test)
predicciones_svm<jupyter_output><empty_output><jupyter_text>Ahora comprobaremos lo eficaz que resulta este modelo predictivo. Para ello haremos uso del reporte de clasificación:<jupyter_code>print(classification_report(y_test, predicciones_svm))<jupyter_output>precision    recall  f1-score   support

           0       0.86      0.83      0.84        29
           1       0.85      0.88      0.86        32

    accuracy                           0.85        61
   macro avg       0.85      0.85      0.85        61
weighted avg       0.85      0.85      0.85        61<jupyter_text>Los valores de precisión obtenidos son mejores a los obtenidos en el apartado anterior, contando con un 86% de acierto en los casos en que hay menos probabilidad de ataque al corazón (valor de la variable "resultado" = 0), y con un 85% de acierto para los supuestos en los que hay alta probabilidad de sufrir un infarto (valor de la variable "resultado" = 1). Vamos a ver cómo quedaría la matriz de confusión:<jupyter_code>print(confusion_matrix(y_test, predicciones_svm))<jupyter_output>[[24  5]
 [ 4 28]]<jupyter_text>Se comprueba que se obtienen 24 verdaderos positivos y 28 verdaderos negativos. Hasta este punto, podemos concluir que resulta más efectivo el método de predicción a través de máquinas de soporte de vectores que el método de árboles de decisión.  Optimización de parámetros Veamos qué resultados podemos llegar a obtener si optimizamos la variables de entrada en los modelos predictivos. Primero de todo, vamos a determinar qué variables tienen más peso para los modelos predictivos, con objeto de simplificar y reducir la cantidad de variables a introducir antes de llevar a cabo el entrenamiento de los datos. Para ello haremos uso del método "feature_importances":  Árboles de decisión<jupyter_code>accuracy_score(y_test, predicciones_dtc)
dtc_feat_imp = pd.DataFrame(dtc.feature_importances_,
                index = x_train.columns,
                columns = ['importancia']).sort_values('importancia', ascending = False)
dtc_feat_imp<jupyter_output><empty_output><jupyter_text>Nota: las variables se ordenan de mayor a menor importancia a través de la instancia“.sort_values(‘importancia’, ascending=False)”. Ahora es el momento de llevar a cabo las predicciones considerando de forma iterativa cada unade estas características. Es decir, se realiza una primera predicción contando únicamente con lavariable "talio_2". Después, se obtiene la predicción considerando las dos primeras variables ("talio_2" y"depresión_ST_por_ejercicio"), pasando posteriormente a ejecutarse la predicción añadiendo secuencialmente una a unacada una de las variables restantes. Por último, se devolverá por pantalla la puntuación de lasprecisiones obtenidas a través del método accuracy_score.<jupyter_code>dtc_feat_imp.size
range_features = range(1,23)<jupyter_output><empty_output><jupyter_text>Creamos una función que irá rellenando de forma iterativa una lista vacía donde se irán almacenando las puntuaciones de nuestro modelo en base a las categorías que vayan incluyéndose:<jupyter_code>def puntuaciones_mayores(n):
    dtc = DecisionTreeClassifier()
    dtc.fit(x_train[list(dtc_feat_imp.index[:n])], y_train)
    predicciones_dtc = dtc.predict(x_test[list(dtc_feat_imp.index[:n])])
    return accuracy_score(y_test, predicciones_dtc)
accuracy_scores = []
for n in range_features:
    precision_actual = puntuaciones_mayores(n)
    accuracy_scores.append(precision_actual)
accuracy_scores<jupyter_output><empty_output><jupyter_text>Se observa que el mejor valor de precisión se consigue considerando las 9 primeras variables (0.836). Procedemos a suprimir de x_dummy las variables "despreciables":<jupyter_code>x_dummy.drop(['número_vasos_1', 'número_vasos_2', 'talio_1', 'electro_reposo_1', 'número_vasos_3', 'número_vasos_4', 'electro_reposo_2', 'pendiente_ST_max_ejercicio_2', 'pendiente_ST_max_ejercicio_1', 'angina_por_ejercicio_1', 'azúcar_ayunas_1', 'dolor_pecho_1', 'talio_3'], axis = 1, inplace = True )
x_dummy<jupyter_output><empty_output><jupyter_text>Quedando reducidas su columnas a 9. Ahora volvemos a dividir en datos de entrenamiento y testeo:<jupyter_code>x_train, x_test, y_train, y_test = train_test_split(x_dummy, y, test_size = 0.2)<jupyter_output><empty_output><jupyter_text>Seguidamente, se aplicará la función GridSearchCV(), de la librería SCIKIT-LEARN. Estafunción sirve para, una vez definido el conjunto de hiperparámetros que constituyen elalgoritmo, determinar los valores con los que se consigue una predicción másexacta: Definiremos los posibles hiperparámetros de la siguiente forma:<jupyter_code>param_grid = {
    'max_depth': [x for x in range(1,100,10)],
    'criterion': ['gini', 'entropy'],
    'splitter': ['best', 'random'],
    'random_state': [x for x in range(1,100,10)],
    'min_samples_split': [x for x in range(2,20,2)],
    'min_samples_leaf': [1,2,3,4,5,6]
}<jupyter_output><empty_output><jupyter_text>Se instancia la función GridSearchCV(), especificando que se ejecuta sobre el modelo dtc anteriormente declarado:<jupyter_code>dtc_CV = GridSearchCV(estimator = dtc, param_grid = param_grid, n_jobs = -1)<jupyter_output><empty_output><jupyter_text>Y se vuelve a ajustar:<jupyter_code>dtc_CV.fit(x_train, y_train)<jupyter_output><empty_output><jupyter_text>Para determinar con qué hiperparámetros las predicciones son óptimas:<jupyter_code>dtc_CV.best_params_<jupyter_output><empty_output><jupyter_text>Se instancia nuevamente el modelo de Árboles de Decisión, teniendo en cuenta estos hiperparámetros óptimos. Sería posible recurrir a la misma variable ya definida, pero por claridad se ha optado por renombrarla otra vez a través de:<jupyter_code>dtc2 = DecisionTreeClassifier(criterion = 'gini', max_depth = 11, random_state = 21, 
                              splitter = 'random', min_samples_split = 12, min_samples_leaf = 4)<jupyter_output><empty_output><jupyter_text>Ajustamos el nuevo modelo y realizamos las predicciones:<jupyter_code>dtc2.fit(x_train, y_train)
predicciones_dtc2 = dtc2.predict(x_test)
predicciones_dtc2<jupyter_output><empty_output><jupyter_text>Y comprobamos el reporte de clasificación actualizado:<jupyter_code>print(classification_report(y_test, predicciones_dtc2))<jupyter_output>precision    recall  f1-score   support

           0       0.78      0.56      0.65        25
           1       0.74      0.89      0.81        36

    accuracy                           0.75        61
   macro avg       0.76      0.72      0.73        61
weighted avg       0.76      0.75      0.74        61
{'fraction_non_alphanumeric': 0.0884162235429048, 'fraction_numerical': 0.024606401835053696, 'mean_word_length': 4.887865766318805, 'pattern_counts': {'":': 2, '<': 255, '<?xml version=': 0, '>': 256, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '66', 'dir': 'jupyter-structured-clean-dedup', 'id': '574478', 'license': 'MIT', 'n_tokens_mistral': 10493, 'n_tokens_neox': 9938, 'n_words': 2812, 'path': 'Notebooks/Heart_Attack_Prediction - DT_SVM.ipynb', 'repo_name': 'ccastillo-py/Heart_Attack_EDA_and_Prediction', 'size': '28773'}