<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 <center>
<img src="../../img/ods_stickers.jpg">
Открытый курс по машинному обучению. Сессия № 2
Автор материала
Step1:  Теперь перейдем непосредственно к машинному обучению.
Данные по кредитному скорингу представлены следующим образом
Step2:  Напишем функцию, которая будет заменять значения NaN на медиану в каждом столбце таблицы.
Step3:  Считываем данные
Step4:  Рассмотрим типы считанных данных
Step5:  Посмотрим на распределение классов в зависимой переменной
Step6:  Выберем названия всех признаков, кроме прогнозируемого
Step7:  Применяем функцию, заменяющую все значения NaN на медианное значение соответствующего столбца.
Step8:  Разделяем целевой признак и все остальные – получаем обучающую выборку.
Step9:  Бутстрэп
<font color='red'>Задание 2.</font> Сделайте интервальную оценку (на основе бутстрэпа) среднего дохода (MonthlyIncome) клиентов, просрочивших выплату кредита, и отдельно – для вовремя заплативших. Стройте 90% доверительный интервал. Найдите разницу между нижней границей полученного интервала для не просрочивших кредит и верхней границей – для просрочивших. 
То есть вас просят построить 90%-ые интервалы для дохода "хороших" клиентов $[good_income_lower, good_income_upper]$ и для "плохих" – $[bad_income_lower, bad_income_upper]$ и найти разницу $good_income_lower - bad_income_upper$.
Используйте пример из статьи. Поставьте np.random.seed(17). Округлите ответ до целых.
<font color='red'>Варианты ответа
Step10:  Дерево решений, подбор гиперпараметров
Одной из основных метрик качества модели является площадь под ROC-кривой. Значения ROC-AUC лежат от 0 до 1. Чем ближе значение ROC-AUC к 1, тем качественнее происходит классификация моделью.
Найдите с помощью GridSearchCV гиперпараметры DecisionTreeClassifier, максимизирующие площадь под ROC-кривой.
Step11:  Используем модуль DecisionTreeClassifier для построения дерева решений. Из-за несбалансированности классов  в целевом признаке добавляем параметр балансировки. Используем также параметр random_state=17 для воспроизводимости результатов.
Step12:  Перебирать будем вот такие значения гиперпараметров
Step13:  Зафиксируем кросс-валидацию
Step14:  <font color='red'>Задание 3.</font>
Сделайте GridSearch с метрикой ROC AUC по гиперпараметрам из словаря tree_params. Какое максимальное значение ROC AUC получилось (округлите до 2 знаков после разделителя)? Назовем кросс-валидацию устойчивой, если стандартное отклонение метрики качества на кросс-валидации меньше 1%. Получилась ли кросс-валидация устойчивой при оптимальных сочетаниях гиперпараметров (т.е. обеспечивающих максимум среднего значения ROC AUC на кросс-валидации)?
<font color='red'>Варианты ответа
Step15:  Простая реализация случайного леса
<font color='red'>Задание 4.</font>
Реализуйте свой собственный случайный лес с помощью DecisionTreeClassifier с лучшими параметрами из прошлого задания. В нашем лесу будет 10 деревьев, предсказанные вероятности которых вам нужно усреднить. 
Краткая спецификация
Step16:  <font color='red'>Задание 5.</font>
Тут сравним нашу собственную реализацию случайного леса с sklearn-овской. Для этого воспользуйтесь RandomForestClassifier(n_jobs=1, random_state=17), укажите все те же значения max_depth и max_features, что и раньше. Какое среднее значение ROC AUC на кросс-валидации мы в итоге получили? Выберите самое близкое значение.
<font color='red'>Варианты ответа
Step17:  Случайный лес sklearn, подбор гиперпараметров
<font color='red'>Задание 6.</font> 
В 3 задании мы находили оптимальные гиперпараметры для одного дерева, но может быть, для ансамбля эти параметры дерева не будут оптимальными. Давайте проверим это с помощью GridSearchCV (RandomForestClassifier(random_state=17)).  Только теперь расширим перебираемые занчения max_depth до 15 включительно, так как в лесу нужны деревья поглубже (а почему именно – вы поняли из статьи). Какими теперь стали лучшие значения гиперпараметров?
<font color='red'>Варианты ответа
Step18:  Логистическая регрессия, подбор гиперпараметров
<font color='red'>Задание 7.</font>  Теперь сравним с логистической регрессией (укажем class_weight='balanced' и random_state=17). Сделайте полный перебор по параметру C из широкого диапазона значений np.logspace(-8, 8, 17). 
Только сделаем это корректно и выстроим пайплайн – сначала масштабирование, затем обучение модели. 
Разберитесь с пайплайнами и проведите кросс-валидацию. Какое получилось лучшее значение средней ROC AUC? Выберите самое близкое значение.
<font color='red'>Варианты ответа
Step19:  Логистическая регрессия и случайный лес на разреженных признаках
В случае небольшого числа признаков случайный лес показал себя лучше логистической регрессии. Однако один из главных недостатков деревьев проявляется при работе с разреженным данными, например с текстами. Давайте сравним логистическую регрессию и случайный лес в новой задаче.
Скачайте данные с отзывами к фильмам отсюда.
Step20:  <font color='red'>Задание 8.</font> Сделайте полный перебор по параметру C из выборки [0.1, 1, 10, 100]. Какое лучшее значение ROC AUC получилось на кросс-валидации? Выберите самое близкое значение.
<font color='red'>Варианты ответа
Step21:  <font color='red'>Задание 9.</font> Теперь попробуем сравнить со случайным лесом. Аналогично делаем перебор и получаем максимальное ROC AUC. Выберите самое близкое значение.
<font color='red'>Варианты ответа
<ASSISTANT_TASK:>
Python Code:
import math
def nCr(n,r):
    f = math.factorial
    return f(n) / f(r) / f(n - r)
p, N, m, s = 0.8, 7, 4, 0
for i in range(m, N+1):
    s += nCr(N, i) * p**i * (1 - p) ** (N - i)
print(s)
Explanation: <center>
<img src="../../img/ods_stickers.jpg">
Открытый курс по машинному обучению. Сессия № 2
Автор материала: Data Science интерн Ciklum, студент магистерской программы CSDS UCU Виталий Радченко, программист-исследователь Mail.ru Group, старший преподаватель Факультета Компьютерных Наук ВШЭ Юрий Кашницкий. Материал распространяется на условиях лицензии Creative Commons CC BY-NC-SA 4.0. Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала.
<center> Домашнее задание №5
<center> Случайный лес и логистическая регрессия в задачах кредитного скоринга и классификации отзывов к фильмам
Нашей главной задачей будет построение и настройка моделей для задач кредитного скоринга и анализа отзывов к фильмам. Заполните код в клетках (где написано "Ваш код здесь") и ответьте на вопросы в веб-форме.
Но для разминки решите первое задание.
<font color='red'>Задание 1.</font> В зале суда есть 7 присяжных, каждый из них по отдельности с вероятностью 80% может правильно определить, виновен подсудимый или нет. С какой вероятностью присяжные все вместе вынесут правильный вердикт, если решение принимается большинством голосов?
<font color='red'>Варианты ответа:</font>
- 20.97%
- 80.00%
- 83.70%
- 96.66%
<font color="red">Решение</font>:
поскольку большинство голосов – 4, тогда наше m = 4, N = 7, p = 0.8. Подставляем в формулу из статьи $$ \large \mu = \sum_{i=4}^{7}C_7^i0.8^i(1-0.8)^{7-i} $$
После подставления и проделывания всех операций получим ответ 96.66%
End of explanation
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
Explanation: Теперь перейдем непосредственно к машинному обучению.
Данные по кредитному скорингу представлены следующим образом:
Прогнозируемая  переменная
SeriousDlqin2yrs  – Человек имел долгие просрочки выплат платежей за 2 года; бинарный признак
Независимые признаки
age                            – Возраст заёмщика кредитных средств (число полных лет); тип – integer
NumberOfTime30-59DaysPastDueNotWorse  – Количество раз, когда человек имел просрочку выплаты других кредитов более 30-59 дней (но не больше) в течение последних двух лет; тип -  integer
DebtRatio –   Ежемесячный отчисления на задолжености(кредиты,алименты и т.д.)  / совокупный месячный доход    percentage; тип – float
MonthlyIncome – Месячный доход в долларах; тип –  float
NumberOfTimes90DaysLate – Количество раз, когда человек имел просрочку выплаты других кредитов более 90 дней; тип –   integer
NumberOfTime60-89DaysPastDueNotWorse –    Количество раз, когда человек имел просрочку выплаты других кредитов более 60-89 дней (но не больше) в течение последних двух лет; тип – integer
NumberOfDependents – Число человек в семье кредитозаёмщика; тип – integer
End of explanation
def impute_nan_with_median(table):
    for col in table.columns:
        table[col]= table[col].fillna(table[col].median())
    return table   
Explanation: Напишем функцию, которая будет заменять значения NaN на медиану в каждом столбце таблицы.
End of explanation
data = pd.read_csv('../../data/credit_scoring_sample.csv', sep=";")
data.head()
Explanation: Считываем данные
End of explanation
data.dtypes
Explanation: Рассмотрим типы считанных данных
End of explanation
ax = data['SeriousDlqin2yrs'].hist(orientation='horizontal', color='red')
ax.set_xlabel("number_of_observations")
ax.set_ylabel("unique_value")
ax.set_title("Target distribution")
print('Distribution of target:')
data['SeriousDlqin2yrs'].value_counts() / data.shape[0]
Explanation: Посмотрим на распределение классов в зависимой переменной
End of explanation
independent_columns_names = data.columns.values
independent_columns_names = [x for x in data if x != 'SeriousDlqin2yrs']
independent_columns_names
Explanation: Выберем названия всех признаков, кроме прогнозируемого
End of explanation
table = impute_nan_with_median(data)
Explanation: Применяем функцию, заменяющую все значения NaN на медианное значение соответствующего столбца.
End of explanation
X = table[independent_columns_names]
y = table['SeriousDlqin2yrs']
Explanation: Разделяем целевой признак и все остальные – получаем обучающую выборку.
End of explanation
def get_bootstrap_samples(data, n_samples, seed=0):
    # функция для генерации подвыборок с помощью бутстрэпа
    np.random.seed(seed)
    indices = np.random.randint(0, len(data), (n_samples, len(data)))
    samples = data[indices]
    return samples
def stat_intervals(stat, alpha):
    # функция для интервальной оценки
    boundaries = np.percentile(stat, [100 * alpha / 2., 100 * (1 - alpha / 2.)])
    return boundaries
# сохранение в отдельные numpy массивы данных по просрочке
churn = data[data['SeriousDlqin2yrs'] == 1]['MonthlyIncome'].values
not_churn = data[data['SeriousDlqin2yrs'] == 0]['MonthlyIncome'].values
# генерируем выборки с помощью бутстрэра и сразу считаем по каждой из них среднее
churn_mean_scores = [np.mean(sample) 
                     for sample in get_bootstrap_samples(churn, 1000, seed=17)]
not_churn_mean_scores = [np.mean(sample) 
                         for sample in get_bootstrap_samples(not_churn, 1000, seed=17)]
#  выводим интервальную оценку среднего
print("Mean interval",  stat_intervals(churn_mean_scores, 0.1))
print("Mean interval",  stat_intervals(not_churn_mean_scores, 0.1))
print("Difference is", stat_intervals(not_churn_mean_scores, 0.1)[0] - 
      stat_intervals(churn_mean_scores, 0.1)[1])
Explanation: Бутстрэп
<font color='red'>Задание 2.</font> Сделайте интервальную оценку (на основе бутстрэпа) среднего дохода (MonthlyIncome) клиентов, просрочивших выплату кредита, и отдельно – для вовремя заплативших. Стройте 90% доверительный интервал. Найдите разницу между нижней границей полученного интервала для не просрочивших кредит и верхней границей – для просрочивших. 
То есть вас просят построить 90%-ые интервалы для дохода "хороших" клиентов $[good_income_lower, good_income_upper]$ и для "плохих" – $[bad_income_lower, bad_income_upper]$ и найти разницу $good_income_lower - bad_income_upper$.
Используйте пример из статьи. Поставьте np.random.seed(17). Округлите ответ до целых.
<font color='red'>Варианты ответа:</font>
- 344
- 424
- 584
- 654
End of explanation
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV, StratifiedKFold
Explanation: Дерево решений, подбор гиперпараметров
Одной из основных метрик качества модели является площадь под ROC-кривой. Значения ROC-AUC лежат от 0 до 1. Чем ближе значение ROC-AUC к 1, тем качественнее происходит классификация моделью.
Найдите с помощью GridSearchCV гиперпараметры DecisionTreeClassifier, максимизирующие площадь под ROC-кривой.
End of explanation
dt = DecisionTreeClassifier(random_state=17, class_weight='balanced')
Explanation: Используем модуль DecisionTreeClassifier для построения дерева решений. Из-за несбалансированности классов  в целевом признаке добавляем параметр балансировки. Используем также параметр random_state=17 для воспроизводимости результатов.
End of explanation
max_depth_values = [5, 6, 7, 8, 9]
max_features_values = [4, 5, 6, 7]
tree_params = {'max_depth': max_depth_values,
               'max_features': max_features_values}
Explanation: Перебирать будем вот такие значения гиперпараметров:
End of explanation
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=17)
Explanation: Зафиксируем кросс-валидацию: стратифицированная, 5 разбиений с перемешиванием, не забываем про random_state.
End of explanation
dt_grid_search = GridSearchCV(dt, tree_params, n_jobs=-1, scoring ='roc_auc', cv=skf)
dt_grid_search.fit(X, y)
round(float(dt_grid_search.best_score_), 3)
dt_grid_search.best_params_
dt_grid_search.cv_results_["std_test_score"][np.argmax(dt_grid_search.cv_results_["mean_test_score"])]
Explanation: <font color='red'>Задание 3.</font>
Сделайте GridSearch с метрикой ROC AUC по гиперпараметрам из словаря tree_params. Какое максимальное значение ROC AUC получилось (округлите до 2 знаков после разделителя)? Назовем кросс-валидацию устойчивой, если стандартное отклонение метрики качества на кросс-валидации меньше 1%. Получилась ли кросс-валидация устойчивой при оптимальных сочетаниях гиперпараметров (т.е. обеспечивающих максимум среднего значения ROC AUC на кросс-валидации)?
<font color='red'>Варианты ответа:</font>
- 0.82, нет
- 0.84, нет
- 0.82, да
- 0.84, да
End of explanation
from sklearn.base import BaseEstimator
from sklearn.model_selection import cross_val_score
class RandomForestClassifierCustom(BaseEstimator):
    def __init__(self, n_estimators=10, max_depth=10, max_features=10, 
                 random_state=17):
        self.n_estimators = n_estimators
        self.max_depth = max_depth
        self.max_features = max_features
        self.random_state = random_state
        
        self.trees = []
        self.feat_ids_by_tree = []
        
    def fit(self, X, y):
        for i in range(self.n_estimators):
            
            np.random.seed(i + self.random_state)
            
            feat_to_use_ids = np.random.choice(range(X.shape[1]), self.max_features, 
                                              replace=False)
            examples_to_use = list(set(np.random.choice(range(X.shape[0]), X.shape[0],
                                              replace=True)))
            
            self.feat_ids_by_tree.append(feat_to_use_ids)
            
            dt = DecisionTreeClassifier(class_weight='balanced',
                                        max_depth=self.max_depth, 
                                        max_features=self.max_features, 
                                        random_state = self.random_state)
            dt.fit(X[examples_to_use, :][:, feat_to_use_ids], y[examples_to_use])
            self.trees.append(dt)
        return self
    
    def predict_proba(self, X):
        predictions = []
        for i in range(self.n_estimators):
            feat_to_use_ids = self.feat_ids_by_tree[i]
            predictions.append(self.trees[i].predict_proba(X[:,feat_to_use_ids]))
        return np.mean(predictions, axis=0)
rf = RandomForestClassifierCustom(max_depth=7, max_features=6).fit(X.values, y.values)
cv_aucs = cross_val_score(RandomForestClassifierCustom(max_depth=7, max_features=6), 
                          X.values, y.values, scoring="roc_auc", cv=skf)
print("Средняя ROC AUC для собственного случайного леса:", np.mean(cv_aucs))
Explanation: Простая реализация случайного леса
<font color='red'>Задание 4.</font>
Реализуйте свой собственный случайный лес с помощью DecisionTreeClassifier с лучшими параметрами из прошлого задания. В нашем лесу будет 10 деревьев, предсказанные вероятности которых вам нужно усреднить. 
Краткая спецификация: 
 - Используйте основу ниже
 - В методе fit в цикле (i от 0 до n_estimators-1) фиксируйте seed, равный (random_state + i). Почему именно так – неважно, главное чтоб на каждой итерации seed был новый, при этом все значения можно было бы воспроизвести
 - Зафиксировав seed, выберите без замещения max_features признаков, сохраните список выбранных id признаков в self.feat_ids_by_tree
 - Также сделайте bootstrap-выборку (т.е. с замещением) из множества id объектов 
 - Обучите дерево с теми же max_depth, max_features и random_state, что и у RandomForestClassifierCustom на выборке с нужным подмножеством объектов и признаков
 - Метод fit возвращает текущий экземпляр класса RandomForestClassifierCustom, то есть self
 - В методе predict_proba опять нужен цикл по всем деревьям. У тестовой выборки нужно взять те признаки, на которых соответсвующее дерево обучалось, и сделать прогноз вероятностей (predict_proba уже для дерева). Метод должен вернуть усреднение прогнозов по всем деревьям.
Проведите кросс-валидацию. Какое получилось среднее значение ROC AUC на кросс-валидации? Выберите самое близкое значение.
<font color='red'>Варианты ответа:</font>
- 0.823
- 0.833
- 0.843
- 0.853
End of explanation
from sklearn.ensemble import RandomForestClassifier
cv_aucs = cross_val_score(RandomForestClassifier(n_estimators=10, max_depth=7, 
                                               max_features=6,
                                               random_state=17, n_jobs=-1,
                                              class_weight='balanced'), 
                        X.values, y.values, scoring="roc_auc", cv=skf)
print("Средняя ROC AUC для случайного леса Sklearn:", np.mean(cv_aucs))
Explanation: <font color='red'>Задание 5.</font>
Тут сравним нашу собственную реализацию случайного леса с sklearn-овской. Для этого воспользуйтесь RandomForestClassifier(n_jobs=1, random_state=17), укажите все те же значения max_depth и max_features, что и раньше. Какое среднее значение ROC AUC на кросс-валидации мы в итоге получили? Выберите самое близкое значение.
<font color='red'>Варианты ответа:</font>
- 0.823
- 0.833
- 0.843
- 0.853
End of explanation
max_depth_values = range(5, 15)
max_features_values = [4, 5, 6, 7]
forest_params = {'max_depth': max_depth_values,
               'max_features': max_features_values}
rf = RandomForestClassifier(random_state=17, n_jobs=-1, 
                            class_weight='balanced')
rf_grid_search = GridSearchCV(rf, forest_params, n_jobs=-1, 
                              scoring='roc_auc', cv=skf)
rf_grid_search.fit(X.values, y.values)
rf_grid_search.best_score_
rf_grid_search.best_params_
rf_grid_search.cv_results_["std_test_score"][np.argmax(rf_grid_search.cv_results_["mean_test_score"])]
Explanation: Случайный лес sklearn, подбор гиперпараметров
<font color='red'>Задание 6.</font> 
В 3 задании мы находили оптимальные гиперпараметры для одного дерева, но может быть, для ансамбля эти параметры дерева не будут оптимальными. Давайте проверим это с помощью GridSearchCV (RandomForestClassifier(random_state=17)).  Только теперь расширим перебираемые занчения max_depth до 15 включительно, так как в лесу нужны деревья поглубже (а почему именно – вы поняли из статьи). Какими теперь стали лучшие значения гиперпараметров?
<font color='red'>Варианты ответа:</font>
- max_depth=8, max_features=4
- max_depth=9, max_features=5
- max_depth=10, max_features=6
- max_depth=11, max_features=7
End of explanation
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
scaler = StandardScaler()
logit = LogisticRegression(random_state=17, class_weight='balanced')
logit_pipe = Pipeline([('scaler', scaler), ('logit', logit)])
logit_pipe_params = {'logit__C': np.logspace(-8, 8, 17)}
logit_pipe_grid_search = GridSearchCV(logit_pipe, logit_pipe_params, n_jobs=-1, 
                           scoring ='roc_auc', cv=skf)
logit_pipe_grid_search.fit(X.values, y.values)
logit_pipe_grid_search.best_score_
logit_pipe_grid_search.best_params_
logit_pipe_grid_search.cv_results_["std_test_score"][np.argmax(logit_pipe_grid_search.cv_results_["mean_test_score"])]
Explanation: Логистическая регрессия, подбор гиперпараметров
<font color='red'>Задание 7.</font>  Теперь сравним с логистической регрессией (укажем class_weight='balanced' и random_state=17). Сделайте полный перебор по параметру C из широкого диапазона значений np.logspace(-8, 8, 17). 
Только сделаем это корректно и выстроим пайплайн – сначала масштабирование, затем обучение модели. 
Разберитесь с пайплайнами и проведите кросс-валидацию. Какое получилось лучшее значение средней ROC AUC? Выберите самое близкое значение.
<font color='red'>Варианты ответа:</font>
- 0.778
- 0.788
- 0.798
- 0.808
End of explanation
# Загрузим данные
df = pd.read_csv("../../data/movie_reviews_train.csv", nrows=50000)
# Разделим данные на текст и целевой признак
X_text = df["text"]
y_text = df["label"]
# Соотношения классов
df.label.value_counts()
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.pipeline import Pipeline
# будем разбивать на 3 фолда
skf = StratifiedKFold(n_splits=3, shuffle=True, random_state=17)
# в Pipeline будем сразу преобразовать наш текст и обучать логистическую регрессию
classifier = Pipeline([
    ('vectorizer', CountVectorizer(max_features = 100000, ngram_range = (1, 3))),
    ('clf', LogisticRegression(random_state=17))])
Explanation: Логистическая регрессия и случайный лес на разреженных признаках
В случае небольшого числа признаков случайный лес показал себя лучше логистической регрессии. Однако один из главных недостатков деревьев проявляется при работе с разреженным данными, например с текстами. Давайте сравним логистическую регрессию и случайный лес в новой задаче.
Скачайте данные с отзывами к фильмам отсюда.
End of explanation
%%time
parameters = {'clf__C': (0.1, 1, 10, 100)}
grid_search = GridSearchCV(classifier, parameters, n_jobs=-1, scoring ='roc_auc', cv=skf)
grid_search = grid_search.fit(X_text, y_text)
grid_search.best_params_
grid_search.best_score_
Explanation: <font color='red'>Задание 8.</font> Сделайте полный перебор по параметру C из выборки [0.1, 1, 10, 100]. Какое лучшее значение ROC AUC получилось на кросс-валидации? Выберите самое близкое значение.
<font color='red'>Варианты ответа:</font>
- 0.74
- 0.75
- 0.84
- 0.85
End of explanation
classifier = Pipeline([
    ('vectorizer', CountVectorizer(max_features = 100000, ngram_range = (1, 3))),
    ('clf', RandomForestClassifier(random_state=17, n_jobs=-1))])
min_samples_leaf = [1, 2, 3]
max_features = [0.3, 0.5, 0.7]
max_depth = [None]
%%time
parameters = {'clf__max_features': max_features,
              'clf__min_samples_leaf': min_samples_leaf,
              'clf__max_depth': max_depth}
grid_search = GridSearchCV(classifier, parameters, n_jobs=-1, scoring ='roc_auc', cv=skf)
grid_search = grid_search.fit(X_text, y_text)
grid_search.best_params_
grid_search.best_score_
Explanation: <font color='red'>Задание 9.</font> Теперь попробуем сравнить со случайным лесом. Аналогично делаем перебор и получаем максимальное ROC AUC. Выберите самое близкое значение.
<font color='red'>Варианты ответа:</font>
- 0.74
- 0.75
- 0.84
- 0.85
End of explanation
<END_TASK>