.

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

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

воскресенье, 20 декабря 2020 г.

Открытый он-лайн курс Python для HR

ПРИГЛАШАЮ ОТСЛЕЖИВАТЬ НАС В ТЕЛЕГРАМ


Решил написать открытый он-лайн курс Python для HR. Открытый - значит бесплатный (но я, конечно, не откажусь от доната

  1. Номер карты Сбербанка 4274 2780 5167 5026
  2. Карта Тинкофф банк 5280 4137 5046 3700

если Вам курс будет полезен). Видеолекции буду выкладыать на YouTube канале HR-аналитики - рекомендую подписаться и следить за каналом. Рекомендую также свои курсы



Цели курса Python для HR 

  1. познакомить с Python
  2. показать примеры решения задач аналитики / авоматизации операций HR через Python 

Как будет реализован курс Python для HR:

  • Буду выкладывать видео, начиная с базового уровня, чтобы было понятно тем, кто делает первые шаги. 
  • Видео будут выкладываться по мере написания роликов, поэтому это скорее серия видеолекций. 
  • И третье: надеюсь, тематика будет рождаться в том числе на основе обратной связи. На первом этапе тематика понятна: загрузка данных, манипуляции с данными, визуализации. А что дальше? Матстатистика? Парсинг сайтов? Анализ текстов? Прикладные задачи типа расчета текучести / автоматизация расчета премий и все такое? В этом месте нужна будет ваша помощь.

В общем посмотрим, как пойдет. 

  1. До курса надо устаовить дистрибутив Anaconda и Jupyter Notebook, см. Как установить Anaconda Python и Jupyter Notebook
  2. Или, если нет возможности установить, можно воспользоваться инструментом от Google, см. Google Colab для новичков (изучаем Python)

Содержание курса

1. Загрузка данных 

  1. Знакомство с Jupyter Notebook загрузка данных из excel и csv
    • знакомимся с ноутбуком (основные кнопки, ячейки кода / Markdown)
    • загружаем библиотеку pandas 
    • загружаем данные из excel в ноутбук (pd.read_excel) 
    • df.head() 
    • параметры sheet_name, skiprows, nrows, header 
    • загружаем данные из csv в ноутбук (pd.read_csv), параметры sep, encoding (последняя команда важна, когда возникает проблема с загрузкой файла в кириллице)
    • создаем DataFrame "руками"
  2. Как загрузить excel файл в google colab с локального компьютера - если вы не установили Anaconda и Jupyter Notebook, то рекомендую посмотреть данное видео, поскольку загрузка файлов с Вашего локального компа отличается от загрузки в Jupyter Notebook
  3. Как загрузить и объединить несколько файлов в Jupyter Notebook - загружаем и объединяем сразу несколько файлов excel, изучаем формулы pd.append(), pd.merge()
    • формула pd.append() - объединяем несколько датасетов в один
    • библиотека glob - скачиваем названия файлов
    • пишем первый цикл for in для автоматизации загрузки и слияния нескольких файлов
    • pd.to_excel() - выгрузка из ноутука в excel 
    • pd.merge() - сливаем данные по типа ВПР в excel
  4. Как загрузить данные в Jupyter Notebook Python из Google таблиц и из интенета
    • загрузка данных из Google таблиц
    • загрузка данных из интернета pd.read_html()

2. Pandas DataFrame: знакомство, манипуляции с данными

фильтр, сортировка, описательные статистики, агрегация, пропущенные значе
  1. Pandas DataFrame: общий обзор данных, сортировка, фильтр 
    • переименование ноутбука в Notebook 
    • закомментировать выражение 
    • df.info()
    • Типы данных pandas 
    • df.shape
    • df.values
    • df.columns
    • df.index
    • df.sort_values() - сортировка
    • Фильтр по колонкам (отбор переменных)
    • Фильтр по строкам
    • isin() - фильтр сразу двух и более уровней категориальной переменной
  2. Pandas DataFrame: индекс отбор строк и переменных на основе индекса
    • df.index
    • df.set_index() создаем индекс
    • df.reset_index() сбрасываем индекс
    • df.loc() отбор строк / переменных на оcнове имен индекса
    • df.iloc() отбор строк / переменных на оcнове позиции индекса
    • отбор, когда индекс имеет формат даты
  3. Pandas Python описательные статистики
    • mean(), median(), min(), max(), std(), sum(), quantile() 
    • agg()
    • pd.set_option() меняем научный формат данных 5.000e+03 на привычный нам формат 5 000
    • describe()
    • describe(include = 'object') - описательные статистики для категориальных переменных
    • value_counts()
  4. Pandas Python работа с пропущенными значениями
    • isna()
    • isna().any()
    • isna().sum()
    • визуализация числа пропущенных значений по переменным 
    • dropna()
    • inplace=True
    • fillna()
  5. Pandas Python агрегация данных сводные таблицы 
    • groupby()
    • pivot_table()
    • crosstab()

3. Визуализация matplotlib 

  1. Знакомство с matplotlib Динамика увольнений по месяцам в Pandas Python
    • dt.strftime("%Y-%m") извлекаем год и месяц из даты
    • value_counts() агрегируем данные
    • def fire(x) все действия по подсчету динамики автоматизируем в формуле
    • plt.plot() визуализация 
    • кастомизация диаграммы: plt.axes(), plt.xticks(), plt.grid(), plt.xlabel(), plt.ylabel(), plt.title()
    • plt.annotate()
    • Цикл для значений диаграммы
  2. Matplotlib компоненты figure axes
    • figure, axes
    • facecolor
    • figsize=()
    • ax.tick_params, ax.grid(), ax.set_xlabel(), ax.set_ylabel(), ax.set_title()
    • ax.annotate
  3. Python Matplotlib subplots несколько диаграмм - на прошлой лекции мы построили диаграмму динамики уволенных по месяцам, в этой - посмотрим на динамику в разрезе причин увольнения - построим несколько диаграмм на одном листе
    • plt.subplots() 
    • координаты позиций диаграмм 
    • squeeze 
    • sharey
  4. Matplotlib кастомизация plt subplots - мы разместили несколько диаграмм на одном листе, а теперь всю кастомизацию "зашиваем" в одну формулу.
    • plt.tight_layout() - настраивает размеры диаграмм так, чтобы подписи и заголовки умещались в размер 
    • ax.xaxis.set_major_locator(plt.MultipleLocator()) - подписи оси X разреживаем - даем их не подряд по всем значениям, а пропускам часть, чтобы подписи не сливались, а были читаемы
  5. Гистограмма распределения зарплат Matplotlib Python
    • ax.hist
    • help(ax.hist)
    • ax.hist(x, range = ...) - границы гистограммы
    • ax.hist(x, bins = ...) - управление примоугольниками 
    • ax.xaxis.set_major_locator(plt.IndexLocator(base = 20, offset =0))
    • x.annotate() - подписи данных
  6. Matplotlib barplot распределение респондентов по сферам деятельности медианы зарплат по группам
    • groupby('var').size() - агрегируем данные по сфере деятельности респондентов
    • df.replace() меняем некорректное значение данных 
    • ax.bar(x.index, y.values) - визуализирем распределение респондентов по сферам деятельности 
    • df.groupby('var')['zp'].median().sort_values(ascending=False) - считаем меины зарплат по группам сфер деятельности респондентов
    • визуализируем медианы зарплат 
    • добавляем стандартное отклонение к данным и визуализируем errorbar
  7. Boxplot Ящик с усами matplotlib pandas распределение зарплат по группам 
    • ax.boxplot()
    • df.boxplot(column, , ax=ax)
    • распологаем ящики в порядке возрастания / убывания
  8. Scatterplot Точечная диаграмма matplotlib взаимосязь возраста и зарплаты респондентов
  9. Стиль диаграммы сохранить лиаграмму как рисунок plt style use savefig matplotlib 
    • plt.style.use() 
    • fig.savefig()

4. "Чистка", cleaning данных

  1. Python cleaning data чистка данных str strip replace 
    • str.strip()
    • rstrip() lstrip()
    • .replace()
    • как одной командой провести все преобразования данных
    • как обработать сразу несколько переменных pandas
  2. Раcчет стажа сотрудников Pandas Python формат дат
    • notnull()
    • pandas.to_datetime()
    • создаем переменную event
    • считаем стаж
  3. Считаем eNPS сотрудников в Python Бинниг переменных
    • np.where 
    • pandas.cut
    • pandas.DataFrame.quantile
    • eNPS


Далее все новые видеолекции курса Python для HR будут выкладываться сюда.

Посты блога с тегом Python

См. также все посты блога с тегом Python, например, 



ПРИГЛАШАЮ ОТСЛЕЖИВАТЬ НАС В ТЕЛЕГРАМ

понедельник, 24 декабря 2018 г.

Снижение числа уровней категориальной переменной Pandas Python





Задачка простая, но мне она показалась очень интересной, и я вынес ее в блог.
Задача часто встречается в наших HR данных, смысл ее в том, что в категориальной переменной много уровней с очень низкой частотой, и мы должны эти уровни переменной вынести в один уровень "другое", чтобы анализ был корректным.

Решение


Загружаем библиотеки
import numpy as np
import pandas as pd
В качестве toy example такой датафрейм
df = pd.DataFrame({'Город':
'Москва Москва Москва Москва Москва Москва Санкт-Петербург Санкт-Петербург Санкт-Петербург Новосибирск Новосибирск Красноярск Магадан'
                   .split()})
df
 Город
0 Москва
1 Москва
2 Москва
3 Москва
4 Москва
5 Москва
6 Санкт-Петербург
7 Санкт-Петербург
8 Санкт-Петербург
9 Новосибирск
10 Новосибирск
11 Красноярск
12 Магадан
Сначала мы считаем количество значений
val = pd.value_counts(df['Город'])
val
Москва             6
Санкт-Петербург    3
Новосибирск        2
Магадан            1
Красноярск         1
Name: Город, dtype: int64
А потом пользуемся замечательной формулой pandas.Series.lt


mask = val.lt(2)
mask 
Москва             False
Санкт-Петербург    False
Новосибирск        False
Магадан             True
Красноярск          True
Name: Город, dtype: bool
Мы указали все города, частота которых менее 2 раз. А далее аналог формулы ЕСЛИ в excel
df['city']  = np.where(df['Город'].isin(val[mask].index),'другое',df['Город'] ).astype(object)
И проверяем
pd.crosstab(df['Город'], df['city'])
city      Москва Новосибирск Санкт-Петербург другое
Город    
Красноярск 0 0     0           1
Магадан         0 0     0           1
Москва         6 0     0           0
Новосибирск 0 2     0           0
Санкт-Петербург 0 0     3           0
Сошлось.
А если мы хотим задать порог не в абсолютных числах, а в %? Уровнем статистической значимости считают 5%, поэтому мы можем смело его задавать.
Правда я ниже укажу не 5, а 10 %, потому что в нашем датасете нет значений менее 5 %. Мы меняем только эту формулу, остальное все тоже самое
mask = (val/val.sum() * 100).lt(10)
Все. Можно применять

пятница, 21 декабря 2018 г.

Считаем количество поручений работника на актуальную дату (код в Python)

Передо мной встала задача прогноза исполнения поручений. В CRM фиксируются данные по поручению: кто назначил, когда, дата исполнения, подразделение, сорк исполнения и т.п..
Вот у меня возникла идея посчитать количество незакрытых актуальных поручений на дату назначения нового поручения. Ну т.е. руководитель назначает новое поручение, а машинка в этом момент считает, что вместе с этим поручением у Васи Иванова еще четыре незакрытых поручения. Вот такая задачка, и я хочу показать, как эта задача решается в Python.
Код


Понадобится всего одна библиотека


import pandas as pd
И мы имеет вот примерно такой датафрейм
df = pd.DataFrame({'id': (1,1,1,2,2), 'begin': ('01.01.2018','01.02.2018', '01.03.2018', '01.01.2018', '01.02.2018'),
    'end': ('01.02.2018','01.03.2018', '01.04.2018', '01.02.2018', '01.03.2018')})
df['begin']= pd.to_datetime(df['begin'])
df['end']= pd.to_datetime(df['end'])
df
    begin     end        id
0 2018-01-01 2018-01-02 1
1 2018-01-02 2018-01-03 1
2 2018-01-03 2018-01-04 1
3 2018-01-01 2018-01-02 2
4 2018-01-02 2018-01-03 2
Где begin это дата начала проекта, end дата окончания проекта, а id - это исполнитель. Наша задача получить такой датафрейм
      begin       end      id  new
0   2018-01-01  2018-01-02  1   3
1   2018-01-02  2018-01-03  1   2
2   2018-01-03  2018-01-04  1   1
3   2018-01-01  2018-01-02  2   2
4   2018-01-02  2018-01-03  2   1
Где new это переменная, показывающая количество актуальных / незакрытых поручений на дату открытия нового проекта. В строке 0 у нас стоит 3, поскольку на дату 01 января 2018 у этого работника три незакрытых поручения. Цифру три можно получить, если мы возьмем даты окончания поручений по этому работнику, сравним их с датой начала данного поручения и посчитаем количество случаев, когда дата начала поручения меньше, чем дата окончания поручения.
Первое, с чего мы начинаем - сформируем по каждому работнику списки дат начала и окончания проектов
gr_end = df.groupby(['id'])['end'].apply(list)
gr_begin = df.groupby(['id'])['begin'].apply(list)
Получается вот так
gr_begin
id
1    [2018-01-01 00:00:00, 2018-01-02 00:00:00, 201...
2           [2018-01-01 00:00:00, 2018-01-02 00:00:00]
Name: begin, dtype: object
id в данном случае индекс и обозначает id работника, а каждому id соответствует объект типа list с датами в данном случае начала поручения.
Теперь самое сложное. По каждому работнику надо сравнить дату начала поручения со всеми датами окончания поручений и посчитать количество случаев, где дата начала поручения была меньше даты окончания.
Получился вот такой цикл
l = []
for i in gr_begin.index:
    for x in gr_begin[i]:
        cv = [j == 1 for j in gr_end[i] if j > x].count(False)
        l.append(cv)


В написании циклов я не очень силен, поэтому, если вы увидите, что цикл можно улучшить, буду вам благодарен. Итог такой
l
[3, 2, 1, 2, 1]
Далее мы list l превращаем в новую переменную и окончательный датафрейм выглядит так
df['new'] = l
df
      begin        end     id  new
0   2018-01-01  2018-01-02  1   3
1   2018-01-02  2018-01-03  1   2
2   2018-01-03  2018-01-04  1   1
3   2018-01-01  2018-01-02  2   2
4   2018-01-02  2018-01-03  2   1
После написания кода я обратился на stackoverflow с просьбой предложить идеи по улучшению кода см. Create new variable based on groupby & value comparison
Мне предложили такой вариант 
merged = df.merge(df.drop(columns='end'), on='id', suffixes=('', '_y'))
live_projects = merged[merged.begin<=merged.begin_y]
result = live_projects.groupby(['id','begin','end']).count().reset_index()
result.rename(columns={'begin_y':'new'}, inplace=True)
Вполне рабочий код.



четверг, 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]
['классическая',
 'российская',
 'компания',
 'с',
 'назначениями',
 'не',
 'по',
 'знаниям',
 'а',
 'по',
 'личной',
 'приверженности',
 'не',
 'соблюдающая',
 'свои',
 'же',
 'правила',
 'делающая',
 'глупость',
 'за',
 'глупостью',
 'и',
 'оправдывающая',
 'их',
 'еще',
 'большими',
 'глупостями']
Этот формат уже можно использовать для создания словаря. Что мы и делаем.

четверг, 29 марта 2018 г.

Как скачивать / парсить тексты постов блога blogspot



Пост для пользователей Python и тех, кто занимается парсингом контента из интернета.
Недавно я показывал, как скачиваю / парсю вакансии с сайта ХХ.ру Как я скачиваю вакансии компаний с HH.ru (на примере Газпрома), сейчас я хочу показать, как можно скачивать тексты постов блогов платформы blogspot, т.е. как я могу скачать свой блог и покажу это на своем блоге.
Текст блога может быть использован в исследовании широкого класса задач, связанных с анализом текста.
Как скачивать / парсить тексты постов блога blogspot

Итак

Необходимые пакеты


import pandas as pd 
import feedparser
from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
Вам надо будет установить библиотеки, которых нет по дефолту.

Сам парсинг

Первый шаг - нам необходимо получить урлы всех постов в блоге, а уже по ним скачивать содержание. И урлы мы скачиваем так.
urla = []
url = feedparser.parse('https://www.blogger.com/feeds/1020819187099399113/posts/default?redirect=false&start-index=1&max-results=50')
for x in url.entries:
    urla.append(str(x.link))
Здесь
  1. 1020819187099399113 - ID блога, который вы хотите скачать 
  2. max-results=50 - я сделал 50, но максимальное значение 500. И это плохо для блогов, где более 500 постов, но для это есть гиперпараметр 
  3. start-index=1 - если указано 1, значит вы с последнего поста начинаете отматывать парсинг, поэтому, когда вы скачали 500 постов, ставите 500 (или 501?) и продолжаете парсинг

urla = pd.Series(urla)
зачем то я полученный лист оборачиваю в формат series. И далее создаю переменную дата

Дата

date = []
# создаю переменную дата
for i, idbank in enumerate(urla):
    html = urlopen(idbank)
    bsObj = BeautifulSoup(html, "html.parser")
    nameList = bsObj.findAll("h2", { 'class':"date-header"  })
    date.append(nameList)
Нам надо очистить дату от тегов и прочего мусора, я это делаю так.
df = pd.DataFrame({'date':date})
df['date'] = df['date'].apply(lambda x: (re.sub(r'<.*?>', '', str(x))))
df['date'] =  df['date'].str.strip('[]')
df['date'] 
И мы получаем вот такой вид даты
0           вторник, 27 марта 2018 г.
1       воскресенье, 25 марта 2018 г.
2       понедельник, 19 марта 2018 г.
3       воскресенье, 18 марта 2018 г.
4           четверг, 15 марта 2018 г.
5           вторник, 13 марта 2018 г.
6       воскресенье, 11 марта 2018 г.
Это необязательная переменная, я ее добавил просто в качестве примера.


Текст постов

скачиваем так
art = []
for i, idbank in enumerate(urla):
    html = urlopen(idbank)
    bsObj = BeautifulSoup(html, "html.parser")
    nameList = bsObj.findAll("div", {"dir":"ltr", "style":"text-align: left;", "trbidi":"on"})
    art.append(nameList)
bsObj.findAll("div", {"dir":"ltr", "style":"text-align: left;", "trbidi":"on"}) - вот эта запись может быть не единственным вариантом скачивания / парсинга теста поста, я не придумал ничего более умного. И текст тоже надо причесать
df['text'] = art
df['text'] = df['text'].apply(lambda x: (re.sub(r'<.*?>', '', str(x))))
df['text'] = df['text'].str.strip('[]')
rem = {'\(adsbygoogle = window.adsbygoogle \|\| \[]\)\.push\({}\);': ''}
df['text'].replace(rem, regex=True, inplace=True)
df['text'] = df['text'].replace('\n', '', regex = True)
df['text']
Вот над этим шедевром я поработал основательно)) rem = {'\(adsbygoogle = window.adsbygoogle \|\| \[]\)\.push\({}\);': ''} в итоге получаем
0     Перевод статьи 3 Behaviors That Drive Successf...
1     Вдохновившись переводамистатей на тему Organiz...
2     Для тех, кто хочет погрузитьсяв статистическое...
3     Перевод статьи Case Study: Using ONA to Identi...
4          Провокативный пост на самом деле.Я провож...
5     Перевод статьи Consumer-goods giant Unilever h...
6     Перевод статьи The 4 Trends Changing How You H...
7          Провел опрос в телеграм канале HR-аналити...
Обращаю ваше внимание, что я ставил задачу парсить скачивать только текст постов, если вы хотите качать целые посты, то надо отказаться от очистки от тегов. Ну и т.п.
Ну или вместе это выглядит так
df
df
      date                                       text
0 вторник, 27 марта 2018 г.     Перевод статьи 3 Behaviors That Drive Successf...
1 воскресенье, 25 марта 2018 г. Вдохновившись переводамистатей на тему Organiz...
2 понедельник, 19 марта 2018 г. Для тех, кто хочет погрузитьсяв статистическое...
3 воскресенье, 18 марта 2018 г. Перевод статьи Case Study: Using ONA to Identi...
4 четверг, 15 марта 2018 г.     Провокативный пост на самом деле.Я провож...
Пользуйтесь на здоровье. Формат выгрузки можно менять, т.е. вместо переменной Дата можно поставить автора, а дату в другом формате, но это уже детали, их можно менять.



__________________________________________________________
На этом все, читайте нас в фейсбукетелеграмме и вконтакте

вторник, 20 февраля 2018 г.

Как я скачиваю вакансии компаний с HH.ru (на примере Газпрома)



Больше интересных материалов - в телеграм канале HR-аналитике. Сразу признаюсь: неделю назад я ничего не знал про API HH, парсинг вакансий с ХХ.ру. Поэтому не претендую на лучший способ парсинга вакансий. 
Если Вы предложите более удобный способ - буду благодарен.
Мне вакансии потребовалось парсить в рамках задачи Сравнительный анализ корпоративной культуры Альфабанка и Сбербанка на основе текстов вакансий
Работаю в Python.
И предупреждаю: данный алгоритм будет полезен только для парсинга открытых вакансий конкретных компаний. Для парсинга, например вакансий по определенному запросу с определенной даты в определенном регионе уже потребуется другой алгоритм - обращайтесь, если что.

Алгоритм

Необходимые пакеты

import numpy as np
import requests
from tqdm import tqdm_notebook
import pandas as pd
Давайте на примере Газпрома. Самый типа простой способ
Как я скачиваю вакансии компаний с HH.ru


r = requests.get('https://api.hh.ru/vacancies?employer_id=39305').json() 
r
39305 - это номер работодателя на ХХ, в нашем случае Газпром. Как найти ID компании, думаете не проблема: ищите страницу работодателя в ХХ.ру, там в урле берете ID.
Этот способ нас сразу не устраивает, потому что по дефолту эта команда скачивает только 20 вакансий - вакансии размещаются на страницах по 20 вакансий на каждой, и приведенная команда скачает вакансии только с первой страницы. А у нас всего найдено
'found': 774,

В самом низу выдачи r видим

'pages': 39,
'per_page': 20

Т.е. у нас 774 вакансии на 39 страницах. Запускам цикл, чтобы скачать все страницы с 774 вакансиями Газпрома

vac = []
for i in tqdm_notebook(range(0, 39)):
    vac.append(requests.get("https://api.hh.ru/vacancies?employer_id=39305", params={'page': i, 'per_page':20}).json())
И получаем объект типа лист vac. Но и эта выдача меня не устраивает. Если вы посмотрите на отдельные элементы листа типа v[0], то можете обратить внимание, что описание требований к кандидату и предложение компании обрезаны.

'snippet': {'requirement': 'Высшее образование. Опыт работы в банковской сфере 
не менее одного года. Опыт работы в клиентском сервисе не менее двух лет. ',
    'responsibility': 'Привлечение и обслуживание клиентов премиум сегмента. 
Развитие текущих клиентов (установление долгосрочных отношений). 
Выполнение плана продаж. Финансовое консультирование и продажа инвестиционных...'},
Это значит, что надо искать другой путь. Я сначала скачиваю ID всех вакансий Газпрома

pac=[]
for i in range(0, 39):
    for j in range(0, 20):
        pac.append(vac[i]['items'][j]['alternate_url'])
И мы получаем объект типа лист с всеми урлами вакансий Газпрома. Нам надо еще оставить одни номера урлов, а не сами урлы
lili = [re.sub(r'[^0-9]', '', e) for e in pac]
У вас в этом месте выдаст ошибку, потому что я забыл указать библиотеку regex - установите ее. Теперь у нас набор ID вакансий Газпрома. Но вы заметили, что мы сначала скачали все содержимое вакансий, а из него вытащили уже ID. Это нихт гут, если парсить надо много контента, поэтому можно сразу парсить ID вакансий так

vah = []
for i in tqdm_notebook(range(0, 39)):
    for j in tqdm_notebook(range(0, 20)):
        vah.append(requests.get("https://api.hh.ru/vacancies?employer_id=39305", params={'page': i, 'per_page':20}).json()['items'][j]['alternate_url'])
формула страшная для гуманитариев типа меня. И кстати, по времени парсинга я не заметил сильного отличия с парсингом всех вакансий.
Далее мы скачиваем вакансии уже по этим прямым ID. Сначала опять выделяем только числа из урлов, потом скачиваем


lulu = [re.sub(r'[^0-9]', '', e) for e in vah]
vak_url = 'https://api.hh.ru/vacancies/{}'

var = []
for i in lulu:
    var.append(requests.get(vak_url.format(i)).json())
В общем все, дальше мы превращаем скачанные вакансии в объект pandas
df = pd.DataFrame(var)
И последнюю беду, которую мне лично нужно было решить, это избавиться от тегов урлов, потому что описание вакансий идет в таком виде
df['description']
Чтобы избавиться от тегов, выполняем команду
df['description'] = df['description'].apply(lambda x: (re.sub(r'<.*?>', '', str(x))))
Напомню, что я решал локальную задачу парсинга открытых вакансий конкретной компании, для решения других задач потребуется возможно другой алгоритм.




__________________________________________________________
На этом все, читайте нас в телеграмме и вконтакте





среда, 7 февраля 2018 г.

Исследование email коммуникаций в компании



Прохожу на Курсере курс Applied Social Network Analysis in Python Мичиганского университета. Вообще рекомендую курсы этого университета: много интересного контента. Курсы на Python проходят.
Прошел я пока две недели, хвастать особо нечем, хочу в посте скорее закрепить свои знания. Заранее предупреждаю, что я не ас, могу писать ерунду в каких-то местах, просто тема для меня новая. А новая потому, что я не могу найти понимания того, куда в HR ее применить. Буду вам благодарен за идеи. Вообще же на Западе тема Organisational Network Analytics очень популярна и набирает темп. Поэтому я и решил подробней в ней разобраться. 

Данные е майл коммуникаций

У нас есть данные типа
#Sender Recipient time
1 2 1262454010
1 3 1262454010
1 4 1262454010
1 5 1262454010
1 6 1262454010
1 7 1262454010
1 8 1262454010
1 9 1262454010
1 10 1262454010
1 11 1262454010

Где первая колонка - отправитель, вторая - получатель, и время отправления. При этом перечень полей не ограничен: это такой датасет в задании, а вообще первое, что приходит на ум - можно добавить роль отправителя и получателя.
И первое, чему я самостоятельно научился по этой теме - раскрашивать узлы (nodes) цветом в зависимости от роли. Как вариант - можно брать не роль, а класс письма: заявка, обращение, нецензурная брань и т.п..
Исследование email коммуникаций в компании
Эта картинка из другого задания.
Если вы хотите потренироваться, запишитесь на курс и скачайте данные второй недели. Или лучше работать со своими данными: можете мне прислать, я буду очень рад поработать с ними.
Код

import networkx as nx
import pandas as pd
%matplotlib notebook
import matplotlib.pyplot as plt
Загрузка
G = nx.read_edgelist('email_network.txt', data=[ ('time', float)], 
                         create_using=nx.MultiDiGraph())
Я не уверен, что стоит подробно функции объяснять, все равно придется копаться в пакете, отмечу только, что самое важное здесь - MultiDiGraph - мы указываем, что отношения между узлами (nodes) директивные (Di), т.е. письмо идет в определенном направлении от кого то к кому то, т.е. у нас на диаграмме появляется стрелочка. И отношения Multi - может быть много связей, а точнее писем. И data - мы указываем, что время - это атрибут сети, а не сами связи. После загрузки мы получаем объект edges (ребра? грани?)
G.edges(data=True)
[('158', '64', {'time': 1271766218.0}),
 ('154', '68', {'time': 1271766200.0}),
 ('42', '54', {'time': 1276505582.0}),
 ('42', '1', {'time': 1266579464.0}),
 ('42', '61', {'time': 1273146267.0}),
 ('42', '151', {'time': 1266319958.0}),
 ('42', '151', {'time': 1268999261.0}), ....
Тут все понятно. Количество строк
len(G.edges(data=True))
82927
И вот здесь сразу одна интересная ситуевина. С помощью команды
G.degree()
{'1': 3376,
 '10': 643,
 '100': 126,
 '101': 758,
 '102': 31,
 '103': 461,
 '104': 632,
 '105': 321,
 '106': 860,
 '107': 192,
 '108': 151,
 '109': 166,
 '11': 635,
 '110': 85,
......

Мы получаем список количества писем каждого чувака. И всего у нас
nx.number_of_nodes(G)
167 
чуваков, а писем
sum(G.degree().values())
165854
Что в два раза больше, чем строк в датасете (82927). Т.е. это значит, что пакет учитывает не только тех, кто слева, но и справа. И если ты значишься не только в числе посылателей писем, но и получателей, вам это будет зачтено.

Визуализация


plt.figure(figsize=(10,9))
node_color = [G.degree(v) for v in G]
pos = nx.fruchterman_reingold_layout(G)
nx.draw_networkx(G, pos, 
                 node_color=node_color, alpha=0.7, with_labels=True, 
                 edge_color='.4', cmap=plt.cm.Blues)
plt.axis('off')
plt.tight_layout();
Цвет узла (node) я определил в зависимости от количества получаемых писем


Исследование email коммуникаций в компании
Не очень внятно, верно?) функция pos = nx.fruchterman_reingold_layout(G) отвечает за представление результатов сети, можете поиграться, картинка будет другая. К тому же можно выделить отдельный диапазон картинки.
Исследование email коммуникаций в компании
Матрица.

суббота, 29 июля 2017 г.

Тренируем нейронную сеть для прогноза эффективности работников



На моем семинаре по аналитике есть задачка в 87 строк, где выходная переменная - эффективность работника (категориальная переменная их трех классов: 1 - звезда, 2 - середняк, 3 - лузер), а факторы - несколько шкал теста CPI (калифорнийский чего-то там) и пол. Все.
Данных мало, зато данные реальные реальной компании. Надеюсь, что этот датасет будет "ирисами" HR-аналитики.
Эту задачу я уже решал с помощью логистической регрессии, деревьев решений, случайными лесами, градиентным бустингом. Сегодня покажу код в Python, как настраивать нейронную сеть для этой задачи. На самом деле есть несколько подходов к созданию нейронной сети, отдельно можно пользоваться библиотекой Microsoft, можно было добавлять скрытые слои, можно было тренировать больше параметров, все можно было сделать. Я преследую цель показать просто шаблон, как можно тренировать модель использую sklearn обертку - в этом варианте есть одно очень важное преимущество - простота в тренировке параметров сетки.
Итак
Загружаем пакеты
import numpy
import pandas
from keras.models import Sequential
from keras.layers import Dense
from keras.wrappers.scikit_learn import KerasClassifier
from keras.utils import np_utils
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold
from sklearn.preprocessing import LabelEncoder
from sklearn.pipeline import Pipeline
from sklearn import model_selection, preprocessing, metrics
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV, ShuffleSplit
from sklearn.metrics import confusion_matrix
Закладываем воспроизводимость
seed = 7
numpy.random.seed(seed)

Загружаем данные (здесь уже простите, участники семинара сами могут достать этот датасет)
разбиваем на X, Y
dataset = df.values
X = dataset[:,0:7].astype(float)
Y = dataset[:,7]
Далее Y мы из формата (1,2, 3) превращаем в матрицу фиктивных переменных
encoder = LabelEncoder()
encoder.fit(Y)
encoded_Y = encoder.transform(Y)
# convert integers to dummy variables (i.e. one hot encoded)
dummy_y = np_utils.to_categorical(encoded_Y)

Собственно модель

# baseline model
def baseline_model():
    # create model
    model = Sequential()
    model.add(Dense(6, input_dim=7, kernel_initializer='normal', activation='relu'))
    model.add(Dense(3, kernel_initializer='normal', activation='sigmoid'))
    # Compile model
    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
    return model
Здесь model.add обозначает слой нейронной сети, input_dim - размерность входных данных (у нас 7 переменных: 6 шкал теста + пол), во втором слое model.add тройка обозначает размерность выходной переменной (у нас три возможных класса работников - звезды, середняки и лузеры).
Про остальные параметры рекомендую читать в специальной литературе. Далее разбиваем данные на трейн и тест сет
X_train,  X_test, y_train, y_test = model_selection.train_test_split(X, Y, test_size = 0.3,random_state = 12)
Задаем модель, кросс валидацию и параметры, которые требуется натренировать
# create model
model = KerasClassifier(build_fn=baseline_model, verbose=0)
cv = KFold(n_splits=3, shuffle=True, random_state=1)
# grid search epochs, batch size
epochs = [ 150, 170, 200, 250, 300, 350]
batches = [ 5, 10, 20, 25, 30]
param_grid = dict(epochs=epochs, batch_size=batches)
grid = GridSearchCV(estimator=model, param_grid=param_grid, cv = cv)
Эпохи в нейронной сети соответствуют n_estimators в бустинге или случайном лесе. Батчи - это размер выборки, на которой происходит обучение. Далее закладываем в сетку (grid) и обучаем.
%%time
grid_result = grid.fit(X_train, y_train)
({'batch_size': 5, 'epochs': 300}, 0.73333334922790527)
На тестовом сете точность почти один в один, хотя при датасете в 87 строк мы ни на что не можем надеяться)
confusion_matrix(y_test, grid_result.predict(X_test))
array([[ 9,  2,  0],
       [ 1, 11,  0],
       [ 1,  3,  0]])

В качестве визуализации confusion_matrix

Тренируем нейронную сеть для прогноза эффективности работников
В строках реальные значения, 1 - звезда, 2 - середняк, 3 - лузер, в колонках прогнозные значения. Заметно, что нейронная сеть не увидела лузеров.
Не вспомню уже ссылки на предыдущие решения этого кейса, но precision и recall для нейронной сети для звезд вполне сопоставимы для алгоритмов случайного леса и бустинга.
Но интересная штука: многослойный персептрон дает прогноз хуже, чем тот, что я использовал в примере. Дело даже не в хужести, а в том, что алгоритм все прогнозные значения загоняет в один класс, в нашем случае это класс середняков - он самый большой. Т.е. многослойный персептрон все прогнозные значения к одному классу причисляет. Если есть кто в теме, подскажите, что это за эффект?
И сравнил эти данные с данными библиотеки Microsoft CNTK - не увидел различий (причем, я говорю не о датасете в 87 строк, а проверил также на другой HR задачке в 1500 строк).

Понравился пост? 

и Вы захотите выразить мне благодарность, просто покликайте на директ рекламу ниже на странице - у вас это отнимет несколько секунд, а мне принесет немного денег.

воскресенье, 25 июня 2017 г.

LightGBM vs XGBoost


Сейчас в моду входит алгоритм LightGBM, появляются статьи а ля Which algorithm takes the crown: Light GBM vs XGBOOST?. В задаче говорится о том, что LightGBM  дал на одинаковых данных прогноз чуть лучше, чем XGBoost, но зато по времени LightGBM работает гораздо быстрее, чем XGBoost
На Каггле LightGBM во многих задачах стал обходить XGBoost.
Я решил проверить на своих данных. Алгоритм моей услуги - прогноз зарплаты Сколько я стою на рынке - построен на XGBoost. Сегодня на том же датасете я запустил оба алгоритма. 
Для XGBoost
xgb = XGBRegressor( nthread=4,   seed=1234567890)
Для LightGBM 
gbm = lgb.LGBMRegressor(objective='regression', nthread=4,   seed=1)
Далее параметры для обоих алгоритмов одинаковые
param1 = {
 'model_fitting__learning_rate': [ 0.01, 0.02,  0.03, 0.04, 0.05,  0.06, 0.07, 0.08, 0.09, 1],
 'model_fitting__n_estimators': [   100, 150, 200, 250, 300, 350, 400, 450, 500, 550]
}
Плюс настраиваю сетку
cv = ShuffleSplit(n_splits=6, test_size=0.25, random_state=678901)
score = 'neg_mean_absolute_error'
gsearch1 = GridSearchCV(pipeline, param1 , scoring= score,iid=False, cv=cv)

Результаты

XGBoost работал более 5 часов
({'model_fitting__learning_rate': 0.03, 'model_fitting__n_estimators': 500},
 -0.30208288404701737)
LightGBM работал в два раза быстрее Wall time: 2h 42min 59s
({'model_fitting__learning_rate': 0.03, 'model_fitting__n_estimators': 200},
 -0.30150633397192345)

* Вставка 26.06.

Я проверил данные также на похожем датасете.
XGBoost также отработал более 6 часов, метрика -0.28208288404701737
LightGBM  Wall time: 3h 34min 35s, метрика -0.28217897475881154
В качестве метрики была MAE - y измеряется в логарифмированных рублях, поэтому различие практически никакое. А вот время работы да, отличается сильно.
И логика экономии времени понятна: LightGBM обходится меньшим количеством деревьев - n_estimators
Ну как результаты?) Вполне себе согласуются с тем, что написал индус в своем посте по ссылке выше. Я не стал пока дальше сетку копать, у меня датасет объемный. Плюс надо еще разбираться с гиперпараметрами LightGBM. Обязательно отпишусь, когда полностью настрою модель.

Полезные ссылки про LightGBM

Про установку пакета не буду писать, а вот для работы с пакетом мне самыми полезными показались две ссылки

  1. LightGBM в sklearn - я ленивый, работаю в sklearn обертке, мне такой вариант наиболее подходящий для работы с пакетом и настройки сетки grid
  2. Source code for lightgbm.sklearn - а этот пост помогает понять, какие гипрепараметры настраивать. Тут много вкусного, с чем можно поиграться.

Понравился пост? 

и Вы захотите выразить мне благодарность, просто покликайте на директ рекламу ниже на странице - у вас это отнимет несколько секунд, а мне принесет немного денег.

вторник, 18 апреля 2017 г.

Python и машинное обучение Себастьяна Рашки



Python и машинное обучение
Python и машинное обучениеКоллеги, начну с рекламы издательства Книжный интернет-магазин КТК "Галактика" - издательство выпускает на рынок не только книги про аналитику, но и, например, книги для детей, но для меня это лучшее издательство по аналитике.
Мне уже говорили некоторые аналитики, что лучше изучать машинное обучение на курсах, дабы руками запоминать уроки. Но я получил просто неоценимые знания в книге Коэльо. Или как можно говорить об аналитике в R без книги Введение в статистическое обучение с примерами на языке R - и курс по этой книге в Стенфорде является дополнением к книге, а не наоборот.
-
Python и машинное обучение - книга из этого же золотого фонда. Скажу мистическую вещь: эта книга просто должна стоять на полке. И любой аналитик / специалист по машинному обучению должен знать имя Себастьяна Рашки (по ссылке - профиль автора в Линкедине, а на фото, как вы ужу догадались - сам автор. На фото справа мой нос и очки поверх книги). Мне кажется, фото Себастьяна Рашки - лучшая реклама книги. И если верить профилю Рашки в Линкедине, он работает сейчас PhD Candidate - Computational Biology в Мичиганском Университете (до кучи замечу, что у Мичиганского университета куча интересных курсов по Python на курсере - от работы с текстом до построения рекомендательных систем).
Сайт индийских аналитиков Analytics Vidhya (этот сайт стремительно завоевыает популярность среди аналитиков, специалистов по data science и машинному обучению) считает Рашку самым влиятельным аналитиком в Мире. Себастьян Рашка один из разработчиков библиотеки Python scikit-learn.
Книгу эту могут использовать как новички, так и продвинутые спецы: она содержит базовые алгоритмы машинного обучения в библиотеке scikit-learn, но также содержит продвинутые вещи типа тонких настроек алгоритмов через сетку (grid) параметров, встраивание алгоритмов машинного обучения в веб- приложения (Flask, а не Django если что). И тренировка нейронных сетей (ну куда же без распознавания изображений???:)) с помощью библиотек Keras и Theano.
Еще от себя: я очень бегло посмотрел книгу, но по ощущениям - в книге Python и машинное обучение идеальное сочетание методологии, кода на Python, математики и визуализации.
Резюме: я ставлю эту книгу в ряд самых моих затрепанных книг по аналитике:
  1. Введение в статистическое обучение с примерами на языке R - стенфордские профессора;
  2. Построение систем машинного обучения на языке Python - Луиса Педро Коэльо 
  3. И вот теперь третья книга - Python и машинное обучение Себастьяна Рашки.
Есть еще классные книги Мастицкого по R, есть технические книги по R типа R в действии. Анализ и визуализация данных на языке R или классная книга по технике в Python Python и анализ данных - эти две книги важные, они про технику, они как библиотечные / справочные издания для работы в R и Python, вторую книгу преподы курса Вышки Введение в машинное обучение на Курсере представляли как базовую книгу своего курса, но если говорить именно о машинном обучении, то я бы в базовые книги, книги золотого фонда, самые-затрепанные-книги поставил три выше перечисленные.
И вам всем удачи в изучении машинного обучения через книгу Python и машинное обучение Себастьяна Рашки:)
Купить на Озоне>>

понедельник, 2 января 2017 г.

Stackoverflow как ресурс развития

Отчасти хвастаюсь, отчасти призываю пользоваться.
Существует такой ресурс Stackoverflow (для программистов? айтишников? аналитиков?). Всем, кто пытался ответ на вопросы про R или Python в интернете, обязательно знаком этот ресурс. Большая часть в поисковых запросах про Python точно ведет на этот ресурс.
Логика ресурса простая: любой желающий может задать вопрос по любому языку программирования, а любой желающий может ответить на ваш вопрос. Любой зарегистрированный на ресурсе может поставить лайк как понравившемуся ответу, так и понравившемуся вопросу. Лайки приносят баллы. Т.е. баллы можно заработать и вопросом. Кроме того, вы, как задавший вопрос, можете выбрать лучший ответ.
Но есть и возможность поставить дис лайк - каждый дис лайк снимает баллы.
Есть модераторы, которые следят за тем, чтобы вопросы соответствовали правилам и нормам сообщества (например, в вопросе должен быть обязательно пример кода).
Система очень простая, но весьма эффективна в мотивации развития, если вы вступаете на путь вопросов и ответов.
Я поначалу получил несколько дис лайков за свои тупые вопросы, и знаете, это очень дисциплинирует. Ты начинаешь корректно формулировать вопрос.
И может быть самое ценное, что я из этого вынес: работа с Toy example - игрушечными примерами: создаешь случайный, небольшой набор данных, на котором пытаешься воспроизвести и решить проблему.
Вот пример вопроса, за который я получил три лайка How to combine multiple cells into a single text сell - суть вопроса проста: когда вы создаете сводную таблицу, то можете агрегировать числовые переменные: считать, суммировать, брать среднее и т.п.. Но если у вас есть текст, то что как объединить текстовые ячейки? И этот вопрос имеет непосредственное отношение к HR -тематике.

И самое приятное 

На прошлой неделе ответил на вопрос и получил сразу три лайка. How to add a column to pandas DataFrame based on the result of a condition.
Логика вопроса такая:
есть переменная со статусом человека (?) - "Blocked" или "Cleared";
есть дата какого-то события, есть дата другого события.
Задача простая: создать новую переменную на основе условий: переменная "BlockedAfter" состоит из двух значений - 1 и 0. 1 присваивается, если статус "Blocked" и дата одного события раньше другого события.
В excel это можно решить формулой ЕСЛИ. В Python я написал такой код
df['ReceiptDate'] = pd.to_datetime(df['ReceiptDate'])
df['IncentiveStart'] = pd.to_datetime(df['IncentiveStart'])
df['time'] = df['IncentiveStart'].values.astype('datetime64[D]').astype(int) - df['ReceiptDate'].values.astype('datetime64[D]').astype(int)
df['BlockedAfter'] = 0
df.ix[(df['time'] > 0) & (df['Clear_Decline'] == 'Blocked'), 'BlockedAfter'] = 1
Код можно было проще написать, без переменной "time", но когда ты видишь вопрос, хочешь ответить быстрее других, то уже в детали не вникаешь.

Резюме

Как только вы в освоении R или Python поднялись выше первого класса общеобразовательной школы, регистрируйтесь на этом ресурсе и развивайтесь. Вопросы развивают не меньше, чем сами ответы. Учитесь задавать вопросы. 

четверг, 13 октября 2016 г.

Как вычислять Крускал Уоллис тест в Python



Продолжаю публиковать стандартные функции в Python. См. предыдущий пост Как считать Хи квадрат в Python.
Крускал Уоллис тест позволяет определить значимость различий признака по нескольким группам. Например, уровень зарплаты (числовая переменная) по Москве, Санкт-Петербургу и Красноярску. Стаж (числовая переменная) по группам бухгалтеров, продажников и кассиров. Крускал Уоллис тест -тест непараметрический, это значит, что для различия поста русских, китайцев, эфиопов мы применим, скорее всего, дисперсионный анализ - параметрический аналог теста Крускал Уоллис. Рост у нас имеет нормальное распределение, что позволяет применить диспресионный анализ. А вот стаж, зарплата не имеют нормального рапределения, что требует непараметрический тест, в нашем случае Крускал Уоллис тест.
Загружаем необходимые пакеты
import pandas as pd
import numpy as np
import scipy
 
Делаем игрушечный набор данных
df = pd.DataFrame({'numbers':range(9), 'group':['a', 'b', 'c']*3})

Самое интересное в данном посте - переводим наши данные в требуемый формат.
groups = {}
for grp in df['group'].unique():
    groups[grp] = df['numbers'][df['group']==grp].values
print(groups)

Теперь вычисляем Крускал Уоллис тест
args = groups.values()
scipy.stats.kruskal(*args)

Или, если нужна группировка
args = [groups[grp] for grp in sorted(df['group'].unique())]

Все. В Python Крускал Уоллис тест вычисляется не так просто, как в R, но ... привыкаешь:)

Пост показался полезным?

Кликните на директ рекламу - вы сэкономите время с помощью кода, а  потрачу время не зря

вторник, 11 октября 2016 г.

Как считать Хи квадрат в Python

После того, как сгорает жесткий диск ноутбука, начинаешь думать о вечности - как сохранить свои коды. И тогда начинаешь ценить посты как этот Как в Python читать файлы формата excel.
В этом посте я сохраняю и показываю код, как считать Хи квадрат в Python. У меня уже есть пост Как считать Хи квадрат в excel. Так что я продолжаю традицию.
Для начала загружаем необходимые пакеты
import pandas as pd
import scipy
Создаем наш игрушечный набор данных
df = pd.DataFrame({'a' : ['b', 'b','a', 'a' , 'a', 'a', 'a', 'a' , 'a', 'a','a',
'a', 'b', 'b', 'b', 'b', 'b', 'a','a','a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'a', 'a'],

 'b' : ['c','c', 'c','c', 'c', 'c','c', 'c', 'c','d' , 'd'  , 'd' , 'd' , 'd', 
'd',  'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd']})
Наш файл содержит две колонки: a и b с признаками a,b,c,d.
Создаем сводную таблицу или таблицу сопряженности
pd.crosstab(df['a'], df['b'])
Получаем вот такое распределение
c d

a 10 5
b 6 8
Далее такой код
table = [ [ 10, 5 ], [ 6, 8 ] ]

chi2, prob, df, expected = scipy.stats.chi2_contingency(table)

output = "test Statistics: {}\ndegrees of freedom: {}\np-value: {}\n"

print(output.format( chi2, df, prob))

print(expected)
Думаю, этот код понятен: создаем объект table, создаем формулу подсчета показателей и ожидаемых значений.
и получаем такой вывод
test Statistics: 0.8367044413919416
degrees of freedom: 1
p-value: 0.3603410063766622

[[ 8.27586207  6.72413793]
 [ 7.72413793  6.27586207]]
Все. Хи квадрат в Python считается легко.

Пост показался полезным?

Кликните на директ рекламу - вы сэкономите время с помощью кода, а мое затраченное время будет не таким бессмысленным)))

среда, 17 августа 2016 г.

Как в Python читать файлы формата excel



Иногда задача кажется бессмысленной, но все равно хочется ее решить: ведь совершенно просто файл формата .xlsx сохранить в формате .csv и не парить по поводу загрузки в Python для обработки.
Но раз уж попала вожжа под хвост, то покатимся. Это первая причина, а вторая причина - мои посты на 99 % носят содержательный характер, гуманитарный я бы сказал. Т.е. я показываю содержательное решение задачи, а где-нибудь в уголке показываю метод (с помощью xgboost). А сегодня под настроение хочется показать кусок кода.

Ну и так

давайте загрузим в Python такой файл
first
second
2
5
3
6
Загружаем сначала необходимые пакеты
import pandas as pd
import xlrd
За загрузку файла excel отвечает пакет xlrd, и загружается он напрямую, не через pip - мелочь, а приятно.
Загружаем
rb = xlrd.open_workbook('D:1.xlsx',formatting_info=False)
sheet = rb.sheet_by_index(0)
Далее вот такой хитрый код
row = []
for i in range(sheet.nrows):
    r = sheet.row_values(i)
    row.append(r)
print (row)
Данные у нас выходят в таком виде
[['first', 'second'], [2.0, 5.0], [3.0, 6.0]]

Нам это не нравится, мы переводим в табличный вид
df = pd.DataFrame(row)
df.columns = df.iloc[0]
df = df.reindex(df.index.drop(0))
Последние две строки для того, чтобы первая строка стала заголовками. Можно, видимо, сделать это элегантней.
Ну и как то так
first
second
1
2
5
2
3
6

Такой способ загрузки может быть востребован, если вы не хотите тратить время на переформатирование файла (чаще присылают excel, а не csv). И кроме того, при данном способе Python обрезает пустые строки сверху. Т.е. если вам прислали файл, в котором сверху отступ в виде пустых строк, то загрузятся только строки с данными

Пост показался полезным?

Кликните на директ рекламу - вы сэкономите время с помощью кода, а мое затраченное время будет не таким бессмысленным)))