Caso de estudio - Supervivencia en el Titanic

Extracción de características

Ahora trataremos parte muy importante del aprendizaje automático: la extracción de características cuantitativas a partir de los datos. Con este fin:

  • Aprenderemos como las características pueden extraerse a partir de datos del mundo real.
  • Veremos como extraer características numéricas a partir de datos textuales. Además, repasaremos algunas herramientas básicas en scikit-learn que pueden utilizarse para realizar estas tareas.

¿Qué son características?

Características numéricas

Recuerda que los datos en scikit-learn vienen en arrays de dos dimensiones con tamaño n_samples $\times$ n_features.

Anteriormente, vimos el dataset iris, que tienen 150 ejemplos y 4 características.


In [ ]:
from sklearn.datasets import load_iris

iris = load_iris()
print(iris.data.shape)

Las características son:

  • Longitud de sépalo en cm
  • Ancho de sépalo en cm
  • Longitud de pétalo en cm
  • Ancho de pétalo en cm

Las características numéricas como estas son directas: cada ejemplo contiene una lista de números con precisión real que se corresponden con las características.

Características categóricas

¿Qué pasa si tenemos características categóricas?. Por ejemplo, imagina que disponemos del color de cada flor de iris: $color \in \{red, blue, purple\}$

Podrías estar tentado de usar algo así como i.e. red=1, blue=2, purple=3, pero, en general, esto es una mala idea. Los estimadores tienden a trabajar con la suposición de que las características numéricas se sitúan en una escala continua por lo que, en este ejemplo, 1 y 2 serían más parecidos que 1 y 3 y esto no tiene porque ser generalmente verdad.

De hecho, el ejemplo anterior es una subcategoría de las variables categóricas, en concreto, una variable nominal. Las variables nominales no tienen asociado un orden, mientras que las variables ordinales si que implican un orden. Por ejemplo, las tallas de las camisetas formarían una variable ordinal "XL > L > M > S".

Una forma de transformar variables nominales en un formato que prevenga al estimador de asumir un orden es la llamada representación $1$-$de$-$J$ (one-hot encoding). Cada categoría genera su propia variable por separado.

El conjunto de características aumentado sería:

  • Longitud de sépalo en cm
  • Ancho de sépalo en cm
  • Longitud de pétalo en cm
  • Ancho de pétalo en cm
  • color=purple (1.0 o 0.0)
  • color=blue (1.0 o 0.0)
  • color=red (1.0 o 0.0)

Observa que al usar este conjunto de características puede que los datos se representen mejor usando matrices dispersas, como veremos en el ejemplo de clasificación de texto que analizaremos después.

Utilizando DictVectorizer para codificar variables categóricas

Cuando los datos de entrada están codificados con un diccionario de tal forma que los valores son o cadenas o valores numéricos, se puede usar la clase DictVectorizer para obtener la expansión booleana sin tocar las características numéricas:


In [ ]:
measurements = [
    {'city': 'Dubai', 'temperature': 33.},
    {'city': 'London', 'temperature': 12.},
    {'city': 'San Francisco', 'temperature': 18.}
]

In [ ]:
from sklearn.feature_extraction import DictVectorizer

vec = DictVectorizer()
vec

In [ ]:
vec.fit_transform(measurements).toarray()

In [ ]:
vec.get_feature_names()

Características derivadas

Otro tipo bastante común de características son las características derivadas, que son características obtenidas a partir de algún paso previo de preprocesamiento y que se supone que son más informativas que las originales. Este tipo de características pueden estar basadas en extracción de características y en reducción de la dimensionalidad (tales como PCA o aprendizaje de variedades) y pueden ser combinaciones lineales o no lineales de las características originales (como en regresión polinómica) o transformaciones más sofisticadas de las características.

Combinando características numéricas y categóricas

Como un ejemplo de la forma en que se trabaja con datos numéricos y categóricos, vamos a realizar un ejercicio en el que predeciremos la supervivencia de los pasajeros del HMS Titanic.

Utilizaremos una versión del dataset Titatic que puede descargarse de titanic3.xls. Previamente, ya hemos convertido el .xls a .csv para que sea más fácil su manipulación (como texto), de manera que los datos no fueron modificados.

Necesitamos leer todas las líneas del fichero titanic3.csv, ignorar la cabecera y encontrar las etiquetas (sobrevivió o murió) y los datos de entrada (características de la persona). Vamos a ver la cabecera y algunas líneas de ejemplo:


In [ ]:
import os
import pandas as pd

titanic = pd.read_csv(os.path.join('datasets', 'titanic3.csv'))
print(titanic.columns)

Aquí tenemos una descripción de lo que significan cada una de las variables:

pclass          Passenger Class
                (1 = 1st; 2 = 2nd; 3 = 3rd)
survival        Survival
                (0 = No; 1 = Yes)
name            Name
sex             Sex
age             Age
sibsp           Number of Siblings/Spouses Aboard
parch           Number of Parents/Children Aboard
ticket          Ticket Number
fare            Passenger Fare
cabin           Cabin
embarked        Port of Embarkation
                (C = Cherbourg; Q = Queenstown; S = Southampton)
boat            Lifeboat
body            Body Identification Number
home.dest       Home/Destination

Parece que las variables name, sex, cabin, embarked, boat, body y homedest son candidatas a ser variables categóricas, mientras que el resto parecen variables numéricas. Vamos a ver las primeras filas para tener un mejor conocimiento de la base de datos:


In [ ]:
titanic.head()

Podemos descartar directamente las columnas "boat" y "body" ya que está directamente relacionadas con que el pasajero sobreviviese. El nombre es (probablemente) único para cada persona y por tanto no es informativo. Vamos a intentar en primer lugar usar "pclass", "sibsp", "parch", "fare" y "embarked" como características:


In [ ]:
labels = titanic.survived.values
features = titanic[['pclass', 'sex', 'age', 'sibsp', 'parch', 'fare', 'embarked']]

In [ ]:
features.head()

En principio, los datos ahora solo contienen características útiles, pero no están en un formato que los algoritmos de aprendizaje automático puedan entender. Necesitamos transformar las cadenas "male" y "female" en variables binarias que indiquen el género y lo mismo para embarked.Podemos hacer esto usando la función get_dummies de pandas:


In [ ]:
pd.get_dummies(features).head()

Esta transformación ha codificado bien las columnas de cadenas. Sin embargo, parece que la variable pclass también es una variable categórica. Podemos listar de forma explícita las variables que queremos codificar utilizando el parámetro columns para incluir pclass:


In [ ]:
features_dummies = pd.get_dummies(features, columns=['pclass', 'sex', 'embarked'])
features_dummies.head(n=16)

In [ ]:
#También podríamos hacerlo con DictVectorizer
from sklearn.feature_extraction import DictVectorizer

diccionario = features.to_dict('records')
vec = DictVectorizer()
dataset = vec.fit_transform(diccionario)
print(dataset.todense())

In [ ]:
data = features_dummies.values

In [ ]:
# Comprobamos que hay valores perdidos, tendremos que aplicar un Imputer
import numpy as np
np.isnan(data).any()

Una vez hemos hecho el trabajo de duro de cargar los datos, evaluar un clasificador con estos datos es directo. Vamos a ver que rendimiento obtenemos con el clasificador más simple, DummyClassifier('most_frequent'), que es equivalente al ZeroR.


In [ ]:
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import Imputer


train_data, test_data, train_labels, test_labels = train_test_split(
    data, labels, random_state=0)

imp = Imputer()
imp.fit(train_data)
train_data_finite = imp.transform(train_data)
test_data_finite = imp.transform(test_data)

In [ ]:
np.isnan(train_data_finite).any()

In [ ]:
from sklearn.dummy import DummyClassifier

clf = DummyClassifier('most_frequent')
clf.fit(train_data_finite, train_labels)
print("Accuracy: %f"
      % clf.score(test_data_finite, test_labels))
EJERCICIO:
  • Intenta ejecutar el problema de clasificación anterior pero usando ``LogisticRegression`` y ``RandomForestClassifier`` en lugar de ``DummyClassifier``
  • Prueba a cambiar el conjunto de características considerado. ¿Consigues mejorar los resultados?

In [ ]: