Saltar a contenido

La librería SciKit-Learn

SciKit-Learn (o abreviado, sklearn) es una librería fundamentalmente empleada en tareas de aprendizaje automático (machine/deep learning). Dispone de numerosos modelos que realizan tareas de regresión, clasificación, etc, y que, a partir de un conjunto de datos de entrada, aprenden a ajustar ciertos parámetros para deducir o calcular resultados de salida.

El uso avanzado de esta librería excede de los límites de este curso, centrado en el ámbito de la ciencia de datos. Sin embargo, sklearn dispone de algunas clases y métodos útiles en este proceso que estamos aprendiendo.

1. Instalación y primeros pasos

Para empezar, necesitamos instalar el módulo scikit-learn en el sistema utilizando la herramienta pip:

pip install scikit-learn

Advertencia

En este documento anterior se recomendó crear un entorno propio con conda, llamado data_science, e instalar esta librería (entre otras). Si ya has completado ese ejercicio, sólo te queda activar el entorno para trabajar con él (conda activate data_science). Si no, completa el ejercicio y deja el entorno activado para completar los ejercicios de esta sesión.

Ahora ya podremos emplear la librería en nuestros programas. No es habitual importarla completa, a diferencia de otras cono NumPy o Pandas, ya que es muy grande. En este caso, lo que haremos será importar individualmente las clases o métodos que vayamos a usar. Veremos a continuación algunos ejemplos.

2. Escalado de datos

El escalado de datos es una operación que reduce el rango de valores de esos datos a un intervalo acotado. Ya hemos visto en algún ejemplo anterior los tipos de escalado existentes, y sus fórmulas para poderlos calcular.

En el caso de sklearn, se incorporan a través del módulo sklearn.preprocessing las clases MinMaxScaler y StandardScaler, que realizan el escalado por normalización y por estandarización, respectivamente. Aquí vemos un ejemplo de cómo usarlas:

import pandas as pd
from sklearn.preprocessing import MinMaxScaler, StandardScaler

df = pd.DataFrame({
    'altura': [150, 160, 170, 180, 190],
    'peso': [50, 65, 80, 95, 110]
})

# Crear escaladores
minmax = MinMaxScaler()
standard = StandardScaler()

# Escalamos con MinMax la altura y con Standard el peso
df[['altura']] = minmax.fit_transform(df[['altura']])
df[['peso']] = standard.fit_transform(df[['peso']])

Lo que obtendremos como resultado es un dataframe con este aspecto:

altura peso
0.00 -1.414214
0.25 -0.707107
0.50 0.000000
0.75 0.707107
1.00 1.414214

Si luego queremos agregar nuevos datos al conjunto y mantener los patrones de escalado originales, debemos usar la instrucción transform para aplicar las medidas originales (mínimo/máximo o media/desviación, según el caso) con los nuevos datos. Aquí vemos un ejemplo, aplicado a estos nuevos datos:

df2 = pd.DataFrame({
    'altura': [167, 198],
    'peso': [55, 95]
})

# Usamos transform en lugar de fit_transform para usar los parámetros de escalado originales
df2[['altura']] = minmax.transform(df2[['altura']])
df2[['peso']] = standard.transform(df2[['peso']])

Los nuevos datos quedarían escalados de este modo:

altura peso
0.425 -1.178511
1.200 0.707107

Como vemos, el segundo dato, que tiene una altura mayor que los anteriores, se escala a 1.2. Si hubiéramos aplicado fit_transform se habría escalado a 1.000, y si hubiéramos unido las dos tablas habría resultado que esas dos personas tendrían la misma altura, cosa que no es cierta. Con transform aplicamos de nuevo los mismos criterios de escalado que se hicieron inicialmente con fit_transform.

3. Muestreo de datos

En ocasiones nos interesa elegir una parte de un conjunto de datos o data frame. Esto es habitual en procesos de aprendizaje automático, donde destinamos una parte de los datos a entrenar al modelo (lo que se denomina conjunto de entrenamiento) y otra parte a evaluar el modelo una vez entrenado (lo que se llama conjunto de test).

La instrucción train_test_split del paquete sklearn.model_selection permite partir un dataframe en estos dos conjuntos, indicando en el parámetro test_size el porcentaje que se destina al conjunto de test (normalmente un valor en torno al 10-30%), y dejando el resto para entrenamiento.

Aquí vemos un ejemplo de uso:

import pandas as pd
from sklearn.model_selection import train_test_split

df = pd.DataFrame({
    'nombre': [
        'Ana', 'Luis', 'Marta', 'Carlos', 'Lucía', 'Pedro', 'Sofía', 'Tomás',
        'Elena', 'Jorge', 'Nuria', 'Raúl', 'Clara', 'Mario', 'Sara', 'Iván',
        'Laura', 'Andrés', 'Patricia', 'Alberto'
    ],
    'edad': [
        25, 34, 29, 41, 22, 35, 28, 39, 31, 30,
        27, 33, 45, 23, 36, 32, 26, 38, 24, 40
    ],
    'ingresos': [
        20000, 32000, 28000, 40000, 18000, 35000, 27000, 39000, 30000, 31000,
        25000, 33000, 42000, 19000, 36000, 29000, 21000, 37000, 22000, 41000
    ],
    'ciudad': [
        'Madrid', 'Madrid', 'Sevilla', 'Valencia', 'Madrid', 'Bilbao', 'Sevilla', 'Madrid',
        'Bilbao', 'Valencia', 'Madrid', 'Sevilla', 'Bilbao', 'Valencia', 'Madrid', 'Sevilla',
        'Bilbao', 'Valencia', 'Madrid', 'Madrid'
    ],
    'etiqueta': [
        'A', 'A', 'A', 'B', 'A', 'A', 'A', 'C', 'C', 'A',
        'A', 'B', 'A', 'A', 'A', 'A', 'A', 'C', 'A', 'B'
    ]
})

# 30% para test, y con semilla aleatoria fija de 1 para generar siempre
# los mismos subconjuntos
df_train, df_test = train_test_split(df, test_size=0.3, random_state=1)

El parámetro random_state sirve para fijar la semilla aleatoria, de forma que, dentro de que los elementos que se destinan a uno u otro conjunto se eligen al azar, siempre se elijan los mismos. Esto es muy habitual en procesos de aprendizaje automático ya que, si cada vez se eligen elementos distintos para cada conjunto, no podemos saber si el modelo mejora/empeora porque se han elegido ahora otros datos, o porque lo hemos mejorado nosotros. De este modo, fijando los elementos que se eligen para cada conjunto, tendremos claro que cualquier mejora/empeoramiento del modelo se deberá a un ajuste de otros parámetros.

3.1. Muestreo estratificado

Puede ocurrir en ocasiones que alguna columna o categoría de nuestros datos no esté correctamente balanceada. Por ejemplo, en el caso anterior, para la columna etiqueta hay muchos más individuos de tipo A que de los tipos B o C. Esto puede ocasionar que en el conjunto de entrenamiento o de test no haya individuos de alguna de las categorías existentes.

Para evitar este problema podemos realizar un muestreo estratificado. Consiste en indicar dentro de la instrucción train_test_split qué columna es la que queremos monitorizar, y entonces nos aseguramos de que tanto en entrenamiento como en test haya la misma proporción de elementos de cada categoría de esa columna.

Con esta variante nos aseguramos que tanto en df_train como en df_test haya una proporción equivalente de elementos de tipo A, B y C:

df_train, df_test = train_test_split(df, test_size=0.3,
    stratify=df['etiqueta'], random_state=1)

4. Codificación de variables categóricas

Otro proceso importante dentro del análisis de datos es la codificación de variables categóricas, que también hemos explicado en este apartado anterior.

La librería sklearn dispone de dos clases que permiten hacer los dos tipos de codificaciones: label encoding (asignar a cada categoría un valor numérico correlativo), o one hot encoding (generar una columna para cada posible categoría y rellenar los valores con 0 o 1 dependiendo de a qué categoría pertenece el elemento de cada fila).

Aquí vemos un ejemplo basado en el dataframe anterior:

from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder

...

# Creamos un codificador de cada tipo
one_hot = OneHotEncoder(sparse_output=False)
ordinal = OrdinalEncoder()

# Codificamos en "one hot" las ciudades
codigos_ciudad = one_hot.fit_transform(df[['ciudad']])
df_codigos_ciudad = pd.DataFrame(codigos_ciudad, columns=one_hot.get_feature_names_out(['ciudad']))

# Codificamos con "label encoding" las etiquetas
codigos_etiquetas = ordinal.fit_transform(df[['etiqueta']])
df_codigos_etiqueta = pd.DataFrame(codigos_etiquetas, columns=['cod_etiqueta'])

# Añadimos las nuevas columnas al final del dataframe original
df = pd.concat([df, df_codigos_ciudad, df_codigos_etiqueta], axis=1)

La clase OneHotEncoder se emplea para hacer codificaciones one hot. El parámetro sparse_output a False se define para que guarde un array con ceros y unos completo, no sólo los unos. De este modo se puede concatenar luego mejor con el dataframe original.

La clase OrdinalEncoder genera una codificación label encoding, aunque arbitraria: va asignando a cada categoría en orden alfabético un código numérico correlativo 0, 1, 2...

Ambas clases se lanzan con fit_transform para transformar la(s) columna(s) que indiquemos, y devuelven un array con los códigos generados. Lo que queda, según se ve en el ejemplo anterior, es generar un dataframe con cada array y concatenarlo al final de las columnas del dataframe original.

Sin embargo, normalmente nos interesará tener un control más manual del label encoding (y asignar por ejemplo con map de Pandas los códigos a mano de cada categoría), y la instrucción get_dummies de Pandas también ofrece una integración más sencilla de las nuevas columnas generadas en el dataframe original.

Aquí puedes descargar un cuaderno Colab para probar los ejemplos vistos en este documento.