Evalml: L'imputeur ne peut pas s'adapter lorsqu'il n'y a aucun dans une colonne catégorielle ou booléenne

Créé le 19 août 2020  ·  3Commentaires  ·  Source: alteryx/evalml

Reproducteur

from evalml.pipelines.components import Imputer
df = pd.DataFrame({"a": [1, 2, 3], "b": ["1", "2", None]})
imputer = Imputer()
imputer.fit(df)
from evalml.pipelines.components import Imputer
df_with_bool = pd.DataFrame({"a": [1, 2, 3], "b": [True, False, None]})
imputer = Imputer()
imputer.fit(df_with_bool)

Les deux ont le même stacktrace :

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-69-9af4cfc17aec> in <module>
      1 df_with_bool = pd.DataFrame({"a": [1, 2, 3], "b": [True, False, None]})
      2 imputer = Imputer()
----> 3 imputer.fit(df_with_bool)

~/sources/evalml/evalml/utils/base_meta.py in _set_fit(self, X, y)
     12         @wraps(method)
     13         def _set_fit(self, X, y=None):
---> 14             return_value = method(self, X, y)
     15             self._is_fitted = True
     16             return return_value

~/sources/evalml/evalml/pipelines/components/transformers/imputers/imputer.py in fit(self, X, y)
     76         X_categorical = X_null_dropped.select_dtypes(include=categorical_dtypes + boolean)
     77         if len(X_categorical.columns) > 0:
---> 78             self._categorical_imputer.fit(X_categorical, y)
     79             self._categorical_cols = X_categorical.columns
     80         return self

~/sources/evalml/evalml/utils/base_meta.py in _set_fit(self, X, y)
     12         @wraps(method)
     13         def _set_fit(self, X, y=None):
---> 14             return_value = method(self, X, y)
     15             self._is_fitted = True
     16             return return_value

~/sources/evalml/evalml/pipelines/components/transformers/imputers/simple_imputer.py in fit(self, X, y)
     42         if not isinstance(X, pd.DataFrame):
     43             X = pd.DataFrame(X)
---> 44         self._component_obj.fit(X, y)
     45         self._all_null_cols = set(X.columns) - set(X.dropna(axis=1, how='all').columns)
     46         return self

~/miniconda3/envs/evalml/lib/python3.8/site-packages/sklearn/impute/_base.py in fit(self, X, y)
    300                                                     fill_value)
    301         else:
--> 302             self.statistics_ = self._dense_fit(X,
    303                                                self.strategy,
    304                                                self.missing_values,

~/miniconda3/envs/evalml/lib/python3.8/site-packages/sklearn/impute/_base.py in _dense_fit(self, X, strategy, missing_values, fill_value)
    384                 row_mask = np.logical_not(row_mask).astype(np.bool)
    385                 row = row[row_mask]
--> 386                 most_frequent[i] = _most_frequent(row, np.nan, 0)
    387 
    388             return most_frequent

~/miniconda3/envs/evalml/lib/python3.8/site-packages/sklearn/impute/_base.py in _most_frequent(array, extra_value, n_repeat)
     40             # has already been NaN-masked.
     41             warnings.simplefilter("ignore", RuntimeWarning)
---> 42             mode = stats.mode(array)
     43 
     44         most_frequent_value = mode[0][0]

~/miniconda3/envs/evalml/lib/python3.8/site-packages/scipy/stats/stats.py in mode(a, axis, nan_policy)
    498     counts = np.zeros(a_view.shape[:-1], dtype=np.int)
    499     for ind in inds:
--> 500         modes[ind], counts[ind] = _mode1D(a_view[ind])
    501     newshape = list(a.shape)
    502     newshape[axis] = 1

~/miniconda3/envs/evalml/lib/python3.8/site-packages/scipy/stats/stats.py in _mode1D(a)
    485 
    486     def _mode1D(a):
--> 487         vals, cnts = np.unique(a, return_counts=True)
    488         return vals[cnts.argmax()], cnts.max()
    489 

<__array_function__ internals> in unique(*args, **kwargs)

~/miniconda3/envs/evalml/lib/python3.8/site-packages/numpy/lib/arraysetops.py in unique(ar, return_index, return_inverse, return_counts, axis)
    259     ar = np.asanyarray(ar)
    260     if axis is None:
--> 261         ret = _unique1d(ar, return_index, return_inverse, return_counts)
    262         return _unpack_tuple(ret)
    263 

~/miniconda3/envs/evalml/lib/python3.8/site-packages/numpy/lib/arraysetops.py in _unique1d(ar, return_index, return_inverse, return_counts)
    320         aux = ar[perm]
    321     else:
--> 322         ar.sort()
    323         aux = ar
    324     mask = np.empty(aux.shape, dtype=np.bool_)

TypeError: '<' not supported between instances of 'NoneType' and 'bool'

Cela fonctionne quand c'est np.nan au lieu de None

bug

Tous les 3 commentaires

@freddyaboulton merci pour le reproducteur clair ! Il semble que cela explique également un autre bogue #1092.

Problème
Si une fonctionnalité dans le cadre de données des pandas a le type object et contient une None , notre Imputer échoue.

  1. X = pd.DataFrame({'feature1': [False, True, None, np.nan]}) crée une fonctionnalité avec le type object . Imputer.fit échoue.
  2. X = pd.DataFrame({'feature1': [False, True, np.nan]}) crée une entité avec le type object . Imputer.fit fonctionne.
  3. X = pd.DataFrame({'feature1': [False, True]}) crée une entité avec le type bool . Imputer.fit fonctionne.

Il en est de même pour le type category . Une situation similaire se produit pour les types chaîne, bien que le dernier cas ne s'applique pas.

Remarques
La chose déroutante ici est que None peut signifier différentes choses. Il peut être identique à nan , ou il peut être conçu comme sa propre catégorie.

Je pense que c'est bien de le traiter comme nan tant que nous documentons et expliquons cette convention.

solution de contournement
Supprimer None des caractéristiques bool/catégorie/chaîne : df = df.fillna(value=np.nan)

Réparer
Court terme:

  • Mettez Imputer jour None par np.nan
  • Mettez Imputer jour le
  • Ajoutez une couverture de test de Imputer avec l'inclusion de None dans les données, pour tous les types de données souhaités.

Nous pourrions plutôt ajouter un DataCheck qui génère des erreurs s'il y a des None dans les données. Mais cela semble inutile car les None s peuvent être facilement convertis.

Long terme:
Une fois que nous aurons mis à jour evalml pour utiliser la nouvelle structure de données DataTable , les utilisateurs pourront configurer les types de chaque fonctionnalité à l'avance. J'espère que cela signifie que la normalisation rendra ce genre d'erreurs non pertinent.

Est-ce lié au #540 ?

@angela97lin 🤦 100% lié... en fait c'est un dupe. Haha. Nous avons même décidé là-bas que l'imputeur convertisse les None s en np.nan s.

Clôture #540 en faveur de cela parce que les articles ici sont plus à jour.

Merci!

Cette page vous a été utile?
0 / 5 - 0 notes