Estatus de la Salud Mental en adultos del municipio más acaudalado de América Látina.¶
Reporte técnico del análisis de la salud mental en adultos dentro de la población en edad de trabajar del municipio de San Pedro Garza García.
Hecho por: Estefania Nájera de la Rosa - estefania.najera@udem.edu a 26 de marzo del 2026.
# Importar las librerías.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
import statsmodels.formula.api as smf
import statsmodels.api as sm
from sklearn.preprocessing import LabelEncoder
from statsmodels.stats.outliers_influence import variance_inflation_factor
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import ConfusionMatrixDisplay, classification_report
from sklearn.linear_model import LogisticRegression
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.tree import DecisionTreeClassifier as DTC
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, f1_score
from sklearn.model_selection import cross_val_score
1.1 Introducción.¶
Para este presente análisis, se seguirá utilizando la base de datos que ha sido explorada desde el proyecto del primer parcial y que dentro de este segundo parcial se estudió ahora con diversos enfoques clasificatorios. Esto bajo las condiciones establecidas donde los datos a utilizar sean aquellos de adultos en la edad económicamente activa (18-65 años) que residen en el municipio de San Pedro Garza García.
Dichos enfoques clasificatorios han sido trabajados anteriormente y se mostrará tanto su construcción como sus resultados para delimitar cuál algoritmo de clasificación es más compatible con el escenario a descubrir.
Vale la pena volver a mencionar que los datos con los que se estará trabajando, provienen del portal de datos abiertos del Gobierno del Estado de Nuevo León, estos recaen bajo la categoría de Salud - que nos da a entender que para el estado, la salud mental es un factor importante a considerar de la población de una región que constantemente se encuentra en crecimiento y mejora.
1.2 Objetivo.¶
El objetivo de seguir trabajando con la misma base de datos pero ahora bajo el término de clasificación, es para conocer en qué umbral recaen los adultos económicamente activos que residen en el municipio de San Pedro Garza García:
Trastornos mentales y del comportamiento.
Factores que influyen en el estado de salud y contacto con los servicios de salud.
De esta manera, respondiendo a la pregunta de: Cómo se encuentra el nivel de salud mental en los adultos que sostienen el desarrollo del municipio de San Pedro Garza García?
Esto debido a que aquellos que recaigan en el grupo de trastornos mentales, son individuos que se encuentran en condiciones graves de salud mental y que por ende, necesitan de apoyo y trato profesional más avanzado; a diferencia de aquellos que presentan factores que influyen en su estado de salud, los cuáles no se hacen de lado, sin embargo, no necesitan de un seguimiento tan estricto a comparación del primer grupo. Dicha delimitación da feedback de la calidad de vida de los Sanpetrinos.
Mencionando de nuevo el aspecto de la clasificación, es que se trabajarán con los siguientes modelos:
Regresión Logística.
Linear Discriminant Analysis (LDA).
Árbol de Decisión.
Métodos de ensamble (Random Forest Classifier).
Support Vector Machine (SVM).
Red Neuronal (Construida desde cero).
Cada uno de estos se explicará brevemente para justificar su uso y explicar sus resultados para al final delimitar el mejor.
2.1 Planteamiento.¶
Recordando la información del proyecto de primer parcial, según la OPS/OMS (2026), la salud mental se define como un estado del bienestar que permite al individuo afrontar momentos díficiles, desarrollar sus habilidades, aprender, enseñar y trabajar de forma adecuada para contribuir a la mejora de su comunidad.
Este bienestar es fundamental, ya que sustenta las capacidades individuales como colectivas que permiten dar forma al mundo.
En el estado de Nuevo León, actualmente se cuenta con un hospital de especialidades en salud mental, lo que evidencia que las autoridades reconocen la importancia de este bienestar en su población.
Por otro lado, el municipio de San Pedro Garza García se encuentra en México, exactamente en el estado de Nuevo León y es parte del área metropolitana de Monterrey, este es conocido como el mejor de América Latina por diversas razones. Según Pardo (2025), su PIB per cápita es de $107,000 dólares anuales, cinco veces el promedio nacional; además, el 70% de los adultos poseé de un título universitario, el tripe que el promedio del resto de México.
Es justo aquí donde unimos A con B, es decir, tenemos el aspecto de la salud mental justificado y planteado como el problema principal, directamente relacionado con el municipio de San Pedro Garza García, donde esa riqueza y altos niveles educativos generan expectativas y responsabilidades en los individuos que podría influir en su bienestar psicológico, dado que estos sostienen la imagen y el progreso del municipio mediante su esfuerzo constante.
Por ello, este análisis se centrará en los adultos de 18 a 65 años (la población económicamente activa) del municipio, quienes son los pilares que impulsan, organizan y mejoran la economía, la plusvalía y la calidad de vida deSan Pedro Garza García. La investigación parte de la premisa de que a pesar de vivir en una "burbuja de privilegios", estas personas no están exentas de presentar problemas, inquietudes o incertidumbres que afecten su salud mental, sea por razones académicas, laborales o personales.
2.2 Contexto de los datos.¶
Conocemos la procedencia de los datos, ahora se cargarán:
# Cargar los datos.
url = 'https://raw.githubusercontent.com/estefaniadelarosa/IA-I/refs/heads/main/P1.%20Regresi%C3%B3n/P1.%20Regresi%C3%B3n/2024_2025_salud_mental.csv'
df = pd.read_csv(url)
print(df.shape)
df.head()
(48224, 14)
| fecha | id_consulta | edad | edad_meses | edad_dias | sexo | peso | altura | municipio_unidad_medica | institucion_unidad_medica | clave_grupo_ enfermedad | descripcion_grupo_enfermedad | clave_enfermedad | descripcion_enfermedad | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 02/10/2024 | SM_2024_38869 | 21 | 0 | 0 | Masculino | 82 | 174 | LINARES | HOSPITAL GENERAL DE LINARES | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F412 | TRASTORNO MIXTO DE ANSIEDAD Y DEPRESION |
| 1 | 08/10/2024 | SM_2024_38870 | 21 | 0 | 0 | Masculino | 82 | 174 | LINARES | HOSPITAL GENERAL DE LINARES | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F412 | TRASTORNO MIXTO DE ANSIEDAD Y DEPRESION |
| 2 | 08/10/2024 | SM_2024_38871 | 5 | 0 | 0 | Masculino | 21 | 111 | LINARES | HOSPITAL GENERAL DE LINARES | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F919 | TRASTORNO DE LA CONDUCTA NO ESPECIFICADO |
| 3 | 09/10/2024 | SM_2024_38872 | 69 | 0 | 0 | Masculino | sin valor | sin valor | LINARES | HOSPITAL GENERAL DE LINARES | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F321 | EPISODIO DEPRESIVO MODERADO |
| 4 | 09/10/2024 | SM_2024_38873 | 78 | 0 | 0 | Masculino | sin valor | sin valor | LINARES | HOSPITAL GENERAL DE LINARES | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F321 | EPISODIO DEPRESIVO MODERADO |
df['municipio_unidad_medica'].unique()
array(['LINARES', 'MONTEMORELOS', 'MONTERREY', 'SABINAS HIDALGO',
'SAN NICOLAS DE LOS GARZA', 'SANTA CATARINA', 'GENERAL ESCOBEDO',
'PESQUERIA', 'CADEREYTA JIMENEZ', 'SAN PEDRO GARZA GARCIA',
'GARCIA', 'GALEANA', 'CERRALVO', 'CHINA'], dtype=object)
Los datos originales provienen de personas que residen en distintos municipios del estado de Nuevo León, por mencionar algunos que pertenecen al área metropólitana como:
Monterrey.
San Nicolás de los Garza.
Santa Catarina.
General Escobedo.
entre otros.
Sin dejar de lado que también hay datos de municipios aledaños a la metrópoli como lo son:
Montemorelos.
Linares.
Cadereyta Jiménez
entre otros.
Por cada municipio, es que se tiene registrado por individuo lo siguiente:
Fecha de la consulta.
Número de identificación de la consulta.
Básicos de la persona como edad, sexo, peso y altura.
Municipio.
Institución médica dentro del municipio.
Número de identificación y descripción de su grupo de enfermedad.
Número de identificación y descripción de la enfermedad como tal.
3.1 Exploración y comprensión del conjunto de datos.¶
Como se tiene el interés de trabajar con los datos que provengan de San Pedro Garza García, el dataset se filtrará para que mediante el municipio, recopilemos aquellos específicos que se necesitan.
df1 = df[df['municipio_unidad_medica'] == 'SAN PEDRO GARZA GARCIA']
print(df1.shape)
df1.head()
(1060, 14)
| fecha | id_consulta | edad | edad_meses | edad_dias | sexo | peso | altura | municipio_unidad_medica | institucion_unidad_medica | clave_grupo_ enfermedad | descripcion_grupo_enfermedad | clave_enfermedad | descripcion_enfermedad | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2625 | 02/10/2024 | SM_2024_41494 | 33 | 0 | 0 | Masculino | 60 | 160 | SAN PEDRO GARZA GARCIA | CENTRO COMUNITARIO DE SALUD MENTAL Y ADICCIONE... | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F122 | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO DEBID... |
| 2626 | 02/10/2024 | SM_2024_41495 | 53 | 0 | 0 | Femenino | 60 | 160 | SAN PEDRO GARZA GARCIA | CENTRO COMUNITARIO DE SALUD MENTAL Y ADICCIONE... | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F412 | TRASTORNO MIXTO DE ANSIEDAD Y DEPRESION |
| 2627 | 02/10/2024 | SM_2024_41496 | 60 | 0 | 0 | Femenino | 60 | 160 | SAN PEDRO GARZA GARCIA | CENTRO COMUNITARIO DE SALUD MENTAL Y ADICCIONE... | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F630 | JUEGO PATOLOGICO |
| 2628 | 04/10/2024 | SM_2024_41497 | 46 | 0 | 0 | Femenino | 70 | 165 | SAN PEDRO GARZA GARCIA | CENTRO COMUNITARIO DE SALUD MENTAL Y ADICCIONE... | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F412 | TRASTORNO MIXTO DE ANSIEDAD Y DEPRESION |
| 2629 | 04/10/2024 | SM_2024_41498 | 47 | 0 | 0 | Femenino | 60 | 160 | SAN PEDRO GARZA GARCIA | CENTRO COMUNITARIO DE SALUD MENTAL Y ADICCIONE... | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F412 | TRASTORNO MIXTO DE ANSIEDAD Y DEPRESION |
Seguido de aplicar otro filtro que ahora nos recopile todos los datos donde la edad se encuentre dentro del rango de los 18 a los 65 años como se había establecido anteriormente.
# Aplicar el filtro de personas dentro del rango de edad de 18 a 65 años.
df2 = df1[(df1['edad'] >= 18) & (df1['edad'] <= 65)]
print(df2.shape)
df2.head()
(720, 14)
| fecha | id_consulta | edad | edad_meses | edad_dias | sexo | peso | altura | municipio_unidad_medica | institucion_unidad_medica | clave_grupo_ enfermedad | descripcion_grupo_enfermedad | clave_enfermedad | descripcion_enfermedad | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2625 | 02/10/2024 | SM_2024_41494 | 33 | 0 | 0 | Masculino | 60 | 160 | SAN PEDRO GARZA GARCIA | CENTRO COMUNITARIO DE SALUD MENTAL Y ADICCIONE... | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F122 | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO DEBID... |
| 2626 | 02/10/2024 | SM_2024_41495 | 53 | 0 | 0 | Femenino | 60 | 160 | SAN PEDRO GARZA GARCIA | CENTRO COMUNITARIO DE SALUD MENTAL Y ADICCIONE... | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F412 | TRASTORNO MIXTO DE ANSIEDAD Y DEPRESION |
| 2627 | 02/10/2024 | SM_2024_41496 | 60 | 0 | 0 | Femenino | 60 | 160 | SAN PEDRO GARZA GARCIA | CENTRO COMUNITARIO DE SALUD MENTAL Y ADICCIONE... | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F630 | JUEGO PATOLOGICO |
| 2628 | 04/10/2024 | SM_2024_41497 | 46 | 0 | 0 | Femenino | 70 | 165 | SAN PEDRO GARZA GARCIA | CENTRO COMUNITARIO DE SALUD MENTAL Y ADICCIONE... | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F412 | TRASTORNO MIXTO DE ANSIEDAD Y DEPRESION |
| 2629 | 04/10/2024 | SM_2024_41498 | 47 | 0 | 0 | Femenino | 60 | 160 | SAN PEDRO GARZA GARCIA | CENTRO COMUNITARIO DE SALUD MENTAL Y ADICCIONE... | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F412 | TRASTORNO MIXTO DE ANSIEDAD Y DEPRESION |
Aspectos como los outliers y los huecos ya se trataron en el proyecto del primer parcial, sin embargo, se vuelve a recalcar en este punto que no existen datos vacíos.
# Verificar si hay datos vacíos.
df2.isna().sum().sort_values(ascending = False)
fecha 0 id_consulta 0 edad 0 edad_meses 0 edad_dias 0 sexo 0 peso 0 altura 0 municipio_unidad_medica 0 institucion_unidad_medica 0 clave_grupo_ enfermedad 0 descripcion_grupo_enfermedad 0 clave_enfermedad 0 descripcion_enfermedad 0 dtype: int64
Dentro del objetivo se definió que se va a delimitar bajo este tipo de regresión la clasificación de un individuo bajo dos clases, siendo estas pertenecientes a la descripción del grupo de la enfermedad. De estas dos se mencionó su nombre y el propósito de clasificar a las personas bajo alguno de estos bajo las predicciones que se harán más adelante para ver la calidad del modelo según su predictor.
Una vez volviendo a clarificar lo anterior, cuando indagamos en el dataset a aspectos más técnicos de cuántos datos existen ya bajo alguna de las clases, podemos darnos cuenta que entre ambos existe un desbalance, mejor conocido como desbalance de clases dentro del mundo de Machine Learning, donde esto consiste en que las clases cuentan con una diferencia significativa de registros bajo los mismos, por ejemplo, en este caso obtenemos los siguientes números bajo cada clase:
Trastornos mentales y del comportamiento con 547 registros.
Factores que influyen en el estado de salud y contacto con los servicios de salud con 173 registros.
Por ende, obtenemos que hay más registros de personas bajo la descripción de trastornos mentales y del comportamiento - visto por porcentaje, de un 100%, estos encabezan el 75% de los datos, sin embargo, este desbalance no es un limitante para desarrollar el modelo ya que podemos aplicar técnicas para balancear esta diferencia, teniendo el desbalance original para hacer una comparación de desempeños entre los modelos.
df2['descripcion_grupo_enfermedad'].value_counts()
descripcion_grupo_enfermedad TRASTORNOS MENTALES Y DEL COMPORTAMIENTO 547 FACTORES QUE INFLUYEN EN EL ESTADO DE SALUD Y CONTACTO CON LOS SERVICIOS DE SALUD 173 Name: count, dtype: int64
# Para el porcentaje.
df2['descripcion_grupo_enfermedad'].value_counts()/(df2['descripcion_grupo_enfermedad'].value_counts().sum())*100
descripcion_grupo_enfermedad TRASTORNOS MENTALES Y DEL COMPORTAMIENTO 75.972222 FACTORES QUE INFLUYEN EN EL ESTADO DE SALUD Y CONTACTO CON LOS SERVICIOS DE SALUD 24.027778 Name: count, dtype: float64
# Gráfica del balance de clases.
df2['descripcion_grupo_enfermedad'].str[:43].value_counts().plot(kind = 'bar')
plt.show()
Transformaremos las variables categóricas a numéricas para poderlas manipular.
df2.dtypes
fecha object id_consulta object edad int64 edad_meses int64 edad_dias int64 sexo object peso object altura object municipio_unidad_medica object institucion_unidad_medica object clave_grupo_ enfermedad object descripcion_grupo_enfermedad object clave_enfermedad object descripcion_enfermedad object dtype: object
Los datos y su tipo que disponemos son los siguientes:
id_consulta → object
edad → int64
sexo → object
peso → object
altura → object
municipio_unidad_medica → object
institucion_unidad_medica → object
clave_grupo_ enfermedad → object
descripcion_grupo_enfermedad → object
clave_enfermedad → object
descripcion_enfermedad → object
Por ende, las que se transformarán mediante LabelEncoder son las siguientes:
sexo
institucion_unidad_medica
descripcion_grupo_enfermedad_num
clave_enfermedad_num
# Convertimos las variables categóricas a categóricas numéricas.
from sklearn.preprocessing import LabelEncoder
# Aplicamos la transformación.
df2['sexo_num'] = LabelEncoder().fit_transform(df2['sexo'])
df2['institucion_unidad_medica_num'] = LabelEncoder().fit_transform(df2['institucion_unidad_medica'])
df2['descripcion_grupo_enfermedad_num'] = LabelEncoder().fit_transform(df2['descripcion_grupo_enfermedad'])
df2['clave_enfermedad_num'] = LabelEncoder().fit_transform(df2['clave_enfermedad'])
df2['descripcion_enfermedad_num'] = LabelEncoder().fit_transform(df2['descripcion_enfermedad'])
df2.sample(5)
/var/folders/rw/krrrqrzn68j3jq_d4yl8mzk80000gn/T/ipykernel_57416/763801538.py:5: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df2['sexo_num'] = LabelEncoder().fit_transform(df2['sexo']) /var/folders/rw/krrrqrzn68j3jq_d4yl8mzk80000gn/T/ipykernel_57416/763801538.py:6: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df2['institucion_unidad_medica_num'] = LabelEncoder().fit_transform(df2['institucion_unidad_medica']) /var/folders/rw/krrrqrzn68j3jq_d4yl8mzk80000gn/T/ipykernel_57416/763801538.py:7: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df2['descripcion_grupo_enfermedad_num'] = LabelEncoder().fit_transform(df2['descripcion_grupo_enfermedad']) /var/folders/rw/krrrqrzn68j3jq_d4yl8mzk80000gn/T/ipykernel_57416/763801538.py:8: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df2['clave_enfermedad_num'] = LabelEncoder().fit_transform(df2['clave_enfermedad']) /var/folders/rw/krrrqrzn68j3jq_d4yl8mzk80000gn/T/ipykernel_57416/763801538.py:9: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df2['descripcion_enfermedad_num'] = LabelEncoder().fit_transform(df2['descripcion_enfermedad'])
| fecha | id_consulta | edad | edad_meses | edad_dias | sexo | peso | altura | municipio_unidad_medica | institucion_unidad_medica | clave_grupo_ enfermedad | descripcion_grupo_enfermedad | clave_enfermedad | descripcion_enfermedad | sexo_num | institucion_unidad_medica_num | descripcion_grupo_enfermedad_num | clave_enfermedad_num | descripcion_enfermedad_num | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 11244 | 11/12/2024 | SM_2024_50113 | 40 | 0 | 0 | Femenino | 86.5 | 156 | SAN PEDRO GARZA GARCIA | CENTRO DE SALUD CON SERVICIOS AMPLIADOS SAN PE... | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F411 | TRASTORNO DE ANSIEDAD GENERALIZADA | 0 | 1 | 1 | 31 | 29 |
| 32284 | 16/05/2025 | SM_2025_19420 | 33 | 0 | 0 | Femenino | 57 | 153 | SAN PEDRO GARZA GARCIA | CENTRO DE SALUD CON SERVICIOS AMPLIADOS SAN PE... | XXI | FACTORES QUE INFLUYEN EN EL ESTADO DE SALUD Y ... | Z630 | PROBLEMAS EN LA RELACION ENTRE ESPOSOS O PAREJA | 0 | 1 | 0 | 46 | 16 |
| 37258 | 17/06/2025 | SM_2025_24394 | 42 | 0 | 0 | Femenino | 46.5 | 146 | SAN PEDRO GARZA GARCIA | CENTRO DE SALUD CON SERVICIOS AMPLIADOS SAN PE... | XXI | FACTORES QUE INFLUYEN EN EL ESTADO DE SALUD Y ... | Z630 | PROBLEMAS EN LA RELACION ENTRE ESPOSOS O PAREJA | 0 | 1 | 0 | 46 | 16 |
| 41673 | 22/07/2025 | SM_2025_28809 | 43 | 0 | 0 | Masculino | 75 | 175 | SAN PEDRO GARZA GARCIA | CENTRO COMUNITARIO DE SALUD MENTAL Y ADICCIONE... | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F100 | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO DEBID... | 1 | 0 | 1 | 0 | 40 |
| 36851 | 11/06/2025 | SM_2025_23987 | 24 | 0 | 0 | Masculino | 83 | 175 | SAN PEDRO GARZA GARCIA | CENTRO COMUNITARIO DE SALUD MENTAL Y ADICCIONE... | V | TRASTORNOS MENTALES Y DEL COMPORTAMIENTO | F412 | TRASTORNO MIXTO DE ANSIEDAD Y DEPRESION | 1 | 0 | 1 | 32 | 38 |
Se eliminarán del dataset las variables siguientes:
edad_meses
edad_dias
Ya que estas solo contienen 0 como registro, no nos sirve para nada y solo genera ruido.
df2 = df2.drop(columns=['edad_meses', 'edad_dias'])
De igual forma, al tener variables de peso y altura respectivas, se puede unir ambas como una sola variable nueva, siendo esta la de IMC donde se crea dicha variable bajo la operación para encontrar este dato.
# Convertir peso y altura a enteros.
df2['peso'] = df2['peso'].astype(float)
df2['altura'] = df2['altura'].astype(float)
# Verificar.
print(df2.dtypes[['peso','altura']])
peso float64 altura float64 dtype: object
# Convertir altura a metros.
df2['altura_m'] = df2['altura'] / 100
# IMC.
df2['IMC'] = df2['peso'] / (df2['altura_m']**2)
4.1 Selección de características.¶
Para delimitar cuáles son las variables de entrada para el modelo, se usarán varios métodos:
Mapa de calor.
ANOVA.
VIF.
plt.figure(figsize = (10, 6))
sns.heatmap(df2.corr(numeric_only = True), annot = True)
plt.show()
# Definimos el modelo de Feature Selection - ANOVA.
modelFS = smf.ols(formula='descripcion_grupo_enfermedad_num ~ edad + sexo_num + IMC + institucion_unidad_medica_num + clave_enfermedad_num + descripcion_enfermedad_num', data = df2).fit()
modelFS.summary()
| Dep. Variable: | descripcion_grupo_enfermedad_num | R-squared: | 0.557 |
|---|---|---|---|
| Model: | OLS | Adj. R-squared: | 0.553 |
| Method: | Least Squares | F-statistic: | 149.4 |
| Date: | Tue, 24 Mar 2026 | Prob (F-statistic): | 1.74e-122 |
| Time: | 23:52:19 | Log-Likelihood: | -116.25 |
| No. Observations: | 720 | AIC: | 246.5 |
| Df Residuals: | 713 | BIC: | 278.6 |
| Df Model: | 6 | ||
| Covariance Type: | nonrobust |
| coef | std err | t | P>|t| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| Intercept | 1.2893 | 0.078 | 16.470 | 0.000 | 1.136 | 1.443 |
| edad | -0.0005 | 0.001 | -0.575 | 0.565 | -0.002 | 0.001 |
| sexo_num | -0.1472 | 0.026 | -5.559 | 0.000 | -0.199 | -0.095 |
| IMC | -0.0018 | 0.002 | -0.876 | 0.382 | -0.006 | 0.002 |
| institucion_unidad_medica_num | -0.0496 | 0.026 | -1.882 | 0.060 | -0.101 | 0.002 |
| clave_enfermedad_num | -0.0189 | 0.001 | -17.611 | 0.000 | -0.021 | -0.017 |
| descripcion_enfermedad_num | 0.0056 | 0.001 | 5.564 | 0.000 | 0.004 | 0.008 |
| Omnibus: | 758.401 | Durbin-Watson: | 1.802 |
|---|---|---|---|
| Prob(Omnibus): | 0.000 | Jarque-Bera (JB): | 46.874 |
| Skew: | 0.010 | Prob(JB): | 6.63e-11 |
| Kurtosis: | 1.750 | Cond. No. | 473. |
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
# Imprimimos los P value para ver cuál es más importante.
pvalue_percent = (modelFS.pvalues * 100).sort_values(ascending = True)
pvalue_percent
clave_enfermedad_num 6.584618e-56 Intercept 6.892180e-50 descripcion_enfermedad_num 3.738917e-06 sexo_num 3.829040e-06 institucion_unidad_medica_num 6.021948e+00 IMC 3.815116e+01 edad 5.652051e+01 dtype: float64
X = df2[['edad', 'sexo_num', 'IMC', 'institucion_unidad_medica_num', 'clave_enfermedad_num', 'descripcion_enfermedad_num']]
vif_data = pd.DataFrame()
vif_data["feature"] = X.columns
vif_data["VIF"] = [variance_inflation_factor(X.values, i)
for i in range(len(X.columns))]
print(vif_data)
feature VIF 0 edad 9.406207 1 sexo_num 2.008371 2 IMC 20.720012 3 institucion_unidad_medica_num 2.930642 4 clave_enfermedad_num 8.236488 5 descripcion_enfermedad_num 6.445571
Según GeeksforGeeks (2025), para mejorar la fiabilidad del modelo bajo el criterio de la colinealidad, se utiliza este método de inflación de varianza, donde este muestra como la varianza aumenta debido a los valores tan parecidos entre las variables independientes.
Interpretación del FIV:
FIV ≈ 1: Sin correlación con otros predictores.
1 < FIV ≤ 5: Correlación leve a moderada (generalmente fina).
FIV > 10: Fuerte multicolinealidad -> tomar medidas correctivas.
X = df2[['edad', 'sexo_num', 'institucion_unidad_medica_num', 'descripcion_enfermedad_num']]
vif_data = pd.DataFrame()
vif_data["feature"] = X.columns
vif_data["VIF"] = [variance_inflation_factor(X.values, i)
for i in range(len(X.columns))]
print(vif_data)
feature VIF 0 edad 4.187639 1 sexo_num 1.918244 2 institucion_unidad_medica_num 2.000134 3 descripcion_enfermedad_num 4.060178
Según la interpretación del VIF, se eliminarán las variables siguientes:
IMC por su VIF de 20.
clave_enfermedad_num que es redundante con descripcion_enfermedad_num
Una vez haciendo la eliminación de estas variables, los VIF mejorán mucho más, estando todos de respectivas variables dentro del umbral de una correlación fina para la elaboración del modelo.
5.1 Definición de variables.¶
Variable dependiente.
Esta variable se tomará como descripcion_grupo_enfermedad_num, ya que desde un principio se delimitó que queremos evaluar dentro de que umbral se encontrará un individuo, siendo dentro de uno que necesite seguimiento profesional con un especialista o chequeos de vez en cuando sin la necesidad de dicho seguimiento tan riguroso.
Variables independientes.
Para las entradas del modelo, se usarán las siguientes después de evaluar la colinealidad de las variables:
edad
sexo_num
institucion_unidad_medica_num
Vale la pena mencionar que descripcion_enfermedad_num fue excluida intencionalmente. Esta variable es un subconjunto directo de la variable dependiente descripcion_grupo_enfermedad_num, lo que genera un caso de Data Leakage — específicamente de contaminación de datos externos — donde la fusión de datos externos con datos de entrenamiento puede generar predicciones sesgadas o inexactas ya que estos datos contienen información directa sobre la variable objetivo. Incluirla haría que el modelo aprenda la respuesta en lugar de las relaciones reales entre predictores y variable dependiente.
# Definimos entrada y salida.
X = df2[['edad', 'sexo_num', 'institucion_unidad_medica_num']]
y = df2['descripcion_grupo_enfermedad_num']
print(X.shape)
print(y.shape)
(720, 3) (720,)
6.1 Construcción.¶
Antes de construir para este proyecto, anteriormente cuando se estuvo realizando la Regresión Logística, se tuvo como resultado que el mejor modelo balanceado es aquel bajo la técnica de SMOTE, por lo que se evaluará tanto el modelo desbalanceado como el de SMOTE, una vez habiendo clarificado esto para que todas las construcciones sean consistentes en cuánto que modelos vamos a estar probando.
Regresión Logística.¶
Logistic by Default.
Separamos los datos de entrenamiento y prueba:
# Dividir los datos en train y test.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0, stratify = y)
X_train.shape, X_test.shape, y_train.shape, y_test.shape
# No dividir de manera que se desbalanceen los datos -> cuidado con el random_state).
((576, 3), (144, 3), (576,), (144,))
Construimos el modelo:
# Definimos el modelo.
model1 = LogisticRegression(random_state = 0)
# Entrenamos el modelo.
model1.fit(X_train, y_train)
LogisticRegression(random_state=0)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
Para seguir con la validación cruzada, es importante saber que según 3.1. Cross-validation: Evaluating Estimator Performance, (s. f.), dicha validación se hace para obtener una estimación del desempeño del modelo después de aplicar la separación de los datos en entrenamiento y prueba.
Como parámetro de la validación, se toma k = 5 ya que además de ser un valor común, esto indica que el conjunto de datos se dividen en 5 subconjuntos - uno de ellos es para validar, mientras que el resto es para entrenar.
scores = cross_val_score(model1, X, y, cv = 5)
print(scores.mean())
0.775
OR = np.exp(model1.coef_[0])
coef_df = pd.DataFrame({
"Variables": X.columns,
"Coeficientes": model1.coef_[0],
"Odds Ratio": OR
})
print(coef_df)
Variables Coeficientes Odds Ratio 0 edad -0.025575 0.974749 1 sexo_num 0.536856 1.710620 2 institucion_unidad_medica_num -1.831048 0.160246
Según Egbuchulem (2025), los significados del valor de la razón de probabilidades de Odd Ratios son los siguientes:
OR de 1: No habría asociación entre la exposición y el resultado en la clase 1.
OR > 1: Sugiere que las probabilidades de exposición/intervención están asociadas positivamente con el resultado adverso en la clase 1.
OR < 1: Sugiere que la probabilidad de exposición se asocia negativamente con los resultados adversos en la clase 1.
Entonces podemos concluir de estos resultados que:
sexo_num y descripcion_enfermedad_num generan algo de efecto para el trastorno mental.
edad genera casi nada de efecto para el trastorno mental ya que está casi en 1.
institucion_unidad_medica_num genera mucho efecto para el trastorno mental.
Checamos cómo pronostica este modelo construido:
# Validar si el modelo pronostica adecuadamente.
y_pred_test = model1.predict(X_test)
print(y_pred_test[0:5])
print(y_test.head())
[1 1 1 1 1] 46382 0 32275 0 7409 1 32248 1 46407 1 Name: descripcion_grupo_enfermedad_num, dtype: int64
Vamos a probar como cambian los pronósticos que hace el modelo mediante el ajuste del umbral de clasificación, con model1.predict ya trabajamos con este establecido en 0.5 por automático.
Probabilidad cortando en mayor o igual a 0.8:
y_proba = model1.predict_proba(X)[:, 1]
y_pred = (y_proba >= 0.8).astype(int)
print(y_pred[0:5])
[1 1 1 1 1]
accuracy_train = model1.score(X_train, y_train)
print('Accuracy train = {:.2f}'.format(accuracy_train))
accuracy_test = model1.score(X_test, y_test)
print('Accuracy test = {:.2f}'.format(accuracy_test))
print('Diferencia = {:.4f}%'.format(np.abs(accuracy_train-accuracy_test)*100))
Accuracy train = 0.79 Accuracy test = 0.82 Diferencia = 3.2986%
Hasta este punto, obtenemos que el modelo presenta algo de sobreajuste, es decir, que generalizar hasta cierta escala entre los positivos reales y negativos reales (1 y 0 respectivamente).
predRF = model1.predict(X_test)
repDT = classification_report(y_test, predRF)
print(repDT)
from imblearn.metrics import geometric_mean_score
print('G-mean =', geometric_mean_score(y_test, predRF))
disp = ConfusionMatrixDisplay.from_predictions(y_test, predRF, cmap = plt.cm.Blues)
# 0: Factores influyendo en el estado de salud. 1: Trastorno mental y del comportamiento.
precision recall f1-score support
0 0.76 0.37 0.50 35
1 0.83 0.96 0.89 109
accuracy 0.82 144
macro avg 0.80 0.67 0.69 144
weighted avg 0.81 0.82 0.80 144
G-mean = 0.5981623234019464
Matriz de confusión.
13 + 105 = 118 aciertos.
Porcentaje de aciertos → 118 / 144 = 0.81 → 81%.
4 + 22 = 26 errores.
144 datos en total.
Reporte de clasificación.
35 datos de prueba de Factores influyendo en el estado de salud.
- 37% de positivos encontrados.
- 76% fueron clasificados correctamente.
109 datos de prueba de Trastorno mental y del comportamiento.
- 96% de positivos encontrados.
- 83% fueron clasificados correctamente.
F1-Score: promedio ponderado de las clases.
- Factores influyendo en el estado de salud: 50%.
- Trastorno mental y del comportamiento: 89%.
Porcentaje de proporción a encontrar todas las clasificaciones de este estudio (G-mean): 59%.
Según Torres (2024), para evaluar el rendimiento del modelo de clasificación, se utiliza la curva ROC, codificada bajo los estándares de Roc_Curve (s. f) con Scikit-learn.
from IPython.display import Image
Image(filename='/Users/estefaniadelarosa/Downloads/roc-curve-v2.png')
Imagen 1. ROC.
from sklearn.model_selection import cross_val_predict
from sklearn.metrics import roc_curve, roc_auc_score
# Validación cruzada.
y_probs = cross_val_predict(model1, X, y, cv = 5, method = 'predict_proba')[:,1] # Agregando método como scores.
# Curva ROC.
fpr, tpr, thresholds = roc_curve(y, y_probs)
auc = roc_auc_score(y, y_probs)
# Graficar
plt.plot(fpr, tpr)
plt.plot([0,1], [0,1])
plt.xlabel("False Positive Rate.")
plt.ylabel("True Positive Rate.")
plt.title("ROC Curve.")
plt.show()
print("AUC:", auc)
AUC: 0.7687227229977491
Basándonos en la imagen de Draelos, podemos delimitar de forma visual que el clasificador no es tan bueno al ejemplo ilustrado anteriormente, su curva de clasificación es muy lejana a la que se considera perfecta, de igual forma, el área bajo la curva de 0.76 indica que este modelo se necesita mejorar ya que no es bueno en definitiva (aunque pudiera está peor).
Logistic-SMOTE.
Comenzamos la técnica de balanceo de SMOTE, donde dicha técnica permite que se imputen datos sintéticos para que las clases se encuentren equilibradas - en este caso, ya no estamos alterando los pesos, sino que brindando datos para rellenar.
from imblearn.over_sampling import SMOTE
# Definir la técnica.
smote = SMOTE(random_state = 0)
# Aplicamos.
X_smote, y_smote = smote.fit_resample(X, y)
# Comprobar si funicona.
print('Tamaño de X antes de SMOTE:', X.shape)
print('Tamaño de X después de SMOTE:', X_smote.shape)
print('Balance de clases con SMOTE:', y_smote.value_counts())
print('Nuestras clases están balanceadas.')
Tamaño de X antes de SMOTE: (720, 3) Tamaño de X después de SMOTE: (1094, 3) Balance de clases con SMOTE: descripcion_grupo_enfermedad_num 1 547 0 547 Name: count, dtype: int64 Nuestras clases están balanceadas.
# Dividir los tratos en train y test.
X_train, X_test, y_train, y_test = train_test_split(X_smote, y_smote, test_size = 0.2, random_state = 0, stratify = y_smote)
print(y_train.value_counts())
X_train.shape, X_test.shape, y_train.shape, y_test.shape
# No dividir de manera que se desbalanceen los datos -> cuidado con el random_state).
descripcion_grupo_enfermedad_num 1 438 0 437 Name: count, dtype: int64
((875, 3), (219, 3), (875,), (219,))
Construimos el modelo:
# Definimos el modelo.
model2 = LogisticRegression(random_state = 0)
# Entrenamos el modelo.
model2.fit(X_train, y_train)
LogisticRegression(random_state=0)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
Para esta técnica también evaluaremos Cross-validation bajo los mismos parámetros.
scores = cross_val_score(model2, X_smote, y_smote, cv = 5)
print(scores.mean())
0.7194294332034686
OR = np.exp(model2.coef_[0])
coef_df = pd.DataFrame({
"Variables": X.columns,
"Coeficientes": model2.coef_[0],
"Odds Ratio": OR
})
print(coef_df)
Variables Coeficientes Odds Ratio 0 edad -0.022775 0.977482 1 sexo_num 1.005345 2.732851 2 institucion_unidad_medica_num -1.639948 0.193990
Según Egbuchulem (2025), los significados del valor de la razón de probabilidades de Odd Ratios son los siguientes:
OR de 1: No habría asociación entre la exposición y el resultado en la clase 1.
OR > 1: Sugiere que las probabilidades de exposición/intervención están asociadas positivamente con el resultado adverso en la clase 1.
OR < 1: Sugiere que la probabilidad de exposición se asocia negativamente con los resultados adversos en la clase 1.
Entonces podemos concluir de estos resultados que:
sexo_num genera mucho más impacto que el modelo desbalanceado, es la variable más fuerte.
descripcion_enfermedad_num genera algo de efecto para el trastorno mental.
edad genera casi nada de efecto para el trastorno mental ya que está casi en 1.
institucion_unidad_medica_num genera mucho efecto para el trastorno mental.
Checamos cómo pronostica este modelo construido:
# Validar si el modelo pronostica adecuadamente.
y_pred_test = model2.predict(X_test)
print(y_pred_test[0:5])
print(y_test.head())
[0 1 0 1 0] 944 0 199 1 1034 0 405 0 727 0 Name: descripcion_grupo_enfermedad_num, dtype: int64
Veremos cómo pronostica con un umbral de clasificación en mayor o igual a 0.8:
y_proba = model2.predict_proba(X)[:, 1]
y_pred = (y_proba >= 0.8).astype(int)
print(y_pred[0:5])
[1 0 0 0 0]
accuracy_train = model2.score(X_train, y_train)
print('Accuracy train = {:.2f}'.format(accuracy_train))
accuracy_test = model2.score(X_test, y_test)
print('Accuracy test = {:.2f}'.format(accuracy_test))
print('Diferencia = {:.4f}%'.format(np.abs(accuracy_train-accuracy_test)*100))
Accuracy train = 0.72 Accuracy test = 0.74 Diferencia = 2.6578%
Este modelo con la técnica de balanceo SMOTE con imputación de datos sintéticos, nos muestra que tiene un leve sobreajuste de 2.6%, sin embargo, esto no indica el no saber generalizar entre los positivos reales y negativos reales (1 y 0 respectivamente), además de que es un valor mejor a aquel del modelo desbalanceado.
predRF = model2.predict(X_test)
repDT = classification_report(y_test, predRF)
print(repDT)
from imblearn.metrics import geometric_mean_score
print('G-mean =', geometric_mean_score(y_test, predRF))
disp = ConfusionMatrixDisplay.from_predictions(y_test, predRF, cmap = plt.cm.Blues)
# 0: Factores influyendo en el estado de salud. 1: Trastorno mental y del comportamiento.
precision recall f1-score support
0 0.72 0.80 0.76 110
1 0.77 0.69 0.73 109
accuracy 0.74 219
macro avg 0.75 0.74 0.74 219
weighted avg 0.75 0.74 0.74 219
G-mean = 0.7419290502442469
Matriz de confusión.
88 + 75 = 163 aciertos.
Porcentaje de aciertos → 163 / 219 = 0.74 → 74%.
34 + 22 = 56 errores.
219 datos en total.
Reporte de clasificación.
110 datos de prueba de Factores influyendo en el estado de salud.
- 80% de positivos encontrados.
- 72% fueron clasificados correctamente.
109 datos de prueba de Trastorno mental y del comportamiento.
- 69% de positivos encontrados.
- 77% fueron clasificados correctamente.
F1-Score: promedio ponderado de las clases.
- Factores influyendo en el estado de salud: 76%.
- Trastorno mental y del comportamiento: 73%.
Porcentaje de proporción a encontrar todas las clasificaciones de este estudio (G-mean): 74%.
from sklearn.model_selection import cross_val_predict
from sklearn.metrics import roc_curve, roc_auc_score
import matplotlib.pyplot as plt
# Validación cruzada.
y_probs = cross_val_predict(model2, X, y, cv = 5, method = 'predict_proba')[:,1] # Agregando método como scores.
# Curva ROC.
fpr, tpr, thresholds = roc_curve(y, y_probs)
auc = roc_auc_score(y, y_probs)
# Graficar.
plt.plot(fpr, tpr)
plt.plot([0,1], [0,1])
plt.xlabel("False Positive Rate.")
plt.ylabel("True Positive Rate.")
plt.title("ROC Curve.")
plt.show()
print("AUC:", auc)
AUC: 0.7687227229977491
La curva ROC de la técnica SMOTE arroja que el clasificador del mismo (tomando también en consideración el área bajo la curva de 0.90) que este está muy parecido a la curva del es el mejor de todos hasta el momento con una diferencia pequeña a comparación del balanceo manual, pero existente.
Métricas.
Las métricas de desempeño evaluadas para estos modelos fueron:
Accuracy.
Matriz de confusión.
G-mean.
Precision.
Recall (Sensibilidad).
F1-Score.
Curva ROC-AUC.
Linear Discriminant Analysis.¶
Para el desarrollo de este modelo, se tomó como guía aquella de Kavlakoglu (s. f.), llamada: "How to implement linear discriminant analysis in Python." publicada en el sitio de IBM.
# Definimos entrada y salida.
X = df2[['edad', 'sexo_num', 'institucion_unidad_medica_num']]
y = df2['descripcion_grupo_enfermedad_num']
print(X.shape)
print(y.shape)
(720, 3) (720,)
LDA by Default.
Separamos los datos de entrenamiento y prueba:
# Dividir los datos en train y test.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0, stratify = y)
X_train.shape, X_test.shape, y_train.shape, y_test.shape
# No dividir de manera que se desbalanceen los datos -> cuidado con el random_state).
((576, 3), (144, 3), (576,), (144,))
Visualizamos con Pair Plot:
# Create a pair plot to visualize relationships between different features and species.
ax = sns.pairplot(df2, hue = 'descripcion_grupo_enfermedad_num', markers = ["o", "s", "D"])
plt.suptitle("Pair Plot: Grupos de Enfermedades.")
sns.move_legend(
ax, "lower center",
bbox_to_anchor = (.5, 1), ncol = 3, title = None, frameon = False)
plt.tight_layout()
plt.show()
/opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs) /opt/anaconda3/lib/python3.13/site-packages/seaborn/axisgrid.py:1615: UserWarning: The markers list has more values (3) than needed (2), which may not be intended. func(x=x, y=y, **kwargs)
Visualizamos con Histogramas:
# Visualize the distribution of each feature using histograms.
plt.figure(figsize =( 12, 6))
for i, feature in enumerate(X[:-1]):
plt.subplot(2, 2, i + 1)
sns.histplot(data = df2, x = feature, hue = 'descripcion_grupo_enfermedad_num', kde = True)
plt.title(f'{feature} Distribution.')
plt.tight_layout()
plt.show()
En este caso, se pueden observar diversas cosas:
En el rango de edad de los 20 a los tardíos 30, la mayoría se encuentran en el grupo 1, es decir, dentro de padecer trastornos mentales y del comportamiento.
Dentro de aquí, existe un hecho atípico ya que en los 40 y 50, la mayoría se encuentra en el grupo 0, es decir, dentro de estar viviendo con factores que influyen en su salud mental, no de forma tan grave como los trastornos.
Pero en edades más avanzadas, vuelve a repuntar el grupo 1 de los trastornos, esto sería interesante de estudiar y cosas así que se descubran de forma visual mediante histogramas u otros gráficos.
correlation_matrix = df2.corr(numeric_only = True)
plt.figure(figsize=(8, 6))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', linewidths=0.5)
plt.title("Correlation Heatmap")
plt.show()
Más adelante, al momento de implementar LDA, se tiene que establecer un número de componentes, en el caso de este dataset, solo se tiene que incorporar 1 componente ya que solo vamos a estar tratando dos clases.
# Verificación de n_components = 1 siendo 2 clases.
print(np.unique(y_train))
print(len(np.unique(y_train)))
[0 1] 2
Implementamos el modelo de LDA gracias a la librería importada anteriormente de sklearn.discriminant_analysis que contiene el LinearDiscriminantAnalysis:
# Apply Linear Discriminant Analysis.
lda = LinearDiscriminantAnalysis(n_components = 1)
X_train_lda = lda.fit_transform(X_train, y_train)
X_test_lda = lda.transform(X_test)
Importante aquí notar el nombre de las variables, estamos trabajando con el modelo desbalanceado.
tmp_Df = pd.DataFrame(X_train_lda, columns = ['LDA Component 1'])
tmp_Df['Class'] = y_train.values
# Grafica.
sns.histplot(data = tmp_Df, x = 'LDA Component 1', hue = 'Class', kde = True)
plt.show()
Este histograma por sí mismo nos muestra la distribución de cada clase a lo largo del LDA, podemos observar cómo se delimitaron las regiones en el respectivo lado izquierdo y lado derecho.
Lado izquierdo: clase 0 - Factores influyendo en el estado de salud.
Lado derecho: clase 1 - Trastorno mental y del comportamiento.
Ahora, ¿por qué visualizamos con un histograma dicha distribución lineal?
- Esto es debido a que estamos trabajando en 1D, es decir, que estamos bajo una dimensión, por lo que no podemos generar gráficos que se extiendan de las barras.
En el modelo desbalanceado, se puede observar que la separación entre clases existe pero no es perfecta — las distribuciones presentan un traslape notable, reflejo directo del desbalance entre las clases 0 y 1.
print(tmp_Df.head())
LDA Component 1 Class 0 -0.695258 0 1 -1.551494 0 2 0.251022 1 3 -1.727778 1 4 0.580071 1
LDA SMOTE.
Separamos los datos de entrenamiento y prueba:
# Dividir los datos en train y test.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0, stratify = y)
X_train.shape, X_test.shape, y_train.shape, y_test.shape
# No dividir de manera que se desbalanceen los datos -> cuidado con el random_state).
((576, 3), (144, 3), (576,), (144,))
Aplicamos el SMOTE:
from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state = 0)
X_train_smote, y_train_smote = smote.fit_resample(X_train, y_train)
Implemetamos el modelo de LDA:
# Apply Linear Discriminant Analysis.
lda = LinearDiscriminantAnalysis(n_components = 1)
X_train_lda = lda.fit_transform(X_train_smote, y_train_smote)
X_test_lda = lda.transform(X_test)
Ahora, aquí estamos trabajando con el modelo balanceado bajo SMOTE, notando esto en las variables.
tmp_Df = pd.DataFrame(X_train_lda, columns = ['LDA Component 1'])
tmp_Df['Class'] = y_train_smote.values
# Grafica.
sns.histplot(data = tmp_Df, x = 'LDA Component 1', hue = 'Class', kde = True)
plt.show()
print(tmp_Df.head())
LDA Component 1 Class 0 -0.571684 0 1 -1.299334 0 2 0.177260 1 3 -1.449144 1 4 1.296899 1
Este histograma nos muestra la distribución de cada clase a lo largo del LDA bajo la técnica de balanceo SMOTE.
Lado izquierdo: clase 0 - Factores influyendo en el estado de salud.
Lado derecho: clase 1 - Trastorno mental y del comportamiento.
Nuevamente, ¿por qué lo visualizamos con un histograma dicha distribución lineal?
- Esto es debido a que estamos trabajando en 1D, es decir, que estamos bajo una dimensión, por lo que no podemos generar gráficos que se extiendan de las barras.
A diferencia del modelo desbalanceado, con SMOTE se puede observar que la separación entre clases mejora visiblemente: las distribuciones están más definidas hacia sus respectivos lados, reduciendo el traslape entre ambas clases. Esto es el resultado directo de haber equilibrado las clases con datos sintéticos.
print(tmp_Df.head())
LDA Component 1 Class 0 -0.571684 0 1 -1.299334 0 2 0.177260 1 3 -1.449144 1 4 1.296899 1
Métricas.
Para el LDA, al ser un modelo de reducción de dimensionalidad que proyecta los datos en 1D para separar las clases, no se obtienen métricas de clasificación directas como Accuracy o F1-score propias del modelo. Lo que el LDA nos brinda es la proyección lineal que alimenta al Árbol de Decisión para que este pueda clasificar con datos más ordenados y balanceados.
LDA Desbalanceado: la proyección muestra traslape entre clases, lo que anticipa un menor desempeño del clasificador sobre estos datos.
LDA SMOTE: la proyección muestra clases más separadas y equilibradas, favoreciendo un mejor desempeño del árbol de decisión posterior.
Árbol de Decisión.¶
Árbol de Decisión by Default.
Generamos el árbol gracias a la librería de sklearn:
tree = DTC().fit(X_train, y_train)
Visualizamos el árbol:
from sklearn.tree import plot_tree
plt.figure(figsize=(15,10))
plot_tree(tree, filled = True, feature_names = X_train.columns)
[Text(0.4519230769230769, 0.9615384615384616, 'institucion_unidad_medica_num <= 0.5\ngini = 0.364\nsamples = 576\nvalue = [138, 438]'), Text(0.2097902097902098, 0.8846153846153846, 'sexo_num <= 0.5\ngini = 0.144\nsamples = 308\nvalue = [24, 284]'), Text(0.33085664335664333, 0.9230769230769231, 'True '), Text(0.1258741258741259, 0.8076923076923077, 'edad <= 42.5\ngini = 0.206\nsamples = 137\nvalue = [16, 121]'), Text(0.08391608391608392, 0.7307692307692307, 'edad <= 41.5\ngini = 0.287\nsamples = 69\nvalue = [12, 57]'), Text(0.06993006993006994, 0.6538461538461539, 'edad <= 27.5\ngini = 0.216\nsamples = 65\nvalue = [8, 57]'), Text(0.04195804195804196, 0.5769230769230769, 'edad <= 23.5\ngini = 0.053\nsamples = 37\nvalue = [1, 36]'), Text(0.027972027972027972, 0.5, 'edad <= 21.5\ngini = 0.105\nsamples = 18\nvalue = [1, 17]'), Text(0.013986013986013986, 0.4230769230769231, 'gini = 0.0\nsamples = 17\nvalue = [0, 17]'), Text(0.04195804195804196, 0.4230769230769231, 'gini = 0.0\nsamples = 1\nvalue = [1, 0]'), Text(0.055944055944055944, 0.5, 'gini = 0.0\nsamples = 19\nvalue = [0, 19]'), Text(0.0979020979020979, 0.5769230769230769, 'edad <= 29.0\ngini = 0.375\nsamples = 28\nvalue = [7, 21]'), Text(0.08391608391608392, 0.5, 'gini = 0.49\nsamples = 7\nvalue = [4, 3]'), Text(0.11188811188811189, 0.5, 'edad <= 34.0\ngini = 0.245\nsamples = 21\nvalue = [3, 18]'), Text(0.0979020979020979, 0.4230769230769231, 'gini = 0.0\nsamples = 8\nvalue = [0, 8]'), Text(0.1258741258741259, 0.4230769230769231, 'edad <= 36.0\ngini = 0.355\nsamples = 13\nvalue = [3, 10]'), Text(0.11188811188811189, 0.34615384615384615, 'gini = 0.444\nsamples = 9\nvalue = [3, 6]'), Text(0.13986013986013987, 0.34615384615384615, 'gini = 0.0\nsamples = 4\nvalue = [0, 4]'), Text(0.0979020979020979, 0.6538461538461539, 'gini = 0.0\nsamples = 4\nvalue = [4, 0]'), Text(0.16783216783216784, 0.7307692307692307, 'edad <= 53.5\ngini = 0.111\nsamples = 68\nvalue = [4, 64]'), Text(0.13986013986013987, 0.6538461538461539, 'edad <= 49.0\ngini = 0.042\nsamples = 47\nvalue = [1, 46]'), Text(0.1258741258741259, 0.5769230769230769, 'gini = 0.0\nsamples = 24\nvalue = [0, 24]'), Text(0.15384615384615385, 0.5769230769230769, 'edad <= 50.5\ngini = 0.083\nsamples = 23\nvalue = [1, 22]'), Text(0.13986013986013987, 0.5, 'gini = 0.219\nsamples = 8\nvalue = [1, 7]'), Text(0.16783216783216784, 0.5, 'gini = 0.0\nsamples = 15\nvalue = [0, 15]'), Text(0.1958041958041958, 0.6538461538461539, 'edad <= 54.5\ngini = 0.245\nsamples = 21\nvalue = [3, 18]'), Text(0.18181818181818182, 0.5769230769230769, 'gini = 0.5\nsamples = 4\nvalue = [2, 2]'), Text(0.2097902097902098, 0.5769230769230769, 'edad <= 58.5\ngini = 0.111\nsamples = 17\nvalue = [1, 16]'), Text(0.1958041958041958, 0.5, 'gini = 0.0\nsamples = 13\nvalue = [0, 13]'), Text(0.22377622377622378, 0.5, 'edad <= 60.5\ngini = 0.375\nsamples = 4\nvalue = [1, 3]'), Text(0.2097902097902098, 0.4230769230769231, 'gini = 0.0\nsamples = 1\nvalue = [1, 0]'), Text(0.23776223776223776, 0.4230769230769231, 'gini = 0.0\nsamples = 3\nvalue = [0, 3]'), Text(0.2937062937062937, 0.8076923076923077, 'edad <= 46.0\ngini = 0.089\nsamples = 171\nvalue = [8, 163]'), Text(0.26573426573426573, 0.7307692307692307, 'edad <= 32.5\ngini = 0.058\nsamples = 133\nvalue = [4, 129]'), Text(0.2517482517482518, 0.6538461538461539, 'edad <= 21.5\ngini = 0.081\nsamples = 95\nvalue = [4, 91]'), Text(0.23776223776223776, 0.5769230769230769, 'gini = 0.0\nsamples = 26\nvalue = [0, 26]'), Text(0.26573426573426573, 0.5769230769230769, 'edad <= 23.0\ngini = 0.109\nsamples = 69\nvalue = [4, 65]'), Text(0.2517482517482518, 0.5, 'gini = 0.5\nsamples = 2\nvalue = [1, 1]'), Text(0.27972027972027974, 0.5, 'edad <= 31.5\ngini = 0.086\nsamples = 67\nvalue = [3, 64]'), Text(0.26573426573426573, 0.4230769230769231, 'edad <= 25.5\ngini = 0.065\nsamples = 59\nvalue = [2, 57]'), Text(0.23776223776223776, 0.34615384615384615, 'edad <= 24.5\ngini = 0.198\nsamples = 9\nvalue = [1, 8]'), Text(0.22377622377622378, 0.2692307692307692, 'gini = 0.0\nsamples = 3\nvalue = [0, 3]'), Text(0.2517482517482518, 0.2692307692307692, 'gini = 0.278\nsamples = 6\nvalue = [1, 5]'), Text(0.2937062937062937, 0.34615384615384615, 'edad <= 28.5\ngini = 0.039\nsamples = 50\nvalue = [1, 49]'), Text(0.27972027972027974, 0.2692307692307692, 'edad <= 27.5\ngini = 0.071\nsamples = 27\nvalue = [1, 26]'), Text(0.26573426573426573, 0.19230769230769232, 'gini = 0.0\nsamples = 7\nvalue = [0, 7]'), Text(0.2937062937062937, 0.19230769230769232, 'gini = 0.095\nsamples = 20\nvalue = [1, 19]'), Text(0.3076923076923077, 0.2692307692307692, 'gini = 0.0\nsamples = 23\nvalue = [0, 23]'), Text(0.2937062937062937, 0.4230769230769231, 'gini = 0.219\nsamples = 8\nvalue = [1, 7]'), Text(0.27972027972027974, 0.6538461538461539, 'gini = 0.0\nsamples = 38\nvalue = [0, 38]'), Text(0.32167832167832167, 0.7307692307692307, 'edad <= 48.0\ngini = 0.188\nsamples = 38\nvalue = [4, 34]'), Text(0.3076923076923077, 0.6538461538461539, 'gini = 0.494\nsamples = 9\nvalue = [4, 5]'), Text(0.3356643356643357, 0.6538461538461539, 'gini = 0.0\nsamples = 29\nvalue = [0, 29]'), Text(0.6940559440559441, 0.8846153846153846, 'edad <= 40.5\ngini = 0.489\nsamples = 268\nvalue = [114, 154]'), Text(0.5729895104895105, 0.9230769230769231, ' False'), Text(0.4991258741258741, 0.8076923076923077, 'sexo_num <= 0.5\ngini = 0.426\nsamples = 143\nvalue = [44, 99]'), Text(0.42482517482517484, 0.7307692307692307, 'edad <= 21.5\ngini = 0.461\nsamples = 114\nvalue = [41, 73]'), Text(0.36363636363636365, 0.6538461538461539, 'edad <= 19.5\ngini = 0.298\nsamples = 22\nvalue = [4, 18]'), Text(0.3356643356643357, 0.5769230769230769, 'edad <= 18.5\ngini = 0.444\nsamples = 3\nvalue = [2, 1]'), Text(0.32167832167832167, 0.5, 'gini = 0.0\nsamples = 1\nvalue = [0, 1]'), Text(0.34965034965034963, 0.5, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]'), Text(0.3916083916083916, 0.5769230769230769, 'edad <= 20.5\ngini = 0.188\nsamples = 19\nvalue = [2, 17]'), Text(0.3776223776223776, 0.5, 'gini = 0.245\nsamples = 7\nvalue = [1, 6]'), Text(0.40559440559440557, 0.5, 'gini = 0.153\nsamples = 12\nvalue = [1, 11]'), Text(0.486013986013986, 0.6538461538461539, 'edad <= 23.5\ngini = 0.481\nsamples = 92\nvalue = [37, 55]'), Text(0.44755244755244755, 0.5769230769230769, 'edad <= 22.5\ngini = 0.48\nsamples = 15\nvalue = [9, 6]'), Text(0.43356643356643354, 0.5, 'gini = 0.48\nsamples = 10\nvalue = [6, 4]'), Text(0.46153846153846156, 0.5, 'gini = 0.48\nsamples = 5\nvalue = [3, 2]'), Text(0.5244755244755245, 0.5769230769230769, 'edad <= 25.5\ngini = 0.463\nsamples = 77\nvalue = [28, 49]'), Text(0.48951048951048953, 0.5, 'edad <= 24.5\ngini = 0.32\nsamples = 20\nvalue = [4, 16]'), Text(0.4755244755244755, 0.4230769230769231, 'gini = 0.42\nsamples = 10\nvalue = [3, 7]'), Text(0.5034965034965035, 0.4230769230769231, 'gini = 0.18\nsamples = 10\nvalue = [1, 9]'), Text(0.5594405594405595, 0.5, 'edad <= 29.5\ngini = 0.488\nsamples = 57\nvalue = [24, 33]'), Text(0.5314685314685315, 0.4230769230769231, 'edad <= 28.5\ngini = 0.219\nsamples = 8\nvalue = [7, 1]'), Text(0.5174825174825175, 0.34615384615384615, 'gini = 0.0\nsamples = 5\nvalue = [5, 0]'), Text(0.5454545454545454, 0.34615384615384615, 'gini = 0.444\nsamples = 3\nvalue = [2, 1]'), Text(0.5874125874125874, 0.4230769230769231, 'edad <= 31.5\ngini = 0.453\nsamples = 49\nvalue = [17.0, 32.0]'), Text(0.5734265734265734, 0.34615384615384615, 'gini = 0.0\nsamples = 2\nvalue = [0, 2]'), Text(0.6013986013986014, 0.34615384615384615, 'edad <= 36.5\ngini = 0.462\nsamples = 47\nvalue = [17, 30]'), Text(0.5664335664335665, 0.2692307692307692, 'edad <= 34.5\ngini = 0.478\nsamples = 33\nvalue = [13, 20]'), Text(0.5384615384615384, 0.19230769230769232, 'edad <= 33.5\ngini = 0.444\nsamples = 18\nvalue = [6, 12]'), Text(0.5244755244755245, 0.11538461538461539, 'edad <= 32.5\ngini = 0.457\nsamples = 17\nvalue = [6, 11]'), Text(0.5104895104895105, 0.038461538461538464, 'gini = 0.5\nsamples = 2\nvalue = [1, 1]'), Text(0.5384615384615384, 0.038461538461538464, 'gini = 0.444\nsamples = 15\nvalue = [5, 10]'), Text(0.5524475524475524, 0.11538461538461539, 'gini = 0.0\nsamples = 1\nvalue = [0, 1]'), Text(0.5944055944055944, 0.19230769230769232, 'edad <= 35.5\ngini = 0.498\nsamples = 15\nvalue = [7, 8]'), Text(0.5804195804195804, 0.11538461538461539, 'gini = 0.444\nsamples = 3\nvalue = [2, 1]'), Text(0.6083916083916084, 0.11538461538461539, 'gini = 0.486\nsamples = 12\nvalue = [5, 7]'), Text(0.6363636363636364, 0.2692307692307692, 'edad <= 38.5\ngini = 0.408\nsamples = 14\nvalue = [4, 10]'), Text(0.6223776223776224, 0.19230769230769232, 'gini = 0.346\nsamples = 9\nvalue = [2, 7]'), Text(0.6503496503496503, 0.19230769230769232, 'gini = 0.48\nsamples = 5\nvalue = [2, 3]'), Text(0.5734265734265734, 0.7307692307692307, 'edad <= 19.0\ngini = 0.185\nsamples = 29\nvalue = [3, 26]'), Text(0.5594405594405595, 0.6538461538461539, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]'), Text(0.5874125874125874, 0.6538461538461539, 'edad <= 32.0\ngini = 0.071\nsamples = 27\nvalue = [1, 26]'), Text(0.5734265734265734, 0.5769230769230769, 'gini = 0.0\nsamples = 17\nvalue = [0, 17]'), Text(0.6013986013986014, 0.5769230769230769, 'edad <= 34.0\ngini = 0.18\nsamples = 10\nvalue = [1, 9]'), Text(0.5874125874125874, 0.5, 'gini = 0.0\nsamples = 1\nvalue = [1, 0]'), Text(0.6153846153846154, 0.5, 'gini = 0.0\nsamples = 9\nvalue = [0, 9]'), Text(0.888986013986014, 0.8076923076923077, 'edad <= 56.5\ngini = 0.493\nsamples = 125\nvalue = [70, 55]'), Text(0.833916083916084, 0.7307692307692307, 'edad <= 52.5\ngini = 0.462\nsamples = 91\nvalue = [58.0, 33.0]'), Text(0.7937062937062938, 0.6538461538461539, 'sexo_num <= 0.5\ngini = 0.5\nsamples = 47\nvalue = [23.0, 24.0]'), Text(0.7797202797202797, 0.5769230769230769, 'edad <= 48.5\ngini = 0.465\nsamples = 38\nvalue = [14, 24]'), Text(0.7132867132867133, 0.5, 'edad <= 43.5\ngini = 0.494\nsamples = 27\nvalue = [12, 15]'), Text(0.6643356643356644, 0.4230769230769231, 'edad <= 41.5\ngini = 0.444\nsamples = 9\nvalue = [3, 6]'), Text(0.6503496503496503, 0.34615384615384615, 'gini = 0.444\nsamples = 3\nvalue = [2, 1]'), Text(0.6783216783216783, 0.34615384615384615, 'edad <= 42.5\ngini = 0.278\nsamples = 6\nvalue = [1, 5]'), Text(0.6643356643356644, 0.2692307692307692, 'gini = 0.444\nsamples = 3\nvalue = [1, 2]'), Text(0.6923076923076923, 0.2692307692307692, 'gini = 0.0\nsamples = 3\nvalue = [0, 3]'), Text(0.7622377622377622, 0.4230769230769231, 'edad <= 45.5\ngini = 0.5\nsamples = 18\nvalue = [9, 9]'), Text(0.7342657342657343, 0.34615384615384615, 'edad <= 44.5\ngini = 0.444\nsamples = 6\nvalue = [4, 2]'), Text(0.7202797202797203, 0.2692307692307692, 'gini = 0.5\nsamples = 4\nvalue = [2, 2]'), Text(0.7482517482517482, 0.2692307692307692, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]'), Text(0.7902097902097902, 0.34615384615384615, 'edad <= 47.0\ngini = 0.486\nsamples = 12\nvalue = [5, 7]'), Text(0.7762237762237763, 0.2692307692307692, 'gini = 0.32\nsamples = 5\nvalue = [1, 4]'), Text(0.8041958041958042, 0.2692307692307692, 'gini = 0.49\nsamples = 7\nvalue = [4, 3]'), Text(0.8461538461538461, 0.5, 'edad <= 51.0\ngini = 0.298\nsamples = 11\nvalue = [2, 9]'), Text(0.8321678321678322, 0.4230769230769231, 'edad <= 49.5\ngini = 0.219\nsamples = 8\nvalue = [1, 7]'), Text(0.8181818181818182, 0.34615384615384615, 'gini = 0.278\nsamples = 6\nvalue = [1, 5]'), Text(0.8461538461538461, 0.34615384615384615, 'gini = 0.0\nsamples = 2\nvalue = [0, 2]'), Text(0.8601398601398601, 0.4230769230769231, 'gini = 0.444\nsamples = 3\nvalue = [1, 2]'), Text(0.8076923076923077, 0.5769230769230769, 'gini = 0.0\nsamples = 9\nvalue = [9, 0]'), Text(0.8741258741258742, 0.6538461538461539, 'edad <= 53.5\ngini = 0.325\nsamples = 44\nvalue = [35, 9]'), Text(0.8601398601398601, 0.5769230769230769, 'gini = 0.0\nsamples = 6\nvalue = [6, 0]'), Text(0.8881118881118881, 0.5769230769230769, 'edad <= 54.5\ngini = 0.361\nsamples = 38\nvalue = [29, 9]'), Text(0.8741258741258742, 0.5, 'gini = 0.408\nsamples = 14\nvalue = [10, 4]'), Text(0.9020979020979021, 0.5, 'edad <= 55.5\ngini = 0.33\nsamples = 24\nvalue = [19, 5]'), Text(0.8881118881118881, 0.4230769230769231, 'gini = 0.198\nsamples = 9\nvalue = [8, 1]'), Text(0.916083916083916, 0.4230769230769231, 'gini = 0.391\nsamples = 15\nvalue = [11, 4]'), Text(0.9440559440559441, 0.7307692307692307, 'edad <= 58.0\ngini = 0.457\nsamples = 34\nvalue = [12, 22]'), Text(0.9300699300699301, 0.6538461538461539, 'gini = 0.278\nsamples = 18\nvalue = [3, 15]'), Text(0.958041958041958, 0.6538461538461539, 'edad <= 61.0\ngini = 0.492\nsamples = 16\nvalue = [9, 7]'), Text(0.9440559440559441, 0.5769230769230769, 'gini = 0.0\nsamples = 6\nvalue = [6, 0]'), Text(0.972027972027972, 0.5769230769230769, 'edad <= 64.5\ngini = 0.42\nsamples = 10\nvalue = [3, 7]'), Text(0.958041958041958, 0.5, 'edad <= 62.5\ngini = 0.219\nsamples = 8\nvalue = [1, 7]'), Text(0.9440559440559441, 0.4230769230769231, 'gini = 0.0\nsamples = 4\nvalue = [0, 4]'), Text(0.972027972027972, 0.4230769230769231, 'edad <= 63.5\ngini = 0.375\nsamples = 4\nvalue = [1, 3]'), Text(0.958041958041958, 0.34615384615384615, 'gini = 0.0\nsamples = 1\nvalue = [1, 0]'), Text(0.986013986013986, 0.34615384615384615, 'gini = 0.0\nsamples = 3\nvalue = [0, 3]'), Text(0.986013986013986, 0.5, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]')]
El árbol generado cuenta con los siguientes datos:
Su profundidad (longitud vertical), es de 12.
Cuenta con 69 hojas.
Esto nos ayuda a dimensionar que es un árbol complejo respectivo al tamaño del dataset.
print(tree.tree_.max_depth)
12
print(tree.get_n_leaves())
69
Métricas: aquí evaluaremos dos aspectos:
Accuracy: aciertos totales.
F1-score: promedio ponderado de las clases.
from sklearn.metrics import accuracy_score, f1_score
yhat0 = tree.predict(X_test)
acc0 = accuracy_score(y_test, yhat0)
f10 = f1_score(y_test, yhat0, average='weighted')
print("Accuracy inicial: ", acc0)
print("F1-score inicial: ", f10)
Accuracy inicial: 0.8402777777777778 F1-score inicial: 0.8377859946121579
El Accuracy del modelo es bueno, obtenemos un valor de 0.84, por lo que se puede concluir que bajo el árbol original se acierta la clasificación un 84%.
El F1-score del modelo también es bueno, obtenemos un valor de 0.83; este resultado puede presentar sesgo hacia la clase mayoritaria dada la ausencia de balanceo.
# Matriz de confusión - Árbol by Default.
from sklearn.metrics import confusion_matrix
conf_m = confusion_matrix(y_test, yhat0)
sns.heatmap(conf_m, annot = True, fmt = "d", cmap = "Blues", cbar = False, square = True)
plt.ylabel('y_true')
plt.xlabel('y_pred')
plt.title('Matriz de Confusión - Árbol Inicial.')
plt.show()
Matriz de confusión.
25 + 92 = 117 aciertos.
Porcentaje de aciertos → 117/144 = 0.81 → 81%.
17 + 10 = 27 errores.
144 datos en total.
Árbol de Decisión SMOTE.
Generamos el árbol:
tree_smote = DTC().fit(X_train_smote, y_train_smote)
Visualizamos el árbol:
from sklearn.tree import plot_tree
plt.figure(figsize=(15,10))
plot_tree(tree_smote, filled = True, feature_names = X_train_smote.columns)
[Text(0.4680059523809524, 0.96875, 'institucion_unidad_medica_num <= 0.5\ngini = 0.5\nsamples = 876\nvalue = [438, 438]'), Text(0.2222222222222222, 0.90625, 'sexo_num <= 0.5\ngini = 0.38\nsamples = 381\nvalue = [97, 284]'), Text(0.3451140873015873, 0.9375, 'True '), Text(0.09523809523809523, 0.84375, 'edad <= 21.5\ngini = 0.484\nsamples = 205\nvalue = [84, 121]'), Text(0.07936507936507936, 0.78125, 'gini = 0.0\nsamples = 17\nvalue = [0, 17]'), Text(0.1111111111111111, 0.78125, 'edad <= 49.5\ngini = 0.494\nsamples = 188\nvalue = [84, 104]'), Text(0.047619047619047616, 0.71875, 'edad <= 24.5\ngini = 0.498\nsamples = 136\nvalue = [72, 64]'), Text(0.031746031746031744, 0.65625, 'gini = 0.0\nsamples = 7\nvalue = [7, 0]'), Text(0.06349206349206349, 0.65625, 'edad <= 27.5\ngini = 0.5\nsamples = 129\nvalue = [65, 64]'), Text(0.031746031746031744, 0.59375, 'edad <= 25.5\ngini = 0.172\nsamples = 21\nvalue = [2, 19]'), Text(0.015873015873015872, 0.53125, 'gini = 0.408\nsamples = 7\nvalue = [2, 5]'), Text(0.047619047619047616, 0.53125, 'gini = 0.0\nsamples = 14\nvalue = [0, 14]'), Text(0.09523809523809523, 0.59375, 'edad <= 29.0\ngini = 0.486\nsamples = 108\nvalue = [63.0, 45.0]'), Text(0.07936507936507936, 0.53125, 'gini = 0.255\nsamples = 20\nvalue = [17, 3]'), Text(0.1111111111111111, 0.53125, 'edad <= 34.0\ngini = 0.499\nsamples = 88\nvalue = [46, 42]'), Text(0.07142857142857142, 0.46875, 'edad <= 32.5\ngini = 0.32\nsamples = 10\nvalue = [2, 8]'), Text(0.05555555555555555, 0.40625, 'edad <= 31.0\ngini = 0.5\nsamples = 4\nvalue = [2, 2]'), Text(0.03968253968253968, 0.34375, 'gini = 0.0\nsamples = 2\nvalue = [0, 2]'), Text(0.07142857142857142, 0.34375, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]'), Text(0.0873015873015873, 0.40625, 'gini = 0.0\nsamples = 6\nvalue = [0, 6]'), Text(0.15079365079365079, 0.46875, 'edad <= 42.5\ngini = 0.492\nsamples = 78\nvalue = [44, 34]'), Text(0.11904761904761904, 0.40625, 'edad <= 41.5\ngini = 0.401\nsamples = 36\nvalue = [26, 10]'), Text(0.10317460317460317, 0.34375, 'edad <= 36.0\ngini = 0.473\nsamples = 26\nvalue = [16, 10]'), Text(0.0873015873015873, 0.28125, 'gini = 0.444\nsamples = 18\nvalue = [12, 6]'), Text(0.11904761904761904, 0.28125, 'edad <= 38.5\ngini = 0.5\nsamples = 8\nvalue = [4, 4]'), Text(0.10317460317460317, 0.21875, 'gini = 0.0\nsamples = 2\nvalue = [0, 2]'), Text(0.1349206349206349, 0.21875, 'edad <= 40.5\ngini = 0.444\nsamples = 6\nvalue = [4, 2]'), Text(0.11904761904761904, 0.15625, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]'), Text(0.15079365079365079, 0.15625, 'gini = 0.5\nsamples = 4\nvalue = [2, 2]'), Text(0.1349206349206349, 0.34375, 'gini = 0.0\nsamples = 10\nvalue = [10, 0]'), Text(0.18253968253968253, 0.40625, 'edad <= 45.0\ngini = 0.49\nsamples = 42\nvalue = [18, 24]'), Text(0.16666666666666666, 0.34375, 'gini = 0.0\nsamples = 12\nvalue = [0, 12]'), Text(0.1984126984126984, 0.34375, 'edad <= 48.5\ngini = 0.48\nsamples = 30\nvalue = [18, 12]'), Text(0.18253968253968253, 0.28125, 'edad <= 46.5\ngini = 0.494\nsamples = 27\nvalue = [15, 12]'), Text(0.16666666666666666, 0.21875, 'gini = 0.375\nsamples = 4\nvalue = [3, 1]'), Text(0.1984126984126984, 0.21875, 'edad <= 47.5\ngini = 0.499\nsamples = 23\nvalue = [12, 11]'), Text(0.18253968253968253, 0.15625, 'gini = 0.498\nsamples = 17\nvalue = [8, 9]'), Text(0.21428571428571427, 0.15625, 'gini = 0.444\nsamples = 6\nvalue = [4, 2]'), Text(0.21428571428571427, 0.28125, 'gini = 0.0\nsamples = 3\nvalue = [3, 0]'), Text(0.1746031746031746, 0.71875, 'edad <= 53.5\ngini = 0.355\nsamples = 52\nvalue = [12, 40]'), Text(0.14285714285714285, 0.65625, 'edad <= 50.5\ngini = 0.083\nsamples = 23\nvalue = [1, 22]'), Text(0.12698412698412698, 0.59375, 'gini = 0.219\nsamples = 8\nvalue = [1, 7]'), Text(0.15873015873015872, 0.59375, 'gini = 0.0\nsamples = 15\nvalue = [0, 15]'), Text(0.20634920634920634, 0.65625, 'edad <= 54.5\ngini = 0.471\nsamples = 29\nvalue = [11, 18]'), Text(0.19047619047619047, 0.59375, 'gini = 0.298\nsamples = 11\nvalue = [9, 2]'), Text(0.2222222222222222, 0.59375, 'edad <= 58.5\ngini = 0.198\nsamples = 18\nvalue = [2, 16]'), Text(0.20634920634920634, 0.53125, 'gini = 0.0\nsamples = 13\nvalue = [0, 13]'), Text(0.23809523809523808, 0.53125, 'edad <= 60.5\ngini = 0.48\nsamples = 5\nvalue = [2, 3]'), Text(0.2222222222222222, 0.46875, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]'), Text(0.25396825396825395, 0.46875, 'gini = 0.0\nsamples = 3\nvalue = [0, 3]'), Text(0.3492063492063492, 0.84375, 'edad <= 46.0\ngini = 0.137\nsamples = 176\nvalue = [13, 163]'), Text(0.31746031746031744, 0.78125, 'edad <= 32.5\ngini = 0.058\nsamples = 133\nvalue = [4, 129]'), Text(0.30158730158730157, 0.71875, 'edad <= 21.5\ngini = 0.081\nsamples = 95\nvalue = [4, 91]'), Text(0.2857142857142857, 0.65625, 'gini = 0.0\nsamples = 26\nvalue = [0, 26]'), Text(0.31746031746031744, 0.65625, 'edad <= 23.0\ngini = 0.109\nsamples = 69\nvalue = [4, 65]'), Text(0.30158730158730157, 0.59375, 'gini = 0.5\nsamples = 2\nvalue = [1, 1]'), Text(0.3333333333333333, 0.59375, 'edad <= 31.5\ngini = 0.086\nsamples = 67\nvalue = [3, 64]'), Text(0.31746031746031744, 0.53125, 'edad <= 25.5\ngini = 0.065\nsamples = 59\nvalue = [2, 57]'), Text(0.2857142857142857, 0.46875, 'edad <= 24.5\ngini = 0.198\nsamples = 9\nvalue = [1, 8]'), Text(0.2698412698412698, 0.40625, 'gini = 0.0\nsamples = 3\nvalue = [0, 3]'), Text(0.30158730158730157, 0.40625, 'gini = 0.278\nsamples = 6\nvalue = [1, 5]'), Text(0.3492063492063492, 0.46875, 'edad <= 28.5\ngini = 0.039\nsamples = 50\nvalue = [1, 49]'), Text(0.3333333333333333, 0.40625, 'edad <= 27.5\ngini = 0.071\nsamples = 27\nvalue = [1, 26]'), Text(0.31746031746031744, 0.34375, 'gini = 0.0\nsamples = 7\nvalue = [0, 7]'), Text(0.3492063492063492, 0.34375, 'gini = 0.095\nsamples = 20\nvalue = [1, 19]'), Text(0.36507936507936506, 0.40625, 'gini = 0.0\nsamples = 23\nvalue = [0, 23]'), Text(0.3492063492063492, 0.53125, 'gini = 0.219\nsamples = 8\nvalue = [1, 7]'), Text(0.3333333333333333, 0.71875, 'gini = 0.0\nsamples = 38\nvalue = [0, 38]'), Text(0.38095238095238093, 0.78125, 'edad <= 48.0\ngini = 0.331\nsamples = 43\nvalue = [9, 34]'), Text(0.36507936507936506, 0.71875, 'gini = 0.459\nsamples = 14\nvalue = [9, 5]'), Text(0.3968253968253968, 0.71875, 'gini = 0.0\nsamples = 29\nvalue = [0, 29]'), Text(0.7137896825396826, 0.90625, 'edad <= 42.5\ngini = 0.429\nsamples = 495\nvalue = [341, 154]'), Text(0.5908978174603174, 0.9375, ' False'), Text(0.5466269841269841, 0.84375, 'sexo_num <= 0.5\ngini = 0.493\nsamples = 232\nvalue = [130, 102]'), Text(0.4742063492063492, 0.78125, 'edad <= 19.5\ngini = 0.468\nsamples = 203\nvalue = [127, 76]'), Text(0.42857142857142855, 0.71875, 'edad <= 18.5\ngini = 0.124\nsamples = 15\nvalue = [14, 1]'), Text(0.4126984126984127, 0.65625, 'gini = 0.18\nsamples = 10\nvalue = [9, 1]'), Text(0.4444444444444444, 0.65625, 'gini = 0.0\nsamples = 5\nvalue = [5, 0]'), Text(0.5198412698412699, 0.71875, 'edad <= 21.5\ngini = 0.48\nsamples = 188\nvalue = [113, 75]'), Text(0.47619047619047616, 0.65625, 'edad <= 20.5\ngini = 0.188\nsamples = 19\nvalue = [2, 17]'), Text(0.4603174603174603, 0.59375, 'gini = 0.245\nsamples = 7\nvalue = [1, 6]'), Text(0.49206349206349204, 0.59375, 'gini = 0.153\nsamples = 12\nvalue = [1, 11]'), Text(0.5634920634920635, 0.65625, 'edad <= 23.5\ngini = 0.451\nsamples = 169\nvalue = [111.0, 58.0]'), Text(0.5238095238095238, 0.59375, 'edad <= 22.5\ngini = 0.26\nsamples = 39\nvalue = [33, 6]'), Text(0.5079365079365079, 0.53125, 'gini = 0.238\nsamples = 29\nvalue = [25, 4]'), Text(0.5396825396825397, 0.53125, 'gini = 0.32\nsamples = 10\nvalue = [8, 2]'), Text(0.6031746031746031, 0.59375, 'edad <= 25.5\ngini = 0.48\nsamples = 130\nvalue = [78, 52]'), Text(0.5714285714285714, 0.53125, 'edad <= 24.5\ngini = 0.473\nsamples = 26\nvalue = [10, 16]'), Text(0.5555555555555556, 0.46875, 'gini = 0.486\nsamples = 12\nvalue = [5, 7]'), Text(0.5873015873015873, 0.46875, 'gini = 0.459\nsamples = 14\nvalue = [5, 9]'), Text(0.6349206349206349, 0.53125, 'edad <= 28.5\ngini = 0.453\nsamples = 104\nvalue = [68, 36]'), Text(0.6190476190476191, 0.46875, 'gini = 0.0\nsamples = 12\nvalue = [12, 0]'), Text(0.6507936507936508, 0.46875, 'edad <= 41.5\ngini = 0.476\nsamples = 92\nvalue = [56, 36]'), Text(0.6349206349206349, 0.40625, 'edad <= 40.5\ngini = 0.472\nsamples = 89\nvalue = [55.0, 34.0]'), Text(0.6190476190476191, 0.34375, 'edad <= 36.5\ngini = 0.479\nsamples = 83\nvalue = [50.0, 33.0]'), Text(0.5793650793650794, 0.28125, 'edad <= 31.5\ngini = 0.46\nsamples = 64\nvalue = [41, 23]'), Text(0.5476190476190477, 0.21875, 'edad <= 29.5\ngini = 0.48\nsamples = 5\nvalue = [2, 3]'), Text(0.5317460317460317, 0.15625, 'gini = 0.444\nsamples = 3\nvalue = [2, 1]'), Text(0.5634920634920635, 0.15625, 'gini = 0.0\nsamples = 2\nvalue = [0, 2]'), Text(0.6111111111111112, 0.21875, 'edad <= 32.5\ngini = 0.448\nsamples = 59\nvalue = [39, 20]'), Text(0.5952380952380952, 0.15625, 'gini = 0.32\nsamples = 5\nvalue = [4, 1]'), Text(0.626984126984127, 0.15625, 'edad <= 34.5\ngini = 0.456\nsamples = 54\nvalue = [35, 19]'), Text(0.5952380952380952, 0.09375, 'edad <= 33.5\ngini = 0.471\nsamples = 29\nvalue = [18, 11]'), Text(0.5793650793650794, 0.03125, 'gini = 0.459\nsamples = 28\nvalue = [18, 10]'), Text(0.6111111111111112, 0.03125, 'gini = 0.0\nsamples = 1\nvalue = [0, 1]'), Text(0.6587301587301587, 0.09375, 'edad <= 35.5\ngini = 0.435\nsamples = 25\nvalue = [17, 8]'), Text(0.6428571428571429, 0.03125, 'gini = 0.278\nsamples = 6\nvalue = [5, 1]'), Text(0.6746031746031746, 0.03125, 'gini = 0.465\nsamples = 19\nvalue = [12, 7]'), Text(0.6587301587301587, 0.28125, 'edad <= 38.5\ngini = 0.499\nsamples = 19\nvalue = [9, 10]'), Text(0.6428571428571429, 0.21875, 'gini = 0.463\nsamples = 11\nvalue = [4, 7]'), Text(0.6746031746031746, 0.21875, 'gini = 0.469\nsamples = 8\nvalue = [5, 3]'), Text(0.6507936507936508, 0.34375, 'gini = 0.278\nsamples = 6\nvalue = [5, 1]'), Text(0.6666666666666666, 0.40625, 'gini = 0.444\nsamples = 3\nvalue = [1, 2]'), Text(0.6190476190476191, 0.78125, 'edad <= 19.0\ngini = 0.185\nsamples = 29\nvalue = [3, 26]'), Text(0.6031746031746031, 0.71875, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]'), Text(0.6349206349206349, 0.71875, 'edad <= 32.0\ngini = 0.071\nsamples = 27\nvalue = [1, 26]'), Text(0.6190476190476191, 0.65625, 'gini = 0.0\nsamples = 17\nvalue = [0, 17]'), Text(0.6507936507936508, 0.65625, 'edad <= 34.0\ngini = 0.18\nsamples = 10\nvalue = [1, 9]'), Text(0.6349206349206349, 0.59375, 'gini = 0.0\nsamples = 1\nvalue = [1, 0]'), Text(0.6666666666666666, 0.59375, 'gini = 0.0\nsamples = 9\nvalue = [0, 9]'), Text(0.8809523809523809, 0.84375, 'edad <= 56.5\ngini = 0.317\nsamples = 263\nvalue = [211, 52]'), Text(0.8253968253968254, 0.78125, 'edad <= 52.5\ngini = 0.249\nsamples = 206\nvalue = [176.0, 30.0]'), Text(0.7936507936507936, 0.71875, 'sexo_num <= 0.5\ngini = 0.363\nsamples = 88\nvalue = [67, 21]'), Text(0.7777777777777778, 0.65625, 'edad <= 48.5\ngini = 0.458\nsamples = 59\nvalue = [38, 21]'), Text(0.746031746031746, 0.59375, 'edad <= 47.0\ngini = 0.397\nsamples = 44\nvalue = [32, 12]'), Text(0.7301587301587301, 0.53125, 'edad <= 45.5\ngini = 0.495\nsamples = 20\nvalue = [11, 9]'), Text(0.7142857142857143, 0.46875, 'edad <= 43.5\ngini = 0.444\nsamples = 15\nvalue = [10, 5]'), Text(0.6984126984126984, 0.40625, 'gini = 0.375\nsamples = 4\nvalue = [1, 3]'), Text(0.7301587301587301, 0.40625, 'edad <= 44.5\ngini = 0.298\nsamples = 11\nvalue = [9, 2]'), Text(0.7142857142857143, 0.34375, 'gini = 0.444\nsamples = 6\nvalue = [4, 2]'), Text(0.746031746031746, 0.34375, 'gini = 0.0\nsamples = 5\nvalue = [5, 0]'), Text(0.746031746031746, 0.46875, 'gini = 0.32\nsamples = 5\nvalue = [1, 4]'), Text(0.7619047619047619, 0.53125, 'gini = 0.219\nsamples = 24\nvalue = [21, 3]'), Text(0.8095238095238095, 0.59375, 'edad <= 51.0\ngini = 0.48\nsamples = 15\nvalue = [6, 9]'), Text(0.7936507936507936, 0.53125, 'edad <= 49.5\ngini = 0.219\nsamples = 8\nvalue = [1, 7]'), Text(0.7777777777777778, 0.46875, 'gini = 0.278\nsamples = 6\nvalue = [1, 5]'), Text(0.8095238095238095, 0.46875, 'gini = 0.0\nsamples = 2\nvalue = [0, 2]'), Text(0.8253968253968254, 0.53125, 'gini = 0.408\nsamples = 7\nvalue = [5, 2]'), Text(0.8095238095238095, 0.65625, 'gini = 0.0\nsamples = 29\nvalue = [29, 0]'), Text(0.8571428571428571, 0.71875, 'edad <= 53.5\ngini = 0.141\nsamples = 118\nvalue = [109, 9]'), Text(0.8412698412698413, 0.65625, 'gini = 0.0\nsamples = 22\nvalue = [22, 0]'), Text(0.873015873015873, 0.65625, 'edad <= 54.5\ngini = 0.17\nsamples = 96\nvalue = [87, 9]'), Text(0.8571428571428571, 0.59375, 'gini = 0.208\nsamples = 34\nvalue = [30, 4]'), Text(0.8888888888888888, 0.59375, 'edad <= 55.5\ngini = 0.148\nsamples = 62\nvalue = [57, 5]'), Text(0.873015873015873, 0.53125, 'gini = 0.08\nsamples = 24\nvalue = [23, 1]'), Text(0.9047619047619048, 0.53125, 'gini = 0.188\nsamples = 38\nvalue = [34, 4]'), Text(0.9365079365079365, 0.78125, 'edad <= 58.0\ngini = 0.474\nsamples = 57\nvalue = [35, 22]'), Text(0.9206349206349206, 0.71875, 'gini = 0.332\nsamples = 19\nvalue = [4, 15]'), Text(0.9523809523809523, 0.71875, 'edad <= 61.5\ngini = 0.301\nsamples = 38\nvalue = [31, 7]'), Text(0.9365079365079365, 0.65625, 'gini = 0.0\nsamples = 23\nvalue = [23, 0]'), Text(0.9682539682539683, 0.65625, 'edad <= 64.5\ngini = 0.498\nsamples = 15\nvalue = [8, 7]'), Text(0.9523809523809523, 0.59375, 'edad <= 63.5\ngini = 0.497\nsamples = 13\nvalue = [6, 7]'), Text(0.9365079365079365, 0.53125, 'edad <= 62.5\ngini = 0.494\nsamples = 9\nvalue = [5, 4]'), Text(0.9206349206349206, 0.46875, 'gini = 0.49\nsamples = 7\nvalue = [3, 4]'), Text(0.9523809523809523, 0.46875, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]'), Text(0.9682539682539683, 0.53125, 'gini = 0.375\nsamples = 4\nvalue = [1, 3]'), Text(0.9841269841269841, 0.59375, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]')]
El árbol generado cuenta con los siguientes datos:
Su profundidad (longitud vertical), es de 15.
Cuenta con 78 hojas.
Esto nos ayuda a dimensionar que es un árbol complejo respectivo al tamaño del dataset.
print(tree_smote.tree_.max_depth)
15
print(tree_smote.get_n_leaves())
78
from sklearn.metrics import accuracy_score, f1_score
yhat0 = tree_smote.predict(X_test)
acc0 = accuracy_score(y_test, yhat0)
f10 = f1_score(y_test, yhat0, average='weighted')
print("Accuracy inicial: ", acc0)
print("F1-score inicial: ", f10)
Accuracy inicial: 0.8125 F1-score inicial: 0.8179125376992675
El Accuracy del modelo es bueno, obtenemos un valor de 0.81, por lo que se puede concluir que bajo el árbol original se acierta la clasificación un 81 - 3 cifras menos que el árbol de decisión sin balancear.
El F1-score del modelo también es bueno, obtenemos el mismo valor de 0.81 (81%), siendo 2 cifras menos que el árbol de decisión sin balancear.
# Matriz de confusión - Árbol inicial.
from sklearn.metrics import confusion_matrix
conf_m = confusion_matrix(y_test, yhat0)
sns.heatmap(conf_m, annot = True, fmt = "d", cmap = "Blues", cbar = False, square = True)
plt.ylabel('y_true')
plt.xlabel('y_pred')
plt.title('Matriz de Confusión - Árbol Inicial.')
plt.show()
Matriz de confusión.
25 + 92 = 117 aciertos.
Porcentaje de aciertos → 117/144 = 0.81 → 81%.
17 + 10 = 27 errores.
144 datos en total.
Árbol de Decisión Podado by Default.
Vamos a comenzar con los árboles podados mediante la creación con StratifiedKFold un sistema de separación de datos con validación cruzada de 4 folds, seguido de un iterador que recorre todos los valores posibles de alpha hasta encontrar el adecuado.
- Alpha es un coeficiente que ayuda para optimizar búsquedas.
from sklearn.model_selection import cross_val_score, StratifiedKFold
skf = StratifiedKFold(n_splits = 4)
ccp = np.linspace(0.001, 0.2, 250)
cv_scores = []
for alpha in ccp:
pruned_tree = DTC(ccp_alpha = alpha, class_weight='balanced')
cv_scores.append(np.mean(cross_val_score(pruned_tree, X_train, y_train, cv = skf, scoring = 'f1')))
Imprimimos el valor de alpha para ver cuánto nos dió, este será el que se trabajará para la poda.
alpha = ccp[np.argmax(cv_scores)]
print("Best alpha: ", alpha)
Best alpha: 0.001
Visualizamos el árbol podado:
pruned_tree = DTC(ccp_alpha = alpha).fit(X_train, y_train)
plot_tree(pruned_tree, filled = True, feature_names = X_train.columns)
[Text(0.4642857142857143, 0.95, 'institucion_unidad_medica_num <= 0.5\ngini = 0.364\nsamples = 576\nvalue = [138, 438]'), Text(0.25, 0.85, 'sexo_num <= 0.5\ngini = 0.144\nsamples = 308\nvalue = [24, 284]'), Text(0.35714285714285715, 0.8999999999999999, 'True '), Text(0.19047619047619047, 0.75, 'edad <= 42.5\ngini = 0.206\nsamples = 137\nvalue = [16, 121]'), Text(0.14285714285714285, 0.65, 'edad <= 41.5\ngini = 0.287\nsamples = 69\nvalue = [12, 57]'), Text(0.11904761904761904, 0.55, 'edad <= 27.5\ngini = 0.216\nsamples = 65\nvalue = [8, 57]'), Text(0.07142857142857142, 0.45, 'edad <= 23.5\ngini = 0.053\nsamples = 37\nvalue = [1, 36]'), Text(0.047619047619047616, 0.35, 'edad <= 21.5\ngini = 0.105\nsamples = 18\nvalue = [1, 17]'), Text(0.023809523809523808, 0.25, 'gini = 0.0\nsamples = 17\nvalue = [0, 17]'), Text(0.07142857142857142, 0.25, 'gini = 0.0\nsamples = 1\nvalue = [1, 0]'), Text(0.09523809523809523, 0.35, 'gini = 0.0\nsamples = 19\nvalue = [0, 19]'), Text(0.16666666666666666, 0.45, 'edad <= 29.0\ngini = 0.375\nsamples = 28\nvalue = [7, 21]'), Text(0.14285714285714285, 0.35, 'gini = 0.49\nsamples = 7\nvalue = [4, 3]'), Text(0.19047619047619047, 0.35, 'gini = 0.245\nsamples = 21\nvalue = [3, 18]'), Text(0.16666666666666666, 0.55, 'gini = 0.0\nsamples = 4\nvalue = [4, 0]'), Text(0.23809523809523808, 0.65, 'edad <= 53.5\ngini = 0.111\nsamples = 68\nvalue = [4, 64]'), Text(0.21428571428571427, 0.55, 'gini = 0.042\nsamples = 47\nvalue = [1, 46]'), Text(0.2619047619047619, 0.55, 'edad <= 54.5\ngini = 0.245\nsamples = 21\nvalue = [3, 18]'), Text(0.23809523809523808, 0.45, 'gini = 0.5\nsamples = 4\nvalue = [2, 2]'), Text(0.2857142857142857, 0.45, 'edad <= 58.5\ngini = 0.111\nsamples = 17\nvalue = [1, 16]'), Text(0.2619047619047619, 0.35, 'gini = 0.0\nsamples = 13\nvalue = [0, 13]'), Text(0.30952380952380953, 0.35, 'edad <= 60.5\ngini = 0.375\nsamples = 4\nvalue = [1, 3]'), Text(0.2857142857142857, 0.25, 'gini = 0.0\nsamples = 1\nvalue = [1, 0]'), Text(0.3333333333333333, 0.25, 'gini = 0.0\nsamples = 3\nvalue = [0, 3]'), Text(0.30952380952380953, 0.75, 'edad <= 46.0\ngini = 0.089\nsamples = 171\nvalue = [8, 163]'), Text(0.2857142857142857, 0.65, 'gini = 0.058\nsamples = 133\nvalue = [4, 129]'), Text(0.3333333333333333, 0.65, 'edad <= 48.0\ngini = 0.188\nsamples = 38\nvalue = [4, 34]'), Text(0.30952380952380953, 0.55, 'gini = 0.494\nsamples = 9\nvalue = [4, 5]'), Text(0.35714285714285715, 0.55, 'gini = 0.0\nsamples = 29\nvalue = [0, 29]'), Text(0.6785714285714286, 0.85, 'edad <= 40.5\ngini = 0.489\nsamples = 268\nvalue = [114, 154]'), Text(0.5714285714285714, 0.8999999999999999, ' False'), Text(0.5119047619047619, 0.75, 'sexo_num <= 0.5\ngini = 0.426\nsamples = 143\nvalue = [44, 99]'), Text(0.4523809523809524, 0.65, 'edad <= 21.5\ngini = 0.461\nsamples = 114\nvalue = [41, 73]'), Text(0.40476190476190477, 0.55, 'edad <= 19.5\ngini = 0.298\nsamples = 22\nvalue = [4, 18]'), Text(0.38095238095238093, 0.45, 'edad <= 18.5\ngini = 0.444\nsamples = 3\nvalue = [2, 1]'), Text(0.35714285714285715, 0.35, 'gini = 0.0\nsamples = 1\nvalue = [0, 1]'), Text(0.40476190476190477, 0.35, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]'), Text(0.42857142857142855, 0.45, 'gini = 0.188\nsamples = 19\nvalue = [2, 17]'), Text(0.5, 0.55, 'edad <= 23.5\ngini = 0.481\nsamples = 92\nvalue = [37, 55]'), Text(0.47619047619047616, 0.45, 'gini = 0.48\nsamples = 15\nvalue = [9, 6]'), Text(0.5238095238095238, 0.45, 'edad <= 25.5\ngini = 0.463\nsamples = 77\nvalue = [28, 49]'), Text(0.5, 0.35, 'gini = 0.32\nsamples = 20\nvalue = [4, 16]'), Text(0.5476190476190477, 0.35, 'edad <= 29.5\ngini = 0.488\nsamples = 57\nvalue = [24, 33]'), Text(0.5238095238095238, 0.25, 'gini = 0.219\nsamples = 8\nvalue = [7, 1]'), Text(0.5714285714285714, 0.25, 'gini = 0.453\nsamples = 49\nvalue = [17.0, 32.0]'), Text(0.5714285714285714, 0.65, 'edad <= 19.0\ngini = 0.185\nsamples = 29\nvalue = [3, 26]'), Text(0.5476190476190477, 0.55, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]'), Text(0.5952380952380952, 0.55, 'edad <= 32.0\ngini = 0.071\nsamples = 27\nvalue = [1, 26]'), Text(0.5714285714285714, 0.45, 'gini = 0.0\nsamples = 17\nvalue = [0, 17]'), Text(0.6190476190476191, 0.45, 'edad <= 34.0\ngini = 0.18\nsamples = 10\nvalue = [1, 9]'), Text(0.5952380952380952, 0.35, 'gini = 0.0\nsamples = 1\nvalue = [1, 0]'), Text(0.6428571428571429, 0.35, 'gini = 0.0\nsamples = 9\nvalue = [0, 9]'), Text(0.8452380952380952, 0.75, 'edad <= 56.5\ngini = 0.493\nsamples = 125\nvalue = [70, 55]'), Text(0.7857142857142857, 0.65, 'edad <= 52.5\ngini = 0.462\nsamples = 91\nvalue = [58.0, 33.0]'), Text(0.7380952380952381, 0.55, 'sexo_num <= 0.5\ngini = 0.5\nsamples = 47\nvalue = [23.0, 24.0]'), Text(0.7142857142857143, 0.45, 'edad <= 48.5\ngini = 0.465\nsamples = 38\nvalue = [14, 24]'), Text(0.6904761904761905, 0.35, 'edad <= 43.5\ngini = 0.494\nsamples = 27\nvalue = [12, 15]'), Text(0.6309523809523809, 0.25, 'edad <= 41.5\ngini = 0.444\nsamples = 9\nvalue = [3, 6]'), Text(0.6071428571428571, 0.15, 'gini = 0.444\nsamples = 3\nvalue = [2, 1]'), Text(0.6547619047619048, 0.15, 'gini = 0.278\nsamples = 6\nvalue = [1, 5]'), Text(0.75, 0.25, 'edad <= 45.5\ngini = 0.5\nsamples = 18\nvalue = [9, 9]'), Text(0.7023809523809523, 0.15, 'edad <= 44.5\ngini = 0.444\nsamples = 6\nvalue = [4, 2]'), Text(0.6785714285714286, 0.05, 'gini = 0.5\nsamples = 4\nvalue = [2, 2]'), Text(0.7261904761904762, 0.05, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]'), Text(0.7976190476190477, 0.15, 'edad <= 47.0\ngini = 0.486\nsamples = 12\nvalue = [5, 7]'), Text(0.7738095238095238, 0.05, 'gini = 0.32\nsamples = 5\nvalue = [1, 4]'), Text(0.8214285714285714, 0.05, 'gini = 0.49\nsamples = 7\nvalue = [4, 3]'), Text(0.7380952380952381, 0.35, 'gini = 0.298\nsamples = 11\nvalue = [2, 9]'), Text(0.7619047619047619, 0.45, 'gini = 0.0\nsamples = 9\nvalue = [9, 0]'), Text(0.8333333333333334, 0.55, 'edad <= 53.5\ngini = 0.325\nsamples = 44\nvalue = [35, 9]'), Text(0.8095238095238095, 0.45, 'gini = 0.0\nsamples = 6\nvalue = [6, 0]'), Text(0.8571428571428571, 0.45, 'gini = 0.361\nsamples = 38\nvalue = [29, 9]'), Text(0.9047619047619048, 0.65, 'edad <= 58.0\ngini = 0.457\nsamples = 34\nvalue = [12, 22]'), Text(0.8809523809523809, 0.55, 'gini = 0.278\nsamples = 18\nvalue = [3, 15]'), Text(0.9285714285714286, 0.55, 'edad <= 61.0\ngini = 0.492\nsamples = 16\nvalue = [9, 7]'), Text(0.9047619047619048, 0.45, 'gini = 0.0\nsamples = 6\nvalue = [6, 0]'), Text(0.9523809523809523, 0.45, 'edad <= 64.5\ngini = 0.42\nsamples = 10\nvalue = [3, 7]'), Text(0.9285714285714286, 0.35, 'edad <= 62.5\ngini = 0.219\nsamples = 8\nvalue = [1, 7]'), Text(0.9047619047619048, 0.25, 'gini = 0.0\nsamples = 4\nvalue = [0, 4]'), Text(0.9523809523809523, 0.25, 'edad <= 63.5\ngini = 0.375\nsamples = 4\nvalue = [1, 3]'), Text(0.9285714285714286, 0.15, 'gini = 0.0\nsamples = 1\nvalue = [1, 0]'), Text(0.9761904761904762, 0.15, 'gini = 0.0\nsamples = 3\nvalue = [0, 3]'), Text(0.9761904761904762, 0.35, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]')]
El árbol generado cuenta con los siguientes datos:
Su profundidad (longitud vertical), es de 9.
Cuenta con 41 hojas.
print(pruned_tree.tree_.max_depth)
9
print(pruned_tree.get_n_leaves())
41
yhat_p = pruned_tree.predict(X_test)
acc_p = accuracy_score(y_test, yhat_p)
f1_p = f1_score(y_test, yhat_p, average = 'weighted')
print("Accuracy final: ", acc_p)
print("F1-score final: ", f1_p)
Accuracy final: 0.8611111111111112 F1-score final: 0.8546087480572208
El Accuracy del modelo podado es bueno, obtenemos un valor que se puede concluir que bajo el árbol podado se acierta la clasificación. La poda reduce la complejidad del árbol sin sacrificar significativamente el desempeño.
El F1-score del modelo también es bueno; este resultado puede presentar cierto sesgo hacia la clase mayoritaria dada la ausencia de balanceo, sin embargo, es comparable al árbol inicial desbalanceado.
# Matriz de confusión - Árbol inicial
from sklearn.metrics import confusion_matrix
conf_m = confusion_matrix(y_test, yhat_p)
sns.heatmap(conf_m, annot=True, fmt="d", cmap="Blues", cbar=False, square=True)
plt.ylabel('y_true')
plt.xlabel('y_pred')
plt.title('Matriz de Confusión - Árbol Podado.')
plt.show()
Matriz de confusión.
21 + 103 = 124 aciertos.
Porcentaje de aciertos → 124/144 = 0.81 → 86%.
6 + 14 = 20 errores.
144 datos en total.
Árbol de Decisión Podado SMOTE.
from sklearn.model_selection import cross_val_score, StratifiedKFold
skf = StratifiedKFold(n_splits = 4)
ccp = np.linspace(0.001, 0.2, 250)
cv_scores = []
for alpha in ccp:
pruned_tree = DTC(ccp_alpha = alpha, class_weight='balanced')
cv_scores.append(np.mean(cross_val_score(pruned_tree, X_train_smote, y_train_smote, cv = skf, scoring = 'f1')))
alpha = ccp[np.argmax(cv_scores)]
print("Best alpha: ", alpha)
Best alpha: 0.001
pruned_tree = DTC(ccp_alpha = alpha).fit(X_train_smote, y_train_smote)
plot_tree(pruned_tree, filled = True, feature_names = X_train_smote.columns)
[Text(0.4253472222222222, 0.9583333333333334, 'institucion_unidad_medica_num <= 0.5\ngini = 0.5\nsamples = 876\nvalue = [438, 438]'), Text(0.19791666666666666, 0.875, 'sexo_num <= 0.5\ngini = 0.38\nsamples = 381\nvalue = [97, 284]'), Text(0.3116319444444444, 0.9166666666666667, 'True '), Text(0.11805555555555555, 0.7916666666666666, 'edad <= 21.5\ngini = 0.484\nsamples = 205\nvalue = [84, 121]'), Text(0.09027777777777778, 0.7083333333333334, 'gini = 0.0\nsamples = 17\nvalue = [0, 17]'), Text(0.14583333333333334, 0.7083333333333334, 'edad <= 49.5\ngini = 0.494\nsamples = 188\nvalue = [84, 104]'), Text(0.06944444444444445, 0.625, 'edad <= 24.5\ngini = 0.498\nsamples = 136\nvalue = [72, 64]'), Text(0.041666666666666664, 0.5416666666666666, 'gini = 0.0\nsamples = 7\nvalue = [7, 0]'), Text(0.09722222222222222, 0.5416666666666666, 'edad <= 27.5\ngini = 0.5\nsamples = 129\nvalue = [65, 64]'), Text(0.06944444444444445, 0.4583333333333333, 'gini = 0.172\nsamples = 21\nvalue = [2, 19]'), Text(0.125, 0.4583333333333333, 'edad <= 29.0\ngini = 0.486\nsamples = 108\nvalue = [63.0, 45.0]'), Text(0.09722222222222222, 0.375, 'gini = 0.255\nsamples = 20\nvalue = [17, 3]'), Text(0.1527777777777778, 0.375, 'edad <= 34.0\ngini = 0.499\nsamples = 88\nvalue = [46, 42]'), Text(0.08333333333333333, 0.2916666666666667, 'edad <= 32.5\ngini = 0.32\nsamples = 10\nvalue = [2, 8]'), Text(0.05555555555555555, 0.20833333333333334, 'edad <= 31.0\ngini = 0.5\nsamples = 4\nvalue = [2, 2]'), Text(0.027777777777777776, 0.125, 'gini = 0.0\nsamples = 2\nvalue = [0, 2]'), Text(0.08333333333333333, 0.125, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]'), Text(0.1111111111111111, 0.20833333333333334, 'gini = 0.0\nsamples = 6\nvalue = [0, 6]'), Text(0.2222222222222222, 0.2916666666666667, 'edad <= 42.5\ngini = 0.492\nsamples = 78\nvalue = [44, 34]'), Text(0.16666666666666666, 0.20833333333333334, 'edad <= 41.5\ngini = 0.401\nsamples = 36\nvalue = [26, 10]'), Text(0.1388888888888889, 0.125, 'gini = 0.473\nsamples = 26\nvalue = [16, 10]'), Text(0.19444444444444445, 0.125, 'gini = 0.0\nsamples = 10\nvalue = [10, 0]'), Text(0.2777777777777778, 0.20833333333333334, 'edad <= 45.0\ngini = 0.49\nsamples = 42\nvalue = [18, 24]'), Text(0.25, 0.125, 'gini = 0.0\nsamples = 12\nvalue = [0, 12]'), Text(0.3055555555555556, 0.125, 'edad <= 48.5\ngini = 0.48\nsamples = 30\nvalue = [18, 12]'), Text(0.2777777777777778, 0.041666666666666664, 'gini = 0.494\nsamples = 27\nvalue = [15, 12]'), Text(0.3333333333333333, 0.041666666666666664, 'gini = 0.0\nsamples = 3\nvalue = [3, 0]'), Text(0.2222222222222222, 0.625, 'edad <= 53.5\ngini = 0.355\nsamples = 52\nvalue = [12, 40]'), Text(0.19444444444444445, 0.5416666666666666, 'gini = 0.083\nsamples = 23\nvalue = [1, 22]'), Text(0.25, 0.5416666666666666, 'edad <= 54.5\ngini = 0.471\nsamples = 29\nvalue = [11, 18]'), Text(0.2222222222222222, 0.4583333333333333, 'gini = 0.298\nsamples = 11\nvalue = [9, 2]'), Text(0.2777777777777778, 0.4583333333333333, 'edad <= 58.5\ngini = 0.198\nsamples = 18\nvalue = [2, 16]'), Text(0.25, 0.375, 'gini = 0.0\nsamples = 13\nvalue = [0, 13]'), Text(0.3055555555555556, 0.375, 'edad <= 60.5\ngini = 0.48\nsamples = 5\nvalue = [2, 3]'), Text(0.2777777777777778, 0.2916666666666667, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]'), Text(0.3333333333333333, 0.2916666666666667, 'gini = 0.0\nsamples = 3\nvalue = [0, 3]'), Text(0.2777777777777778, 0.7916666666666666, 'edad <= 46.0\ngini = 0.137\nsamples = 176\nvalue = [13, 163]'), Text(0.25, 0.7083333333333334, 'gini = 0.058\nsamples = 133\nvalue = [4, 129]'), Text(0.3055555555555556, 0.7083333333333334, 'edad <= 48.0\ngini = 0.331\nsamples = 43\nvalue = [9, 34]'), Text(0.2777777777777778, 0.625, 'gini = 0.459\nsamples = 14\nvalue = [9, 5]'), Text(0.3333333333333333, 0.625, 'gini = 0.0\nsamples = 29\nvalue = [0, 29]'), Text(0.6527777777777778, 0.875, 'edad <= 42.5\ngini = 0.429\nsamples = 495\nvalue = [341, 154]'), Text(0.5390625, 0.9166666666666667, ' False'), Text(0.4722222222222222, 0.7916666666666666, 'sexo_num <= 0.5\ngini = 0.493\nsamples = 232\nvalue = [130, 102]'), Text(0.4166666666666667, 0.7083333333333334, 'edad <= 19.5\ngini = 0.468\nsamples = 203\nvalue = [127, 76]'), Text(0.3888888888888889, 0.625, 'gini = 0.124\nsamples = 15\nvalue = [14, 1]'), Text(0.4444444444444444, 0.625, 'edad <= 21.5\ngini = 0.48\nsamples = 188\nvalue = [113, 75]'), Text(0.4166666666666667, 0.5416666666666666, 'gini = 0.188\nsamples = 19\nvalue = [2, 17]'), Text(0.4722222222222222, 0.5416666666666666, 'edad <= 23.5\ngini = 0.451\nsamples = 169\nvalue = [111.0, 58.0]'), Text(0.4444444444444444, 0.4583333333333333, 'gini = 0.26\nsamples = 39\nvalue = [33, 6]'), Text(0.5, 0.4583333333333333, 'edad <= 25.5\ngini = 0.48\nsamples = 130\nvalue = [78, 52]'), Text(0.4722222222222222, 0.375, 'gini = 0.473\nsamples = 26\nvalue = [10, 16]'), Text(0.5277777777777778, 0.375, 'edad <= 28.5\ngini = 0.453\nsamples = 104\nvalue = [68, 36]'), Text(0.5, 0.2916666666666667, 'gini = 0.0\nsamples = 12\nvalue = [12, 0]'), Text(0.5555555555555556, 0.2916666666666667, 'gini = 0.476\nsamples = 92\nvalue = [56, 36]'), Text(0.5277777777777778, 0.7083333333333334, 'edad <= 19.0\ngini = 0.185\nsamples = 29\nvalue = [3, 26]'), Text(0.5, 0.625, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]'), Text(0.5555555555555556, 0.625, 'edad <= 32.0\ngini = 0.071\nsamples = 27\nvalue = [1, 26]'), Text(0.5277777777777778, 0.5416666666666666, 'gini = 0.0\nsamples = 17\nvalue = [0, 17]'), Text(0.5833333333333334, 0.5416666666666666, 'edad <= 34.0\ngini = 0.18\nsamples = 10\nvalue = [1, 9]'), Text(0.5555555555555556, 0.4583333333333333, 'gini = 0.0\nsamples = 1\nvalue = [1, 0]'), Text(0.6111111111111112, 0.4583333333333333, 'gini = 0.0\nsamples = 9\nvalue = [0, 9]'), Text(0.8333333333333334, 0.7916666666666666, 'edad <= 56.5\ngini = 0.317\nsamples = 263\nvalue = [211, 52]'), Text(0.7777777777777778, 0.7083333333333334, 'edad <= 52.5\ngini = 0.249\nsamples = 206\nvalue = [176.0, 30.0]'), Text(0.75, 0.625, 'sexo_num <= 0.5\ngini = 0.363\nsamples = 88\nvalue = [67, 21]'), Text(0.7222222222222222, 0.5416666666666666, 'edad <= 48.5\ngini = 0.458\nsamples = 59\nvalue = [38, 21]'), Text(0.6666666666666666, 0.4583333333333333, 'edad <= 47.0\ngini = 0.397\nsamples = 44\nvalue = [32, 12]'), Text(0.6388888888888888, 0.375, 'edad <= 45.5\ngini = 0.495\nsamples = 20\nvalue = [11, 9]'), Text(0.6111111111111112, 0.2916666666666667, 'edad <= 43.5\ngini = 0.444\nsamples = 15\nvalue = [10, 5]'), Text(0.5833333333333334, 0.20833333333333334, 'gini = 0.375\nsamples = 4\nvalue = [1, 3]'), Text(0.6388888888888888, 0.20833333333333334, 'gini = 0.298\nsamples = 11\nvalue = [9, 2]'), Text(0.6666666666666666, 0.2916666666666667, 'gini = 0.32\nsamples = 5\nvalue = [1, 4]'), Text(0.6944444444444444, 0.375, 'gini = 0.219\nsamples = 24\nvalue = [21, 3]'), Text(0.7777777777777778, 0.4583333333333333, 'edad <= 51.0\ngini = 0.48\nsamples = 15\nvalue = [6, 9]'), Text(0.75, 0.375, 'gini = 0.219\nsamples = 8\nvalue = [1, 7]'), Text(0.8055555555555556, 0.375, 'gini = 0.408\nsamples = 7\nvalue = [5, 2]'), Text(0.7777777777777778, 0.5416666666666666, 'gini = 0.0\nsamples = 29\nvalue = [29, 0]'), Text(0.8055555555555556, 0.625, 'gini = 0.141\nsamples = 118\nvalue = [109, 9]'), Text(0.8888888888888888, 0.7083333333333334, 'edad <= 58.0\ngini = 0.474\nsamples = 57\nvalue = [35, 22]'), Text(0.8611111111111112, 0.625, 'gini = 0.332\nsamples = 19\nvalue = [4, 15]'), Text(0.9166666666666666, 0.625, 'edad <= 61.5\ngini = 0.301\nsamples = 38\nvalue = [31, 7]'), Text(0.8888888888888888, 0.5416666666666666, 'gini = 0.0\nsamples = 23\nvalue = [23, 0]'), Text(0.9444444444444444, 0.5416666666666666, 'edad <= 64.5\ngini = 0.498\nsamples = 15\nvalue = [8, 7]'), Text(0.9166666666666666, 0.4583333333333333, 'gini = 0.497\nsamples = 13\nvalue = [6, 7]'), Text(0.9722222222222222, 0.4583333333333333, 'gini = 0.0\nsamples = 2\nvalue = [2, 0]')]
De este otro árbol generado, también vamos a abordar estos dos datos que arroja el mismo:
Su profundidad (longitud vertical), es de 11.
Cuenta con 42 hojas.
print(pruned_tree.tree_.max_depth)
11
print(pruned_tree.get_n_leaves())
42
yhat_p = pruned_tree.predict(X_test)
acc_p = accuracy_score(y_test, yhat_p)
f1_p = f1_score(y_test, yhat_p, average = 'weighted')
print("Accuracy final: ", acc_p)
print("F1-score final: ", f1_p)
Accuracy final: 0.7916666666666666 F1-score final: 0.8018790849673203
El Accuracy del modelo podado con SMOTE es bueno, obtenemos un valor de 0.79, por lo que se puede concluir que bajo el árbol podado balanceado se acierta la clasificación un 79%.
El F1-score del modelo también es bueno, obtenemos un valor de 0.80 y esto viene debido al SMOTE — no se tendrá un sesgo hacia una clase en específico, confirmando lo dicho a un 80%. La leve diferencia con el árbol inicial SMOTE confirma que la poda no degradó el desempeño de forma importante.
# Matriz de confusión - Árbol inicial
from sklearn.metrics import confusion_matrix
conf_m = confusion_matrix(y_test, yhat_p)
sns.heatmap(conf_m, annot=True, fmt="d", cmap="Blues", cbar=False, square=True)
plt.ylabel('y_true')
plt.xlabel('y_pred')
plt.title('Matriz de Confusión - Árbol Podado.')
plt.show()
Matriz de confusión.
27 + 87 = 114 aciertos.
Porcentaje de aciertos → 114/144 = 0.79 → 79%.
22 + 8 = 30 errores.
144 datos en total.
Métricas.
Las métricas de desempeño evaluadas para estos modelos fueron:
Accuracy: aciertos totales.
F1-score: promedio ponderado de las clases.
Random Forest Classifier.¶
Random Forest Classifier - by Default.
Comenzamos con el modelo de Random Forest Classifier donde no se está implementando una técnica de balanceo de clases para ver cómo se comporta el algoritmo con el desbalance que existe y que se mencionó anteriormente.
Definimos variables:
# Definimos entrada y salida.
X = df2[['edad', 'sexo_num', 'institucion_unidad_medica_num']]
y = df2['descripcion_grupo_enfermedad_num']
print(X.shape)
print(y.shape)
(720, 3) (720,)
Separamos los datos en entrenamiento y prueba:
# Dividir los datos en train y test.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0, stratify = y)
print(y_train.value_counts())
X_train.shape, X_test.shape, y_train.shape, y_test.shape
descripcion_grupo_enfermedad_num 1 438 0 138 Name: count, dtype: int64
((576, 3), (144, 3), (576,), (144,))
Definimos el modelo de Random Forest Classifier sin balancear con los siguientes hiperparámetros principales:
n_estimators: 100 → valor por default.
criterion: Gini → definir la mejor regla de separación.
max_depth: none → no existe límite para el crecimiento del bosque.
class_weight: none → no se está aplicando ninguna técnica de balanceo.
random_state = 0 → con esta condición, al momento de hacer el bootstrap se asegura que los datos se dividan igual en cada ocasión.
# Definir el modelo by Default.
model1 = RandomForestClassifier(random_state = 0)
# Entrenemos el modelo FS.
model1.fit(X_train, y_train)
RandomForestClassifier(random_state=0)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
Validamos si el modelo pronostica adecuadamente:
# Validar si el modelo pronostica adecuadamente.
y_pred_test = model1.predict(X_test)
print(y_pred_test[0:5])
print(y_test.head())
[1 0 0 1 1] 46382 0 32275 0 7409 1 32248 1 46407 1 Name: descripcion_grupo_enfermedad_num, dtype: int64
3 aciertos de 5.
accuracy_train = model1.score(X_train, y_train)
print('Accuracy train = {:.2f}'.format(accuracy_train))
accuracy_test = model1.score(X_test, y_test)
print('Accuracy test = {:.2f}'.format(accuracy_test))
print('Diferencia = {:.4f}%'.format(np.abs(accuracy_train-accuracy_test)*100))
Accuracy train = 0.88 Accuracy test = 0.86 Diferencia = 2.0833%
Hasta este punto, obtenemos que el modelo tiene un leve sobreajuste.
Sin embargo, su Accuracy de 86% apunta a que sabe generalizar bien entre los positivos reales y negativos reales (1 y 0 respectivamente).
predRF = model1.predict(X_test)
repDT = classification_report(y_test, predRF)
print(repDT)
from imblearn.metrics import geometric_mean_score
print('G-mean =', geometric_mean_score(y_test, predRF))
disp = ConfusionMatrixDisplay.from_predictions(y_test, predRF, cmap = plt.cm.Blues)
# 0: Factores influyendo en el estado de salud. 1: Trastorno mental y del comportamiento.
precision recall f1-score support
0 0.78 0.60 0.68 35
1 0.88 0.94 0.91 109
accuracy 0.86 144
macro avg 0.83 0.77 0.79 144
weighted avg 0.86 0.86 0.85 144
G-mean = 0.7529757479920719
Matriz de confusión.
21 + 103 = 124 aciertos.
Porcentaje de aciertos → 124 / 144 = 0.86 → 86%.
6 + 14 = 20 errores.
144 datos en total.
Reporte de clasificación.
35 datos de prueba de Factores influyendo en el estado de salud.
- 60% de positivos encontrados.
- 78% fueron clasificados correctamente.
109 datos de prueba de Trastorno mental y del comportamiento.
- 94% de positivos encontrados.
- 88% fueron clasificados correctamente.
F1-Score: promedio ponderado de las clases.
- Factores influyendo en el estado de salud: 68%.
- Trastorno mental y del comportamiento: 91%.
Porcentaje de proporción a encontrar todas las clasificaciones de este estudio (G-mean): 75%.
Este porcentaje de proporción es el que queremos mejorar lo más que se pueda con la técnica de balanceo.
Random Forest Classifier - SMOTE.
Procedemos a realizar el Random Forest Classifier con la técnica de balanceo de SMOTE, esta se escogió ya que fue la técnica con un mejor desempeño — esto es mejor que SMOTEENN que va a eliminar datos, escenario que en este caso no es factible ya que se tienen relativamente pocos datos con el dataset construido.
from imblearn.over_sampling import SMOTE
# Definir la técnica.
smote = SMOTE(random_state = 0)
# Aplicamos.
X_smote, y_smote = smote.fit_resample(X, y)
# Comprobar si funicona.
print('Tamaño de X antes de SMOTE:', X.shape)
print('Tamaño de X después de SMOTE:', X_smote.shape)
print('Balance de clases con SMOTE:', y_smote.value_counts())
print('Nuestras clases están balanceadas.')
Tamaño de X antes de SMOTE: (720, 3) Tamaño de X después de SMOTE: (1094, 3) Balance de clases con SMOTE: descripcion_grupo_enfermedad_num 1 547 0 547 Name: count, dtype: int64 Nuestras clases están balanceadas.
Separamos los datos en entrenamiento y prueba:
# Dividir los tratos en train y test.
X_train, X_test, y_train, y_test = train_test_split(X_smote, y_smote, test_size = 0.2, random_state = 0, stratify = y_smote)
print(y_train.value_counts())
X_train.shape, X_test.shape, y_train.shape, y_test.shape
# No dividir de manera que se desbalanceen los datos -> cuidado con el random_state).
descripcion_grupo_enfermedad_num 1 438 0 437 Name: count, dtype: int64
((875, 3), (219, 3), (875,), (219,))
Definimos el modelo de Random Forest Classifier balanceado con los siguientes hiperparámetros principales:
n_estimators: 100 → valor por default.
criterion: Gini → definir la mejor regla de separación.
max_depth: none → no existe límite para el crecimiento del bosque.
class_weight: none → el balanceo viene de la librería SMOTE, no del hiperparámetro.
random_state = 0 → con esta condición, al momento de hacer el bootstrap se asegura que los datos se dividan igual en cada ocasión.
Son los mismos al Random Forest Classifier by Default.
# Definir el modelo balanceado.
model2 = RandomForestClassifier(random_state = 0)
# Entrenemos el modelo FS.
model2.fit(X_smote, y_smote)
RandomForestClassifier(random_state=0)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
Validamos si el modelo pronostica adecuadamente:
# Validar si el modelo pronostica adecuadamente.
y_pred_test = model2.predict(X_test)
print(y_pred_test[0:5])
print(y_test.head())
[0 1 0 0 0] 944 0 199 1 1034 0 405 0 727 0 Name: descripcion_grupo_enfermedad_num, dtype: int64
5 aciertos de 5.
accuracy_train = model2.score(X_train, y_train)
print('Accuracy train = {:.2f}'.format(accuracy_train))
accuracy_test = model2.score(X_test, y_test)
print('Accuracy test = {:.2f}'.format(accuracy_test))
print('Diferencia = {:.4f}%'.format(np.abs(accuracy_train-accuracy_test)*100))
Accuracy train = 0.87 Accuracy test = 0.90 Diferencia = 2.7543%
Hasta este punto, obtenemos que el modelo balanceado también tiene un leve sobreajuste.
Sin embargo, su Accuracy de 90% apunta a que sabe generalizar muy bien entre los positivos reales y negativos reales (1 y 0 respectivamente).
predRF = model2.predict(X_test)
repDT = classification_report(y_test, predRF)
print(repDT)
from imblearn.metrics import geometric_mean_score
print('G-mean =', geometric_mean_score(y_test, predRF))
disp = ConfusionMatrixDisplay.from_predictions(y_test, predRF, cmap = plt.cm.Blues)
# 0: Factores influyendo en el estado de salud. 1: Trastorno mental y del comportamiento.
precision recall f1-score support
0 0.89 0.91 0.90 110
1 0.91 0.89 0.90 109
accuracy 0.90 219
macro avg 0.90 0.90 0.90 219
weighted avg 0.90 0.90 0.90 219
G-mean = 0.8994484455794076
Matriz de confusión.
100 + 97 = 197 aciertos.
Porcentaje de aciertos → 197 / 219 = 0.89 → 89%.
12 + 10 = 22 errores.
219 datos en total.
Reporte de clasificación.
110 datos de prueba de Factores influyendo en el estado de salud.
- 91% de positivos encontrados.
- 89% fueron clasificados correctamente.
109 datos de prueba de Trastorno mental y del comportamiento.
- 89% de positivos encontrados.
- 91% fueron clasificados correctamente.
F1-Score: promedio ponderado de las clases.
- Factores influyendo en el estado de salud: 90%.
- Trastorno mental y del comportamiento: 90%.
Porcentaje de proporción a encontrar todas las clasificaciones de este estudio (G-mean): 89%.
Este G-mean de 89% representa una mejora significativa respecto al modelo desbalanceado, gracias a la técnica de SMOTE.
Métricas.
Las métricas de desempeño evaluadas para estos modelos fueron:
Accuracy.
Matriz de confusión.
G-mean.
Precision.
Recall (Sensibilidad).
F1-Score.
Support Vector Machine (SVM).¶
SVM - by Default.
Procederemos a realizar el SVM sin técnica de balanceo para ver cómo se comporta con el desbalance existente.
from sklearn.svm import SVC
# Definimos entrada y salida.
X = df2[['edad', 'sexo_num', 'institucion_unidad_medica_num']]
y = df2['descripcion_grupo_enfermedad_num']
Separamos los datos en entrenamiento y prueba:
# Dividimos en train y test.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0, stratify = y)
print(y_train.value_counts())
X_train.shape, X_test.shape
descripcion_grupo_enfermedad_num 1 438 0 138 Name: count, dtype: int64
((576, 3), (144, 3))
En este punto vale la pena mencionar que se utilizará un escalador de características, siendo en este caso el StandardScaler.
Según Aylin Tokuç (2025), este escalado de características consiste en asignar los valores de las características de un conjunto de datos al mismo rango — es crucial para algoritmos como el SVM ya que este considera las distancias entre observaciones (margen), dicha distancia difiere entre los datos sin escalar a aquellos escalados, aparte de la sensibilidad en las características.
# Escalamos las características (recomendado para SVM).
scaler_svm = StandardScaler()
X_train_svm = scaler_svm.fit_transform(X_train)
X_test_svm = scaler_svm.transform(X_test)
Definimos el modelo de SVM sin balancear con los siguientes hiperparámetros principales:
C: 1 → penalización proporcional al valor absoluto de los coeficientes del modelo.
kernel: rbf → definir la mejor regla de separación.
degree: 3 → grado del kernel.
gamma: scale → coeficiente del kernel.
class_weight: none → no se está aplicando ninguna técnica de balanceo.
# Definir el modelo by Default.
model_svm1 = SVC(kernel = 'rbf', random_state = 0)
# Entrenemos el modelo SVM.
model_svm1.fit(X_train_svm, y_train)
SVC(random_state=0)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
accuracy_train_svm1 = model_svm1.score(X_train_svm, y_train)
print('Accuracy train = {:.2f}'.format(accuracy_train_svm1))
accuracy_test_svm1 = model_svm1.score(X_test_svm, y_test)
print('Accuracy test = {:.2f}'.format(accuracy_test_svm1))
print('Diferencia = {:.4f}%'.format(np.abs(accuracy_train_svm1-accuracy_test_svm1)*100))
Accuracy train = 0.79 Accuracy test = 0.81 Diferencia = 1.9097%
El modelo tiene un pequeño sobreajuste.
Su Accuracy de 81% apunta a que sabe generalizar bien entre los positivos reales y negativos reales, sin embargo, puede aún mejorar.
pred_svm1 = model_svm1.predict(X_test_svm)
rep_svm1 = classification_report(y_test, pred_svm1)
print(rep_svm1)
from imblearn.metrics import geometric_mean_score
print('G-mean =', geometric_mean_score(y_test, pred_svm1))
disp = ConfusionMatrixDisplay.from_predictions(y_test, pred_svm1, cmap = plt.cm.Blues)
# 0: Factores influyendo en el estado de salud. 1: Trastorno mental y del comportamiento.
precision recall f1-score support
0 0.64 0.51 0.57 35
1 0.85 0.91 0.88 109
accuracy 0.81 144
macro avg 0.75 0.71 0.73 144
weighted avg 0.80 0.81 0.81 144
G-mean = 0.6834497338233234
Matriz de confusión.
18 + 99 = 117 aciertos.
Porcentaje de aciertos → 117 / 144 = 0.81 → 81%.
10 + 17 = 27 errores.
144 datos en total.
Reporte de clasificación.
35 datos de prueba de Factores influyendo en el estado de salud.
- 51% de positivos encontrados.
- 64% fueron clasificados correctamente.
109 datos de prueba de Trastorno mental y del comportamiento.
- 91% de positivos encontrados.
- 85% fueron clasificados correctamente.
F1-Score: promedio ponderado de las clases.
- Factores influyendo en el estado de salud: 57%.
- Trastorno mental y del comportamiento: 88%.
Porcentaje de proporción a encontrar todas las clasificaciones de este estudio (G-mean): 68%.
SVM - SMOTE.
Procederemos a realizar el SVM con la técnica de balanceo de SMOTE.
from imblearn.over_sampling import SMOTE
# Aplicamos SMOTE sobre los datos originales.
smote = SMOTE(random_state=0)
X_smote_svm, y_smote_svm = smote.fit_resample(X, y)
print('Tamaño de X antes de SMOTE:', X.shape)
print('Tamaño de X después de SMOTE:', X_smote_svm.shape)
print('Balance de clases con SMOTE:')
print(y_smote_svm.value_counts())
print('Nuestras clases están balanceadas.')
Tamaño de X antes de SMOTE: (720, 3) Tamaño de X después de SMOTE: (1094, 3) Balance de clases con SMOTE: descripcion_grupo_enfermedad_num 1 547 0 547 Name: count, dtype: int64 Nuestras clases están balanceadas.
Separamos los datos en entrenamiento y prueba:
# Dividir los datos balanceados en train y test.
X_train, X_test, y_train, y_test = train_test_split(X_smote_svm, y_smote_svm, test_size=0.2, random_state=0, stratify=y_smote_svm)
print(y_train.value_counts())
X_train.shape, X_test.shape
descripcion_grupo_enfermedad_num 1 438 0 437 Name: count, dtype: int64
((875, 3), (219, 3))
Seguimos con SVM, aunque utilicemos la técnica de balanceo de SMOTE, todavía se aplica el escalador StandardScaler.
# Escalamos las características.
scaler_svm2 = StandardScaler()
X_train_svm2 = scaler_svm2.fit_transform(X_train)
X_test_svm2 = scaler_svm2.transform(X_test)
Definimos el modelo de SVM balanceado con los siguientes hiperparámetros principales:
C: 1 → penalización proporcional al valor absoluto de los coeficientes del modelo.
kernel: rbf → definir la mejor regla de separación.
degree: 3 → grado del kernel.
gamma: scale → coeficiente del kernel.
class_weight: none → el balanceo viene de la librería SMOTE.
# Definir el modelo balanceado.
model_svm2 = SVC(kernel = 'rbf', random_state = 0)
# Entrenemos el modelo SVM.
model_svm2.fit(X_train_svm2, y_train)
SVC(random_state=0)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
# Evaluación del modelo SVM balanceado.
accuracy_train_svm2 = model_svm2.score(X_train_svm2, y_train)
print('Accuracy train = {:.2f}'.format(accuracy_train_svm2))
accuracy_test_svm2 = model_svm2.score(X_test_svm2, y_test)
print('Accuracy test = {:.2f}'.format(accuracy_test_svm2))
print('Diferencia = {:.4f}%'.format(np.abs(accuracy_train_svm2-accuracy_test_svm2)*100))
Accuracy train = 0.75 Accuracy test = 0.75 Diferencia = 0.8282%
El modelo tiene un muy pequeño sobreajuste.
Su Accuracy de 75% apunta a que sabe generalizar entre los positivos reales y negativos reales, sin embargo, puede aún mejorar en gran escala ya que en otros modelos llegó hasta el 90%.
pred_svm2 = model_svm2.predict(X_test_svm2)
rep_svm2 = classification_report(y_test, pred_svm2)
print(rep_svm2)
print('G-mean =', geometric_mean_score(y_test, pred_svm2))
disp = ConfusionMatrixDisplay.from_predictions(y_test, pred_svm2, cmap = plt.cm.Blues)
# 0: Factores influyendo en el estado de salud. 1: Trastorno mental y del comportamiento.
precision recall f1-score support
0 0.73 0.82 0.77 110
1 0.79 0.69 0.74 109
accuracy 0.75 219
macro avg 0.76 0.75 0.75 219
weighted avg 0.76 0.75 0.75 219
G-mean = 0.7503126954482326
Matriz de confusión.
90 + 75 = 165 aciertos.
Porcentaje de aciertos → 165 / 219 = 0.75 → 75%.
34 + 20 = 54 errores.
219 datos en total.
Reporte de clasificación.
110 datos de prueba de Factores influyendo en el estado de salud.
- 82% de positivos encontrados.
- 73% fueron clasificados correctamente.
109 datos de prueba de Trastorno mental y del comportamiento.
- 69% de positivos encontrados.
- 79% fueron clasificados correctamente.
F1-Score: promedio ponderado de las clases.
- Factores influyendo en el estado de salud: 77%.
- Trastorno mental y del comportamiento: 74%.
Porcentaje de proporción a encontrar todas las clasificaciones de este estudio (G-mean): 75%.
Métricas.
Las métricas de desempeño evaluadas para estos modelos fueron:
Accuracy.
Matriz de confusión.
G-mean.
Precision.
Recall (Sensibilidad).
F1-Score.
Red Neuronal.¶
Construcción de Red Neuronal según Persson (2020).
# Activación y perdida.
def relu(z):
return np.maximum(0, z)
def relu_deriv(z):
return (z > 0).astype(float)
def sigmoid(z):
return 1/(1+np.exp(-np.clip(z, -500, 500)))
def binary_cross_entropy(y_true, y_pred):
eps = 1e-8
return -np.mean(y_true*np.log(y_pred+eps)+(1-y_true)*np.log(1 - y_pred+eps))
# Generación de la Red Neuronal.
class RedNeuronal:
"""Red neuronal binaria de una capa oculta construida desde cero con NumPy."""
def __init__(self, n_entrada = 3, n_oculta = 6, lr = 0.01, epochs = 1000, random_state = 0):
self.n_entrada = n_entrada
self.n_oculta = n_oculta
self.lr = lr
self.epochs = epochs
self.random_state = random_state
self.losses = []
def _init_params(self):
rng = np.random.default_rng(self.random_state)
# He initialization (recomendada para ReLU).
self.W1 = rng.standard_normal((self.n_entrada, self.n_oculta))*np.sqrt(2/self.n_entrada)
self.b1 = np.zeros((1, self.n_oculta))
self.W2 = rng.standard_normal((self.n_oculta, 1))*np.sqrt(2/self.n_oculta)
self.b2 = np.zeros((1, 1))
def _forward(self, X):
self.Z1 = X @ self.W1+self.b1 # (n, n_oculta).
self.A1 = relu(self.Z1) # (n, n_oculta).
self.Z2 = self.A1 @ self.W2+self.b2 # (n, 1).
self.A2 = sigmoid(self.Z2) # (n, 1).
return self.A2
def _backward(self, X, y):
n = X.shape[0]
# Gradiente capa de salida.
dZ2 = self.A2-y # (n, 1).
dW2 = (self.A1.T @ dZ2)/n
db2 = np.mean(dZ2, axis = 0, keepdims = True)
# Gradiente capa oculta.
dA1 = dZ2 @ self.W2.T # (n, n_oculta).
dZ1 = dA1*relu_deriv(self.Z1)
dW1 = (X.T @ dZ1)/n
db1 = np.mean(dZ1, axis = 0, keepdims = True)
# Actualizar pesos.
self.W2 -= self.lr*dW2
self.b2 -= self.lr*db2
self.W1 -= self.lr*dW1
self.b1 -= self.lr*db1
def fit(self, X, y):
self._init_params()
y = y.reshape(-1, 1).astype(float)
for epoch in range(self.epochs):
y_pred = self._forward(X)
loss = binary_cross_entropy(y, y_pred)
self.losses.append(loss)
self._backward(X, y)
if (epoch+1)%200 == 0:
print(f'Epoch {epoch+1}/{self.epochs} | Loss: {loss:.4f}')
return self
def predict_proba(self, X):
return self._forward(X).flatten()
def predict(self, X, threshold=0.5):
return (self.predict_proba(X) >= threshold).astype(int)
def score(self, X, y):
return accuracy_score(y, self.predict(X))
def plot_loss(self, title='Curva de pérdida'):
plt.figure(figsize=(7, 3))
plt.plot(self.losses)
plt.xlabel('Época')
plt.ylabel('Binary Cross-Entropy')
plt.title(title)
plt.tight_layout()
plt.show()
print('Clase RedNeuronal definida correctamente.')
Clase RedNeuronal definida correctamente.
Al momento de generar la clase de la Red Neuronal, se utilizarán 6 neuronas, esto debido a que se sigue la regla de 2 * entradas, resultando en:
2 * 3 = 6 → 6 neuronas.
Red Neuronal - by Default.
Procederemos a realizar la Red Neuronal sin técnica de balanceo.
Definimos variables:
# Definimos entrada y salida.
X = df2[['edad', 'sexo_num', 'institucion_unidad_medica_num']]
y = df2['descripcion_grupo_enfermedad_num']
Separamos los datos en entrenamiento y prueba:
# Dividimos en train y test.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0, stratify=y)
print(y_train.value_counts())
X_train.shape, X_test.shape
descripcion_grupo_enfermedad_num 1 438 0 138 Name: count, dtype: int64
((576, 3), (144, 3))
Nuevamente, se utilizará un escalador de características, siendo el mismo de StandardScaler. Esto sucede debido a que la red neuronal repite el patrón de sensibilidad de SVM, solo que en este caso sucede (a diferencia de margen) por el gradiente descendente.
# Escalamos las características (necesario para la red neuronal).
scaler_nn1 = StandardScaler()
X_train_nn1 = scaler_nn1.fit_transform(X_train)
X_test_nn1 = scaler_nn1.transform(X_test)
Los hiperparámetros principales de la red neuronal son los siguientes:
n_entrada: 3 → número de variables independientes (edad, sexo_num, institucion_unidad_medica_num).
n_oculta: 6 → neuronas en la capa oculta, calculadas con la regla 2 × n_entrada = 2 × 3 = 6.
lr (learning rate): 0.01 → tasa de aprendizaje conservadora que permite convergencia estable sin saltar mínimos.
epochs: 1000 → número de iteraciones de entrenamiento suficientes para observar convergencia en la curva de pérdida.
Función de activación capa oculta: ReLU → eficiente y estable, evita el problema del gradiente desvaneciente.
Función de activación capa de salida: Sigmoid → convierte la salida a probabilidad entre 0 y 1.
# Definir el modelo by Default.
model_nn1 = RedNeuronal(n_entrada = 3, n_oculta = 6, lr = 0.01, epochs = 1000, random_state = 0)
# Entrenamos el modelo.
model_nn1.fit(X_train_nn1, y_train.values)
Epoch 200/1000 | Loss: 0.4644 Epoch 400/1000 | Loss: 0.4563 Epoch 600/1000 | Loss: 0.4531 Epoch 800/1000 | Loss: 0.4518 Epoch 1000/1000 | Loss: 0.4511
<__main__.RedNeuronal at 0x31f27d6a0>
Curva de pérdida:
# Curva de pérdida.
model_nn1.plot_loss(title='Curva de pérdida - Red Neuronal Desbalanceada')
La curva indica que el modelo disminuye a lo largo de las épocas del entrenamiento.
# Evaluación del modelo.
accuracy_train_nn1 = model_nn1.score(X_train_nn1, y_train.values)
print('Accuracy train = {:.2f}'.format(accuracy_train_nn1))
accuracy_test_nn1 = model_nn1.score(X_test_nn1, y_test.values)
print('Accuracy test = {:.2f}'.format(accuracy_test_nn1))
print('Diferencia = {:.4f}%'.format(np.abs(accuracy_train_nn1-accuracy_test_nn1)*100))
Accuracy train = 0.76 Accuracy test = 0.76 Diferencia = 0.3472%
El modelo tiene un muy pequeño sobreajuste.
Su Accuracy de 76% apunta a que sabe generalizar entre los positivos reales y negativos reales, sin embargo, puede aún mejorar en gran escala ya que en otros modelos llegó hasta el 90%.
pred_nn1 = model_nn1.predict(X_test_nn1)
rep_nn1 = classification_report(y_test, pred_nn1)
print(rep_nn1)
print('G-mean =', geometric_mean_score(y_test, pred_nn1))
disp = ConfusionMatrixDisplay.from_predictions(y_test, pred_nn1, cmap = plt.cm.Blues)
# 0: Factores influyendo en el estado de salud. 1: Trastorno mental y del comportamiento.
precision recall f1-score support
0 0.00 0.00 0.00 35
1 0.76 1.00 0.86 109
accuracy 0.76 144
macro avg 0.38 0.50 0.43 144
weighted avg 0.57 0.76 0.65 144
G-mean = 0.0
/opt/anaconda3/lib/python3.13/site-packages/sklearn/metrics/_classification.py:1833: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
_warn_prf(average, modifier, f"{metric.capitalize()} is", result.shape[0])
/opt/anaconda3/lib/python3.13/site-packages/sklearn/metrics/_classification.py:1833: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
_warn_prf(average, modifier, f"{metric.capitalize()} is", result.shape[0])
/opt/anaconda3/lib/python3.13/site-packages/sklearn/metrics/_classification.py:1833: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
_warn_prf(average, modifier, f"{metric.capitalize()} is", result.shape[0])
Matriz de confusión.
0 + 109 = 109 aciertos.
Porcentaje de aciertos → 109 / 144 = 0.76 → 76%.
35 + 0 = 35 errores.
144 datos en total.
Reporte de clasificación.
35 datos de prueba de Factores influyendo en el estado de salud.
- 0% de positivos encontrados.
- 0% fueron clasificados correctamente.
109 datos de prueba de Trastorno mental y del comportamiento.
- 100% de positivos encontrados.
- 76% fueron clasificados correctamente.
F1-Score: promedio ponderado de las clases.
- Factores influyendo en el estado de salud: 0%.
- Trastorno mental y del comportamiento: 86%.
Porcentaje de proporción a encontrar todas las clasificaciones de este estudio (G-mean): 0%.
Este G-mean de 0 es el resultado más revelador: la red neuronal sin balanceo predice únicamente la clase 1, ignorando por completo la clase minoritaria. Esto reafirma que la red neuronal es extremadamente sensible al desbalance de clases.
Red Neuronal - SMOTE.
Procederemos a realizar la Red Neuronal con la técnica de balanceo de SMOTE.
from imblearn.over_sampling import SMOTE
# Aplicamos SMOTE sobre los datos originales.
smote = SMOTE(random_state=0)
X_smote_nn, y_smote_nn = smote.fit_resample(X, y)
print('Tamaño de X antes de SMOTE:', X.shape)
print('Tamaño de X después de SMOTE:', X_smote_nn.shape)
print('Balance de clases con SMOTE:')
print(y_smote_nn.value_counts())
print('Nuestras clases están balanceadas.')
Tamaño de X antes de SMOTE: (720, 3) Tamaño de X después de SMOTE: (1094, 3) Balance de clases con SMOTE: descripcion_grupo_enfermedad_num 1 547 0 547 Name: count, dtype: int64 Nuestras clases están balanceadas.
Separamos los datos en entrenamiento y prueba:
# Dividir los datos balanceados en train y test.
X_train, X_test, y_train, y_test = train_test_split(X_smote_nn, y_smote_nn, test_size = 0.2, random_state = 0, stratify = y_smote_nn)
print(y_train.value_counts())
X_train.shape, X_test.shape
descripcion_grupo_enfermedad_num 1 438 0 437 Name: count, dtype: int64
((875, 3), (219, 3))
Repetimos el escalador StandardScaler:
# Escalamos las características.
scaler_nn2 = StandardScaler()
X_train_nn2 = scaler_nn2.fit_transform(X_train)
X_test_nn2 = scaler_nn2.transform(X_test)
Los hiperparámetros principales de la red neuronal son los siguientes:
n_entrada: 3 → número de variables independientes (edad, sexo_num, institucion_unidad_medica_num).
n_oculta: 6 → neuronas en la capa oculta, calculadas con la regla 2 × n_entrada = 2 × 3 = 6.
lr (learning rate): 0.01 → tasa de aprendizaje conservadora que permite convergencia estable sin saltar mínimos.
epochs: 1000 → número de iteraciones de entrenamiento suficientes para observar convergencia en la curva de pérdida.
Función de activación capa oculta: ReLU → eficiente y estable, evita el problema del gradiente desvaneciente.
Función de activación capa de salida: Sigmoid → convierte la salida a probabilidad entre 0 y 1.
Son los mismos a la Red Neuronal by Default.
# Definir el modelo balanceado.
model_nn2 = RedNeuronal(n_entrada = 3, n_oculta = 6, lr = 0.01, epochs = 1000, random_state = 0)
# Entrenemos el modelo.
model_nn2.fit(X_train_nn2, y_train.values)
Epoch 200/1000 | Loss: 0.5910 Epoch 400/1000 | Loss: 0.5781 Epoch 600/1000 | Loss: 0.5706 Epoch 800/1000 | Loss: 0.5658 Epoch 1000/1000 | Loss: 0.5624
<__main__.RedNeuronal at 0x327c92850>
Curva de pérdida:
# Curva de pérdida.
model_nn2.plot_loss(title='Curva de pérdida - Red Neuronal Balanceada (SMOTE)')
Nuevamente, la curva de pérdida va disminuyendo a lo largo del entrenamiento.
# Evaluación del modelo.
accuracy_train_nn2 = model_nn2.score(X_train_nn2, y_train.values)
print('Accuracy train = {:.2f}'.format(accuracy_train_nn2))
accuracy_test_nn2 = model_nn2.score(X_test_nn2, y_test.values)
print('Accuracy test = {:.2f}'.format(accuracy_test_nn2))
print('Diferencia = {:.4f}%'.format(np.abs(accuracy_train_nn2-accuracy_test_nn2)*100))
Accuracy train = 0.72 Accuracy test = 0.74 Diferencia = 2.6578%
El modelo tiene un pequeño sobreajuste.
Su Accuracy de 74% apunta a que sabe generalizar entre los positivos reales y negativos reales, sin embargo, puede aún mejorar en gran escala ya que en otros modelos llegó hasta el 90%.
pred_nn2 = model_nn2.predict(X_test_nn2)
rep_nn2 = classification_report(y_test, pred_nn2)
print(rep_nn2)
print('G-mean =', geometric_mean_score(y_test, pred_nn2))
disp = ConfusionMatrixDisplay.from_predictions(y_test, pred_nn2, cmap = plt.cm.Blues)
# 0: Factores influyendo en el estado de salud. 1: Trastorno mental y del comportamiento.
precision recall f1-score support
0 0.72 0.80 0.76 110
1 0.77 0.69 0.73 109
accuracy 0.74 219
macro avg 0.75 0.74 0.74 219
weighted avg 0.75 0.74 0.74 219
G-mean = 0.7419290502442469
Matriz de confusión.
88 + 75 = 163 aciertos.
Porcentaje de aciertos → 163 / 219 = 0.74 → 74%.
34 + 22 = 56 errores.
219 datos en total.
Reporte de clasificación.
110 datos de prueba de Factores influyendo en el estado de salud.
- 80% de positivos encontrados.
- 72% fueron clasificados correctamente.
109 datos de prueba de Trastorno mental y del comportamiento.
- 69% de positivos encontrados.
- 77% fueron clasificados correctamente.
F1-Score: promedio ponderado de las clases.
- Factores influyendo en el estado de salud: 76%.
- Trastorno mental y del comportamiento: 73%.
Porcentaje de proporción a encontrar todas las clasificaciones de este estudio (G-mean): 74%.
Este G-mean mejora considerablemente a comparación del obtenido con el modelo desbalanceado (0%), lo que confirma que la técnica de SMOTE es indispensable para la Red Neuronal.
Métricas.
Las métricas de desempeño evaluadas para estos modelos fueron:
Accuracy.
Matriz de confusión.
G-mean.
Precision.
Recall (Sensibilidad).
F1-Score.
Curva de pérdida.
7.1 Resultados.¶
Para evaluar todos los modelos desarrollados en este proyecto, se presenta la siguiente tabla comparativa que integra los resultados de:
Regresión Logística.
Linear Discriminant Analysis (LDA).
Árbol de Decisión.
Random Forest Classifier.
Boosting.
Support Vector Machine (SVM).
Red Neuronal.
| Modelo | Escenario | Accuracy | G-mean | F1 Clase 0 | F1 Clase 1 |
|---|---|---|---|---|---|
| Regresión Logística | Desbalanceado | 81% | 59% | 50% | 89% |
| Regresión Logística | SMOTE | 74% | 74% | 76% | 73% |
| Árbol de Decisión | Desbalanceado | 84% | - | - | - |
| Árbol de Decisión | SMOTE | 81% | - | - | - |
| Árbol Podado | Desbalanceado | 86% | - | - | - |
| Árbol Podado | SMOTE | 79% | - | - | - |
| Random Forest | Desbalanceado | 86% | 75% | 68% | 91% |
| Random Forest | SMOTE | 89% | 89% | 90% | 90% |
| SVM | Desbalanceado | 81% | 68% | 57% | 88% |
| SVM | SMOTE | 75% | 75% | 77% | 74% |
| Red Neuronal | Desbalanceado | 76% | 0% | 0% | 86% |
| Red Neuronal | SMOTE | 74% | 74% | 76% | 73% |
El mejor modelo de todos es el: Random Forest con SMOTE, es decir, un bosque balanceado.
Esto se debe a que combina el poder de múltiples árboles en paralelo con el balanceo de clases de SMOTE, logrando el mayor G-mean (89%) y un F1-score equilibrado entre ambas clases (90% para cada una).
Para justificar de forma cualitativa, lo que permite al Random Forest ser tan bueno es que sus submuestras no tienen correlación entre sí, es decir, que el mismo no generaliza y sabe muy bien a donde dirigir su resultado según las características.
8.1 Discusiones.¶
Para este proyecto del segundo parcial, no se presentaron dificultades al momento de estar desarrollando el código, incluyendo el aspecto que cada uno de estos modelos ya habían sido construidos anteriormente y por ende, se ahorró mucho tiempo y esfuerzo al tener esta organización de entregas que en este caso, fue de mucha ayuda ya que se mantuvo el uso de la misma base de datos.
9.1 Conclusiones.¶
Para concluir, la construcción e implementación de todos estos modelos en la base de datos elegida, brindó un enfoque sumamente profundo al mismo conjunto de datos para ver desde otra lupa el comportamiento de los datos que están disponibles a analizar, en este caso siendo la adaptación de modelos de clasificación, que brindan resultados sumamente diferentes a aquellos de una regresión - estos pueden llegar a ser menos interpretables y no nos ayudan a segmentar a los individuos según sus características importantes, algo que pudimos resolver y visualizar en clasificar el dataset.
Además, aunque ya se conocían diversos modelos de clasificación, aquel con el de la Red Neuronal construida desde cero fue sumamente interesante ya que es el primer paso a LLM, un alcance mucho más allá que ya entra dentro del análisis profundo y que sin duda, el implementar este con enfoques más amplios del conjunto de datos, brindaría mucha información interesante y que vale la pena analizar.
Vale la pena también conectar el cómo el modelo brindaría apoyo para la problemática establecida, donde el tener estudios y desarrollos que justifiquen los dos padecimientos de salud mental en el municipio, puede explicar el estado de las peronas que más brindan económica y socialmente al municipio - pudiendo profundizar en problemáticas que se tienen en dicha sociedad que como se ha mencionado, se puede considerar "perfecta", no es solo un estudio social, sino que también puede ser uno valioso para el gobierno en saber por dónde (por el lado de la salud mental) deben alinearse para brindar una mejor calidad de vida, algo que todos los municipios quieren para poder establecerse como los mejores seún sus habitantes.
10.1 Aprendizajes.¶
Los aprendizajes de este proyecto se pueden resumir en cómo aplicar y contrastar diversos modelos de clasificación para llegar a la conclusión de cuál es el mejor para el conjunto de datos que estemos utilizando - tomando en cuenta que primero hay que delimitar muy bien el objetivo del problema planteado para saber si se puede resolver el mismo mediante una clasificación, ya que pueden haber otras maneras de resolver a la pregunta detonante (como lo es en la regresión, esperando resultados cuantitativos), por lo que también, se obtiene el aprendizaje de aprender a hacer las preguntas correctas para llegar a descubrir el alcance que se desea abordar, en este caso, siendo el clasificar en qué tipo de padecimiento entran los individuos adultos en el municipio de San Pedro Garza García.
11.1 Posibles líneas futuras.¶
Como posibles líneas futuras, es que se puede resolver el mismo planteamiento establecido pero ahora con una cantidad de datos más grande (probablemente igual por municipio o por edades), donde ahora se tomen más registros como de años consecutivos para ir conociendo si dicha salud mental ha ido en aumento (buen caso) por los tratamientos y apoyos, o la misma ha ido disminuyendo (mal caso) por la falta de estos apoyos - esto siendo tratado con una Red Neuronal por la cantidad de datos y probablemente de más variables que se tengan que tomar en cuenta por este estudio que termina siendo más grande que el desarrollado en este proyecto.
12.1 Referencias.¶
3.1. Cross-validation: evaluating estimator performance. (s. f.). Scikit-learn. https://scikit-learn.org/stable/modules/cross_validation.html
Aladdin Persson. (2020). Neural Network from Scratch - Machine Learning Python [Vídeo]. YouTube. https://www.youtube.com/watch?v=NJvojeoTnNM
Aylin Tokuç, A. (2025). Why Feature Scaling in SVM? Baeldung. https://www.baeldung.com/cs/svm-feature-scaling#:~:text=SVM%20y%20escalado%20de%20caracter%C3%ADsticas,-SVM%20es%20un
Draelos, V. A. P. B. R., MD PhD. (2025). Measuring Performance: AUC (AUROC). Glass Box Medicine. https://glassboxmedicine.com/2019/02/23/measuring-performance-auc-auroc/
Egbuchulem, K. (2025). THE ODDS RATIO: a MEASURE OF STRENGTH IN CLINICAL RESEARCH AND AN ANTITHESIS TO ODDS IN GAMBLING. https://pmc.ncbi.nlm.nih.gov/articles/PMC12337969/#:~:text=%C2%BFQu%C3%A9%20significa%20el%20valor%20de%20la%20raz%C3%B3n%20de%20probabilidades%3F&text=OR%20de%201%3A%20No%20hay,la%20exposici%C3%B3n%20y%20el%20resultado.
GeeksforGeeks. (2025). Detecting Multicollinearity with VIF Python. GeeksforGeeks. https://www.geeksforgeeks.org/python/detecting-multicollinearity-with-vif-python/
Salud mental. (2026). OPS/OMS | Organización Panamericana de la Salud. https://www.paho.org/es/temas/salud-mental
Kavlakoglu, E. (s. f.). How to implement linear discriminant analysis in Python. IBM Developer. https://developer.ibm.com/tutorials/awb-implementing-linear-discriminant-analysis-python/
Mucci, T. (2025). What is Data Leakage in Machine Learning? Ibm.com. https://www.ibm.com/think/topics/data-leakage-machine-learning
Pardo, D. - Corresponsal de BBC News Mundo en México. (2025, 26 septiembre). «Somos un rancho, pero de primer mundo»: cómo es San Pedro Garza García, uno de los municipios más ricos de América Latina. Yahoo News. https://es-us.noticias.yahoo.com/somos-rancho-mundo-san-pedro-130147939.html?guccounter=1&guce_referrer=aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8&guce_referrer_sig=AQAAAMmp2Bvqk5tPLjoZLTT6gG1nJpi7JuPC-S2SRk7bnb9FcZT43OxX7CFUYPWuQtbqKLrXcOkVJqszWl5x6TmjK1cyMBpiTlnnUiyESrYs7Sy00CyLDoKUkaZTu5bvX-dLaN9A7sqbnjMF3Kii-dMXw_3eT4rAl3O_6VOD4OLWU2FL
Torres, L. (2024). Curva ROC y AUC en Python. The Machine Learners. https://www.themachinelearners.com/curva-roc-vs-prec-recall/
13.1 Código de Honor de la Universidad de Monterrey.¶
Doy mi palabra que he realizado esta actividad con integridad académica.