panda colonne dataframe ridimensionamento con sklearn


137

Ho un frame di dati Panda con colonne di tipo misto e vorrei applicare min_max_scaler di sklearn ad alcune delle colonne. Idealmente, mi piacerebbe fare queste trasformazioni sul posto, ma non ho ancora trovato un modo per farlo. Ho scritto il seguente codice che funziona:

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

Sono curioso di sapere se questo è il modo preferito / più efficiente per fare questa trasformazione. C'è un modo per usare df.apply che sarebbe meglio?

Sono anche sorpreso di non riuscire a far funzionare il seguente codice:

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

Se passo un intero frame di dati allo scaler, funziona:

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

Sono confuso perché il passaggio di una serie allo scaler non riesce. Nel mio codice di lavoro completo sopra, avevo sperato di passare una serie al ridimensionatore e quindi impostare la colonna dataframe = sulla serie ridimensionata. Ho visto questa domanda posta in pochi altri posti, ma non ho trovato una buona risposta. Qualsiasi aiuto per capire cosa sta succedendo qui sarebbe molto apprezzato!


1
Funziona se lo fai bad_output = min_max_scaler.fit_transform(dfTest['A'].values)? l'accesso valuesall'attributo restituisce un array intorpidito, per qualche motivo a volte lo scikit learn api chiamerà correttamente il metodo giusto che rende i panda restituisce un array intorpidito e talvolta no.
EdChum,

I frame di dati di Pandas sono oggetti piuttosto complicati con convenzioni che non corrispondono alle convenzioni di scikit-learn. Se converti tutto in array NumPy, scikit-learn diventa molto più facile da lavorare.
Fred Foo,

@edChum - bad_output = in_max_scaler.fit_transform(dfTest['A'].values)non ha funzionato neanche. @larsmans - sì, avevo pensato di percorrere questa strada, mi sembra solo una seccatura. Non so se sia un bug o meno che Panda possa passare un frame di dati completo a una funzione sklearn, ma non a una serie. La mia comprensione di un dataframe era che si tratta di un dettato di serie. Leggendo nel libro "Python for Data Analysis", si afferma che i panda sono costruiti su numpy per renderlo facile da usare nelle applicazioni incentrate su NumPy.
flyingmeatball

Risposte:


214

Non sono sicuro che le versioni precedenti lo pandasimpedissero, ma ora il seguente frammento funziona perfettamente per me e produce esattamente quello che vuoi senza dover usareapply

>>> 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

80
! Neat Una versione più generalizzatadf[df.columns] = scaler.fit_transform(df[df.columns])
citynorman

6
@RajeshThevar Le parentesi esterne sono le parentesi di selezione tipiche dei panda, che indicano ai panda di selezionare una colonna dal frame di dati. Le parentesi interne indicano un elenco. Stai passando un elenco al selettore panda. Se usi solo parentesi singole - con un nome di colonna seguito da un altro, separati da una virgola - Panda interpreta questo come se stessi cercando di selezionare una colonna da un frame di dati con colonne multi-livello (un MultiIndex) e lancerà un keyerror .
Ken,

1
per aggiungere alla risposta di @ ken se vuoi vedere esattamente come i panda implementano questa logica di indicizzazione e perché una tupla di valori sarebbe interpretata in modo diverso rispetto a un elenco, puoi vedere come DataFrames implementa il __getitem__metodo. In particolare puoi aprire ipython e fare pd.DataFrame.__getitem__??; dopo aver importato i panda come pd ovviamente;)
LetsPlayYahtzee

4
Una nota pratica: per coloro che utilizzano suddivisioni dei dati di treno / test, ti consigliamo di adattarti solo ai dati di allenamento, non ai dati di test.
David J.

1
Per ridimensionare tutti tranne la colonna timestamp, columns =df.columns.drop('timestamps') df[df.columns] = scaler.fit_transform(df[df.columns]
combinali

19

Come questo?

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

3
Ricevo un sacco di DeprecationWarnings quando eseguo questo script. Come dovrebbe essere aggiornato?
pir

Vedi la risposta di @ LetsPlayYahtzee di seguito
AJP,

2
Una versione più semplice: dfTest [['A', 'B']] = dfTest [['A', 'B']]. Apply (MinMaxScaler (). Fit_transform)
Alexandre V.,

12

Come viene menzionato nel commento di Pir, il .apply(lambda el: scale.fit_transform(el))metodo produrrà il seguente avviso:

DeprecationWarning: il passaggio di array 1d quando i dati sono deprecati in 0.17 e aumenteranno ValueError in 0.19. Rimodella i tuoi dati usando X.reshape (-1, 1) se i tuoi dati hanno una singola funzione o X.reshape (1, -1) se contiene un singolo campione.

La conversione delle colonne in array intorpiditi dovrebbe fare il lavoro (preferisco StandardScaler):

from sklearn.preprocessing import StandardScaler
scale = StandardScaler()

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

- Modifica novembre 2018 (Testato per i panda 0.23.4 ) -

Come Rob Murray menziona nei commenti, nella versione attuale (v0.23.4) dei panda .as_matrix()ritorna FutureWarning. Pertanto, dovrebbe essere sostituito da .values:

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()

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

- Modifica maggio 2019 (Tested for panda 0.24.2 ) -

Come menziona joelostblom nei commenti, "Da allora 0.24.0, si consiglia di utilizzare .to_numpy()invece di .values".

Esempio aggiornato:

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

1
usare .valuesal posto di .as_matrix()come as_matrix()ora dà a FutureWarning.
Rob Murray,


10
df = pd.DataFrame(scale.fit_transform(df.values), columns=df.columns, index=df.index)

Questo dovrebbe funzionare senza avvisi di ammortamento.


7

Puoi farlo 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

6
So che posso farlo solo in panda, ma potrei eventualmente applicare un metodo sklearn diverso che non è facile da scrivere da solo. Sono più interessato a capire perché applicare a una serie non funziona come mi aspettavo di quanto non lo sia trovare una soluzione rigorosamente più semplice. Il mio prossimo passo sarà eseguire un RandomForestRegressor e voglio essere sicuro di capire come Panda e sklearn lavorano insieme.
flyingmeatball

5
Questa risposta è pericolosa perché df.max() - df.min()può essere 0, portando a un'eccezione. Inoltre, df.min()viene calcolato due volte, il che è inefficiente. Si noti che df.ptp()equivale a df.max() - df.min().
Acumenus,

3

So che è un commento molto vecchio, ma comunque:

Invece di utilizzare una parentesi singola (dfTest['A']), utilizzare doppie parentesi (dfTest[['A']]).

Per esempio: min_max_scaler.fit_transform(dfTest[['A']]).

Credo che questo darà il risultato desiderato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.