<jupyter_start><jupyter_text>Финальный проектФинальный проект на курсе по машинному обучению на курсере В этом ноутбуке предлагается решать задачу предсказания средней цены квадратного метра жилья в некоторых районах Москвы и Московской области. В качестве метрики будем использовать MAPE.MAPE (Mean Absolute Percentage Error) = $\dfrac{1}{\ell}\sum\limits_{i=1}^{\ell} \left| \dfrac{y_i - a(x_i)}{y_i} \right|$  Импорт библиотек и чтение данных<jupyter_code>import pandas as pd
import numpy as np
# !pip uninstall -y seaborn
# !pip install seaborn
import matplotlib.pyplot as plt
import seaborn as sns

sns.set(style='darkgrid', rc={'figure.figsize': (16, 8)})

pd.options.display.max_columns = 100
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')

housebase = pd.read_csv('housebase.csv')<jupyter_output><empty_output><jupyter_text>train.csv и test.csvОписание полей в файлах train.csv и test.csv:**month** - месяц, за который указаны данные в датасете**city_quadkey** - полигон, его координаты можно его узнать из скрипта quadkey.py**apartment_ratio** - доля квартир в этом районе в этом месяце со статусом "апартаменты"**offers_in_sites_ratio** - доля офферов в ЖК в этом районе в этом месяце**studio_ratio** - доля объявлений о продаже студии среди всех объявлений в этом районе в этом месяце**rooms_offered_*_ratio** - доля квартир указанной в * комнатности от всех квартир в продаже в этом районе в этом месяце**unique_active_buildings_count** - количество домов, в которых есть квартиры в продаже в этом районе в этом месяце**unique_active_building_series_count** - количество уникальных серий домов, в которых есть квартиры в продаже в этом районе в этом месяце**total_area_avg** - средний метраж продаваемой квартиры в этом районе в этом месяце**offer_count** - число объявлений о продаже квартир в этом районе в этом месяце**avg_price_sqm** - средняя цена за квадратный метр в этом районе в этом месяце  housebase.csvОписание полей в датасете про базу домов housebase.csv:**city_quadkey** - полигон, его координаты можно его узнать из скрипта quadkey.py**building_id** - id дома**building_series_id** - id серии дома**unified_address** - адрес дома**latitude, longitude** - координаты дома**flats_count** - количество квартир в доме**has_elevator** - наличие лифта в доме**ceiling_height** - высота потолков в доме**build_year** - год постройки дома**expect_demolition** - дом входит в программу реновации и ожидает сноса**art_cnt** - количество организаций в сфере искусства (музеи, галереи и т.п.) в этом районе city_quadkey**beauty_cnt** - количество салонов красоты в этом районе city_quadkey**cafe_restaurant_eating_out_cnt** - количество точек общественного питания в этом районе city_quadkey**chain_cnt** - количество сетевых организаций в этом районе city_quadkey**entertainment_cnt** - количество организаций в сфере развлечений в этом районе city_quadkey**groceries_and_everyday_items_cnt** - количество продуктовых магазинов в этом районе city_quadkey**healthcare_cnt** - количество организаций в сфере здравоохранения в этом районе city_quadkey**laundry_and_repair_services_cnt** - количество прачечных и ремонтных мастерских в этом районе city_quadkey**shopping_cnt** - количество магазинов в этом районе city_quadkey**sport_cnt** - количество спортивных организаций в этом районе city_quadkey<jupyter_code>train.head()
test.head()
housebase.head()
f'Количество объектов в train {len(train)}, в test {len(test)}, количество записей в housebase {len(housebase)}.'<jupyter_output><empty_output><jupyter_text>EDA Посмотрим есть ли пропуски в данных:<jupyter_code>train.isna().sum()
test.isna().sum()
housebase.isna().sum()<jupyter_output><empty_output><jupyter_text>Посмотрим на типы колонок:<jupyter_code>train.dtypes
test.dtypes
housebase.dtypes<jupyter_output><empty_output><jupyter_text>Разделим признаки на несколько групп:**-1:** `avg_price_sqm` (средняя цена, наша целевая переменная)**0:** `city_quadkey`, `month` (район и месяц, для чего мы предсказываем)**1:** `apartment_ratio`, `offers_in_sites_ratio`, `studio_ratio`, `rooms_offered_1_ratio`, `rooms_offered_2_ratio`, `rooms_offered_3_ratio`, `rooms_offered_4_more_ratio`, `total_area_avg` (описание объвлений на районе в этом месяце тип 1, из train)**2:** `unique_active_buildings_count`, `unique_active_building_series_count`, `offer_count` (описание объявлений на районе в этом месяце тип 2, из train)**3:** `beauty_cnt`, `shopping_cnt`, `cafe_restaurant_eating_out_cnt`, `entertainment_cnt`, `sport_cnt`, `chain_cnt`, `groceries_and_everyday_items_cnt`, `art_cnt`, `healthcare_cnt`, `laundry_and_repair_services_cnt` (описание района, из housebase)**4:** `building_id`, `building_series_id`, `building_type`, `unified_address`, `flats_count`, `expect_demolition`, `ceiling_height`, `has_elevator`, `build_year` + `latitude`, `longitude` (описание дома, из housebase)  Группа -1Постройте график распределения целевой переменной (`avg_price_sqm`). Здесь и далее лучше использовать функцию `distplot` из библиотеки `seaborn`.<jupyter_code>### YOUR CODE HERE
# sns.distplot(train['avg_price_sqm'])<jupyter_output><empty_output><jupyter_text>Группа 0- Выведите все уникальные значения месяцев в датасете, пересекаются ли они в обучающей и тестовой выборке?- Постройте график распределения количества объявлений в зависимости от месяца для обучающей выборки.- Посчитайте сколько уникальных районов представлено в датасете, в обучающей и тестовой выборке. Есть ли в тестовой выборке новые районы?<jupyter_code>### YOUR CODE HERE
# city_quadkey, mont
# test['month'].unique()
# train['month'].unique()
# train.plot(x='month',y='offer_count')
# print(train['city_quadkey'].nunique())
# print(test['city_quadkey'].nunique())
# np.in1d(train['city_quadkey'].nunique(),test['city_quadkey'].nunique())<jupyter_output><empty_output><jupyter_text>Запишите в переменные (`train_nunique_month` и `train_nunique_quadkey`) количество уникальных значений в обучающей выборке для признаков `month` и `city_quadkey`.<jupyter_code># your code here
train_nunique_month = train['month'].nunique()
train_nunique_quadkey = train['city_quadkey'].nunique()
### GRADING, RUN TO TEST

train_nunique_month
train_nunique_quadkey

print('... TESTING ...')<jupyter_output>... TESTING ...<jupyter_text>Группа 1Для каждого признака постройте график распределения. Есть ли какие-то неинформативные признаки, которые можно сразу выбросить?<jupyter_code>### YOUR CODE HERE
# columns = ['apartment_ratio', 'offers_in_sites_ratio', 'studio_ratio', 'rooms_offered_1_ratio',
#       'rooms_offered_2_ratio', 'rooms_offered_3_ratio', 'rooms_offered_4_more_ratio', 'total_area_avg']
# for column in columns:
#     sns.histplot(train[column])
#     plt.show()<jupyter_output><empty_output><jupyter_text>Запишите в переменную `unwanted_cols` список с признаками, которые можно будет удалить.<jupyter_code># your code here
unwanted_cols = ['offers_in_sites_ratio']
### GRADING

unwanted_cols

print('... TESTING ...')<jupyter_output>... TESTING ...<jupyter_text>Группа 2Для каждого признака постройте график распределения. Есть ли какие-то неинформативные признаки, которые можно сразу выбросить?<jupyter_code>### YOUR CODE HERE
# columns = ['unique_active_buildings_count', 'unique_active_building_series_count', 'offer_count']
# for column in columns:
#     sns.histplot(train[column])
#     plt.show()<jupyter_output><empty_output><jupyter_text>Группа 3Для каждого признака постройте график распределения. Так как в этой группе признаки содержат информацию про количество тех или иных объектов на районе, физический смысл отсутствия информации - на районе нет таких объектов. В дальнейшем мы воспользуемся этой идеей для заполнения пропусков.<jupyter_code>### YOUR CODE HERE
# columns = ['beauty_cnt', 'shopping_cnt', 'cafe_restaurant_eating_out_cnt',
#            'entertainment_cnt', 'sport_cnt', 'chain_cnt',
#            'groceries_and_everyday_items_cnt', 'art_cnt',
#            'healthcare_cnt', 'laundry_and_repair_services_cnt']
# for column in columns:
#     sns.histplot(housebase[column])
#     plt.show()<jupyter_output><empty_output><jupyter_text>Группа 4- Постройте графики распределения для `building_series_id` и `building_type`.- Постройте графики распределения для `flats_count`, `ceiling_height` и `build_year`.- Постройте графики распределения для `has_elevator` и `expect_demolition`.<jupyter_code>### YOUR CODE HERE
# columns = ['building_id', 'building_series_id', 'building_type',
#            'unified_address', 'flats_count', 'expect_demolition',
#            'ceiling_height', 'has_elevator', 'build_year', 'latitude',
#            'longitude']
# sns.displot(housebase['building_series_id'])
# sns.catplot(data=housebase,x='building_type',kind='count',height=6, aspect=2)
# sns.displot(housebase['flats_count'])
# sns.displot(housebase['ceiling_height'])
# sns.displot(housebase['build_year'])
# sns.displot(housebase['has_elevator'])
# sns.displot(housebase['expect_demolition'])
# plt.show()<jupyter_output><empty_output><jupyter_text>Деление на категориальные и вещественныеРазделите признаки на категориальные и вещественные, запишите их в переменные `cat_features` и `real_features`. Какие признаки вы не будете использовать?<jupyter_code># your code here
cat_features = ['building_type',
                'expect_demolition','has_elevator','is_in_Moscow']

real_features = ['apartment_ratio', 'studio_ratio',
                'rooms_offered_1_ratio', 'rooms_offered_2_ratio', 
                'rooms_offered_3_ratio', 'rooms_offered_4_more_ratio', 'total_area_avg',
                'unique_active_buildings_count',
                'unique_active_building_series_count', 'offer_count',
                'beauty_cnt', 'shopping_cnt', 'cafe_restaurant_eating_out_cnt',
                'entertainment_cnt', 'sport_cnt',
                'chain_cnt', 'groceries_and_everyday_items_cnt',
                'art_cnt', 'healthcare_cnt',
                'laundry_and_repair_services_cnt','build_year',
                'ceiling_height',
                'flats_count','center_dist']<jupyter_output><empty_output><jupyter_text>Генерация признаковСгенерируем несколько новых признаков, разделим их на несколько групп:- Отсутствие информации про район/дом- Находится ли дом в Москве- Расстояние до центра города- Находится ли дом внутри колец- Время года  Отсутствие информации про район/домВспомните признаки из группы 3 и группы 4, для каждого признака сделайте бинарный признак, который будет обозначать, что информация отсутствует, назовите их `col_name + '_isna'`. Создавать новые признаки стоит в `housebase`. Куда стоит отнести новые признаки? Заполните пропуски: для категориальных новым значением `UNKNOWN`, для вещественных нулём.<jupyter_code>columns_3 = ['beauty_cnt', 'shopping_cnt', 'cafe_restaurant_eating_out_cnt',
           'entertainment_cnt', 'sport_cnt', 'chain_cnt',
           'groceries_and_everyday_items_cnt', 'art_cnt',
           'healthcare_cnt', 'laundry_and_repair_services_cnt']
columns_4 = ['building_id', 'building_series_id', 'building_type',
           'unified_address', 'flats_count', 'expect_demolition',
           'ceiling_height', 'has_elevator', 'build_year', 'latitude',
           'longitude']
for column in columns_3 + columns_4:
    housebase[column+'_isna']=housebase[column].isna().astype(int)
values = {}
for feature in cat_features:
    values[feature]='UNKNOWN'
for feature in real_features:
    values[feature]=0
values
housebase.fillna(values,inplace=True)
test.fillna(values,inplace=True)
train.fillna(values,inplace=True)
### GRADING

for col in ['art_cnt', 'beauty_cnt', 'cafe_restaurant_eating_out_cnt', 'chain_cnt', 'entertainment_cnt', 'groceries_and_everyday_items_cnt', 'healthcare_cnt', 'laundry_and_repair_services_cnt', 'shopping_cnt', 'sport_cnt']:
    assert col + '_isna' in housebase.columns

print('... TESTING ...')<jupyter_output>... TESTING ...<jupyter_text>Находится ли дом в МосквеСделайте новый бинарный признак, который будет обозначать находится ли дом в Москве, назовите его `is_in_Moscow`. Для этого стоит анализировать уже имеющийся признак `unified_address`.<jupyter_code># your code here
housebase['is_in_Moscow'] = housebase['unified_address'].apply(lambda x: x.split(',')).apply(lambda x: 1 if ' Москва' in x or 'Москва' in x else 0)
### GRADING

assert 'is_in_Moscow' in housebase.columns

print('... TESTING ...')<jupyter_output>... TESTING ...<jupyter_text>Расстояние до центра городаСоздайте новый вещественный признак, который будет равен расстоянию от дома до Кремля, назовите его `center_dist`. Координаты Кремля: (55.7528, 37.6178).<jupyter_code>!pip install haversine
from haversine import haversine
# your code hereLatitude and longitude
msk = [55.7528, 37.6178]
housebase['center_dist'] = housebase[['latitude','longitude']].apply(lambda x:haversine(x,msk,unit='km'),axis=1)
housebase['center_dist']
### GRADING

assert 'center_dist' in housebase.columns

print('... TESTING ...')<jupyter_output>... TESTING ...<jupyter_text>Находится ли дом внутри колецСоздайте три новых бинарных признака - находится ли дом внутри Бульварного, Садового или Третьего Транспортного Кольца. Для этого стоит сравнить уже полученное расстояние до Кремля с 1.5, 3 и 6 километрами, соответственно.<jupyter_code>### YOUR CODE HERE
housebase['inside_bulvar'] = housebase['center_dist'].apply(lambda x: x<1.5).astype(int)
housebase['inside_cad'] = housebase['center_dist'].apply(lambda x: x<3).astype(int)
housebase['inside_ttk'] = housebase['center_dist'].apply(lambda x: x<6).astype(int)<jupyter_output><empty_output><jupyter_text>Время годаДобавьте новый категориальный признак, который будет равен времени года.<jupyter_code>### YOUR CODE HERE
seasons = {
    '12' : 'winter',
    '01' : 'winter',
    '02' : 'winter',
    '03' : 'spring',
    '04' : 'spring',
    '05' : 'spring',
    '06' : 'summer',
    '07' : 'summer',
    '08' : 'summer',
    '09' : 'fall',
    '10' : 'fall',
    '11' : 'fall',
}
train['season'] = train['month'].apply(lambda x: seasons[x.split('-')[1]])
test['season'] = test['month'].apply(lambda x: seasons[x.split('-')[1]])<jupyter_output><empty_output><jupyter_text>Подготовка данных для обучения Теперь нам надо объединить обе таблички, чтобы добавить информацию про дома для каждого района. Перед тем как объединять таблички нам нужно будет создать для каждого района образ "среднего" дома по району.Для этого мы для каждого категориального признака возьмем моду, для каждого вещественного медиану.Произведите вышеописанные манипуляции с housebase.<jupyter_code>real_housebase = np.intersect1d(real_features, housebase.columns)
cat_housebase = np.intersect1d(cat_features, housebase.columns)
def combine_housebase(quadkeys_):
    groups = housebase.loc[housebase['city_quadkey'].isin(quadkeys_)].groupby('city_quadkey')

    aggs = {}
    for feature in cat_housebase:
        aggs[feature]=lambda x: x.mode()[0]
    for feature in real_housebase:
        aggs[feature]=lambda x: x.median()
    
    return groups.agg(aggs).reset_index(drop=False)
from multiprocessing import Pool, cpu_count
from itertools import chain

try:
    with Pool(cpu_count()) as pool:
        results = pool.map(combine_housebase, np.array_split(housebase['city_quadkey'].unique(), cpu_count()))
except KeyboardInterrupt:
    pass

housebase = pd.concat(results)
### GRADING

assert len(housebase) == housebase['city_quadkey'].nunique()

print('... TESTING ...')

train_data = train.join(housebase.set_index('city_quadkey'), on='city_quadkey', how='left')
test_data = test.join(housebase.set_index('city_quadkey'), on='city_quadkey', how='left')<jupyter_output><empty_output><jupyter_text>Добавим еще один вещественный признак - возраст дома.<jupyter_code>train_data['build_year'] = train_data['month'].str.split('-').map(lambda x: x[0]).astype(int) - train_data['build_year']
test_data['build_year'] = test_data['month'].str.split('-').map(lambda x: x[0]).astype(int) - test_data['build_year']
train_data.columns
train_data.drop(['month', 'city_quadkey','offers_in_sites_ratio'],inplace=True,axis=1)
test_data.drop(['month', 'city_quadkey','offers_in_sites_ratio'],inplace=True,axis=1)<jupyter_output><empty_output><jupyter_text>Разделим данные на признаки и целевую переменную.<jupyter_code>x_train, y_train = train_data.drop(['avg_price_sqm'], axis=1), train_data[['avg_price_sqm']]
x_test, y_test = test_data.drop(['avg_price_sqm'], axis=1), test_data[['avg_price_sqm']]<jupyter_output><empty_output><jupyter_text>В качестве бейзлайна возьмем значение нашей метрики (MAPE) для медианы обучающей выборки:<jupyter_code>def mape(y_true, y_pred):
    y_true, y_pred = np.array(y_true), np.array(y_pred)
    return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
y_dummy = y_train.median()

f'MAPE baseline: TRAIN {mape(y_train, y_dummy):.2f}, TEST {mape(y_test, y_dummy):.2f}'<jupyter_output><empty_output><jupyter_text>Перебор гиперпараметров Заполните пропуски в функции для поиска лучших гиперпараметров для модели.<jupyter_code>from sklearn.compose import ColumnTransformer
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.metrics import make_scorer

scorer = make_scorer(mape, greater_is_better=False)


def hyperopt(estimator, params, cat=None, real=None):
    if cat is None:
        cat = cat_features
    
    if real is None:
        real = real_features

    column_transformer = ColumnTransformer(
        transformers=[
            ('first',OneHotEncoder(handle_unknown = 'ignore'),cat),
            ('second',StandardScaler(),real)
            
        ]
    )
    
    pipeline = Pipeline(
        steps=[
            ('preprocessor' ,column_transformer),
            ('estimator' , estimator)
            
        ]
    )
    grid = GridSearchCV(
        estimator=pipeline,
        param_grid=params,
        scoring=scorer,
        cv=3,
        verbose=10,
        n_jobs=1
    )
    
    grid.fit(x_train, y_train)
    
    # write best params to `best_params`
    # your code here
    best_params = grid.best_params_
    
    score_train = mape(y_train, grid.predict(x_train))
    score_test = mape(y_test, grid.predict(x_test))
    
    return score_train, score_test, best_params
### GRADING

print('... TESTING ...')<jupyter_output>... TESTING ...<jupyter_text>Сравнение моделей Подберите для каждой из следующих моделей лучшие гиперпараметры с помощью функции, которую вы написали выше.- Линейная модель с l2 регуляризацией- Решающее дерево- Случайный лес- Градиентный бустинг- Метод ближайших соседей  Линейная модель с l2 регуляризациейПопробуйте исследовать влияние `alpha` и `max_iter` на результат.<jupyter_code>from sklearn.linear_model import Ridge
params = {
    'estimator__max_iter': [300,400,500,600,700],
    'estimator__alpha':[4,3,2,1.0,0.8,0.5,0.3,0.2,0.1,0.05,0.02,0.01]
    
}
hyperopt(Ridge(),params)

### YOUR CODE HERE<jupyter_output>Fitting 3 folds for each of 60 candidates, totalling 180 fits
[CV 1/3; 1/60] START estimator__alpha=4, estimator__max_iter=300................
[CV 1/3; 1/60] END estimator__alpha=4, estimator__max_iter=300;, score=-26.892 total time=   0.2s
[CV 2/3; 1/60] START estimator__alpha=4, estimator__max_iter=300................
[CV 2/3; 1/60] END estimator__alpha=4, estimator__max_iter=300;, score=-27.359 total time=   0.2s
[CV 3/3; 1/60] START estimator__alpha=4, estimator__max_iter=300................
[CV 3/3; 1/60] END estimator__alpha=4, estimator__max_iter=300;, score=-26.289 total time=   0.1s
[CV 1/3; 2/60] START estimator__alpha=4, estimator__max_iter=400................
[CV 1/3; 2/60] END estimator__alpha=4, estimator__max_iter=400;, score=-26.892 total time=   0.2s
[CV 2/3; 2/60] START estimator__alpha=4, estimator__max_iter=400................
[CV 2/3; 2/60] END estimator__alpha=4, estimator__max_iter=400;, score=-27.359 total time=   0.2s
[CV 3/3; 2/60] START estimator__alpha=4, es[...]<jupyter_text>Запишите в переменную `best_params_ridge` ваши лучшие гиперпараметры для `Ridge`.<jupyter_code># your code here
best_params_ridge = {'alpha':4,
                    'max_iter' :300}
### GRADING

best_params_ridge

print('... TESTING ...')<jupyter_output>... TESTING ...<jupyter_text>Решающее деревоПопробуйте исследовать влияние `max_depth`, `min_samples_split` и `max_features` на результат.<jupyter_code>from sklearn.tree import DecisionTreeRegressor
params = {
    'estimator__max_depth': list(range(1,6)),
    'estimator__min_samples_split':[2,5,10],
    'estimator__max_features':[2,5,10]
    
}
hyperopt(DecisionTreeRegressor(),params)

### YOUR CODE HERE<jupyter_output>Fitting 3 folds for each of 45 candidates, totalling 135 fits
[CV 1/3; 1/45] START estimator__max_depth=1, estimator__max_features=2, estimator__min_samples_split=2<jupyter_text>Запишите в переменную `best_params_dt` ваши лучшие гиперпараметры для `DecisionTreeRegressor`.<jupyter_code># your code here
best_params_dt = {
        'max_depth': ,
        'min_samples_split': ,
        'max_features':
}
### GRADING

best_params_dt

print('... TESTING ...')<jupyter_output><empty_output><jupyter_text>Случайный лесПопробуйте исследовать влияние `n_estimators`, `max_depth`, `min_samples_split` и `max_features` на результат.<jupyter_code>from sklearn.ensemble import RandomForestRegressor
params = {
    'estimator__n_estimators': list(range(1,100,20)),
    'estimator__max_depth': list(range(1,15)),
    'estimator__min_samples_split':list(range(1,20)),
    'estimator__max_features':list(range(1,20))
    
}
hyperopt(RandomForestRegressor(),params)

### YOUR CODE HERE<jupyter_output><empty_output><jupyter_text>Запишите в переменную `best_params_rf` ваши лучшие гиперпараметры для `RandomForestRegressor`.<jupyter_code># your code here
best_params_rf = {
        'estimator__n_estimators': ,
        'max_depth': ,
        'min_samples_split': ,
        'max_features':
}
### GRADING

best_params_rf

print('... TESTING ...')<jupyter_output><empty_output><jupyter_text>Градиентный бустингПопробуйте исследовать влияние `n_estimators`, `learning_rate`, `subsample` и `min_samples_split` на результат.<jupyter_code>from sklearn.ensemble import GradientBoostingRegressor

params = {
    'estimator__n_estimators': list(range(1,100,20)),
    'estimator__learning_rate': [0.1,0.2,0.5,1],
    'estimator__subsample':[0.5,1],
    'estimator__min_samples_split':list(range(1,7))
    
}
hyperopt(GradientBoostingRegressor(),params)
### YOUR CODE HERE<jupyter_output><empty_output><jupyter_text>Запишите в переменную `best_params_gb` ваши лучшие гиперпараметры для `GradientBoostingRegressor`.<jupyter_code># your code here
best_params_gb = {
    'n_estimators': ,
    'learning_rate': ,
    'subsample': ,
    'min_samples_split':
    
}
### GRADING

best_params_gb

print('... TESTING ...')<jupyter_output><empty_output><jupyter_text>Метод ближайших соседейПопробуйте исследовать влияние `n_neighbors`, `algorithm` и `leaf_size` на результат.<jupyter_code>from sklearn.neighbors import KNeighborsRegressor
params = {
    'estimator__n_neighbors': [1,20,30,50,100],
    'estimator__algorithm': ['ball_tree', 'kd_tree', 'brute'],
    'estimator__leaf_size':[1,10,20,30]
    
}
hyperopt(KNeighborsRegressor(),params)

### YOUR CODE HERE<jupyter_output><empty_output><jupyter_text>Запишите в переменную `best_params_knb` ваши лучшие гиперпараметры для `KNeighborsRegressor`.<jupyter_code># your code here
best_params_knb = {
    'n_neighbors': ,
    'algorithm': ,
    'leaf_size':
    
}
### GRADING

best_params_knb

print('... TESTING ...')<jupyter_output><empty_output><jupyter_text>Сравнение моделейСравните модели с лучшими гиперпараметрами между собой по- Значению метрики на обучающей выборке- Значению метрики на тестовой выборке- Времени на обучение на обучающей выборке<jupyter_code>### YOUR CODE HERE<jupyter_output><empty_output>
StarcoderdataJupyterStructuredCleanDedup