columnas de marco de datos de pandas escalando con sklearn

137

Tengo un marco de datos de pandas con columnas de tipo mixto, y me gustaría aplicar min_max_scaler de sklearn a algunas de las columnas. Idealmente, me gustaría hacer estas transformaciones en su lugar, pero aún no he encontrado una manera de hacerlo. He escrito el siguiente 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

Tengo curiosidad por saber si esta es la forma preferida / más eficiente de hacer esta transformación. ¿Hay alguna forma en que pueda usar df.apply que sería mejor?

También me sorprende no poder hacer funcionar el siguiente código:

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

Si paso un marco de datos completo al escalador, funciona:

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

Estoy confundido por qué pasar una serie al escalador falla. En mi código de trabajo completo anterior, esperaba pasar una serie al escalador y luego establecer la columna del marco de datos = a la serie escalada. He visto esta pregunta en otros lugares, pero no he encontrado una buena respuesta. ¡Cualquier ayuda para entender lo que está pasando aquí sería muy apreciada!

bola de carne voladora
fuente
1
¿Funciona si haces esto bad_output = min_max_scaler.fit_transform(dfTest['A'].values)? el acceso al valuesatributo devuelve una matriz numpy, por alguna razón, la API de aprendizaje scikit llamará correctamente al método correcto que hace que los pandas devuelvan una matriz numpy y a veces no.
EdChum
Los marcos de datos de Pandas son objetos bastante complicados con convenciones que no coinciden con las convenciones de scikit-learn. Si convierte todo a matrices NumPy, es mucho más fácil trabajar con scikit-learn.
Fred Foo
@edChum - bad_output = in_max_scaler.fit_transform(dfTest['A'].values)tampoco funcionó. @larsmans: sí, había pensado en seguir esta ruta, parece una molestia. No sé si es un error o no, que Pandas puede pasar un marco de datos completo a una función sklearn, pero no una serie. Mi comprensión de un marco de datos fue que es un dict de serie. Leyendo en el libro "Python for Data Analysis", declara que pandas está construido sobre numpy para que sea fácil de usar en aplicaciones centradas en NumPy.
flyingmeatball

Respuestas:

214

No estoy seguro de si las versiones anteriores de pandasprevienen esto, pero ahora el siguiente fragmento funciona perfectamente para mí y produce exactamente lo que desea sin tener que 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
fuente
80
¡Ordenado! Una versión más generalizadadf[df.columns] = scaler.fit_transform(df[df.columns])
citynorman
66
@RajeshThevar Los corchetes externos son los corchetes selectores típicos de los pandas, que les indican a los pandas que seleccionen una columna del marco de datos. Los corchetes internos indican una lista. Estás pasando una lista al selector de pandas. Si solo usa paréntesis individuales, con un nombre de columna seguido de otro, separados por una coma, los pandas interpretan esto como si estuviera tratando de seleccionar una columna de un marco de datos con columnas de varios niveles (un MultiIndex) y arrojará un keyerror .
Ken
1
para agregar a la respuesta de @ ken si desea ver exactamente cómo los pandas implementan esta lógica de indexación y por qué una tupla de valores se interpretaría de manera diferente a una lista, puede ver cómo DataFrames implementa el __getitem__método. Específicamente puedes abrir tu ipython y hacerlo pd.DataFrame.__getitem__??; después de importar pandas como pd, por supuesto;)
LetsPlayYahtzee
44
Una nota práctica: para aquellos que usan divisiones de datos de entrenamiento / prueba, querrá ajustar solo sus datos de entrenamiento, no sus datos de prueba.
David J.
1
Para escalar todas las marcas de tiempo, pero la columna, se combinan con columns =df.columns.drop('timestamps') df[df.columns] = scaler.fit_transform(df[df.columns]
intotecho
19

¿Me gusta esto?

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 checo
fuente
3
Recibo un montón de DeprecationWarnings cuando ejecuto este script. ¿Cómo debería ser actualizado?
pir
Ver la respuesta de @ LetsPlayYahtzee abajo
AJP
2
Una versión más simple: dfTest [['' A ',' B ']] = dfTest [[' A ',' B ']]. Apply (MinMaxScaler (). Fit_transform)
Alexandre V.
12

Como se menciona en el comentario de pir, el .apply(lambda el: scale.fit_transform(el))método producirá la siguiente advertencia:

DeprecationWarning: Pasar matrices 1d ya que los datos están en desuso en 0.17 y aumentarán ValueError en 0.19. Cambie la forma de sus datos usando X.reshape (-1, 1) si sus datos tienen una sola característica o X.reshape (1, -1) si contiene una sola muestra.

La conversión de sus columnas a matrices numpy debería hacer el trabajo (prefiero StandardScaler):

from sklearn.preprocessing import StandardScaler
scale = StandardScaler()

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

- Edición de noviembre de 2018 (probado para pandas 0.23.4 ) -

Como Rob Murray menciona en los comentarios, en la versión actual (v0.23.4) de los pandas .as_matrix()regresa FutureWarning. Por lo tanto, debe ser reemplazado por .values:

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()

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

- Editar mayo de 2019 (probado para pandas 0.24.2 ) -

Como joelostblom menciona en los comentarios, "desde entonces 0.24.0, se recomienda usar en .to_numpy()lugar de .values".

Ejemplo actualizado:

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
Bono de bajo rendimiento
fuente
1
utilizar .valuesen lugar de .as_matrix()como as_matrix()ahora da un FutureWarning.
Rob Murray
1
Desde entonces 0.24.0, se recomienda usar en .to_numpy()lugar de.values .
joelostblom
10
df = pd.DataFrame(scale.fit_transform(df.values), columns=df.columns, index=df.index)

Esto debería funcionar sin advertencias de depreciación.

athlonshi
fuente
7

Puedes hacerlo usando pandassolo:

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
fuente
66
Sé que puedo hacerlo solo en pandas, pero es posible que desee aplicar un método de aprendizaje diferente que no sea tan fácil de escribir. Estoy más interesado en descubrir por qué aplicar a una serie no funciona como esperaba que en encontrar una solución estrictamente más simple. Mi próximo paso será ejecutar un RandomForestRegressor, y quiero asegurarme de que entiendo cómo trabajan juntos Pandas y sklearn.
flyingmeatball
55
Esta respuesta es peligrosa porque df.max() - df.min()puede ser 0, lo que lleva a una excepción. Además, df.min()se calcula dos veces, lo que es ineficiente. Tenga en cuenta que df.ptp()es equivalente a df.max() - df.min().
Acumenus
3

Sé que es un comentario muy antiguo, pero aún así:

En lugar de usar un solo corchete (dfTest['A']), use corchetes dobles (dfTest[['A']]).

es decir: min_max_scaler.fit_transform(dfTest[['A']]).

Creo que esto dará el resultado deseado.

PÁLIDO
fuente