UD05 · Notebook 4 — Reglas sobre datos: el Titanic¶
En este ejercicio, utilizaremos los datos Titanic para predecir si un pasajero sobrevivió o no al naufragio. Veremos diferentes formas de hacerlo; con varios grados de automatismo.
Human Learn¶
Human Learn es una librería que nos permite combinar el aprendizaje automático con el conocimiento humano de una manera simple.
# Cargamos las librerías y los datos
# Instalamos la librería
%pip install git+https://github.com/koaning/human-learn.git
Collecting git+https://github.com/koaning/human-learn.git Cloning https://github.com/koaning/human-learn.git to /tmp/pip-req-build-chdvtz20 Running command git clone --filter=blob:none --quiet https://github.com/koaning/human-learn.git /tmp/pip-req-build-chdvtz20 Resolved https://github.com/koaning/human-learn.git to commit ab961c200829a3270084fb1345eeea60623fa89d Installing build dependencies ... done Getting requirements to build wheel ... done Preparing metadata (pyproject.toml) ... done Collecting scikit-learn>=0.23.2 (from human-learn==0.3.5) Downloading scikit_learn-1.7.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (11 kB) Collecting pandas>=0.23.4 (from human-learn==0.3.5) Downloading pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.metadata (91 kB) Collecting clumper<0.3.0,>=0.2.5 (from human-learn==0.3.5) Downloading clumper-0.2.15-py2.py3-none-any.whl.metadata (1.2 kB) Collecting Shapely>=1.7.1 (from human-learn==0.3.5) Downloading shapely-2.1.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (6.8 kB) Collecting bokeh<3.0.0,>=2.2.1 (from human-learn==0.3.5) Downloading bokeh-2.4.3-py3-none-any.whl.metadata (14 kB) Requirement already satisfied: Jinja2>=2.9 in /usr/local/lib/python3.10/site-packages (from bokeh<3.0.0,>=2.2.1->human-learn==0.3.5) (3.1.6) Requirement already satisfied: numpy>=1.11.3 in /usr/local/lib/python3.10/site-packages (from bokeh<3.0.0,>=2.2.1->human-learn==0.3.5) (1.23.5) Requirement already satisfied: packaging>=16.8 in /usr/local/lib/python3.10/site-packages (from bokeh<3.0.0,>=2.2.1->human-learn==0.3.5) (25.0) Requirement already satisfied: pillow>=7.1.0 in /usr/local/lib/python3.10/site-packages (from bokeh<3.0.0,>=2.2.1->human-learn==0.3.5) (12.0.0) Requirement already satisfied: PyYAML>=3.10 in /usr/local/lib/python3.10/site-packages (from bokeh<3.0.0,>=2.2.1->human-learn==0.3.5) (6.0.3) Requirement already satisfied: tornado>=5.1 in /usr/local/lib/python3.10/site-packages (from bokeh<3.0.0,>=2.2.1->human-learn==0.3.5) (6.5.2) Requirement already satisfied: typing-extensions>=3.10.0 in /usr/local/lib/python3.10/site-packages (from bokeh<3.0.0,>=2.2.1->human-learn==0.3.5) (4.15.0) Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/site-packages (from Jinja2>=2.9->bokeh<3.0.0,>=2.2.1->human-learn==0.3.5) (3.0.3) Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.10/site-packages (from pandas>=0.23.4->human-learn==0.3.5) (2.9.0.post0) Collecting pytz>=2020.1 (from pandas>=0.23.4->human-learn==0.3.5) Downloading pytz-2025.2-py2.py3-none-any.whl.metadata (22 kB) Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.10/site-packages (from pandas>=0.23.4->human-learn==0.3.5) (2025.2) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/site-packages (from python-dateutil>=2.8.2->pandas>=0.23.4->human-learn==0.3.5) (1.17.0) Requirement already satisfied: scipy>=1.8.0 in /usr/local/lib/python3.10/site-packages (from scikit-learn>=0.23.2->human-learn==0.3.5) (1.15.3) Collecting joblib>=1.2.0 (from scikit-learn>=0.23.2->human-learn==0.3.5) Downloading joblib-1.5.2-py3-none-any.whl.metadata (5.6 kB) Collecting threadpoolctl>=3.1.0 (from scikit-learn>=0.23.2->human-learn==0.3.5) Downloading threadpoolctl-3.6.0-py3-none-any.whl.metadata (13 kB) Downloading bokeh-2.4.3-py3-none-any.whl (18.5 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 18.5/18.5 MB 28.4 MB/s 0:00:00m0:00:0100:01 Downloading clumper-0.2.15-py2.py3-none-any.whl (18 kB) Downloading pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (12.8 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.8/12.8 MB 23.6 MB/s 0:00:00 eta 0:00:01 Downloading pytz-2025.2-py2.py3-none-any.whl (509 kB) Downloading scikit_learn-1.7.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (9.7 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 9.7/9.7 MB 30.8 MB/s 0:00:00 eta 0:00:01 Downloading joblib-1.5.2-py3-none-any.whl (308 kB) Downloading shapely-2.1.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (3.1 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.1/3.1 MB 32.4 MB/s 0:00:00 Downloading threadpoolctl-3.6.0-py3-none-any.whl (18 kB) Building wheels for collected packages: human-learn Building wheel for human-learn (pyproject.toml) ... done Created wheel for human-learn: filename=human_learn-0.3.5-py3-none-any.whl size=116523 sha256=383b213712406de6e8251e28373991ba0eddfa6db407a4256a53af613a014cc6 Stored in directory: /tmp/pip-ephem-wheel-cache-t7brfw2y/wheels/84/81/6b/a9c7f40f0c7d485c35c319703ee16c9a3b0ae88f5bd49d8248 Successfully built human-learn Installing collected packages: pytz, clumper, threadpoolctl, Shapely, joblib, scikit-learn, pandas, bokeh, human-learn ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 9/9 [human-learn] [bokeh]]learn]] Successfully installed Shapely-2.1.2 bokeh-2.4.3 clumper-0.2.15 human-learn-0.3.5 joblib-1.5.2 pandas-2.3.3 pytz-2025.2 scikit-learn-1.7.2 threadpoolctl-3.6.0 WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning. Note: you may need to restart the kernel to use updated packages.
# Cargamos el Dataset
from hulearn.datasets import load_titanic
df = load_titanic(as_frame=True)
df
| survived | pclass | name | sex | age | fare | sibsp | parch | |
|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 7.2500 | 1 | 0 |
| 1 | 1 | 1 | Cumings, Mrs. John Bradley (Florence Briggs Th... | female | 38.0 | 71.2833 | 1 | 0 |
| 2 | 1 | 3 | Heikkinen, Miss. Laina | female | 26.0 | 7.9250 | 0 | 0 |
| 3 | 1 | 1 | Futrelle, Mrs. Jacques Heath (Lily May Peel) | female | 35.0 | 53.1000 | 1 | 0 |
| 4 | 0 | 3 | Allen, Mr. William Henry | male | 35.0 | 8.0500 | 0 | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 709 | 0 | 3 | Rice, Mrs. William (Margaret Norton) | female | 39.0 | 29.1250 | 0 | 5 |
| 710 | 0 | 2 | Montvila, Rev. Juozas | male | 27.0 | 13.0000 | 0 | 0 |
| 711 | 1 | 1 | Graham, Miss. Margaret Edith | female | 19.0 | 30.0000 | 0 | 0 |
| 712 | 1 | 1 | Behr, Mr. Karl Howell | male | 26.0 | 30.0000 | 0 | 0 |
| 713 | 0 | 3 | Dooley, Mr. Patrick | male | 32.0 | 7.7500 | 0 | 0 |
714 rows × 8 columns
from sklearn.model_selection import train_test_split
# Preparamos los datos: separamos las variables explicativas de la variable de destino y creamos los conjuntos de entrenamiento y test
X_train, X_test, y_train, y_test = train_test_split(
df.drop(['name', 'survived'], axis=1), df['survived'], test_size=0.25, random_state=42)
Clasificación con una función¶
En este primer ejemplo, utilizaremos una función para clasificar a los pasajeros. Es una función muy simple que clasifica a los pasajeros en función del precio del billete. El enfoque es que los pasajeros que más pagaban tenían más probabilidades de sobrevivir.
# FunctionClassifier nos permite crear un clasificador a partir de una función
from hulearn.classification import FunctionClassifier
# Usaremos el FunctionClassifier Para crear un clasificador que use el precio del boleto para predecir si un pasajero sobrevivió o no
def classificador_preu_bitllet(df, preu_minim=20):
"""
Clasificar a los pasajeros según el precio del billete
El enfoque es que los pasajeros que más pagaban tenían más probabilidades de sobrevivir.
El valor predeterminado es 20 pero podemos cambiarlo
"""
return (df['fare'] > preu_minim).astype(int)
model = FunctionClassifier(classificador_preu_bitllet)
# Preparem el classificador
model.fit(X_train, y_train)
FunctionClassifier(func=<function classificador_preu_bitllet at 0x7f22d9fd49d0>)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
| func | <function cla...x7f22d9fd49d0> |
Evaluamos el clasificador¶
from sklearn.metrics import classification_report
print(classification_report(y_test, model.predict(X_test)))
precision recall f1-score support
0 0.73 0.69 0.71 107
1 0.57 0.61 0.59 72
accuracy 0.66 179
macro avg 0.65 0.65 0.65 179
weighted avg 0.66 0.66 0.66 179
El rendimiento del clasificador es bastante malo. Seguramente el precio del boleto no es una buena variable para predecir si un pasajero sobrevivió o no. Aun así, intentaremos mejorar su rendimiento de refinación el parámetro de precio_minim.
Usaremos GridSearchCV para encontrar el mejor valor para Price_Minim. GridSearchCV nos permite hacer una búsqueda de cuadrícula para encontrar los mejores parámetros, la puntuación será dada por la exactitud (Accuracy), la precisión (Precision) y la sensibilidad (Recall).
- Accuracy: ¿Cuántas predicciones son correctas?
- Precision: ¿Cuántas predicciones positivas son correctas?
- Recall: ¿Cuántas predicciones positivas son correctas de todos los aspectos positivos?
import numpy as np
from sklearn.metrics import make_scorer, accuracy_score, precision_score, recall_score
from sklearn.model_selection import GridSearchCV
# Creamos un clasificador con un precio mínimo de 20
mod = FunctionClassifier(classificador_preu_bitllet, preu_minim=20)
# El objeto GridSearchCV nos permite hacer una búsqueda de cuadrícula para encontrar los mejores parámetros.
# En este caso buscaremos el mejor precio mínimo para clasificar a los pasajeros
grid = GridSearchCV(mod,
cv=2,
param_grid={'preu_minim': np.linspace(0, 100, 30)},
scoring={'accuracy': make_scorer(accuracy_score),
'precision': make_scorer(precision_score),
'recall': make_scorer(recall_score)},
refit='accuracy')
grid.fit(X_train, y_train)
print(classification_report(y_test, grid.predict(X_test)))
precision recall f1-score support
0 0.66 0.93 0.77 107
1 0.74 0.28 0.40 72
accuracy 0.67 179
macro avg 0.70 0.61 0.59 179
weighted avg 0.69 0.67 0.62 179
# Podemos ver cuál era el mejor precio mínimo
grid.best_params_
{'preu_minim': 48.275862068965516} Exploración interactiva¶
Ahora que hemos visto que el precio del boleto no es una buena variable para predecir si un pasajero sobrevivió o no, podemos explorar los datos para encontrar una mejor variable.
Para hacer esto, utilizaremos la función Interactivecharts.Esta función nos permitirá explorar los datos de manera interactiva.En este caso, utilizaremos la función Parallel_Coordinates para ver cómo se distribuyen los datos en función de las variables explicativas.
from hulearn.experimental.interactive import parallel_coordinates
parallel_coordinates(df, label="survived", height=200)
Basado en la hipótesis de que las mujeres y los niños tenían más probabilidades de sobrevivir, podemos explorar datos para ver si es cierto y parece que sí (teniendo en cuenta las diferencias de clase social): si solo dejamos mujeres y niños en primera y segunda clase, la mayoría sobrevivió.
Entonces podemos crear un clasificador que use el sexo, la edad y la clase del pasajero para predecir si sobrevivió o no.
def classificador_edat_sexe_classe(df, edat=12):
"""
Clasificar a los pasajeros según el sexo, la edad y la clase de pasajeros
El enfoque es que las mujeres y los niños tenían más probabilidades de sobrevivir
"""
regla_dones = (df['pclass'] < 3.0) & (df['sex'] == "female")
regla_nens = (df['pclass'] < 3.0) & (df['age'] <= edat)
return regla_dones | regla_nens
mod = FunctionClassifier(classificador_edat_sexe_classe)
mod.fit(X_train, y_train)
mod.score(X_test, y_test)
0.8212290502793296
grid = GridSearchCV(mod,
cv=2,
param_grid={'edat': np.linspace(0, 50, 50)},
scoring={'accuracy': make_scorer(accuracy_score),
'precision': make_scorer(precision_score),
'recall': make_scorer(recall_score)},
refit='accuracy')
grid.fit(X_train, y_train)
print(grid.best_params_)
predict = grid.predict(X_train)
print(predict)
print(classification_report(y_test, grid.predict(X_test)))
{'edat': 3.0612244897959187}
649 False
360 False
518 False
248 True
344 False
...
71 False
106 False
270 True
435 False
102 False
Length: 535, dtype: bool
precision recall f1-score support
0 0.76 0.97 0.86 107
1 0.93 0.56 0.70 72
accuracy 0.80 179
macro avg 0.85 0.76 0.78 179
weighted avg 0.83 0.80 0.79 179
Los números son lo mucho mejores que los anteriores. Por lo tanto, podemos usar este clasificador para hacer predicciones en nuevos pasajeros.
FIGS¶
FIGS Es una librería que nos permite crear reglas para clasificar los datos.En este caso, utilizaremos los datos Titanic para crear reglas que nos permitan predecir si un pasajero sobrevivió o no.
# Instalamos la librería
%pip install imodels
Collecting imodels Downloading imodels-2.0.3-py3-none-any.whl.metadata (29 kB) Requirement already satisfied: matplotlib in /usr/local/lib/python3.10/site-packages (from imodels) (3.10.7) Collecting mlxtend (from imodels) Downloading mlxtend-0.23.4-py3-none-any.whl.metadata (7.3 kB) Requirement already satisfied: numpy in /usr/local/lib/python3.10/site-packages (from imodels) (1.23.5) Requirement already satisfied: pandas in /usr/local/lib/python3.10/site-packages (from imodels) (2.3.3) Requirement already satisfied: requests in /usr/local/lib/python3.10/site-packages (from imodels) (2.32.5) Requirement already satisfied: scipy in /usr/local/lib/python3.10/site-packages (from imodels) (1.15.3) Requirement already satisfied: scikit-learn in /usr/local/lib/python3.10/site-packages (from imodels) (1.7.2) Collecting tqdm (from imodels) Downloading tqdm-4.67.1-py3-none-any.whl.metadata (57 kB) Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/site-packages (from matplotlib->imodels) (1.3.2) Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/site-packages (from matplotlib->imodels) (0.12.1) Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/site-packages (from matplotlib->imodels) (4.60.1) Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.10/site-packages (from matplotlib->imodels) (1.4.9) Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.10/site-packages (from matplotlib->imodels) (25.0) Requirement already satisfied: pillow>=8 in /usr/local/lib/python3.10/site-packages (from matplotlib->imodels) (12.0.0) Requirement already satisfied: pyparsing>=3 in /usr/local/lib/python3.10/site-packages (from matplotlib->imodels) (3.2.5) Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/site-packages (from matplotlib->imodels) (2.9.0.post0) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib->imodels) (1.17.0) Requirement already satisfied: joblib>=0.13.2 in /usr/local/lib/python3.10/site-packages (from mlxtend->imodels) (1.5.2) Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/site-packages (from pandas->imodels) (2025.2) Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.10/site-packages (from pandas->imodels) (2025.2) Requirement already satisfied: threadpoolctl>=3.1.0 in /usr/local/lib/python3.10/site-packages (from scikit-learn->imodels) (3.6.0) Requirement already satisfied: charset_normalizer<4,>=2 in /usr/local/lib/python3.10/site-packages (from requests->imodels) (3.4.4) Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/site-packages (from requests->imodels) (3.11) Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/site-packages (from requests->imodels) (2.5.0) Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/site-packages (from requests->imodels) (2025.11.12) Downloading imodels-2.0.3-py3-none-any.whl (274 kB) Downloading mlxtend-0.23.4-py3-none-any.whl (1.4 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 17.9 MB/s 0:00:00 Downloading tqdm-4.67.1-py3-none-any.whl (78 kB) Installing collected packages: tqdm, mlxtend, imodels ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3/3 [imodels]m2/3 [imodels] Successfully installed imodels-2.0.3 mlxtend-0.23.4 tqdm-4.67.1 WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning. Note: you may need to restart the kernel to use updated packages.
# Cargamos la librería
from imodels import FIGSClassifier
# Preparamos los datos
X_skope = df.drop(columns=['name', 'sibsp'])
X_skope['sex'] = X_skope['sex'].replace(['female', 'male'], [0, 1])
X_train, X_test, y_train, y_test = train_test_split(
X_skope.drop(['survived'], axis=1), X_skope['survived'], test_size=0.25, random_state=42)
/tmp/ipykernel_384/94841749.py:3: FutureWarning: Downcasting behavior in `replace` is deprecated and will be removed in a future version. To retain the old behavior, explicitly call `result.infer_objects(copy=False)`. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`
X_skope['sex'] = X_skope['sex'].replace(['female', 'male'], [0, 1])
# Creamos el clasificador
clf = FIGSClassifier(max_rules=4)
# Entrenamos el clasificador
clf.fit(X_train, y_train)
> ------------------------------ > FIGS-Fast Interpretable Greedy-Tree Sums: > Predictions are made by summing the "Val" reached by traversing each tree. > For classifiers, a softmax function is then applied to the sum. > ------------------------------ sex <= 0.500 (Tree #0 root) pclass <= 2.500 (split) Val: 0.051 0.949 (leaf) Val: 0.506 0.494 (leaf) pclass <= 1.500 (split) Val: 0.608 0.392 (leaf) Val: 0.856 0.144 (leaf) + age <= 1.500 (Tree #1 root) Val: -0.631 0.631 (leaf) Val: 0.016 -0.016 (leaf)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
| max_rules | 4 | |
| max_trees | None | |
| min_impurity_decrease | 0.0 | |
| random_state | None | |
| max_features | None | |
| max_depth | None |
# Podem veure el rendiment del classificador
print(classification_report(y_test, clf.predict(X_test)))
precision recall f1-score support
0 0.76 0.96 0.85 107
1 0.91 0.54 0.68 72
accuracy 0.79 179
macro avg 0.83 0.75 0.76 179
weighted avg 0.82 0.79 0.78 179
Podemos ver que las reglas creadas por el clasificador tienen un rendimiento similar al clasificador que hemos creado con Human Learn y los conceptos que hemos creado con Human Learn son más fáciles de interpretar y podemos continuar agregando reglas para mejorar el rendimiento del clasificador (por ejemplo, podríamos incorporar la variable fare(tarifa)).