mise à l'échelle des colonnes de dataframe pandas avec sklearn

138

J'ai un dataframe pandas avec des colonnes de type mixte, et j'aimerais appliquer min_max_scaler de sklearn à certaines des colonnes. Idéalement, j'aimerais faire ces transformations en place, mais je n'ai pas encore trouvé de moyen de le faire. J'ai écrit le code suivant qui fonctionne:

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

Je suis curieux de savoir si c'est la manière préférée / la plus efficace de faire cette transformation. Y a-t-il un moyen d'utiliser df.apply qui serait mieux?

Je suis également surpris de ne pas pouvoir faire fonctionner le code suivant:

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

Si je passe un dataframe entier au scaler, cela fonctionne:

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

Je ne comprends pas pourquoi la transmission d'une série au scaler échoue. Dans mon code de travail complet ci-dessus, j'avais espéré simplement passer une série au scaler, puis définir la colonne dataframe = sur la série mise à l'échelle. J'ai vu cette question posée à quelques autres endroits, mais je n'ai pas trouvé de bonne réponse. Toute aide pour comprendre ce qui se passe ici serait grandement appréciée!

boulette de viande
la source
1
Cela fonctionne-t-il si vous faites cela bad_output = min_max_scaler.fit_transform(dfTest['A'].values)? l'accès à l' valuesattribut renvoie un tableau numpy, pour une raison quelconque, l'api scikit learn appellera correctement la bonne méthode qui fait que les pandas retournent un tableau numpy et parfois ce n'est pas le cas.
EdChum
Les dataframes de Pandas sont des objets assez compliqués avec des conventions qui ne correspondent pas aux conventions de scikit-learn. Si vous convertissez tout en tableaux NumPy, scikit-learn devient beaucoup plus facile à utiliser.
Fred Foo
@edChum - bad_output = in_max_scaler.fit_transform(dfTest['A'].values)n'a pas fonctionné non plus. @larsmans - ouais, j'avais pensé à emprunter cette voie, cela semble juste être un problème. Je ne sais pas si c'est un bogue ou non que les Pandas peuvent passer une trame de données complète à une fonction sklearn, mais pas une série. Ma compréhension d'un dataframe était qu'il s'agit d'un dict de série. En lisant le livre "Python for Data Analysis", il indique que pandas est construit sur numpy pour le rendre facile à utiliser dans les applications centrées sur NumPy.
flyingmeatball

Réponses:

214

Je ne sais pas si les versions précédentes de l'ont pandasempêché, mais maintenant l'extrait de code suivant fonctionne parfaitement pour moi et produit exactement ce que vous voulez sans avoir à utiliserapply

>>> 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
la source
80
Soigné! Une version plus généraliséedf[df.columns] = scaler.fit_transform(df[df.columns])
citynorman
6
@RajeshThevar Les crochets extérieurs sont les crochets de sélection typiques des pandas, indiquant aux pandas de sélectionner une colonne dans le dataframe. Les crochets intérieurs indiquent une liste. Vous passez une liste au sélecteur de pandas. Si vous utilisez simplement des crochets simples - avec un nom de colonne suivi d'un autre, séparé par une virgule - pandas interprète cela comme si vous essayez de sélectionner une colonne à partir d'un dataframe avec des colonnes à plusieurs niveaux (un MultiIndex) et lèvera une erreur de clé .
ken
1
pour ajouter à la réponse de @ ken si vous voulez voir exactement comment pandas implémente cette logique d'indexation et pourquoi un tuple de valeurs serait interprété différemment d'une liste, vous pouvez voir comment DataFrames implémente la __getitem__méthode. Plus précisément, vous pouvez ouvrir votre ipython et faire pd.DataFrame.__getitem__??; après avoir importé des pandas en tant que pd bien sûr;)
LetsPlayYahtzee
4
Remarque pratique: pour ceux qui utilisent les fractionnements de données train / test, vous souhaiterez ne tenir compte que de vos données d'entraînement, pas de vos données de test.
David J.
1
Pour mettre à l'échelle tout sauf la colonne d'horodatage, combinez avec columns =df.columns.drop('timestamps') df[df.columns] = scaler.fit_transform(df[df.columns]
intotecho
19

Comme ça?

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 tchèque
la source
3
Je reçois un tas de DeprecationWarnings lorsque j'exécute ce script. Comment doit-il être mis à jour?
pir
Voir la réponse de @ LetsPlayYahtzee ci
AJP
2
Une version plus simple: dfTest [['A', 'B']] = dfTest [['A', 'B']]. Apply (MinMaxScaler (). Fit_transform)
Alexandre V.
12

Comme il est mentionné dans le commentaire de pir - la .apply(lambda el: scale.fit_transform(el))méthode produira l'avertissement suivant:

DeprecationWarning: la transmission de tableaux 1d en tant que données est obsolète dans la version 0.17 et lèvera ValueError dans la version 0.19. Remodelez vos données en utilisant X.reshape (-1, 1) si vos données ont une seule fonctionnalité ou X.reshape (1, -1) si elle contient un seul échantillon.

La conversion de vos colonnes en tableaux numpy devrait faire le travail (je préfère StandardScaler):

from sklearn.preprocessing import StandardScaler
scale = StandardScaler()

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

- Edit Nov 2018 (Testé pour les pandas 0.23.4 ) -

Comme Rob Murray le mentionne dans les commentaires, dans la version actuelle (v0.23.4) de pandas .as_matrix()revient FutureWarning. Par conséquent, il doit être remplacé par .values:

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()

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

- Edit mai 2019 (Testé pour les pandas 0.24.2 ) -

Comme le mentionne joelostblom dans les commentaires, "Depuis 0.24.0, il est recommandé d'utiliser à la .to_numpy()place de .values."

Exemple mis à jour:

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
Obligation à faible rendement
la source
1
utiliser .valuesà la place de .as_matrix()comme as_matrix()donne maintenant un FutureWarning.
Rob Murray
1
Depuis 0.24.0, il est recommandé d'utiliser à la .to_numpy()place de.values .
joelostblom
10
df = pd.DataFrame(scale.fit_transform(df.values), columns=df.columns, index=df.index)

Cela devrait fonctionner sans avertissements de dépréciation.

athlonshi
la source
7

Vous pouvez le faire en utilisant pandasuniquement:

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
la source
6
Je sais que je peux le faire uniquement dans les pandas, mais je peux éventuellement appliquer une méthode sklearn différente qui n'est pas aussi facile à écrire moi-même. Je suis plus intéressé à comprendre pourquoi l'application à une série ne fonctionne pas comme prévu que je ne le suis à trouver une solution strictement plus simple. Ma prochaine étape sera d'exécuter un RandomForestRegressor, et je veux m'assurer de bien comprendre comment Pandas et sklearn fonctionnent ensemble.
flyingmeatball
5
Cette réponse est dangereuse car elle df.max() - df.min()peut être égale à 0, entraînant une exception. De plus, df.min()est calculé deux fois, ce qui est inefficace. Notez que cela df.ptp()équivaut à df.max() - df.min().
Acumenus
3

Je sais que c'est un très vieux commentaire, mais quand même:

Au lieu d'utiliser un support simple (dfTest['A']), utilisez des supports doubles (dfTest[['A']]).

à savoir: min_max_scaler.fit_transform(dfTest[['A']]).

Je pense que cela donnera le résultat souhaité.

BLÊME
la source