Scikit-learn: Repenser l'API CategoricalEncoder ?

Créé le 23 janv. 2018  ·  63Commentaires  ·  Source: scikit-learn/scikit-learn

Sur la base de certaines discussions que nous avons ici et des problèmes qui sont ouverts, nous doutons que CategoricalEncoder (https://github.com/scikit-learn/scikit-learn/pull/9151) était le bon choix du nom (et comme il n'est pas encore publié, nous avons de la place pour le changement).

Donc résumé de comment c'est maintenant:

  • Le nom de la classe CategoricalEncoder indique le type de données qu'elle accepte (données catégorielles)
  • L'argument mot-clé encoding spécifie comment encoder ces données

Actuellement, nous avons déjà encoding='onehot'|'onehot-dense'|'ordinal' .

Mais que faire dans les cas suivants :

  • Nous voulons ajouter plus d'options d'encodage (par exemple encodage binaire, encodage cible moyen, encodage unaire, ...). Est-ce que nous continuons à les ajouter en tant que nouvelles valeurs pour le encoding kwarg dans la seule grande classe CategoricalEncoder ?
  • Nous voulons ajouter une option spécifique à l'un des encodages (par exemple pour l'encodage 'onehot' pour supprimer la première colonne (redondant), ou pour l'encodage 'ordinal' baser l'ordre des catégories sur la fréquence, ...). Le problème ici est que nous devons ensuite ajouter des arguments de mot-clé supplémentaires à CategoricalEncoder qui sont ou ne sont pas actifs en fonction de ce que vous avez passé pour encoding kwarg, ce qui n'est pas la meilleure conception d'API.

Pour ce dernier problème, nous l'avions déjà avec l'option sparse=True/False , qui n'était pertinente que pour 'onehot' et non pour 'ordinal', et que nous avons résolue en ayant à la fois 'onehot' et 'onehot-dense' options d'encodage et non un mot-clé sparse . Mais une telle approche n'est pas non plus à grande échelle.

En lien avec cela, il existe un PR pour ajouter un UnaryEncoder (https://github.com/scikit-learn/scikit-learn/pull/8652). Il y a eu une discussion connexe sur la dénomination dans ce PR, car actuellement le nom indique comment il code, pas le type de données qu'il obtient (dans la conception actuelle, il accepte les entiers déjà codés, pas les données catégorielles réelles. À cet égard, pour être cohérent avec CategoricalEncoder, il vaut mieux le nommer OrdinalEncoder car il a besoin de données ordinales en entrée).


Quelles sont les options à venir :

1) Gardez les choses telles que nous les avons maintenant dans le maître et soyez d'accord avec l'ajout de nouvelles options à la classe unique (une question importante à laquelle il est difficile de répondre maintenant, c'est combien de nouvelles fonctionnalités nous voudrons ajouter à l'avenir) .
2) Changez le schéma de nommage et ayez un tas d''encodeurs catégoriels' où le nom indique comment il encode (OnehotEncoder, OrdinalEncoder, et plus tard peut-être BinaryEncoder, UnaryEncoder, ...)

C'est donc un peu un compromis entre l'accumulation potentielle du nombre de classes par rapport au nombre d'arguments de mots clés dans une seule classe.


Un problème avec la deuxième approche (et l'une des raisons pour lesquelles nous avons choisi CategoricalEncoder en premier lieu, avant même d'avoir ajouté les multiples options d'encodage), est qu'il existe déjà un OnehotEncoder , qui a une API différente de celle du CategoricalEncoder . Et, il n'y a pas vraiment d'autre nom que nous pourrions utiliser pour l'encodeur qui effectue un encodage à chaud.
Cependant, je pense qu'avec quelques hacks temporaires laids, nous pourrions réutiliser le nom, si nous sommes d'accord avec la dépréciation des attributs actuels (et je pense que nous convenons que ce ne sont pas les attributs les plus utiles). L'idée serait que si vous adaptez la classe avec des données de chaîne, vous obtenez le nouveau comportement, et si vous adaptez la classe avec des données entières, vous obtenez un avertissement de dépréciation indiquant que le comportement par défaut changera (et indiquant quel mot-clé spécifier pour obtenir débarrasser de l'avertissement).

cc @jnothman @amueller @GaelVaroquaux @rth

Commentaire le plus utile

L'idée de revenir à CategoricalEncoder me rend assez triste, mais je pense
vous avez raison que les futurs utilisateurs seraient moins mystifiés par l'option 2. Mon principal
l'inquiétude est que nous avons essayé de mettre en œuvre cela comme un changement à OHE pour un
longtemps et il n'a jamais volé. Il serait peut-être bon de tenter le
modifications de la docstring OneHotEncoder selon celle proposée
changer, afin que nous puissions voir si cela semble sain.

Tous les 63 commentaires

Merci pour le résumé @jorisvandenbossche. Je pense que je suis en faveur de l'option 2: réutiliser la classe OneHotEncoder , déprécier les attributs bizarres et ajouter un paramètre de constructeur pour sélectionner le comportement avec un futur avertissement qui dit que le comportement par défaut va changer mais le rend facile à faire taire cet avertissement juste en passant une valeur pour cette option.

L'idée de revenir à CategoricalEncoder me rend assez triste, mais je pense
vous avez raison que les futurs utilisateurs seraient moins mystifiés par l'option 2. Mon principal
l'inquiétude est que nous avons essayé de mettre en œuvre cela comme un changement à OHE pour un
longtemps et il n'a jamais volé. Il serait peut-être bon de tenter le
modifications de la docstring OneHotEncoder selon celle proposée
changer, afin que nous puissions voir si cela semble sain.

+1 à ce que Joël a dit

Envoyé depuis mon téléphone. Veuillez pardonner les fautes de frappe et la brièveté.​

Le 23 janvier 2018, 12h28, à 12h28, Joel Nothman [email protected] a écrit :

L'idée de revenir à CategoricalEncoder me rend assez triste, mais je
pense
vous avez raison, les futurs utilisateurs seraient moins mystifiés par l'option 2. Mon
principale
l'inquiétude est que nous avons essayé de mettre en œuvre cela comme un changement à OHE pour
une
longtemps et il n'a jamais volé. Il serait peut-être bon de tenter le
modifications de la docstring OneHotEncoder selon celle proposée
changer, afin que nous puissions voir si cela semble sain.

--
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail ou consultez-le sur GitHub :
https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment-359761818

L'idée de revenir à CategoricalEncoder me rend assez triste

Pour être clair, ce ne serait pas un revert, ce serait un refactor/rename qui garde toutes les fonctionnalités !
Mais j'aime aussi le nom "CategoricalEncoder", ce serait effectivement triste.

Cela dit, je vais rapidement essayer de faire les changements pour avoir une idée de la possibilité d'intégrer cela dans OnehotEncoder.

OK, j'ai ouvert un PR avec une preuve de concept : https://github.com/scikit-learn/scikit-learn/pull/10523.
Ce n'est pas encore terminé (aucun avertissement de dépréciation et les nouveaux attributs ne sont encore calculés dans l'ancien comportement).

La principale question de l'API concerne le format des données d'entrée.
Donc pour récapituler, il y a deux manières différentes que nous traitons actuellement les données catégorielles :

1) En tant que données catégoriques réelles, pas encore codées (entier ou chaîne), (comment cela se fait dans CategoricalEncoder ) -> déduire des catégories à partir des valeurs uniques dans les données d'entraînement
2) En tant qu'entier, données déjà encodées (comment cela se fait dans le OneHotEncoder actuel) -> déduire des catégories à partir de la valeur maximale dans les données d'entraînement

La question est la suivante : trouvons-nous que les deux cas valent la peine d'être soutenus ? Ainsi, dans le OneHotEncoder potentiellement fusionné, conservons-nous la possibilité de faire les deux, ou déprécions-nous complètement puis supprimons-nous la possibilité de traiter l'entrée ordinale ?

Si vous voulez pouvoir traiter les deux, nous pouvons ajouter un mot-clé booléen pour spécifier le type de données d'entrée (pour l'instant j'utilise encoded_input=False/True , mais d'autres idées sont ordinal_input , ...)

Pour la période de dépréciation, nous devons de toute façon prendre en charge les deux, et devons également introduire un mot-clé pour choisir le comportement (pour pouvoir faire taire l'avertissement et choisir le nouveau comportement).
Donc, en principe, nous pourrions simplement garder le mot-clé par la suite.

Étant donné que nous voulons gérer les deux, un aperçu du fonctionnement de OneHotEncoder :

  • pour l'instant encoded_input=None , et nous déduisons la valeur par défaut en fonction des données
  • si les données de type int (traitées auparavant par OneHotEncoder) encoded_input sont définies en interne sur True et un avertissement de dépréciation est déclenché. Si l'utilisateur souhaite conserver le comportement actuel, il peut le spécifier manuellement sous la forme OneHotEncoder(encoded_input=True) pour faire taire l'avertissement.
  • si l'entrée n'est pas de type int, nous définissons encoded_input interne sur False et sans avertissement, utilisons le nouveau comportement (= le comportement actuel de CategoricalEncoder)
  • à l'avenir, nous modifions la valeur par défaut de encoded_input de None à False (par défaut le nouveau comportement, également pour les données de type int)

Je ne suis toujours pas sûr de ce que vous suggérez, c'est la différence pratique due à la déduction des catégories à partir de la valeur maximale.

@jnothman Je suppose que vous reconnaissez qu'il peut y avoir une différence dans la pratique ? (la sortie que vous obtenez en fonction des données dont vous disposez)

Mais si cette différence est importante dans la pratique, je ne sais pas. C'est là que j'aimerais avoir des retours. Si quelqu'un veut réellement cette méthode basée sur la "valeur maximale", ou si nous sommes d'accord avec (à l'avenir, après la dépréciation) d'avoir uniquement la méthode basée sur les "valeurs uniques".

Personnellement, je pense que je n'aurais jamais besoin de cette méthode basée sur la valeur maximale, mais le OneHotEncoder est comme ça depuis de nombreuses années (pour une bonne raison ou non ?).

En fait, la dépréciation de la catégorisation basée sur la valeur maximale rendrait certainement la mise en œuvre (après la dépréciation) plus simple.
Et si nous choisissons pour cet itinéraire, je suis d'accord que l'option devrait plutôt être legacy_mode=True/False plutôt que encoded_input / ordinal_input

Rappelez-moi quelle est la différence réelle de sortie, lorsque n_values='auto',
s'il te plaît? J'avais pensé que la chose active_features_ les rendait fondamentalement
identique, mais j'oublie probablement quelque chose.

Aha, cela clarifie notre malentendu :-)
J'ai mal compris comment le OneHotEncoder actuel fonctionne réellement. Supposons que vous ayez une caractéristique avec les valeurs [2, 3, 5, 2]. Je pensais que le OneHotEncoder actuel aurait des catégories [0, 1, 2, 3, 4, 5] (tandis que le CategoricalEncoder actuel aurait des catégories [2, 3, 5]). Mais vous avez raison de dire que le active_features_ n'est également que [2, 3, 5], ce qui les rend essentiellement identiques avec la valeur par défaut de n_values='auto' .

C'est donc uniquement le cas où vous passez un entier à n_values (comme n_values=6 pour categories=[0, 1, 2, 3, 4, 5] dans le cas ci-dessus) pour spécifier le nombre de catégories qui seront réellement un changement d'API (obsolète / supprimée).
Et cela sera facilement remplaçable par l'utilisateur avec categories=range(6)

Désolé pour la confusion.
Dans cette optique, je pense que nous n'avons même pas besoin de l'option legacy_mode . Nous pouvons simplement traduire n_values=6 en categories=range(6) interne et lever un avertissement pour cela (mais nous devons vérifier cela avec les tests réels).

L'autre différence est la gestion des catégories invisibles. Avec le comportement actuel du OneHotEncoder, si les valeurs invisibles sont comprises dans la plage (0, max), il ne générera pas d'erreur même si handle_unknow='error' (par défaut). Mais cela peut également être résolu séparément en levant dans un tel cas un avertissement indiquant que l'utilisateur doit définir manuellement handle_unknown='ignore' pour conserver le comportement existant.

La seule caractéristique que nous perdrions est la distinction entre les catégories inconnues qui se trouvent dans la plage (0, max) (par le OneHotEncoder actuel non considéré comme « inconnu ») et celles qui sont plus grandes que cela (> max, celles-ci sont actuellement déjà considérées comme inconnu par le OneHotEncoder).

non, c'est le genre de chose que nous avons essayé avant et c'est juste trop
pointilleux. à moins qu'il n'y ait de bonnes raisons de maintenir le comportement actuel, nous
devrait juste avoir un legacy_mode pour nous amener lentement vers le futur.

non, c'est le genre de chose que nous avons essayé avant et c'est tout simplement trop capricieux.

Pouvez-vous préciser à quel aspect ce « non » fait référence ?
Au fait que je pense qu'un legacy_mode n'est pas nécessaire ?

oui, à l'idée que vous pouvez juste faire quelque chose qui est à la fois à l'envers
compatible et ce que nous voulons à l'avenir

oui, à l'idée que vous pouvez simplement faire quelque chose qui soit à la fois rétrocompatible et ce que nous voulons à l'avenir

Ce n'était pas ce que j'essayais de suggérer. Je voulais préciser qu'il est possible de ne pas avoir legacy_mode mot-clé

Donc pour être concret : une valeur non par défaut de n_values peut être dépréciée et doit être remplacée par la spécification categories . handle_unknow dans le cas de données entières doit être défini explicitement par l'utilisateur pour choisir soit une ignorance totale, soit une erreur totale au lieu du mélange actuel (sinon, un avertissement de dépréciation est déclenché).

donc si je fais .fit([[5]]).transform([[4]]), pour quelles valeurs de n_values,
categories et handle_umknown vont-ils générer une erreur ?

Le 25 janvier 2018 9h32, notifications "Joris Van den Bossche"@
a écrit:

oui, à l'idée que vous pouvez juste faire quelque chose qui est à la fois à l'envers
compatible et ce que nous voulons à l'avenir

Ce n'était pas ce que j'essayais de suggérer. Je voulais préciser que je pense qu'il
est possible de ne pas avoir de mot-clé legacy_mode, pas en l'ayant par magie
à la fois rétrocompatible et ce que nous voulons à l'avenir, mais en dépréciant
le comportement des mots-clés existants.

Donc pour être concret : une valeur non par défaut de n_values ​​peut être dépréciée et
doit être remplacé par la spécification des catégories. handle_unknow en cas de
les données entières doivent être définies explicitement par l'utilisateur pour choisir
ignorant ou erreur complète au lieu du mix actuel (et sinon dépréciation
l'avertissement est levé).

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment-360296569 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAEz6-DrQWep22_gs-hg9cC0u19B1_PSks5tN6-HgaJpZM4RpUE8
.

pouvons-nous simplement faire en sorte que pendant la dépréciation, les catégories doivent être définies
explicitement, et le mode hérité avec avertissements est-il autrement en vigueur ? Est-ce
qu'est ce que tu proposes ?

pouvons-nous simplement faire en sorte que pendant la dépréciation, les catégories doivent être définies explicitement et que le mode hérité avec avertissements soit autrement en vigueur ? C'est ce que vous proposez ?

Oui, il manque peut-être encore un cas, mais je pense que c'est possible (je le vérifierai en le codant la semaine prochaine).

Les différents cas « legacy » :

  • n_values='auto' (la valeur par défaut)

    • handle_unknown='ignore' -> bien, pas de changement de comportement

    • handle_unknown='error' -> Problème, les valeurs dans la plage sont toujours ignorées, les valeurs au-dessus de la plage sont erronées



      • Solution possible:





        • en forme, si la plage est consécutive => bien, pas de changement de comportement (pour toutes les personnes qui ont maintenant combiné LabelEncoder avec, ce qui est un cas d'utilisation typique je pense)



        • si ce n'est pas le cas : lever l'avertissement de dépréciation qu'ils doivent définir des catégories explicitement pour conserver ce comportement (et utiliser en interne le mode hérité)






  • n_values=valeur

    • cela peut être traduit en categories=[range(value)] en interne, et déclencher un avertissement de dépréciation indiquant que l'utilisateur doit le faire lui-même à l'avenir

    • dans ce cas, handle_unknown='error' / 'ignore' fonctionne comme prévu

L'avertissement de dépréciation en cas de n_values='auto' ne sera levé qu'en fit et pas lors de la construction (ce qui n'est pas vraiment idéal), mais ce n'est qu'en ajustement que nous savons que l'utilisateur le passe des données numériques et non des données de chaîne.

nous n'élevons généralement pas d'avertissements jusqu'à ce que nous soyons en forme, alors ne vous inquiétez pas
cette.

cette stratégie semble la plupart du temps bonne.

Je ne sais pas vraiment si nous devrions rechercher des chaînes dans les données,
bien que. Vous voulez fondamentalement que ce soit : le mode hérité est actif si les catégories sont
pas défini et si les données sont toutes des nombres entiers ?

Une question : si les paramètres categories et n_values ​​sont leurs valeurs par défaut, ne
nous publions categories_? Si n_values ​​est défini explicitement, publions-nous
categories_?

Le 29 janvier 2018 10h00, notifications "Joris Van den Bossche"@
a écrit:

pouvons-nous simplement faire en sorte que pendant la dépréciation, les catégories doivent être définies
explicitement, et le mode hérité avec avertissements est-il autrement en vigueur ? Est-ce
qu'est ce que tu proposes ?

Oui, il manque peut-être encore un cas, mais je pense que c'est possible (sera
vérifiez en le codant la semaine prochaine).

Les différents cas « legacy » :

  • n_values='auto' (la valeur par défaut)

    • handle_unknown='ignore' -> bien, pas de changement de comportement

    • handle_unknown='error' -> Problème, les valeurs dans la plage sont toujours

      ignoré, les valeurs au-dessus de la plage d'erreur



      • Solution possible:





        • en forme, si la plage est consécutive => fine, pas de changement dans



          comportement (pour toutes les personnes qui ont maintenant combiné LabelEncoder avec, ce qui est



          un cas d'utilisation typique je pense)



        • si ce n'est pas le cas : lever la dépréciation avertissant que



          ils doivent définir des catégories explicitement pour conserver ce comportement (et



          utiliser en interne le mode hérité)





      • n_values=valeur



    • cela peut être traduit en categories=[range(value)] en interne,

      et lever l'avertissement de dépréciation que l'utilisateur doit le faire lui-même dans le

      futur

    • dans ce cas handle_unknown='error' / 'ignore' fonctionne comme prévu

L'avertissement de dépréciation dans le cas de n_values='auto' ne sera déclenché que dans
ajustement et non sur la construction (ce qui n'est pas vraiment idéal), mais c'est seulement
dans l'ajustement que nous savons que l'utilisateur lui transmet des données numériques et non une chaîne
Les données.

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment-361104495 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAEz6x8xnyZXBLij-DCC45JyYNf8pA5kks5tPPwXgaJpZM4RpUE8
.

Vous voulez fondamentalement que ce soit : le mode hérité est actif si les catégories ne sont pas définies et si les données sont toutes des nombres entiers ?

Oui en effet (en pratique ce sera plus ou moins la même chose)

Une question : si les paramètres categories et n_values ​​sont leurs valeurs par défaut, publions-nous categories_ ? Si n_values ​​est défini explicitement, publions-nous categories_ ?

Personnellement, je fournirais déjà autant que possible les attributs de la nouvelle interface, même en mode legacy. Donc dans les deux cas je calculerais categories_ (même si ce serait un peu plus de travail)


J'ai donc essayé de mettre la logique ci-dessus dans le code (va pousser quelques mises à jour vers le PR), et j'ai une autre question pour le cas des données entières lorsque n_values ou categories n'est pas défini ( cas typique pour 'legacy_mode'). Le problème réside dans le fait que si les catégories inférées sont simplement une plage consécutive (0, 1, 2, 3, ... max), il n'y a pas de différence entre le nouveau et l'ancien comportement (héritage), et nous ne nécessairement besoin de lever un avertissement de dépréciation.
Quelques possibilités à faire dans ce cas précis :

1) Détectez ce cas (que les catégories inférées sont une plage consécutive), et dans ce cas, ne déclenchez pas d'avertissement.
- Ceci est possible à détecter (avec un peu de complexité de code supplémentaire) car nous sommes déjà en forme de toute façon
- Je pense que ce sera un cas courant lors de l'utilisation de OneHotEncoder avec des données entières, et un cas où l'utilisateur n'a en fait pas besoin de s'inquiéter de notre refactorisation, il serait donc bien de ne pas le déranger avec un avertissement
2) Levez toujours un avertissement, et indiquez dans le message d'avertissement quoi faire si vous êtes dans un tel cas (en plus d'une explication quoi faire si vous n'avez pas d'intervalle consécutif) :
- S'ils savent qu'ils n'ont que des plages consécutives en tant que catégories, ils veulent ignorer l'avertissement, nous pouvons donc ajouter au message d'avertissement une explication sur la façon de procéder (ajouter un exemple de code avec des avertissements de filtre qu'ils peuvent copier-coller)
- Un avantage potentiel de ceci est que nous pouvons également ajouter au message d'avertissement que s'ils ont utilisé le LabelEncoder pour créer les entiers, ils peuvent désormais utiliser directement OneHotEncoder (je pense qu'il s'agit actuellement d'un modèle d'utilisation typique). De cette façon, l'avertissement disparaîtra également
3) Toujours lever un avertissement mais fournir un mot-clé pour le faire taire (par exemple legacy_mode=False )
- Si nous trouvons le conseil d'utiliser une instruction filterwarnings (voir point 2 ci-dessus) trop lourd, nous pouvons également ajouter un mot-clé pour obtenir le même résultat
- L'inconvénient de ceci est l'introduction d'un mot-clé qui ne sera plus nécessaire dans quelques versions lorsque les dépréciations seront nettoyées.

Personnellement, je suis en faveur de l'option 1 ou 2. L'utilisation de LabelEncoder avant OneHotEncoder semble être un modèle typique (à partir d'une recherche rapide sur github), et dans ce cas, vous avez toujours des plages consécutives, et il n'y aura jamais de changement de comportement avec la nouvelle implémentation, nous ne devrions donc pas en avertir. En revanche, si on les avertit on peut leur signaler que s'ils ont utilisé LabelEncoder, ils n'ont plus besoin de le faire. Ce qui serait bien de donner ce conseil explicitement.
La question est de savoir à quelle fréquence les utilisateurs ont de tels entiers consécutifs en tant que catégories sans avoir utilisé LabelEncoder à l'étape précédente ..

Hmm, un cas que j'ai oublié est celui où vous avez des catégories inférées d'entiers qui ne sont
Si nous ne fournissons pas le mot-clé legacy_mode=False , le seul moyen d'obtenir le nouveau comportement est de passer manuellement categories=[1,3,5] , ce qui peut être un léger inconvénient. Cela pourrait être une raison de privilégier l'option 3 et d'abandonner mon objection à l'introduction d'un mot-clé temporaire legacy_mode=False (mais pas tout à fait sûr que cela en vaille la peine, car ce serait le seul cas* où un tel mot-clé est en fait nécessaire)

* ce seul cas = données entières avec des catégories inférées qui ne sont pas des plages consécutives, et où vous ne pouvez / ne voulez pas définir les catégories manuellement ou définir handle_unknown à ignorer.

Désolé pour tout le long texte, mais c'est assez complexe :)

Nous ne parlons que du cas où n_values ​​n'est pas défini, n'est-ce pas ?

Je vais bien avec 1., et ce ne serait pas plus cher, puisque auto
doit déjà examiner l'ensemble des étiquettes. Je pourrais aussi accepter, pour
simplicité, une variante de 3. qui était juste "OneHotEncoder fonctionnant dans l'héritage
mode. Définir categories='auto' pour un comportement légèrement différent sans
Attention."

Nous ne parlons que du cas où n_values ​​n'est pas défini, n'est-ce pas ?

Oui (l'autre cas se traduit facilement dans sa categories équivalente

une variante de 3. qui était juste "OneHotEncoder s'exécutant en mode hérité. Définissez categories='auto' pour un comportement légèrement différent sans avertissement."

Ah, ça a l'air d'être une bonne idée ! (indépendamment de la détection ou non du cas des catégories consécutives). Nous avons donc défini dans le code la valeur par défaut de categories sur Aucun (sans changer la sémantique de sa valeur par défaut), ainsi nous savons si l'utilisateur l'a défini explicitement, et de cette façon c'est un bon moyen d'indiquer legacy_mode=False sans avoir besoin de ce mot-clé supplémentaire.

Oui, mais seulement si on veut prévenir à chaque fois que quelqu'un l'utilise sans passer
catégories. C'est l'approche de mise en œuvre bon marché, mais cela pourrait être
inutilement verbeux pour les utilisateurs, c'est pourquoi je préférerais 1 s'il
peut se faire simplement.

Qu'est-ce que c'est que cet enfer :-/

OU nous pourrions nommer le nouveau DummyEncoder ;) (bien que cela soit un peu en conflit avec le DummyClassifier)

@amueller Ne lis pas tout ce qui précède !
J'avais juste l'intention de faire un joli résumé pour les nouveaux lecteurs du numéro. La discussion ci-dessus est trop compliquée (également parce que je ne comprenais toujours pas complètement le comportement complexe actuel de OneHotEncoder ... :-))

OU nous pourrions nommer le nouveau DummyEncoder ;)

Je pense que @GaelVaroquaux était contre cela parce que "one-hot" est connu pour être cela dans plus de domaines (et nous utilisons déjà "Dummy" pour d'autres choses dans scikit-learn ...)

Refaire cela pour plus de cohérence dans la dénomination n'en vaut pas la peine à mon humble avis. Nous ne sommes pas cohérents dans les noms. Pouvez-vous résumer les discussions qui ont mené à cela ?

Je pense que "factice" est ce que les statisticiens utilisent et c'est ce que les pandas utilisent.

Le premier message est toujours précis et mérite d'être lu, et il résume le raisonnement pour ne pas garder CategoricalEncoder (ce qui ne signifie pas que nous devons utiliser OneHotEncoder au lieu de, par exemple, DummyEncoder, c'est une question distincte)

J'ai lu le premier post. C'est ce à quoi j'ai fait référence quand j'ai dit "refaire ça pour plus de cohérence n'en vaut pas la peine".

les problèmes qui sont ouverts

Pouvez-vous expliquer cela?

"refaire ça pour plus de cohérence n'en vaut pas la peine"

Avec cohérence, indiquez-vous le schéma de nommage de « ce qu'il accepte » par rapport à « ce qu'il fait » ? Si oui, ce n'était qu'une raison mineure. Pour moi, c'est principalement une question d'évolutivité en ajoutant plus de fonctionnalités à une seule classe.

les problèmes qui sont ouverts

Nous avons eu le problème de savoir comment gérer les valeurs manquantes (https://github.com/scikit-learn/scikit-learn/issues/10465), et pour cela, vous pourriez vouloir un comportement différent pour l'encodage ordinal et one-hot (ou non toutes les options sont valables pour les deux, ..). Nous avons également déjà le handle_unknown existant qui n'est pertinent que pour l'encodage one-hot et non pour l'ordinal. Et il y avait https://github.com/scikit-learn/scikit-learn/issues/10518 sur la pondération des caractéristiques pour l'encodage onehot, mais pas non plus pour l'ordinal (ce problème n'était finalement pas un problème, comme vous pouvez le faire la pondération avec l'argument ColumnTransformer transformer_weights). Et nous avons également la demande de fonctionnalité pour ajouter quelque chose comme drop_first pour one-hot, ce qui n'est encore une fois pas pertinent pour l'encodage ordinal.

Je ne vois pas en quoi le changement proposé aiderait autant avec les valeurs manquantes. Et avoir des options incompatibles est quelque chose qui arrive souvent dans scikit-learn. Pas idéal, mais pas grave non plus à mon humble avis.

Je ne vois pas en quoi le changement proposé aiderait autant avec les valeurs manquantes.

Cela n'aide pas en tant que tel , mais cela rend moins complexe d'avoir des options spécifiques spécifiquement adaptées aux différents types d'encodage.

Et avoir des options incompatibles est quelque chose qui arrive souvent dans scikit-learn. Pas idéal, mais pas grave non plus à mon humble avis.

Actuellement, c'est certainement toujours OK, il n'y a pas trop d'options incompatibles (mais aussi en partie parce que j'ai déplacé sparse=True/False dans l'option encoding ). Mais la question est de savoir dans quelle mesure nous voulons étendre la fonctionnalité d'encodage dans scikit-learn à l'avenir. Ce qui est bien sûr une question difficile à répondre maintenant .
Nous avons déjà un PR pour « encodage unaire ». Cela ne devrait-il pas plutôt être ajouté à CategoricalEncoder au lieu d'ajouter une nouvelle classe UnaryEncoder ? Et si quelqu'un souhaite ajouter un « encodage binaire » ? Ou un « encodeur cible (moyen) » ?

Le "mean target encoder" est CountTransformer , il y a un PR pour ça ;)

As-tu un lien pour ça ? La recherche de "CountTransformer" ne donne aucun résultat

Désolé, CountFeaturizer #9614

C'est certainement lié, mais pas exactement un codage cible moyen. En outre, il ajoute des colonnes, ne remplace pas, donc ne fonctionnera pas encore immédiatement pour les données catégorielles de chaîne (mais c'est plus de retour sur ce PR, pas à discuter ici).

Pourquoi cela ne signifie-t-il pas un codage cible ? Mais ouais ne nous égarons pas trop ici ;)

Donc, en résumé des questions réelles auxquelles nous devons répondre (dans cet ordre !) :

  1. Conservons-nous le CategoricalEncoder actuel ? Sinon, l'idée est de le diviser en différentes classes, une classe pour chaque type d'encodage (actuellement encodage 'onehot' et 'ordinal').

  2. Si nous nous séparons en plusieurs classes, nous pourrions (idéalement ?) utiliser OneHotEncoder pour l'encodage 'onehot', mais cette classe existe déjà. Alors, intégrons-nous le nouvel encodage « onehot » (qui prend en charge les chaînes et a des paramètres différents) dans la classe OneHotEncoder existante ? Ou choisissons-nous un autre nom ? (par exemple DummyEncoder)

  3. Si nous choisissons d'intégrer le OneHotEncoder existant, sommes-nous d'accord avec les conséquences suivantes : nous déprécions un tas de mots-clés/attributs de OneHotEncoder, et un cas d'utilisation spécifique (ignorant automatiquement les valeurs invisibles dans la plage des valeurs vues) ne sera pas possible plus après la période de dépréciation.

La plupart des discussions ci-dessus concernaient la question 3 (les détails complexes sur la façon d'intégrer CategoricalEncoder(encoding='onehot') dans OneHotEncoder). Mais mettons d'abord d'accord sur une décision pour les 2 premières questions.

l'autre facteur pour moi est que tout le monde pense que le mode automatique actuel dans
OneHotEncoder est bizarre. sa mise en œuvre convertissant coo en csr est également
bizarre. il mérite une refonte. et dire aux gens "si tu veux un chaud
encoder des chaînes, allez plutôt à CategoricalEncoder" est gênant, car OHE
est déjà destiné aux catégoriques...

hum. Je suppose que nous avons conservé OneHotEncoder car il est plus efficace lorsqu'il peut être utilisé... Idéalement, nous nous débarrasserions de tous les comportements étranges. J'avais un peu voulu le déprécier mais ensuite nous ne l'avons pas fait...

J'avais un peu voulu le déprécier mais ensuite nous ne l'avons pas fait...

Dans mon POC PR (https://github.com/scikit-learn/scikit-learn/pull/10523), j'ai déprécié presque tout de OneHotEncoder, sauf son nom ...

Ce n'est pas beaucoup plus efficace. Et si LabelEncoder avait des chemins rapides pour les ints
dans la plage [0, n_values-1], si justifié, ce serait suffisant.

@amueller , êtes-vous persuadé par le fait que nous voulons finalement différents paramètres supplémentaires (par exemple drop_first, nan handling) en fonction de l'encodage, et cela justifie d'avoir un encodeur discret différent pour chaque format d'encodage ?

J'essaierai de regarder ça pendant les vacances de printemps dans deux semaines, d'accord ? Je ne sais pas si j'aurai le temps avant ça :-/

J'espère que ce n'est pas le mauvais endroit pour poser la question, mais que fait l'implémentation actuelle avec des tableaux mixtes catégoriques et non catégoriques dans une colonne ? Prenant l'exemple de https://github.com/pandas-dev/pandas/issues/17418

Considérons le dataframe df = pd.DataFrame([{'apple': 1, 'pear':'a', 'carrot': 1}, {'apple':'a', 'pear':2, 'carrot':3}, {'apple': 2, 'pear':3, 'carrot':1}, {'apple': 3, 'pear':'b', 'carrot': 1}, {'apple': 4, 'pear':4, 'carrot': 1}]) qui est égal à :

  apple  carrot pear
0     1       1    a
1     a       3    2
2     2       1    3
3     3       1    b
4     4       1    4

DictVectorizer donne exactement ce dont j'ai besoin dans ce cas.

    from sklearn.feature_extraction import DictVectorizer
    enc = DictVectorizer(sparse = False)
    enc.fit_transform(df.to_dict(orient='r'))

Cela donne:

array([[ 1.,  0.,  1.,  0.,  1.,  0.],
       [ 0.,  1.,  3.,  2.,  0.,  0.],
       [ 2.,  0.,  1.,  3.,  0.,  0.],
       [ 3.,  0.,  1.,  0.,  0.,  1.],
       [ 4.,  0.,  1.,  4.,  0.,  0.]])

On peut voir les noms des caractéristiques des colonnes avec :

    enc.feature_names_
    ['apple', 'apple=a', 'carrot', 'pear', 'pear=a', 'pear=b']

Ce serait formidable si le nouveau CategoricalEncoder avait la possibilité de faire de même.

Je ne pense pas que nous ayons l'intention de traiter ce genre de cas mixte

C'est une honte. Un sous-cas simple est celui où une colonne est numérique mais a des valeurs manquantes. Une solution simple consiste à convertir les NaN en chaînes vides, puis à utiliser DictVectorizer comme dans mon exemple ci-dessus. Cela crée effectivement une nouvelle fonctionnalité lorsque la valeur est manquante mais laisse les valeurs numériques inchangées dans le cas contraire. J'ai trouvé cette technique très utile.

Le nouveau CategoricalEncoder sera-t-il capable de faire quelque chose de similaire ?

nous avons envisagé de permettre aux utilisateurs de traiter NaN comme une catégorie distincte
ou similaire. mais ce n'est pas la même chose que de gérer des valeurs numériques arbitraires comme
différent des cordes.

Ça sonne bien.

Vous avez raison, il y a deux cas d'utilisation. Permettez-moi d'expliquer un exemple particulier où le traitement des valeurs numériques comme différentes des chaînes m'a été utile. Il y a peut-être une meilleure solution.

Supposons que vous ayez une fonction numérique entière qui prend une large plage de valeurs. Cependant, vous soupçonnez que pour certaines petites valeurs, la valeur précise est significative. Pour des valeurs plus élevées, vous pensez que ce n'est pas le cas. Une chose simple à faire est de convertir toutes les petites valeurs en chaînes, d'exécuter DictVectorizer comme ci-dessus, puis d'effectuer une sélection de fonctionnalités ou d'utiliser directement votre classificateur préféré.

Vous l'utilisez donc pour une discrétisation non linéaire ? La prochaine version est
susceptible d'inclure un discrétiseur à largeur fixe, mais faisant suite à un journal
ou une transformation quantile, il devrait agir de manière assez similaire à ce que vous
voulez... Mais la transformation de journal peut à elle seule suffire dans votre configuration.

Le 25 février 2018 à 18h10, lesshaste [email protected] a écrit :

Ça sonne bien.

Vous avez raison, il y a deux cas d'utilisation. Laissez-moi vous expliquer un exemple particulier
d'où le traitement des valeurs numériques comme différentes des chaînes a été utile
pour moi. Il y a peut-être une meilleure solution.

Supposons que vous ayez une fonction numérique entière qui prend une large plage de
valeurs. Cependant, vous soupçonnez que pour certaines petites valeurs, la valeur précise
est significatif. Pour des valeurs plus élevées, vous pensez que ce n'est pas le cas. Un simple
la chose à faire est de convertir toutes les petites valeurs en chaînes, exécutez DictVectorizer
comme ci-dessus, puis effectuez la sélection des fonctionnalités ou utilisez simplement votre favori
classificateur directement.

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment-368288727 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAEz60cmjwlDVKGyXc6oPyIC9oLbptSgks5tYQdvgaJpZM4RpUE8
.

@jnothman Oui dans un sens sauf avec une torsion. Disons que je soupçonne que certaines des valeurs de 1 à 1024 sont significatives. C'est 22 indique quelque chose de spécifique qui est assez différent de 21 ou 23. Prendre des journaux n'aidera pas ici. Mais je veux laisser toutes les valeurs supérieures à 1024 comme numériques car je ne pense pas que ces valeurs spécifiques signifient grand-chose.

Il semble que vous en sachiez trop sur votre variable pour un générique
transformer pour être le genre de chose dont vous avez besoin.

Le 25 février 2018 à 20h37, lesshaste [email protected] a écrit :

@jnothman https://github.com/jnothman Oui dans un sens sauf avec un
tourner. Disons que je soupçonne que certaines des valeurs de 1 à 1024 sont significatives.
C'est-à-dire que 22 indique quelque chose de spécifique qui est assez différent de 21 ou

  1. Prendre des journaux n'aidera pas ici. Mais je veux laisser toutes les valeurs sur
    1024 aussi numérique que je ne pense pas que ces valeurs spécifiques signifient grand-chose.

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment-368295895 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAEz65bOdVB6k7rCAcgLBYz_NslxXWV0ks5tYSnggaJpZM4RpUE8
.

@jnothman Pour être un peu plus clair, je ne sais pas si 22 est significatif. Je soupçonne juste que certaines valeurs le sont, mais je ne sais pas lesquelles ou combien il y en a. J'ai trouvé que la méthode "convertir en chaîne" puis DictVectorizer étaient très utiles pour découvrir de quoi il s'agissait.

@lesshaste Pour le problème sur les NaN en tant que catégorie distincte, voir https://github.com/scikit-learn/scikit-learn/issues/10465
Si vous souhaitez discuter plus en détail de la discrétisation non linéaire spécifique ou de l'encodage mixte numérique/chaîne, n'hésitez pas à ouvrir un nouveau numéro. Mais j'aimerais garder celui-ci concentré sur le problème d'origine, c'est-à-dire le nommage et l'organisation en différentes classes du CategoricalEncoder/OneHotEncoder.

J'essaierai de regarder ça pendant les vacances de printemps dans deux semaines, d'accord ? Je ne sais pas si j'aurai le temps avant ça :-/

@amueller c'est bien. Je n'aurai pas le temps les deux semaines à venir pour travailler sur le PR qui est bloqué par cela de toute façon. Après cela, je devrais aussi avoir à nouveau le temps de travailler dessus.

@amueller avez-vous eu le temps de jeter un coup d'œil?

@amueller êtes-vous d'accord avec le fait que je travaille sur le PR pour diviser CategoricalEncoder en OrdinalEncoder et OneHotEncoder (et en désapprouvant les arguments actuels de OneHotEncoder) ?

Désolé d'être absent. Cela semble correct, mais pouvez-vous peut-être me donner deux semaines pour que je puisse réellement réviser ? Merci!

@amueller pas de problème, pour moi pareil :-)
Mais, je prévois maintenant de regarder cela à nouveau. Donc si vous pouviez y jeter un œil, ce serait le bienvenu. J'ai du travail à faire sur le PR (https://github.com/scikit-learn/scikit-learn/pull/10523), alors ne l'examinez pas encore en détail (vous pouvez le regarder pour avoir une idée de ce que nous proposons cependant).
Je pense que la principale question à laquelle je veux voir une réponse avant d'y consacrer beaucoup de temps est de savoir si vous êtes d'accord pour diviser CategoricalEncoder en plusieurs classes, et dans ce cas, si vous êtes d'accord pour réutiliser OneHotEncoder (ce qui signifie déprécier certaines de ses fonctionnalités actuelles (étranges)). Ces questions sont résumées dans https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment-363851328 et https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment -364802471.

(et une fois que nous sommes d'accord sur cette partie, il y a encore beaucoup à discuter sur la mise en œuvre réelle dans le PR :))

J'ai mis à jour le PR https://github.com/scikit-learn/scikit-learn/pull/10523 , prêt pour examen

Je dirai prudemment que je suis de retour ;)

À mon humble avis, la chose la plus importante est une API universelle (c'est-à-dire des paramètres et des modèles de comportement) pour tous les encodeurs dont nous discutons

PS https://github.com/scikit-learn-contrib/categorical-encoding ?

Dans le package category_encoders , tous les encodeurs ont un argument cols , similaire au categorical_features de l'ancien OneHotEncoder (bien qu'il n'accepte pas exactement le même type de valeurs). Voir par exemple http://contrib.scikit-learn.org/categorical-encoding/onehot.html
Cela est donc lié à la discussion actuelle que nous avons dans https://github.com/scikit-learn/scikit-learn/pull/10523 sur la dépréciation de categorical_features ou non.

Pour le reste je pense qu'il n'y a pas vraiment de mots clés en conflit (ils en ont d'autres spécifiques aux dataframes que nous n'ajouterons pas à sklearn à ce stade). La dénomination pour OneHotEncoder et OrdinalEncoder est au moins cohérente avec le package category_encoders .

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