Scikit-learn: Repensando a API CategoricalEncoder?

Criado em 23 jan. 2018  ·  63Comentários  ·  Fonte: scikit-learn/scikit-learn

Com base em algumas discussões que estamos tendo aqui e nos problemas que foram abertos, temos algumas dúvidas de que CategoricalEncoder (https://github.com/scikit-learn/scikit-learn/pull/9151) foi o bom escolha do nome (e como ainda não foi divulgado, temos espaço para mudanças).

Portanto, um resumo de como é agora:

  • O nome da classe CategoricalEncoder diz que tipo de dados ela aceita (dados categóricos)
  • O argumento de palavra-chave encoding especifica como codificar esses dados

Atualmente já temos encoding='onehot'|'onehot-dense'|'ordinal' .

Mas o que fazer nos seguintes casos:

  • Queremos adicionar mais opções de codificação (por exemplo, codificação binária, codificação de destino médio, codificação unária, ...). Continuamos adicionando esses como novos valores para encoding kwarg em uma grande classe CategoricalEncoder ?
  • Queremos adicionar uma opção específica para uma das codificações (por exemplo, para a codificação 'onehot' para eliminar a primeira coluna (redundante), ou para a codificação 'ordinal' com base na ordem das categorias na frequência, ...). O problema aqui é que precisamos adicionar argumentos de palavra-chave adicionais a CategoricalEncoder que estão ou não ativos, dependendo do que você passou por encoding kwarg, que não é o design de API mais agradável.

Para esse último problema, já tínhamos isso com a opção sparse=True/False , que era relevante apenas para 'onehot' e não para 'ordinal', e que resolvemos tendo 'onehot' e 'onehot-dense' opções de codificação e não uma palavra-chave sparse . Mas essa abordagem também não é escalável.

Relacionado a isso, há um PR para adicionar UnaryEncoder (https://github.com/scikit-learn/scikit-learn/pull/8652). Houve uma discussão relacionada à nomenclatura naquele PR, já que atualmente o nome diz como ele codifica, não que tipo de dados ele obtém (no design atual, ele aceita inteiros já codificados, não dados categóricos reais. ser consistente com CategoricalEncoder, pode ser melhor denominado OrdinalEncoder porque precisa de dados ordinais como entrada).


Quais são as opções futuras:

1) Mantenha as coisas como as temos agora no mestre e fique bem em adicionar algumas novas opções à classe única (uma questão importante que é difícil de responder agora é quantos novos recursos desejaremos adicionar no futuro) .
2) Mude o esquema de nomenclatura e tenha um monte de 'codificadores categóricos' onde o nome diz como ele codifica (OnehotEncoder, OrdinalEncoder e, posteriormente, talvez BinaryEncoder, UnaryEncoder, ...)

Portanto, é um pouco uma troca de potencial acumulado de número de classes versus número de argumentos de palavra-chave em uma única classe.


Um problema com a segunda abordagem (e uma das razões pelas quais optamos por CategoricalEncoder em primeiro lugar, mesmo antes de adicionarmos as várias opções de codificação), é que já existe um OnehotEncoder , que tem uma API diferente de CategoricalEncoder . E, não há realmente um outro nome bom que possamos usar para o codificador que faz a codificação one-hot.
No entanto, acho que, com alguns hacks temporários, poderíamos reutilizar o nome, se concordarmos em descontinuar os atributos atuais (e acho que concordamos que não são os atributos mais úteis). A ideia seria que se você ajustasse a classe com dados de string, você obteria o novo comportamento, e se você ajustasse a classe com dados inteiros, você obteria um aviso de depreciação indicando que o comportamento padrão mudará (e indicando qual palavra-chave especificar para obter livrar-se do aviso).

cc @jnothman @amueller @GaelVaroquaux @rth

Comentários muito úteis

A ideia de reverter o CategoricalEncoder me deixa muito triste, mas acho
você está certo de que futuros usuários ficariam menos confusos com a opção 2. Meu principal
preocupação é que tentamos implementar isso como uma mudança para OHE para um
muito tempo e nunca voou. Talvez seja bom tentar o
modificações na docstring OneHotEncoder de acordo com a proposta
mudar, para que possamos ver se parece lógico.

Todos 63 comentários

Obrigado pelo resumo @jorisvandenbossche. Acho que sou a favor da opção 2: reutilizar a classe OneHotEncoder , descontinuar os atributos estranhos e adicionar um parâmetro de construtor para selecionar o comportamento com um aviso futuro que diz que o comportamento padrão mudará, mas torna mais fácil silenciar esse aviso apenas passando um valor para essa opção.

A ideia de reverter o CategoricalEncoder me deixa muito triste, mas acho
você está certo de que futuros usuários ficariam menos confusos com a opção 2. Meu principal
preocupação é que tentamos implementar isso como uma mudança para OHE para um
muito tempo e nunca voou. Talvez seja bom tentar o
modificações na docstring OneHotEncoder de acordo com a proposta
mudar, para que possamos ver se parece lógico.

+1 para o que Joel disse

“Enviado do meu telefone. Por favor, perdoe erros de digitação e brevidade.

Em 23 de janeiro de 2018, 12h28, às 12h28, Joel Nothman [email protected] escreveu:

A ideia de reverter CategoricalEncoder me deixa muito triste, mas eu
pensar
você está certo de que futuros usuários ficariam menos perplexos com a opção 2. Meu
a Principal
preocupação é que tentamos implementar isso como uma mudança para OHE para
uma
muito tempo e nunca voou. Talvez seja bom tentar o
modificações na docstring OneHotEncoder de acordo com a proposta
mudar, para que possamos ver se parece lógico.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente ou visualize-o no GitHub:
https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment -359761818

A ideia de reverter CategoricalEncoder me deixa muito triste

Para ser claro, não seria uma reversão, seria uma refatoração / renomeação que mantém todas as funcionalidades!
Mas também gosto do nome "CategoricalEncoder", isso seria realmente triste.

Dito isso, vou tentar fazer as mudanças rapidamente para ter uma ideia de como é possível integrar isso no OnehotEncoder.

OK, abri um PR com uma prova de conceito: https://github.com/scikit-learn/scikit-learn/pull/10523.
Ainda não está completo (sem avisos de depreciação e novos atributos ainda não são calculados no comportamento antigo).

A principal questão da API é sobre o formato dos dados de entrada.
Então, para recapitular, existem duas maneiras diferentes de processarmos os dados categóricos :

1) Como dados reais, ainda não codificados (inteiro ou string), dados categóricos (como isso é feito em CategoricalEncoder ) -> inferir categorias a partir de valores únicos nos dados de treinamento
2) Como número inteiro, dados já codificados (como é feito no OneHotEncoder atual) -> inferir categorias a partir do valor máximo nos dados de treinamento

A questão é: achamos ambos os casos dignos de apoio? Assim, no OneHotEncoder potencialmente mesclado, mantemos a capacidade de fazer ambos ou tornamos totalmente obsoleto e, em seguida, removemos a capacidade de processar a entrada ordinal?

Se quisermos a capacidade de processar ambos, podemos adicionar uma palavra-chave booleana para especificar o tipo de dados de entrada (por enquanto, eu uso encoded_input=False/True , mas outras ideias são ordinal_input , ...)

Para o período de suspensão de uso, temos que oferecer suporte a ambos de qualquer maneira, e também temos que introduzir uma palavra-chave para escolher o comportamento (para ser capaz de silenciar o aviso e escolher o novo comportamento).
Portanto, em princípio, poderíamos apenas manter a palavra-chave depois.

Visto que queremos lidar com ambos, uma visão geral de como o OneHotEncoder funcionaria:

  • por enquanto encoded_input=None , e inferimos o padrão com base nos dados
  • se dados semelhantes a int (manipulados anteriormente por OneHotEncoder) encoded_input são internamente configurados como True e um aviso de depreciação é gerado. Se o usuário quiser manter o comportamento atual, ele pode especificá-lo manualmente como OneHotEncoder(encoded_input=True) para silenciar o aviso.
  • se a entrada não for semelhante a int, definimos encoded_input internamente como False e, sem aviso, usamos o novo comportamento (= o comportamento atual do CategoricalEncoder)
  • no futuro, mudaremos o padrão de encoded_input de Nenhum para Falso (por padrão, o novo comportamento, também para dados do tipo int)

Ainda não tenho certeza de que você está sugerindo a diferença prática devido à inferência de categorias a partir do valor máximo.

@jnothman Suponho que você reconheça que pode haver uma diferença na prática? (a saída que você obtém dependendo dos dados que você tem)

Mas se essa diferença é importante na prática, não sei. É onde eu gostaria de ver o feedback. Se alguém realmente deseja esse método baseado em "valor máximo", ou se estamos bem com (no futuro, após a depreciação) ter apenas o método baseado em "valores únicos".

Acho que pessoalmente nunca precisaria desse método baseado em valor máximo, mas o OneHotEncoder tem sido assim por muitos anos (por um bom motivo ou não?).

Na verdade, descontinuar a categorização baseada em valor máximo certamente tornaria a implementação (após a descontinuação) mais simples.
E se escolhermos essa rota, concordo que a opção deve ser legacy_mode=True/False vez de encoded_input / ordinal_input

Lembre-me qual é a diferença real na saída, quando n_values ​​= 'auto',
por favor? Eu pensei que a coisa active_features_ os tornava basicamente
idêntico, mas provavelmente estou esquecendo algo.

Aha, isso esclarece nosso mal-entendido :-)
Não entendi como o OneHotEncoder atual está realmente funcionando. Suponha que você tenha um recurso com os valores [2, 3, 5, 2]. Achei que o OneHotEncoder atual teria categorias [0, 1, 2, 3, 4, 5] (enquanto o CategoricalEncoder atual teria categorias [2, 3, 5]). Mas você está certo de que active_features_ também é apenas [2, 3, 5], essencialmente tornando-os iguais com o valor padrão de n_values='auto' .

Portanto, é apenas o caso em que você passa um número inteiro para n_values (como n_values=6 para categorias = [0, 1, 2, 3, 4, 5] no caso acima) para especificar o número de categorias que realmente serão uma alteração da API (obsoleta / removida).
E isso será facilmente substituído pelo usuário por categories=range(6)

Desculpe pela confusão.
Diante disso, acho que nem mesmo precisamos da opção legacy_mode . Podemos apenas traduzir n_values=6 para categories=range(6) internamente e gerar um aviso para isso (mas precisamos verificar isso com os testes reais).

A outra diferença é o manuseio de categorias invisíveis. Com o comportamento atual do OneHotEncoder, se os valores não vistos estiverem dentro do intervalo (0, máximo), ele não gerará um erro mesmo se handle_unknow='error' (o padrão). Mas também isso pode ser resolvido separadamente, em tal caso, gerando um aviso de que o usuário deve definir handle_unknown='ignore' manualmente para manter o comportamento existente.

A única característica que perderíamos é a distinção entre categorias desconhecidas que estão dentro do intervalo (0, max) (pelo OneHotEncoder atual não considerado 'desconhecido') e aquelas que são maiores do que (> max, aqueles que já são considerados como desconhecido pelo OneHotEncoder).

não, esse é o tipo de coisa que tentamos antes e é muito
enjoado. a menos que haja um bom motivo para manter o comportamento atual, nós
deve ter apenas um legacy_mode para nos levar lentamente ao futuro.

não, esse é o tipo de coisa que tentamos antes e é muito meticuloso.

Você pode esclarecer a qual aspecto esse "não" se refere?
Para o fato de que eu acho que legacy_mode não é necessário?

sim, à ideia de que você pode simplesmente fazer algo que é ao mesmo tempo ao contrário
compatível e o que queremos daqui para frente

sim, à ideia de que você pode simplesmente fazer algo que seja compatível com versões anteriores e o que queremos daqui para frente

Não foi isso que tentei sugerir. Eu queria deixar claro que acho que é possível não ter uma palavra-chave legacy_mode , não por tê-la magicamente compatível com versões anteriores e o que queremos no futuro, mas por descontinuar o comportamento das palavras-chave existentes.

Portanto, para ser concreto: um valor não padrão de n_values pode ser descontinuado e deve ser substituído pela especificação categories . handle_unknow no caso de dados inteiros deve ser definido explicitamente pelo usuário para escolher entre ignorar totalmente ou erro completo em vez da combinação atual (caso contrário, o aviso de depreciação é gerado).

então, se eu fizer .fit ([[5]]). transform ([[4]]), para quais valores de n_values,
categorias e handle_umknown isso gerará um erro?

Em 25 de janeiro de 2018, às 9h32, "Joris Van den Bossche" [email protected]
escreveu:

sim, à ideia de que você pode simplesmente fazer algo que é ao mesmo tempo ao contrário
compatível e o que queremos daqui para frente

Não foi isso que tentei sugerir. Eu queria deixar claro que acho que
é possível não ter uma palavra-chave legacy_mode, não por tê-la magicamente
compatível com versões anteriores e o que queremos no futuro, mas descontinuando
o comportamento das palavras-chave existentes.

Portanto, para ser concreto: um valor não padrão de n_values ​​pode ser descontinuado e
deve ser substituído pela especificação de categorias. handle_unknow no caso de
os dados inteiros devem ser definidos explicitamente pelo usuário para escolher um completo
ignorando ou erro completo em vez da combinação atual (e de outra forma depreciação
aviso é gerado).

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment-360296569 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AAEz6-DrQWep22_gs-hg9cC0u19B1_PSks5tN6-HgaJpZM4RpUE8
.

podemos apenas fazer isso durante a suspensão de uso, as categorias devem ser definidas
explicitamente, e o modo legado com avisos está de outra forma em vigor? É aquele
o que você está sugerindo?

podemos apenas fazer com que, durante a depreciação, as categorias devam ser definidas explicitamente e o modo legado com avisos esteja de outra forma em vigor? É isso que você está sugerindo?

Sim, pode ainda estar faltando o caso, mas acho que isso é possível (vou verificar codificando-o na próxima semana).

Os diferentes casos 'legados':

  • n_values ​​= 'auto' (o padrão)

    • handle_unknown = 'ignore' -> bem, sem mudança no comportamento

    • handle_unknown = 'error' -> Problema, valores no intervalo ainda são ignorados, valores acima do erro de intervalo



      • Solução possível:





        • no ajuste, se o intervalo for consecutivo => bom, nenhuma mudança no comportamento (para todas as pessoas que agora combinaram LabelEncoder com ele, que é um caso de uso típico, eu acho)



        • se este não for o caso: levante o aviso de suspensão de uso de que eles precisam definir categorias explicitamente para manter esse comportamento (e usar internamente o modo legado)






  • n_values ​​= valor

    • isso pode ser traduzido em categorias = [intervalo (valor)] internamente e aumentar o aviso de suspensão de uso de que o usuário deve fazer isso no futuro

    • neste caso handle_unknown='error' / 'ignore' trabalhe conforme o esperado

O aviso de suspensão de uso no caso de n_values='auto' só será gerado em fit e não na construção (o que não é realmente ideal), mas é apenas na medida em que sabemos que o usuário está passando. dados numéricos e não dados de string.

geralmente não emitimos avisos até o ajuste em qualquer caso, então não se preocupe com
naquela.

essa estratégia soa muito boa.

Não tenho certeza se devemos farejar strings nos dados,
no entanto. Você basicamente quer que seja: o modo legado estará ativo se as categorias forem
não definido e se os dados forem todos inteiros?

Uma pergunta: se os parâmetros categorias e n_values ​​forem o padrão, faça
publicamos categorias_? Se n_values ​​for definido explicitamente, publicamos
categorias_?

Em 29 de janeiro de 2018, às 10h, "Joris Van den Bossche" [email protected]
escreveu:

podemos apenas fazer isso durante a suspensão de uso, as categorias devem ser definidas
explicitamente, e o modo legado com avisos está de outra forma em vigor? É aquele
o que você está sugerindo?

Sim, pode ainda estar faltando o caso, mas acho que isso é possível (será
verifique codificando-o na próxima semana).

Os diferentes casos 'legados':

  • n_values ​​= 'auto' (o padrão)

    • handle_unknown = 'ignore' -> bem, sem mudança no comportamento

    • handle_unknown = 'error' -> Problema, os valores no intervalo ainda estão

      ignorado, valores acima do intervalo de erro



      • Solução possível:





        • no ajuste, se o intervalo for consecutivo => fino, nenhuma alteração no



          comportamento (para todas as pessoas que agora combinam LabelEncoder com ele, que é



          um caso de uso típico, eu acho)



        • se este não for o caso: aumente a depreciação avisando que



          eles têm que definir categorias explicitamente para manter esse comportamento (e



          usar internamente o modo legado)





      • n_values ​​= valor



    • isso pode ser traduzido em categorias = [intervalo (valor)] internamente,

      e aumentar o aviso de suspensão de uso de que o usuário deve fazer isso por conta própria no

      futuro

    • neste caso, handle_unknown = 'erro' / 'ignorar' funciona como esperado

O aviso de depreciação no caso de n_values ​​= 'auto' só será gerado em
adequado e não na construção (o que não é realmente ideal), mas é apenas
na medida em que sabemos que o usuário está passando dados numéricos e não string
dados.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment-361104495 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AAEz6x8xnyZXBLij-DCC45JyYNf8pA5kks5tPPwXgaJpZM4RpUE8
.

Você basicamente quer que seja: o modo legado está ativo se as categorias não forem definidas e se os dados forem todos inteiros?

Sim, de fato (na prática, será mais ou menos o mesmo)

Uma pergunta: se as categorias e os parâmetros n_values ​​forem o padrão, publicamos as categorias_? Se n_values ​​for definido explicitamente, publicamos categorias_?

Eu pessoalmente já forneceria o máximo possível os atributos da nova interface, mesmo no modo legado. Então, em ambos os casos, eu calcularia categories_ (mesmo que fosse um pouco mais trabalhoso)


Então, tentei colocar a lógica acima no código (enviará algumas atualizações para o PR) e tenho mais uma pergunta para o caso de dados inteiros quando n_values ou categories não está definido ( caso típico para 'legacy_mode'). O problema reside no fato de que se as categorias inferidas são simplesmente um intervalo consecutivo (0, 1, 2, 3, ... máx.), Não há diferença entre o comportamento novo e o antigo (legado), e nós não necessariamente precisa gerar um aviso de depreciação.
Algumas possibilidades para fazer neste caso específico:

1) Detecte este caso (que as categorias inferidas são um intervalo consecutivo) e, nesse caso, não gere um aviso.
- Isso é possível detectar (com um pouco de complexidade de código extra), pois já estamos preparados de qualquer maneira
- Acho que este será um caso comum ao usar OneHotEncoder com dados inteiros, e um caso em que o usuário realmente não precisa se preocupar com nossa refatoração, então seria bom não incomodá-lo com um aviso
2) Sempre emita um aviso e indique na mensagem de aviso o que fazer se você estiver em tal caso (além de uma explicação sobre o que fazer se você não tiver um intervalo consecutivo):
- Se eles souberem que têm apenas intervalos consecutivos como categorias, eles desejam ignorar o aviso, então podemos adicionar à mensagem de aviso uma explicação de como fazer isso (adicionar uma amostra de código com avisos de filtro que eles podem copiar e colar)
- Uma vantagem potencial disso é que também podemos adicionar à mensagem de aviso que, se eles usaram o LabelEncoder para criar os inteiros, agora eles podem usar diretamente o OneHotEncoder (acho que atualmente é um padrão de uso típico). Dessa forma, o aviso também irá embora
3) Sempre gere um aviso, mas forneça uma palavra-chave para silenciá-lo (por exemplo, legacy_mode=False )
- Se acharmos o conselho para usar uma instrução filterwarnings (consulte o ponto 2 acima) muito complicado, também podemos adicionar uma palavra-chave para obter o mesmo resultado
- A desvantagem disso é a introdução de uma palavra-chave que não será mais necessária em alguns releases, quando as depreciações forem eliminadas.

Pessoalmente, sou a favor da opção 1 ou 2. Usar o LabelEncoder antes de OneHotEncoder parece ser um padrão típico (de uma pesquisa rápida no github) e, nesse caso, você sempre tem intervalos consecutivos e nunca haverá uma mudança no comportamento com a nova implementação, por isso não devemos alertar para isso. Por outro lado, se avisarmos, podemos apontá-los para o fato de que, se usaram o LabelEncoder, não precisam mais fazer isso. O que seria bom dar esse conselho explicitamente.
A questão é com que frequência os usuários têm números inteiros consecutivos como categorias sem ter usado LabelEncoder como a etapa anterior.

Hmm, um caso que esqueci é quando você tem categorias inferidas de inteiros que não são consecutivas (digamos [1,3,5]), mas você quer o novo comportamento e não o comportamento legado (então, nesse caso, você não pode simplesmente ignorar o aviso , pois isso trataria valores não vistos de forma diferente na etapa de transformação, ou seja, valores entre a faixa (por exemplo, 2) não gerariam um erro).
Caso não forneçamos a palavra-chave legacy_mode=False , a única maneira de obter o novo comportamento é passando manualmente categories=[1,3,5] , o que pode ser um pequeno inconveniente. Isso pode ser um motivo para favorecer a opção 3 e desistir de minha objeção sobre a introdução de uma palavra-chave temporária legacy_mode=False (mas também não tenho certeza se vale a pena, pois este seria o único caso * em que tal palavra-chave é realmente precisava)

* este único caso = dados inteiros com categorias inferidas que não são intervalos consecutivos e onde você não pode / não deseja definir as categorias manualmente ou definir handle_unknown para ignorar.

Desculpe pelo texto longo, mas é bastante complexo :)

Estamos apenas falando sobre o caso em que n_values ​​não está definido, certo?

Estou bem com 1., e não seria mais caro, já que o automóvel
já precisa examinar o conjunto de rótulos. Eu também poderia aceitar, por
simplicidade, uma variante de 3. que era apenas "OneHotEncoder rodando em legado
modo. Defina categorias = 'auto' para um comportamento ligeiramente diferente sem um
aviso."

Estamos apenas falando sobre o caso em que n_values ​​não está definido, certo?

Sim (o outro caso pode ser facilmente traduzido em seu categories equivalente, com um bom aviso de depreciação e sem diferença no comportamento novo e legado)

uma variante de 3. que era apenas "OneHotEncoder em execução no modo legado. Defina as categorias = 'auto' para um comportamento ligeiramente diferente sem um aviso."

Ah, parece uma boa ideia! (independente de detectar o caso de categorias consecutivas ou não). Portanto, definimos no código o padrão de categories para Nenhum (sem alterar a semântica de seu padrão), então sabemos se o usuário definiu explicitamente, e dessa forma é uma boa maneira de indicar legacy_mode=False sem precisar dessa palavra-chave extra.

Sim, mas apenas se quisermos avisar toda vez que alguém usar sem passar
categorias. É a abordagem de implementação barata, mas pode ser
desnecessariamente verborrágico para os usuários, por isso eu preferiria 1 se
pode ser feito de forma simples.

Que inferno fresco é este: - /

OU poderíamos nomear o novo DummyEncoder ;) (embora isso seja um pouco conflitante com o DummyClassifier)

@amueller Não leia tudo acima!
Eu estava planejando fazer um bom resumo para novos leitores da edição. A discussão acima é muito complicada (também porque eu ainda não estava entendendo totalmente o comportamento complexo atual do OneHotEncoder ... :-))

OU podemos nomear o novo DummyEncoder;)

Acho que @GaelVaroquaux foi contra isso porque "one-hot" é conhecido por ser isso em mais campos (e já usamos 'Dummy' para outras coisas no scikit-learn ...)

Refazer isso para consistência na nomenclatura não vale a pena imho. Não somos consistentes em nomear em lugar nenhum. Você poderia resumir as discussões que levaram a isso?

Acho que "dummy" é o que os estatísticos usam e é o que os pandas usam.

A postagem principal ainda é precisa e vale a pena ler, e resume o motivo para não manter CategoricalEncoder (o que não significa que precisamos usar OneHotEncoder em vez de, por exemplo, DummyEncoder, essa é uma questão separada)

Eu li o post principal. É a isso que me referi quando disse "não vale a pena refazer isso para consistência".

questões que são abertas

Você pode explicar isso?

"Refazer isso para manter a consistência não vale a pena"

Com consistência, você está apontando para o esquema de nomenclatura de "o que aceita" versus "o que faz"? Nesse caso, esse foi apenas um motivo menor. Para mim, é principalmente uma questão de escalabilidade para adicionar mais recursos a uma única classe.

questões que são abertas

Tivemos o problema de como lidar com valores ausentes (https://github.com/scikit-learn/scikit-learn/issues/10465) e, para isso, você pode querer um comportamento diferente para codificação ordinal e one-hot (ou não todas as opções são válidas para ambos, ..). Também já temos o handle_unknown que só é relevante para codificação one-hot e não para ordinal. E havia https://github.com/scikit-learn/scikit-learn/issues/10518 sobre a ponderação de recurso para codificação onehot, mas também não relevante para ordinal (este problema no final não foi um problema, como você pode fazer a ponderação com o argumento ColumnTransformer transformer_weights). E também temos a solicitação de recurso para adicionar algo como drop_first para one-hot, que novamente não é relevante para a codificação ordinal.

Não vejo como a mudança proposta ajudaria tanto com os valores ausentes. E ter opções incompatíveis é algo que acontece com frequência no scikit-learn. Não é o ideal, mas também não é grande coisa imho.

Não vejo como a mudança proposta ajudaria tanto com os valores ausentes.

Não ajuda, como tal, mas torna menos complexo para ter opções específicas especificamente adaptados aos diferentes tipos de codificação.

E ter opções incompatíveis é algo que acontece com frequência no scikit-learn. Não é o ideal, mas também não é grande coisa imho.

Atualmente, certamente ainda está OK, não há muitas opções incompatíveis (mas também em parte porque movi sparse=True/False para a opção encoding ). Mas a questão é até que ponto queremos expandir a funcionalidade de codificação no scikit-learn no futuro. O que, obviamente, é uma pergunta difícil de responder agora .
Já temos um PR para 'codificação unária'. Isso não deveria ser adicionado ao CategoricalEncoder em vez de adicionar uma nova classe UnaryEncoder? E se alguém quiser adicionar uma 'codificação binária'? Ou um 'codificador de destino (médio)'?

O "codificador de destino médio" é CountTransformer , há um PR para isso;)

você tem um link para isto? Pesquisar por "CountTransformer" não dá nenhum resultado

Desculpe, CountFeaturizer # 9614

Certamente está relacionado, mas não é exatamente uma codificação de destino média. Além disso, ele adiciona colunas, não substitui, então ainda não funcionará fora da caixa para dados categóricos de string (mas isso é mais feedback sobre aquele PR, não para discutir aqui).

Por que não significa codificação de destino? Mas sim, não vamos desviar muito aqui;)

Portanto, como um resumo das questões reais que precisamos responder (nesta ordem!):

  1. Mantemos os CategoricalEncoder atuais? Caso contrário, a ideia é dividi-lo em classes diferentes, uma classe para cada tipo de codificação (atualmente codificação 'onehot' e 'ordinal').

  2. Se dividirmos em várias classes, poderíamos (idealmente?) Usar OneHotEncoder para a codificação 'onehot', mas essa classe já existe. Então, integramos a nova codificação 'onehot' (que suporta strings e tem parâmetros diferentes) na classe OneHotEncoder existente? Ou escolhemos outro nome? (por exemplo, DummyEncoder)

  3. Se optarmos por integrar no OneHotEncoder existente, estamos OK com as seguintes consequências: descontinuamos várias palavras-chave / atributos de OneHotEncoder e um caso de uso específico (ignorando automaticamente valores não vistos dentro da faixa de valores vistos) não será possível mais após o período de suspensão de uso.

A maior parte da discussão acima foi sobre a questão 3 (os detalhes complexos de como integrar CategoricalEncoder (encoding = 'onehot') em OneHotEncoder). Mas primeiro vamos chegar a um acordo sobre uma decisão para as 2 primeiras questões.

o outro fator para mim é que todos pensam que o modo automático atual
OneHotEncoder é estranho. sua implementação convertendo coo em csr também é
estranhas. ele merece um redesenho. e dizendo às pessoas "se você quiser um gostoso
codificar strings, vá para CategoricalEncoder "é estranho, porque OHE
já se destina a categorias ...

hrm. Acho que mantivemos o OneHotEncoder porque é mais eficiente quando pode ser usado ... O ideal seria nos livrar de todos os comportamentos estranhos. Eu meio que queria desaprovar, mas nós não ...

Eu meio que queria desaprovar, mas nós não ...

No meu POC PR (https://github.com/scikit-learn/scikit-learn/pull/10523), desativei quase tudo do OneHotEncoder, exceto seu nome ...

Não é muito mais eficiente. E se LabelEncoder tivesse atalhos para ints
no intervalo [0, n_values-1], se justificado, isso seria bom o suficiente.

@amueller , você está persuadido pelo problema de que, no final das contas, queremos parâmetros adicionais diferentes (por exemplo, drop_first, manuseio de nan) dependendo da codificação, e isso justifica ter um codificador discreto diferente para cada formato de codificação?

Vou tentar ver isso nas férias de primavera daqui a duas semanas, ok? não tenho certeza se terei tempo antes disso: - /

Espero que este não seja o lugar errado para perguntar, mas o que a implementação atual faz com tabelas que são misturadas categóricas e não categóricas em uma coluna? Tomando o exemplo de https://github.com/pandas-dev/pandas/issues/17418

Considere o 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}]) que é 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

O DictVectorizer fornece exatamente o que eu preciso neste caso.

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

Isto dá:

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 os nomes dos recursos das colunas com:

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

Seria ótimo se o novo CategoricalEncoder tivesse a opção de fazer o mesmo.

Não acho que pretendemos lidar com esse tipo de caso misto

Isso é uma vergonha. Um subcaso simples é quando uma coluna é numérica, mas tem alguns valores ausentes. Uma solução simples é converter os NaNs em strings vazias e, em seguida, usar o DictVectorizer como no meu exemplo acima. Isso cria efetivamente um novo recurso para quando o valor está ausente, mas deixa os valores numéricos inalterados de outra forma. Achei esta técnica muito útil.

O novo CategoricalEncoder será capaz de fazer algo semelhante?

consideramos permitir que os usuários tenham NaN tratado como uma categoria separada
ou similar. mas isso não é o mesmo que lidar com valores numéricos arbitrários como
diferente de cordas.

Isso soa bem.

Você está certo, há dois casos de uso. Deixe-me explicar um exemplo específico de onde o tratamento de valores numéricos como diferentes de strings tem sido útil para mim. Pode ser que haja uma solução melhor.

Digamos que você tenha um recurso numérico inteiro que aceita uma grande variedade de valores. No entanto, você suspeita que, para alguns valores pequenos, o valor preciso é significativo. Para valores maiores, você suspeita que esse não seja o caso. Uma coisa simples a fazer é converter todos os valores pequenos em strings, executar o DictVectorizer como acima e, em seguida, realizar a seleção de recursos ou apenas usar seu classificador favorito diretamente.

Então você está usando para uma discretização não linear? O próximo lançamento é
provavelmente incluirá um discretizador de largura fixa, mas seguindo a partir de um log
transformada ou uma transformada de quantil, deve agir de forma bastante semelhante ao que você
deseja ... Mas a transformação do log pode ser suficiente por si só em sua configuração.

Em 25 de fevereiro de 2018 às 18:10, lesshaste [email protected] escreveu:

Isso soa bem.

Você está certo, há dois casos de uso. Deixe-me explicar um exemplo particular
de onde o tratamento de valores numéricos como diferentes de strings tem sido útil
para mim. Pode ser que haja uma solução melhor.

Digamos que você tenha um recurso numérico inteiro que leva uma grande variedade de
valores. No entanto, você suspeita que, para alguns valores pequenos, o valor preciso
é significativo. Para valores maiores, você suspeita que esse não seja o caso. Um simples
coisa a fazer é converter todos os pequenos valores em strings, execute o DictVectorizer
como acima e, em seguida, execute a seleção de recursos ou apenas use o seu
classificador diretamente.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment-368288727 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AAEz60cmjwlDVKGyXc6oPyIC9oLbptSgks5tYQdvgaJpZM4RpUE8
.

@jnothman Sim em certo sentido, exceto com uma

Parece que você sabe muito sobre sua variável para um genérico
transformar para ser o tipo de coisa que você precisa.

Em 25 de fevereiro de 2018 às 20:37, lesshaste [email protected] escreveu:

@jnothman https://github.com/jnothman Sim em certo sentido, exceto com um
torção. Digamos que eu suspeite que alguns dos valores de 1 ... 1024 sejam significativos.
Isso é 22 indica algo específico que é bastante diferente de 21 ou

  1. Pegar logs não vai ajudar aqui. Mas eu quero deixar todos os valores de lado
    1024 tão numéricos quanto eu não acho que esses valores específicos significam muito.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment-368295895 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AAEz65bOdVB6k7rCAcgLBYz_NslxXWV0ks5tYSnggaJpZM4RpUE8
.

@jnothman Para ser um pouco mais claro, não sei se 22 é significativo. Eu apenas suspeito que alguns valores são, mas não sei quais ou quantos existem. Eu descobri que o método "converter em uma string" e, em seguida, DictVectorizer é muito útil para descobrir quais são.

@lesshaste Para o problema sobre NaNs como categoria separada, consulte https://github.com/scikit-learn/scikit-learn/issues/10465
Se você quiser discutir mais sobre a discretização não linear específica ou codificação numérica / string mista, sinta-se à vontade para abrir um novo problema. Mas gostaria de manter este foco no problema original, ou seja, a nomenclatura e a organização em diferentes classes do CategoricalEncoder / OneHotEncoder.

Vou tentar ver isso nas férias de primavera daqui a duas semanas, ok? não tenho certeza se terei tempo antes disso: - /

@amueller , tudo bem. Não terei tempo nas próximas duas semanas para trabalhar no PR que está bloqueado por isso de qualquer maneira. Depois disso, eu também terei tempo novamente para trabalhar nisso.

@amueller você teve tempo para dar uma olhada nisso?

@amueller, você

Desculpe por estar ausente. Parece bom, mas você pode me dar duas semanas para que eu possa realmente revisar? Obrigado!

@amueller sem problemas, para mim o mesmo :-)
Mas, agora estou planejando olhar para isso novamente. Então, se você pudesse dar uma olhada, seria bem-vindo. Tenho algum trabalho a fazer no PR (https://github.com/scikit-learn/scikit-learn/pull/10523), então não revise isso ainda em detalhes (você pode dar uma olhada para ter uma ideia do que propomos no entanto).
Acho que a questão principal que quero ver respondida antes de dedicar muito tempo a ela é se você concorda em dividir o CategoricalEncoder em várias classes e, nesse caso, se você concorda em reutilizar o OneHotEncoder (o que significa descontinuando alguns de seus recursos atuais (estranhos)). Essas perguntas estão resumidas em https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment -363851328 e https://github.com/scikit-learn/scikit-learn/issues/10521#issuecomment -364802471.

(e uma vez que concordamos com essa parte, ainda há muito o que discutir sobre a implementação real no PR :))

Eu atualizei o PR https://github.com/scikit-learn/scikit-learn/pull/10523 , pronto para revisão

Eu direi cautelosamente que estou de volta;)

IMHO, a coisa mais importante é uma API universal (ou seja, parâmetros e padrões de comportamento b) para todos os codificadores que discutimos

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

No pacote category_encoders , todos os codificadores têm um argumento cols , semelhante ao categorical_features do antigo OneHotEncoder (embora não aceite exatamente o mesmo tipo de valores). Veja, por exemplo, http://contrib.scikit-learn.org/categorical-encoding/onehot.html
Então, isso está relacionado à discussão atual que estamos tendo em https://github.com/scikit-learn/scikit-learn/pull/10523 sobre descontinuar categorical_features ou não.

Quanto ao resto, acho que não há palavras-chave realmente conflitantes (eles têm algumas outras específicas para dataframes que não adicionaremos ao sklearn neste ponto). A nomenclatura para OneHotEncoder e OrdinalEncoder pelo menos é consistente com o pacote category_encoders .

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

ben519 picture ben519  ·  3Comentários

StevenLOL picture StevenLOL  ·  3Comentários

amueller picture amueller  ·  3Comentários

rth picture rth  ·  3Comentários

vitorcoliveira picture vitorcoliveira  ·  3Comentários