Preprocesamiento Avanzado de datos. Reducción

Minería de Datos: Preprocesamiento y clasificación

Máster en Ciencias de Datos e Ingeniería de Computadores

Reducción de dimensionalidad

Reducción de dimensionalidad

Una de las prácticas más comunes de procesamiento de datos es la reducción de la dimensional, lo cual ayuda a transformar o seleccionar las características que mejor representan la estructura, y que por tanto, son más adecuadas para el aprendizaje.

Reducción de la Dimensionalidad No supervisada

Si el número de características es alto, puede ser útil reducirlas mediante una fase no supervisada.

Principal Components Analysis (PCA)

Descomponer un dataset multivariante en un conjunto de componentes ortogonales que explica la cantidad de varianza.

from sklearn import preprocessing, datasets, decomposition

#Load the iris dataset and scale it
X_iris, y_iris = datasets.load_iris(return_X_y=True)

scaler = preprocessing.StandardScaler().fit(X_iris)
X_scaled = scaler.transform(X_iris)

Aplico el PCA:

#Apply principal componentes analysis to reduce the iris number of features from 4 to 3
pca = decomposition.PCA(n_components=3)
X_reduced = pca.fit_transform(X_scaled)
print(X_scaled[:3,:])
print("Con menos dimensión")
print(X_reduced[:3,:])
[[-0.90068117  1.01900435 -1.34022653 -1.3154443 ]
 [-1.14301691 -0.13197948 -1.34022653 -1.3154443 ]
 [-1.38535265  0.32841405 -1.39706395 -1.3154443 ]]
Con menos dimensión
[[-2.26470281  0.4800266  -0.12770602]
 [-2.08096115 -0.67413356 -0.23460885]
 [-2.36422905 -0.34190802  0.04420148]]

Aglomeración

Esta técnica hace clusters de forma jerárquica y va agrupando características que se comportan de forma similar.

import numpy as np
from sklearn import cluster

#Build a FeatureAgglomeration object and transform the iris dataset for it to have 3 features
agglo = cluster.FeatureAgglomeration(n_clusters=3,
                                     pooling_func=np.mean, linkage="ward")
X_reduced2 = agglo.fit_transform(X_scaled)
print(X_scaled[:3,:])
print("Con menos dimensión")
print(X_reduced2[:3,:])
[[-0.90068117  1.01900435 -1.34022653 -1.3154443 ]
 [-1.14301691 -0.13197948 -1.34022653 -1.3154443 ]
 [-1.38535265  0.32841405 -1.39706395 -1.3154443 ]]
Con menos dimensión
[[-1.32783541  1.01900435 -0.90068117]
 [-1.32783541 -0.13197948 -1.14301691]
 [-1.35625412  0.32841405 -1.38535265]]

Visualizando en 2D el original:

#Plot the iris dataset in a 2D pairplot
iris = datasets.load_iris(as_frame=True)
palette = sns.color_palette("hls", 3)
g = sns.pairplot(iris.frame, hue="target", palette=palette,
                 height=1.25, aspect=2)
plt.show()

Visualizando en 2D el PCA:

df = pd.DataFrame(X_reduced, columns=["X1", "X2", "X3"])
#Plot the iris dataset in a 2D pairplot
df["target"] = iris.frame["target"]
g = sns.pairplot(df, hue="target", palette=sns.color_palette("hls", 3),
                 height=1.25, aspect=2)
plt.show()

Visualizando en 2D la reducción mediante Aglomeración:

df = pd.DataFrame(X_reduced2, columns=["X1", "X2", "X3"])
#Plot the iris dataset in a 2D pairplot
df["target"] = iris.frame["target"]
g = sns.pairplot(df, hue="target", palette=sns.color_palette("hls", 3),
                 height=1.25, aspect=2)
plt.show()

Selección de Características

Selección de Características

En este caso se reducen las características eligiendo las características que permitirían un mejor desempeño del clasificador.

Eliminar características con menor varianza

Es un modelo muy simple. Borra todas las características cuya varianza no cumpla un umbral.

Por defecto borra las que tengan 0 varianza.

# Load the iris dataset and plt the variance for each feature
iris = datasets.load_iris(as_frame=True)
X_iris = iris.data
y_iris = iris.target
X_iris.var()
sepal length (cm)    0.685694
sepal width (cm)     0.189979
petal length (cm)    3.116278
petal width (cm)     0.581006
dtype: float64
from sklearn.feature_selection import VarianceThreshold

#Build a variance threshold selector and transform the iris dataet for it to have three features
sel = VarianceThreshold(threshold=0.2)
X_reduced3 = sel.fit_transform(X_iris)
print(X_reduced3.shape)
print(X_iris.iloc[:3,:])
print("Tras reducir")
print(X_reduced3[:3,:])
(150, 3)
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)
0                5.1               3.5                1.4               0.2
1                4.9               3.0                1.4               0.2
2                4.7               3.2                1.3               0.2
Tras reducir
[[5.1 1.4 0.2]
 [4.9 1.4 0.2]
 [4.7 1.3 0.2]]

Midiendo la correlación

La matriz de correlación permite mostrar atributos que pueden ser redundantes:

print(X_iris.corr())
                   sepal length (cm)  sepal width (cm)  petal length (cm)  \
sepal length (cm)           1.000000         -0.117570           0.871754   
sepal width (cm)           -0.117570          1.000000          -0.428440   
petal length (cm)           0.871754         -0.428440           1.000000   
petal width (cm)            0.817941         -0.366126           0.962865   

                   petal width (cm)  
sepal length (cm)          0.817941  
sepal width (cm)          -0.366126  
petal length (cm)          0.962865  
petal width (cm)           1.000000  

Yellowbrick nos permite visualizarlo:

from yellowbrick.features import Rank1D, Rank2D
visualizer = Rank2D(algorithm='pearson').fit(X_iris, y_iris)
visualizer.transform(X_iris); visualizer.show()

<AxesSubplot: title={'center': 'Pearson Ranking of 4 Features'}>

Aplicar la importancia según métrica

from yellowbrick.features import Rank1D
visualizer = Rank1D(algorithm='shapiro')
visualizer.fit(X_iris, y_iris)
visualizer.transform(X_iris); visualizer.show()

<AxesSubplot: title={'center': 'Shapiro Ranking of 4 Features'}>

Selección Univariante

La selección de características univariante funciona seleccionando escogiendo las mejores características según tests univariantes.

from sklearn.feature_selection import SelectKBest, chi2, SelectPercentile
# Según el criterio chi-squared dustribution for it to have three features
sel = SelectKBest(chi2, k=3)
X_reduced4 = sel.fit_transform(X_iris, y_iris)
print(X_reduced4.shape)
print(X_iris.iloc[:3,:])
print("Tras reducir")
print(X_reduced4[:3,:])
(150, 3)
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)
0                5.1               3.5                1.4               0.2
1                4.9               3.0                1.4               0.2
2                4.7               3.2                1.3               0.2
Tras reducir
[[5.1 1.4 0.2]
 [4.9 1.4 0.2]
 [4.7 1.3 0.2]]
# Según un percentil
sel = SelectPercentile(chi2, percentile=50)
X_reduced5 = sel.fit_transform(X_iris, y_iris)
print(X_reduced5.shape)
print(X_iris.iloc[:3,:])
print("Tras reducir")
print(X_reduced5[:3,:])
(150, 2)
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)
0                5.1               3.5                1.4               0.2
1                4.9               3.0                1.4               0.2
2                4.7               3.2                1.3               0.2
Tras reducir
[[1.4 0.2]
 [1.4 0.2]
 [1.3 0.2]]

Eliminación recursiva de características

Dado un estimador que asigna pesos a características, la eliminación recursiva, recursive feature elimination (RFE) selecciona recursivamente menos y menos características.

  • Primero se entrena el estimador, y se mide la importancia de cada atributo.

  • Las menos importantes son eliminadas del conjunto de características de forma recursiva hasta que se alcanza el número de características deseadas.

Vamos a aplicarlo hasta 2 características:

from sklearn.svm import SVC
from sklearn.feature_selection import RFE

#Use a Support Vector Classifier as the base model for feature selection
svc = SVC(kernel="linear", C=1)
#Build a RFE model with the SVC to reduce the number of features of iris to 2
rfe = RFE(estimator=svc, n_features_to_select=2, step=1)
ranking = rfe.fit(X_iris, y_iris).ranking_
X_reduced_rfe = rfe.fit_transform(X_iris, y_iris)

print(ranking); print(X_reduced_rfe.shape)
print(X_iris.iloc[:3,:])
print("Reduced"); print(X_reduced_rfe[:3,:])
[3 2 1 1]
(150, 2)
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)
0                5.1               3.5                1.4               0.2
1                4.9               3.0                1.4               0.2
2                4.7               3.2                1.3               0.2
Reduced
[[1.4 0.2]
 [1.4 0.2]
 [1.3 0.2]]

Vamos a visualizarlo:

df = pd.DataFrame(X_reduced_rfe, columns=X_iris.columns[2:])
df["target"] = y_iris
sns.relplot(data=df, x=df.columns[0], y=df.columns[1], hue="target", aspect=2,
            palette=sns.color_palette("tab10")); plt.show()

Vamos a probar otro dataset:

#Code from sklearn documentation
from sklearn.svm import SVC
from sklearn.datasets import load_digits
from sklearn.feature_selection import RFE
import matplotlib.pyplot as plt

# Load the digits dataset
digits = load_digits()
X = digits.images.reshape((len(digits.images), -1))
y = digits.target
# Create the RFE object and rank each pixel
svc = SVC(kernel="linear", C=1)
rfe = RFE(estimator=svc, n_features_to_select=1, step=1)
rfe.fit(X, y);

Vamos a visualizar el ranking por pixel:

ranking = rfe.ranking_.reshape(digits.images[0].shape)
# Plot pixel ranking
plt.matshow(ranking, cmap=plt.cm.Blues)
plt.colorbar()
plt.title("Ranking of pixels with RFE")
plt.show()

Selección de Características con SelectFromModel

SelectFromModel es un meta-transformador que puede usar cualquier estimador que asigne importancia de los atributos:

  • Si quedan por debajo de un umbral se eliminan.

  • Se puede usar también mean, median y multiplicadores como 0.1*mean.

from sklearn.svm import LinearSVC
from sklearn.feature_selection import SelectFromModel

#Use a LinearSVC as the base model for feature selection
lsvc = LinearSVC(C=0.01, penalty="l1", dual=False).fit(X_iris, y_iris)
#Build a model-based selector with the lsvc to reduce the number of features of iris, preserving only thos avobe the average relevance
SFmodel = SelectFromModel(lsvc, prefit=True, threshold="1.25*mean")
X_reduced_model = SFmodel.transform(X_iris)
print(X_iris.iloc[:5,:])
print(X_reduced_model.shape)
print(X_reduced_model[:5,:])
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)
0                5.1               3.5                1.4               0.2
1                4.9               3.0                1.4               0.2
2                4.7               3.2                1.3               0.2
3                4.6               3.1                1.5               0.2
4                5.0               3.6                1.4               0.2
(150, 2)
[[3.5 1.4]
 [3.  1.4]
 [3.2 1.3]
 [3.1 1.5]
 [3.6 1.4]]
#Plot the reduced iris dataset
sns.relplot(x=X_reduced[:, 0], y=X_reduced[:, 1], hue=y_iris,
            palette=sns.color_palette("hls", 3))
plt.show()

Cambiamos el modelo:

from sklearn.ensemble import ExtraTreesClassifier
#now we do the same but using a tree classifier as the base model for feature selection
clf = ExtraTreesClassifier(n_estimators=50)
clf = clf.fit(X_iris, y_iris)
SFmodel = SelectFromModel(clf, prefit=True, threshold="1.25*mean")
X_reduced = SFmodel.transform(X_iris)

#We can use any model that computes the faeture importance a the base model
print(clf.feature_importances_)
print(X_reduced.shape)
[0.08416323 0.05199978 0.41812623 0.44571076]
(150, 2)
sns.relplot(x=X_reduced[:, 0], y=X_reduced[:, 1], hue=y_iris,
            palette=sns.color_palette("hls", 3))
plt.show()

#We can do the same for the diabetes dataset
diabetes = datasets.load_diabetes()
X_diabetes = diabetes.data
y_diabetes = diabetes.target
from sklearn.linear_model import RidgeCV
ridge = RidgeCV(alphas=np.logspace(-6, 6, num=5)).fit(X_diabetes, y_diabetes)
importance = np.abs(ridge.coef_)
df = pd.DataFrame({"feature_names": diabetes.feature_names, "importance": importance})
sns.catplot(x="feature_names", y="importance", data=df, kind="bar",
            errorbar=None, aspect=2, height=4, color="skyblue")
plt.show()

Selección de Características Secuencial

La selección secuencial (Forward-SFS) busca iterativamente una nueva característica a añadir a las ya seleccionadas. Empieza con cero características y escoge aquella que maximiza aplicando CV usando un estimador (cualquiera le vale, pero mejor que no sea lento) sobre una única característica.

Luego repite el procedimiento añadiendo una nueva característica cada vez, hasta terminar con el número pedido de características.

Backward-SFS

Backward-SFS sigue la misma idea, pero al revés, en vez de ir añadiendo va eliminando características aplicando un estimador.

No dan los mismos resultado, ni son igualmente eficientes. Si tenemos 10 características y queremos siete será más eficiente Backward-SFS que Forward-SFS.

Scikit-learn ofrece SequentialFeatureSelector que implementa ambos ( direction puede ser forward o backward).

Ejemplo:

from sklearn.feature_selection import SequentialFeatureSelector
import warnings
warnings.filterwarnings('ignore')

#Perform FORDWARD feature selection over the diabetes dataset to reduce it to 3 dimensions
sfs_forward = SequentialFeatureSelector(clf, n_features_to_select=3, direction="forward")
sfs_forward_fitted = sfs_forward.fit(X_diabetes, y_diabetes)

X_reduced_for = sfs_forward.transform(X_diabetes)
print(X_reduced_for.shape)
print(sfs_forward_fitted.get_support())
atribs = np.array(diabetes.feature_names)
print("Atributos elegidos")
print(atribs[sfs_forward_fitted.get_support()])
(442, 3)
[False  True False  True False False False  True False False]
Atributos elegidos
['sex' 'bp' 's4']
#Plot the reduced diabetes dataset
fig = plt.figure(1, figsize=(8, 7))
ax = fig.add_subplot(111, projection="3d", elev=48, azim=134)
ax.set_position([0, 0, 0.95, 1])
ax.scatter(X_reduced_for[:, 0], X_reduced_for[:, 1], X_reduced_for[:, 2], c=y_diabetes, s=100)
plt.show()

Aplicando con backward

#Perform BACKWARD feature selection over the diabetes dataset to reduce it to 3 dimensions
sfs_backward = SequentialFeatureSelector(clf, n_features_to_select=3, direction="backward")
sfs_backward_fitted = sfs_backward.fit(X_diabetes, y_diabetes)

X_reduced_back = sfs_backward_fitted.transform(X_diabetes)
print(X_reduced_back.shape)
print(sfs_backward_fitted.get_support())
print("Atributos elegidos")
print(atribs[sfs_backward_fitted.get_support()])
(442, 3)
[False False  True False  True False  True False False False]
Atributos elegidos
['bmi' 's1' 's3']
fig = plt.figure(1, figsize=(8, 7))
ax = fig.add_subplot(111, projection="3d", elev=48, azim=134)
ax.set_position([0, 0, 0.95, 1])
ax.scatter(X_reduced_back[:, 0], X_reduced_back[:, 1], X_reduced_back[:, 2], c=y_diabetes, s=100)
plt.show()

Desbalanceo y Reducción de Instancias

Reducción de Instancias (Under-Sampling)

A menudo los datos contienen instancias redundantes. Como el tamaño puede afectar a la calidad de los resultados (y el tiempo) vamos a intentar reducirlo.

También es importante cuando las muestras están poco balanceadas.

Veremos dos enfoques: generación de prototipos, y selección de prototipos.

Usaremos en este apartado el estupendo paquete imblearn, documentado en https://imbalanced-learn.org/stable/

Generación de Prototipos

En este caso se reducen las muestras pero las que quedan son generadas (no seleccionadas) del original. La técnica más usada es el K-Means, y sintetizar cada clase con un centroide del K-Means en vez de uar las muestras originales.

Vamos a crear unos datos de ejemplo:

from sklearn.datasets import make_classification
from collections import Counter
#Create an artificial datasets to see the effects of under-sampling and over-sampling
X, y = make_classification(n_samples=5000, n_features=2, n_informative=2,
                           n_redundant=0, n_repeated=0, n_classes=3,
                           n_clusters_per_class=1,
                           weights=[0.01, 0.05, 0.94],
                           class_sep=0.8, random_state=10)

print(sorted(Counter(y).items()))
[(0, 67), (1, 264), (2, 4669)]

Están poco balanceadas.

Visualizamos los puntos:

sns.reset_defaults()
sns.relplot(x=X[:,0], y=X[:,1], hue=y, palette=palette)
plt.show()

Aplicamos el under_sampling:

from imblearn.under_sampling import ClusterCentroids

#Perform clustering-based prototype generation
cc = ClusterCentroids(random_state=0, voting="soft")
X_resampled, y_resampled = cc.fit_resample(X, y)

print(sorted(Counter(y_resampled).items()))
reduced = (X.shape[0]-X_resampled.shape[0])/X.shape[0]
print(f"Reduce el {100*reduced} %")
[(0, 67), (1, 67), (2, 67)]
Reduce el 95.98 %

Ahora cada clase está balanceada.

Visualizamos la salida:

sns.relplot(x=X_resampled[:,0], y=X_resampled[:,1], hue=y_resampled, palette=palette)
plt.show()

Selección de prototipos

En este enfoque se muestrean menos instancias pero las que quedan son instancias del conjunto original.

En imblearn hay muchos, escogeremos dos:

  • Aleatorio: Random under sampling technique, una técnica rápida muestreando aleatoriamente para cada clase.
  • Usando vecindario: Edited nearest neighbours, usa el vecindario para eliminar instancias demasiado alejadas del vecindario.

Random under sampling technique

from imblearn.under_sampling import RandomUnderSampler

#Perform random prototype selection
rus = RandomUnderSampler(random_state=0)
X_resampled, y_resampled = rus.fit_resample(X, y)
print(sorted(Counter(y_resampled).items()))
reduced = (X.shape[0]-X_resampled.shape[0])/X.shape[0]
print(f"Reduce el {100*reduced:.2} %")
[(0, 67), (1, 67), (2, 67)]
Reduce el 9.6e+01 %

Vamos a mostrarlo:

sns.relplot(x=X_resampled[:,0], y=X_resampled[:,1], hue=y_resampled, palette=palette)
plt.show()

Edited Nearest Neighbours

Edited Nearest Neighbours aplica el algoritmo de vecinos más cercanos y borra las instancias que no son suficientemente similares a las del vecindario.

Para cada instancia, se calculas sus vecinas y si no se cumple el criterio se borra.

Existen dos criterio a la hora de comparar las vecindas:

  • La mayoría (kind_sel='mode') deben pertenecer a la misma clase.

  • Que todas (kind_sel='all') pertenezcan a la misma clase.

El primero es más conservador y el segundo excluye más.

Aplicamos primero el más conservador:

from imblearn.under_sampling import EditedNearestNeighbours
enn = EditedNearestNeighbours(kind_sel="all")
X_resampled, y_resampled = enn.fit_resample(X, y)
print(X_resampled.shape)
print(sorted(Counter(y_resampled).items()))
reduced = (X.shape[0]-X_resampled.shape[0])/X.shape[0]
print(f"Reduce el {100*reduced:.2} %")
(4824, 2)
[(0, 67), (1, 223), (2, 4534)]
Reduce el 3.5 %

Visualizamos:

sns.relplot(x=X_resampled[:,0], y=X_resampled[:,1], hue=y_resampled, palette=palette)
plt.show()

Aplicamos el menos conservador:

from imblearn.under_sampling import EditedNearestNeighbours

enn = EditedNearestNeighbours(kind_sel="mode")
X_resampled, y_resampled = enn.fit_resample(X, y)

print(X_resampled.shape)
print(sorted(Counter(y_resampled).items()))
reduced = (X.shape[0]-X_resampled.shape[0])/X.shape[0]
print(f"Reduce el {100*reduced:.2} %")
(4972, 2)
[(0, 67), (1, 244), (2, 4661)]
Reduce el 0.56 %

Visualizamos:

sns.relplot(x=X_resampled[:,0], y=X_resampled[:,1], hue=y_resampled, palette=palette)
plt.show()

Sobre Muestreo

Aunque no es reducción vamos a ver cómo se pueden sobre-muestrear las instancias para tener más ejemplares de las clases minoritarias y obtener mejor resultados.

imblearn ofrece varios, como el aleatorio, SMOTE, entre otros. Vamos a ver los dos primeros.

Primero creamos los datos

from sklearn.datasets import make_classification
X, y = make_classification(n_samples=500, n_features=2, n_informative=2,
                           n_redundant=0, n_repeated=0, n_classes=3,
                           n_clusters_per_class=1,
                           weights=[0.01, 0.05, 0.94],
                           class_sep=0.8, random_state=42)

print(sorted(Counter(y).items()))
[(0, 5), (1, 27), (2, 468)]

Vamos a visualizar el original:

sns.relplot(x=X[:,0], y=X[:,1], hue=y, palette=palette)
plt.show()

RandomOverSampler

Ahora aplicamos el RandomOverSampler

from imblearn.over_sampling import RandomOverSampler
ros = RandomOverSampler(random_state=0)
X_resampled_ros, y_resampled_ros = ros.fit_resample(X, y)

print(X_resampled.shape)
print(sorted(Counter(y_resampled_ros).items()))
(4972, 2)
[(0, 468), (1, 468), (2, 468)]

Vamos a aplicarlo:

sns.relplot(x=X_resampled_ros[:,0], y=X_resampled_ros[:,1], hue=y_resampled_ros, palette=palette)
plt.show()

SMOTE

Otra técnica clásica es SMOTE, que genera datos de forma sintética.

from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state=42, k_neighbors=3)
X_resampled_smote, y_resampled_smote = smote.fit_resample(X, y)
print(X_resampled.shape)
print(sorted(Counter(y_resampled_smote).items()))
(4972, 2)
[(0, 468), (1, 468), (2, 468)]

Vamos a aplicarlo:

sns.relplot(x=X_resampled_smote[:,0], y=X_resampled_smote[:,1], hue=y_resampled_smote, palette=palette)
plt.show()

Datos anómalos

Datos anómalos

A veces en los datos se presentan valores anómalos, que se han introducido, por ejemplo, debido a errores en los procesos de recogida de datos.

Quizás el valor anómalo se deba a una cambio en la distribución de valores y no a un error.

La intuición básica en las técnicas detección de anomalías es:

  • La mayoría de los datos siguen una determinada distribución.
  • Los datos las anomalías representan entonces una distribución distinta, que no coincide con el resto.

Detección a mano

Un método clásico es considerar como datos anómalos aquellos para los que el valor de un atributo esté fuera del 1.5*rango intercuartil

Supongamos unos datos:

# Generate train data
rng = np.random.RandomState(42)
X_orig = 0.3 * rng.randn(100, 2)
#X_good = X_orig-4
X_good = np.r_[X_orig + 2, X_orig - 2]
# Generate some abnormal novel observations
X_outliers = rng.uniform(low=5, high=8, size=(10, 2))
X = np.vstack([X_good, X_outliers])
np.random.shuffle(X)
X_df = pd.DataFrame(X, columns=["V1", "V2"])
print(X_df.shape)
print(X_df.columns)
(210, 2)
Index(['V1', 'V2'], dtype='object')
sns.catplot(X_df)
plt.show()
sns.catplot(X_df, kind="box")
plt.show()

Vamos a aplicar:

# eliminar outliers como aquellos casos fuera de 1.25 veces el rango intercuartil
ratio = 1.25
Q1 = X_df.quantile(0.25)
Q3 = X_df.quantile(0.75)
IQR = Q3 - Q1
outliers = ((X_df < (Q1 - ratio * IQR)) |(X_df > (Q3 + ratio * IQR))).any(axis=1)
X_df_irq= X_df.copy()
X_df_irq["outlier"] = outliers

Visualizamos:

sns.relplot(x="V1", y="V2", data=X_df_irq, hue="outlier", aspect=2)
plt.show()

Detección Outliers usando scikit-learn

Scikit-learn ofrece distintos algoritmos para detectar outliers En la Documentación

Vamos a probar Isolation Forest.

Isolation Forest

‘Aisla’ observaciones aleatoriamente escogiendo una característica y aleatoriamente divide recursivamente según sus valores, usando una estructura de árbol.

Cerca de -1 si lo considera outlier, 1 en caso contrario.

from sklearn.ensemble import IsolationForest
clf = IsolationForest(n_estimators=10, warm_start=True)
clf.fit(X_df)  # fit 10 trees
X_df_if = X_df.copy()
X_df_if["outlier"] = clf.predict(X_df) < 1
print(X_df_if.head(3))
         V1        V2  outlier
0  1.819488  2.555683     True
1 -1.753382 -1.430962     True
2  1.836685  2.033277    False

Visualizamos:

sns.relplot(x="V1", y="V2", data=X_df_if, hue="outlier", aspect=2)
plt.show()

LocalOutlierFactor

Esta técnica mide la desviación local de una muestra respecto a los vecinos (usando k-vecinos). Al comparar la distancia local con la de los vecinos, se ientifica las instancias con una densidad sustanciamente menor que sus vecinos.

Cerca de -1 si lo considera outlier, 1 en caso contrario.

from sklearn.neighbors import LocalOutlierFactor
clf = LocalOutlierFactor(n_neighbors=10)
X_df_loc = X_df.copy()
X_df_loc["outlier"] = np.abs(clf.fit_predict(X_df) - -1) <= 1e-3
print(X_df_loc.head(3))
         V1        V2  outlier
0  1.819488  2.555683    False
1 -1.753382 -1.430962    False
2  1.836685  2.033277    False

Visualizamos:

sns.relplot(x="V1", y="V2", data=X_df_loc, hue="outlier", aspect=2)
plt.show()

Ruido y Filtros

Ruido y Filtros

En muchos casos existe ruido o inexactitudes en los parámetros.

Hay que evitar el sobre-aprendizaje que puede llegar a aprender dicho ruido.

Tiene especial interés cuando hay una variable que cambia con el tiempo, hay muchos filtros asociado a series temporales.

En R existe el paquete NoiseFiltersR pero no tiene contrapartida similar en Python.

Veremos filtros disponibles en scipy.signal.

Señales temporales

# Generating the noisy signal
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x) + np.cos(x) + np.random.random(100)
sns.relplot(x=x, y=y, kind="line")
plt.show()

Aplicamos el filtro Savitzky-Golay

from scipy.signal import savgol_filter
y_filtered = savgol_filter(y, 99, 3)
fig, axs=plt.subplots(1, 2)
sns.lineplot(x=x, y=y, label="Original", ax=axs[0])
sns.lineplot(x=x, y=y_filtered, label="Filtrada", ax=axs[1])
plt.show()

Imágenes

En las imágenes es muy común que exista ruido, vamos a ver varios filtros que los eliminan.

Para ello usaremos el paquete scipy y opencv-python (muy conocida).

Se puede instalar con:

!pip install opencv-python --user
import cv2
fig, axs = plt.subplots(1,2)
image_orig=cv2.imread("squirrel_cls.jpg", cv2.IMREAD_GRAYSCALE)
size=(image_orig.shape[0],image_orig.shape[1])
white_noise = np.random.randint(-20, 40, size = image_orig.shape)
image=image_orig+white_noise
image=np.maximum(np.zeros(size), image)
image=np.minimum(255*np.ones(size), image)
axs[0].imshow(image_orig, cmap='gray'); axs[0].set_title("Original")
axs[1].imshow(image, cmap='gray'); axs[1].set_title("Con Ruido")
plt.show()

Vamos a usar wiener

from scipy.signal import wiener
filtered_img = wiener(image, (5, 5))  #Filter the image
fig, axs = plt.subplots(1,2)
axs[0].imshow(image, cmap='gray'); axs[0].set_title("Con Ruido")
axs[1].imshow(filtered_img, cmap='gray'); axs[1].set_title("Filtrado")
plt.show()

Comparamos cómo queda respecto al original

fig, axs = plt.subplots(1,2)
axs[0].imshow(image_orig, cmap='gray'); axs[0].set_title("Original")
axs[1].imshow(filtered_img, cmap='gray'); axs[1].set_title("Filtrado")
plt.show()

Aplicamos cv2.fastNlMeansDenoising

from cv2 import fastNlMeansDenoising
filtered2_img = fastNlMeansDenoising(image.astype(np.uint8), None, h=10)
fig, axs = plt.subplots(1,2)
axs[0].imshow(image, cmap='gray'); axs[0].set_title("Con Ruido")
axs[1].imshow(filtered2_img, cmap='gray'); axs[1].set_title("Filtrado")
plt.show()

Comparamos los resultados de los dos filtrados

fig, axs = plt.subplots(1,2)
#axs[0].imshow(image_orig, cmap='gray'); axs[0].set_title("Original")
axs[0].imshow(filtered_img, cmap='gray'); axs[0].set_title("wiener")
axs[1].imshow(filtered2_img, cmap='gray'); axs[1].set_title("fastNlMeansDenoising")
plt.show()

Ejercicios

Ejercicios de dimensionalidad

Vamos a usar el datasets de los pingüinos:

penguins = sns.load_dataset("penguins")
  1. Reduce usando PCA o el cluster agglomerativo la dimensionalidad de los valores numéricos del problema en 2 componentes. Nota: Es cómodo usar el atributo select_dtypes de pandas para filtrar atributos de tipo np.number.

  2. Visualiza la representación, resaltando la especie. ¿Es separable en dos?

Ejercicios de selección de características

Vamos a usar aquí el dataset de estudiantes.

students = pd.read_csv("estudiantes.csv")

Target es el valor con el que se quiere predecir, que indica si los estudiantes terminan la carrera o no.

  1. Elimina directamente los atributos con valores perdidos, y usa StandardScaler sobre el resto.

  2. Separa el atributo Target en otra variable.

  3. Visualizar usando yellobricks la importancia de las características.

  4. Carga el estimador

from sklearn import svm
lsvc = svm.LinearSVC(C=0.01, penalty="l1", dual=False)
  1. Usando SelectModel con ese estimador eliminar el 20% de los atributos.

  2. Usa SequentialFeatureSelector con ese estimador para eliminar el 20% de los atributos en ambas direcciones, ¿cuáles son eliminados?

  3. ¿Son iguales en ambas direcciones?

Ejercicio de Balanceo

  1. Students no está bien balanceado. Haz un Random Under sampling y comprueba que ahora sí esté balanceado.

  2. Haz un Edited Nearest Neighbours sobre el original menos conservador, ¿cuántos ha quitado?

Ejercicio de anomalías

  1. Elimina Datos anómalos usando LocalOutlierFactor e indica el porcentaje de eliminados.