Source code for dstk.features._transformers
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Classe et fonction facilitant le wrapping Scikit-Learn d'un transformer
personalisé.
Created on Mon Nov 23 12:12:30 2020
@author: Cyrile Delestre
"""
from collections.abc import Iterable, Callable
import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator, TransformerMixin
from joblib import Parallel, delayed
from tqdm import tqdm
[docs]def make_transformer(udf_transform: Callable,
n_jobs: int=1,
verbose: bool=False):
r"""
Instentiatieur de classe Transformer, sert à éviter d'écrire une classe
directement.
Parameters
----------
udf_transform : Callable
fonction de transformation défini par l'utilisateur (user difined
function) qui doit avoir pour prototypage :
udf_transform(X, **kargs)
n_jobs : int
nombre de processeurs en parallèle
verbose : int
Si True bare de progression de process.
Notes
-----
Si le transformer que l'on souhaite créer nécessite la surcharge de la
fonction fit alors il est obliger de passer par la définition d'une
classe.
De même udf_transform doit être une fonction simple, sinon il est très
conseiller de passer par la définition d'une classe.
Examples
--------
>>> from dataclasses import dataclass
>>> from sklearn.ensemble import RandomForestClassifier
>>> from sklearn.pipeline import Pipeline
>>> from dstk.features import make_transformer
>>> from dstk.utils import check_dataframe
>>>
>>> def udf_transform(X, order_col=['col_3', 'col_1', 'col_2', 'col_0']):
>>> # check_dataframe vérifie s'il s'agit bien d'une DataFrame
>>> # et que les colonnes de order_col sont bien présentes
>>> check_dataframe(X, order_col)
>>> return X[order_col]
>>>
>>> orderer = make_transformer(udf_transform)
>>> clf = RandomForestClassifier()
>>> pipe = Pipeline([('order_col', orderer), ('rf', clf)])
>>> pipe.fit(X, y)
See also
--------
Transformer
"""
if not isinstance(udf_transform, Callable):
raise AttributeError(
"udf_transform doit être une fonction Callable et non de type "
f"{type(udf_transform)}."
)
trans = Transformer(n_jobs=n_jobs, verbose=verbose)
trans.udf_transform = udf_transform
return trans
[docs]class Transformer(BaseEstimator, TransformerMixin):
r"""
Classe générique de transformation Scikit-Learn qui implémente une
exécution compatible avec les Pipeline où les éléments doivent être
traités un à un si ils sont dans une liste ou un itérable.
Parameters
----------
n_jobs : int
nombre de processeurs en parallèle
verbose : bool
Si True bare de progression de process.
Notes
-----
Il est important d'implémenter la méthode udf_transform(self, X, **kargs)
et surcharger les autres méthodes si besoin.
Examples
--------
Exemple très simple d'un transformer utilisant la classe Transfomer qui
permet d'ordonner les colonnes d'une DataFramme en fonction d'un ordre
souhaité.
>>> from dataclasses import dataclass
>>> from sklearn.ensemble import RandomForestClassifier
>>> from sklearn.pipeline import Pipeline
>>> from dstk.features import Transformer
>>> from dstk.utils import check_dataframe
>>>
>>> @dataclass
>>> class SortColumns(Transformer):
>>> "Classe qui ordonne les colonnes d'une DataFrame."
>>> order_col: list
>>> n_jobs: int=1
>>> verbose: bool=False
>>>
>>> def udf_transform(self, X):
>>> # check_dataframe vérifie s'il s'agit bien d'une DataFrame
>>> # et que les colonnes de self.order_col sont bien présentes
>>> check_dataframe(X, self.order_col)
>>> return X[self.order_col]
>>> orderer = SortColumns(['col_3', 'col_1', 'col_2', 'col_0'], n_jobs=2)
>>> clf = RandomForestClassifier()
>>> pipe = Pipeline([('order_col', orderer), ('rf', clf)])
>>> pipe.fit(X, y)
See also
--------
make_transformer
"""
def __init__(self, n_jobs: int, verbose: bool):
self.n_jobs = n_jobs
self.verbose = n_jobs
[docs] def fit(self, X: Iterable, y: None=None):
r"""
Fonction fit pour être ISO avec Scikit-Learn
Parameters
----------
X : Iterable
Observations
y : None
pour être ISO avec Scikit-Learn
"""
return self
def _transform_iter(self, X: Iterable, **kargs):
r"""
Itération de transformations.
Parameters
----------
X : Iterable
Observations
"""
iterator = tqdm(
X,
desc = f"{self.__class__.__name__}",
mininterval = 0.5
) if self.verbose else X
trans = delayed(self.udf_transform)
with Parallel(n_jobs=self.n_jobs) as par:
return par(trans(x, **kargs) for x in iterator)
[docs] def udf_transform(self, X: Iterable, **kargs):
r"""\
Transformation unitaire.
Parameters
----------
X : Iterable
Observations
"""
raise NotImplementedError(
"Il faut implémenter la méthode udf_transform(self, X, **kargs) "
f"à la classe {self.__name__} qui hérite de la classe "
"Transformer."
)
[docs] def transform(self, X: Iterable, **kargs):
r"""\
Méthode générique scikit-learn pour la transformation.
Parameters
----------
X : Iterable
Array-like, pour un traitement itératif un Iterable quelconque
:**kargs: arguments propres au transformer
"""
if isinstance(X, (pd.DataFrame, np.ndarray,)):
return self.udf_transform(X, **kargs)
elif isinstance(X, Iterable):
return self._transform_iter(X, **kargs)
else:
raise AttributeError(
"L'argument X de transform doit être un Iterable."
)