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 OneHotEncoder
from sklearn.model_selection import train_test_split
from data import data
age = np.array([d['age'] 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)
cat_data = np.array([[d['sex'], d['BP'], d['cholesterol']] for d in data])
encoder = OneHotEncoder(sparse_output=False)
cat_encoded = encoder.fit_transform(cat_data)
cat_feature_names = encoder.get_feature_names_out(['sex', 'BP', 'cholesterol'])
X = np.column_stack([age, cat_encoded, Na, K])
y = np.array([d['drug'] for d in data]).reshape(-1, 1)
feature_names = ['age'] + list(cat_feature_names) + ['Na', 'K']
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_high <= 0.50
| |--- Na <= 0.73
| | |--- cholesterol_high <= 0.50
| | | |--- class: C
| | |--- cholesterol_high > 0.50
| | | |--- class: D
| |--- Na > 0.73
| | |--- class: B
|--- BP_high > 0.50
| |--- Na <= 0.44
| | |--- class: D
| |--- Na > 0.44
| | |--- age <= 47.50
| | | |--- class: A
| | |--- age > 47.50
| | | |--- class: B
Importancia de características:
age: 0.1543
sex_F: 0.0000
sex_M: 0.0000
BP_high: 0.3155
BP_low: 0.0000
BP_normal: 0.0000
cholesterol_high: 0.1543
cholesterol_normal: 0.0000
Na: 0.3759
K: 0.0000
Scores: [0.75 0.75 0.75 0.5 0.75], Mean: 0.7, Std: 0.09999999999999999
Top 3 características (por importancia): ['Na', 'BP_high', 'cholesterol_high']

Á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
BP_high0.3155
cholesterol_high0.1543
age0.1543
sex_F0.0000
sex_M0.0000
BP_low0.0000
BP_normal0.0000
cholesterol_normal0.0000
K0.0000

Las tres características más importantes son: Na, BP_high y cholesterol_high.

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_high <= 0.50
| |--- Na <= 0.73
| | |--- cholesterol_high <= 0.50
| | | |--- class: C
| | |--- cholesterol_high > 0.50
| | | |--- class: D
| |--- Na > 0.73
| | |--- class: B
|--- BP_high > 0.50
| |--- Na <= 0.44
| | |--- class: D
| |--- Na > 0.44
| | |--- Na <= 0.80
| | | |--- class: A
| | |--- Na > 0.80
| | | |--- Na <= 0.85
| | | | |--- class: B
| | | |--- Na > 0.85
| | | | |--- class: A
Importancia de características:
Na: 0.5302
BP_high: 0.3155
cholesterol_high: 0.1543
Scores: [1. 0.75 1. 0.5 0.75], Mean: 0.8, Std: 0.18708286933869706
Comparación de exactitud media:
Todas las características (age, sex_F, sex_M, BP_high, BP_low, BP_normal, cholesterol_high, cholesterol_normal, Na, K): 0.7000
Top 3 (Na, BP_high, cholesterol_high): 0.8000

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

Se reentrena un árbol usando solo Na, BP_high y cholesterol_high. Resultados:

Visualización del árbol (top 3):

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

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

ModeloExactitud media (validación cruzada)
Todas (10 características, codificadas)0.7000
Top 3 (Na, BP_high, cholesterol_high)0.8000

En esta experiencia, el árbol con solo las tres características más importantes (sobre todo identificando explícitamente valores altos en presión y colesterol) obtiene mejor exactitud media en validación cruzada (0.80) que el árbol con las diez características (0.70), lo que sugiere que el modelo completo puede estar sobreajustando a variables menos ruidosas como la edad en vez de enfocarse en las principales; 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 que aprovecha ahora codificaciones directas (high vs no high): el médico separa primero por presión arterial alta (BP_high) y luego usa sodio (Na) y, en algunos casos, colesterol alto e indirectamente la edad. Así se resume el razonamiento:

En conjunto, el médico parece seguir reglas basadas en umbrales de alta presión, alto colesterol y cortes definidos de sodio, mostrando patrones de prescripción dependientes de condiciones particulares.

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

Sí, y es sumamente clara para el sodio (Na). Na es la variable general con mayor peso predictivo en la decisión e integra los principales divisores lógicos del razonamiento (0.37 a 0.53 de importancia). Picos bajos dirigen explícitamente a fármacos D y C, valores moderados hacia A, y picos concentrados altos inducen la prescripción de B. La nueva codificación expone con especial fuerza el rol contundente de las variables categóricas específicas BP_high (0.31) y cholesterol_high (0.15). A la par, el potasio (K) se mantiene consistente en no aportar peso predictivo a los árboles probados (importancia 0 en ambos esquemas); en esta muestra reducida su valor no sirve de discriminante para elegir tratamientos.


Conclusiones

Los árboles de decisión permiten explicar la relación entre variables clínicas y valores en sangre. Tras implementar una codificación One-Hot para aislar variables como “Presión Alta” y “Colesterol Alto”, el modelo se enfoca en que Na, BP_high y cholesterol_high son las más útiles. Curiosamente, la exactitud media sube a 0.80 en validación cruzada con solo esas 3 subvariables frente al 0.70 con todo el conjunto. Esto afirma la importancia de proveerle a los árboles de toma de decisiones características pre-procesadas de una manera lógica; un codificador nominal (“high” no es número inherentemente relativo a “normal” numéricamente hablando para el árbol) fue preferible para modelar mejor frente al anterior acercamiento.