<jupyter_start><jupyter_text>Специализация "Машинное обучение и анализ данных"Автор материала: программист-исследователь Mail.Ru Group, старший преподаватель <NAME> [<NAME>](https://yorko.github.io/)    Capstone проект №1  Идентификация пользователей по посещенным веб-страницам Неделя 3. Визуальный анализ данных и построение признаковНа 3 неделе мы займемся визуальным анализом данных и построением признаков. Сначала мы вместе построим и проанализируем несколько признаков, потом Вы сможете сами придумать и описать различные признаки. **План 3 недели:** - Часть 1. Построение признаков - Часть 2. Визуальный анализ данных - Часть 3. Дальнейшее построение признаков - Часть 4. Проверка построенных признаков**В этой части проекта Вам могут быть полезны видеозаписи следующих лекций курса "Поиск структуры в данных":**   - [Задача визуализации](https://www.coursera.org/learn/unsupervised-learning/lecture/hlvlT/zadacha-vizualizatsii)   - [Визуализация данных в sklearn](https://www.coursera.org/learn/unsupervised-learning/lecture/ityMo/vizualizatsiia-dannykh-v-sklearn)   **Также в задании будет использоваться библиотека Seaborn (ее можно дополнительно установить командой *pip install seaborn*), будет полезно обращаться к документациям [Matplotlib](http://matplotlib.org/users/) и [Seaborn](http://seaborn.pydata.org/), а также к примерам визуализации, описанным на StackOverflow.**  Задание1. Заполните код в этой тетрадке 2. Если вы проходите специализацию Яндеса и МФТИ, пошлите тетрадку в соответствующем Peer Review.  Если вы проходите курс ODS, выберите ответы в [веб-форме](https://docs.google.com/forms/d/1EbjK7-hF-Gepi6RH-K5I2XeiYGRoY0LNDx03QmLu9Xo).   Часть 1. Построение признаков<jupyter_code>from __future__ import division, print_function
# отключим всякие предупреждения Anaconda
import warnings
warnings.filterwarnings('ignore')
from glob import glob
from glob import iglob
import os
from tqdm import tqdm_notebook as tqdm
import numpy as np
import pandas as pd
pd.set_option('display.max.columns', 25)
import pickle
#pip install seaborn
import seaborn as sns
%matplotlib inline
from matplotlib import pyplot as plt
import re
# Поменяйте на свой путь к данным
PATH_TO_DATA = 'identification'<jupyter_output><empty_output><jupyter_text>**Создайте на основе функций *prepare_train_set* и *prepare_sparse_train_set_window*  новую – *prepare_train_set_with_fe*, (от "feature engineering"), создайте следующие признаки:**- `session_timespan` – продолжительность сессии (разница между максимальным и минимальным временем посещения сайтов в сессии, в секундах)- `unique_sites` – число уникальных сайтов в сессии - `start_hour` – час начала сессии (то есть час в записи минимального timestamp среди десяти)- `day_of_week` – день недели (то есть день недели в записи минимального timestamp среди десяти)Функция должна возвращать новый DataFrame (как возвращала функция *prepare_train_set*), только признаков должно быть на 4 больше. Порядок, в котором добавляются признаки: *site1*, ... *site10*, *session_timespan*, *unique_sites*, *start_hour*, *day_of_week* и *user_id* (это видно и чуть ниже по тому, как функция вызывается).<jupyter_code>from collections import namedtuple
from datetime import datetime
Visit = namedtuple("Visit", ['user_id', 'timestamp','site','site_id'])

user_id_pattern = re.compile("(\d{4}).csv")
def extract_user_id(filename):
    return int(user_id_pattern.search(filename).group(1))

# generator of Visit tupples
# Example source: 2013-11-15 09:28:17,vk.com\n
def visit_generator(path_to_csv_files, site_freq_path):
    files = iglob(os.path.join(path_to_csv_files, '*.csv'))
    
    # site string -> (id, frequency)
    with open(site_freq_path, "rb") as f:
        dict = pickle.load(f) 
    
    for file in tqdm(files, desc = path_to_csv_files):
        user_id = extract_user_id(file)
        with open(file, 'r') as f:
            next(f) # skip header line            
            for line in f:
                split = line.strip().split(',')
                timestamp = datetime.strptime(split[0], '%Y-%m-%d %H:%M:%S')
                site = split[1]                                   
                site_id = dict[site][0]
                yield Visit(user_id, timestamp, site, site_id) 
visit_test = next(visit_generator(os.path.join(PATH_TO_DATA,'3users'), os.path.join(PATH_TO_DATA, 'site_freq_3users.pkl')))
visit_test
# accumulates visits of single user
# to form a session
class VisitAccumulator:
    def __init__(self, session_length, window_size):
        self.session_length = session_length
        self.window_size = window_size
        self.reset()
        
    def reset(self):
        self.user_id = -1
        self.visits = []

    def accumulate(self, visit: Visit):
        if (len(self.visits) == self.session_length):
            raise ValueError("accumulator is already full")
        
        user_id = visit.user_id
        if (self.user_id == -1):
            self.user_id = user_id
        elif (self.user_id != user_id):
            raise ValueError("user ids must be the same across visits")
        self.visits.append(visit)
        
    def isFull(self):
        return len(self.visits) == self.session_length
    
    def isEmpty(self):
        return len(self.visits) == 0
        
    def getSession(self):
        if (self.isEmpty()):
            raise ValueError("accumulator is exhausted")
        session = Session(self.session_length, self.visits)
        self.visits = self.visits[self.window_size:]
        return session
    
    def exhaust(self):
        while(not self.isEmpty()):
            yield self.getSession()
        self.reset()

#session_timespan – продолжительность сессии (разница между максимальным и минимальным временем посещения сайтов в сессии, в секундах)
#unique_sites – число уникальных сайтов в сессии
#start_hour – час начала сессии (то есть час в записи минимального timestamp среди десяти)
#day_of_week – день недели (то есть день недели в записи минимального timestamp среди десяти)
class Session:
    def __init__(self, session_length, visits):
        assert len(visits) > 0
        
        self.session_length = session_length
        self.visits = visits
        
    def asData(self):
        visits = self.visits
        site_ids = list(map(lambda visit: visit.site_id, visits))
        time_diffs = list(map(lambda x: (x[1].timestamp - x[0].timestamp).total_seconds(),
                              zip(visits, visits[1:])))
        
        start = visits[0].timestamp
        end = visits[-1].timestamp
        timespan = (end - start).total_seconds()
        
        unique_sites = len(set(site_ids))
        start_hour = start.hour
        day_of_week = start.weekday()
        user_id = visits[0].user_id
        
        # site1..siteN,time1..timeN-1,timespan,unique_sites,start_hour,weekday,user
        result = np.zeros(self.session_length + (self.session_length - 1) + 1 + 4, dtype=int)
        np.put(result, range(len(site_ids)), site_ids)
        np.put(result, range(self.session_length, self.session_length + len(time_diffs)), time_diffs)
        
        result[-5] = timespan
        result[-4] = unique_sites
        result[-3] = start_hour
        result[-2] = day_of_week
        result[-1] = user_id
        return result
test_generator = visit_generator(os.path.join(PATH_TO_DATA,'3users'), os.path.join(PATH_TO_DATA, 'site_freq_3users.pkl'))
test_accumulator = VisitAccumulator(10, 3)
for i in range(10):
    test_accumulator.accumulate(next(test_generator))
session = test_accumulator.getSession()
session.asData()
#user1: 1 2 2 3 2 4 5 6 7 8 1 4 4 4 
#user2: 1 2 9 9 2 ...
def session_generator(path_to_csv_files, site_freq_path, 
                                    session_length=10, window_size=10):
    assert window_size <= session_length
    
    accumulator = VisitAccumulator(session_length, window_size)
    
    prev_user_id = -1
    for visit in visit_generator(path_to_csv_files, site_freq_path):
        user_id = visit.user_id
        
        # if user is new
        if (prev_user_id != -1 and user_id != prev_user_id):
            # exhaust existing accumulator first
            yield from accumulator.exhaust()      
        
        # accumulate visits, if full yield session
        accumulator.accumulate(visit)
        if (accumulator.isFull()):
            yield accumulator.getSession()
        prev_user_id = user_id
        
    # exhaust if something was left
    yield from accumulator.exhaust()
                               
def prepare_train_set_with_fe(path_to_csv_files, site_freq_path, feature_names,
                                    session_length=10, window_size=10):
    assert window_size <= session_length
    sessions = (session.asData() 
                for session in session_generator(path_to_csv_files, site_freq_path, session_length, window_size))
    return pd.DataFrame(sessions, columns=feature_names)<jupyter_output><empty_output><jupyter_text>**Проверим функцию на игрушечном примере.**<jupyter_code>feature_names = ['site' + str(i) for i in range(1,11)] + \
                ['time_diff' + str(j) for j in range(1,10)] + \
                ['session_timespan', '#unique_sites', 'start_hour', 
                 'day_of_week', 'target']
train_data_toy  = prepare_train_set_with_fe(os.path.join(PATH_TO_DATA, 
                                                         '3users'), 
                  site_freq_path=os.path.join(PATH_TO_DATA, 
                                              'site_freq_3users.pkl'),
                  feature_names=feature_names, session_length=10)
train_data_toy #note: site_ids are different but sites are the same as in original notebook, so it's perfectly fine<jupyter_output><empty_output><jupyter_text>**Примените функцию *prepare_train_set_with_fe* к данным по 10 пользователям, укажите *session_length*=10.**<jupyter_code>%%time
train_data_10users = prepare_train_set_with_fe(os.path.join(PATH_TO_DATA, '10users'), 
                  site_freq_path=os.path.join(PATH_TO_DATA, 'site_freq_10users.pkl'),
                  feature_names=feature_names, session_length=10)
train_data_10users.head()<jupyter_output><empty_output><jupyter_text>**Примените функцию *prepare_train_set_with_fe* к данным по 150 пользователям, укажите *session_length*=10.**<jupyter_code>%%time
train_data_150users = prepare_train_set_with_fe(os.path.join(PATH_TO_DATA, '150users'), 
                  site_freq_path=os.path.join(PATH_TO_DATA, 'site_freq_150users.pkl'),
                  feature_names=feature_names, session_length=10)<jupyter_output><empty_output><jupyter_text>**Сохраните в pickle-файлы признаки *session_timespan*, *unique_sites*, *start_hour* и *day_of_week* для 10 и 150 пользователей.**<jupyter_code>new_features_10users = train_data_10users[['session_timespan', '#unique_sites', 'start_hour', 'day_of_week']]
new_features_150users = train_data_150users[['session_timespan', '#unique_sites', 'start_hour', 'day_of_week']]
with open(os.path.join(PATH_TO_DATA, 
                       'new_features_10users.pkl'), 'wb') as new_features_10users_pkl:
    pickle.dump(new_features_10users, new_features_10users_pkl)
with open(os.path.join(PATH_TO_DATA, 
                       'new_features_150users.pkl'), 'wb') as new_features_150users_pkl:
    pickle.dump(new_features_150users, new_features_150users_pkl)<jupyter_output><empty_output><jupyter_text>**Вопрос 1.  Выведите медианную продолжительность сессии (*session_timespan*) для сессий 10 пользователей.**<jupyter_code>train_data_10users.session_timespan.median()<jupyter_output><empty_output><jupyter_text>**Вопрос 2.  Выведите медианный день недели, в который началась сессия, для сессий 10 пользователей.**<jupyter_code>train_data_10users.day_of_week.median()<jupyter_output><empty_output><jupyter_text>**Вопрос 3. Выведите медианный час начала сессии для сессий 150 пользователей.**<jupyter_code>train_data_150users.start_hour.median()<jupyter_output><empty_output><jupyter_text>**Вопрос 4. Выведите медианное значение числа уникальных сайтов в сессиях 150 пользователей.**<jupyter_code>train_data_150users['#unique_sites'].median()<jupyter_output><empty_output><jupyter_text>Часть 2. Визуальный анализ данных **Забавы ради, потехи для дадим пользователям имена и ассоциируем с ними цвета.**<jupyter_code>id_name_dict = {128: 'Mary-Kate', 39: 'Ashley', 207: 'Lindsey', 127: 'Naomi', 237: 'Avril',
               33: 'Bob', 50: 'Bill', 31: 'John', 100: 'Dick', 241: 'Ed'}
train_data_10users['target'] = train_data_10users['target'].map(id_name_dict)
color_dic = {'Mary-Kate': 'pink', 'Ashley': 'darkviolet', 'Lindsey':'blueviolet', 
             'Naomi': 'hotpink', 'Avril': 'orchid', 
             'Bob': 'firebrick', 'Bill': 'gold', 'John': 'forestgreen', 
             'Dick': 'slategrey', 'Ed':'brown'}<jupyter_output><empty_output><jupyter_text>**1. Постройте гистограмму распределения длины сессии в секундах (*session_timespan*). Ограничьте по *x* значением 200 (иначе слишком тяжелый хвост). Сделайте гистограмму цвета *darkviolet*, подпишите оси по-русски.**<jupyter_code>series = train_data_10users['session_timespan']
axes = series[series < 200].hist(color='darkviolet')
axes.set_xlabel("Продолжительность сессии")
axes.set_ylabel("Количество в корзине");<jupyter_output><empty_output><jupyter_text>**2. Постройте гистограмму распределения числа уникальных сайтов в сессии (*unique_sites*). Сделайте гистограмму цвета *aqua*, подпишите оси по-русски.**<jupyter_code>axes = train_data_10users['#unique_sites'].hist(color='aqua')
axes.set_xlabel("Уникальных сайтов")
axes.set_ylabel("Количество в корзине");<jupyter_output><empty_output><jupyter_text>**3. Постройте гистограммы распределения числа уникальных сайтов в сессии (*unique_sites*) для каждого из 10 пользователей по отдельности. Используйте *subplots*, чтоб разместить все 10 картинок на одной большой. Пометьте легендой каждую картинку, на легенде должно быть написано имя пользователя. Для каждого пользователя раскрасьте гистограмму его/ее цветом (*color_dic*). Подпишите оси по-русски в каждой из 10 гистограмм.**<jupyter_code>fig, axes = plt.subplots(nrows=3, ncols=4, figsize=(16, 10))

# как вариант, можно и по-другому
for idx, (user, sub_df) in  enumerate(pd.groupby(train_data_10users, 'target')): 
    ax = axes[idx // 4, idx % 4]
    sub_df[['#unique_sites']].hist(ax=ax, label=user, color=color_dic.get(user), bins=range(12))
    ax.set_xlabel("Уникальных сайтов")
    ax.set_ylabel("Колиство в корзине")
    ax.set_xticks(np.arange(12))
    ax.legend()<jupyter_output><empty_output><jupyter_text>**4. Постройте гистограмму распределения часа начала сессии (*start_hour*). Сделайте гистограмму цвета *darkgreen*, подпишите оси по-русски.**<jupyter_code>axes = train_data_10users['start_hour'].hist(color='darkgreen')
axes.set_xlabel("Час начала сессии")
axes.set_ylabel("Количество в корзине");<jupyter_output><empty_output><jupyter_text>**5. Постройте гистограммы распределения часа начала сессии (*start_hour*) для каждого из 10 пользователей по отдельности. Используйте *subplots*, чтоб разместить все 10 картинок на одной большой. Пометьте легендой каждую картинку, на легенде должно быть написано имя пользователя. Для каждого пользователя раскрасьте гистограмму его/ее цветом (*color_dic*). Подпишите оси по-русски в каждой из 10 гистограмм.**<jupyter_code>fig, axes = plt.subplots(nrows=3, ncols=4, figsize=(16, 10))

# как вариант, можно и по-другому
for idx, (user, sub_df) in  enumerate(pd.groupby(train_data_10users, 'target')): 
    ax = axes[idx // 4, idx % 4]
    sub_df[['start_hour']].hist(ax=ax, label=user, color=color_dic.get(user), bins=range(26))
    ax.set_xlabel("Час начала сессии")
    ax.set_ylabel("Колиство в корзине")
    ax.legend()<jupyter_output><empty_output><jupyter_text>**6. Постройте гистограмму распределения дня недели, в который началась сессия (*day_of_week*). Сделайте гистограмму цвета *sienna*, подпишите оси по-русски.**<jupyter_code>axes = train_data_10users['day_of_week'].hist(color='sienna', bins=range(8))
axes.set_xlabel("День недели начала сессии")
axes.set_ylabel("Количество в корзине");<jupyter_output><empty_output><jupyter_text>**7. Постройте гистограммы распределения дня недели, в который началась сессия (*day_of_week*) для каждого из 10 пользователей по отдельности. Используйте *subplots*, чтоб разместить все 10 картинок на одной большой. Измените метки по оси *X* на ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'] – метод *set_xticklabels*. Пометьте легендой каждую картинку, на легенде должно быть написано имя пользователя. Для каждого пользователя раскрасьте гистограмму его/ее цветом (*color_dic*). Подпишите по-русски название каждой из 10 гистограмм.**<jupyter_code>fig, axes = plt.subplots(nrows=3, ncols=4, figsize=(16, 10), sharey=True)

# как вариант, можно и по-другому
#pivot = pd.pivot_table(train_data_10users,
#                       index="target", columns="day_of_week", aggfunc='size', fill_value=0)
data = train_data_10users[["target", "day_of_week"]]
group = pd.groupby(data, 'target')
for idx, (user, sub_df) in  enumerate(group): 
    ax = axes[idx // 4, idx % 4]
    sub_df.hist(ax=ax, label=user, color=color_dic.get(user), bins=range(8))
    ax.set_xlabel("День начала сессии")
    ax.set_xticks(range(8))
    ax.set_xticks(np.arange(8)+0.5)
    ax.set_xticklabels(['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'])
    ax.set_ylabel("Колиство в корзине")
    ax.legend()<jupyter_output><empty_output><jupyter_text>**8. Сделайте выводы про каждого пользователя по построенным графикам.** Анализ приведён по графикам "на глаз". Заметно, что некоторые юзеры имеют особенности в поведении, кто-то не заходит по выходным, кто-то заходит с утра, но не на ночь, итд. Ashley* unique site - чаще всего сессии содержат 7\8 сайтов* start hour - чаще всего с утра и в районе 15-00, почти нет сессий после 18-00* day of week - среда, и нет сессий на выходных Avril* unique site - чаще всего сессии содержат 5\6\7 сайтов* start hour - с 13-00 до 15-00 и в районе 17-00, малая активность после 20-00* day of week - почти равномерно, кроме понедельника, пятницы и субботы, в которые сессий втрое меньше. Bill* unique site - чаще всего сессии содержат 6/7/8/9 сайтов* start hour - c 8-00 до 10-00 и около 15-00, малая активность после 20-00* day of week - чаще всего выходит в понедельник, вторник, вдвое меньше в среду, и изредка в остальные дни, в субботу сессии реже всего Bob* unique site - чаще всего сессии содержат 5/6/7/8 сайтов* start hour - чаще всего в районе 10-00 и около 15-00, почти нет сессий после 18-00* day of week - чаще всего вторник, четверг, пятница. Изредка в понедельник и среду. Почти ничего в субботу и воскресенье. Dick* unique site - два пика: 2 сайта и 6\7\8 сайтов за сессию.* start hour - начиная с 9-00 и до 24-00 исключая несколько провалов в районе 13-00 и 18-00* day of week - очень мало сессий в понедельник, вторник, четверг и воскресенье. Среда, суббота - пики, пятница средне Ed* unique site - чаще всего сессии содержат 5/6/7/8 сайтов* start hour - немного с 8-00 до 10-00 и с 20-00 до 24-00, много сесиий около 16-00, 17-00, остальное время средне.* day of week - чаще среда, суббота, воскресенье. немножко вторник и четверг. Почти ничего в понедельник и пятницу. John* unique site - чаще всего сессии содержат 7/8/9 сайтов* start hour - часто с 10-00 до 16-00 с пиком в в 15-00, почти нет после 18-00* day of week - чаще четверг, пятница, почти ничего в выходные, остальные - средне Lindsey* unique site - чаще 6/7/8 сайтов* start hour - чаще с 12-00 до 15-00, довольно редко после 18-00б с 8-00 до 12-00 средне* day of week - пик вторник, среда. Немного на выходных, остальные дни - средне. Mary-Kate * unique site - пик 2 сайта, очень редко 1, 9, 10 сайтов. Довольно часто остальные.* start hour - пики 9-00, 10-00 и около 20-00, редко около 8-00 и 24-00, остльное - средне* day of week - очень часто в выходные, редко в понедельник, остальное - средне Naomi* unique site - чаще всего сессии содержат 5/6/7/8 сайтов* start hour - пик с 12-00 до 15-00, немного с 9-00 до 11-00 и с 20-00. Очень редко 8-00, 9-00 и 18-00.* day of week - до среды - средне, пик в четверг. Пятница редко, суббота реже, воскресенье ещё реже. **Загрузите сохраненный ранее в pickle-файл частотный словарь сайтов для 10 пользователей. **<jupyter_code>path = os.path.join(PATH_TO_DATA, 'site_freq_10users.pkl')
with open(path, "rb") as f:
    dict = pickle.load(f)<jupyter_output><empty_output><jupyter_text>**Определите топ-10 самых посещаемых сайтов (*top10_sites*) и соответствующие кол-ва посещений (*top10_freqs*).**<jupyter_code>import heapq
top10 = heapq.nlargest(10,dict.items(), key = lambda x: x[1][1])

top10_ids = set(map(lambda x: x[1][0], top10))
top10_freqs = list(map(lambda x: x[1][1], top10))
top10_sites = list(map(lambda x: x[0], top10))
top10<jupyter_output><empty_output><jupyter_text>**9. Нарисуйте *seaborn barplot*, показывающий частоты посещений топ-10 сайтов. Сделайте подписи сайтов вертикальными, иначе они сливаются (*xticks*).**<jupyter_code>ax = sns.barplot(top10_sites, top10_freqs)
for item in ax.get_xticklabels():
    item.set_rotation(90)<jupyter_output><empty_output><jupyter_text>Часть 3. Дальнейшее построение признаков Это задание творческое, тут надо придумать, как еще учесть время посещения веб-страниц и прочие признаки. На следующей неделе мы будем использовать "мешок" сайтов  для классификации сессий по принадлежности разным пользователям, а эти новые признаки, которые Вы сейчас создадите, потом добавим и посмотрим, улучшается ли модель. Поэтому можно их создать в виде отдельных матриц и сохранить их также отдельно.  В этой части задания Вы можете построить и визуально исследовать самые разные признаки (ничто фантазию не ограничивает):- год, месяц и день начала сессии- час начала сессии (с учетом года, месяца и дня)- время суток- среднее время пребывания на сайте, посчитать можно, скажем, для топ-30 популярных сайтов- индикаторы посещения популярных сайтов (скажем, тоже для топ-30 популярных сайтов)- частота посещения Facebook- ... **Напишите функцию для создания новых признаков и примените ее к исходным данным – каталогам с 10 и 150 файлами. Сделайте это только для набора данных, полученного с параметрами *session_length=10* и *window_size=10*. Сериализуйте полученные матрицы с помощью pickle. Функция может возвращать как только новые признаки, так и старые с новыми. При этом сигнатура функции может быть другой – тут уже свобода выбора.**<jupyter_code>columns = feature_names + ["Month", "Year", "#top_site_visits"]
session_length = 10
window_size = 10

# session contains visits
# visit contains user_id, timestamp, site, site_id
def extractFeatures(session: Session):
    first_visit = session.visits[0]
    start_timestamp = first_visit.timestamp
    month = start_timestamp.month
    year = start_timestamp.year
    visited = list(map(lambda x: x.site_id, session.visits))
    visited_top = [site_id for site_id in visited if site_id in top10_ids]
    top_10_visits = len(visited_top)
    return np.append(session.asData(), [month, year, top_10_visits]) 

def feature_engineering(path_to_csv_files, site_freq_path, session_length=10):
    sessions = (extractFeatures(session) 
                for session in session_generator(path_to_csv_files, site_freq_path, session_length, window_size))
    return pd.DataFrame(sessions, columns=columns)
new_features_10users = feature_engineering(os.path.join(PATH_TO_DATA, '10users'), 
                                           os.path.join(PATH_TO_DATA, 'site_freq_10users.pkl'))
new_features_150users = feature_engineering(os.path.join(PATH_TO_DATA, '150users'), 
                                           os.path.join(PATH_TO_DATA, 'site_freq_150users.pkl'))
new_features_10users.head()<jupyter_output><empty_output><jupyter_text>**10. Постройте картинки для новых признаков, поисследуйте их, прокомментируйте результаты.**<jupyter_code>new_features_10users['target'] = new_features_10users['target'].map(id_name_dict)
axes = new_features_10users['Month'].hist(bins=range(12))
axes.set_xticks(np.arange(12)+0.5)
axes.set_xticklabels(range(1,13))
axes.set_xlabel("Месяц начала сессии 10 юзеров")
axes.set_ylabel("Количество в корзине");
axes = new_features_150users['Month'].hist(bins=range(12))
axes.set_xticks(np.arange(12)+0.5)
axes.set_xticklabels(range(1,13))
axes.set_xlabel("Месяц начала сессии 150 юзеров")
axes.set_ylabel("Количество в корзине");
fig, axes = plt.subplots(nrows=3, ncols=4, figsize=(16, 10), sharey=True)

# как вариант, можно и по-другому
data = new_features_10users[["target", "Month"]]
group = pd.groupby(data, 'target')
for idx, (user, sub_df) in  enumerate(group):
    ax = axes[idx // 4, idx % 4]
    sub_df.hist(ax=ax, label=user, color=color_dic.get(user), bins=range(13))
    ax.set_xlabel("Месяц начала сессии")
    ax.set_xticks(np.arange(13)+0.5)
    ax.set_xticklabels(range(1,13))
    ax.set_ylabel("Колиство в корзине")
    ax.legend()
axes = new_features_10users['Year'].hist()
axes.set_xlabel("Год начала сессии 10 юзеров")
axes.set_ylabel("Количество в корзине");
axes = new_features_150users['Year'].hist()
axes.set_xlabel("Год начала сессии 150 юзеров")
axes.set_ylabel("Количество в корзине");
axes = new_features_10users['#top_site_visits'].hist()
axes.set_xlabel("Количество визитов топ 10 сайтов за сессию 10 юзеров")
axes.set_ylabel("Количество в корзине");
axes = new_features_150users['#top_site_visits'].hist()
axes.set_xlabel("Количество визитов топ 10 сайтов за сессию 150 юзеров")
axes.set_ylabel("Количество в корзине");
fig, axes = plt.subplots(nrows=3, ncols=4, figsize=(16, 10), sharey=True)

# как вариант, можно и по-другому
data = new_features_10users[["target", "#top_site_visits"]]
group = pd.groupby(data, 'target')
for idx, (user, sub_df) in  enumerate(group):
    ax = axes[idx // 4, idx % 4]
    sub_df.hist(ax=ax, label=user, color=color_dic.get(user))
    ax.set_xlabel("Количество визитов топ 10 сайтов")
    ax.set_ylabel("Колиство в корзине")
    ax.legend()<jupyter_output><empty_output><jupyter_text>Данные по месяцам и годам не внушают доверия (половина месяцев отсутствует по непонятной причине), так что использовать их я не стал бы. Количество визитов самых популярных сайтов за сессию лучше и можно попробовать использовать эти данные для обучения модели. Однако не уверен, что он сильно улучшит качество модели, так как для 150 юзеров подавлящее большинство сессий имеет 1-2 посещения топ сайтов.  **В конце сохраните в pickle-файлы только те признаки, которые, как Вы предполагаете, помогут идентифицировать пользователя более точно. Это касается и признаков, которые мы вместе создали в начале (*session_timespan, unique_sites, start_hour, day_of_week*), и Ваших собственных. Можно создать все эти признаки не только для сессий из 10 сайтов, но и для других сочетаний параметров *session_length* и *window_size*.**<jupyter_code>features = ["start_hour", "day_of_week", "#top_site_visits"]
selected_features_10users = new_features_10users[features]
selected_features_150users = new_features_150users[features]
with open(os.path.join(PATH_TO_DATA, 
                       'selected_features_10users.pkl'), 'wb') as selected_features_10users_pkl:
    pickle.dump(selected_features_10users, selected_features_10users_pkl, 
                protocol=2)
with open(os.path.join(PATH_TO_DATA, 
                       'selected_features_150users.pkl'), 'wb') as selected_features_150users_pkl:
    pickle.dump(selected_features_150users, selected_features_150users_pkl, 
                protocol=2)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10516391064693936, 'fraction_numerical': 0.026979982593559618, 'mean_word_length': 4.393506747506357, 'pattern_counts': {'":': 1, '<': 102, '<?xml version=': 0, '>': 101, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 13, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '24', 'dir': 'jupyter-structured-clean-dedup', 'id': '349197', 'license': 'Apache-2.0', 'n_tokens_mistral': 10741, 'n_tokens_neox': 11163, 'n_words': 2441, 'path': 'coursera/ml_yandex/course6/identification/week3_visual_analysis_and_fe.ipynb', 'repo_name': 'VadimKirilchuk/education', 'size': '27610'}