масштабирование столбцов в пандах с помощью sklearn

138

У меня есть фреймворк pandas со столбцами смешанного типа, и я хотел бы применить min_max_scaler sklearn к некоторым столбцам. В идеале я хотел бы выполнить эти преобразования на месте, но еще не придумал, как это сделать. Я написал следующий код, который работает:

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

Мне любопытно, является ли это предпочтительным / наиболее эффективным способом сделать это преобразование. Есть ли способ использовать df.apply, который был бы лучше?

Я также удивлен, что не могу заставить работать следующий код:

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

Если я передаю масштабировщику весь фрейм данных, он работает:

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

Я не понимаю, почему не удается передать серию в скейлер. В моем полном рабочем коде выше я надеялся просто передать серию в масштабирующее устройство, а затем установить столбец dataframe = масштабированной серии. Я видел, как этот вопрос задавали в нескольких других местах, но не нашел хорошего ответа. Любая помощь в понимании того, что здесь происходит, будет принята с благодарностью!

flyingmeatball
источник
1
Это сработает, если вы это сделаете bad_output = min_max_scaler.fit_transform(dfTest['A'].values)? доступ к valuesатрибуту возвращает массив numpy, по какой-то причине иногда api scikit learn правильно вызывает правильный метод, который заставляет pandas возвращать массив numpy, а иногда это не так.
EdChum 09
Фреймы данных Pandas - это довольно сложные объекты с соглашениями, которые не соответствуют соглашениям scikit-learn. Если вы все конвертируете в массивы NumPy, работать с scikit-learn станет намного проще.
Fred Foo
@edChum - bad_output = in_max_scaler.fit_transform(dfTest['A'].values)тоже не заработало. @larsmans - да, я думал о том, чтобы пойти по этому пути, это просто похоже на хлопот. Я не знаю, является ли это ошибкой или нет, что Pandas может передать полный фрейм данных в функцию sklearn, но не серию. Мое понимание фрейма данных заключалось в том, что это диктат серии. В книге «Python для анализа данных» говорится, что pandas построен на основе numpy, чтобы упростить использование в приложениях, ориентированных на NumPy.
flyingmeatball 09

Ответы:

214

Я не уверен, pandasпредотвращали ли это предыдущие версии, но теперь следующий фрагмент отлично работает для меня и дает именно то, что вы хотите, без необходимости использоватьapply

>>> 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
источник
80
Ухоженная! Более обобщенная версияdf[df.columns] = scaler.fit_transform(df[df.columns])
citynorman
6
@RajeshThevar Внешние скобки - это типичные скобки селектора панд, указывающие пандам выбрать столбец из фрейма данных. Внутренние скобки обозначают список. Вы передаете список селектору панд. Если вы просто используете одиночные скобки - с одним именем столбца, за которым следует другое, разделенное запятой, - pandas интерпретирует это так, как будто вы пытаетесь выбрать столбец из фрейма данных с многоуровневыми столбцами (MultiIndex), и выдаст ошибку ключа ,
Кен
1
чтобы добавить к ответу @ken, если вы хотите точно увидеть, как pandas реализует эту логику индексации и почему кортеж значений будет интерпретироваться иначе, чем список, вы можете посмотреть, как DataFrames реализуют __getitem__метод. В частности, вы можете открыть ipython и выполнить pd.DataFrame.__getitem__??; после того, как вы импортируете панд как pd, конечно;)
LetsPlayYahtzee
4
Практическое замечание: тем, кто использует разделение данных обучения / тестирования, вы захотите подогнать только свои данные обучения, а не данные тестирования.
Дэвид Дж.
1
Чтобы масштабировать все, кроме столбца временных меток, объедините с columns =df.columns.drop('timestamps') df[df.columns] = scaler.fit_transform(df[df.columns]
intotecho
19

Как это?

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
Когда я запускаю этот скрипт, я получаю кучу предупреждений об устаревании. Как его обновить?
пир
См. Ответ @ LetsPlayYahtzee ниже
AJP
2
Более простая версия: dfTest [['A', 'B']] = dfTest [['A', 'B']]. Apply (MinMaxScaler (). Fit_transform)
Александр В.
12

Как упоминается в комментарии pir, .apply(lambda el: scale.fit_transform(el))метод выдаст следующее предупреждение:

DeprecationWarning: передача 1d-массивов в качестве данных устарела в версии 0.17 и вызовет ValueError в 0.19. Измените форму данных с помощью X.reshape (-1, 1), если ваши данные имеют одну функцию, или X.reshape (1, -1), если они содержат один образец.

Преобразование столбцов в массивы numpy должно сработать (я предпочитаю StandardScaler):

from sklearn.preprocessing import StandardScaler
scale = StandardScaler()

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

- Редактировать ноябрь 2018 г. (проверено на pandas 0.23.4 ) -

Как упоминает Роб Мюррей в комментариях, в текущей (v0.23.4) версии pandas .as_matrix()возвращается FutureWarning. Поэтому его следует заменить на .values:

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()

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

- Изменить май 2019 г. (проверено для панд 0.24.2 ) -

Как упоминает joelostblom в комментариях: «Так 0.24.0как рекомендуется использовать .to_numpy()вместо .values

Обновленный пример:

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
использование .valuesвместо .as_matrix()как as_matrix()сейчас дает FutureWarning.
Роб Мюррей
10
df = pd.DataFrame(scale.fit_transform(df.values), columns=df.columns, index=df.index)

Это должно работать без предупреждений об амортизации.

athlonshi
источник
7

Сделать это можно, используя pandasтолько:

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
источник
6
Я знаю, что могу сделать это только в пандах, но, возможно, в конечном итоге я захочу применить другой метод sklearn, который не так легко написать самому. Мне больше интересно выяснить, почему применение к серии статей не работает так, как я ожидал, чем найти строго более простое решение. Следующим моим шагом будет запуск RandomForestRegressor, и я хочу убедиться, что понимаю, как Pandas и sklearn работают вместе.
flyingmeatball 09
5
Этот ответ опасен, потому что df.max() - df.min()может быть 0, что приведет к исключению. Более того, df.min()вычисляется дважды, что неэффективно. Обратите внимание, что df.ptp()эквивалентно df.max() - df.min().
Acumenus
3

Я знаю, что это очень старый комментарий, но все же:

Вместо одинарной скобки (dfTest['A'])используйте двойные скобки (dfTest[['A']]).

то есть: min_max_scaler.fit_transform(dfTest[['A']]).

Я верю, что это даст желаемый результат.

WAN
источник