.

Сделать репост в соц сети!

четверг, 27 сентября 2018 г.

LeaveOneOutEncoder: препроцессинг категориальных переменных в sklearn pipeline Python


В продолжение темы препроцессинга категориальных переменных в sklearn pipeline Python.
См. также пост Препроцессинг категориальных переменных в sklearn pipeline Python с LabelEncoder.
Т.е. предыдущий пост про технику препроцессинга на основе LabelEncoder, а в данном посте я покажу, как работаю с LeaveOneOutEncoder.
LeaveOneOutEncoder пожалуй самый необычный способ препроцессинга категориальных переменных.


LeaveOneOutEncoder считается очень удобным, когда у вас много (от нескольких десятков до нескольких сотен) уровней фактора, а также когда часть уровней фактора очень немногочисленны. В моей практике самый лучший пример - уровень зарплат по регионам. У нас 89 регионов в России, причем, львиная доля приходится на Москву, меньше на Санкт-Петербург, а потом уже на остальные регионы, а на некоторые приходится совсем единицы.

Пример

У нас есть такой датасет

df= pd.DataFrame({ 'y': [10,2,3,4,5,6,7,8], 'a': [np.nan, 'b','a', 'b','a', 'b','a', 'b' ],
                  'c': ['a', 'b','a', 'b','a', 'b','b', 'b' ]})
Где y - целевая переменная, которую мы предсказываем, а a, c - категориальные переменные, на основе которых мы прогнозируем y.
После препроцессинга по методу LeaveOneOutEncoder категориальные переменные принимают такой вид
 0 1
0 5.625 6.0
1 5.000 5.4
2 5.000 6.0
3 5.000 5.4
4 5.000 6.0
5 5.000 5.4
6 5.000 5.4
7 5.000 5.4
 
Вы еще не знакомы с техникой препроцессинга категориальных переменных по методу LeaveOneOutEncoder? После препроцессинга переменные становятся типа numeric, т.е. из категориальных превращаются в числовые.


LeaveOneOutEncoder - это среднее по каждому уровню фактора. Возьмем переменную c. Уровню переменной a соответствуют значения целевой переменной Y 10, 3 и 5. Среднее значение по ним равно (10+3+5)/3 = 6. Таким образом, в категориальной переменной с уровень a принимает значение 6.0. А если у нас уровень пропущен, т.е поле пустое, то оно принимает значение среднего по всем данным. В переменной a есть пропущенное значение (np.nan), после препроцессинга оно принимает значение 5.625. Оно получается как среднее значение по всем переменным: (10+2+3+4+5+6+7+8)/8=5.625.
Простой код препроцессинга
lb = df[['a', 'c']]
enc = LeaveOneOutEncoder()
encc = enc.fit(np.asarray(lb), df['y'])
enc_data = enc.transform(np.asarray(lb))
enc_data 
Обратите внимание,  в команде fit у нас участвует целевая переменная. И поэтому код для pipeline у нас будет немного отличаться от того, что мы делали, например, для LabelEncoder.

LeaveOneOutEncoder в в sklearn pipeline Python

загружаем необходимые пакеты
import pandas as pd
import numpy as np
from sklearn import preprocessing
import sklearn
from sklearn.pipeline import Pipeline
from sklearn.pipeline import FeatureUnion
from category_encoders import  LeaveOneOutEncoder
from sklearn import linear_model
Toy датасет
 df= pd.DataFrame({ 'y': [10,2,3,4,5,6,7,8], 'a': [np.nan, 'b','a', 'b','a', 'b','a', 'b' ],
                  'c': ['a', 'b','a', 'b','a', 'b','b', 'b' ], 'b': [5,5,3,4,8,6,7,3],})
Я добавил еще переменную b только для того, чтобы показать, что мы можем брать в препроцессинг любое количество переменных. И декларируем формулу по выбору этим переменных
 
class MultiColumn():
    def __init__(self,columns = None):
        self.columns = columns # array of column names to encode

    def fit(self,X,y=None):
        return self
    def transform(self, X):                                                           
        return X[self.columns]
Теперь объект class для препроцессинга категориальных переменных с помощью LeaveOneOutEncoder.
lb = df[['a', 'c']]
class MyLEncoder(BaseEstimator, TransformerMixin):

    def transform(self, X, **fit_params):
        enc = LeaveOneOutEncoder()
        encc = enc.fit(np.asarray(lb), y)
        enc_data = encc.transform(np.asarray(X))

        return enc_data

    def fit_transform(self, X,y=None,  **fit_params):
        self.fit(X,y,  **fit_params)
        return self.transform(X)

    def fit(self, X, y, **fit_params):
        return self


На самом деле все просто. И выходим на pipeline
X = df[['a', 'b', 'c']]
y = df['y']

regressor = linear_model.SGDRegressor()

pipeline = Pipeline([
    ('union', FeatureUnion(
        transformer_list=[
             # categorical
            ('categorical', Pipeline([
                 ('selector', MultiColumn(columns=['a', 'c'])),
                ('one_hot', MyLEncoder())
                
            ])),
        
        ])),
    # Use a regression
    ('model_fitting', linear_model.SGDRegressor()),
])

pipeline.fit(X, y)
 
Делаем predict на нашем датасете.
 
pipeline.predict(X)
array([5.49996715, 4.92702859, 5.19722919, 4.92702859, 5.19722919,
       4.92702859, 4.92702859, 4.92702859])



Но в нашем случае нужно обязательно проверить, что препроцессинг категориальных переменных в sklearn pipeline Python с помощью LeaveOneOutEncoder работает на новом датасете.
Создаем его и проверяем.
 new= pd.DataFrame({ 'y': [3, 8], 'a': ['a', 'b' ],'c': ['b', 'a' ], 'b': [3, 6],})
pipeline.predict(new)
array([4.92702859, 5.19722919])
Работает. Буду рад критике и замечаниям.



Комментариев нет:

Отправить комментарий