Scikit-learn: ¿Está repensando la API CategoricalEncoder?

Creado en 23 ene. 2018  ·  63Comentarios  ·  Fuente: scikit-learn/scikit-learn

Según algunas discusiones que estamos teniendo aquí y los problemas que están abiertos, tenemos algunas dudas de que CategoricalEncoder (https://github.com/scikit-learn/scikit-learn/pull/9151) fue lo bueno elección del nombre (y dado que aún no se ha publicado, tenemos margen de cambio).

Entonces resumen de cómo está ahora:

  • El nombre de la clase CategoricalEncoder dice qué tipo de datos acepta (datos categóricos)
  • El argumento de palabra clave encoding especifica cómo codificar esos datos

Actualmente ya tenemos encoding='onehot'|'onehot-dense'|'ordinal' .

Pero qué hacer en los siguientes casos:

  • Queremos agregar más opciones de codificación (por ejemplo, codificación binaria, codificación de destino medio, codificación unaria, ...). ¿Seguimos agregando esos como nuevos valores para el encoding kwarg en la única clase grande CategoricalEncoder ?
  • Queremos agregar una opción específica a una de las codificaciones (por ejemplo, para la codificación 'onehot' para eliminar la primera columna (redundante), o para la codificación 'ordinal' basar el orden de las categorías en la frecuencia, ...). El problema aquí es que luego necesitamos agregar argumentos de palabras clave adicionales a CategoricalEncoder que estén o no activos dependiendo de lo que haya pasado por encoding kwarg, que no es el mejor diseño de API.

Para ese último problema, ya teníamos esto con la opción sparse=True/False , que solo era relevante para 'onehot' y no para 'ordinal', y que resolvimos teniendo tanto 'onehot' como 'onehot-dense' opciones de codificación y no una palabra clave sparse . Pero tal enfoque tampoco escala.

En relación con esto, hay un PR para agregar un UnaryEncoder (https://github.com/scikit-learn/scikit-learn/pull/8652). Hubo una discusión relacionada sobre el nombre en ese RP, ya que actualmente el nombre dice cómo se codifica, no qué tipo de datos obtiene (en el diseño actual, acepta números enteros ya codificados, no datos categóricos reales. En ese sentido, para ser coherente con CategoricalEncoder, es mejor que se llame OrdinalEncoder porque necesita datos ordinales como entrada).


Cuáles son las opciones hacia adelante:

1) Mantenga las cosas como las tenemos ahora en el maestro, y esté de acuerdo con agregar algunas opciones nuevas a la clase única (una pregunta importante que es difícil de responder ahora es cuántas características nuevas querremos agregar en el futuro) .
2) Cambie el esquema de nombres y tenga un montón de 'codificadores categóricos' donde el nombre dice cómo se codifica (OnehotEncoder, OrdinalEncoder, y más tarde tal vez BinaryEncoder, UnaryEncoder, ...)

Por lo tanto, es un poco una compensación de la acumulación potencial del número de clases frente al número de argumentos de palabras clave en una sola clase.


Un problema con el segundo enfoque (y una de las razones por las que elegimos CategoricalEncoder en primer lugar, incluso antes de agregar las múltiples opciones de codificación), es que ya existe un OnehotEncoder , que tiene una API diferente a la CategoricalEncoder . Y, realmente no hay otro buen nombre que podamos usar para el codificador que hace codificación one-hot.
Sin embargo, creo que, con algunos trucos feos temporales, podríamos reutilizar el nombre, si estamos de acuerdo con desaprobar los atributos actuales (y creo que estamos de acuerdo en que no son los atributos más útiles). La idea sería que si ajusta la clase con datos de cadena, obtiene el nuevo comportamiento, y si ajusta la clase con datos enteros, obtiene una advertencia de desaprobación que indica que el comportamiento predeterminado cambiará (e indica qué palabra clave especificar para obtener deshacerse de la advertencia).

cc @jnothman @amueller @GaelVaroquaux @rth

Comentario más útil

La idea de revertir CategoricalEncoder me pone bastante triste, pero creo que
tiene razón en que los futuros usuarios estarían menos desconcertados con la opción 2. Mi
La preocupación es que hemos intentado implementar esto como un cambio a OHE para un
mucho tiempo y nunca voló. Quizás sería bueno intentar el
modificaciones a la cadena de documentos OneHotEncoder de acuerdo con lo propuesto
cambiar, para que podamos ver si parece cuerdo.

Todos 63 comentarios

Gracias por el resumen @jorisvandenbossche. Creo que estoy a favor de la opción 2: reutilizar OneHotEncoder class, desaprobar los atributos extraños y agregar un parámetro de constructor para seleccionar el comportamiento con una advertencia futura que dice que el comportamiento predeterminado cambiará pero facilita el silencio esa advertencia simplemente pasando un valor para esa opción.

La idea de revertir CategoricalEncoder me pone bastante triste, pero creo que
tiene razón en que los futuros usuarios estarían menos desconcertados con la opción 2. Mi
La preocupación es que hemos intentado implementar esto como un cambio a OHE para un
mucho tiempo y nunca voló. Quizás sería bueno intentar el
modificaciones a la cadena de documentos OneHotEncoder de acuerdo con lo propuesto
cambiar, para que podamos ver si parece cuerdo.

+1 a lo que dijo Joel

⁣Enviado desde mi teléfono. Perdone los errores tipográficos y la brevedad.

El 23 de enero de 2018, 12:28, a las 12:28, Joel Nothman [email protected] escribió:

La idea de revertir CategoricalEncoder me entristece bastante, pero
pensar
tiene razón en que los futuros usuarios estarían menos desconcertados con la opción 2. Mi
principal
La preocupación es que hemos intentado implementar esto como un cambio a OHE para
a
mucho tiempo y nunca voló. Quizás sería bueno intentar el
modificaciones a la cadena de documentos OneHotEncoder de acuerdo con lo propuesto
cambiar, para que podamos ver si parece cuerdo.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente o véalo en GitHub:
https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment -359761818

La idea de revertir CategoricalEncoder me pone bastante triste

Para ser claros, no sería una reversión, sería una refactorización / cambio de nombre que mantiene toda la funcionalidad.
Pero también me gusta el nombre "CategoricalEncoder", que de hecho sería triste.

Dicho esto, intentaré hacer los cambios rápidamente para tener una idea de cómo es posible integrar esto en OnehotEncoder.

OK, abrí un PR con una prueba de concepto: https://github.com/scikit-learn/scikit-learn/pull/10523.
Aún no está completo (no hay advertencias de desaprobación y los nuevos atributos aún no se calculan en el comportamiento anterior).

La pregunta principal de la API es sobre el formato de los datos de entrada.
Entonces, a modo de resumen, hay dos formas diferentes en las que actualmente procesamos los datos categóricos :

1) Como datos categóricos reales, aún no codificados (entero o cadena) (cómo se hace en CategoricalEncoder ) -> inferir categorías a partir de los valores únicos en los datos de entrenamiento
2) Como entero, datos ya codificados (cómo se hace en el actual OneHotEncoder ) -> inferir categorías a partir del valor máximo en los datos de entrenamiento

La pregunta es: ¿creemos que vale la pena apoyar ambos casos? Por lo tanto, en el OneHotEncoder potencialmente fusionado, ¿mantenemos la capacidad de hacer ambas cosas, o desaprobamos por completo y luego eliminamos la capacidad de procesar la entrada ordinal?

Si queremos tener la capacidad de procesar ambos, podemos agregar una palabra clave booleana para especificar el tipo de datos de entrada (por ahora uso encoded_input=False/True , pero otras ideas son ordinal_input , ...)

Para el período de desactivación, tenemos que admitir ambos de todos modos, y también tenemos que introducir una palabra clave para elegir el comportamiento (para poder silenciar la advertencia y elegir el nuevo comportamiento).
Entonces, en principio, podríamos mantener la palabra clave después.

Dado que queremos manejar ambos, una descripción general de cómo funcionaría OneHotEncoder:

  • por ahora encoded_input=None , y deducimos el valor predeterminado en función de los datos
  • si los datos de tipo int (manejados antes por OneHotEncoder) encoded_input se establecen internamente en True y se genera una advertencia de obsolescencia. Si el usuario desea mantener el comportamiento actual, puede especificarlo manualmente como OneHotEncoder(encoded_input=True) para silenciar la advertencia.
  • si la entrada no es int-like, establecemos encoded_input internamente en False y sin advertencia usamos el nuevo comportamiento (= el comportamiento actual de CategoricalEncoder)
  • en el futuro, cambiamos el valor predeterminado de encoded_input de Ninguno a Falso (de forma predeterminada, el nuevo comportamiento, también para datos de tipo int)

Todavía no estoy seguro de lo que está sugiriendo es la diferencia práctica debido a inferir categorías del valor máximo.

@jnothman ¿Supongo que reconoce que puede haber una diferencia en la práctica? (la salida que obtiene dependiendo de los datos que tenga)

Pero si esta diferencia es importante en la práctica, no lo sé. Ahí es donde me gustaría ver comentarios. Si alguien realmente quiere este método basado en "valor máximo", o si estamos bien con (en el futuro, después de la desaprobación) solo tener el método basado en "valores únicos".

Creo que personalmente nunca necesitaría este método basado en el valor máximo, pero OneHotEncoder ha sido así durante muchos años (¿por una buena razón o no?).

En realidad, la desaprobación de la categorización basada en el valor máximo sin duda simplificaría la implementación (después de la desaprobación).
Y si elegimos esa ruta, estoy de acuerdo en que la opción debería ser legacy_mode=True/False lugar de encoded_input / ordinal_input

Recuérdame cuál es la diferencia real en la salida, cuando n_values ​​= 'auto',
¿por favor? Pensé que lo active_features_ los hacía básicamente
idéntico, pero probablemente me estoy olvidando de algo.

Ajá, eso aclara nuestro malentendido :-)
Entendí mal cómo funciona realmente el OneHotEncoder actual. Suponga que tiene una característica con valores [2, 3, 5, 2]. Pensé que el OneHotEncoder actual tendría categorías [0, 1, 2, 3, 4, 5] (mientras que el CategoricalEncoder actual tendría categorías [2, 3, 5]). Pero tienes razón en que active_features_ también es solo [2, 3, 5], esencialmente haciéndolos iguales con el valor predeterminado de n_values='auto' .

Por lo tanto, es solo el caso en el que pasa un número entero a n_values (como n_values=6 para categorías = [0, 1, 2, 3, 4, 5] en el caso anterior) para especificar el número de categorías que realmente serán un cambio de API (obsoleto / eliminado).
Y eso será fácilmente reemplazado por el usuario con categories=range(6)

Perdón por la confusion.
En ese sentido, creo que ni siquiera necesitamos la opción legacy_mode . Podemos simplemente traducir n_values=6 a categories=range(6) internamente y generar una advertencia para eso (pero necesitamos verificar esto con las pruebas reales).

La otra diferencia es el manejo de categorías invisibles. Con el comportamiento actual de OneHotEncoder, si los valores invisibles están dentro del rango (0, máximo), no generará un error incluso si handle_unknow='error' (el valor predeterminado). Pero también eso se puede resolver por separado, en tal caso, emitiendo una advertencia de que el usuario debe configurar handle_unknown='ignore' manualmente para mantener el comportamiento existente.

La única característica que perderíamos es la distinción entre categorías desconocidas que están dentro del rango (0, max) (por el OneHotEncoder actual no considerado como 'desconocido') y aquellas que son más grandes que eso (> max, esas ya están consideradas actualmente como desconocido por OneHotEncoder).

no, ese es el tipo de cosas que hemos probado antes y es demasiado
melindroso. a menos que exista una buena razón para mantener el comportamiento actual,
debería tener un legacy_mode para llevarnos lentamente al futuro.

no, ese es el tipo de cosas que hemos probado antes y es demasiado delicado.

¿Puede aclarar a qué aspecto se refiere este "no"?
¿Al hecho de que creo que un legacy_mode no es necesario?

sí, a la idea de que puedes hacer algo que sea al revés
compatible y lo que queremos en el futuro

sí, a la idea de que puedes hacer algo que sea compatible con versiones anteriores y lo que queremos en el futuro

Eso no fue lo que intenté sugerir. Quería dejar en claro que creo que es posible no tener una palabra clave legacy_mode , no por tenerla mágicamente compatible con versiones anteriores y lo que queremos en el futuro, sino por desaprobar el comportamiento de las palabras clave existentes.

Entonces, para ser concreto: un valor no predeterminado de n_values puede quedar obsoleto y debe ser reemplazado por la especificación categories . handle_unknow en caso de datos enteros debe ser establecido explícitamente por el usuario para elegir entre ignorar por completo o error completo en lugar de la combinación actual (y de lo contrario, se genera una advertencia de desaprobación).

así que si lo hago .fit ([[5]]). transform ([[4]]), para qué valores de n_values,
categorías y handle_umknown ¿generará un error?

El 25 de enero de 2018 a las 9:32 a. M., "Joris Van den Bossche" [email protected]
escribió:

sí, a la idea de que puedes hacer algo que sea al revés
compatible y lo que queremos en el futuro

Eso no fue lo que intenté sugerir. Quería dejar claro que piénsalo
es posible no tener una palabra clave legacy_mode, no por tenerla mágicamente
compatibilidad con versiones anteriores y lo que queremos en el futuro, pero desaprobando
el comportamiento de las palabras clave existentes.

Para ser concreto: un valor no predeterminado de n_values ​​puede quedar obsoleto y
tiene que ser reemplazado por la especificación de categorías. handle_unknow en caso de
Los datos enteros deben ser establecidos explícitamente por el usuario para elegir entre
ignorar o cometer errores completos en lugar de la combinación actual (y, de lo contrario, la desaprobación
se emite una advertencia).

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment-360296569 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AAEz6-DrQWep22_gs-hg9cC0u19B1_PSks5tN6-HgaJpZM4RpUE8
.

¿Podemos simplemente hacer que durante la desactivación, las categorías deban establecerse
explícitamente, y el modo heredado con advertencias está en efecto de otra manera? Es eso
que estas sugiriendo

¿Podemos simplemente hacer que durante la desaprobación, las categorías deban establecerse explícitamente y el modo heredado con advertencias esté en efecto? Es eso lo que está sugiriendo?

Sí, es posible que todavía falte el caso, pero creo que esto es posible (lo comprobaré codificándolo la próxima semana).

Los diferentes casos de 'legado':

  • n_values ​​= 'auto' (el predeterminado)

    • handle_unknown = 'ignore' -> bien, sin cambios en el comportamiento

    • handle_unknown = 'error' -> Problema, los valores en el rango aún se ignoran, los valores por encima del rango de error



      • Solución posible:





        • en forma, si el rango es consecutivo => bien, no hay cambios en el comportamiento (para todas las personas que ahora combinaron LabelEncoder con él, que es un caso de uso típico, creo)



        • si este no es el caso: genere una advertencia de desaprobación de que tienen que establecer categorías explícitamente para mantener este comportamiento (y usar internamente el modo heredado)






  • n_values ​​= valor

    • esto se puede traducir a categorías = [rango (valor)] internamente y generar una advertencia de desaprobación de que el usuario debe hacerlo por sí mismo en el futuro

    • en este caso handle_unknown='error' / 'ignore' funcionan como se esperaba

La advertencia de depreciación en el caso de n_values='auto' solo se aumentará en fit y no en la construcción (lo cual no es realmente ideal), pero solo es adecuado que sepamos que el usuario la está pasando. datos numéricos y no datos de cadena.

En cualquier caso, no solemos generar advertencias hasta que encaje en cualquier caso, así que no se preocupe por
ese.

esa estrategia suena bastante bien.

En realidad, no estoy seguro de si deberíamos buscar cadenas en los datos,
aunque. Básicamente quiere que sea: el modo heredado está activo si las categorías están
no establecido y si los datos son todos enteros?

Una pregunta: si las categorías y los parámetros n_values ​​son sus valores predeterminados,
publicamos categorías_? Si n_values ​​se establece explícitamente, publicamos
categorías_?

El 29 de enero de 2018 a las 10:00 a. M., "Joris Van den Bossche" [email protected]
escribió:

¿Podemos simplemente hacer que durante la desactivación, las categorías deban establecerse
explícitamente, y el modo heredado con advertencias está en efecto de otra manera? Es eso
que estas sugiriendo

Sí, es posible que todavía falte el caso, pero creo que esto es posible (¿
compruébelo codificándolo la semana que viene).

Los diferentes casos de 'legado':

  • n_values ​​= 'auto' (el predeterminado)

    • handle_unknown = 'ignore' -> bien, sin cambios en el comportamiento

    • handle_unknown = 'error' -> Problema, los valores en el rango siguen siendo

      ignorado, valores por encima del rango de error



      • Solución posible:





        • en forma, si el rango es consecutivo => bien, no hay cambios en



          comportamiento (para todas las personas que ahora combinaron LabelEncoder con él, que es



          un caso de uso típico, creo)



        • si este no es el caso: aumente la desaprobación advirtiendo que



          tienen que establecer categorías explícitamente para mantener este comportamiento (y



          utilizar internamente el modo heredado)





      • n_values ​​= valor



    • esto se puede traducir a categorías = [rango (valor)] internamente,

      y generar una advertencia de desaprobación de que el usuario debe hacerlo ellos mismos en el

      futuro

    • en este caso, handle_unknown = 'error' / 'ignore' funciona como se esperaba

La advertencia de desaprobación en el caso de n_values ​​= 'auto' solo se generará en
encajar y no sobre la construcción (que no es realmente ideal), pero es sólo
en forma que sabemos que el usuario le está pasando datos numéricos y no cadenas
datos.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment-361104495 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AAEz6x8xnyZXBLij-DCC45JyYNf8pA5kks5tPPwXgaJpZM4RpUE8
.

Básicamente, quiere que sea: el modo heredado está activo si las categorías no están configuradas y si los datos son todos enteros.

Sí, de hecho (en la práctica será más o menos lo mismo)

Una pregunta: si las categorías y los parámetros n_values ​​son sus valores predeterminados, ¿publicamos categorías_? Si n_values ​​se establece explícitamente, ¿publicamos categorías_?

Yo personalmente ya proporcionaría en la medida de lo posible los atributos de la nueva interfaz, incluso en el modo heredado. Entonces, en ambos casos, calcularía categories_ (incluso si sería un poco más de trabajo)


Así que intenté poner la lógica anterior en el código (enviaré algunas actualizaciones al PR), y tengo una pregunta más para el caso de datos enteros cuando n_values o categories no están configurados ( caso típico de 'legacy_mode'). El problema radica en el hecho de que si las categorías inferidas son simplemente un rango consecutivo (0, 1, 2, 3, ... máx.), No hay diferencia entre el comportamiento nuevo y antiguo (heredado), y nosotros no necesariamente debe generar una advertencia de desaprobación.
Algunas posibilidades para hacer en este caso específico:

1) Detecte este caso (que las categorías inferidas son un rango consecutivo) y, en ese caso, no genere una advertencia.
- Esto es posible de detectar (con un poco de complejidad de código adicional) ya que de todos modos ya estamos en forma
- Creo que este será un caso común al usar OneHotEncoder con datos enteros, y un caso en el que el usuario en realidad no necesita preocuparse por nuestra refactorización, por lo que sería bueno no molestarlo con una advertencia.
2) Siempre emita una advertencia e indique en el mensaje de advertencia qué hacer si se encuentra en tal caso (además de una explicación de qué hacer si no tiene un rango consecutivo):
- Si saben que solo tienen rangos consecutivos como categorías, quieren ignorar la advertencia, por lo que podemos agregar al mensaje de advertencia una explicación de cómo hacer esto (agregue una muestra de código con advertencias de filtro que pueden copiar y pegar)
- Una ventaja potencial de esto es que también podemos agregar al mensaje de advertencia que si usaron LabelEncoder para crear los números enteros, ahora pueden usar directamente OneHotEncoder (creo que actualmente este es un patrón de uso típico). De esa forma, la advertencia también desaparecerá.
3) Siempre envíe una advertencia, pero proporcione una palabra clave para silenciarla (por ejemplo, legacy_mode=False )
- Si consideramos que el consejo de usar una instrucción filterwarnings (consulte el punto 2 anterior) es demasiado engorroso, también podríamos agregar una palabra clave para obtener el mismo resultado
- La desventaja de esto es la introducción de una palabra clave que ya no será necesaria en algunas versiones cuando se eliminen las obsoletas.

Personalmente estoy a favor de la opción 1 o 2. Usar LabelEncoder antes de OneHotEncoder parece ser un patrón típico (de una búsqueda rápida en github), y en ese caso siempre tienes rangos consecutivos, y nunca habrá un cambio de comportamiento con la nueva implementación, por lo que no deberíamos advertir sobre ello. Por otro lado, si advertimos podemos señalarles el hecho de que si usaron LabelEncoder, ya no necesitan hacerlo. Lo cual sería bueno dar este consejo explícitamente.
La pregunta es con qué frecuencia los usuarios tienen números enteros consecutivos como categorías sin haber usado LabelEncoder como paso anterior.

Hmm, un caso que olvidé es cuando tienes categorías inferidas de enteros que no son consecutivas (digamos [1,3,5]), pero quieres el nuevo comportamiento y no el comportamiento heredado (en ese caso, no puedes simplemente ignorar la advertencia , ya que eso manejaría los valores no vistos de manera diferente en el paso de transformación, es decir, los valores entre el rango (por ejemplo, 2) no generarán un error).
En caso de que no proporcionemos la palabra clave legacy_mode=False , la única forma de obtener el nuevo comportamiento es pasando manualmente categories=[1,3,5] , lo que puede ser un pequeño inconveniente. Esa podría ser una razón para favorecer la opción 3 y renunciar a mi objeción sobre la introducción de una palabra clave temporal legacy_mode=False (pero tampoco estoy completamente seguro de que valga la pena, ya que este sería el único caso * en el que dicha palabra clave es en realidad necesario)

* este único caso = datos enteros con categorías inferidas que no son un rango consecutivo, y donde no puede / no desea establecer las categorías manualmente o establecer handle_unknown para ignorar.

Perdón por todo el texto extenso, pero es bastante complejo :)

Solo estamos hablando del caso en el que n_values ​​no está establecido, ¿verdad?

Estoy bien con 1., y no sería más caro, ya que auto
ya necesita examinar el conjunto de etiquetas. También podría aceptar, por
simplicidad, una variante de 3. que era simplemente "OneHotEncoder ejecutándose en versiones heredadas
modo. Establecer categorías = 'auto' para un comportamiento ligeramente diferente sin un
advertencia."

Solo estamos hablando del caso en el que n_values ​​no está establecido, ¿verdad?

Sí (el otro caso se puede traducir fácilmente en su categories equivalente

una variante de 3. que era simplemente "OneHotEncoder ejecutándose en modo heredado. Establecer categorías = 'auto' para un comportamiento ligeramente diferente sin una advertencia".

¡Ah, eso suena como una buena idea! (independientemente de que se detecte el caso de categorías consecutivas o no). Así que configuramos en el código el valor predeterminado de categories en Ninguno (sin cambiar la semántica de su valor predeterminado), para saber si el usuario lo configuró explícitamente, y de esa manera es una buena manera de indicar legacy_mode=False sin necesidad de esa palabra clave adicional.

Sí, pero solo si queremos avisar cada vez que alguien lo usa sin pasar
categorías. Es el enfoque de implementación económico, pero podría ser
innecesariamente detallado para los usuarios, por lo que preferiría 1 si
se puede hacer de forma sencilla.

Qué nuevo infierno es este: - /

O podríamos nombrar el nuevo DummyEncoder ;) (aunque eso es un poco conflictivo con el DummyClassifier)

@amueller ¡No leas todo lo anterior!
Solo estaba planeando hacer un buen resumen para los nuevos lectores del tema. La discusión anterior es demasiado complicada (también porque todavía no entendía completamente el complejo comportamiento actual de OneHotEncoder ... :-))

O podríamos nombrar el nuevo DummyEncoder;)

Creo que @GaelVaroquaux estaba en contra de eso porque se sabe que "one-hot" es esto en más campos (y ya usamos 'Dummy' para otras cosas en scikit-learn ...)

Rehacer esto para que haya consistencia en el nombre no vale la pena, en mi humilde opinión. No somos consistentes al nombrar en ninguna parte. ¿Podría resumir las discusiones que llevaron a esto?

Creo que "ficticio" es lo que usan los estadísticos y es lo que usan los pandas.

La publicación superior sigue siendo precisa y vale la pena leerla, y resume el razonamiento para no mantener CategoricalEncoder (lo que no significa que debamos usar OneHotEncoder en lugar de, por ejemplo, DummyEncoder, esa es una pregunta separada)

Leí la publicación superior. A eso me referí cuando dije "no vale la pena rehacer esto para mantener la coherencia".

problemas que se abren

¿Puedes explicar eso?

"no vale la pena rehacer esto para mantener la coherencia"

Con coherencia, ¿está apuntando al esquema de nomenclatura de "lo que acepta" frente a "lo que hace"? Si es así, esa fue solo una razón menor. Para mí, es principalmente una cuestión de escalabilidad al agregar más funciones a una sola clase.

problemas que se abren

Tuvimos el problema de cómo manejar los valores faltantes (https://github.com/scikit-learn/scikit-learn/issues/10465), y para esto, es posible que desee un comportamiento diferente para la codificación ordinal y one-hot (o no todas las opciones son válidas para ambos, ..). También ya tenemos el handle_unknown que solo es relevante para la codificación one-hot y no para ordinal. Y hubo https://github.com/scikit-learn/scikit-learn/issues/10518 sobre la ponderación de funciones para la codificación onehot, pero tampoco relevante para ordinal (este problema al final no fue un problema, como puede hacer la ponderación con el argumento transformer_weights de ColumnTransformer). Y también tenemos la solicitud de función para agregar algo como drop_first para one-hot, que nuevamente no es relevante para la codificación ordinal.

No veo cómo el cambio propuesto ayudaría tanto con los valores faltantes. Y tener opciones incompatibles es algo que sucede a menudo en scikit-learn. No es ideal, pero tampoco es un gran problema en mi humilde opinión.

No veo cómo el cambio propuesto ayudaría tanto con los valores faltantes.

No ayuda como tal , pero hace que sea menos complejo tener opciones específicas adaptadas específicamente a los diferentes tipos de codificación.

Y tener opciones incompatibles es algo que sucede a menudo en scikit-learn. No es ideal, pero tampoco es un gran problema en mi humilde opinión.

Actualmente, ciertamente todavía está bien, no hay demasiadas opciones incompatibles (pero también en parte porque moví sparse=True/False a la opción encoding ). Pero la pregunta es hasta qué punto queremos expandir la funcionalidad de codificación en scikit-learn en el futuro. Lo cual, por supuesto, es una pregunta difícil de responder ahora .
Ya tenemos un PR para 'codificación unaria'. ¿No debería agregarse esto a CategoricalEncoder en lugar de agregar una nueva clase UnaryEncoder? ¿Y si alguien quiere agregar una 'codificación binaria'? ¿O un 'codificador de destino (medio)'?

El "codificador de destino medio" es CountTransformer , hay un PR para eso;)

¿Tiene un enlace para eso? La búsqueda de "CountTransformer" no da ningún resultado

Lo siento, CountFeaturizer # 9614

Ciertamente está relacionado, pero no es exactamente una codificación de destino media. Además, agrega columnas, no reemplaza, por lo que aún no funcionará de inmediato para datos categóricos de cadenas (pero eso es más comentarios sobre ese PR, no para discutir aquí).

¿Por qué no se trata de codificación de destino? Pero sí, no nos desviemos demasiado aquí;)

Entonces, como resumen de las preguntas reales, debemos responder (¡en este orden!):

  1. ¿Mantenemos los CategoricalEncoder actuales? Si no, la idea es dividirlo en diferentes clases, una clase para cada tipo de codificación (actualmente codificación 'onehot' y 'ordinal').

  2. Si nos dividimos en varias clases, podríamos (¿idealmente?) Usar OneHotEncoder para la codificación 'onehot', pero esta clase ya existe. Entonces, ¿integramos la nueva codificación 'onehot' (que admite cadenas y tiene diferentes parámetros) en la clase OneHotEncoder existente? ¿O elegimos otro nombre? (por ejemplo, DummyEncoder)

  3. Si elegimos integrarnos en el OneHotEncoder existente, estamos de acuerdo con las siguientes consecuencias: desaprobamos un montón de palabras clave / atributos de OneHotEncoder, y un caso de uso específico (ignorando automáticamente los valores no vistos dentro del rango de valores vistos) no será posible más después del período de depreciación.

La mayor parte de la discusión anterior fue sobre la pregunta 3 (los detalles complejos de cómo integrar CategoricalEncoder (encoding = 'onehot') en OneHotEncoder). Pero primero acuerdemos una decisión para las dos primeras preguntas.

el otro factor para mí es que todos piensan que el modo automático actual en
OneHotEncoder es extraño. su implementación convirtiendo coo a csr también es
extraño. merece un rediseño. y decirle a la gente "si quieres una caliente
codificar cadenas, vaya a CategoricalEncoder en su lugar "es incómodo, porque OHE
ya está destinado a categóricos ...

hrm. Supongo que mantuvimos OneHotEncoder porque es más eficiente cuando se puede usar ... Lo ideal sería deshacernos de todos los comportamientos extraños. Un poco había querido desaprobarlo, pero luego no lo hicimos ...

Un poco había querido desaprobarlo, pero luego no lo hicimos ...

En mi POC PR (https://github.com/scikit-learn/scikit-learn/pull/10523), desaprobé casi todo de OneHotEncoder, excepto su nombre ...

No es mucho más eficiente. Y si LabelEncoder tuviera rutas rápidas para ints
en el rango [0, n_values-1], si se justifica, sería suficiente.

@amueller , ¿está persuadido por el problema de que, en última instancia, queremos diferentes parámetros adicionales (por ejemplo, drop_first, nan handling) dependiendo de la codificación, y eso justifica tener un codificador discreto diferente para cada formato de codificación?

Intentaré ver esto en las vacaciones de primavera en dos semanas, ¿de acuerdo? no estoy seguro de si tendré tiempo antes de eso: - /

Espero que este no sea el lugar equivocado para preguntar, pero ¿qué hace la implementación actual con tablas que se mezclan categóricas y no categóricas en una columna? Tomando el ejemplo de https://github.com/pandas-dev/pandas/issues/17418

Considere el marco de datos 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}]) que es igual a:

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

DictVectorizer da exactamente lo que necesito en este caso.

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

Esto da:

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.]])

Podemos ver los nombres de las características de las columnas con:

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

Sería genial si el nuevo CategoricalEncoder tuviera una opción para hacer lo mismo.

No creo que tengamos la intención de manejar ese tipo de caso mixto

Es una pena. Un caso secundario simple es cuando una columna es numérica pero tiene algunos valores faltantes. Una solución simple es convertir los NaN en cadenas vacías y luego usar DictVectorizer como en mi ejemplo anterior. Esto crea efectivamente una nueva característica para cuando falta el valor, pero deja los valores numéricos sin cambios en caso contrario. He encontrado esta técnica muy útil.

¿El nuevo CategoricalEncoder podrá hacer algo similar?

hemos considerado permitir que los usuarios tengan NaN tratado como una categoría separada
o similar. pero eso no es lo mismo que manejar valores numéricos arbitrarios como
diferente de las cuerdas.

Eso suena bien.

Tienes razón, hay dos casos de uso. Permítanme explicarles un ejemplo particular en el que tratar los valores numéricos como diferentes de las cadenas me ha resultado útil. Puede ser que haya una mejor solución.

Supongamos que tiene una función numérica entera que toma una amplia gama de valores. Sin embargo, sospecha que para algunos valores pequeños, el valor exacto es significativo. Para valores más grandes, sospecha que este no es el caso. Una cosa simple que hacer es convertir todos los valores pequeños en cadenas, ejecutar DictVectorizer como arriba y luego realizar la selección de características o simplemente usar su clasificador favorito directamente.

Entonces, ¿lo está utilizando para una discretización no lineal? La próxima versión es
Es probable que incluya un discretizador de ancho fijo, pero siguiendo un registro
transformada o una transformada de cuantiles, debería actuar de manera bastante similar a lo que
quiero ... Pero la transformación de registro por sí sola podría ser suficiente en su entorno.

El 25 de febrero de 2018 a las 18:10, lesshaste [email protected] escribió:

Eso suena bien.

Tienes razón, hay dos casos de uso. Déjame explicarte un ejemplo en particular.
de donde ha sido útil tratar los valores numéricos como diferentes de las cadenas
para mi. Puede ser que haya una mejor solución.

Supongamos que tiene una función numérica entera que requiere un amplio rango de
valores. Sin embargo, sospecha que para algunos valores pequeños, el valor exacto
es significante. Para valores más grandes, sospecha que este no es el caso. Un simple
Lo que hay que hacer es convertir todos los valores pequeños en cadenas, ejecutar DictVectorizer
como arriba y luego realice la selección de funciones o simplemente use su favorito
clasificador directamente.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment-368288727 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AAEz60cmjwlDVKGyXc6oPyIC9oLbptSgks5tYQdvgaJpZM4RpUE8
.

@jnothman Sí, en cierto sentido, excepto con un giro. Digamos que sospecho que algunos de los valores de 1 ... 1024 son significativos. Eso es 22 indica algo específico que es bastante diferente de 21 o 23. Tomar registros no ayudará aquí. Pero quiero dejar todos los valores superiores a 1024 como numéricos, ya que no creo que esos valores específicos signifiquen mucho.

Parece que sabe demasiado sobre su variable para una genérica
transformarse para ser el tipo de cosa que necesita.

El 25 de febrero de 2018 a las 20:37, lesshaste [email protected] escribió:

@jnothman https://github.com/jnothman Sí en cierto sentido, excepto con un
giro. Digamos que sospecho que algunos de los valores de 1 ... 1024 son significativos.
Eso es 22 indica algo específico que es bastante diferente de 21 o

  1. Tomar registros no ayudará aquí. Pero quiero dejar todos los valores
    1024 tan numérico como no creo que esos valores específicos signifiquen mucho.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment-368295895 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AAEz65bOdVB6k7rCAcgLBYz_NslxXWV0ks5tYSnggaJpZM4RpUE8
.

@jnothman Para ser un poco más claro, no sé que 22 sea significativo. Solo sospecho que algunos valores lo son, pero no sé cuáles ni cuántos hay. He encontrado que el método "convertir a una cadena" y luego DictVectorizer es muy útil para descubrir cuáles son.

@lesshaste Para conocer el problema de los NaN como categoría separada, consulte https://github.com/scikit-learn/scikit-learn/issues/10465
Si desea analizar más a fondo la discretización no lineal específica o la codificación mixta numérica / de cadena, no dude en abrir una nueva edición. Pero me gustaría mantener este centrado en el problema original, es decir, el nombre y la organización en diferentes clases de CategoricalEncoder / OneHotEncoder.

Intentaré ver esto en las vacaciones de primavera en dos semanas, ¿de acuerdo? no estoy seguro de si tendré tiempo antes de eso: - /

@amueller eso está bien. No tendré tiempo en las próximas dos semanas para trabajar en el RP que está bloqueado por esto de todos modos. Después de eso, también debería tener tiempo de nuevo para trabajar en ello.

@amueller, ¿tuviste tiempo para darle un vistazo a esto?

@amueller, ¿está de acuerdo con que sigo trabajando en el PR para dividir CategoricalEncoder en OrdinalEncoder y OneHotEncoder (y con la desaprobación de los argumentos actuales de OneHotEncoder)?

Perdón por estar ausente. Parece estar bien, pero ¿podrías darme dos semanas para que pueda revisar? ¡Gracias!

@amueller no hay problema, para mí lo mismo :-)
Pero ahora planeo mirar esto nuevamente. Entonces, si pudieras echarle un vistazo a esto, sería bienvenido. Tengo trabajo que hacer en el PR (https://github.com/scikit-learn/scikit-learn/pull/10523), así que no lo revise todavía en detalle (puede verlo para tener una idea de lo que proponemos sin embargo).
Creo que la pregunta principal que quiero que se responda antes de dedicarle mucho tiempo es si está de acuerdo con dividir CategoricalEncoder en varias clases y, en ese caso, si está de acuerdo con la reutilización de OneHotEncoder (lo que significa desaprobando algunas de sus características actuales (extrañas)). Esas preguntas se resumen en https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment -363851328 y https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment -364802471.

(y una vez que estemos de acuerdo en esa parte, todavía hay mucho que discutir sobre la implementación real en el RP :))

Actualicé el PR https://github.com/scikit-learn/scikit-learn/pull/10523 , listo para su revisión

Con cautela diré que he vuelto;)

En mi humilde opinión, lo más importante es una API universal (es decir, parámetros y patrones de comportamiento b) para todos los codificadores que discutimos

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

En el paquete category_encoders , todos los codificadores tienen un argumento cols , similar al categorical_features del antiguo OneHotEncoder (aunque no acepta exactamente el mismo tipo de valores). Ver, por ejemplo, http://contrib.scikit-learn.org/categorical-encoding/onehot.html
Así que eso está relacionado con la discusión actual que estamos teniendo en https://github.com/scikit-learn/scikit-learn/pull/10523 sobre la desaprobación de categorical_features o no.

Por lo demás, creo que no hay palabras clave realmente conflictivas (tienen algunas otras específicas para los marcos de datos que no agregaremos a sklearn en este momento). El nombre de OneHotEncoder y OrdinalEncoder al menos es consistente con el paquete category_encoders .

¿Fue útil esta página
0 / 5 - 0 calificaciones