.

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

Показаны сообщения с ярлыком pipeline. Показать все сообщения
Показаны сообщения с ярлыком pipeline. Показать все сообщения

вторник, 21 апреля 2020 г.

Построение нормированной гистограммы в R с использованием dplyr и pipeline



Такая задача часто возникает в анализе данных. Давайте посмотрим ее решение на примере Toy example

Создаем данные
 df = data.frame(a = sample(c('a', 'b', 'c', 'd'), 1000, replace = TRUE), 
                b = sample(c('x', 'y', 'zizuzi'), 1000, replace = TRUE),
c = rnorm(1000)) 
Загружаем необходимые библиотеки
library(dplyr) # преобразование данных
library(ggplot2) # визуализация 
library(scales) # на диаграмме значения преобразовывает в %
Далее собственно преобразование

четверг, 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])
Работает. Буду рад критике и замечаниям.



воскресенье, 23 сентября 2018 г.

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



Для тех, кто работает в Python. Пост показывает ответы на два вопроса:

  1. Препроцессинг категориальных переменных с помощью sklearn pipeline;
  2. Использование LabelEncoder в sklearn pipeline

Код
Загружаем пакеты

import pandas as pd
import numpy as np
from sklearn import preprocessing
from sklearn.preprocessing import LabelEncoder
import sklearn
from sklearn.pipeline import Pipeline
from sklearn.pipeline import FeatureUnion
from sklearn import linear_model

df= pd.DataFrame({ 'y': [10,2,3,4,5,6,7,8], 'a': ['a', 'b','a', 'b','a', 'b','a', 'b' ],
                  'b': ['a', 'b','a', 'b','a', 'b','b', 'b' ],  'c': ['a', 'b','a', 'a','a', 'b','b', 'b' ]})
df
Определяем объект типа class для отбора категориальных переменных из датасета.
 
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 для препроцессинга категориальных переменных с применением LabelEncoder.
lb = df[['a', 'c']]
class MyLEncoder():

    def transform(self, X, **fit_params):
        enc = preprocessing.LabelEncoder()
        enc_data = []
        for i in list(lb.columns):
            encc = enc.fit(lb[i])
            enc_data.append(encc.transform(X[i]))

        return np.asarray(enc_data).T

    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 

lb - это набор категориальных переменных, на которых мы делаем препроцессинг. Такой код необходим для решения двух проблем:

четверг, 26 июля 2018 г.

Анализ тональности текста с использованием word2vec и реализацией в pipeline Python



Анализ тональности текста или сентимент анализ - это метод классификации текста. Самый популярный пример из курсов по машинному обучению - прогноз оценки, которую поставит посетитель ресторана заведению на основе его отзыва. Или, по другому, можем ли мы спрогнозировать на основе отзыва посетителя, будет ли он рекомендовать этот ресторан или нет.
В HR сама собой напрашивается аналогичная задача: можем ли мы спрогнозировать на основе отзыва увольняющегося работника в exit интервью спрогнозировать, будет ли он рекомендовать нашу компанию коллегам / будет ли он отзываться о компании позитивно или негативно. Хотя, безусловно, класс решаемых задач значительно шире. Я бы отослал здесь к статье Raja Sengupta Как NLP может в корне изменить HR. NLP - это Natural language processing или проще - анализ текстов. Моя задача проще - я хочу показать код для решения одной задачи в Python.

word2vec vs "bag of words"

Одним из самых популярных методов анализа текстов (а точнее, этот метод просто хронологически более ранний - и, может быть, более интуитивно понятный) является метод "мешок слов "bag of words". Мы просто получаем столько переменных, сколько у нас слов в тексте (исключая "мусорные" или редкие слова). Т.е. если в отзывах у нас используется 1 485 слов, то у нас будет 1485 новых переменных / колонок. И если в отзыве содержится слово - оно же название переменной - то переменная принимает значение "1", в противном случае "0". Т.е. если респондент написал отзыв "хорошая компания", то из 1485 ячеек напротив данного респондента будет только две "1" - в колонках "хорошая" и "компания".
Этот подход интуитивно понятен, но он имеет ряд недостатков (что делать с "не"? и т.п...), но главное: в этом подходе не отражается смысл слов, фраз.
word2vec
Преодолением такого подхода является метод word2vec (буквально 'word' to 'vector'), который превращает весь текст в N-мерное пространство, и каждое слово это вектор со своими координатами, т.е. буквально можно записать так:

'опрос': array([ 0.05069825, -0.01941545,  0.00567565, -0.0276236 ,  0.01180002,
      .......  0.00385726])
Не показываю весь вектор, потому что он имеет 100 значений. И эти 100 значений - это переменные в нашем уравнении.
"Плюс" этого метода в том, что близкие по значению слова имеют близкие координаты векторов. Например, когда я делал модель для функционала HR, то для слова "компенсации" самые близкие координаты вектора имело слово "c&b". И это замечательно, потому что в подходе "bag of words" слова "c&b" и "компенсации" это разные слова, а в подходе word2vec эти слова хоть и не идентичны, но очень близки.

Данные

У меня свой датасет, которым я с вами не поделюсь, но вы можете опробовать этот код на своих данных. Структура данных достаточно проста:
  1. переменная "текст";
  2. бинарная переменная 1/ 0, +1 / -1 и т.п..

Я свои данные взял из нашего исследования факторов текучести персонала (участвуем в исследовании) . Помимо всего прочего, в нашем опросе есть две переменные:
"Отзыв о компании";
"Готовы ли Вы рекомендовать эту компанию в качестве работодателя своим знакомым, коллегам?"


Реализация в Python


Итак, начинаем  с загрузки данных
import pandas as pd
import numpy as np
df = pd.read_csv('data.csv', sep=',', encoding = 'cp1251')
df.info()
Data columns (total 2 columns):
y 792 non-null int64
Отзыв о компании 792 non-null object
dtypes: int64(1), object(1)
df['y'].value_counts()
1    404
0    388
Name: y, dtype: int64


Да, у нас очень небольшой датасет, лучше иметь несколько тысяч, даже несколько десятков тысяч строк данных. Но моя задача скромнее - показать алгоритм. Выборка у нас достаточно сбалансированная - соотношение тех, кто готов рекомендовать компанию, и тех, кто не готов - почти 50/50.
Первая задача, которую нам надо решить - создать словарь слов. Т.е. присвоить каждому слову координаты вектора. Для этого нам необходимо взять весь текст и обучить его. Но прежде нам необходимо преобразовать наши отзывы из формата pandas в формат, годный для преобразований word2vec.
Преобразуем таким образом

import nltk
import nltk.data
tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')
from nltk.tokenize import sent_tokenize, word_tokenize
import re 
 def preproc(sentence):
    sent_text = re.sub(r'[^\w\s]','', sentence)
    words = sent_text.lower().split()
    return(words)
 def senttxt(sent, tokenizer, remove_stopwords=False ):
        raw_sentences = tokenizer.tokenize(oped.strip())
        sentences = []
        for raw_sentence in raw_sentences:
            
            sentences.append(preproc(raw_sentence))
        
        len(sentences)
        return sentences
txt_snt = df['Отзыв о компании'].tolist()
sentences = []
for i in range(0,len(nyt_opeds)):
    sent = txt_snt[i].replace("/.", '')
    sentences += senttxt(sent, tokenizer)
В итоге мы получаем объект вот такого формата
sentences[0]
['классическая',
 'российская',
 'компания',
 'с',
 'назначениями',
 'не',
 'по',
 'знаниям',
 'а',
 'по',
 'личной',
 'приверженности',
 'не',
 'соблюдающая',
 'свои',
 'же',
 'правила',
 'делающая',
 'глупость',
 'за',
 'глупостью',
 'и',
 'оправдывающая',
 'их',
 'еще',
 'большими',
 'глупостями']
Этот формат уже можно использовать для создания словаря. Что мы и делаем.