Predecir Los Supervivientes Del Titanic

Dorian Lazar
Jul 11, 2020

Predecir Los Supervivientes Del Titanic

Jul 11, 2020 9 minutes read

¿Quién Sobrevivirá A Un Naufragio? Podemos Usar El Aprendizaje Automático Para Responder A Esas Preguntas.

El RMS Titanic era un transatlántico de pasajeros británico operado por la White Star Line que se hundió en el Océano Atlántico Norte en la madrugada del 15 de abril de 1912, después de chocar con un iceberg durante su viaje inaugural desde Southampton a la ciudad de Nueva York. De los 2.224 pasajeros y tripulación estimados a bordo, más de 1.500 murieron, lo que hace que el hundimiento sea uno de los desastres marinos comerciales más mortíferos de la historia moderna en tiempos de paz.

F.G.O. Stuart (1843–1923) / Public domain

Lo que queremos ahora es crear un modelo de aprendizaje automático que sea capaz de predecir quién sobrevivirá al naufragio del Titanic. Para ello utilizaremos este conjunto de datos. Este conjunto de datos contiene la siguiente información sobre los pasajeros:


Análisis exploratorio

Ahora, antes de hacer cualquier aprendizaje de máquina en este conjunto de datos, es una buena práctica hacer algún análisis exploratorio y ver cómo se ven nuestros datos. También queremos prepararlos/limpiarlos a lo largo del camino.

import numpy as np
import pandas as pd
df = pd.read_csv('train.csv')
df


df.describe()


¿Qué vemos en nuestros datos a primera vista? Vemos que la columna PassengerId tiene un número único asociado a cada pasajero. Este campo puede ser usado fácilmente por algoritmos de aprendizaje automático para sólo memorizar los resultados de cada pasajero sin la capacidad de generalizar. También está la variable del nombre, que personalmente no creo que determine de ninguna manera si una persona sobrevivió o no. Por lo tanto, eliminaremos estas dos variables.

del df['PassengerId']
del df['Name']
Ahora veamos si tenemos valores faltantes y cuántos son 

df.isnull().sum()

De 891 muestras, 687 tienen valores nulos para la variable Cabin. Hay demasiados valores faltantes para que esta variable pueda ser utilizada. La borraremos.

del df['Cabin']
No queremos borrar las otras dos columnas porque les faltan algunos valores, por lo tanto las aumentaremos.

En el caso de la variable Embarked, como es una variable categórica, miraremos los recuentos de cada categoría y sustituiremos los 2 valores nulos por la categoría que tiene más elementos.

df['Embarked'].value_counts()



El valor más frecuente para Embarked es 'S', así que lo usaremos para reemplazar los valores nulos.

df['Embarked'].loc[pd.isnull(df['Embarked'])] = 'S'
En cuanto a la edad, sustituiremos los valores faltantes con la edad promedio

mean_age_train = np.mean(df['Age'].loc[pd.isnull(df['Age']) == False].values)
df['Age'].loc[pd.isnull(df['Age'])] = mean_age_train

Tenga en cuenta que debemos almacenar todo lo que aprendemos de nuestros datos de entrenamiento, como la frecuencia de la clase Embarked o la edad promedio, ya que utilizaremos esta información para hacer predicciones en caso de que también haya valores faltantes en los datos de las pruebas.

También calcularemos y almacenaremos la tarifa media en caso de que la necesitemos en los datos de las pruebas.

mean_fare_train = np.mean(df['Fare'].loc[pd.isnull(df['Fare']) == False].values)

Nos deshicimos de los valores nulos, ahora veamos qué sigue.

Lea también:
Una guia práctica para la limpieza de datos

Codificación ordinal

Los algoritmos de aprendizaje automático no funcionan con texto (al menos no directamente), necesitamos convertir todos los strings a números. Usaremos la codificación ordinal para esta conversión. La codificación ordinal es una forma de convertir una variable categórica a números asignando a cada categoría un número. Usaremos el OrdinalEncoder de Scikit-Learn para aplicar esta transformación en las variables Sex, Ticket, Embarked.

Antes de eso, haremos una copia de seguridad de nuestro actual formato de datos para su uso futuro.

df_bkp = df.copy()
from sklearn.preprocessing import OrdinalEncoder
df['Sex'] = OrdinalEncoder().fit_transform(df['Sex'].values.reshape((-1, 1)))
df['Ticket'] = OrdinalEncoder().fit_transform(df['Ticket'].values.reshape((-1, 1)))
df['Embarked'] = OrdinalEncoder().fit_transform(df['Embarked'].values.reshape((-1, 1)))

Visualizaciones

Ahora visualizaremos nuestros datos para ver cómo varía la distribución de nuestras variables entre las clases sobrevivientes y no sobrevivientes.

Abajo están los histogramas de cada variable de nuestro dataset (junto con el código que lo generó), a la izquierda está el subconjunto de personas que sobrevivieron, y a la derecha el de las que no sobrevivieron.

import matplotlib.pyplot as plt
from IPython.display import display, Markdown

def show(txt):
    # this function is for printing markdown in jupyter notebook
    display(Markdown(txt))

for i in range(1, 9):
    show(f'### {df.columns[i]}')
    f, (survived, not_survived) = plt.subplots(1, 2, sharey=True, figsize=(18, 8))
    survived.hist(df.iloc[np.where(df['Survived'] == 1)[0], i])
    survived.set_title('Survived')

not_survived.hist(df.iloc[np.where(df['Survived'] == 0)[0], i])
    not_survived.set_title('Not Survived')
    plt.show()

Corriendo aprendizaje automático en este formato de datos

Ahora, probaremos un par de métodos de aprendizaje automático en nuestro dataset para ver qué resultados obtenemos. Los métodos de aprendizaje automático que usaremos son:

  • Regresión logística
  • Support vector machines
  • Árbol de decisión
  • K Vecinos más cercanos
  • Perceptrón multicapa

En lugar de elegir un conjunto de validación fijo para estimar la precisión del conjunto de pruebas, utilizaremos una validación cruzada (5 veces) con el cross_val_score de Scikit-Learn, que devuelve una matriz con las puntuaciones de cada iteración de validación cruzada.


from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.neural_network import MLPClassifier


X = df.iloc[:, 1:].values
y = df.iloc[:, 0].values


# Logistic Regression
lr = LogisticRegression()
lr_score = np.mean(cross_val_score(lr, X, y))
print(f'Logistic Regression: {lr_score}')


# Support Vector Machine
svc = SVC()
svc_score = np.mean(cross_val_score(svc, X, y))
print(f'Support Vector Machine: {svc_score}')


# Decision Tree
dtc = DecisionTreeClassifier()
dtc_score = np.mean(cross_val_score(dtc, X, y))
print(f'Decision Tree: {dtc_score}')


# K Nearest Neighbors
knc = KNeighborsClassifier()
knc_score = np.mean(cross_val_score(knc, X, y))
print(f'K Nearest Neighbors: {knc_score}')

# Multi-Layer Perceptron
mlpc = MLPClassifier()
mlpc_score = np.mean(cross_val_score(mlpc, X, y))
print(f'Multi-Layer Perceptron: {mlpc_score}')

Ejecutando este código obtuvimos los siguientes resultados:


Ingeniería de características

Veamos si podemos mejorar la precisión trabajando en las características de nuestro conjunto de datos. Para ello, primero restauraremos la copia de seguridad que hicimos antes de aplicar la codificación ordinal.

df = df_bkp.copy()

Codificación única (One Hot Encoding)

En lugar de la codificación ordinal, ahora queremos aplicar one-hot encoding a nuestras variables categóricas. Con one-hot encoding, en lugar de asignar a cada clase un número, asignamos un vector lleno de 0’s excepto para una posición específica de esa clase en la que ponemos un 1. Es decir, transformamos cada clase en una variable por sí misma que toma el valor 0 o 1. Aplicaremos one-hot encoding en las variables Pclass, Sex, Ticket y Embarked usando el OneHotEncoder de Scikit-Learn.


from sklearn.preprocessing import OneHotEncoder

# Pclass
pclass_transf = OneHotEncoder(sparse=False, dtype=np.uint8, handle_unknown='ignore')
pclass_transf.fit(df['Pclass'].values.reshape((-1, 1)))
pclass = pclass_transf.transform(df['Pclass'].values.reshape((-1, 1)))
df['Pclass0'] = pclass[:, 0]
df['Pclass1'] = pclass[:, 1]
df['Pclass2'] = pclass[:, 2]
del df['Pclass']


# Sex
gender_transf = OneHotEncoder(sparse=False, dtype=np.uint8, handle_unknown='ignore')
gender_transf.fit(df['Sex'].values.reshape((-1, 1)))
gender = gender_transf.transform(df['Sex'].values.reshape((-1, 1)))
df['Male'] = gender[:, 0]
df['Female'] = gender[:, 1]
del df['Sex']


# Ticket
ticket_transf = OneHotEncoder(sparse=False, dtype=np.uint8, handle_unknown='ignore')
ticket_transf.fit(df['Ticket'].values.reshape((-1, 1)))
ticket = ticket_transf.transform(df['Ticket'].values.reshape((-1, 1)))
for i in range(ticket.shape[1]):
    df[f'Ticket{i}'] = ticket[:, i]
del df['Ticket']


# Embarked
embarked_transf = OneHotEncoder(sparse=False, dtype=np.uint8, handle_unknown='ignore')
embarked_transf.fit(df['Embarked'].values.reshape((-1, 1)))
embarked = embarked_transf.transform(df['Embarked'].values.reshape((-1, 1)))
for i in range(embarked.shape[1]):
    df[f'Embarked{i}'] = embarked[:, i]
del df['Embarked']

Escalando a rangos entre [0, 1]


También queremos escalar las variables numéricas a un rango [0, 1]. Para ello utilizaremos MinMaxScaler que escala las variables de manera que el valor mínimo se mueva a 0, el máximo a 1 y los valores intermedios se escalen consecuentemente entre 0 y 1. 

Aplicaremos esta transformación a las variables Age, SibSp, Parch, Fare.

from sklearn.preprocessing import MinMaxScaler

age_transf = MinMaxScaler().fit(df['Age'].values.reshape(-1, 1))
df['Age'] = age_transf.transform(df['Age'].values.reshape(-1, 1))

sibsp_transf = MinMaxScaler().fit(df['SibSp'].values.reshape(-1, 1))
df['SibSp'] = sibsp_transf.transform(df['SibSp'].values.reshape(-1, 1))

parch_transf = MinMaxScaler().fit(df['Parch'].values.reshape(-1, 1))
df['Parch'] = parch_transf.transform(df['Parch'].values.reshape(-1, 1))

fare_transf = MinMaxScaler().fit(df['Fare'].values.reshape(-1, 1))
df['Fare'] = fare_transf.transform(df['Fare'].values.reshape(-1, 1))

Haciendo aprendizaje automático en este nuevo formato de datos


Ahora ejecutaremos los mismos algoritmos de aprendizaje de la máquina en este nuevo formato de datos para ver qué resultados obtenemos.

X = df.iloc[:, 1:].values
y = df.iloc[:, 0].values

# Logistic Regression
lr = LogisticRegression()
lr_score = np.mean(cross_val_score(lr, X, y))
print(f'Logistic Regression: {lr_score}')

# Support Vector Machine
svc = SVC()
svc_score = np.mean(cross_val_score(svc, X, y))
print(f'Support Vector Machine: {svc_score}')

# Decision Tree
dtc = DecisionTreeClassifier()
dtc_score = np.mean(cross_val_score(dtc, X, y))
print(f'Decision Tree: {dtc_score}')

# K Nearest Neighbors
knc = KNeighborsClassifier()
knc_score = np.mean(cross_val_score(knc, X, y))
print(f'K Nearest Neighbors: {knc_score}')

# Multi-Layer Perceptron
mlpc = MLPClassifier()
mlpc_score = np.mean(cross_val_score(mlpc, X, y))
print(f'Multi-Layer Perceptron: {mlpc_score}')


Después de ajustar nuestras características de esta manera obtuvimos una mejora significativa en la precisión de todos los clasificadores. El mejor clasificador entre ellos es el Clasificador de Árbol de Decisión. Ahora intentaremos mejorarlo haciendo un ajuste de hiper parámetros usando GridSearchCV.

Ajuste de Hiper-Parámetros


from sklearn.model_selection import GridSearchCV


dtc = DecisionTreeClassifier()
params = {
    'max_depth': list(range(2, 151)),
    'min_samples_split': list(range(2, 15))
}

clf = GridSearchCV(dtc, params)
clf.fit(X, y)
print(f'Best params: {clf.best_params_}')
print(f'Best score: {clf.best_score_}')

Los parámetros que obtenemos son:

Y la puntuación que obtuvimos es 84,40%, una mejora del 0,9% después del ajuste de hiperparametros


Join our private community in Discord

Keep up to date by participating in our global community of data scientists and AI enthusiasts. We discuss the latest developments in data science competitions, new techniques for solving complex challenges, AI and machine learning models, and much more!