2017-10-04 1 views
3

J'écris un ensemble de transformations personnalisées en sklearn afin de nettoyer les données dans un pipeline. Chaque transformation personnalisée prend deux données Pandas DataFrame comme paramètres pour fit et transform, transform renvoie deux DataFrames également (voir les exemples ci-dessous). Cela fonctionne correctement lorsqu'il n'y a qu'un seul Transformer dans un pipeline: DataFrames in et DataFrames out.Sur quelles données fonctionne la transformation de Sklearn?

Cependant, lorsque les deux Rransformers sont combinés dans un pipeline, comme celui-ci:

pipeline = Pipeline ([ 
     ('remove_missing_columns', RemoveAllMissing (['mailing_address_str_number'])), 
     ('remove_rows_based_on_target', RemoveMissingRowsBasedOnTarget()), 
     ]) 

X, y = pipeline.fit_transform (X, y) 

==>TypeError: tuple indices must be integers or slices, not Series 

classe RemoveMissingRowsBasedOnTarget reçoit mystérieusement un tuple comme entrée. Lorsque je passe les positions des transformateurs comme celui-ci

pipeline = Pipeline ([ 
     ('remove_rows_based_on_target', RemoveMissingRowsBasedOnTarget()), 
     ('remove_missing_columns', RemoveAllMissing (['mailing_address_str_number'])), 
     ]) 

==> AttributeError: 'tuple' object has no attribute 'apply' 

L'erreur se produit dans la classe RemoveAllMissing. Dans les deux cas, le message d'erreur est indiqué par ==> au-dessus de la ligne où l'erreur se produit. Je pense que j'ai fait beaucoup de lecture sur ce qui exactement pourrait se passer, mais je n'ai rien trouvé à ce sujet. Quelqu'un pourrait-il me dire ce que je fais de mal? Vous trouverez ci-dessous le code du problème isolé.

import numpy as np 
import pandas as pd 
import random 
from sklearn.base import BaseEstimator, TransformerMixin 
from sklearn.pipeline import Pipeline 

def create_data (rows, cols, frac_nan, random_state=42): 
    random.seed (random_state) 
    X = pd.DataFrame (np.zeros ((rows, cols)), 
         columns=['col' + str(i) for i in range (cols)], 
         index=None) 
    # Create dataframe of (rows * cols) with random floating points 
    y = pd.DataFrame (np.zeros ((rows,))) 
    for row in range(rows): 
     for col in range(cols): 
      X.iloc [row,col] = random.random() 
     X.iloc [row,1] = np.nan # column 1 exists colely of NaN's 
     y.iloc [row] = random.randint (0, 1) 
    # Assign NaN's to a fraction of X 
    n = int(frac_nan * rows * cols) 
    for i in range (n): 
     row = random.randint (0, rows-1) 
     col = random.randint (0, cols-1) 
     X.iloc [row, col] = np.nan 
    # Same applies to y 
    n = int(frac_nan * rows) 
    for i in range (n): 
     row = random.randint (0, rows-1) 
     y.iloc [row,] = np.nan 

    return X, y  

class RemoveAllMissing (BaseEstimator, TransformerMixin): 
    # remove columns containg NaN only 
    def __init__ (self, requested_cols=[]): 
     self.all_missing_data = requested_cols 

    def fit (self, X, y=None): 
     # find empty columns == columns with all missing data 
     missing_cols = X.apply (lambda x: x.count(), axis=0) 
     for idx in missing_cols.index: 
      if missing_cols [idx] == 0: 
       self.all_missing_data.append (idx) 

     return self 

    def transform (self, X, y=None): 
     print (">RemoveAllMissing - X shape: " + str (X.shape), " y shape: " + str (y.shape), 'type (X):', type(X)) 
     for all_missing_predictor in self.all_missing_data: 
      del X [all_missing_predictor] 

     print ("<RemoveAllMissing - X shape: " + str (X.shape), " y shape: " + str (y.shape), 'type (X):', type(X)) 
     return X, y 

    def fit_transform (self, X, y=None): 
     return self.fit (X, y).transform (X, y) 

class RemoveMissingRowsBasedOnTarget (BaseEstimator, TransformerMixin): 
    # remove each row where target contains one or more NaN's 
    def __init__ (self): 
     self.missing_rows = [] 

    def fit (self, X, y = None): 
     # remove all rows where the target value is missing data 
     print (type (X)) 
     if y is None: 
      print ('RemoveMissingRowsBasedOnTarget: target (y) cannot be None') 
      return self 

     self.missing_rows = np.array (y.notnull()) # false = missing data 

     return self 

    def transform (self, X, y=None): 
     print (">RemoveMissingRowsBasedOnTarget - X shape: " + str (X.shape), " y shape: " + str (y.shape), 'type (X):', type(X)) 
     if y is None: 
      print ('RemoveMissingRowsBasedOnTarget: target (y) cannot be None') 
      return X, y 

     X = X [self.missing_rows].reset_index() 
     del X ['index'] 
     y = y [self.missing_rows].reset_index() 
     del y ['index'] 

     print ("<RemoveMissingRowsBasedOnTarget - X shape: " + str (X.shape), " y shape: " + str (y.shape), 'type (X):', type(X)) 
     return X, y 

    def fit_transform (self, X, y=None): 
     return self.fit (X, y).transform (X, y) 

pipeline = Pipeline ([ 
     ('RemoveAllMissing', RemoveAllMissing()), 
     ('RemoveMissingRowsBasedOnTarget', RemoveMissingRowsBasedOnTarget()), 
     ]) 

X, y = create_data (25, 10, 0.1) 
print ("X shape: " + str (X.shape), " y shape: " + str (y.shape), 'type (X):', type(X)) 
X, y = pipeline.fit_transform (X, y) 
#X, y = RemoveAllMissing().fit_transform (X, y) 
#X, y = RemoveMissingRowsBasedOnTarget().fit_transform (X, y) 

Modifier Comme demandé @Vivek j'ai remplacé le code d'origine par le code où le problème est isolé et fonctionne autonome. Le code tel quel se bloque quelque part car un tuple est transféré en tant que paramètre au lieu d'un DataFrame. Pipeline modifie les types de données et je ne le trouve pas dans la documentation. Quand on commente l'appel à la canalisation et supprime les commentaires avant que les appels séparés du transformateur everyting fonctionne très bien, comme ceci:

#X, y = pipeline.fit_transform (X, y) 
X, y = RemoveAllMissing().fit_transform (X, y) 
X, y = RemoveMissingRowsBasedOnTarget().fit_transform (X, y) 
+0

Qu'est-ce que ' print (type (X)) 'imprime à ce moment-là?(Dans la classe 'RemoveMissingRowsBasedOnTarget', lorsqu'il est appelé en premier) Il semble que' X' doive être un DataFrame pour appeler la classe suivante ('RemoveAllMissing') mais que c'est devenu un tuple à ce moment-là ... – Eskapp

+0

Cela dépend de l'ordre d'appel: lorsque RemoveMissingRowsBasedOnTarget est appelé en premier, il imprime un DataFrame, quand il est appelé en second il imprime tuple. Les messages d'erreur se plaignent également d'un tuple n'ayant pas les méthodes rfeferred. – Arnold

+0

Vous devez ajouter un code complet facile à copier avec des exemples de données. –

Répondre

2

Ok, maintenant j'ai l'erreur, ce qui semble être avec vos classes renvoyant les deux X, y alors qu'un pipeline peut prendre l'entrée de y (et le passer le long de ses transformateurs internes), il suppose que y est constant et jamais retourné par une méthode transform(). Ce qui n'est pas le cas dans votre code. Si vous pouvez séparer cette partie ailleurs, cela peut fonctionner.

Voir this line in the source code of pipeline:

if hasattr(transformer, 'fit_transform'): 
     res = transformer.fit_transform(X, y, **fit_params) 
    else: 
     res = transformer.fit(X, y, **fit_params).transform(X) 

Vous retournez deux valeurs (x, y), mais son contenu que dans une seule variable res et, par conséquent, il devient un tuple. Ce qui échoue alors dans votre prochain transformateur.

Vous pouvez gérer ces données en décompressant le tuple dans X, y comme ceci:

class RemoveMissingRowsBasedOnTarget (BaseEstimator, TransformerMixin): 
    ... 
    ... 

    def fit (self, X, y = None): 
     # remove all rows where the target value is missing data 
     print (type (X)) 
     if isinstance(X, tuple): 
      y=X[1] 
      X=X[0] 

     ... 
     ... 

     return self 

    def transform (self, X, y=None): 
     if isinstance(X, tuple): 
      y=X[1] 
      X=X[0] 

     ... 
     ... 

     return X, y 

    def fit_transform(self, X, y=None): 
     self.fit(X, y).transform(X, y) 

Assurez-vous que vous faites cela pour tous les transformateurs ultérieurs dans le pipeline. Mais je vous recommande de séparer le traitement X et y. De plus, je trouve qu'il ya des questions pertinentes pour transformer les variables cibles y à l'intérieur du pipeline que vous pouvez consulter à:

+0

Cela fonctionne en effet, merci! Je comprends maintenant ce qui ne va pas dans mon code La décision de transmettre X et non X et y apparaît Un peu douteux, surtout quand on fait des opérations en ligne, il serait bon de les faire sur X et Y. De toute façon, c'est comme ça et merci de fournir une solution. – Arnold