From 5bc2f0c7b7aed92781a9e228f3cae440bc261de0 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 12 Dec 2024 16:13:28 +0300 Subject: [PATCH] lec-12 recsys --- README.md | 4 +- assets/recsys/.gitignore | 2 + assets/recsys/requirements.txt | 9 + assets/recsys/research/recommendations.ipynb | 4637 +++++++++++++++++ assets/recsys/service/events/Dockerfile | 9 + .../recsys/service/events/events_service.py | 54 + assets/recsys/service/events/requirements.txt | 2 + assets/recsys/service/features/Dockerfile | 9 + .../service/features/feature_service.py | 57 + .../recsys/service/features/requirements.txt | 6 + .../recsys/service/recommendations/Dockerfile | 9 + .../service/recommendations/rec_handler.py | 43 + .../recommendations/recommendation_service.py | 116 + .../service/recommendations/requirements.txt | 12 + 14 files changed, 4968 insertions(+), 1 deletion(-) create mode 100644 assets/recsys/.gitignore create mode 100644 assets/recsys/requirements.txt create mode 100644 assets/recsys/research/recommendations.ipynb create mode 100644 assets/recsys/service/events/Dockerfile create mode 100644 assets/recsys/service/events/events_service.py create mode 100644 assets/recsys/service/events/requirements.txt create mode 100644 assets/recsys/service/features/Dockerfile create mode 100644 assets/recsys/service/features/feature_service.py create mode 100644 assets/recsys/service/features/requirements.txt create mode 100644 assets/recsys/service/recommendations/Dockerfile create mode 100644 assets/recsys/service/recommendations/rec_handler.py create mode 100644 assets/recsys/service/recommendations/recommendation_service.py create mode 100644 assets/recsys/service/recommendations/requirements.txt diff --git a/README.md b/README.md index aed965a..c070272 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,9 @@ | 19.11.2024 | [Docker compose](./lectures/lec10-docker_compose.pptx) | | 21.11.2024 | [Мониторинг сервиса. Prometheus. Graphana](./lectures/lec11-monitoring.pptx) | | 28.11.2024 | [Работа с БД](./lectures/lec12-database.pptx) | - +| 05.12.2024 | [Рекомендательные системы](./lectures/lec13-recsys.pptx) | +| 12.12.2024 | Создание сервиса рекомендаций - код в [директории](./assets/recsys)| +| 19.12.2024 | Создание сервиса рекомендаций - код в [директории](./assets/recsys)| ## Перенос занятий ### Лекции diff --git a/assets/recsys/.gitignore b/assets/recsys/.gitignore new file mode 100644 index 0000000..22a94e1 --- /dev/null +++ b/assets/recsys/.gitignore @@ -0,0 +1,2 @@ +*.parquet +*__pycache__* diff --git a/assets/recsys/requirements.txt b/assets/recsys/requirements.txt new file mode 100644 index 0000000..2c909dc --- /dev/null +++ b/assets/recsys/requirements.txt @@ -0,0 +1,9 @@ +pandas +matplotlib +seaborn +pyarrow==13.0.0 +mlflow==2.7.1 +implicit==0.7.2 +catboost +fastapi +uvicorn diff --git a/assets/recsys/research/recommendations.ipynb b/assets/recsys/research/recommendations.ipynb new file mode 100644 index 0000000..ebc8257 --- /dev/null +++ b/assets/recsys/research/recommendations.ipynb @@ -0,0 +1,4637 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 53, + "id": "662d04e7-1b0b-4e4a-9ddf-4526d7fef119", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd" + ] + }, + { + "cell_type": "markdown", + "id": "bf80fbc5-b660-4fac-8fbb-a5cae77313b3", + "metadata": {}, + "source": [ + "# === ЭТАП 1 ===" + ] + }, + { + "cell_type": "markdown", + "id": "5263a8b3-fe99-4204-8a2e-105182792c11", + "metadata": {}, + "source": [ + "# Загрузка первичных данных" + ] + }, + { + "cell_type": "markdown", + "id": "1b54a6a5-1656-4e3c-99d1-49dc39451d33", + "metadata": {}, + "source": [ + "Загружаем первичные данные из файлов:\n", + "- tracks.parquet\n", + "- catalog_names.parquet\n", + "- interactions.parquet" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "5d4b8961-3f35-4e58-9d6b-3e2dbd2c4224", + "metadata": {}, + "outputs": [], + "source": [ + "tracks = pd.read_parquet('../data/tracks.parquet')\n", + "interactions = pd.read_parquet('../data/interactions.parquet')\n", + "catalog = pd.read_parquet('../data/catalog_names.parquet')" + ] + }, + { + "cell_type": "markdown", + "id": "e8f2a1f7-a05f-4f39-af90-5f4018aa6f9d", + "metadata": {}, + "source": [ + "# Обзор данных" + ] + }, + { + "cell_type": "markdown", + "id": "46a85307-896c-4fac-9fcf-f0dffa90889e", + "metadata": {}, + "source": [ + "Проверяем данные, есть ли с ними явные проблемы." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "c9f8f17e-9b56-4f5a-a463-f694a993effb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
track_idalbumsartistsgenresnamecount
026[3, 2490753][16][11, 21]Complimentary Me5
138[3, 2490753][16][11, 21]Momma's Boy8
2135[12, 214, 2490809][84][11]Atticus16
3136[12, 214, 2490809][84][11]24 Hours7
4138[12, 214, 322, 72275, 72292, 91199, 213505, 24...[84][11]Don't Upset The Rhythm (Go Baby Go)17
\n", + "
" + ], + "text/plain": [ + " track_id albums artists \\\n", + "0 26 [3, 2490753] [16] \n", + "1 38 [3, 2490753] [16] \n", + "2 135 [12, 214, 2490809] [84] \n", + "3 136 [12, 214, 2490809] [84] \n", + "4 138 [12, 214, 322, 72275, 72292, 91199, 213505, 24... [84] \n", + "\n", + " genres name count \n", + "0 [11, 21] Complimentary Me 5 \n", + "1 [11, 21] Momma's Boy 8 \n", + "2 [11] Atticus 16 \n", + "3 [11] 24 Hours 7 \n", + "4 [11] Don't Upset The Rhythm (Go Baby Go) 17 " + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tracks.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0d9d8b5d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 932664 entries, 0 to 932663\n", + "Data columns (total 6 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 track_id 932664 non-null int64 \n", + " 1 albums 932664 non-null object\n", + " 2 artists 932664 non-null object\n", + " 3 genres 932664 non-null object\n", + " 4 name 932664 non-null object\n", + " 5 count 932664 non-null int64 \n", + "dtypes: int64(2), object(4)\n", + "memory usage: 42.7+ MB\n" + ] + } + ], + "source": [ + "tracks.info()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "092a1f8b", + "metadata": {}, + "outputs": [], + "source": [ + "tracks['empty_genre'] = tracks['genres'].apply(lambda data: len(data) == 0)\n", + "tracks['empty_album'] = tracks['albums'].apply(lambda data: len(data) == 0)\n", + "tracks['empty_artist'] = tracks['artists'].apply(lambda data: len(data) == 0)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "7bd05c0f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "932664" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tracks = tracks.loc[~tracks['empty_artist']]\n", + "tracks = tracks.loc[~tracks['empty_album']]\n", + "tracks = tracks.loc[~tracks['empty_genre']]\n", + "len(tracks)" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "6e455a1f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idtypename
03albumTaller Children
112albumWild Young Hearts
213albumLonesome Crow
317albumGraffiti Soul
426albumBlues Six Pack
\n", + "
" + ], + "text/plain": [ + " id type name\n", + "0 3 album Taller Children\n", + "1 12 album Wild Young Hearts\n", + "2 13 album Lonesome Crow\n", + "3 17 album Graffiti Soul\n", + "4 26 album Blues Six Pack" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "catalog.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "97a67071", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array(['album', 'artist', 'genre', 'track'], dtype=object)" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "catalog.type.unique()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "5ceb5505", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idtypename
81911638012trackThe Riddle
\n", + "
" + ], + "text/plain": [ + " id type name\n", + "819116 38012 track The Riddle" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "catalog.loc[(catalog.type == 'track') & (catalog.id == 38012 )]" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "61fc07be", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "166\n", + "658724\n", + "153581\n" + ] + } + ], + "source": [ + "genre_list = catalog.loc[catalog.type=='genre', 'id'].values\n", + "print(len(genre_list))\n", + "album_list = catalog.loc[catalog.type=='album', 'id'].values\n", + "print(len(album_list))\n", + "artist_list = catalog.loc[catalog.type=='artist', 'id'].values\n", + "print(len(artist_list))" + ] + }, + { + "cell_type": "markdown", + "id": "4b96df54", + "metadata": {}, + "source": [ + "Проверяем, есть ли неизвестные данные в столбцах (которых нет в каталоге)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "0c302f8d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
track_idalbumsartistsgenresnamecountempty_genreempty_albumempty_artistunknown_genreunknown_artistunknown_album
\n", + "
" + ], + "text/plain": [ + "Empty DataFrame\n", + "Columns: [track_id, albums, artists, genres, name, count, empty_genre, empty_album, empty_artist, unknown_genre, unknown_artist, unknown_album]\n", + "Index: []" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tracks['unknown_genre'] = tracks['genres'].apply(lambda alb: sum([1 if a in genre_list else 0 for a in alb]) / len(alb))\n", + "tracks.query('unknown_genre < 1')" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "efd2e2eb-3bec-4ce1-87ac-232bab8bc0d0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
track_idalbumsartistsgenresnamecountempty_genreempty_albumempty_artistunknown_genreunknown_artist
\n", + "
" + ], + "text/plain": [ + "Empty DataFrame\n", + "Columns: [track_id, albums, artists, genres, name, count, empty_genre, empty_album, empty_artist, unknown_genre, unknown_artist]\n", + "Index: []" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tracks['unknown_artist'] = tracks['artists'].apply(lambda alb: sum([1 if a in artist_list else 0 for a in alb]) / len(alb))\n", + "tracks.query('unknown_artist < 1')" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "f462a430", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
track_idalbumsartistsgenresnamecountempty_genreempty_albumempty_artistunknown_genreunknown_artistunknown_album
\n", + "
" + ], + "text/plain": [ + "Empty DataFrame\n", + "Columns: [track_id, albums, artists, genres, name, count, empty_genre, empty_album, empty_artist, unknown_genre, unknown_artist, unknown_album]\n", + "Index: []" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# 10 minutes\n", + "tracks['unknown_album'] = tracks['albums'].apply(lambda alb: sum([1 if a in album_list else 0 for a in alb]) / len(alb))\n", + "tracks.query('unknown_album < 1')" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "2e0fa8d5", + "metadata": {}, + "outputs": [], + "source": [ + "tracks = tracks.loc[tracks['unknown_genre'] == 1]" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "5f6d9ced", + "metadata": {}, + "outputs": [], + "source": [ + "track_list = tracks.track_id.unique()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "2a64e8e6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Index: 214714415 entries, 0 to 291\n", + "Data columns (total 4 columns):\n", + " # Column Dtype \n", + "--- ------ ----- \n", + " 0 user_id int32 \n", + " 1 track_id int32 \n", + " 2 track_seq int16 \n", + " 3 started_at datetime64[ns]\n", + "dtypes: datetime64[ns](1), int16(1), int32(2)\n", + "memory usage: 5.2 GB\n" + ] + }, + { + "data": { + "text/plain": [ + "user_id 1368700\n", + "track_id 932664\n", + "track_seq 16636\n", + "started_at 365\n", + "dtype: int64" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interactions.info()\n", + "interactions.nunique()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "5ea6c6df", + "metadata": {}, + "outputs": [], + "source": [ + "interactions = interactions.loc[interactions['track_id'].isin(track_list)]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "55b0cb35", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Index: 214714415 entries, 0 to 291\n", + "Data columns (total 4 columns):\n", + " # Column Dtype \n", + "--- ------ ----- \n", + " 0 user_id int32 \n", + " 1 track_id int32 \n", + " 2 track_seq int16 \n", + " 3 started_at datetime64[ns]\n", + "dtypes: datetime64[ns](1), int16(1), int32(2)\n", + "memory usage: 5.2 GB\n" + ] + }, + { + "data": { + "text/plain": [ + "user_id 1368700\n", + "track_id 932664\n", + "track_seq 16636\n", + "started_at 365\n", + "dtype: int64" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interactions.info()\n", + "interactions.nunique()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "267e9d6d", + "metadata": {}, + "outputs": [], + "source": [ + "def flat_index(df_stats): \n", + " df_stats.columns = df_stats.columns.get_level_values(0) + '_' + df_stats.columns.get_level_values(1) \n", + " df_stats.columns.to_flat_index() \n", + " df_stats.columns = df_stats.columns.to_flat_index() \n", + " df_stats.reset_index(inplace=True) \n", + " return df_stats" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "064423eb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['user_id', 'track_id', 'track_seq']" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numeric_cols = interactions.select_dtypes(include=['int16','int32']).columns.tolist()\n", + "numeric_cols" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "7b22d39e", + "metadata": {}, + "outputs": [], + "source": [ + "users_grouped = interactions[numeric_cols].groupby('user_id').agg(['count', 'sum', 'max'])\n", + "users_grouped = flat_index(users_grouped)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "28240cf5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_idtrack_id_counttrack_id_sumtrack_id_maxtrack_seq_counttrack_seq_sumtrack_seq_max
0026222329191204976212635126
11361433967998834367713666636
2213382479927716502001310314
33331437092275781949993356133
4424585705671429876629524531738256
........................
136869513745781136012196435785893116611
1368696137457923178064881418995162328924
13686971374580274105132440819661810127437911277
136869813745815032132966573699242432503126756503
136869913745822901762670172110073637529042717292
\n", + "

1368700 rows × 7 columns

\n", + "
" + ], + "text/plain": [ + " user_id track_id_count track_id_sum track_id_max track_seq_count \\\n", + "0 0 26 222329191 20497621 26 \n", + "1 1 36 1433967998 83436771 36 \n", + "2 2 13 382479927 71650200 13 \n", + "3 3 33 1437092275 78194999 33 \n", + "4 4 245 8570567142 98766295 245 \n", + "... ... ... ... ... ... \n", + "1368695 1374578 11 360121964 35785893 11 \n", + "1368696 1374579 23 178064881 41899516 23 \n", + "1368697 1374580 274 10513244081 96618101 274 \n", + "1368698 1374581 503 21329665736 99242432 503 \n", + "1368699 1374582 290 17626701721 100736375 290 \n", + "\n", + " track_seq_sum track_seq_max \n", + "0 351 26 \n", + "1 666 36 \n", + "2 103 14 \n", + "3 561 33 \n", + "4 31738 256 \n", + "... ... ... \n", + "1368695 66 11 \n", + "1368696 289 24 \n", + "1368697 37911 277 \n", + "1368698 126756 503 \n", + "1368699 42717 292 \n", + "\n", + "[1368700 rows x 7 columns]" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "users_grouped" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "93201b84", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
track_iduser_id_countuser_id_sumuser_id_maxtrack_seq_counttrack_seq_sumtrack_seq_max
026531873891008720551
138865109211304168892
21351611203352132458616161
31367525397412031847102
41381715656791132458617253
........................
932659101478482628096061055764655494910
9326601014901487244514110132215072389874911
9326611014930579685575912944319119694912
932662101495927201548586813205662097181146
9326631015218193424517206135041134193242768
\n", + "

932664 rows × 7 columns

\n", + "
" + ], + "text/plain": [ + " track_id user_id_count user_id_sum user_id_max track_seq_count \\\n", + "0 26 5 3187389 1008720 5 \n", + "1 38 8 6510921 1304168 8 \n", + "2 135 16 11203352 1324586 16 \n", + "3 136 7 5253974 1203184 7 \n", + "4 138 17 15656791 1324586 17 \n", + "... ... ... ... ... ... \n", + "932659 101478482 6 2809606 1055764 6 \n", + "932660 101490148 72 44514110 1322150 72 \n", + "932661 101493057 9 6855759 1294431 9 \n", + "932662 101495927 20 15485868 1320566 20 \n", + "932663 101521819 34 24517206 1350411 34 \n", + "\n", + " track_seq_sum track_seq_max \n", + "0 5 1 \n", + "1 9 2 \n", + "2 16 1 \n", + "3 10 2 \n", + "4 25 3 \n", + "... ... ... \n", + "932659 5549 4910 \n", + "932660 38987 4911 \n", + "932661 11969 4912 \n", + "932662 9718 1146 \n", + "932663 19324 2768 \n", + "\n", + "[932664 rows x 7 columns]" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tracks_grouped = interactions[numeric_cols].groupby('track_id').agg(['count','sum','max'])\n", + "tracks_grouped = flat_index(tracks_grouped)\n", + "tracks_grouped" + ] + }, + { + "cell_type": "markdown", + "id": "1acece8d", + "metadata": {}, + "source": [ + "Найдем пользователей с малой историей - для них нет смысла считать персональные рекомендации. " + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "407cedc8", + "metadata": {}, + "outputs": [], + "source": [ + "small_users = users_grouped.query('track_id_count <= 3')['user_id'] # пользователи, прослушавшие менее 3 треков\n", + "interactions = interactions.loc[~interactions['user_id'].isin(small_users)]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "f6b86360", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "932664" + ] + }, + "execution_count": 62, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(interactions.track_id.unique())" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "3b02415b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idname
81247126Complimentary Me
81247238Momma's Boy
812473135Atticus
81247413624 Hours
812475138Don't Upset The Rhythm (Go Baby Go)
.........
1812466101478482На лицо
1812467101490148Без капли мысли
1812468101493057SKITTLES
1812469101495927Москва
1812470101521819Вокзал
\n", + "

1000000 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " id name\n", + "812471 26 Complimentary Me\n", + "812472 38 Momma's Boy\n", + "812473 135 Atticus\n", + "812474 136 24 Hours\n", + "812475 138 Don't Upset The Rhythm (Go Baby Go)\n", + "... ... ...\n", + "1812466 101478482 На лицо\n", + "1812467 101490148 Без капли мысли\n", + "1812468 101493057 SKITTLES\n", + "1812469 101495927 Москва\n", + "1812470 101521819 Вокзал\n", + "\n", + "[1000000 rows x 2 columns]" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "catalog_tracks = catalog.loc[catalog.type=='track'][['id', 'name']]\n", + "catalog_tracks.to_parquet('../data/catalog_tracks.parquet')\n", + "catalog_tracks\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e53cf75b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idname
8123050all
8123061eastern
8123072rusrock
8123083rusrap
8123094postrock
.........
8124661182balkan
8124671197experimental
8124681370europop
8124691484meditation
8124701542asiapop
\n", + "

166 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " id name\n", + "812305 0 all\n", + "812306 1 eastern\n", + "812307 2 rusrock\n", + "812308 3 rusrap\n", + "812309 4 postrock\n", + "... ... ...\n", + "812466 1182 balkan\n", + "812467 1197 experimental\n", + "812468 1370 europop\n", + "812469 1484 meditation\n", + "812470 1542 asiapop\n", + "\n", + "[166 rows x 2 columns]" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "catalog_genres = catalog.loc[catalog.type=='genre'][['id', 'name']]\n", + "catalog_genres.to_parquet('../data/catalog_genres.parquet')\n", + "catalog_genres" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "f261c887", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idname
03Taller Children
112Wild Young Hearts
213Lonesome Crow
317Graffiti Soul
426Blues Six Pack
.........
65871921458141The Lazy Singles
65872021458207Jackie Mittoo Anthology
65872121458968Master Composers: Johann Sebastian Bach
65872221459622Take the Money and Run
65872321461648Tropical
\n", + "

658724 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " id name\n", + "0 3 Taller Children\n", + "1 12 Wild Young Hearts\n", + "2 13 Lonesome Crow\n", + "3 17 Graffiti Soul\n", + "4 26 Blues Six Pack\n", + "... ... ...\n", + "658719 21458141 The Lazy Singles\n", + "658720 21458207 Jackie Mittoo Anthology\n", + "658721 21458968 Master Composers: Johann Sebastian Bach\n", + "658722 21459622 Take the Money and Run\n", + "658723 21461648 Tropical\n", + "\n", + "[658724 rows x 2 columns]" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "catalog_albums = catalog.loc[catalog.type=='album'][['id', 'name']]\n", + "catalog_albums.to_parquet('../data/catalog_albums.parquet')\n", + "catalog_albums" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "d5095297", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idname
6587244Kenny Dorham
6587255Max Roach
6587267Francis Rossi
6587279Status Quo
65872812Phil Everly
.........
81230016093680Los Tiburones
81230116097398AMELI
812302160984452GANGSTA
81230316099125Daria
81230416102782Pan dö Baré
\n", + "

153581 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " id name\n", + "658724 4 Kenny Dorham\n", + "658725 5 Max Roach\n", + "658726 7 Francis Rossi\n", + "658727 9 Status Quo\n", + "658728 12 Phil Everly\n", + "... ... ...\n", + "812300 16093680 Los Tiburones\n", + "812301 16097398 AMELI\n", + "812302 16098445 2GANGSTA\n", + "812303 16099125 Daria\n", + "812304 16102782 Pan dö Baré\n", + "\n", + "[153581 rows x 2 columns]" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "catalog_artists = catalog.loc[catalog.type=='artist'][['id', 'name']]\n", + "catalog_artists.to_parquet('data/catalog_artists.parquet')\n", + "catalog_artists" + ] + }, + { + "cell_type": "markdown", + "id": "f762beed", + "metadata": {}, + "source": [ + "## Анализ каталогов\n", + "## Genres" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "41225d91", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idname
8123050all
8123061eastern
8123072rusrock
8123083rusrap
8123094postrock
.........
8124661182balkan
8124671197experimental
8124681370europop
8124691484meditation
8124701542asiapop
\n", + "

166 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " id name\n", + "812305 0 all\n", + "812306 1 eastern\n", + "812307 2 rusrock\n", + "812308 3 rusrap\n", + "812309 4 postrock\n", + "... ... ...\n", + "812466 1182 balkan\n", + "812467 1197 experimental\n", + "812468 1370 europop\n", + "812469 1484 meditation\n", + "812470 1542 asiapop\n", + "\n", + "[166 rows x 2 columns]" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "catalog_genres" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "e3627b2a", + "metadata": {}, + "outputs": [], + "source": [ + "genres_dict = dict(zip(catalog_genres['id'], [0]*catalog.shape[0]))\n", + "\n", + "for k,v in tracks.iterrows():\n", + " genres = v['genres']\n", + " for g in genres:\n", + " genres_dict[g] += 1\n", + "genres_df = pd.DataFrame.from_dict(genres_dict, orient='index')" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "df97b13b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idcountname
000all
111186eastern
2236649rusrock
3365958rusrap
442054postrock
............
1611182454balkan
16211971674experimental
16313700europop
16414841606meditation
1651542176asiapop
\n", + "

166 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " id count name\n", + "0 0 0 all\n", + "1 1 1186 eastern\n", + "2 2 36649 rusrock\n", + "3 3 65958 rusrap\n", + "4 4 2054 postrock\n", + ".. ... ... ...\n", + "161 1182 454 balkan\n", + "162 1197 1674 experimental\n", + "163 1370 0 europop\n", + "164 1484 1606 meditation\n", + "165 1542 176 asiapop\n", + "\n", + "[166 rows x 3 columns]" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "genres_df.reset_index(inplace=True)\n", + "genres_df.columns=['id','count']\n", + "genres_df['name'] = genres_df['id'].apply(lambda x: catalog_genres.loc[catalog_genres['id'] == x]['name'].values[0] )\n", + "genres_df" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "bd810c63", + "metadata": {}, + "outputs": [], + "source": [ + "catalog_genres = catalog_genres.merge(genres_df[['count','id']], on='id')\n", + "catalog_genres.to_parquet('data/catalog_genres.parquet')" + ] + }, + { + "cell_type": "markdown", + "id": "3fe7c8ae", + "metadata": {}, + "source": [ + "## Tracks" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "faebd87d", + "metadata": {}, + "outputs": [], + "source": [ + "tracks_dict = dict(zip(catalog_tracks['id'], [0]*catalog_tracks.shape[0]))\n", + "tracks = tracks.merge(catalog_tracks, left_on='track_id', right_on='id')\n", + "tracks = tracks.merge(tracks_grouped, on='track_id')\n", + "tracks = tracks[['track_id', 'albums', 'artists', 'genres', 'name_x', 'user_id_count']]\n", + "tracks.rename(columns={'user_id_count':'count'}, inplace=True)\n", + "tracks.rename(columns={'name_x':'name'}, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "9bdda605", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
track_idalbumsartistsgenresnamecount
026[3, 2490753][16][11, 21]Complimentary Me5
138[3, 2490753][16][11, 21]Momma's Boy8
2135[12, 214, 2490809][84][11]Atticus16
3136[12, 214, 2490809][84][11]24 Hours7
4138[12, 214, 322, 72275, 72292, 91199, 213505, 24...[84][11]Don't Upset The Rhythm (Go Baby Go)17
.....................
932659101478482[21399811][5540395][3, 75]На лицо6
932660101490148[21403052][9078726][11, 20]Без капли мысли72
932661101493057[21403883][11865715][44, 75]SKITTLES9
932662101495927[21404975][4462686][3, 75]Москва20
932663101521819[21414638][5056591][3, 75]Вокзал34
\n", + "

932664 rows × 6 columns

\n", + "
" + ], + "text/plain": [ + " track_id albums \\\n", + "0 26 [3, 2490753] \n", + "1 38 [3, 2490753] \n", + "2 135 [12, 214, 2490809] \n", + "3 136 [12, 214, 2490809] \n", + "4 138 [12, 214, 322, 72275, 72292, 91199, 213505, 24... \n", + "... ... ... \n", + "932659 101478482 [21399811] \n", + "932660 101490148 [21403052] \n", + "932661 101493057 [21403883] \n", + "932662 101495927 [21404975] \n", + "932663 101521819 [21414638] \n", + "\n", + " artists genres name count \n", + "0 [16] [11, 21] Complimentary Me 5 \n", + "1 [16] [11, 21] Momma's Boy 8 \n", + "2 [84] [11] Atticus 16 \n", + "3 [84] [11] 24 Hours 7 \n", + "4 [84] [11] Don't Upset The Rhythm (Go Baby Go) 17 \n", + "... ... ... ... ... \n", + "932659 [5540395] [3, 75] На лицо 6 \n", + "932660 [9078726] [11, 20] Без капли мысли 72 \n", + "932661 [11865715] [44, 75] SKITTLES 9 \n", + "932662 [4462686] [3, 75] Москва 20 \n", + "932663 [5056591] [3, 75] Вокзал 34 \n", + "\n", + "[932664 rows x 6 columns]" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tracks" + ] + }, + { + "cell_type": "markdown", + "id": "cddebf8e", + "metadata": {}, + "source": [ + "## Artists" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "fdd805b2", + "metadata": {}, + "outputs": [], + "source": [ + "artists_dict = dict(zip(catalog_artists['id'], [0]*catalog_artists.shape[0]))\n", + "\n", + "for k,v in tracks.iterrows():\n", + " artists = v['artists']\n", + " for g in artists:\n", + " artists_dict[g] += 1\n", + "artists_df = pd.DataFrame.from_dict(artists_dict, orient='index')" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "064f70d0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idcountname
041Kenny Dorham
151Max Roach
271Francis Rossi
39182Status Quo
4121Phil Everly
............
153576160936804Los Tiburones
153577160973982AMELI
1535781609844512GANGSTA
153579160991251Daria
153580161027821Pan dö Baré
\n", + "

153581 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " id count name\n", + "0 4 1 Kenny Dorham\n", + "1 5 1 Max Roach\n", + "2 7 1 Francis Rossi\n", + "3 9 182 Status Quo\n", + "4 12 1 Phil Everly\n", + "... ... ... ...\n", + "153576 16093680 4 Los Tiburones\n", + "153577 16097398 2 AMELI\n", + "153578 16098445 1 2GANGSTA\n", + "153579 16099125 1 Daria\n", + "153580 16102782 1 Pan dö Baré\n", + "\n", + "[153581 rows x 3 columns]" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "artists_df.reset_index(inplace=True)\n", + "artists_df.columns=['id','count']\n", + "artists_df['name'] = artists_df['id'].apply(lambda x: catalog_artists.loc[catalog_artists['id'] == x]['name'].values[0] )\n", + "artists_df" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "04e2830a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idcountname
139301187082038Владимир Высоцкий
261711363сборник
151381398961273Armin van Buuren
209691701201Wolfgang Amadeus Mozart
32912271101Johann Sebastian Bach
2570118181045Hans Zimmer
41115511030Pyotr Ilyich Tchaikovsky
242610987973Elvis Presley
18025188963966Аквариум
16706164416919Михаил Шуфутинский
\n", + "
" + ], + "text/plain": [ + " id count name\n", + "13930 118708 2038 Владимир Высоцкий\n", + "26 171 1363 сборник\n", + "15138 139896 1273 Armin van Buuren\n", + "2096 9170 1201 Wolfgang Amadeus Mozart\n", + "329 1227 1101 Johann Sebastian Bach\n", + "2570 11818 1045 Hans Zimmer\n", + "411 1551 1030 Pyotr Ilyich Tchaikovsky\n", + "2426 10987 973 Elvis Presley\n", + "18025 188963 966 Аквариум\n", + "16706 164416 919 Михаил Шуфутинский" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "artists_df.sort_values(by='count', ascending=False).head(10)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "57e71559", + "metadata": {}, + "outputs": [], + "source": [ + "catalog_artists = catalog_artists.merge(artists_df[['count','id']], on='id')\n" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "5dab64e2", + "metadata": {}, + "outputs": [], + "source": [ + "catalog_artists.to_parquet('data/catalog_artists.parquet')" + ] + }, + { + "cell_type": "markdown", + "id": "318b573a-9e2d-4808-95db-60cfb8bbdb73", + "metadata": { + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "# Выводы" + ] + }, + { + "cell_type": "markdown", + "id": "caa96e12-36a8-4401-8f11-98627a49ae9d", + "metadata": {}, + "source": [ + "Приведём выводы по первому знакомству с данными:\n", + "- есть ли с данными явные проблемы,\n", + "1) Огромная таблица взаимодействий - 220 млн записей.\n", + "1) Представление жанров, артистов и альбомов списками, что усложнит обработку\n", + "\n", + "\n", + "- какие корректирующие действия (в целом) были предприняты.\n", + "1) Были удалены треки без авторов, жанров и альбома\n", + "1) Удалены треки, у которых хотя бы один жанр был неизвестным\n", + "1) Удалены взаимодействия с треками, которых нет в tracks\n", + "1) Удалены из взаимодействий пользователи с количеством прослушиваний менее 3. Для них имеет смысл предсказывать только онлайн " + ] + }, + { + "cell_type": "markdown", + "id": "7bc3296b-eba6-4333-a78d-b9304aa87e3d", + "metadata": {}, + "source": [ + "# === ЭТАП 2 ===" + ] + }, + { + "cell_type": "markdown", + "id": "68e73960-fd38-4e15-8db0-9a25c35dfd25", + "metadata": {}, + "source": [ + "# EDA" + ] + }, + { + "cell_type": "markdown", + "id": "a30e823e-8e0f-4a76-a02e-8d1ba8bf0f8a", + "metadata": {}, + "source": [ + "Распределение количества прослушанных треков." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "9bf5eaba-35f7-4da7-be59-9ab4a34b2423", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([3.0000e+00, 6.0000e+00, 2.5500e+02, 4.9647e+04, 7.2378e+04,\n", + " 5.6896e+04, 4.6211e+04, 3.8646e+04, 3.3147e+04, 2.8957e+04,\n", + " 2.5255e+04, 2.2636e+04, 2.0247e+04, 1.8258e+04, 1.6701e+04,\n", + " 1.5122e+04, 1.4046e+04, 1.2963e+04, 1.2195e+04, 1.1213e+04,\n", + " 1.0359e+04, 9.5520e+03, 9.1130e+03, 8.6450e+03, 8.1950e+03,\n", + " 7.6020e+03, 7.0220e+03, 6.9950e+03, 6.4610e+03, 6.2610e+03,\n", + " 5.8450e+03, 5.7380e+03, 5.4560e+03, 5.1840e+03, 4.9350e+03,\n", + " 4.7770e+03, 4.7050e+03, 4.4300e+03, 4.2880e+03, 4.1730e+03,\n", + " 3.8810e+03, 3.8060e+03, 3.6740e+03, 3.5720e+03, 3.3860e+03,\n", + " 3.3100e+03, 3.2350e+03, 3.2640e+03, 2.9770e+03, 0.0000e+00,\n", + " 3.0450e+03, 2.8090e+03, 2.7330e+03, 2.7090e+03, 2.6730e+03,\n", + " 2.6670e+03, 2.5620e+03, 2.3920e+03, 2.3700e+03, 2.3370e+03,\n", + " 2.2830e+03, 2.1520e+03, 2.1690e+03, 2.0780e+03, 2.1080e+03,\n", + " 2.1130e+03, 1.9710e+03, 1.9290e+03, 1.8570e+03, 1.7640e+03,\n", + " 1.7840e+03, 1.7810e+03, 1.6900e+03, 1.7460e+03, 1.6840e+03,\n", + " 1.5710e+03, 1.6040e+03, 1.5720e+03, 1.6200e+03, 1.4670e+03,\n", + " 1.4740e+03, 1.4300e+03, 1.4960e+03, 1.4090e+03, 1.3540e+03,\n", + " 1.4030e+03, 1.3710e+03, 1.3610e+03, 1.3190e+03, 1.3210e+03,\n", + " 1.2080e+03, 1.2230e+03, 1.1540e+03, 1.1670e+03, 1.1550e+03,\n", + " 1.1800e+03, 1.0870e+03, 1.0960e+03, 1.0900e+03, 1.0600e+03]),\n", + " array([ 2. , 2.98, 3.96, 4.94, 5.92, 6.9 , 7.88, 8.86,\n", + " 9.84, 10.82, 11.8 , 12.78, 13.76, 14.74, 15.72, 16.7 ,\n", + " 17.68, 18.66, 19.64, 20.62, 21.6 , 22.58, 23.56, 24.54,\n", + " 25.52, 26.5 , 27.48, 28.46, 29.44, 30.42, 31.4 , 32.38,\n", + " 33.36, 34.34, 35.32, 36.3 , 37.28, 38.26, 39.24, 40.22,\n", + " 41.2 , 42.18, 43.16, 44.14, 45.12, 46.1 , 47.08, 48.06,\n", + " 49.04, 50.02, 51. , 51.98, 52.96, 53.94, 54.92, 55.9 ,\n", + " 56.88, 57.86, 58.84, 59.82, 60.8 , 61.78, 62.76, 63.74,\n", + " 64.72, 65.7 , 66.68, 67.66, 68.64, 69.62, 70.6 , 71.58,\n", + " 72.56, 73.54, 74.52, 75.5 , 76.48, 77.46, 78.44, 79.42,\n", + " 80.4 , 81.38, 82.36, 83.34, 84.32, 85.3 , 86.28, 87.26,\n", + " 88.24, 89.22, 90.2 , 91.18, 92.16, 93.14, 94.12, 95.1 ,\n", + " 96.08, 97.06, 98.04, 99.02, 100. ]),\n", + " )" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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YCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwUrtDztdff61f/epXGjBggCIjIzVy5Ejt3r3bbrcsS3l5ebriiisUGRmptLQ0ffbZZ0HnOHLkiKZOnSqXy6WoqCjNmDFDx44dC6r56KOPdNNNNykiIkLx8fFatGhRi76sW7dOw4cPV0REhEaOHKm33367vbcDAAAM1a6Q8/333+uGG25Qr169tHHjRn388cd6/vnnddlll9k1ixYt0osvvqiVK1dqx44d6tOnj7xer06ePGnXTJ06Vfv371dJSYkKCwu1bds2PfTQQ3a73+/X+PHjNWTIEJWXl+u5557Tk08+qZdeesmu2b59u6ZMmaIZM2bogw8+0IQJEzRhwgTt27fvfMYDAAAYIsSyLKutxXPnztV7772nP//5z622W5aluLg4/eY3v9EjjzwiSaqvr1dMTIwKCgo0efJkffLJJ0pMTNSuXbuUnJwsSSouLtYdd9yhr776SnFxcVqxYoUef/xx+Xw+ORwO+9rr169XZWWlJGnSpEk6fvy4CgsL7eunpqZq9OjRWrlyZZvux+/3y+12q76+Xi6Xq63DcEENnVvUYt+XC9O7oScAAFwc2vr53a6ZnLfeekvJycm65557FB0dreuvv14vv/yy3X7gwAH5fD6lpaXZ+9xut1JSUlRWViZJKisrU1RUlB1wJCktLU2hoaHasWOHXXPzzTfbAUeSvF6vqqqq9P3339s1Z16nuab5Oq1paGiQ3+8P2gAAgJnaFXK++OILrVixQldeeaU2bdqkWbNm6de//rVWrVolSfL5fJKkmJiYoONiYmLsNp/Pp+jo6KD28PBw9e/fP6imtXOceY1z1TS3tyY/P19ut9ve4uPj23P7AACgB2lXyAkEAhozZoyeffZZXX/99XrooYf04IMPtvnroe42b9481dfX29vBgwe7u0sAAKCLtCvkXHHFFUpMTAzaN2LECFVXV0uSYmNjJUk1NTVBNTU1NXZbbGysamtrg9pPnz6tI0eOBNW0do4zr3Gumub21jidTrlcrqANAACYqV0h54YbblBVVVXQvk8//VRDhgyRJCUkJCg2NlalpaV2u9/v144dO+TxeCRJHo9HdXV1Ki8vt2s2b96sQCCglJQUu2bbtm06deqUXVNSUqKrr77afpPL4/EEXae5pvk6AADg0taukJOdna33339fzz77rD7//HOtXr1aL730kjIzMyVJISEhysrK0tNPP6233npLe/fu1X333ae4uDhNmDBB0t9mfm677TY9+OCD2rlzp9577z3Nnj1bkydPVlxcnCTpl7/8pRwOh2bMmKH9+/drzZo1WrJkiXJycuy+PPzwwyouLtbzzz+vyspKPfnkk9q9e7dmz57dSUMDAAB6NKudNmzYYF177bWW0+m0hg8fbr300ktB7YFAwHriiSesmJgYy+l0WrfeeqtVVVUVVPPdd99ZU6ZMsfr27Wu5XC5r2rRp1tGjR4NqPvzwQ+vGG2+0nE6n9aMf/chauHBhi76sXbvWuuqqqyyHw2Fdc801VlFRUbvupb6+3pJk1dfXt+u4C2lIbmGLDQCAS1lbP7/btU6OaVgnBwCAnqdL1skBAADoKQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRwru7A2i/s1dBZgVkAABaYiYHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIhBwAAGAkQg4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAAAAjtSvkPPnkkwoJCQnahg8fbrefPHlSmZmZGjBggPr27auJEyeqpqYm6BzV1dVKT09X7969FR0drUcffVSnT58OqtmyZYvGjBkjp9OpYcOGqaCgoEVfli1bpqFDhyoiIkIpKSnauXNne24FAAAYrt0zOddcc42++eYbe3v33XfttuzsbG3YsEHr1q3T1q1bdejQId111112e1NTk9LT09XY2Kjt27dr1apVKigoUF5enl1z4MABpaen65ZbblFFRYWysrL0wAMPaNOmTXbNmjVrlJOTowULFmjPnj0aNWqUvF6vamtrOzoOAADAMCGWZVltLX7yySe1fv16VVRUtGirr6/XwIEDtXr1at19992SpMrKSo0YMUJlZWVKTU3Vxo0bdeedd+rQoUOKiYmRJK1cuVK5ubk6fPiwHA6HcnNzVVRUpH379tnnnjx5surq6lRc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uFAhISHKysqy9zHenevrr7/Wr371Kw0YMECRkZEaOXKkdu/ebbdblqW8vDxdccUVioyMVFpamj777LNu7HHP1dTUpCeeeEIJCQmKjIzUP/3TP+k///M/g37jiPHuuG3btulnP/uZ4uLiFBISovXr1we1t2Vsjxw5oqlTp8rlcikqKkozZszQsWPHuqbDFtrk9ddftxwOh/X73//e2r9/v/Xggw9aUVFRVk1NTXd3rcfzer3Wq6++au3bt8+qqKiw7rjjDmvw4MHWsWPH7JqZM2da8fHxVmlpqbV7924rNTXV+slPftKNvTbDzp07raFDh1rXXXed9fDDD9v7Ge/Oc+TIEWvIkCHW/fffb+3YscP64osvrE2bNlmff/65XbNw4ULL7XZb69evtz788EPr3/7t36yEhATrxIkT3djznumZZ56xBgwYYBUWFloHDhyw1q1bZ/Xt29dasmSJXcN4d9zbb79tPf7449Ybb7xhSbLefPPNoPa2jO1tt91mjRo1ynr//fetP//5z9awYcOsKVOmdEl/CTltNG7cOCszM9P+e1NTkxUXF2fl5+d3Y6/MVFtba0mytm7dalmWZdXV1Vm9evWy1q1bZ9d88sknliSrrKysu7rZ4x09etS68sorrZKSEuuf//mf7ZDDeHeu3Nxc68YbbzxneyAQsGJjY63nnnvO3ldXV2c5nU7rf//3fy9EF42Snp5uTZ8+PWjfXXfdZU2dOtWyLMa7M50dctoyth9//LElydq1a5dds3HjRiskJMT6+uuvO72PfF3VBo2NjSovL1daWpq9LzQ0VGlpaSorK+vGnpmpvr5ektS/f39JUnl5uU6dOhU0/sOHD9fgwYMZ//OQmZmp9PT0oHGVGO/O9tZbbyk5OVn33HOPoqOjdf311+vll1+22w8cOCCfzxc03m63WykpKYx3B/zkJz9RaWmpPv30U0nShx9+qHfffVe33367JMa7K7VlbMvKyhQVFaXk5GS7Ji0tTaGhodqxY0en96lHr3h8oXz77bdqampqsZJyTEyMKisru6lXZgoEAsrKytINN9yga6+9VpLk8/nkcDha/JhqTEyMfD5fN/Sy53v99de1Z88e7dq1q0Ub4925vvjiC61YsUI5OTn6j//4D+3atUu//vWv5XA4lJGRYY9pa/+/MN7tN3fuXPn9fg0fPlxhYWFqamrSM888o6lTp0oS492F2jK2Pp9P0dHRQe3h4eHq379/l4w/IQcXlczMTO3bt0/vvvtud3fFWAcPHtTDDz+skpISRUREdHd3jBcIBJScnKxnn31WknT99ddr3759WrlypTIyMrq5d+ZZu3atXnvtNa1evVrXXHONKioqlJWVpbi4OMb7EsTXVW1w+eWXKywsrMXbJTU1NYqNje2mXpln9uzZKiws1DvvvKNBgwbZ+2NjY9XY2Ki6urqgesa/Y8rLy1VbW6sxY8YoPDxc4eHh2rp1q1588UWFh4crJiaG8e5EV1xxhRITE4P2jRgxQtXV1ZJkjyn/v3SORx99VHPnztXkyZM1cuRI3XvvvcrOzlZ+fr4kxrsrtWVsY2NjVVtbG9R++vRpHTlypEvGn5DTBg6HQ0lJSSotLbX3BQIBlZaWyuPxdGPPzGBZlmbPnq0333xTmzdvVkJCQlB7UlKSevXqFTT+VVVVqq6uZvw74NZbb9XevXtVUVFhb8nJyZo6dar9Z8a789xwww0tlkT49NNPNWTIEElSQkKCYmNjg8bb7/drx44djHcH/PWvf1VoaPBHW1hYmAKBgCTGuyu1ZWw9Ho/q6upUXl5u12zevFmBQEApKSmd36lOf5TZUK+//rrldDqtgoIC6+OPP7YeeughKyoqyvL5fN3dtR5v1qxZltvttrZs2WJ988039vbXv/7Vrpk5c6Y1ePBga/Pmzdbu3bstj8djeTyebuy1Wc58u8qyGO/OtHPnTis8PNx65plnrM8++8x67bXXrN69e1t/+MMf7JqFCxdaUVFR1h//+Efro48+sn7+85/zSnMHZWRkWD/60Y/sV8jfeOMN6/LLL7cee+wxu4bx7rijR49aH3zwgfXBBx9Ykqzf/e531gcffGD95S9/sSyrbWN72223Wddff721Y8cO691337WuvPJKXiG/GPzXf/2XNXjwYMvhcFjjxo2z3n///e7ukhEktbq9+uqrds2JEyesf//3f7cuu+wyq3fv3tYvfvEL65tvvum+Thvm7JDDeHeuDRs2WNdee63ldDqt4cOHWy+99FJQeyAQsJ544gkrJibGcjqd1q233mpVVVV1U297Nr/fbz388MPW4MGDrYiICOvHP/6x9fjjj1sNDQ12DePdce+8806r/19nZGRYltW2sf3uu++sKVOmWH379rVcLpc1bdo06+jRo13S3xDLOmMZSAAAAEPwTA4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARvp/jW21PUuP8fEAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.hist(tracks.query('count <= 100')['count'], bins=100)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "0ede50ad", + "metadata": {}, + "outputs": [], + "source": [ + "import seaborn as sns" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "3bd4b553", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0, 'Tracks')" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/GU6VOAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAyVUlEQVR4nO3de1xVdb7/8fdGZIvJRURuBYpm4v0uUU5pkoqO1WRTmTaaptWApUxlTJmXToOnGuvUkJ3OI7UeaZa/Y9aYY4OY2gVNKTISSU3DSdBBB7dXRPn+/ui4pp2XAIG99+L1fDzW4+Fa3+/67s/aq/Ld2t+1lsMYYwQAAGBTfp4uAAAAoD4RdgAAgK0RdgAAgK0RdgAAgK0RdgAAgK0RdgAAgK0RdgAAgK35e7oAb1BVVaV9+/YpKChIDofD0+UAAIBqMMboyJEjiomJkZ/fha/fEHYk7du3T7GxsZ4uAwAA1MLevXt1xRVXXLCdsCMpKChI0o9fVnBwsIerAQAA1eFyuRQbG2v9PX4hhB3J+ukqODiYsAMAgI/5pSkoTFAGAAC2RtgBAAC2RtgBAAC2RtgBAAC2RtgBAAC2RtgBAAC2RtgBAAC2RtgBAAC2RtgBAAC2RtgBAAC2RtgBAAC2RtgBAAC2RtgBAAC2RtgBAAC25u/pAuyuuLhYZWVlNd4vPDxccXFx9VARAACNC2GnHhUXFyshoZNOnDhe430DA5tr+/ZCAg8AAJeIsFOPysrKdOLEcSVOmKng6LbV3s9VskebFsxWWVkZYQcAgEtE2GkAwdFtFRbX0dNlAADQKDFBGQAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2BphBwAA2JpHw05mZqb69eunoKAgRURE6JZbblFRUZFbn5MnTyo1NVWtWrVSixYtNGrUKO3fv9+tT3FxsUaMGKHmzZsrIiJCjzzyiE6fPt2QhwIAALyUR8PO+vXrlZqaqo0bNyo7O1uVlZUaMmSIjh07ZvWZNm2a/vrXv2rZsmVav3699u3bp1tvvdVqP3PmjEaMGKFTp07ps88+0+uvv65FixbpySef9MQhAQAAL+PvyQ9fvXq12/qiRYsUERGhvLw8XXfddTp8+LBee+01LVmyRDfccIMkaeHCherUqZM2btyoq6++Wn//+9+1bds2rVmzRpGRkerZs6eeeuopTZ8+XbNmzVJAQIAnDg0AAHgJr5qzc/jwYUlSWFiYJCkvL0+VlZVKTk62+iQkJCguLk65ubmSpNzcXHXr1k2RkZFWn6FDh8rlcumbb7457+dUVFTI5XK5LQAAwJ68JuxUVVVp6tSpuvbaa9W1a1dJUmlpqQICAhQaGurWNzIyUqWlpVafnwads+1n284nMzNTISEh1hIbG1vHRwMAALyF14Sd1NRUFRQUaOnSpfX+WRkZGTp8+LC17N27t94/EwAAeIZH5+yclZaWppUrV2rDhg264oorrO1RUVE6deqUysvL3a7u7N+/X1FRUVafzz//3G28s3drne3zc06nU06ns46PAgAAeCOPXtkxxigtLU3vvvuu1q5dq/j4eLf2Pn36qGnTpsrJybG2FRUVqbi4WElJSZKkpKQkff311zpw4IDVJzs7W8HBwercuXPDHAgAAPBaHr2yk5qaqiVLlui9995TUFCQNccmJCREgYGBCgkJ0cSJE5Wenq6wsDAFBwdrypQpSkpK0tVXXy1JGjJkiDp37qy7775bzzzzjEpLS/XEE08oNTWVqzcAAMCzYWf+/PmSpIEDB7ptX7hwocaPHy9Jev755+Xn56dRo0apoqJCQ4cO1csvv2z1bdKkiVauXKkHHnhASUlJuuyyyzRu3DjNmTOnoQ4DAAB4MY+GHWPML/Zp1qyZsrKylJWVdcE+bdq00apVq+qyNAAAYBNeczcWAABAfSDsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAWyPsAAAAW/No2NmwYYNGjhypmJgYORwOrVixwq3d4XCcd3n22WetPm3btj2nfe7cuQ18JAAAwFt5NOwcO3ZMPXr0UFZW1nnbS0pK3JYFCxbI4XBo1KhRbv3mzJnj1m/KlCkNUT4AAPAB/p788JSUFKWkpFywPSoqym39vffe06BBg9SuXTu37UFBQef0vZiKigpVVFRY6y6Xq9r7AgAA3+Izc3b279+vDz74QBMnTjynbe7cuWrVqpV69eqlZ599VqdPn77oWJmZmQoJCbGW2NjY+iobAAB4mEev7NTE66+/rqCgIN16661u2x988EH17t1bYWFh+uyzz5SRkaGSkhLNmzfvgmNlZGQoPT3dWne5XAQeAABsymfCzoIFCzRmzBg1a9bMbftPQ0v37t0VEBCg++67T5mZmXI6necdy+l0XrANAADYi0/8jPXxxx+rqKhI99577y/2TUxM1OnTp7Vnz576LwwAAHg9nwg7r732mvr06aMePXr8Yt/8/Hz5+fkpIiKiASoDAADezqM/Yx09elQ7d+601nfv3q38/HyFhYUpLi5O0o/zaZYtW6Y///nP5+yfm5urTZs2adCgQQoKClJubq6mTZumsWPHqmXLlg12HAAAwHt5NOxs2bJFgwYNstbPzr8ZN26cFi1aJElaunSpjDEaPXr0Ofs7nU4tXbpUs2bNUkVFheLj4zVt2jS3eTwAAKBx82jYGThwoIwxF+0zefJkTZ48+bxtvXv31saNG+ujNAAAYBM+MWcHAACgtgg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1gg7AADA1vw9XQAurLCwsMb7hIeHKy4urh6qAQDANxF2vNCJwwclOTR27Nga7xsY2FzbtxcSeAAA+D+EHS9UefyIJKOed01X6/iEau/nKtmjTQtmq6ysjLADAMD/Iex4sRYRcQqL6+jpMgAA8GkenaC8YcMGjRw5UjExMXI4HFqxYoVb+/jx4+VwONyWYcOGufU5dOiQxowZo+DgYIWGhmrixIk6evRoAx4FAADwZh4NO8eOHVOPHj2UlZV1wT7Dhg1TSUmJtbz11ltu7WPGjNE333yj7OxsrVy5Uhs2bNDkyZPru3QAAOAjPPozVkpKilJSUi7ax+l0Kioq6rxthYWFWr16tTZv3qy+fftKkl566SUNHz5czz33nGJiYuq8ZgAA4Fu8/jk769atU0REhDp27KgHHnhABw8etNpyc3MVGhpqBR1JSk5Olp+fnzZt2nTBMSsqKuRyudwWAABgT14ddoYNG6Y33nhDOTk5+s///E+tX79eKSkpOnPmjCSptLRUERERbvv4+/srLCxMpaWlFxw3MzNTISEh1hIbG1uvxwEAADzHq+/GuvPOO60/d+vWTd27d1f79u21bt06DR48uNbjZmRkKD093Vp3uVwEHgAAbMqrr+z8XLt27RQeHq6dO3dKkqKionTgwAG3PqdPn9ahQ4cuOM9H+nEeUHBwsNsCAADsyafCzj/+8Q8dPHhQ0dHRkqSkpCSVl5crLy/P6rN27VpVVVUpMTHRU2UCAAAv4tGfsY4ePWpdpZGk3bt3Kz8/X2FhYQoLC9Ps2bM1atQoRUVFadeuXXr00Ud15ZVXaujQoZKkTp06adiwYZo0aZJeeeUVVVZWKi0tTXfeeSd3YgEAAEkevrKzZcsW9erVS7169ZIkpaenq1evXnryySfVpEkTbd26VTfddJOuuuoqTZw4UX369NHHH38sp9NpjbF48WIlJCRo8ODBGj58uAYMGKBXX33VU4cEAAC8jEev7AwcOFDGmAu2f/jhh784RlhYmJYsWVKXZQEAABvxqTk7AAAANUXYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtkbYAQAAtubRsLNhwwaNHDlSMTExcjgcWrFihdVWWVmp6dOnq1u3brrssssUExOj3/3ud9q3b5/bGG3btpXD4XBb5s6d28BHAgAAvJVHw86xY8fUo0cPZWVlndN2/PhxffHFF5oxY4a++OILLV++XEVFRbrpppvO6TtnzhyVlJRYy5QpUxqifAAA4AP8PfnhKSkpSklJOW9bSEiIsrOz3bb95S9/Uf/+/VVcXKy4uDhre1BQkKKioqr9uRUVFaqoqLDWXS5XDSsHAAC+wqfm7Bw+fFgOh0OhoaFu2+fOnatWrVqpV69eevbZZ3X69OmLjpOZmamQkBBriY2NrceqAQCAJ3n0yk5NnDx5UtOnT9fo0aMVHBxsbX/wwQfVu3dvhYWF6bPPPlNGRoZKSko0b968C46VkZGh9PR0a93lchF4AACwKZ8IO5WVlbr99ttljNH8+fPd2n4aWrp3766AgADdd999yszMlNPpPO94Tqfzgm0AAMBevP5nrLNB5/vvv1d2drbbVZ3zSUxM1OnTp7Vnz56GKRAAAHg1r76yczbo7NixQx999JFatWr1i/vk5+fLz89PERERDVAhAADwdrUKO+3atdPmzZvPCR/l5eXq3bu3vvvuu2qNc/ToUe3cudNa3717t/Lz8xUWFqbo6Gjddttt+uKLL7Ry5UqdOXNGpaWlkqSwsDAFBAQoNzdXmzZt0qBBgxQUFKTc3FxNmzZNY8eOVcuWLWtzaAAAwGZqFXb27NmjM2fOnLO9oqJCP/zwQ7XH2bJliwYNGmStn51/M27cOM2aNUvvv/++JKlnz55u+3300UcaOHCgnE6nli5dqlmzZqmiokLx8fGaNm2a2zweAADQuNUo7JwNH5L04YcfKiQkxFo/c+aMcnJy1LZt22qPN3DgQBljLth+sTZJ6t27tzZu3FjtzwMAAI1PjcLOLbfcIklyOBwaN26cW1vTpk3Vtm1b/fnPf66z4gAAAC5VjcJOVVWVJCk+Pl6bN29WeHh4vRQFAABQV2o1Z2f37t11XQcAAEC9qPWt5zk5OcrJydGBAwesKz5nLViw4JILAwAAqAu1CjuzZ8/WnDlz1LdvX0VHR8vhcNR1XQAAAHWiVmHnlVde0aJFi3T33XfXdT0AAAB1qlavizh16pSuueaauq4FAACgztUq7Nx7771asmRJXdcCAABQ52r1M9bJkyf16quvas2aNerevbuaNm3q1j5v3rw6KQ4AAOBS1SrsbN261XqFQ0FBgVsbk5UBAIA3qVXY+eijj+q6DgAAgHpRqzk7AAAAvqJWV3YGDRp00Z+r1q5dW+uCAAAA6lKtws7Z+TpnVVZWKj8/XwUFBee8IBQAAMCTahV2nn/++fNunzVrlo4ePXpJBQEAANSlOp2zM3bsWN6LBQAAvEqdhp3c3Fw1a9asLocEAAC4JLX6GevWW291WzfGqKSkRFu2bNGMGTPqpDAAAIC6UKuwExIS4rbu5+enjh07as6cORoyZEidFAYAAFAXahV2Fi5cWNd1AAAA1ItahZ2z8vLyVFhYKEnq0qWLevXqVSdFAQAA1JVahZ0DBw7ozjvv1Lp16xQaGipJKi8v16BBg7R06VK1bt26LmsEAACotVrdjTVlyhQdOXJE33zzjQ4dOqRDhw6poKBALpdLDz74YF3XCAAAUGu1urKzevVqrVmzRp06dbK2de7cWVlZWUxQBgAAXqVWV3aqqqrUtGnTc7Y3bdpUVVVVl1wUAABAXalV2Lnhhhv00EMPad++fda2H374QdOmTdPgwYPrrDgAAIBLVauw85e//EUul0tt27ZV+/bt1b59e8XHx8vlcumll16q6xoBAABqrVZzdmJjY/XFF19ozZo12r59uySpU6dOSk5OrtPiAAAALlWNruysXbtWnTt3lsvlksPh0I033qgpU6ZoypQp6tevn7p06aKPP/64vmoFAACosRpd2XnhhRc0adIkBQcHn9MWEhKi++67T/PmzdOvfvWrOisQNXf2QY81ER4erri4uHqoBgAAz6rRlZ2vvvpKw4YNu2D7kCFDlJeXV+3xNmzYoJEjRyomJkYOh0MrVqxwazfG6Mknn1R0dLQCAwOVnJysHTt2uPU5dOiQxowZo+DgYIWGhmrixIk6evRoTQ7LNk4cPijJobFjx6pPnz41WhISOqm4uNjThwAAQJ2r0ZWd/fv3n/eWc2swf3/985//rPZ4x44dU48ePTRhwoRz3qQuSc8884xefPFFvf7664qPj9eMGTM0dOhQbdu2Tc2aNZMkjRkzRiUlJcrOzlZlZaXuueceTZ48WUuWLKnJodlC5fEjkox63jVdreMTqr2fq2SPNi2YrbKyMq7uAABsp0Zh5/LLL1dBQYGuvPLK87Zv3bpV0dHR1R4vJSVFKSkp520zxuiFF17QE088oZtvvlmS9MYbbygyMlIrVqzQnXfeqcLCQq1evVqbN29W3759JUkvvfSShg8frueee04xMTE1OTzbaBERp7C4jp4uAwAAr1Cjn7GGDx+uGTNm6OTJk+e0nThxQjNnztSvf/3rOils9+7dKi0tdbvDKyQkRImJicrNzZUk5ebmKjQ01Ao6kpScnCw/Pz9t2rTpgmNXVFTI5XK5LQAAwJ5qdGXniSee0PLly3XVVVcpLS1NHTv+ePVg+/btysrK0pkzZ/T444/XSWGlpaWSpMjISLftkZGRVltpaakiIiLc2v39/RUWFmb1OZ/MzEzNnj27TuoEAADerUZhJzIyUp999pkeeOABZWRkyBgjSXI4HBo6dKiysrLOCSfeKCMjQ+np6da6y+VSbGysBysCAAD1pcYPFWzTpo1WrVqlf/3rX9q5c6eMMerQoYNatmxZp4VFRUVJ+nFS9E/nAe3fv189e/a0+hw4cMBtv9OnT+vQoUPW/ufjdDrldDrrtF4AAOCdavW6CElq2bKl+vXrp/79+9d50JGk+Ph4RUVFKScnx9rmcrm0adMmJSUlSZKSkpJUXl7udrv72rVrVVVVpcTExDqvCQAA+J5avS6irhw9elQ7d+601nfv3q38/HyFhYUpLi5OU6dO1X/8x3+oQ4cO1q3nMTExuuWWWyT9+IqKYcOGadKkSXrllVdUWVmptLQ03XnnnY32TiwAAODOo2Fny5YtGjRokLV+dh7NuHHjtGjRIj366KM6duyYJk+erPLycg0YMECrV6+2nrEjSYsXL1ZaWpoGDx4sPz8/jRo1Si+++GKDHwsAAPBOHg07AwcOtCY5n4/D4dCcOXM0Z86cC/YJCwtrlA8QBAAA1VPrOTsAAAC+gLADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABsjbADAABszevDTtu2beVwOM5ZUlNTJUkDBw48p+3+++/3cNUAAMBb+Hu6gF+yefNmnTlzxlovKCjQjTfeqN/+9rfWtkmTJmnOnDnWevPmzRu0RgAA4L28Puy0bt3abX3u3Llq3769rr/+emtb8+bNFRUVVe0xKyoqVFFRYa27XK5LL9QGCgsLa7VfeHi44uLi6rgaAADqhteHnZ86deqU3nzzTaWnp8vhcFjbFy9erDfffFNRUVEaOXKkZsyYcdGrO5mZmZo9e3ZDlOwTThw+KMmhsWPH1mr/wMDm2r69kMADAPBKPhV2VqxYofLyco0fP97adtddd6lNmzaKiYnR1q1bNX36dBUVFWn58uUXHCcjI0Pp6enWusvlUmxsbH2W7tUqjx+RZNTzrulqHZ9Qo31dJXu0acFslZWVEXYAAF7J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9cADD+jBBx/U66+/Lunf3+XFvqPS0lJFRES4tfv7+yssLKxOzldjORePPfaY7rzzTiUkJKhp06bq1auXpk6dqjFjxkjiXHiSN3331amlsbHF6yLqQ2pqqgoKCvTJJ594upRGae/evXrooYeUnZ2tZs2aebqcRq2qqkp9+/bVn/70J0lSr169VFBQoFdeeUXjxo3zcHWNyzvvvKPFixdryZIl6tKli/Lz8zV16lTFxMRwLoCL4MrOeaSlpWnlypX66KOPdMUVV1jbo6KidOrUKZWXl7v1379/v6Kioqw+P78j6Oz6L/UJDg5WYGCgwsPD1aRJk/P2OTuG3eXl5enAgQPq3bu3/P395e/vr/Xr1+vFF1+Uv7+/IiMjORcNJDo6Wp07d3bb1qlTJxUXF0v693d5se8oKipKBw4ccGs/ffq0Dh06VCfnq7Gci0ceecS6utOtWzfdfffdmjZtmnX1k3PhOd703VenlsaGsPMTxhilpaXp3Xff1dq1axUfH+/W3qdPHzVt2lQ5OTnWtqKiIhUXFyspKUmSlJSUpK+//trtH+js7GwFBwdbf2EkJSW5jXG2z9kxAgIC1KdPH7c+VVVVysnJsfrY3eDBg/X1118rPz/fWvr27asxY8ZYf+ZcNIxrr732nEcwfPvtt2rTpo0kKT4+XlFRUW7fkcvl0qZNm9zORXl5ufLy8qw+a9euVVVVlRITE60+GzZsUGVlpdUnOztbHTt2VMuWLa0+Fztfdnf8+HH5+bn/Z7tJkyaqqqqSxLnwJG/67qtTS6Pj6RnS3uSBBx4wISEhZt26daakpMRajh8/bvW5//77TVxcnFm7dq3ZsmWLSUpKMklJSVb72dudhwwZYvLz883q1atN69atz3u78yOPPGIKCwtNVlbWeW93djqdZtGiRWbbtm1m8uTJJjQ01O3Oosbmp3djGcO5aCiff/658ff3N08//bTZsWOHWbx4sWnevLl58803rT5z5841oaGh5r333jNbt241N99883lvue3Vq5fZtGmT+eSTT0yHDh3cbrktLy83kZGR5u677zYFBQVm6dKlpnnz5ufccuvv72+ee+45U1hYaGbOnGn7251/aty4cebyyy+3bj1fvny5CQ8PN48++qjVh3NRf44cOWK+/PJL8+WXXxpJZt68eebLL78033//vTHGu7776tTSmBB2fkLSeZeFCxdafU6cOGF+//vfm5YtW5rmzZub3/zmN6akpMRtnD179piUlBQTGBhowsPDzR/+8AdTWVnp1uejjz4yPXv2NAEBAaZdu3Zun3HWSy+9ZOLi4kxAQIDp37+/2bhxY30cts/4edjhXDScv/71r6Zr167G6XSahIQE8+qrr7q1V1VVmRkzZpjIyEjjdDrN4MGDTVFRkVufgwcPmtGjR5sWLVqY4OBgc88995gjR4649fnqq6/MgAEDjNPpNJdffrmZO3fuObW888475qqrrjIBAQGmS5cu5oMPPqj7A/ZSLpfLPPTQQyYuLs40a9bMtGvXzjz++ONutylzLurPRx99dN6/I8aNG2eM8a7vvjq1NCYOY37y6E0AAACbYc4OAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOAACwNcIOgEZt3bp1cjgc57xUFoB9EHYAeA2Hw3HRZdasWZ4uEYAP8vd0AQBwVklJifXnt99+W08++aTbG9dbtGhh/dkYozNnzsjfn/+MAbg4ruwA8BpRUVHWEhISIofDYa1v375dQUFB+tvf/qY+ffrI6XTqk08+0a5du3TzzTcrMjJSLVq0UL9+/bRmzRq3cSsqKjR9+nTFxsbK6XTqyiuv1GuvvXbeGo4fP66UlBRde+21Ki8v16lTp5SWlqbo6Gg1a9ZMbdq0UWZmZkN8HQDqCP9LBMCnPPbYY3ruuefUrl07tWzZUnv37tXw4cP19NNPy+l06o033tDIkSNVVFSkuLg4SdLvfvc75ebm6sUXX1SPHj20e/dulZWVnTN2eXm5RowYoRYtWig7O1vNmzfXc889p/fff1/vvPOO4uLitHfvXu3du7ehDxvAJSDsAPApc+bM0Y033mith4WFqUePHtb6U089pXfffVfvv/++0tLS9O233+qdd95Rdna2kpOTJUnt2rU7Z9zS0lLdcccd6tChg5YsWaKAgABJUnFxsTp06KABAwbI4XCoTZs29XyEAOoaP2MB8Cl9+/Z1Wz969KgefvhhderUSaGhoWrRooUKCwtVXFwsScrPz1eTJk10/fXXX3TcG2+8UVdeeaXefvttK+hI0vjx45Wfn6+OHTvqwQcf1N///ve6PygA9YqwA8CnXHbZZW7rDz/8sN5991396U9/0scff6z8/Hx169ZNp06dkiQFBgZWa9wRI0Zow4YN2rZtm9v23r17a/fu3Xrqqad04sQJ3X777brtttvq5mAANAh+xgLg0z799FONHz9ev/nNbyT9eKVnz549Vnu3bt1UVVWl9evXWz9jnc/cuXPVokULDR48WOvWrVPnzp2ttuDgYN1xxx264447dNttt2nYsGE6dOiQwsLC6u24ANQdwg4An9ahQwctX75cI0eOlMPh0IwZM1RVVWW1t23bVuPGjdOECROsCcrff/+9Dhw4oNtvv91trOeee05nzpzRDTfcoHXr1ikhIUHz5s1TdHS0evXqJT8/Py1btkxRUVEKDQ1t4CMFUFuEHQA+bd68eZowYYKuueYahYeHa/r06XK5XG595s+frz/+8Y/6/e9/r4MHDyouLk5//OMfzzve888/7xZ4goKC9Mwzz2jHjh1q0qSJ+vXrp1WrVsnPj1kAgK9wGGOMp4sAAACoL/yvCQAAsDXCDgAAsDXCDgAAsDXCDgAAsDXCDgAAsDXCDgAAsDXCDgAAsDXCDgAAsDXCDgAAsDXCDgAAsDXCDgAAsLX/D4RHhCGR9ogdAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sns.histplot(tracks.query('count >= 20000')['count'].fillna(0))\n", + "plt.xlabel('Tracks')" + ] + }, + { + "cell_type": "markdown", + "id": "d765519a-18dd-4d30-9e29-cc2d84cacd79", + "metadata": {}, + "source": [ + "Наиболее популярные треки" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "aecaf630-fde0-4860-b84a-42c933a9606e", + "metadata": {}, + "outputs": [], + "source": [ + "tracks[['track_id','count','name']].sort_values(by='count', ascending=False).head(10)\n", + "catalog_tracks = catalog_tracks.merge(tracks[['count','track_id']], left_on='id', right_on='track_id')\n", + "catalog_tracks.drop(columns=['track_id'], inplace=True)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "89335ddd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idnamecount_xcount_ycount
821553404Smells Like Teen Spirit111062111062111062
45166433311009Believer106921106921106921
24433178529Numb101924101924101924
47876535505245I Got Love994909949099490
77416965851540Юность866708667086670
34129524692821Way Down We Go862468624686246
44334732947997Shape of You858868588685886
65181051241318In The End852448524485244
83809795836Shape Of My Heart850428504285042
60754245499814Life847488474884748
\n", + "
" + ], + "text/plain": [ + " id name count_x count_y count\n", + "8215 53404 Smells Like Teen Spirit 111062 111062 111062\n", + "451664 33311009 Believer 106921 106921 106921\n", + "24433 178529 Numb 101924 101924 101924\n", + "478765 35505245 I Got Love 99490 99490 99490\n", + "774169 65851540 Юность 86670 86670 86670\n", + "341295 24692821 Way Down We Go 86246 86246 86246\n", + "443347 32947997 Shape of You 85886 85886 85886\n", + "651810 51241318 In The End 85244 85244 85244\n", + "83809 795836 Shape Of My Heart 85042 85042 85042\n", + "607542 45499814 Life 84748 84748 84748" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "catalog_tracks.sort_values('count', ascending=False).head(10)" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "da18b206", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_219026/1451962387.py:2: UserWarning: set_ticklabels() should only be used with a fixed number of ticks, i.e. after set_ticks() or using a FixedLocator.\n", + " p.set_xticklabels(\n" + ] + }, + { + "data": { + "text/plain": [ + "[Text(0, 0, 'Smells Like Teen Spirit'),\n", + " Text(1, 0, 'Believer'),\n", + " Text(2, 0, 'Numb'),\n", + " Text(3, 0, 'I Got Love'),\n", + " Text(4, 0, 'Юность'),\n", + " Text(5, 0, 'Way Down We Go'),\n", + " Text(6, 0, 'Shape of You'),\n", + " Text(7, 0, 'In The End'),\n", + " Text(8, 0, 'Shape Of My Heart'),\n", + " Text(9, 0, 'Life')]" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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REQqYRAjIiIiUgmDGBEREZFKGMSIiIiIVMIgRkRERKQSBjEiIiIilTCIEREREamEQYyIiIhIJQxiRERERCphECMiIiJSCYMYERERkUoYxIiIiIhUwiBGREREpBIGMSIiIiKVMIgRERERqYRBjIiIiEglDGJEREREKmEQIyIiIlIJgxgRERGRShjEiIiIiFTCIEZERESkEgYxIiIiIpUwiBERERGphEGMiIiISCUMYkREREQqYRAjIiIiUgmDGBEREZFKGMSIiIiIVMIgRkRERKQSBjEiIiIilTCIEREREamEQYyIiIhIJQxiRERERCphECMiIiJSCYMYERERkUoYxIiIiIhUwiBGREREpJI0C2K1atVCVFRUiuXR0dGoVatWWlWDiIiISDPSLIgdPHgQcXFxKZa/efMGR44cSatqEBEREWlGps/9AhcuXFD+f/nyZURERCi/JyYmYteuXciVK9fnrgYRERGR5nz2IFamTBnodDrodLpUL0FaWVlh9uzZn7saRERERJrz2YPYzZs3ISLInz8/Tp06hRw5cijrzM3NkTNnTpiamn7uahARERFpzmcPYq6urgAAvV7/uV+KiIiIKF357EEsuRs3buDAgQOIjIxMEcyCgoLSsipEREREqkuzILZo0SL06tULDg4OcHJygk6nU9bpdDoGMSIiIvripNnwFRMnTsSkSZMQERGB8+fP49y5c8rPb7/99klf6969e2jfvj2yZ88OKysrlCxZEmfOnFHWiwiCgoLg7OwMKysreHt748aNG0bP8fTpU7Rr1w52dnawt7eHv78/Xr58aVTmwoULqFatGiwtLZEnTx4EBwenqMuGDRtQtGhRWFpaomTJkti5c+cn3VYiIiJKv9IsiD179gwtWrRIk9epUqUKzMzM8Msvv+Dy5cuYPn06smbNqpQJDg7GrFmzMH/+fJw8eRI2Njbw8fHBmzdvlDLt2rVDeHg49uzZg+3bt+Pw4cPo3r27sj46Ohp16tSBq6srzp49i2nTpmHcuHFYuHChUubYsWNo06YN/P39ce7cOfj6+sLX1xeXLl367O8DERERaV+aBbEWLVpg9+7dn/11pk6dijx58mDZsmWoWLEi3NzcUKdOHRQoUABAUmtYaGgoRo8ejcaNG6NUqVJYuXIl7t+/j61btwIArly5gl27dmHx4sXw8PBA1apVMXv2bKxb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W7ZssLOzQ9++feHp6YlKlSoBAOrUqYPixYujQ4cOCA4ORkREBEaPHo0+ffrAwsICANCzZ098//33GDp0KLp06YL9+/fjxx9/xI4dO5TXDQwMhJ+fH9zd3VGxYkWEhoYiJiYGnTt3TqN3g4iIiLQswwWxDzFjxgyYmJigWbNmiI2NhY+PD+bOnausNzU1xfbt29GrVy94enrCxsYGfn5+GD9+vFLGzc0NO3bswMCBAzFz5kzkzp0bixcvho+Pj1KmVatWePToEYKCghAREYEyZcpg165dKTrwExER0ZfpiwhiBw8eNPrd0tISc+bMwZw5c975GFdXV+zcufO9z+vl5YVz5869t0xAQAACAgI+uK5ERET05chwfcSIiIiI0gsGMSIiIiKVMIgRERERqYRBjIiIiEglDGJEREREKmEQIyIiIlIJgxgRERGRShjEiIiIiFTCIEZERESkEgYxIiIiIpUwiBERERGphEGMiIiISCUMYkREREQqYRAjIiIiUgmDGBEREZFKGMSIiIiIVMIgRkRERKQSBjEiIiIilTCIEREREamEQYyIiIhIJQxiRERERCphECMiIiJSCYMYERERkUoYxIiIiIhUwiBGREREpBIGMSIiIiKVMIgRERERqYRBjIiIiEglDGJEREREKmEQIyIiIlIJgxgRERGRShjEiIiIiFTCIEZERESkEgYxIiIiIpUwiBERERGphEGMiIiISCUMYkREREQqYRAjIiIiUgmDGBEREZFKGMSIiIiIVMIgRkRERKQSBjEiIiIilTCIEREREamEQYyIiIhIJQxiRERERCphECMiIiJSCYMYERERkUoYxIiIiIhUwiBGREREpBIGMSIiIiKVMIgRERERqYRBjIiIiEglDGJEREREKslwQWzKlCmoUKECbG1tkTNnTvj6+uLatWtGZd68eYM+ffoge/bsyJw5M5o1a4aHDx8albl9+zYaNGgAa2tr5MyZE0OGDEFCQoJRmYMHD6JcuXKwsLBAwYIFsXz58hT1mTNnDvLlywdLS0t4eHjg1KlTn3ybiYiIKH3KcEHs0KFD6NOnD06cOIE9e/YgPj4ederUQUxMjFJm4MCB2LZtGzZs2IBDhw7h/v37aNq0qbI+MTERDRo0QFxcHI4dO4YVK1Zg+fLlCAoKUsrcvHkTDRo0QM2aNXH+/HkMGDAAXbt2xa+//qqU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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "p = sns.barplot(catalog_tracks.sort_values('count', ascending=False).head(10),x='name', y='count')\n", + "p.set_xticklabels(\n", + " p.get_xticklabels(), \n", + " rotation=45, \n", + " horizontalalignment='right'\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "3b0bff35", + "metadata": {}, + "outputs": [], + "source": [ + "catalog_tracks.to_parquet('../data/catalog_tracks.parquet')" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "16e6863f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([ 148201., 192358., 172134., 268240., 311700., 262459.,\n", + " 387518., 431634., 351026., 505503., 549948., 442020.,\n", + " 624725., 498827., 702108., 747713., 592265., 826998.,\n", + " 870964., 683504., 950577., 996923., 777570., 1074763.,\n", + " 839507., 1157846., 1207839., 936417., 1289020., 1340665.,\n", + " 1036658., 1425276., 1475006., 1139436., 1561318., 1614665.,\n", + " 1241160., 1698673., 1311020., 1790139., 1842043., 1414538.,\n", + " 1933496., 1989027., 1527155., 2081109., 2138155., 1637404.,\n", + " 2233059., 1708755., 2324778., 2387956., 1824282., 2481403.,\n", + " 2542194., 1945960., 2644429., 2704845., 2068454., 2809762.,\n", + " 2873336., 2195813., 2983642., 2278917., 3093772., 3158010.,\n", + " 2408102., 3269353., 3334345., 2545551., 3454690., 3520903.,\n", + " 2685108., 3640694., 2771240., 3751363., 3817447., 2906555.,\n", + " 3933520., 3998960., 3035922., 4105882., 4164647., 3159655.,\n", + " 4261388., 4319254., 3273782., 4400208., 3328987., 4468841.,\n", + " 4495965., 3374974., 4495049., 4464815., 3306909., 4312803.,\n", + " 4140545., 2908108., 3418731., 1983502.]),\n", + " array([18993. , 18996.64, 19000.28, 19003.92, 19007.56, 19011.2 ,\n", + " 19014.84, 19018.48, 19022.12, 19025.76, 19029.4 , 19033.04,\n", + " 19036.68, 19040.32, 19043.96, 19047.6 , 19051.24, 19054.88,\n", + " 19058.52, 19062.16, 19065.8 , 19069.44, 19073.08, 19076.72,\n", + " 19080.36, 19084. , 19087.64, 19091.28, 19094.92, 19098.56,\n", + " 19102.2 , 19105.84, 19109.48, 19113.12, 19116.76, 19120.4 ,\n", + " 19124.04, 19127.68, 19131.32, 19134.96, 19138.6 , 19142.24,\n", + " 19145.88, 19149.52, 19153.16, 19156.8 , 19160.44, 19164.08,\n", + " 19167.72, 19171.36, 19175. , 19178.64, 19182.28, 19185.92,\n", + " 19189.56, 19193.2 , 19196.84, 19200.48, 19204.12, 19207.76,\n", + " 19211.4 , 19215.04, 19218.68, 19222.32, 19225.96, 19229.6 ,\n", + " 19233.24, 19236.88, 19240.52, 19244.16, 19247.8 , 19251.44,\n", + " 19255.08, 19258.72, 19262.36, 19266. , 19269.64, 19273.28,\n", + " 19276.92, 19280.56, 19284.2 , 19287.84, 19291.48, 19295.12,\n", + " 19298.76, 19302.4 , 19306.04, 19309.68, 19313.32, 19316.96,\n", + " 19320.6 , 19324.24, 19327.88, 19331.52, 19335.16, 19338.8 ,\n", + " 19342.44, 19346.08, 19349.72, 19353.36, 19357. ]),\n", + " )" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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TamxsTEdeuihad26dT2Ob9GiRam+vj7dc8896ec//3l63/vel2bMmJF+//vfdxxz5ZVXpsWLF6cFCxak+vr60hVnP9VQp8WLF6c1a9akZ555Jv3sZz9Ls2bNSrNmzSphlV5VDbU66qij0lVXXZW2bNnS8dp//KVU6fUqFAqd6rRly5Z06aWXphkzZqT29vYSV6vy6/Xb3/42HX744Wn+/PnpiSeeSA8//HCaPXt2+uAHP1jiSr1qsNdr2bJlqampKX31q19NEZFaW1sPOub6669Pzc3Nqbm5OUVE+t///d9+16U7ueq1bt26tHz58rRhw4b09NNPp2984xtp9OjR6dZbb+1xfLl+E8saPA60bdu2FBHpxz/+cUoppR07dqRDDz003XXXXR3HPP744yki0po1a7o9zyc/+ck0d+7cHq91/PHHp6ampm7/vb29PR1xxBHpmmuu6fhsx44dqba2Nt1xxx0HHb906dKyBY8DVXKd9rn33ntTTU1Neumll3q8fn9VYq2OOuqodP3117/eVyuLSqzX/l566aU0YcKEdNVVV/V47VKptHrdeuutaeLEiWnv3r0dxzz66KMpItKTTz7Z85ctgcFUr/09/fTT3QaPfX70ox+VPXgcKGe93v/+96eLLrqo23/P+ZuYdY3Hzp07IyJi3LhxERHR0tISL7/8cpx55pkdxxx77LExbdq0WLNmTY/n2XeOrrS3t8euXbt6PObpp5+OrVu3drp2fX19nHbaaT1eO4dKr9P27dvj9ttvj9mzZ8ehhx7a7blLoVJrtWjRohg/fnycfPLJcc0118Qrr7zS8xctkUqt1z7f/e5344UXXsi2U3Kl1atQKMSIESNi2LDX/qd91KhREfFq+73cBlO9KkGuerW2tsbq1avjXe96V7fH5PxNLPvTafdpb2+PK664Is4444w48cQTIyJi69atMWLEiIPmiSZNmhRbt27t8jyrV6+Ob33rW3H//fd3e61rr702du/eHR/5yEe6PWbf+Q/ccbWna+dQyXX67Gc/GzfffHO8+OKLcfrpp8d9993X7XlLoVJrddlll8Xb3va2GDduXKxevToaGxtjy5YtsXjx4h6/b39Var32t2TJkjjnnHNiypQp3Z63VCqxXu9+97tjwYIFcc0118Tll18ee/bsic997nMREbFly5aev3A/DbZ6DXY56jVlypR4/vnn45VXXol//Md/jEsvvbTb8eT8TczW8WhoaIgNGzbEnXfe2edzbNiwIS644IJYuHBhnH322V0es3z58mhqaopvf/vbMXHixIiIuP3222PMmDEdr5/85Cd9HkO5VXKdPvOZz0Rra2s8+OCDMXz48Lj44osjlXFj3Eqt1YIFC2LOnDkxc+bMmD9/flx33XVx0003RaFQ6PP36I1Krdc+v/nNb+KBBx6Iv/qrv+rz+ItRifU64YQT4t/+7d/iuuuui9GjR8cRRxwRM2bMiEmTJnXqgpRDJdZrIOWo109+8pNYu3ZtfOUrX4kbbrgh7rjjjogYBPXq0wRNkRoaGtKUKVPSr371q06fr1ixoss5tWnTpqXFixd3+uwXv/hFmjhxYvr7v//7bq9zxx13pFGjRqX77ruv0+dtbW3pySef7Hi9+OKL6amnnupyzu+d73xnuuyyyw46d441HtVQp302b96cIiKtXr26h2/cd9VUqw0bNqSISE888UQP37h/qqFeV111VZowYULZ1w2lVB312rp1a9q1a1favXt3GjZsWPr2t7/di2/eN4OxXvsbbGs8ctVrf//0T/+UjjnmmJTSwP8mljV4tLe3p4aGhjR58uS0adOmg/5930Kaf//3f+/47IknnjhoIc2GDRvSxIkT02c+85lur7V8+fI0cuTIdM899/R6bEcccUS69tprOz7buXPngCwuraY67fPss8+miEg/+tGPenWd3qrGWn3zm99Mw4YNS9u3b+/VdYpRLfVqb29PM2bMSH/7t3/bq3P3VbXUa39LlixJo0ePLssP6mCu1/4GS/DIWa8DNTU1paOOOqrHseX6TSxr8PjEJz6R6uvr08qVKzvdDrd/Gp0/f36aNm1a+uEPf5jWrl170G2Yjz32WJowYUK66KKLOp1j27ZtHcfcfvvt6ZBDDklf+tKXOh2zY8eOHse3aNGidNhhh6V77703Pfroo+mCCy446NahZ599NrW2tqampqY0ZsyY1NramlpbW9OuXbvU6f/r9NBDD6Wbbroptba2pmeeeSatWLEizZ49Ox199NHpD3/4Q8nqVA21Wr16dbr++uvT+vXr01NPPZW++c1vpgkTJqSLL764pHXap9Lrtc8PfvCDFBHp8ccfL1FlulYN9brppptSS0tL2rhxY7r55pvTqFGj0o033ljCKr1msNfrhRdeSK2tren+++9PEZHuvPPO1NramrZs2dJxzJYtW1Jra2vHLberVq1Kra2t6YUXXihhpV6Vq14333xz+u53v5s2bdqUNm3alL72ta+lsWPHps9//vM9ji/Xb2JZg0dEdPlaunRpxzG///3v0yc/+cl0+OGHp9GjR6f3v//9nf6jWLhwYZfn2D+5vetd7+rymHnz5vU4vvb29vQP//APadKkSam2tja95z3vSRs3bux0zLx587o8dyn/n3yl1+nRRx9Nc+fOTePGjUu1tbVp+vTpaf78+ek3v/lNqUrUodJr1dLSkk477bRUX1+fRo4cmY477rj0L//yLyUPaPtUer32+ehHP9ppL4NyqYZ6fexjH0vjxo1LI0aMSDNnzkzLli0rRWm6NNjrtXTp0i7/buHCha97/f2/Q6nkqtcXv/jFdMIJJ6TRo0enurq6dPLJJ6cvf/nLnW6z7kqu38Sa/y8GAEDZeVYLAJCN4AEAZCN4AADZCB4AQDaCBwCQjeABAGQjeAAA2QgeAEA2ggcAkI3gAQBkI3gAANkIHgBANv8HjikQDoSO+1sAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.hist(interactions['started_at'], bins=100)" + ] + }, + { + "cell_type": "markdown", + "id": "b1c32a5a-d3be-4f96-8dd9-f7860951020c", + "metadata": {}, + "source": [ + "Наиболее популярные жанры" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "1bc50491-9235-4d3c-a6c2-297f7c05a959", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idnamecount
1111pop166109
7575rap128206
102102allrock118461
6868electronics106478
33rusrap65958
4444foreignrap59772
1414rock55148
1616dance51595
2020ruspop46706
1313alternative42894
\n", + "
" + ], + "text/plain": [ + " id name count\n", + "11 11 pop 166109\n", + "75 75 rap 128206\n", + "102 102 allrock 118461\n", + "68 68 electronics 106478\n", + "3 3 rusrap 65958\n", + "44 44 foreignrap 59772\n", + "14 14 rock 55148\n", + "16 16 dance 51595\n", + "20 20 ruspop 46706\n", + "13 13 alternative 42894" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "catalog_genres = pd.read_parquet('../data/catalog_genres.parquet')\n", + "catalog_genres.sort_values('count', ascending=False).head(10)" + ] + }, + { + "cell_type": "markdown", + "id": "0d850a07-ef1e-462f-891a-1cf89f2e24ef", + "metadata": {}, + "source": [ + "# Преобразование данных" + ] + }, + { + "cell_type": "markdown", + "id": "fabcf8d2-1192-4df5-b20b-fbb84689f57a", + "metadata": {}, + "source": [ + "Преобразуем данные в формат, более пригодный для дальнейшего использования в расчётах рекомендаций." + ] + }, + { + "cell_type": "markdown", + "id": "72ecbbed-c560-44d9-9c14-86c7dc76f399", + "metadata": {}, + "source": [ + "# Очистка памяти" + ] + }, + { + "cell_type": "markdown", + "id": "b5358ede-ba6e-4c4f-bd73-5b9344f0ba79", + "metadata": {}, + "source": [ + "Здесь, может понадобится очистка памяти для высвобождения ресурсов для выполнения кода ниже. \n", + "\n", + "Приведите соответствующие код, комментарии, например:\n", + "- код для удаление более ненужных переменных,\n", + "- комментарий, что следует перезапустить kernel, выполнить такие-то начальные секции и продолжить с этапа 3." + ] + }, + { + "cell_type": "markdown", + "id": "a0c64390", + "metadata": {}, + "source": [ + "Следует перезапустить kernel, выполнять действия начиная с 3го этапа" + ] + }, + { + "cell_type": "markdown", + "id": "5b4e87cc", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "id": "075fd983", + "metadata": {}, + "source": [ + "### ВЫВОДЫ:\n", + "\n", + "Больше всего треков, которые слушают менее 20 раз. Вероятно, их можно убрать из выборки для существенного сокращения размера (делать мы это конечно же не будем)\n", + "\n", + "Треки из топ-10 находятся в моем сердечке :)\n", + "\n", + "Топ жанров возглавляет поп, рэп, рок и электроника, остальные идут с большим отрывом в полтора-два раза по количеству треков" + ] + }, + { + "cell_type": "markdown", + "id": "708503df-ee89-4cf3-8489-093dc478e2a8", + "metadata": {}, + "source": [ + "# === ЭТАП 3 ===" + ] + }, + { + "cell_type": "markdown", + "id": "fd77de22-e10f-4b42-85c1-8fb6f805fe68", + "metadata": {}, + "source": [ + "# Загрузка данных" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b255df91", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "import random\n", + "import scipy\n", + "import sklearn.preprocessing\n", + "from catboost import CatBoostClassifier, Pool" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f19fc8a5-bd2c-40d7-864a-ee75aca6d512", + "metadata": {}, + "outputs": [], + "source": [ + "items = pd.read_parquet('../data/items.parquet')\n", + "interactions = pd.read_parquet('../data/events.parquet')\n", + "#catalog = pd.read_parquet('data/catalog_names.parquet')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "9b4ef95a", + "metadata": {}, + "outputs": [], + "source": [ + "catalog_genres = pd.read_parquet('../data/catalog_genres.parquet')\n", + "catalog_albums = pd.read_parquet('../data/catalog_albums.parquet')\n", + "catalog_artists = pd.read_parquet('../data/catalog_artists.parquet')\n", + "catalog_tracks = pd.read_parquet('../data/catalog_tracks.parquet')" + ] + }, + { + "cell_type": "markdown", + "id": "a694c023-6477-490b-939d-1cfa6f5f1b72", + "metadata": {}, + "source": [ + "# Разбиение данных" + ] + }, + { + "cell_type": "markdown", + "id": "fbd5f6e0-54e7-4428-8678-eabce505d82c", + "metadata": {}, + "source": [ + "Разбиваем данные на тренировочную, тестовую выборки." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "00c2dfa5-d8a2-47d1-922e-6eefee2c62d1", + "metadata": {}, + "outputs": [], + "source": [ + "train_test_global_time_split_date = pd.to_datetime(\"2022-12-16\")\n", + "interactions_train = interactions.loc[interactions[\"started_at\"] < train_test_global_time_split_date]\n", + "interactions_test = interactions.loc[interactions[\"started_at\"] >= train_test_global_time_split_date]\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6bb261d5", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "# В учебных целях берем 25% от всего, потому что иначе все выполняется бессовестно долго. \n", + "interactions_train = interactions_train.sample(frac=0.25, random_state = 42)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "781f047e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(50299870, 4)\n", + "(13514936, 4)\n" + ] + } + ], + "source": [ + "print(interactions_train.shape)\n", + "print(interactions_test.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e73cc82", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(13438773, 4)\n" + ] + } + ], + "source": [ + "# Drop items in test that are not in train\n", + "interactions_test = interactions_test.loc[~interactions_test['track_id'].isin(items_diff)]\n", + "print(interactions_test.shape)" + ] + }, + { + "cell_type": "markdown", + "id": "9131c7e6-8852-4556-b510-51f7253cc299", + "metadata": {}, + "source": [ + "# Топ популярных" + ] + }, + { + "cell_type": "markdown", + "id": "dd70d43a-88cc-4719-b291-feaed7136f30", + "metadata": {}, + "source": [ + "Рассчитаем рекомендации как топ популярных." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "ee45e200-b7d6-4f56-9077-aad431689b96", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
track_idcount
79205340427471
2349117852925393
4270953331100925232
4526923550524523657
3230472469282121052
.........
7479586856271111949
7936107564296111884
7947837594493411797
83945773011691
7466486834838911651
\n", + "

100 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " track_id count\n", + "7920 53404 27471\n", + "23491 178529 25393\n", + "427095 33311009 25232\n", + "452692 35505245 23657\n", + "323047 24692821 21052\n", + "... ... ...\n", + "747958 68562711 11949\n", + "793610 75642961 11884\n", + "794783 75944934 11797\n", + "8394 57730 11691\n", + "746648 68348389 11651\n", + "\n", + "[100 rows x 2 columns]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item_popularity = interactions_train.groupby('track_id')[['user_id']].count().reset_index().sort_values(by='user_id', ascending=False)[:100]\n", + "item_popularity.columns = ['track_id', 'count']\n", + "item_popularity" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "e39319f4", + "metadata": {}, + "outputs": [], + "source": [ + "item_popularity['rank'] = item_popularity['count'].rank(ascending=False)\n", + "item_popularity['rank'] = item_popularity['rank'].astype(int)\n", + "item_popularity = item_popularity.rename(columns= {'track_id': 'item_id'})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bacefc16", + "metadata": {}, + "outputs": [], + "source": [ + "item_popularity.to_parquet('../recommendations/top_popular.parquet')\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "4cc77f15", + "metadata": {}, + "outputs": [], + "source": [ + "item_popularity = pd.read_parquet('../recommendations/top_popular.parquet')" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "705c40a6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
item_idcountrank
792053404274711
23491178529253932
42709533311009252323
45269235505245236574
32304724692821210525
............
747958685627111194996
793610756429611188497
794783759449341179798
8394577301169199
7466486834838911651100
\n", + "

100 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " item_id count rank\n", + "7920 53404 27471 1\n", + "23491 178529 25393 2\n", + "427095 33311009 25232 3\n", + "452692 35505245 23657 4\n", + "323047 24692821 21052 5\n", + "... ... ... ...\n", + "747958 68562711 11949 96\n", + "793610 75642961 11884 97\n", + "794783 75944934 11797 98\n", + "8394 57730 11691 99\n", + "746648 68348389 11651 100\n", + "\n", + "[100 rows x 3 columns]" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item_popularity" + ] + }, + { + "cell_type": "markdown", + "id": "2ad231f2-6158-421a-b7fa-01d8bc3ed572", + "metadata": {}, + "source": [ + "# Персональные" + ] + }, + { + "cell_type": "markdown", + "id": "86159460-cd9d-4b63-8248-604ea3c9aebf", + "metadata": {}, + "source": [ + "Рассчитаем персональные рекомендации." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "25f19ff2", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_155402/3672207653.py:2: SettingWithCopyWarning: \n", + "A value is trying to be set on a copy of a slice from a DataFrame.\n", + "Try using .loc[row_indexer,col_indexer] = value instead\n", + "\n", + "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", + " interactions_test['target'] = 1 # Факт прослушивания\n" + ] + } + ], + "source": [ + "interactions_train['target'] = 1 # Факт прослушивания\n", + "interactions_test['target'] = 1 # Факт прослушивания" + ] + }, + { + "cell_type": "markdown", + "id": "af7a2e3c", + "metadata": {}, + "source": [ + "## ALS" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "51edbd36", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_155402/3892860961.py:7: SettingWithCopyWarning: \n", + "A value is trying to be set on a copy of a slice from a DataFrame.\n", + "Try using .loc[row_indexer,col_indexer] = value instead\n", + "\n", + "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", + " interactions_test[\"user_id_enc\"] = user_encoder.transform(interactions_test[\"user_id\"])\n", + "/tmp/ipykernel_155402/3892860961.py:15: SettingWithCopyWarning: \n", + "A value is trying to be set on a copy of a slice from a DataFrame.\n", + "Try using .loc[row_indexer,col_indexer] = value instead\n", + "\n", + "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", + " interactions_test[\"track_id_enc\"] = item_encoder.transform(interactions_test[\"track_id\"])\n" + ] + } + ], + "source": [ + "user_encoder = sklearn.preprocessing.LabelEncoder()\n", + "\n", + "# перекодируем идентификаторы пользователей: \n", + "# из имеющихся в последовательность 0, 1, 2, ...\n", + "user_encoder.fit(interactions[\"user_id\"])\n", + "interactions_train[\"user_id_enc\"] = user_encoder.transform(interactions_train[\"user_id\"])\n", + "interactions_test[\"user_id_enc\"] = user_encoder.transform(interactions_test[\"user_id\"])\n", + "\n", + "# перекодируем идентификаторы объектов: \n", + "# из имеющихся в последовательность 0, 1, 2, ...\n", + "item_encoder = sklearn.preprocessing.LabelEncoder()\n", + "item_encoder.fit(items[\"track_id\"])\n", + "items[\"track_id_enc\"] = item_encoder.transform(items[\"track_id\"])\n", + "interactions_train[\"track_id_enc\"] = item_encoder.transform(interactions_train[\"track_id\"])\n", + "interactions_test[\"track_id_enc\"] = item_encoder.transform(interactions_test[\"track_id\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "d2dd9c63", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_idtrack_idtrack_seqstarted_attargetuser_id_enctrack_id_enc
2731311288658912712742022-12-0311305663774484
2411182663351129572422022-10-1611177607473510
134785243890849981352022-12-141781869899346
1417291443117152022-06-0811721806832
2057227520003416212022-12-081569812285162
........................
621105625921895186222022-03-1511051720111587
382278471233440683832022-04-021277280321471
136682319593737151372022-11-111679358721033
5955372954859162022-08-051951241196517
896689124277598858972022-06-251686139376419
\n", + "

50299870 rows × 7 columns

\n", + "
" + ], + "text/plain": [ + " user_id track_id track_seq started_at target user_id_enc \\\n", + "273 1311288 65891271 274 2022-12-03 1 1305663 \n", + "241 1182663 35112957 242 2022-10-16 1 1177607 \n", + "134 785243 89084998 135 2022-12-14 1 781869 \n", + "14 172914 43117 15 2022-06-08 1 172180 \n", + "20 572275 20003416 21 2022-12-08 1 569812 \n", + ".. ... ... ... ... ... ... \n", + "621 1056259 2189518 622 2022-03-15 1 1051720 \n", + "382 278471 23344068 383 2022-04-02 1 277280 \n", + "136 682319 59373715 137 2022-11-11 1 679358 \n", + "5 955372 9548591 6 2022-08-05 1 951241 \n", + "896 689124 27759885 897 2022-06-25 1 686139 \n", + "\n", + " track_id_enc \n", + "273 774484 \n", + "241 473510 \n", + "134 899346 \n", + "14 6832 \n", + "20 285162 \n", + ".. ... \n", + "621 111587 \n", + "382 321471 \n", + "136 721033 \n", + "5 196517 \n", + "896 376419 \n", + "\n", + "[50299870 rows x 7 columns]" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interactions_train\n" + ] + }, + { + "cell_type": "markdown", + "id": "aaf9dca7", + "metadata": {}, + "source": [ + "CSR matrix\n", + "\n", + "https://matteding.github.io/images/csr.gif" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "75313c81", + "metadata": {}, + "outputs": [], + "source": [ + "user_item_matrix_train = scipy.sparse.csr_matrix((\n", + " interactions_train[\"target\"],\n", + " (interactions_train['user_id_enc'], interactions_train['track_id_enc'])),\n", + " dtype=np.int8) " + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "ac801f85", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/andrey/work/institute/MLE/assets/recsys/.venv_recsys/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n", + "/home/andrey/work/institute/MLE/assets/recsys/.venv_recsys/lib/python3.10/site-packages/implicit/cpu/als.py:95: RuntimeWarning: OpenBLAS is configured to use 12 threads. It is highly recommended to disable its internal threadpool by setting the environment variable 'OPENBLAS_NUM_THREADS=1' or by calling 'threadpoolctl.threadpool_limits(1, \"blas\")'. Having OpenBLAS use a threadpool can lead to severe performance issues here.\n", + " check_blas_config()\n", + "100%|██████████| 30/30 [02:48<00:00, 5.61s/it]\n" + ] + } + ], + "source": [ + "from implicit.als import AlternatingLeastSquares\n", + "\n", + "als_model = AlternatingLeastSquares(factors=30, iterations=30, regularization=0.05, random_state=0)\n", + "als_model.fit(user_item_matrix_train) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98ce57e3", + "metadata": {}, + "outputs": [], + "source": [ + "def get_recommendations_als(user_item_matrix, model, user_id, user_encoder, item_encoder, include_seen=False, n=5):\n", + " \"\"\"\n", + " Возвращает отранжированные рекомендации для заданного пользователя\n", + " \"\"\"\n", + " user_id_enc = user_encoder.transform([user_id])[0]\n", + " recommendations = model.recommend(\n", + " user_id_enc, \n", + " user_item_matrix[user_id_enc], \n", + " filter_already_liked_items = not include_seen,\n", + " N=n)\n", + " recommendations = pd.DataFrame({\"item_id_enc\": recommendations[0], \"score\": recommendations[1]})\n", + " recommendations[\"item_id\"] = item_encoder.inverse_transform(recommendations[\"item_id_enc\"])\n", + " \n", + " return recommendations" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e39e5237", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "887bf440", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "user_id: 39211\n", + "История (последние события)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_idtrack_idtrack_seqstarted_attargetuser_id_enctrack_id_encnamegenres
5639211339774391432022-11-14139046459580Feel It Still[70]
573921115410295892022-11-03139046229667Танцуй, пока молодая[11, 20]
583921114293633852022-11-03139046219139Trebles 2013[16]
5939211371003701562022-11-16139046502447Белая ночь[11, 20]
6039211320430931332022-11-11139046428825Mr. Vain Recall[16]
6139211878664372022-10-2413904689718Y Si No Existieras (Y Si No Has De Volver) (Et...[325]
6239211674649592822022-12-11139046784874Побуяним[11, 20]
6339211219986921082022-11-07139046313896Don't Think I Will Forgive You[68]
6439211627938812672022-12-07139046748758Kanyelele[68]
65392111710808472022-10-26139046103882Bohemian Rhapsody[14, 17, 25, 102]
\n", + "
" + ], + "text/plain": [ + " user_id track_id track_seq started_at target user_id_enc \\\n", + "56 39211 33977439 143 2022-11-14 1 39046 \n", + "57 39211 15410295 89 2022-11-03 1 39046 \n", + "58 39211 14293633 85 2022-11-03 1 39046 \n", + "59 39211 37100370 156 2022-11-16 1 39046 \n", + "60 39211 32043093 133 2022-11-11 1 39046 \n", + "61 39211 878664 37 2022-10-24 1 39046 \n", + "62 39211 67464959 282 2022-12-11 1 39046 \n", + "63 39211 21998692 108 2022-11-07 1 39046 \n", + "64 39211 62793881 267 2022-12-07 1 39046 \n", + "65 39211 1710808 47 2022-10-26 1 39046 \n", + "\n", + " track_id_enc name \\\n", + "56 459580 Feel It Still \n", + "57 229667 Танцуй, пока молодая \n", + "58 219139 Trebles 2013 \n", + "59 502447 Белая ночь \n", + "60 428825 Mr. Vain Recall \n", + "61 89718 Y Si No Existieras (Y Si No Has De Volver) (Et... \n", + "62 784874 Побуяним \n", + "63 313896 Don't Think I Will Forgive You \n", + "64 748758 Kanyelele \n", + "65 103882 Bohemian Rhapsody \n", + "\n", + " genres \n", + "56 [70] \n", + "57 [11, 20] \n", + "58 [16] \n", + "59 [11, 20] \n", + "60 [16] \n", + "61 [325] \n", + "62 [11, 20] \n", + "63 [68] \n", + "64 [68] \n", + "65 [14, 17, 25, 102] " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "user_id = interactions_train['user_id'].sample().iat[0]\n", + "\n", + "print(f\"user_id: {user_id}\")\n", + "\n", + "print(\"История (последние события)\")\n", + "user_history = (\n", + " interactions_train\n", + " .query(\"user_id == @user_id\")\n", + " .merge(items.set_index(\"track_id\")[[\"name\", \"genres\"]], on=\"track_id\")\n", + ")\n", + "user_history_to_print = user_history.tail(10)\n", + "display(user_history_to_print)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "13e662c5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Рекомендации\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
item_id_encscoreitem_idnamegenres
07741690.51117865851540Юность[11, 20]
15348470.25497539257277In My Mind[16]
24433470.23857832947997Shape of You[11]
36634910.18402652380688Любимка[11, 20]
47282750.17999260292250Blinding Lights[74]
\n", + "
" + ], + "text/plain": [ + " item_id_enc score item_id name genres\n", + "0 774169 0.511178 65851540 Юность [11, 20]\n", + "1 534847 0.254975 39257277 In My Mind [16]\n", + "2 443347 0.238578 32947997 Shape of You [11]\n", + "3 663491 0.184026 52380688 Любимка [11, 20]\n", + "4 728275 0.179992 60292250 Blinding Lights [74]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "print(\"Рекомендации\")\n", + "user_recommendations_als = get_recommendations_als(user_item_matrix_train, als_model, user_id, user_encoder, item_encoder, include_seen=True)\n", + "user_recommendations_als = user_recommendations_als.merge(items.set_index(\"track_id\")[[\"name\", \"genres\"]], left_on=\"item_id\", right_on=\"track_id\")\n", + "\n", + "display(user_recommendations_als)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9ab3670e", + "metadata": {}, + "outputs": [], + "source": [ + "# !!! 45 minutes \n", + "# чтобы не ждать - переместиться на строку считывания parquet\n", + "\n", + "# получаем список всех возможных user_id (перекодированных)\n", + "user_ids_encoded = range(len(user_encoder.classes_)-1)\n", + "\n", + "# получаем рекомендации для всех по+льзователей \n", + "\n", + "\n", + "als_recommendations = als_model.recommend(\n", + " user_ids_encoded, \n", + " user_item_matrix_train[user_ids_encoded], \n", + " filter_already_liked_items=False, N=30)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c32a23c0", + "metadata": {}, + "outputs": [], + "source": [ + "# преобразуем полученные рекомендации в табличный формат\n", + "item_ids_enc = als_recommendations[0]\n", + "als_scores = als_recommendations[1]\n", + "\n", + "als_recommendations = pd.DataFrame({\n", + " \"user_id_enc\": user_ids_encoded,\n", + " \"item_id_enc\": item_ids_enc.tolist(), \n", + " \"score\": als_scores.tolist()})\n", + "als_recommendations = als_recommendations.explode([\"item_id_enc\", \"score\"], ignore_index=True)\n", + "\n", + "# приводим типы данных\n", + "als_recommendations[\"item_id_enc\"] = als_recommendations[\"item_id_enc\"].astype(\"int\")\n", + "als_recommendations[\"score\"] = als_recommendations[\"score\"].astype(\"float\")\n", + "\n", + "# получаем изначальные идентификаторы\n", + "als_recommendations[\"user_id\"] = user_encoder.inverse_transform(als_recommendations[\"user_id_enc\"])\n", + "als_recommendations[\"item_id\"] = item_encoder.inverse_transform(als_recommendations[\"item_id_enc\"])\n", + "als_recommendations = als_recommendations.drop(columns=[\"user_id_enc\", \"item_id_enc\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "625dd757", + "metadata": {}, + "outputs": [], + "source": [ + "als_recommendations = als_recommendations[[\"user_id\", \"item_id\", \"score\"]]\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15923526", + "metadata": {}, + "outputs": [], + "source": [ + "als_recommendations = als_recommendations.sort_values(by = \"score\", ascending=False)\n", + "als_recommendations.to_parquet(\"recsys/recomendations/personal_als.parquet\") " + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "ff125847", + "metadata": {}, + "outputs": [], + "source": [ + "als_recommendations = pd.read_parquet(\"../recommendations/personal_als.parquet\")" + ] + }, + { + "cell_type": "markdown", + "id": "5f09dc7e-7c91-4355-860a-b9cfb9f33f15", + "metadata": {}, + "source": [ + "# Похожие" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dfc5d8ba", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "# !!!! 40 minutes\n", + "# чтобы не ждать - переместиться на строку считывания parquet\n", + "\n", + "# получим энкодированные идентификаторы всех объектов, известных нам из events_train\n", + "train_item_ids_enc = interactions_train['track_id_enc'].unique()\n", + "\n", + "max_similar_items = 10\n", + "\n", + "# получаем списки похожих объектов, используя ранее полученную ALS-модель\n", + "# метод similar_items возвращает и сам объект, как наиболее похожий\n", + "# этот объект мы позже отфильтруем, но сейчас запросим на 1 больше\n", + "\n", + "\n", + "similar_items = als_model.similar_items(train_item_ids_enc, N=max_similar_items+1)\n", + "\n", + "# преобразуем полученные списки в табличный формат\n", + "sim_item_item_ids_enc = similar_items[0]\n", + "sim_item_scores = similar_items[1]\n", + "\n", + "similar_items = pd.DataFrame({\n", + " \"track_id_enc\": train_item_ids_enc,\n", + " \"sim_item_id_enc\": sim_item_item_ids_enc.tolist(), \n", + " \"score\": sim_item_scores.tolist()})\n", + "similar_items = similar_items.sort_values('score')\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62881cf9", + "metadata": {}, + "outputs": [], + "source": [ + "# Надо бы сделать обратную трансформацию, но зависает спустя несколько часов работы.... Пока будем считать, что энкодед это истиные идентификаторы \n", + "# similar_items[\"item_id\"] = similar_items['track_id_enc'].apply(lambda x: item_encoder.inverse_transform([x])[0])\n", + "# similar_items[\"sim_item_id\"] = similar_items['sim_item_id_enc'].apply(lambda x: item_encoder.inverse_transform(x[1:]))\n" + ] + }, + { + "cell_type": "markdown", + "id": "1dfcb683-b440-40a8-9975-894156a53872", + "metadata": {}, + "source": [ + "Рассчитаем похожие, они позже пригодятся для онлайн-рекомендаций." + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "a75d07ee-4b12-4ce5-aa85-e45cb7a7a4f0", + "metadata": {}, + "outputs": [], + "source": [ + "similar_items.rename(columns={'track_id_enc':'item_id', 'sim_item_id_enc':'sim_item_id'}, inplace=True)\n", + "similar_items.to_parquet(\"../recommendations/similar_items.parquet\")" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "ce370904-4c49-4152-8706-416074ea9b9a", + "metadata": {}, + "outputs": [], + "source": [ + "similar_items = pd.read_parquet(\"../recommendations/similar_items.parquet\")" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "be54154c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
item_idsim_item_idscore
787744440450[440450, 794964, 662906, 789388][0.9999995231628418, 0.8756284713745117, 0.841...
89069478024[478024, 542933, 200580, 501613][0.9999995231628418, 0.950843870639801, 0.9462...
142892170948[170948, 357991, 184016, 56267][0.9999995231628418, 0.9587958455085754, 0.956...
636868178504[178504, 92743, 206733, 337648][0.9999995231628418, 0.9872869849205017, 0.986...
694565429070[429070, 428326, 289784, 428323][0.9999995827674866, 0.8366211652755737, 0.836...
............
750870752554[752554, 511740, 397649, 718404][1.0000004768371582, 1.0000003576278687, 0.972...
393268346630[346651, 346630, 346622, 346616][1.0000004768371582, 1.0000004768371582, 1.000...
559345346651[346651, 346630, 346622, 346616][1.0000004768371582, 1.0000004768371582, 1.000...
476378346616[346651, 346630, 346622, 346616][1.0000004768371582, 1.0000004768371582, 1.000...
500411346622[346651, 346630, 346622, 346616][1.0000004768371582, 1.0000004768371582, 1.000...
\n", + "

873606 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " item_id sim_item_id \\\n", + "787744 440450 [440450, 794964, 662906, 789388] \n", + "89069 478024 [478024, 542933, 200580, 501613] \n", + "142892 170948 [170948, 357991, 184016, 56267] \n", + "636868 178504 [178504, 92743, 206733, 337648] \n", + "694565 429070 [429070, 428326, 289784, 428323] \n", + "... ... ... \n", + "750870 752554 [752554, 511740, 397649, 718404] \n", + "393268 346630 [346651, 346630, 346622, 346616] \n", + "559345 346651 [346651, 346630, 346622, 346616] \n", + "476378 346616 [346651, 346630, 346622, 346616] \n", + "500411 346622 [346651, 346630, 346622, 346616] \n", + "\n", + " score \n", + "787744 [0.9999995231628418, 0.8756284713745117, 0.841... \n", + "89069 [0.9999995231628418, 0.950843870639801, 0.9462... \n", + "142892 [0.9999995231628418, 0.9587958455085754, 0.956... \n", + "636868 [0.9999995231628418, 0.9872869849205017, 0.986... \n", + "694565 [0.9999995827674866, 0.8366211652755737, 0.836... \n", + "... ... \n", + "750870 [1.0000004768371582, 1.0000003576278687, 0.972... \n", + "393268 [1.0000004768371582, 1.0000004768371582, 1.000... \n", + "559345 [1.0000004768371582, 1.0000004768371582, 1.000... \n", + "476378 [1.0000004768371582, 1.0000004768371582, 1.000... \n", + "500411 [1.0000004768371582, 1.0000004768371582, 1.000... \n", + "\n", + "[873606 rows x 3 columns]" + ] + }, + "execution_count": 79, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "similar_items" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1011f608", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "ExecuteTimeLog": [ + { + "duration": 66, + "start_time": "2024-09-02T18:34:52.193Z" + }, + { + "duration": 46, + "start_time": "2024-09-02T18:36:43.983Z" + }, + { + "duration": 55, + "start_time": "2024-09-02T18:40:09.918Z" + }, + { + "duration": 53, + "start_time": "2024-09-02T18:43:19.919Z" + }, + { + "duration": 54, + "start_time": "2024-09-02T18:44:22.195Z" + }, + { + "duration": 53, + "start_time": "2024-09-02T18:45:24.351Z" + }, + { + "duration": 61, + "start_time": "2024-09-02T19:00:18.348Z" + } + ], + "kernelspec": { + "display_name": ".venv_recsys", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": true, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": true + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/assets/recsys/service/events/Dockerfile b/assets/recsys/service/events/Dockerfile new file mode 100644 index 0000000..157c63b --- /dev/null +++ b/assets/recsys/service/events/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.11-slim + +COPY . /events_app +WORKDIR /events_app +RUN pip install -r requirements.txt +EXPOSE 8020 + +CMD ["uvicorn", "events_service:app", "--port", "8020", "--host", "0.0.0.0"] + diff --git a/assets/recsys/service/events/events_service.py b/assets/recsys/service/events/events_service.py new file mode 100644 index 0000000..64a7ff7 --- /dev/null +++ b/assets/recsys/service/events/events_service.py @@ -0,0 +1,54 @@ +from fastapi import FastAPI + + +class EventStore: + + def __init__(self, max_events_per_user=10): + self.events = {} + self.max_events_per_user = max_events_per_user + + + def get(self, user_id): + """ + Возвращает события для пользователя + """ + + if user_id in self.events: + user_events = self.events[user_id] + else: + user_events = [] + + return user_events + + + def put(self, user_id, item_id): + """ + Сохраняет событие для пользователя + """ + user_events = self.get(user_id) + self.events[user_id] = [item_id] + user_events[: self.max_events_per_user] + + +events_store = EventStore() + +app = FastAPI(title="events") + +@app.post("/put") +async def put(user_id: int, item_id: int): + """ + Сохраняет событие для user_id, item_id + """ + events_store.put(user_id, item_id) + + return {"result": "ok"} + + +@app.get("/get") +async def get(user_id: int, k: int = 10): + """ + Возвращает список последних k событий для пользователя user_id + """ + events = events_store.get(user_id)[:k] + + return {"events": events} + diff --git a/assets/recsys/service/events/requirements.txt b/assets/recsys/service/events/requirements.txt new file mode 100644 index 0000000..f0615cf --- /dev/null +++ b/assets/recsys/service/events/requirements.txt @@ -0,0 +1,2 @@ +fastapi +uvicorn \ No newline at end of file diff --git a/assets/recsys/service/features/Dockerfile b/assets/recsys/service/features/Dockerfile new file mode 100644 index 0000000..d9eff2e --- /dev/null +++ b/assets/recsys/service/features/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.11-slim + +COPY . /features_app +WORKDIR /features_app +RUN pip install -r requirements.txt + +EXPOSE 8010 + +CMD ["uvicorn", "feature_service:app", "--port", "8010", "--host", "0.0.0.0"] \ No newline at end of file diff --git a/assets/recsys/service/features/feature_service.py b/assets/recsys/service/features/feature_service.py new file mode 100644 index 0000000..0620ca9 --- /dev/null +++ b/assets/recsys/service/features/feature_service.py @@ -0,0 +1,57 @@ +import logging + +import pandas as pd +from fastapi import FastAPI + +PATH_TO_SIMILAR_ITEMS = '../../recommendations/similar_items.parquet' + +logger = logging.getLogger("uvicorn.error") + +class SimilarItems: + + def __init__(self, path, **kwargs): + + """ + Загружаем данные из файла + """ + logger.info(f"Loading data") + self._similar_items = pd.read_parquet(path) + self._similar_items = self._similar_items[kwargs['columns']] + self._similar_items = self._similar_items.set_index('item_id') + logger.info(f"Loaded") + + + def get(self, item_id: int, k: int = 10): + """ + Возвращает список k похожих объектов + """ + + try: + i2i = self._similar_items.loc[item_id].head(k) + i2i = {"item_id_2": i2i["sim_item_id"].tolist(), "score": i2i['score'].tolist()} + except KeyError: + logger.error("No recommendations found") + i2i = {"item_id_2": [], "score": []} + except: + logger.error("problem with similar recomendations") + + return i2i + + +sim_items_store = SimilarItems( + PATH_TO_SIMILAR_ITEMS, + columns=["item_id", "sim_item_id", "score"]) + +logger.info("Ready!") + +# создаём приложение FastAPI +app = FastAPI(title="features") + +@app.get("/similar_items") +async def recommendations(item_id: int, k: int = 10): + """ + Возвращает список похожих объектов длиной k для item_id + """ + i2i = sim_items_store.get(item_id, k) + + return i2i \ No newline at end of file diff --git a/assets/recsys/service/features/requirements.txt b/assets/recsys/service/features/requirements.txt new file mode 100644 index 0000000..54b0a0b --- /dev/null +++ b/assets/recsys/service/features/requirements.txt @@ -0,0 +1,6 @@ +pandas +matplotlib +pyarrow +implicit==0.7.2 +fastapi +uvicorn diff --git a/assets/recsys/service/recommendations/Dockerfile b/assets/recsys/service/recommendations/Dockerfile new file mode 100644 index 0000000..69f005d --- /dev/null +++ b/assets/recsys/service/recommendations/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.11-slim + +COPY . /recs_app +WORKDIR /recs_app +RUN pip install -r requirements.txt +RUN export $(cat ./.env | xargs) +EXPOSE 8000 + +CMD ["uvicorn", "recommendation_service:app", "--port", "8000", "--host", "0.0.0.0"] \ No newline at end of file diff --git a/assets/recsys/service/recommendations/rec_handler.py b/assets/recsys/service/recommendations/rec_handler.py new file mode 100644 index 0000000..44b23e9 --- /dev/null +++ b/assets/recsys/service/recommendations/rec_handler.py @@ -0,0 +1,43 @@ +import logging as logger +import pandas as pd + +class Recommendations: + + def __init__(self): + + self._recs = {"personal": None, "default": None} + + def load(self, rec_type, path, **kwargs): + """ + Загружает рекомендации из файла + """ + + logger.info(f"Loading recommendations, type: {rec_type}") + self._recs[rec_type] = pd.read_parquet(path, **kwargs) + if rec_type == "personal": + self._recs[rec_type] = self._recs[rec_type].set_index("user_id") + logger.info(f"Loaded") + + def get(self, user_id: int, k: int=100): + """ + Возвращает список рекомендаций для пользователя + """ + try: + recs = self._recs["personal"].loc[user_id] + recs = recs["item_id"].to_list()[:k] + except KeyError: + recs = self._recs["default"] + recs = recs["item_id"].to_list()[:k] + except: + logger.error("No recommendations found") + recs = [] + + if not recs: + logger.warning(f"No default recommendations available for user {user_id}") + recs = [] + else: + logger.info(f'recs: {recs}') + + return recs + + diff --git a/assets/recsys/service/recommendations/recommendation_service.py b/assets/recsys/service/recommendations/recommendation_service.py new file mode 100644 index 0000000..3122d58 --- /dev/null +++ b/assets/recsys/service/recommendations/recommendation_service.py @@ -0,0 +1,116 @@ +import logging +import requests +import os +import pandas as pd + +from fastapi import FastAPI +from rec_handler import Recommendations + +PATH_TO_RECOMENDATIONS = '../../recommendations/' +FILENAME_PERS_RECOMENDATIONS = 'personal_als.parquet' +FILENAME_TOP_POPULAR = 'top_popular.parquet' + +logger = logging.getLogger("uvicorn.error") + +rec_store = Recommendations() +features_store_url = "http://localhost:8010" +events_store_url = "http://localhost:8020" + + +logger.info("Starting") + +rec_store.load( +"personal", +PATH_TO_RECOMENDATIONS+FILENAME_PERS_RECOMENDATIONS, +columns=["user_id", "item_id", "score"], +) +rec_store.load( + "default", + PATH_TO_RECOMENDATIONS+FILENAME_TOP_POPULAR, + columns=["item_id", "rank"], +) + + +# создаём приложение FastAPI +app = FastAPI(title="recommendations") + + +@app.post("/recommendations_offline") +async def recommendations_offline(user_id: int, k: int = 100): + """ + Возвращает список рекомендаций длиной k для пользователя user_id + """ + + recs = rec_store.get(user_id=user_id, k=k) + return {"recs": recs} + + +@app.post("/recommendations_online") +async def recommendations_online(user_id: int, k: int = 100): + """ + Возвращает список онлайн-рекомендаций длиной k для пользователя user_id + """ + + headers = {"Content-type": "application/json", "Accept": "text/plain"} + + # получаем последние события пользователя + params = {"user_id": user_id, "k": 3} + resp = requests.get(events_store_url + "/get", headers=headers, params=params) + events = resp.json() + events = events["events"] + + # получаем список похожих объектов + if len(events) > 0: + items = [] + scores = [] + for item_id in events: + params = {"item_id": item_id, "k": k} + headers = {"Content-type": "application/json", "Accept": "text/plain"} + resp = requests.get(features_store_url + "/similar_items", headers=headers, params=params) + if resp.status_code == 200: + similar_items = resp.json() + else: + similar_items = None + print(f"status code: {resp.status_code}") + items += similar_items["item_id_2"] + scores += similar_items["score"] + combined = list(zip(items, scores)) + combined = sorted(combined, key=lambda x: x[1], reverse=True) + combined = [item for item, _ in combined] + recs = combined[:k] + + else: + recs = [] + + return {"recs": recs} + + +@app.post("/recommendations") +async def recommendations(user_id: int, k: int = 100): + """ + Возвращает список рекомендаций длиной k для пользователя user_id + """ + + recs_offline = await recommendations_offline(user_id, k) + recs_online = await recommendations_online(user_id, k) + + recs_offline = recs_offline["recs"] + recs_online = recs_online["recs"] + recs_blended = [] + + min_length = min(len(recs_offline), len(recs_online)) + # чередуем элементы из списков, пока позволяет минимальная длина + for i in range(min_length): + recs_blended.append(recs_online[i]) + recs_blended.append(recs_offline[i]) + + # добавляем оставшиеся элементы в конец + recs_blended += recs_online[min_length:] + recs_blended += recs_offline[min_length:] + + # оставляем только первые k рекомендаций + recs_blended = recs_blended[:k] + + return {"recs": recs_blended} + + diff --git a/assets/recsys/service/recommendations/requirements.txt b/assets/recsys/service/recommendations/requirements.txt new file mode 100644 index 0000000..a62fbe7 --- /dev/null +++ b/assets/recsys/service/recommendations/requirements.txt @@ -0,0 +1,12 @@ +pandas +matplotlib +pyarrow==13.0.0 +mlflow==2.7.1 +psycopg==3.1.12 +psycopg[binary,pool] +boto3==1.34.78 +implicit==0.7.2 +fastapi +uvicorn +prometheus-fastapi-instrumentator +python-dotenv \ No newline at end of file