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)
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
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
Vous devez ajouter un code complet facile à copier avec des exemples de données. –