colunas de dataframe do pandas dimensionadas com o sklearn

137

Eu tenho um dataframe de pandas com colunas de tipo misto e gostaria de aplicar o min_max_scaler do sklearn a algumas das colunas. Idealmente, eu gostaria de fazer essas transformações no local, mas ainda não descobri uma maneira de fazer isso. Eu escrevi o seguinte código que funciona:

import pandas as pd
import numpy as np
from sklearn import preprocessing

scaler = preprocessing.MinMaxScaler()

dfTest = pd.DataFrame({'A':[14.00,90.20,90.95,96.27,91.21],'B':[103.02,107.26,110.35,114.23,114.68], 'C':['big','small','big','small','small']})
min_max_scaler = preprocessing.MinMaxScaler()

def scaleColumns(df, cols_to_scale):
    for col in cols_to_scale:
        df[col] = pd.DataFrame(min_max_scaler.fit_transform(pd.DataFrame(dfTest[col])),columns=[col])
    return df

dfTest

    A   B   C
0    14.00   103.02  big
1    90.20   107.26  small
2    90.95   110.35  big
3    96.27   114.23  small
4    91.21   114.68  small

scaled_df = scaleColumns(dfTest,['A','B'])
scaled_df

A   B   C
0    0.000000    0.000000    big
1    0.926219    0.363636    small
2    0.935335    0.628645    big
3    1.000000    0.961407    small
4    0.938495    1.000000    small

Estou curioso para saber se essa é a maneira preferida / mais eficiente de fazer essa transformação. Existe uma maneira que eu poderia usar df.apply que seria melhor?

Também estou surpreso por não conseguir que o seguinte código funcione:

bad_output = min_max_scaler.fit_transform(dfTest['A'])

Se eu passar um quadro de dados inteiro para o scaler, ele funcionará:

dfTest2 = dfTest.drop('C', axis = 1) good_output = min_max_scaler.fit_transform(dfTest2) good_output

Estou confuso por que a passagem de uma série para o scaler falha. No meu código de trabalho completo acima, eu esperava apenas passar uma série para o scaler e depois definir a coluna dataframe = para a série escalada. Eu já vi essa pergunta em alguns outros lugares, mas não encontrei uma boa resposta. Qualquer ajuda para entender o que está acontecendo aqui seria muito apreciada!

bola de carne
fonte
1
Funciona se você fizer isso bad_output = min_max_scaler.fit_transform(dfTest['A'].values)? acessar o valuesatributo retorna uma matriz numpy; por algum motivo, às vezes a API do scikit learn chama corretamente o método certo que faz com que os pandas retornem uma matriz numpy e outras vezes não.
EdChum
Os quadros de dados do Pandas são objetos bastante complicados, com convenções que não correspondem às convenções do scikit-learn. Se você converter tudo para matrizes NumPy, o scikit-learn fica muito mais fácil de trabalhar.
Fred Foo
@edChum - bad_output = in_max_scaler.fit_transform(dfTest['A'].values)também não funcionou. @ Larsmans - Sim, eu tinha pensado em ir por este caminho, parece apenas um aborrecimento. Não sei se é um bug ou não que o Pandas possa transmitir um quadro de dados completo para uma função sklearn, mas não uma série. Meu entendimento de um quadro de dados era que ele era um ditado de série. Lendo o livro "Python para Análise de Dados", ele afirma que o pandas é construído sobre o numpy para facilitar o uso em aplicações centradas no NumPy.
flyingmeatball

Respostas:

214

Não sei se as versões anteriores pandasimpediram isso, mas agora o seguinte trecho funciona perfeitamente para mim e produz exatamente o que você deseja, sem precisar usarapply

>>> import pandas as pd
>>> from sklearn.preprocessing import MinMaxScaler


>>> scaler = MinMaxScaler()

>>> dfTest = pd.DataFrame({'A':[14.00,90.20,90.95,96.27,91.21],
                           'B':[103.02,107.26,110.35,114.23,114.68],
                           'C':['big','small','big','small','small']})

>>> dfTest[['A', 'B']] = scaler.fit_transform(dfTest[['A', 'B']])

>>> dfTest
          A         B      C
0  0.000000  0.000000    big
1  0.926219  0.363636  small
2  0.935335  0.628645    big
3  1.000000  0.961407  small
4  0.938495  1.000000  small
LetsPlayYahtzee
fonte
80
Arrumado! Uma versão mais generalizadadf[df.columns] = scaler.fit_transform(df[df.columns])
citynorman
6
@RajeshThevar Os colchetes externos são os colchetes seletores típicos dos pandas, dizendo aos pandas para selecionar uma coluna no quadro de dados. Os colchetes internos indicam uma lista. Você está passando uma lista para o seletor de pandas. Se você usar colchetes simples - com um nome de coluna seguido por outro, separados por vírgula - o pandas interpreta isso como se estivesse tentando selecionar uma coluna de um dataframe com colunas de vários níveis (um MultiIndex) e gerará um erro de chave .
ken
1
para adicionar à resposta do @ ken, se você quiser ver exatamente como o pandas implementa essa lógica de indexação e por que uma tupla de valores seria interpretada de forma diferente de uma lista, você pode ver como os DataFrames implementam o __getitem__método. Especificamente, você pode abrir o ipython e fazer pd.DataFrame.__getitem__??; depois de importar pandas como pd, é claro;)
LetsPlayYahtzee
4
Uma observação prática: para aqueles que usam divisões de dados de treinamento / teste, convém caber apenas nos dados de treinamento, não nos dados de teste.
David J.
1
Para dimensionar tudo, exceto a coluna de timestamps, combine com columns =df.columns.drop('timestamps') df[df.columns] = scaler.fit_transform(df[df.columns]
intotecho
19

Como isso?

dfTest = pd.DataFrame({
           'A':[14.00,90.20,90.95,96.27,91.21],
           'B':[103.02,107.26,110.35,114.23,114.68], 
           'C':['big','small','big','small','small']
         })
dfTest[['A','B']] = dfTest[['A','B']].apply(
                           lambda x: MinMaxScaler().fit_transform(x))
dfTest

    A           B           C
0   0.000000    0.000000    big
1   0.926219    0.363636    small
2   0.935335    0.628645    big
3   1.000000    0.961407    small
4   0.938495    1.000000    small
Eric Czech
fonte
3
Recebo vários DeprecationWarnings quando executo esse script. Como deve ser atualizado?
pir
Veja a resposta de @ LetsPlayYahtzee abaixo
AJP
2
Uma versão mais simples: dfTest [[ 'A', 'B']] = dfTest [[ 'A', 'B']] se aplica (MinMaxScaler () fit_transform.).
Alexandre V.
12

Como está sendo mencionado no comentário do pir - o .apply(lambda el: scale.fit_transform(el))método produzirá o seguinte aviso:

DeprecationWarning: Passar matrizes 1d, pois os dados foram descontinuados em 0,17 e aumentará o ValueError em 0,19. Remodele seus dados usando X.reshape (-1, 1) se seus dados tiverem um único recurso ou X.reshape (1, -1) se contiver uma única amostra.

A conversão de suas colunas em matrizes numpy deve fazer o trabalho (eu prefiro StandardScaler):

from sklearn.preprocessing import StandardScaler
scale = StandardScaler()

dfTest[['A','B','C']] = scale.fit_transform(dfTest[['A','B','C']].as_matrix())

- Editar novembro de 2018 (testado para pandas 0.23.4 ) -

Como Rob Murray menciona nos comentários, na versão atual (v0.23.4) dos pandas .as_matrix()retorna FutureWarning. Portanto, deve ser substituído por .values:

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()

scaler.fit_transform(dfTest[['A','B']].values)

- Editar maio de 2019 (testado para pandas 0.24.2 ) -

Como joelostblom menciona nos comentários, "Desde então 0.24.0, é recomendável usar em .to_numpy()vez de .values".

Exemplo atualizado:

import pandas as pd
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
dfTest = pd.DataFrame({
               'A':[14.00,90.20,90.95,96.27,91.21],
               'B':[103.02,107.26,110.35,114.23,114.68],
               'C':['big','small','big','small','small']
             })
dfTest[['A', 'B']] = scaler.fit_transform(dfTest[['A','B']].to_numpy())
dfTest
      A         B      C
0 -1.995290 -1.571117    big
1  0.436356 -0.603995  small
2  0.460289  0.100818    big
3  0.630058  0.985826  small
4  0.468586  1.088469  small
Bond de baixo rendimento
fonte
1
use .valuesno lugar de .as_matrix()como as_matrix()agora dá a FutureWarning.
Rob Murray
1
Desde 0.24.0, é recomendável usar em .to_numpy()vez de.values .
Joelostblom 27/05/19
10
df = pd.DataFrame(scale.fit_transform(df.values), columns=df.columns, index=df.index)

Isso deve funcionar sem avisos de depreciação.

Athlonshi
fonte
7

Você pode fazer isso usando pandasapenas:

In [235]:
dfTest = pd.DataFrame({'A':[14.00,90.20,90.95,96.27,91.21],'B':[103.02,107.26,110.35,114.23,114.68], 'C':['big','small','big','small','small']})
df = dfTest[['A', 'B']]
df_norm = (df - df.min()) / (df.max() - df.min())
print df_norm
print pd.concat((df_norm, dfTest.C),1)

          A         B
0  0.000000  0.000000
1  0.926219  0.363636
2  0.935335  0.628645
3  1.000000  0.961407
4  0.938495  1.000000
          A         B      C
0  0.000000  0.000000    big
1  0.926219  0.363636  small
2  0.935335  0.628645    big
3  1.000000  0.961407  small
4  0.938495  1.000000  small
CT Zhu
fonte
6
Sei que posso fazê-lo apenas em pandas, mas talvez queira aplicar um método sklearn diferente que não seja tão fácil de escrever. Estou mais interessado em descobrir por que a inscrição em uma série não funciona como eu esperava do que em encontrar uma solução estritamente mais simples. Meu próximo passo será executar um RandomForestRegressor e quero ter certeza de que entendo como Pandas e sklearn funcionam juntos.
flyingmeatball
5
Essa resposta é perigosa porque df.max() - df.min()pode ser 0, levando a uma exceção. Além disso, df.min()é calculado duas vezes, o que é ineficiente. Note que df.ptp()é equivalente a df.max() - df.min().
Acumenos
3

Eu sei que é um comentário muito antigo, mas ainda assim:

Em vez de usar colchetes simples (dfTest['A']), use colchetes duplos (dfTest[['A']]).

isto é: min_max_scaler.fit_transform(dfTest[['A']]).

Eu acredito que isso dará o resultado desejado.

WAN
fonte