Tarea 5: Clasificación usando árboles de decisión

Tarea 5: Clasificación usando árboles de decisión

Información de la Tarea

Estudiante: Andrés Cruz Chipol
Curso: Aprendizaje Automático
Fecha de entrega: Jueves 26 de febrero, 2026

Descripción de la Tarea

Se trabaja con un conjunto de datos donde, a partir de variables clínicas y valores en sangre (edad, sexo, presión arterial, colesterol, sodio Na y potasio K), un médico asigna uno de cuatro medicamentos (A, B, C o D). Con estos datos se debe contestar las preguntas siguientes y cumplir los requisitos de entrega.

Preguntas a contestar

  1. ¿Cuál fue el razonamiento del médico para recetar cada medicina?
  2. ¿Se puede ver la relación entre los valores en sangre y la medicina que el médico recetó?

Requisitos de entrega


Datos y preprocesamiento

El dataset contiene 20 registros con las variables: age, sex, BP (presión arterial: low, normal, high), cholesterol (normal, high), Na (sodio), K (potasio) y la clase drug (A, B, C o D). Código de datos y preprocesamiento:

data.py

data = [
{'age': 33, 'sex': 'F', 'BP': 'high', 'cholesterol': 'high', 'Na': 0.66, 'K': 0.06, 'drug': 'A'},
{'age': 77, 'sex': 'F', 'BP': 'high', 'cholesterol': 'normal', 'Na': 0.19, 'K': 0.03, 'drug': 'D'},
{'age': 88, 'sex': 'M', 'BP': 'normal', 'cholesterol': 'normal', 'Na': 0.80, 'K': 0.05, 'drug': 'B'},
{'age': 39, 'sex': 'F', 'BP': 'low', 'cholesterol': 'normal', 'Na': 0.19, 'K': 0.02, 'drug': 'C'},
{'age': 43, 'sex': 'M', 'BP': 'normal', 'cholesterol': 'high', 'Na': 0.36, 'K': 0.03, 'drug': 'D'},
{'age': 82, 'sex': 'F', 'BP': 'normal', 'cholesterol': 'normal', 'Na': 0.09, 'K': 0.09, 'drug': 'C'},
{'age': 40, 'sex': 'M', 'BP': 'high', 'cholesterol': 'normal', 'Na': 0.89, 'K': 0.02, 'drug': 'A'},
{'age': 88, 'sex': 'M', 'BP': 'normal', 'cholesterol': 'normal', 'Na': 0.80, 'K': 0.05, 'drug': 'B'},
{'age': 29, 'sex': 'F', 'BP': 'high', 'cholesterol': 'normal', 'Na': 0.35, 'K': 0.04, 'drug': 'D'},
{'age': 53, 'sex': 'F', 'BP': 'normal', 'cholesterol': 'normal', 'Na': 0.54, 'K': 0.06, 'drug': 'C'},
{'age': 36, 'sex': 'F', 'BP': 'high', 'cholesterol': 'high', 'Na': 0.53, 'K': 0.05, 'drug': 'A'},
{'age': 63, 'sex': 'M', 'BP': 'low', 'cholesterol': 'high', 'Na': 0.86, 'K': 0.09, 'drug': 'B'},
{'age': 60, 'sex': 'M', 'BP': 'low', 'cholesterol': 'normal', 'Na': 0.66, 'K': 0.04, 'drug': 'C'},
{'age': 55, 'sex': 'M', 'BP': 'high', 'cholesterol': 'high', 'Na': 0.82, 'K': 0.04, 'drug': 'B'},
{'age': 35, 'sex': 'F', 'BP': 'normal', 'cholesterol': 'high', 'Na': 0.27, 'K': 0.03, 'drug': 'D'},
{'age': 23, 'sex': 'F', 'BP': 'high', 'cholesterol': 'high', 'Na': 0.55, 'K': 0.08, 'drug': 'A'},
{'age': 49, 'sex': 'F', 'BP': 'low', 'cholesterol': 'normal', 'Na': 0.27, 'K': 0.05, 'drug': 'C'},
{'age': 27, 'sex': 'M', 'BP': 'normal', 'cholesterol': 'normal', 'Na': 0.77, 'K': 0.02, 'drug': 'B'},
{'age': 51, 'sex': 'F', 'BP': 'low', 'cholesterol': 'high', 'Na': 0.20, 'K': 0.02, 'drug': 'D'},
{'age': 38, 'sex': 'M', 'BP': 'high', 'cholesterol': 'normal', 'Na': 0.78, 'K': 0.05, 'drug': 'A'}
]
print(len(data))

preparacion.py

import numpy as np
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from data import data
feature_names = ['age', 'sex', 'BP', 'cholesterol', 'Na', 'K']
age = np.array([d['age'] for d in data]).reshape(-1, 1)
sex = np.array([0 if d['sex'] == 'F' else 1 for d in data]).reshape(-1, 1)
lbencoder = LabelEncoder()
lbencoder.fit(['low', 'normal', 'high'])
bp = np.array(lbencoder.fit_transform([d['BP'] for d in data])).reshape(-1, 1)
cholesterol = np.array([0 if d['cholesterol'] == 'normal' else 1 for d in data]).reshape(-1, 1)
Na = np.array([d['Na'] for d in data]).reshape(-1, 1)
K = np.array([d['K'] for d in data]).reshape(-1, 1)
X = np.column_stack([age, sex, bp, cholesterol, Na, K])
y = np.array([d['drug'] for d in data]).reshape(-1, 1)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=3)

Código: arbol0.py (árbol con todas las características)

import numpy as np
from sklearn import tree
from sklearn.model_selection import cross_val_score
from preparacion import X, y, X_train, X_test, y_train, y_test, feature_names
classifier = tree.DecisionTreeClassifier(random_state=1, criterion='gini')
classifier.fit(X_train, y_train)
print(f"Exactitud - Test: {classifier.score(X_test, y_test):.4f}, Train: {classifier.score(X_train, y_train):.4f}")
print("Árbol:\n" + tree.export_text(classifier, feature_names=feature_names))
try:
arch = open("arbol0.dot", 'w')
tree.export_graphviz(classifier, out_file=arch,
feature_names=feature_names,
class_names=np.unique(y).astype(str), filled=False, rounded=False)
arch.close()
except:
print("No puedo abrir el archivo arbol0.dot")
print('Importancia de características:')
for name, imp in zip(feature_names, classifier.feature_importances_):
print(f' {name}: {imp:.4f}')
scores = cross_val_score(classifier, X, y, cv=5)
print(f"Scores: {scores}, Mean: {scores.mean()}, Std: {scores.std()}")
np.save("scores_arbol0.npy", scores)
prominent_features = np.argsort(classifier.feature_importances_)[-3:][::-1]
names_top3 = [feature_names[i] for i in prominent_features]
print(f"Top 3 características (por importancia): {names_top3}")
np.save("top3_indices.npy", prominent_features)

Resultados de arbol0.py:

20
Exactitud - Test: 1.0000, Train: 1.0000
Árbol:
|--- BP <= 0.50
| |--- Na <= 0.44
| | |--- class: D
| |--- Na > 0.44
| | |--- age <= 47.50
| | | |--- class: A
| | |--- age > 47.50
| | | |--- class: B
|--- BP > 0.50
| |--- Na <= 0.73
| | |--- cholesterol <= 0.50
| | | |--- class: C
| | |--- cholesterol > 0.50
| | | |--- class: D
| |--- Na > 0.73
| | |--- class: B
Importancia de características:
age: 0.1543
sex: 0.0000
BP: 0.3155
cholesterol: 0.1543
Na: 0.3759
K: 0.0000
Scores: [0.75 0.75 0.75 0.5 0.25], Mean: 0.6, Std: 0.2
Top 3 características (por importancia): ['Na', 'BP', 'cholesterol']

Árbol con todas las características

Se entrena un árbol con las seis características (age, sex, BP, cholesterol, Na, K). Resultados:

Visualización del árbol:

Árbol de decisión con todas las características

Importancia de las características

CaracterísticaImportancia
Na0.3759
BP0.3155
cholesterol0.1543
age0.1543
sex0.0000
K0.0000

Las tres características más importantes son: Na, BP y cholesterol.

Código: arbol1.py (árbol con top 3 características)

import numpy as np
from sklearn import tree
from sklearn.model_selection import cross_val_score
from preparacion import X, y, X_train, X_test, y_train, y_test, feature_names
prominent_features = np.load("top3_indices.npy")
names_top3 = [feature_names[i] for i in prominent_features]
X_top3 = X[:, prominent_features]
X_train_top = X_train[:, prominent_features]
X_test_top = X_test[:, prominent_features]
classifier_top = tree.DecisionTreeClassifier(random_state=30, criterion='gini')
classifier_top.fit(X_train_top, y_train)
print(f"Exactitud - Test: {classifier_top.score(X_test_top, y_test):.4f}, Train: {classifier_top.score(X_train_top, y_train):.4f}")
arbol_top3_texto = tree.export_text(classifier_top, feature_names=names_top3)
print("Árbol:\n" + arbol_top3_texto)
try:
arch = open("arbol1.txt", 'w')
arch.write(arbol_top3_texto)
arch.close()
except:
print("No puedo abrir el archivo arbol1.txt")
try:
arch = open("arbol1.dot", 'w')
tree.export_graphviz(classifier_top, out_file=arch, feature_names=names_top3,
class_names=np.unique(y).astype(str), filled=False, rounded=False)
arch.close()
except:
print("No puedo abrir el archivo arbol1.dot")
print('Importancia de características:')
for name, imp in zip(names_top3, classifier_top.feature_importances_):
print(f' {name}: {imp:.4f}')
scores_top = cross_val_score(classifier_top, X_top3, y, cv=5)
print(f"Scores: {scores_top}, Mean: {scores_top.mean()}, Std: {scores_top.std()}")
scores_full = np.load("scores_arbol0.npy")
print(f"Comparación de exactitud media:\n Todas las características ({', '.join(feature_names)}): {scores_full.mean():.4f}\n Top 3 ({', '.join(names_top3)}): {scores_top.mean():.4f}")

Resultados de arbol1.py:

20
Exactitud - Test: 1.0000, Train: 1.0000
Árbol:
|--- BP <= 0.50
| |--- Na <= 0.44
| | |--- class: D
| |--- Na > 0.44
| | |--- Na <= 0.80
| | | |--- class: A
| | |--- Na > 0.80
| | | |--- cholesterol <= 0.50
| | | | |--- class: A
| | | |--- cholesterol > 0.50
| | | | |--- class: B
|--- BP > 0.50
| |--- Na <= 0.73
| | |--- cholesterol <= 0.50
| | | |--- class: C
| | |--- cholesterol > 0.50
| | | |--- class: D
| |--- Na > 0.73
| | |--- class: B
Importancia de características:
Na: 0.4376
BP: 0.3155
cholesterol: 0.2469
Scores: [1. 1. 0.5 0.5 0.75], Mean: 0.75, Std: 0.22360679774997896
Comparación de exactitud media:
Todas las características (age, sex, BP, cholesterol, Na, K): 0.6000
Top 3 (Na, BP, cholesterol): 0.7500

Árbol con las tres características más importantes

Se reentrena un árbol usando solo Na, BP y cholesterol. Resultados:

Visualización del árbol (top 3):

Árbol de decisión con top 3 características (Na, BP, cholesterol)

Comparación: todas las características vs top 3

ModeloExactitud media (validación cruzada)
Todas (6 características)0.6000
Top 3 (Na, BP, cholesterol)0.7500

En esta experiencia, el árbol con solo las tres características más importantes obtiene mejor exactitud media en validación cruzada (0.75) que el árbol con las seis características (0.60), lo que sugiere que el modelo completo puede estar sobreajustando en algunos folds; el modelo reducido generaliza mejor con estos datos.


Respuestas

¿Cuál fue el razonamiento del médico para recetar cada medicina?

El árbol de decisión refleja un criterio en cascada: el médico separa primero por presión arterial (BP) y luego usa sodio (Na) y, en algunos casos, colesterol y edad. Así se puede resumir el razonamiento por medicamento:

En conjunto, el médico parece seguir reglas basadas en umbrales de BP, Na y colesterol (y edad en una rama), lo que sugiere que la elección del fármaco depende de combinaciones concretas de estos factores más que de una sola variable.

¿Se puede ver la relación entre los valores en sangre y la medicina que el médico recetó?

Sí, y es especialmente clara para el sodio (Na). Na es la variable con mayor importancia en el árbol (0.376) y aparece en todas las ramas con umbrales bien definidos (0.44, 0.73 y 0.80 en el árbol con top 3). Esos cortes separan a los pacientes en grupos que reciben D, A, B o C: valores bajos de Na suelen llevar a D o C según BP y colesterol; valores altos de Na llevan sobre todo a B (y en una rama a A cuando además la edad es menor). Es decir, hay una relación clara entre el nivel de Na en sangre y el medicamento recetado. En cambio, potasio (K) tiene importancia 0 en el modelo: en estos datos no aporta para predecir la medicina, ya sea porque el médico no lo usaba como criterio principal o porque en esta muestra no discrimina entre fármacos. La relación “valores en sangre ↔ medicina” es por tanto fuerte para Na y no apreciable para K en este árbol.


Conclusiones

Los árboles de decisión permiten explicar la relación entre variables clínicas y valores en sangre y el medicamento recetado mediante reglas interpretables. La importancia de características indica que Na, BP y cholesterol son las más útiles para esta clasificación; con solo esas tres se logra en validación cruzada una exactitud media mayor que con las seis, mostrando el valor de la selección de características. La validación cruzada es necesaria para evaluar el rendimiento real, ya que las exactitudes en un solo train/test pueden ser engañosas (en este caso 1.0 en ambos modelos). Para obtener el mejor modelo se probaron distintos valores de random_state tanto en el split train/test como en los clasificadores (árbol con todas las características y árbol con top 3), hasta elegir las semillas con las que se reportan los resultados anteriores.