Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save skilfoy/418398aae84892d60f408d8abf90207c to your computer and use it in GitHub Desktop.
Save skilfoy/418398aae84892d60f408d8abf90207c to your computer and use it in GitHub Desktop.
Project 7 - Transformer-Based Recommendation System.ipynb
Display the source blob
Display the rendered blob
Raw
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"mount_file_id": "1WFbYDrGCS5p76ne535kyZoQj5Rfsxax9",
"authorship_tag": "ABX9TyMY23R3yRMwtea2oiEi7E3D",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/gist/skilfoy/418398aae84892d60f408d8abf90207c/project-7-transformer-based-recommendation-system.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"source": [
"# Project 7 - Transformer-based Recommendation System\n",
"\n",
"In this project, I create a transformer-based recommendation system. To train the model, I'll use the [1M MovieLens Dataset](https://grouplens.org/datasets/movielens/1m/). Released in February 2023, it's the most recently relased MovieLens dataset available from GroupLens.\n",
"\n",
"Note: A large part of this notebook is based on the Keras code example, [A Transformer-based recommendation system](https://keras.io/examples/structured_data/movielens_recommendations_transformers/)"
],
"metadata": {
"id": "W0pLVd9TGmsz"
}
},
{
"cell_type": "markdown",
"source": [
"## Notebook Preparation"
],
"metadata": {
"id": "ZlHRmZ-R9RKh"
}
},
{
"cell_type": "markdown",
"source": [
"Import libraries."
],
"metadata": {
"id": "SgcvYq_Y7C5W"
}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "eG4FjVgZ5Gl9"
},
"outputs": [],
"source": [
"import os\n",
"import random\n",
"import math\n",
"import pathlib\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"import tensorflow as tf\n",
"import seaborn as sns\n",
"from tensorflow import keras\n",
"from tensorflow.keras import layers\n",
"from tensorflow.keras.layers import StringLookup\n",
"from tensorflow.keras.callbacks import EarlyStopping\n",
"from sklearn.model_selection import train_test_split\n",
"from sklearn.preprocessing import MinMaxScaler, LabelEncoder\n",
"from collections import Counter\n",
"from tabulate import tabulate"
]
},
{
"cell_type": "markdown",
"source": [
"Set seeds for reproducibility."
],
"metadata": {
"id": "LpwRK8u7l21j"
}
},
{
"cell_type": "code",
"source": [
"os.environ['PYTHONHASHSEED']='7'\n",
"random.seed(7)\n",
"np.random.seed(7)\n",
"tf.random.set_seed(7)\n",
"tf.keras.utils.set_random_seed(7)"
],
"metadata": {
"id": "ei17a5oImHaI"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## Data Preparation"
],
"metadata": {
"id": "GQ7mHbg09TmH"
}
},
{
"cell_type": "markdown",
"source": [
"I ran into errors regarding the encoding of the file while loading the data into memory, so I determined the encoding then specified it while loading the data."
],
"metadata": {
"id": "mt3PpGV4Vjmv"
}
},
{
"cell_type": "code",
"source": [
"import chardet\n",
"file_path = \"/content/drive/MyDrive/Colab Notebooks/DSCI619/Project 7/ml-1m/movies.dat\"\n",
"with open(file_path, 'rb') as f:\n",
" raw_data = f.read(10000)\n",
"result = chardet.detect(raw_data)\n",
"encoding = result['encoding']\n",
"print(\"Detected Encoding:\", encoding)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "uV9LJiN6S8mk",
"outputId": "50bca6b5-f14e-4555-8dee-75564c1fc8fd"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Detected Encoding: ISO-8859-1\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"Load the data into memory."
],
"metadata": {
"id": "wMatu-gq_RzG"
}
},
{
"cell_type": "code",
"source": [
"users_df = pd.read_csv(\n",
" \"/content/drive/MyDrive/Colab Notebooks/DSCI619/Project 7/ml-1m/users.dat\",\n",
" sep=\"::\",\n",
" names=[\"user_id\", \"sex\", \"age_group\", \"occupation\", \"zip_code\"],\n",
" engine='python')\n",
"\n",
"ratings_df = pd.read_csv(\n",
" \"/content/drive/MyDrive/Colab Notebooks/DSCI619/Project 7/ml-1m/ratings.dat\",\n",
" sep=\"::\",\n",
" names=[\"user_id\", \"movie_id\", \"rating\", \"unix_timestamp\"],\n",
" engine='python')\n",
"\n",
"movies_df = pd.read_csv(\n",
" \"/content/drive/MyDrive/Colab Notebooks/DSCI619/Project 7/ml-1m/movies.dat\",\n",
" sep=\"::\",\n",
" names=[\"movie_id\", \"title\", \"genres\"],\n",
" engine='python',\n",
" encoding='ISO-8859-1')"
],
"metadata": {
"id": "YAuauWL6SoNL"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"Fix the data types of the columns."
],
"metadata": {
"id": "YwTcabdzbkZL"
}
},
{
"cell_type": "code",
"source": [
"users_df[\"user_id\"] = users_df[\"user_id\"].apply(lambda x: f\"user_{x}\")\n",
"users_df[\"age_group\"] = users_df[\"age_group\"].apply(lambda x: f\"group_{x}\")\n",
"users_df[\"occupation\"] = users_df[\"occupation\"].apply(lambda x: f\"occupation_{x}\")\n",
"\n",
"ratings_df[\"movie_id\"] = ratings_df[\"movie_id\"].apply(lambda x: f\"movie_{x}\")\n",
"ratings_df[\"user_id\"] = ratings_df[\"user_id\"].apply(lambda x: f\"user_{x}\")\n",
"ratings_df[\"rating\"] = ratings_df[\"rating\"].apply(lambda x: float(x))\n",
"\n",
"movies_df[\"movie_id\"] = movies_df[\"movie_id\"].apply(lambda x: f\"movie_{x}\")"
],
"metadata": {
"id": "g6IxRySRbpD6"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"View the `movies_df` dataset."
],
"metadata": {
"id": "FATw2V1jb5Jn"
}
},
{
"cell_type": "code",
"source": [
"movies_df.head()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 206
},
"id": "yX1UoNEEb92c",
"outputId": "9dfa18b2-31dd-4b2a-ab96-5e5a9a869eea"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" movie_id title genres\n",
"0 movie_1 Toy Story (1995) Animation|Children's|Comedy\n",
"1 movie_2 Jumanji (1995) Adventure|Children's|Fantasy\n",
"2 movie_3 Grumpier Old Men (1995) Comedy|Romance\n",
"3 movie_4 Waiting to Exhale (1995) Comedy|Drama\n",
"4 movie_5 Father of the Bride Part II (1995) Comedy"
],
"text/html": [
"\n",
" <div id=\"df-037080ef-57e9-431d-b1ae-5739172ccf76\">\n",
" <div class=\"colab-df-container\">\n",
" <div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>movie_id</th>\n",
" <th>title</th>\n",
" <th>genres</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>movie_1</td>\n",
" <td>Toy Story (1995)</td>\n",
" <td>Animation|Children's|Comedy</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>movie_2</td>\n",
" <td>Jumanji (1995)</td>\n",
" <td>Adventure|Children's|Fantasy</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>movie_3</td>\n",
" <td>Grumpier Old Men (1995)</td>\n",
" <td>Comedy|Romance</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>movie_4</td>\n",
" <td>Waiting to Exhale (1995)</td>\n",
" <td>Comedy|Drama</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>movie_5</td>\n",
" <td>Father of the Bride Part II (1995)</td>\n",
" <td>Comedy</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>\n",
" <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-037080ef-57e9-431d-b1ae-5739172ccf76')\"\n",
" title=\"Convert this dataframe to an interactive table.\"\n",
" style=\"display:none;\">\n",
" \n",
" <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
" width=\"24px\">\n",
" <path d=\"M0 0h24v24H0V0z\" fill=\"none\"/>\n",
" <path d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/><path d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/>\n",
" </svg>\n",
" </button>\n",
" \n",
" <style>\n",
" .colab-df-container {\n",
" display:flex;\n",
" flex-wrap:wrap;\n",
" gap: 12px;\n",
" }\n",
"\n",
" .colab-df-convert {\n",
" background-color: #E8F0FE;\n",
" border: none;\n",
" border-radius: 50%;\n",
" cursor: pointer;\n",
" display: none;\n",
" fill: #1967D2;\n",
" height: 32px;\n",
" padding: 0 0 0 0;\n",
" width: 32px;\n",
" }\n",
"\n",
" .colab-df-convert:hover {\n",
" background-color: #E2EBFA;\n",
" box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
" fill: #174EA6;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert {\n",
" background-color: #3B4455;\n",
" fill: #D2E3FC;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert:hover {\n",
" background-color: #434B5C;\n",
" box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
" filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
" fill: #FFFFFF;\n",
" }\n",
" </style>\n",
"\n",
" <script>\n",
" const buttonEl =\n",
" document.querySelector('#df-037080ef-57e9-431d-b1ae-5739172ccf76 button.colab-df-convert');\n",
" buttonEl.style.display =\n",
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
"\n",
" async function convertToInteractive(key) {\n",
" const element = document.querySelector('#df-037080ef-57e9-431d-b1ae-5739172ccf76');\n",
" const dataTable =\n",
" await google.colab.kernel.invokeFunction('convertToInteractive',\n",
" [key], {});\n",
" if (!dataTable) return;\n",
"\n",
" const docLinkHtml = 'Like what you see? Visit the ' +\n",
" '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
" + ' to learn more about interactive tables.';\n",
" element.innerHTML = '';\n",
" dataTable['output_type'] = 'display_data';\n",
" await google.colab.output.renderOutput(dataTable, element);\n",
" const docLink = document.createElement('div');\n",
" docLink.innerHTML = docLinkHtml;\n",
" element.appendChild(docLink);\n",
" }\n",
" </script>\n",
" </div>\n",
" </div>\n",
" "
]
},
"metadata": {},
"execution_count": 6
}
]
},
{
"cell_type": "markdown",
"source": [
"Each movie has multiple genres, which need to be split into separate columns."
],
"metadata": {
"id": "vBYHjBmqbyS4"
}
},
{
"cell_type": "code",
"source": [
"genres = [\n",
" \"Action\",\n",
" \"Adventure\",\n",
" \"Animation\",\n",
" \"Children's\",\n",
" \"Comedy\",\n",
" \"Crime\",\n",
" \"Documentary\",\n",
" \"Drama\",\n",
" \"Fantasy\",\n",
" \"Film-Noir\",\n",
" \"Horror\",\n",
" \"Musical\",\n",
" \"Mystery\",\n",
" \"Romance\",\n",
" \"Sci-Fi\",\n",
" \"Thriller\",\n",
" \"War\",\n",
" \"Western\"]\n",
"\n",
"for genre in genres:\n",
" movies_df[genre] = movies_df[\"genres\"].apply(\n",
" lambda values: int(genre in values.split(\"|\")))"
],
"metadata": {
"id": "XHX3C8zZcHm7"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Describe the data."
],
"metadata": {
"id": "wI7asgHJWLVO"
}
},
{
"cell_type": "code",
"source": [
"users_df.describe()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 175
},
"id": "bSuAFQOFS1sq",
"outputId": "12815943-0993-478d-d02e-c2f227b3db01"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" user_id sex age_group occupation zip_code\n",
"count 6040 6040 6040 6040 6040\n",
"unique 6040 2 7 21 3439\n",
"top user_1 M group_25 occupation_4 48104\n",
"freq 1 4331 2096 759 19"
],
"text/html": [
"\n",
" <div id=\"df-4f070738-be91-45ad-9c16-eea5e72696f1\">\n",
" <div class=\"colab-df-container\">\n",
" <div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>user_id</th>\n",
" <th>sex</th>\n",
" <th>age_group</th>\n",
" <th>occupation</th>\n",
" <th>zip_code</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>count</th>\n",
" <td>6040</td>\n",
" <td>6040</td>\n",
" <td>6040</td>\n",
" <td>6040</td>\n",
" <td>6040</td>\n",
" </tr>\n",
" <tr>\n",
" <th>unique</th>\n",
" <td>6040</td>\n",
" <td>2</td>\n",
" <td>7</td>\n",
" <td>21</td>\n",
" <td>3439</td>\n",
" </tr>\n",
" <tr>\n",
" <th>top</th>\n",
" <td>user_1</td>\n",
" <td>M</td>\n",
" <td>group_25</td>\n",
" <td>occupation_4</td>\n",
" <td>48104</td>\n",
" </tr>\n",
" <tr>\n",
" <th>freq</th>\n",
" <td>1</td>\n",
" <td>4331</td>\n",
" <td>2096</td>\n",
" <td>759</td>\n",
" <td>19</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>\n",
" <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-4f070738-be91-45ad-9c16-eea5e72696f1')\"\n",
" title=\"Convert this dataframe to an interactive table.\"\n",
" style=\"display:none;\">\n",
" \n",
" <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
" width=\"24px\">\n",
" <path d=\"M0 0h24v24H0V0z\" fill=\"none\"/>\n",
" <path d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/><path d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/>\n",
" </svg>\n",
" </button>\n",
" \n",
" <style>\n",
" .colab-df-container {\n",
" display:flex;\n",
" flex-wrap:wrap;\n",
" gap: 12px;\n",
" }\n",
"\n",
" .colab-df-convert {\n",
" background-color: #E8F0FE;\n",
" border: none;\n",
" border-radius: 50%;\n",
" cursor: pointer;\n",
" display: none;\n",
" fill: #1967D2;\n",
" height: 32px;\n",
" padding: 0 0 0 0;\n",
" width: 32px;\n",
" }\n",
"\n",
" .colab-df-convert:hover {\n",
" background-color: #E2EBFA;\n",
" box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
" fill: #174EA6;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert {\n",
" background-color: #3B4455;\n",
" fill: #D2E3FC;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert:hover {\n",
" background-color: #434B5C;\n",
" box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
" filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
" fill: #FFFFFF;\n",
" }\n",
" </style>\n",
"\n",
" <script>\n",
" const buttonEl =\n",
" document.querySelector('#df-4f070738-be91-45ad-9c16-eea5e72696f1 button.colab-df-convert');\n",
" buttonEl.style.display =\n",
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
"\n",
" async function convertToInteractive(key) {\n",
" const element = document.querySelector('#df-4f070738-be91-45ad-9c16-eea5e72696f1');\n",
" const dataTable =\n",
" await google.colab.kernel.invokeFunction('convertToInteractive',\n",
" [key], {});\n",
" if (!dataTable) return;\n",
"\n",
" const docLinkHtml = 'Like what you see? Visit the ' +\n",
" '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
" + ' to learn more about interactive tables.';\n",
" element.innerHTML = '';\n",
" dataTable['output_type'] = 'display_data';\n",
" await google.colab.output.renderOutput(dataTable, element);\n",
" const docLink = document.createElement('div');\n",
" docLink.innerHTML = docLinkHtml;\n",
" element.appendChild(docLink);\n",
" }\n",
" </script>\n",
" </div>\n",
" </div>\n",
" "
]
},
"metadata": {},
"execution_count": 8
}
]
},
{
"cell_type": "code",
"source": [
"ratings_df.describe()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 300
},
"id": "4tQHTd_oVJbx",
"outputId": "b6b462e4-d70d-488d-8918-8b99457f3028"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" rating unix_timestamp\n",
"count 1.000209e+06 1.000209e+06\n",
"mean 3.581564e+00 9.722437e+08\n",
"std 1.117102e+00 1.215256e+07\n",
"min 1.000000e+00 9.567039e+08\n",
"25% 3.000000e+00 9.653026e+08\n",
"50% 4.000000e+00 9.730180e+08\n",
"75% 4.000000e+00 9.752209e+08\n",
"max 5.000000e+00 1.046455e+09"
],
"text/html": [
"\n",
" <div id=\"df-6601c7f2-8c2c-42b1-a042-c8fb3f7c2156\">\n",
" <div class=\"colab-df-container\">\n",
" <div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>rating</th>\n",
" <th>unix_timestamp</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>count</th>\n",
" <td>1.000209e+06</td>\n",
" <td>1.000209e+06</td>\n",
" </tr>\n",
" <tr>\n",
" <th>mean</th>\n",
" <td>3.581564e+00</td>\n",
" <td>9.722437e+08</td>\n",
" </tr>\n",
" <tr>\n",
" <th>std</th>\n",
" <td>1.117102e+00</td>\n",
" <td>1.215256e+07</td>\n",
" </tr>\n",
" <tr>\n",
" <th>min</th>\n",
" <td>1.000000e+00</td>\n",
" <td>9.567039e+08</td>\n",
" </tr>\n",
" <tr>\n",
" <th>25%</th>\n",
" <td>3.000000e+00</td>\n",
" <td>9.653026e+08</td>\n",
" </tr>\n",
" <tr>\n",
" <th>50%</th>\n",
" <td>4.000000e+00</td>\n",
" <td>9.730180e+08</td>\n",
" </tr>\n",
" <tr>\n",
" <th>75%</th>\n",
" <td>4.000000e+00</td>\n",
" <td>9.752209e+08</td>\n",
" </tr>\n",
" <tr>\n",
" <th>max</th>\n",
" <td>5.000000e+00</td>\n",
" <td>1.046455e+09</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>\n",
" <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-6601c7f2-8c2c-42b1-a042-c8fb3f7c2156')\"\n",
" title=\"Convert this dataframe to an interactive table.\"\n",
" style=\"display:none;\">\n",
" \n",
" <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
" width=\"24px\">\n",
" <path d=\"M0 0h24v24H0V0z\" fill=\"none\"/>\n",
" <path d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/><path d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/>\n",
" </svg>\n",
" </button>\n",
" \n",
" <style>\n",
" .colab-df-container {\n",
" display:flex;\n",
" flex-wrap:wrap;\n",
" gap: 12px;\n",
" }\n",
"\n",
" .colab-df-convert {\n",
" background-color: #E8F0FE;\n",
" border: none;\n",
" border-radius: 50%;\n",
" cursor: pointer;\n",
" display: none;\n",
" fill: #1967D2;\n",
" height: 32px;\n",
" padding: 0 0 0 0;\n",
" width: 32px;\n",
" }\n",
"\n",
" .colab-df-convert:hover {\n",
" background-color: #E2EBFA;\n",
" box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
" fill: #174EA6;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert {\n",
" background-color: #3B4455;\n",
" fill: #D2E3FC;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert:hover {\n",
" background-color: #434B5C;\n",
" box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
" filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
" fill: #FFFFFF;\n",
" }\n",
" </style>\n",
"\n",
" <script>\n",
" const buttonEl =\n",
" document.querySelector('#df-6601c7f2-8c2c-42b1-a042-c8fb3f7c2156 button.colab-df-convert');\n",
" buttonEl.style.display =\n",
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
"\n",
" async function convertToInteractive(key) {\n",
" const element = document.querySelector('#df-6601c7f2-8c2c-42b1-a042-c8fb3f7c2156');\n",
" const dataTable =\n",
" await google.colab.kernel.invokeFunction('convertToInteractive',\n",
" [key], {});\n",
" if (!dataTable) return;\n",
"\n",
" const docLinkHtml = 'Like what you see? Visit the ' +\n",
" '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
" + ' to learn more about interactive tables.';\n",
" element.innerHTML = '';\n",
" dataTable['output_type'] = 'display_data';\n",
" await google.colab.output.renderOutput(dataTable, element);\n",
" const docLink = document.createElement('div');\n",
" docLink.innerHTML = docLinkHtml;\n",
" element.appendChild(docLink);\n",
" }\n",
" </script>\n",
" </div>\n",
" </div>\n",
" "
]
},
"metadata": {},
"execution_count": 9
}
]
},
{
"cell_type": "code",
"source": [
"movies_df.describe()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 364
},
"id": "n6YNr-DLVMxC",
"outputId": "4b194e05-81ec-410a-c8cd-16366f18afe8"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" Action Adventure Animation Children's Comedy \\\n",
"count 3883.000000 3883.000000 3883.000000 3883.000000 3883.000000 \n",
"mean 0.129539 0.072882 0.027041 0.064641 0.309039 \n",
"std 0.335839 0.259976 0.162224 0.245923 0.462157 \n",
"min 0.000000 0.000000 0.000000 0.000000 0.000000 \n",
"25% 0.000000 0.000000 0.000000 0.000000 0.000000 \n",
"50% 0.000000 0.000000 0.000000 0.000000 0.000000 \n",
"75% 0.000000 0.000000 0.000000 0.000000 1.000000 \n",
"max 1.000000 1.000000 1.000000 1.000000 1.000000 \n",
"\n",
" Crime Documentary Drama Fantasy Film-Noir \\\n",
"count 3883.000000 3883.000000 3883.000000 3883.000000 3883.000000 \n",
"mean 0.054339 0.032707 0.412825 0.017512 0.011331 \n",
"std 0.226715 0.177891 0.492405 0.131187 0.105858 \n",
"min 0.000000 0.000000 0.000000 0.000000 0.000000 \n",
"25% 0.000000 0.000000 0.000000 0.000000 0.000000 \n",
"50% 0.000000 0.000000 0.000000 0.000000 0.000000 \n",
"75% 0.000000 0.000000 1.000000 0.000000 0.000000 \n",
"max 1.000000 1.000000 1.000000 1.000000 1.000000 \n",
"\n",
" Horror Musical Mystery Romance Sci-Fi \\\n",
"count 3883.000000 3883.000000 3883.000000 3883.000000 3883.000000 \n",
"mean 0.088334 0.029359 0.027298 0.121298 0.071079 \n",
"std 0.283816 0.168832 0.162973 0.326515 0.256990 \n",
"min 0.000000 0.000000 0.000000 0.000000 0.000000 \n",
"25% 0.000000 0.000000 0.000000 0.000000 0.000000 \n",
"50% 0.000000 0.000000 0.000000 0.000000 0.000000 \n",
"75% 0.000000 0.000000 0.000000 0.000000 0.000000 \n",
"max 1.000000 1.000000 1.000000 1.000000 1.000000 \n",
"\n",
" Thriller War Western \n",
"count 3883.000000 3883.000000 3883.000000 \n",
"mean 0.126706 0.036827 0.017512 \n",
"std 0.332686 0.188362 0.131187 \n",
"min 0.000000 0.000000 0.000000 \n",
"25% 0.000000 0.000000 0.000000 \n",
"50% 0.000000 0.000000 0.000000 \n",
"75% 0.000000 0.000000 0.000000 \n",
"max 1.000000 1.000000 1.000000 "
],
"text/html": [
"\n",
" <div id=\"df-4035ec16-fbea-4c95-bbc4-195c42104fa4\">\n",
" <div class=\"colab-df-container\">\n",
" <div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Action</th>\n",
" <th>Adventure</th>\n",
" <th>Animation</th>\n",
" <th>Children's</th>\n",
" <th>Comedy</th>\n",
" <th>Crime</th>\n",
" <th>Documentary</th>\n",
" <th>Drama</th>\n",
" <th>Fantasy</th>\n",
" <th>Film-Noir</th>\n",
" <th>Horror</th>\n",
" <th>Musical</th>\n",
" <th>Mystery</th>\n",
" <th>Romance</th>\n",
" <th>Sci-Fi</th>\n",
" <th>Thriller</th>\n",
" <th>War</th>\n",
" <th>Western</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>count</th>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" <td>3883.000000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>mean</th>\n",
" <td>0.129539</td>\n",
" <td>0.072882</td>\n",
" <td>0.027041</td>\n",
" <td>0.064641</td>\n",
" <td>0.309039</td>\n",
" <td>0.054339</td>\n",
" <td>0.032707</td>\n",
" <td>0.412825</td>\n",
" <td>0.017512</td>\n",
" <td>0.011331</td>\n",
" <td>0.088334</td>\n",
" <td>0.029359</td>\n",
" <td>0.027298</td>\n",
" <td>0.121298</td>\n",
" <td>0.071079</td>\n",
" <td>0.126706</td>\n",
" <td>0.036827</td>\n",
" <td>0.017512</td>\n",
" </tr>\n",
" <tr>\n",
" <th>std</th>\n",
" <td>0.335839</td>\n",
" <td>0.259976</td>\n",
" <td>0.162224</td>\n",
" <td>0.245923</td>\n",
" <td>0.462157</td>\n",
" <td>0.226715</td>\n",
" <td>0.177891</td>\n",
" <td>0.492405</td>\n",
" <td>0.131187</td>\n",
" <td>0.105858</td>\n",
" <td>0.283816</td>\n",
" <td>0.168832</td>\n",
" <td>0.162973</td>\n",
" <td>0.326515</td>\n",
" <td>0.256990</td>\n",
" <td>0.332686</td>\n",
" <td>0.188362</td>\n",
" <td>0.131187</td>\n",
" </tr>\n",
" <tr>\n",
" <th>min</th>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>25%</th>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>50%</th>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>75%</th>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>1.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>1.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" <td>0.000000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>max</th>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" <td>1.000000</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>\n",
" <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-4035ec16-fbea-4c95-bbc4-195c42104fa4')\"\n",
" title=\"Convert this dataframe to an interactive table.\"\n",
" style=\"display:none;\">\n",
" \n",
" <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
" width=\"24px\">\n",
" <path d=\"M0 0h24v24H0V0z\" fill=\"none\"/>\n",
" <path d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/><path d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/>\n",
" </svg>\n",
" </button>\n",
" \n",
" <style>\n",
" .colab-df-container {\n",
" display:flex;\n",
" flex-wrap:wrap;\n",
" gap: 12px;\n",
" }\n",
"\n",
" .colab-df-convert {\n",
" background-color: #E8F0FE;\n",
" border: none;\n",
" border-radius: 50%;\n",
" cursor: pointer;\n",
" display: none;\n",
" fill: #1967D2;\n",
" height: 32px;\n",
" padding: 0 0 0 0;\n",
" width: 32px;\n",
" }\n",
"\n",
" .colab-df-convert:hover {\n",
" background-color: #E2EBFA;\n",
" box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
" fill: #174EA6;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert {\n",
" background-color: #3B4455;\n",
" fill: #D2E3FC;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert:hover {\n",
" background-color: #434B5C;\n",
" box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
" filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
" fill: #FFFFFF;\n",
" }\n",
" </style>\n",
"\n",
" <script>\n",
" const buttonEl =\n",
" document.querySelector('#df-4035ec16-fbea-4c95-bbc4-195c42104fa4 button.colab-df-convert');\n",
" buttonEl.style.display =\n",
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
"\n",
" async function convertToInteractive(key) {\n",
" const element = document.querySelector('#df-4035ec16-fbea-4c95-bbc4-195c42104fa4');\n",
" const dataTable =\n",
" await google.colab.kernel.invokeFunction('convertToInteractive',\n",
" [key], {});\n",
" if (!dataTable) return;\n",
"\n",
" const docLinkHtml = 'Like what you see? Visit the ' +\n",
" '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
" + ' to learn more about interactive tables.';\n",
" element.innerHTML = '';\n",
" dataTable['output_type'] = 'display_data';\n",
" await google.colab.output.renderOutput(dataTable, element);\n",
" const docLink = document.createElement('div');\n",
" docLink.innerHTML = docLinkHtml;\n",
" element.appendChild(docLink);\n",
" }\n",
" </script>\n",
" </div>\n",
" </div>\n",
" "
]
},
"metadata": {},
"execution_count": 10
}
]
},
{
"cell_type": "markdown",
"source": [
"Check for missing values in the dataset."
],
"metadata": {
"id": "5UH2oSvaWGK-"
}
},
{
"cell_type": "code",
"source": [
"users_df.isnull().sum()/len(users_df)*100"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "c1dPi9K5V1_y",
"outputId": "7cc916b5-d69a-44ea-8ea1-fcec7a49138f"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"user_id 0.0\n",
"sex 0.0\n",
"age_group 0.0\n",
"occupation 0.0\n",
"zip_code 0.0\n",
"dtype: float64"
]
},
"metadata": {},
"execution_count": 11
}
]
},
{
"cell_type": "code",
"source": [
"ratings_df.isnull().sum()/len(ratings_df)*100"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "CKfOjo5cV1nL",
"outputId": "c06e753c-4f2f-4190-d18a-c87697431e27"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"user_id 0.0\n",
"movie_id 0.0\n",
"rating 0.0\n",
"unix_timestamp 0.0\n",
"dtype: float64"
]
},
"metadata": {},
"execution_count": 12
}
]
},
{
"cell_type": "code",
"source": [
"movies_df.isnull().sum()/len(movies_df)*100"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "2VOGpIrmU0ly",
"outputId": "8c824494-e730-436a-8f82-827760c22ffd"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"movie_id 0.0\n",
"title 0.0\n",
"genres 0.0\n",
"Action 0.0\n",
"Adventure 0.0\n",
"Animation 0.0\n",
"Children's 0.0\n",
"Comedy 0.0\n",
"Crime 0.0\n",
"Documentary 0.0\n",
"Drama 0.0\n",
"Fantasy 0.0\n",
"Film-Noir 0.0\n",
"Horror 0.0\n",
"Musical 0.0\n",
"Mystery 0.0\n",
"Romance 0.0\n",
"Sci-Fi 0.0\n",
"Thriller 0.0\n",
"War 0.0\n",
"Western 0.0\n",
"dtype: float64"
]
},
"metadata": {},
"execution_count": 13
}
]
},
{
"cell_type": "code",
"source": [
"ratings_df[\"movie_id\"].unique()[:20]"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "W0YUiXhsVUKM",
"outputId": "48f92a65-c6d9-44e2-e48e-3cb29fcbb287"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"array(['movie_1193', 'movie_661', 'movie_914', 'movie_3408', 'movie_2355',\n",
" 'movie_1197', 'movie_1287', 'movie_2804', 'movie_594', 'movie_919',\n",
" 'movie_595', 'movie_938', 'movie_2398', 'movie_2918', 'movie_1035',\n",
" 'movie_2791', 'movie_2687', 'movie_2018', 'movie_3105',\n",
" 'movie_2797'], dtype=object)"
]
},
"metadata": {},
"execution_count": 14
}
]
},
{
"cell_type": "code",
"source": [
"ratings_df[\"user_id\"].unique()[:20]"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "4oaQYL67XX-P",
"outputId": "cb4e2712-4a4e-4433-eac4-5cad90064d8e"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"array(['user_1', 'user_2', 'user_3', 'user_4', 'user_5', 'user_6',\n",
" 'user_7', 'user_8', 'user_9', 'user_10', 'user_11', 'user_12',\n",
" 'user_13', 'user_14', 'user_15', 'user_16', 'user_17', 'user_18',\n",
" 'user_19', 'user_20'], dtype=object)"
]
},
"metadata": {},
"execution_count": 15
}
]
},
{
"cell_type": "code",
"source": [
"user_ids = ratings_df[\"user_id\"].unique().tolist()\n",
"user2user_encoded = {x: i for i, x in enumerate(user_ids)}\n",
"userencoded2user = {i: x for i, x in enumerate(user_ids)}\n",
"movie_ids = ratings_df[\"movie_id\"].unique().tolist()\n",
"movie2movie_encoded = {x: i for i, x in enumerate(movie_ids)}\n",
"movie_encoded2movie = {i: x for i, x in enumerate(movie_ids)}\n",
"ratings_df[\"user\"] = ratings_df[\"user_id\"].map(user2user_encoded)\n",
"ratings_df[\"movie\"] = ratings_df[\"movie_id\"].map(movie2movie_encoded)\n",
"num_users = len(user2user_encoded)\n",
"num_movies = len(movie_encoded2movie)\n",
"unique_rating = ratings_df[\"rating\"].unique().tolist()\n",
"ratings_df[\"rating\"] = ratings_df[\"rating\"].values.astype(np.float32)"
],
"metadata": {
"id": "vRrP3sAYWgOD"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"print(\"Number of users:\", num_users)\n",
"print(\"Number of Movies:\", num_movies)\n",
"print(\"The unique rating values:\", sorted(unique_rating))"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "Po_RYFq8W073",
"outputId": "1bfc4e09-1a77-41db-d7d6-7726546a5fe6"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Number of users: 6040\n",
"Number of Movies: 3706\n",
"The unique rating values: [1.0, 2.0, 3.0, 4.0, 5.0]\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"Visualize the data."
],
"metadata": {
"id": "U3gSkutsX0zg"
}
},
{
"cell_type": "code",
"source": [
"sns.countplot(x='rating',\n",
" data=ratings_df)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 467
},
"id": "KKCb8fZEXNhO",
"outputId": "aad99d12-58fd-4d42-ac6d-a4a1c8eda6cc"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"<Axes: xlabel='rating', ylabel='count'>"
]
},
"metadata": {},
"execution_count": 18
},
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 640x480 with 1 Axes>"
],
"image/png": "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\n"
},
"metadata": {}
}
]
},
{
"cell_type": "code",
"source": [
"fig, axes = plt.subplots(1, 3, figsize=(15, 5))\n",
"sns.countplot(x='age_group', data=users_df, ax=axes[0])\n",
"axes[0].set_title('Age Group')\n",
"sns.countplot(x='sex', data=users_df, ax=axes[1])\n",
"axes[1].set_title('Sex')\n",
"sns.countplot(x='occupation', data=users_df, ax=axes[2])\n",
"axes[2].set_title('Occupation')\n",
"plt.suptitle(\"Users Dataset Plots\", fontsize=16)\n",
"plt.tight_layout()\n",
"plt.show()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 370
},
"id": "-9Hls1QDYOPw",
"outputId": "01d904de-1df5-4a99-df05-e0850b4c4c4a"
},
"execution_count": null,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 1500x500 with 3 Axes>"
],
"image/png": "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XUZGhpKTk7VmzRo9/PDDSkxM1IwZMyosnxEjRujLL7/Ue++9pyFDhtj1xcXFad++fUpPT7/mPS5evCiLxSJXVz4AAwCAI88884x8fX21d+9e+fn52fWdOnWqYpICAKCCHD16VCNGjFCjRo20a9cu1a5d2+x77LHH1K1bN40YMUL79+9Xo0aNKjDTm8Pd3V3u7pTzgJuJChZQiSUmJqpGjRqKjIzUvffeq8TERIdxP/30k0aMGCEfHx/5+fkpOjpaX331lVxcXLR8+XK72G+//Vb33nuv/P395eXlpQ4dOuiDDz741VxSUlK0adMmjRkzplTxvESHDh00bNgw83zHjh1ycXHRqlWrNG3aNNWtW1dVqlRRXl6eJGn16tUKCwuTt7e3atWqpeHDh+t///uf3T179uypnj17lnrWAw88oIYNG5rnx44dk4uLi1588UXNmzdPwcHB8vb2Vo8ePfT111//6vgAAKgsjh49qpYtW5YqnktSQECA3flbb71lzqX+/v4aOnSojh8/bvYvW7ZMLi4ueuONN+yue/bZZ+Xi4qKPPvrohowBAIDy8sILL+j8+fNasmSJXfFckmrVqqXXXntN586d05w5c8z2//3vf4qJiVFQUJA8PT0VEhKisWPHqqCgwIzJycnRxIkT1bBhQ3l6eqpevXoaOXKkfvzxR0nS8uXL5eLiomPHjtk9s+T33Cu3V+nZs6datWql1NRU3XHHHfL29lZISIgWL15sd21BQYGmT5+usLAw+fr6qmrVqurWrZu2b99uxhw7dswc56xZs+Ti4iIXFxdzJbqjPdCLior01FNPqXHjxvL09FTDhg31xBNPKD8/3y6uYcOGGjhwoD799FP96U9/kpeXlxo1aqQ333zzN/xLALcuCuhAJZaYmKghQ4bIYrHovvvu0+HDh7V37167mOLiYg0aNEj//ve/FR0drWeeeUYnT55UdHR0qfsdPHhQnTt31jfffKN//OMfeumll1S1alUNHjxYa9euvWYuH374oSRp+PDh1z2Op556Shs2bNDf//53Pfvss7JYLFq+fLn+8pe/yM3NTfHx8Ro9erTWrFmjrl27Kicn57qfUeLNN9/UwoULFRsbq7i4OH399dfq3bu3srOzy3xPAABupuDgYKWmpv7qH4CfeeYZjRw5Uk2bNtXcuXM1YcIEbd26Vd27dzfn0gcffFADBw7UpEmTzML6gQMHNGvWLMXExGjAgAE3ejgAAPwuH374oRo2bKhu3bo57O/evbsaNmyoDRs2SJJOnDihP/3pT1q1apX++te/auHChRoxYoR27typ8+fPS5LOnj2rbt266eWXX1bfvn21YMECPfLII/r222/1/ffflynPM2fOaMCAAQoLC9OcOXNUr149jR071u6P2Hl5efrnP/+pnj176vnnn9fMmTP1ww8/KCIiQmlpaZKk2rVra9GiRZKke+65R//617/0r3/966oL2STpoYce0vTp03X77bdr3rx56tGjh+Lj4zV06NBSsUeOHNG9996rO++8Uy+99JJq1KihBx544A+5lzxQbgwAldK+ffsMSUZSUpJhGIZRXFxs1KtXz3jsscfs4t577z1DkjF//nyz7dKlS0bv3r0NScayZcvM9j59+hitW7c2Ll68aLYVFxcbd9xxh9G0adNr5nPPPfcYkoycnBy79gsXLhg//PCDeZw5c8bs2759uyHJaNSokXH+/HmzvaCgwAgICDBatWplXLhwwWxfv369IcmYPn262dajRw+jR48epfKJjo42goODzfOMjAxDkuHt7W18//33ZvuePXsMScbEiROvOT4AACqLzZs3G25uboabm5ths9mMKVOmGJs2bTIKCgrMmGPHjhlubm7GM888Y3ftgQMHDHd3d7v2kydPGv7+/sadd95p5OfnG+3btzcaNGhg5Obm3rQxAQBQFjk5OYYk4+67775m3F133WVIMvLy8oyRI0carq6uxt69e0vFFRcXG4ZhGNOnTzckGWvWrLlqzLJlywxJRkZGhl1/ye+527dvN9t69OhhSDJeeuklsy0/P99o166dERAQYM7hRUVFRn5+vt39zpw5YwQGBhqjRo0y23744QdDkjFjxoxS+c2YMcO4spyXlpZmSDIeeughu7i///3vhiRj27ZtZltwcLAhydi1a5fZdurUKcPT09N4/PHHSz0LwGWsQAcqqcTERAUGBqpXr16SJBcXF/31r3/VqlWrdOnSJTNu48aN8vDw0OjRo802V1dXxcbG2t3v9OnT2rZtm/7yl7/o559/1o8//qgff/xRP/30kyIiInT48OFS26dcqWTblWrVqtm1L168WLVr1zaPrl27lro2Ojpa3t7e5vm+fft06tQpPfroo3YvPomMjFTz5s3NlQNlMXjwYNWtW9c8/9Of/qROnTrxEXUAgNO48847lZKSorvuuktfffWV5syZo4iICNWtW9fcdm3NmjUqLi7WX/7yF3NO//HHH2W1WtW0aVO7j4JbrVYlJCQoKSlJ3bp1U1pamt544w35+PhU1BABAPhNfv75Z0lS9erVrxlX0p+Xl6d169Zp0KBB6tChQ6m4kq1P3nvvPbVt29bhi0d/uT3Kb+Xu7q6HH37YPLdYLHr44Yd16tQppaamSpLc3NxksVgkXf40+enTp1VUVKQOHTroiy++KNNzS37XnTRpkl37448/Lkmlfr8ODQ21W81fu3ZtNWvWTP/973/L9HzgVkABHaiELl26pFWrVqlXr17KyMjQkSNHdOTIEXXq1EnZ2dnaunWrGfvdd9+pTp06qlKlit09mjRpYnd+5MgRGYahJ5980q7gXfJmb+naLyYr+Q+Ss2fP2rVHRUUpKSlJSUlJatOmjcNrQ0JC7M6/++47SVKzZs1KxTZv3tzsL4umTZuWarvttttK7VsHAEBl1rFjR61Zs0ZnzpzR559/rri4OP3888+69957dejQIR0+fFiGYahp06al5vVvvvmm1Jw+dOhQRUZG6vPPP9fo0aPVp0+fChoZAAC/XcnvoSWF9Ksp6S8qKlJeXp5atWp1zfijR4/+asz1CgoKUtWqVe3abrvtNkmy+310xYoVatOmjby8vFSzZk3Vrl1bGzZsUG5ubpme+91338nV1bVUDcBqtcrPz6/U79cNGjQodY8aNWrozJkzZXo+cCvgtb1AJbRt2zadPHlSq1at0qpVq0r1JyYmqm/fvtd1z+LiYknS3//+d0VERDiM+eWEe6XmzZtLkr7++mt16dLFbK9fv77q168v6fKkW/LClStdufr8erm4uMgwjFLtV67CBwDgj8pisahjx47q2LGjbrvtNj344INavXq1iouL5eLioo8//lhubm6lrvvlJ8Z++ukn7du3T5J06NAhFRcXy9WVtTQAgMrN19dXderU0f79+68Zt3//ftWtW9fuE86/19VWov+e30XfeustPfDAAxo8eLAmT56sgIAA871gR48eLfN9pd++ct7RfzdIcvh7N4DLKKADlVBiYqICAgKUkJBQqm/NmjVau3atFi9eLG9vbwUHB2v79u06f/683Sr0I0eO2F3XqFEjSZKHh4fCw8OvO6eBAwfqueeeU2Jiol0BvSyCg4MlSenp6erdu7ddX3p6utkvXS7KO/oo2dVWqR8+fLhU23/+8x81bNjwd2QMAEDFK/ko+smTJ9W4cWMZhqGQkBBzddu1xMbG6ueff1Z8fLzi4uI0f/78Uh/1BgCgMho4cKBef/11ffrppw63DP3kk0907NgxPfzww6pdu7Z8fHx+9UXcjRs3/tWYGjVqSJL5Yu4SV/td9MSJEzp37pzdKvT//Oc/kmT+Pvruu++qUaNGWrNmjV3Bu+RT4SWuZxuZ4OBgFRcX6/Dhw2rRooXZnp2drZycHLvfrwGUDctOgErmwoULWrNmjQYOHKh777231DFu3Dj9/PPP5h6oERERKiws1Ouvv27eo7i4uFTxPSAgQD179tRrr72mkydPlnruDz/8cM28unTpojvvvFNLlizR+++/7zDmt/7FukOHDgoICNDixYuVn59vtn/88cf65ptvFBkZabY1btxY3377rV1+X331lT777DOH9163bp3dXu6ff/659uzZo/79+/+m3AAAqGjbt293OKeW7HHarFkzDRkyRG5ubpo1a1apWMMw9NNPP5nn7777rt5++20999xz+sc//qGhQ4dq2rRp5i/1AABUZpMnT5a3t7cefvhhu/lNuvyur0ceeURVqlTR5MmT5erqqsGDB+vDDz80P3l1pZI5MyoqSl999ZXWrl171ZjGjRtLknbt2mX2Xbp0SUuWLHGYZ1FRkV577TXzvKCgQK+99ppq166tsLAwSf+3+vvKuXvPnj1KSUmxu1fJ4rhfFu8dGTBggCRp/vz5du1z586VJLvfrwGUDSvQgUrmgw8+0M8//6y77rrLYX/nzp1Vu3ZtJSYm6q9//asGDx6sP/3pT3r88cd15MgRNW/eXB988IFOnz4tyf4v1wkJCeratatat26t0aNHq1GjRsrOzlZKSoq+//57ffXVV9fM7a233lK/fv00ePBg9e/fX+Hh4apRo4aysrK0ZcsW7dq16zcVqj08PPT888/rwQcfVI8ePXTfffcpOztbCxYsUMOGDTVx4kQzdtSoUZo7d64iIiIUExOjU6dOafHixWrZsqX5YtMrNWnSRF27dtXYsWOVn5+v+fPnq2bNmpoyZcqv5gUAQGUwfvx4nT9/Xvfcc4+aN2+ugoICJScn6+2331bDhg314IMPys/PT08//bTi4uJ07NgxDR48WNWrV1dG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DIPB0HjBEV3leAc6EV0RpaWljR0CERERERERUbNhtVoREhLC5DlRI2MCnagZ+vLLL3HNNdfAx8cH/v7+uOmmm7Bnzx41f+3atUhNTYXdbkeXLl3wySefwGAwYMuWLWqZbdu24Xe/+x08PT0RHByMu+66C0ePHq3T5586dQp33nknPDw8EBoaijlz5qBXr14YN26cWiYmJgYzZ87E3XffDW9vb4wePRoAsGzZMrRr1w42mw0xMTF48cUXdWUbDAZ88sknuvd8fHywePFiAMC+fftgMBjwwQcfICMjA3a7He3bt8e3335b9w1IRETUjCxduhTJyclwOBzw9/fHddddhzNnzgAA3nrrLSQmJsJutyMhIQGvvfaaWm/UqFFISUnBuXPnAJz/Mbtjx464++67G6UeREREl8u5c+fw2GOPISgoCHa7Hddccw02bNig5m/fvh033XQTvL294eXlhR49euj60AsXLlT91NDQUDz66KMA/q//WbUvffz4cRgMBqxevRrA/z1m5bPPPkNKSgrsdju6d++Obdu2qXWKioowbNgwhIeHw+l0Ijk5GUuWLFHz77nnHnz77beYO3cuDAYDDAYD9u3b5/YRLhfrU8fExOCZZ57BqFGj4OXlhaioKLz55psNsZmJrlpMoBM1Q2fOnMH48eOxceNGrFixAkajEbfeeisqKytx8uRJDBgwAMnJydi8eTNmzpyJSZMm6dY/fvw4rr32WnTs2BEbN27El19+iUOHDmHw4MF1+vzx48cjJycHy5cvxzfffIM1a9Zg8+bNNZZ74YUX0KFDB+Tm5mLKlCnYtGkTBg8ejKFDh2Lr1q344x//iClTpqjkeH1MmDABjz/+OHJzc5Geno4BAwagqKio3uUQERE1ZQcOHMCwYcMwatQo5OfnY/Xq1bjtttsgInjvvfcwdepUPP3008jPz8czzzyDKVOm4O233wYA/OUvf8GZM2fwxBNPAAAmT56M48eP45VXXmnMKhERETW4iRMnYtmyZXj77bexefNmtG7dGv369cOxY8fw888/o2fPnrDZbFi5ciU2bdqEUaNGoby8HADw+uuv45FHHsHo0aOxdetWLF++HK1bt653DBMmTMCLL76IDRs2IDAwEAMGDEBZWRkA4OzZs+jcuTM+++wzbNu2DaNHj8Zdd92F9evXAwDmzp2L9PR03H///Thw4AAO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\n"
},
"metadata": {}
}
]
},
{
"cell_type": "code",
"source": [
"result = ratings_df.groupby('user').agg({'rating': ['mean', 'min', 'max']})\n",
"result"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 486
},
"id": "C30-6GAKYiwp",
"outputId": "ea193533-b1a9-48c4-e098-c175629f628b"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" rating \n",
" mean min max\n",
"user \n",
"0 4.188679 3.0 5.0\n",
"1 3.713178 1.0 5.0\n",
"2 3.901961 1.0 5.0\n",
"3 4.190476 1.0 5.0\n",
"4 3.146465 1.0 5.0\n",
"... ... ... ...\n",
"6035 3.302928 1.0 5.0\n",
"6036 3.717822 1.0 5.0\n",
"6037 3.800000 1.0 5.0\n",
"6038 3.878049 2.0 5.0\n",
"6039 3.577713 1.0 5.0\n",
"\n",
"[6040 rows x 3 columns]"
],
"text/html": [
"\n",
" <div id=\"df-f17dd02e-06f6-4c8b-9f49-a1700ac1616e\">\n",
" <div class=\"colab-df-container\">\n",
" <div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead tr th {\n",
" text-align: left;\n",
" }\n",
"\n",
" .dataframe thead tr:last-of-type th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr>\n",
" <th></th>\n",
" <th colspan=\"3\" halign=\"left\">rating</th>\n",
" </tr>\n",
" <tr>\n",
" <th></th>\n",
" <th>mean</th>\n",
" <th>min</th>\n",
" <th>max</th>\n",
" </tr>\n",
" <tr>\n",
" <th>user</th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>4.188679</td>\n",
" <td>3.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>3.713178</td>\n",
" <td>1.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>3.901961</td>\n",
" <td>1.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>4.190476</td>\n",
" <td>1.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>3.146465</td>\n",
" <td>1.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6035</th>\n",
" <td>3.302928</td>\n",
" <td>1.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6036</th>\n",
" <td>3.717822</td>\n",
" <td>1.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6037</th>\n",
" <td>3.800000</td>\n",
" <td>1.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6038</th>\n",
" <td>3.878049</td>\n",
" <td>2.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6039</th>\n",
" <td>3.577713</td>\n",
" <td>1.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>6040 rows × 3 columns</p>\n",
"</div>\n",
" <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-f17dd02e-06f6-4c8b-9f49-a1700ac1616e')\"\n",
" title=\"Convert this dataframe to an interactive table.\"\n",
" style=\"display:none;\">\n",
" \n",
" <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
" width=\"24px\">\n",
" <path d=\"M0 0h24v24H0V0z\" fill=\"none\"/>\n",
" <path d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/><path d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/>\n",
" </svg>\n",
" </button>\n",
" \n",
" <style>\n",
" .colab-df-container {\n",
" display:flex;\n",
" flex-wrap:wrap;\n",
" gap: 12px;\n",
" }\n",
"\n",
" .colab-df-convert {\n",
" background-color: #E8F0FE;\n",
" border: none;\n",
" border-radius: 50%;\n",
" cursor: pointer;\n",
" display: none;\n",
" fill: #1967D2;\n",
" height: 32px;\n",
" padding: 0 0 0 0;\n",
" width: 32px;\n",
" }\n",
"\n",
" .colab-df-convert:hover {\n",
" background-color: #E2EBFA;\n",
" box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
" fill: #174EA6;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert {\n",
" background-color: #3B4455;\n",
" fill: #D2E3FC;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert:hover {\n",
" background-color: #434B5C;\n",
" box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
" filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
" fill: #FFFFFF;\n",
" }\n",
" </style>\n",
"\n",
" <script>\n",
" const buttonEl =\n",
" document.querySelector('#df-f17dd02e-06f6-4c8b-9f49-a1700ac1616e button.colab-df-convert');\n",
" buttonEl.style.display =\n",
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
"\n",
" async function convertToInteractive(key) {\n",
" const element = document.querySelector('#df-f17dd02e-06f6-4c8b-9f49-a1700ac1616e');\n",
" const dataTable =\n",
" await google.colab.kernel.invokeFunction('convertToInteractive',\n",
" [key], {});\n",
" if (!dataTable) return;\n",
"\n",
" const docLinkHtml = 'Like what you see? Visit the ' +\n",
" '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
" + ' to learn more about interactive tables.';\n",
" element.innerHTML = '';\n",
" dataTable['output_type'] = 'display_data';\n",
" await google.colab.output.renderOutput(dataTable, element);\n",
" const docLink = document.createElement('div');\n",
" docLink.innerHTML = docLinkHtml;\n",
" element.appendChild(docLink);\n",
" }\n",
" </script>\n",
" </div>\n",
" </div>\n",
" "
]
},
"metadata": {},
"execution_count": 20
}
]
},
{
"cell_type": "code",
"source": [
"result = ratings_df.groupby('movie').agg({'rating': ['mean', 'min', 'max']})\n",
"result"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 486
},
"id": "AyCKFkp0ZGNv",
"outputId": "3e098665-dcc8-424e-b4a2-89f2e97820cc"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" rating \n",
" mean min max\n",
"movie \n",
"0 4.390725 1.0 5.0\n",
"1 3.464762 1.0 5.0\n",
"2 4.154088 1.0 5.0\n",
"3 3.863878 1.0 5.0\n",
"4 3.854375 1.0 5.0\n",
"... ... ... ...\n",
"3701 4.000000 3.0 5.0\n",
"3702 3.000000 3.0 3.0\n",
"3703 1.000000 1.0 1.0\n",
"3704 5.000000 5.0 5.0\n",
"3705 4.000000 4.0 4.0\n",
"\n",
"[3706 rows x 3 columns]"
],
"text/html": [
"\n",
" <div id=\"df-537d079b-e600-47d0-9516-e62540d6e68b\">\n",
" <div class=\"colab-df-container\">\n",
" <div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead tr th {\n",
" text-align: left;\n",
" }\n",
"\n",
" .dataframe thead tr:last-of-type th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr>\n",
" <th></th>\n",
" <th colspan=\"3\" halign=\"left\">rating</th>\n",
" </tr>\n",
" <tr>\n",
" <th></th>\n",
" <th>mean</th>\n",
" <th>min</th>\n",
" <th>max</th>\n",
" </tr>\n",
" <tr>\n",
" <th>movie</th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>4.390725</td>\n",
" <td>1.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>3.464762</td>\n",
" <td>1.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>4.154088</td>\n",
" <td>1.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>3.863878</td>\n",
" <td>1.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>3.854375</td>\n",
" <td>1.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3701</th>\n",
" <td>4.000000</td>\n",
" <td>3.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3702</th>\n",
" <td>3.000000</td>\n",
" <td>3.0</td>\n",
" <td>3.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3703</th>\n",
" <td>1.000000</td>\n",
" <td>1.0</td>\n",
" <td>1.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3704</th>\n",
" <td>5.000000</td>\n",
" <td>5.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3705</th>\n",
" <td>4.000000</td>\n",
" <td>4.0</td>\n",
" <td>4.0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>3706 rows × 3 columns</p>\n",
"</div>\n",
" <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-537d079b-e600-47d0-9516-e62540d6e68b')\"\n",
" title=\"Convert this dataframe to an interactive table.\"\n",
" style=\"display:none;\">\n",
" \n",
" <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
" width=\"24px\">\n",
" <path d=\"M0 0h24v24H0V0z\" fill=\"none\"/>\n",
" <path d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/><path d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/>\n",
" </svg>\n",
" </button>\n",
" \n",
" <style>\n",
" .colab-df-container {\n",
" display:flex;\n",
" flex-wrap:wrap;\n",
" gap: 12px;\n",
" }\n",
"\n",
" .colab-df-convert {\n",
" background-color: #E8F0FE;\n",
" border: none;\n",
" border-radius: 50%;\n",
" cursor: pointer;\n",
" display: none;\n",
" fill: #1967D2;\n",
" height: 32px;\n",
" padding: 0 0 0 0;\n",
" width: 32px;\n",
" }\n",
"\n",
" .colab-df-convert:hover {\n",
" background-color: #E2EBFA;\n",
" box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
" fill: #174EA6;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert {\n",
" background-color: #3B4455;\n",
" fill: #D2E3FC;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert:hover {\n",
" background-color: #434B5C;\n",
" box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
" filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
" fill: #FFFFFF;\n",
" }\n",
" </style>\n",
"\n",
" <script>\n",
" const buttonEl =\n",
" document.querySelector('#df-537d079b-e600-47d0-9516-e62540d6e68b button.colab-df-convert');\n",
" buttonEl.style.display =\n",
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
"\n",
" async function convertToInteractive(key) {\n",
" const element = document.querySelector('#df-537d079b-e600-47d0-9516-e62540d6e68b');\n",
" const dataTable =\n",
" await google.colab.kernel.invokeFunction('convertToInteractive',\n",
" [key], {});\n",
" if (!dataTable) return;\n",
"\n",
" const docLinkHtml = 'Like what you see? Visit the ' +\n",
" '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
" + ' to learn more about interactive tables.';\n",
" element.innerHTML = '';\n",
" dataTable['output_type'] = 'display_data';\n",
" await google.colab.output.renderOutput(dataTable, element);\n",
" const docLink = document.createElement('div');\n",
" docLink.innerHTML = docLinkHtml;\n",
" element.appendChild(docLink);\n",
" }\n",
" </script>\n",
" </div>\n",
" </div>\n",
" "
]
},
"metadata": {},
"execution_count": 21
}
]
},
{
"cell_type": "code",
"source": [
"result = ratings_df.groupby(['user', 'movie']).agg({'rating': ['mean', 'min', 'max']})\n",
"result"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 486
},
"id": "x127iM0GZOrQ",
"outputId": "73a4232d-6b73-46ff-992b-4ac8917b34e5"
},
"execution_count": null,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" rating \n",
" mean min max\n",
"user movie \n",
"0 0 5.0 5.0 5.0\n",
" 1 3.0 3.0 3.0\n",
" 2 3.0 3.0 3.0\n",
" 3 4.0 4.0 4.0\n",
" 4 5.0 5.0 5.0\n",
"... ... ... ...\n",
"6039 2911 5.0 5.0 5.0\n",
" 2955 1.0 1.0 1.0\n",
" 3042 5.0 5.0 5.0\n",
" 3046 3.0 3.0 3.0\n",
" 3412 5.0 5.0 5.0\n",
"\n",
"[1000209 rows x 3 columns]"
],
"text/html": [
"\n",
" <div id=\"df-90a3551d-51f6-4fd9-b21f-37c34f4146b5\">\n",
" <div class=\"colab-df-container\">\n",
" <div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead tr th {\n",
" text-align: left;\n",
" }\n",
"\n",
" .dataframe thead tr:last-of-type th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr>\n",
" <th></th>\n",
" <th></th>\n",
" <th colspan=\"3\" halign=\"left\">rating</th>\n",
" </tr>\n",
" <tr>\n",
" <th></th>\n",
" <th></th>\n",
" <th>mean</th>\n",
" <th>min</th>\n",
" <th>max</th>\n",
" </tr>\n",
" <tr>\n",
" <th>user</th>\n",
" <th>movie</th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th rowspan=\"5\" valign=\"top\">0</th>\n",
" <th>0</th>\n",
" <td>5.0</td>\n",
" <td>5.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>3.0</td>\n",
" <td>3.0</td>\n",
" <td>3.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>3.0</td>\n",
" <td>3.0</td>\n",
" <td>3.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>4.0</td>\n",
" <td>4.0</td>\n",
" <td>4.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>5.0</td>\n",
" <td>5.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th rowspan=\"5\" valign=\"top\">6039</th>\n",
" <th>2911</th>\n",
" <td>5.0</td>\n",
" <td>5.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2955</th>\n",
" <td>1.0</td>\n",
" <td>1.0</td>\n",
" <td>1.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3042</th>\n",
" <td>5.0</td>\n",
" <td>5.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3046</th>\n",
" <td>3.0</td>\n",
" <td>3.0</td>\n",
" <td>3.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3412</th>\n",
" <td>5.0</td>\n",
" <td>5.0</td>\n",
" <td>5.0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>1000209 rows × 3 columns</p>\n",
"</div>\n",
" <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-90a3551d-51f6-4fd9-b21f-37c34f4146b5')\"\n",
" title=\"Convert this dataframe to an interactive table.\"\n",
" style=\"display:none;\">\n",
" \n",
" <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
" width=\"24px\">\n",
" <path d=\"M0 0h24v24H0V0z\" fill=\"none\"/>\n",
" <path d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/><path d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/>\n",
" </svg>\n",
" </button>\n",
" \n",
" <style>\n",
" .colab-df-container {\n",
" display:flex;\n",
" flex-wrap:wrap;\n",
" gap: 12px;\n",
" }\n",
"\n",
" .colab-df-convert {\n",
" background-color: #E8F0FE;\n",
" border: none;\n",
" border-radius: 50%;\n",
" cursor: pointer;\n",
" display: none;\n",
" fill: #1967D2;\n",
" height: 32px;\n",
" padding: 0 0 0 0;\n",
" width: 32px;\n",
" }\n",
"\n",
" .colab-df-convert:hover {\n",
" background-color: #E2EBFA;\n",
" box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
" fill: #174EA6;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert {\n",
" background-color: #3B4455;\n",
" fill: #D2E3FC;\n",
" }\n",
"\n",
" [theme=dark] .colab-df-convert:hover {\n",
" background-color: #434B5C;\n",
" box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
" filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
" fill: #FFFFFF;\n",
" }\n",
" </style>\n",
"\n",
" <script>\n",
" const buttonEl =\n",
" document.querySelector('#df-90a3551d-51f6-4fd9-b21f-37c34f4146b5 button.colab-df-convert');\n",
" buttonEl.style.display =\n",
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
"\n",
" async function convertToInteractive(key) {\n",
" const element = document.querySelector('#df-90a3551d-51f6-4fd9-b21f-37c34f4146b5');\n",
" const dataTable =\n",
" await google.colab.kernel.invokeFunction('convertToInteractive',\n",
" [key], {});\n",
" if (!dataTable) return;\n",
"\n",
" const docLinkHtml = 'Like what you see? Visit the ' +\n",
" '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
" + ' to learn more about interactive tables.';\n",
" element.innerHTML = '';\n",
" dataTable['output_type'] = 'display_data';\n",
" await google.colab.output.renderOutput(dataTable, element);\n",
" const docLink = document.createElement('div');\n",
" docLink.innerHTML = docLinkHtml;\n",
" element.appendChild(docLink);\n",
" }\n",
" </script>\n",
" </div>\n",
" </div>\n",
" "
]
},
"metadata": {},
"execution_count": 22
}
]
},
{
"cell_type": "markdown",
"source": [
"## Model Development"
],
"metadata": {
"id": "NGjzVRX6ZaLh"
}
},
{
"cell_type": "markdown",
"source": [
"### Transform the movie ratings data into sequences."
],
"metadata": {
"id": "F1djANK9ccyI"
}
},
{
"cell_type": "markdown",
"source": [
"Sort the ratings data and group the `movie_id` by `rating` and `user_id`."
],
"metadata": {
"id": "fa-sVSzPciCv"
}
},
{
"cell_type": "code",
"source": [
"ratings_group = ratings_df.sort_values(by=[\"unix_timestamp\"]).groupby(\"user_id\")\n",
"\n",
"ratings_data = pd.DataFrame(\n",
" data={\n",
" \"user_id\": list(ratings_group.groups.keys()),\n",
" \"movie_ids\": list(ratings_group.movie_id.apply(list)),\n",
" \"ratings\": list(ratings_group.rating.apply(list)),\n",
" \"timestamps\": list(ratings_group.unix_timestamp.apply(list))})"
],
"metadata": {
"id": "Z3MlLTnodCxt"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"Split the `movie_ids` and `ratings` into fixed-length sequences. Set the `sequence_length` variable to change the model's input sequence length."
],
"metadata": {
"id": "870ADdctdLUp"
}
},
{
"cell_type": "code",
"source": [
"sequence_length = 4\n",
"step_size = 2\n",
"\n",
"def create_sequences(values, window_size, step_size):\n",
" sequences = []\n",
" start_index = 0\n",
" while True:\n",
" end_index = start_index + window_size\n",
" seq = values[start_index:end_index]\n",
" if len(seq) < window_size:\n",
" seq = values[-window_size:]\n",
" if len(seq) == window_size:\n",
" sequences.append(seq)\n",
" break\n",
" sequences.append(seq)\n",
" start_index += step_size\n",
" return sequences\n",
"\n",
"ratings_data.movie_ids = ratings_data.movie_ids.apply(\n",
" lambda ids: create_sequences(ids, sequence_length, step_size))\n",
"\n",
"ratings_data.ratings = ratings_data.ratings.apply(\n",
" lambda ids: create_sequences(ids, sequence_length, step_size))\n",
"\n",
"del ratings_data[\"timestamps\"]"
],
"metadata": {
"id": "giGWoCMIdgio"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"Process each sequence into separate records and join the user features with the ratings data."
],
"metadata": {
"id": "G_VWcR93dnFl"
}
},
{
"cell_type": "code",
"source": [
"ratings_data_movies = ratings_data[[\"user_id\", \"movie_ids\"]].explode(\n",
" \"movie_ids\", ignore_index=True)\n",
"ratings_data_rating = ratings_data[[\"ratings\"]].explode(\"ratings\",\n",
" ignore_index=True)\n",
"ratings_data_transformed = pd.concat([ratings_data_movies,\n",
" ratings_data_rating],\n",
" axis=1)\n",
"ratings_data_transformed = ratings_data_transformed.join(\n",
" users_df.set_index(\"user_id\"), on=\"user_id\")\n",
"ratings_data_transformed.movie_ids = ratings_data_transformed.movie_ids.apply(\n",
" lambda x: \",\".join(x))\n",
"ratings_data_transformed.ratings = ratings_data_transformed.ratings.apply(\n",
" lambda x: \",\".join([str(v) for v in x]))\n",
"\n",
"del ratings_data_transformed[\"zip_code\"]\n",
"\n",
"ratings_data_transformed.rename(\n",
" columns={\"movie_ids\": \"sequence_movie_ids\",\n",
" \"ratings\": \"sequence_ratings\"},\n",
" inplace=True)"
],
"metadata": {
"id": "_LG_ldXyd5tm"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"Finally split the data into training and test splits and store them in CSV files.\n",
"\n"
],
"metadata": {
"id": "1mGaTJYfZoS7"
}
},
{
"cell_type": "code",
"source": [
"random_selection = np.random.rand(len(ratings_data_transformed.index)) <= 0.80\n",
"train_data = ratings_data_transformed[random_selection]\n",
"test_data = ratings_data_transformed[~random_selection]\n",
"\n",
"train_data.to_csv(\"train_data.csv\", index=False, sep=\"|\", header=False)\n",
"test_data.to_csv(\"test_data.csv\", index=False, sep=\"|\", header=False)"
],
"metadata": {
"id": "RLV_i4sOZleM"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Define metadata"
],
"metadata": {
"id": "15a31PkOfIgz"
}
},
{
"cell_type": "code",
"source": [
"CSV_HEADER = list(ratings_data_transformed.columns)\n",
"\n",
"CATEGORICAL_FEATURES_WITH_VOCABULARY = {\n",
" \"user_id\": list(users_df.user_id.unique()),\n",
" \"movie_id\": list(movies_df.movie_id.unique()),\n",
" \"sex\": list(users_df.sex.unique()),\n",
" \"age_group\": list(users_df.age_group.unique()),\n",
" \"occupation\": list(users_df.occupation.unique())}\n",
"\n",
"USER_FEATURES = [\"sex\", \"age_group\", \"occupation\"]\n",
"\n",
"MOVIE_FEATURES = [\"genres\"]"
],
"metadata": {
"id": "MHYaklhLfPck"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Create `tf.data.Dataset` for training and evaluation"
],
"metadata": {
"id": "ZQuG_W2PfZEq"
}
},
{
"cell_type": "code",
"source": [
"def get_dataset_from_csv(csv_file_path, shuffle=False, batch_size=128):\n",
" def process(features):\n",
" movie_ids_string = features[\"sequence_movie_ids\"]\n",
" sequence_movie_ids = tf.strings.split(movie_ids_string, \",\").to_tensor()\n",
" features[\"target_movie_id\"] = sequence_movie_ids[:, -1]\n",
" features[\"sequence_movie_ids\"] = sequence_movie_ids[:, :-1]\n",
" ratings_string = features[\"sequence_ratings\"]\n",
" sequence_ratings = tf.strings.to_number(\n",
" tf.strings.split(ratings_string, \",\"), tf.dtypes.float32\n",
" ).to_tensor()\n",
" target = sequence_ratings[:, -1]\n",
" features[\"sequence_ratings\"] = sequence_ratings[:, :-1]\n",
" return features, target\n",
"\n",
" dataset = tf.data.experimental.make_csv_dataset(\n",
" csv_file_path,\n",
" batch_size=batch_size,\n",
" column_names=CSV_HEADER,\n",
" num_epochs=1,\n",
" header=False,\n",
" field_delim=\"|\",\n",
" shuffle=shuffle,\n",
" ).map(process)\n",
"\n",
" return dataset"
],
"metadata": {
"id": "F3a5fkaffdoP"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Create model inputs"
],
"metadata": {
"id": "mpLrFfeSfv5A"
}
},
{
"cell_type": "code",
"source": [
"def create_model_inputs():\n",
" return {\n",
" \"user_id\": layers.Input(name=\"user_id\", shape=(1,), dtype=tf.string),\n",
" \"sequence_movie_ids\": layers.Input(\n",
" name=\"sequence_movie_ids\",\n",
" shape=(sequence_length - 1,),\n",
" dtype=tf.string),\n",
" \"target_movie_id\": layers.Input(\n",
" name=\"target_movie_id\",\n",
" shape=(1,),\n",
" dtype=tf.string),\n",
" \"sequence_ratings\": layers.Input(\n",
" name=\"sequence_ratings\",\n",
" shape=(sequence_length - 1,),\n",
" dtype=tf.float32),\n",
" \"sex\": layers.Input(name=\"sex\", shape=(1,), dtype=tf.string),\n",
" \"age_group\": layers.Input(name=\"age_group\", shape=(1,), dtype=tf.string),\n",
" \"occupation\": layers.Input(name=\"occupation\", shape=(1,), dtype=tf.string),\n",
" }"
],
"metadata": {
"id": "aEFaSAX1fxgx"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Encode input features\n",
"\n",
"The `encode_input_features` method works as follows:\n",
"\n",
"1. Each categorical user feature is encoded using `layers.Embedding`, with embedding dimension equals to the square root of the vocabulary size of the feature. The embeddings of these features are concatenated to form a single input tensor.\n",
"\n",
"2. Each movie in the movie sequence and the target movie is encoded `layers.Embedding`, where the dimension size is the square root of the number of movies.\n",
"\n",
"3. A multi-hot genres vector for each movie is concatenated with its embedding vector, and processed using a non-linear `layers.Dense` to output a vector of the same movie embedding dimensions.\n",
"\n",
"4. A positional embedding is added to each movie embedding in the sequence, and then multiplied by its rating from the ratings sequence.\n",
"\n",
"5. The target movie embedding is concatenated to the sequence movie embeddings, producing a tensor with the shape of `[batch size, sequence length, embedding size]`, as expected by the attention layer for the transformer architecture.\n",
"\n",
"6. The method returns a tuple of two elements: `encoded_transformer_features` and encoded_other_features."
],
"metadata": {
"id": "zM3-ckIpgCRD"
}
},
{
"cell_type": "code",
"source": [
"def encode_input_features(\n",
" inputs,\n",
" include_user_id=True,\n",
" include_user_features=True,\n",
" include_movie_features=True):\n",
" encoded_transformer_features = []\n",
" encoded_other_features = []\n",
" other_feature_names = []\n",
" if include_user_id:\n",
" other_feature_names.append(\"user_id\")\n",
" if include_user_features:\n",
" other_feature_names.extend(USER_FEATURES)\n",
" for feature_name in other_feature_names:\n",
" vocabulary = CATEGORICAL_FEATURES_WITH_VOCABULARY[feature_name]\n",
" idx = StringLookup(vocabulary=vocabulary,\n",
" mask_token=None,\n",
" num_oov_indices=0)(inputs[feature_name])\n",
" embedding_dims = int(math.sqrt(len(vocabulary)))\n",
" embedding_encoder = layers.Embedding(\n",
" input_dim=len(vocabulary),\n",
" output_dim=embedding_dims,\n",
" name=f\"{feature_name}_embedding\",)\n",
" encoded_other_features.append(embedding_encoder(idx))\n",
" if len(encoded_other_features) > 1:\n",
" encoded_other_features = layers.concatenate(encoded_other_features)\n",
" elif len(encoded_other_features) == 1:\n",
" encoded_other_features = encoded_other_features[0]\n",
" else:\n",
" encoded_other_features = None\n",
" movie_vocabulary = CATEGORICAL_FEATURES_WITH_VOCABULARY[\"movie_id\"]\n",
" movie_embedding_dims = int(math.sqrt(len(movie_vocabulary)))\n",
" movie_index_lookup = StringLookup(\n",
" vocabulary=movie_vocabulary,\n",
" mask_token=None,\n",
" num_oov_indices=0,\n",
" name=\"movie_index_lookup\")\n",
" movie_embedding_encoder = layers.Embedding(\n",
" input_dim=len(movie_vocabulary),\n",
" output_dim=movie_embedding_dims,\n",
" name=f\"movie_embedding\",)\n",
" genre_vectors = movies_df[genres].to_numpy()\n",
" movie_genres_lookup = layers.Embedding(\n",
" input_dim=genre_vectors.shape[0],\n",
" output_dim=genre_vectors.shape[1],\n",
" embeddings_initializer=tf.keras.initializers.Constant(genre_vectors),\n",
" trainable=False,\n",
" name=\"genres_vector\",)\n",
" movie_embedding_processor = layers.Dense(\n",
" units=movie_embedding_dims,\n",
" activation=\"relu\",\n",
" name=\"process_movie_embedding_with_genres\",)\n",
"\n",
" def encode_movie(movie_id):\n",
" movie_idx = movie_index_lookup(movie_id)\n",
" movie_embedding = movie_embedding_encoder(movie_idx)\n",
" encoded_movie = movie_embedding\n",
" if include_movie_features:\n",
" movie_genres_vector = movie_genres_lookup(movie_idx)\n",
" encoded_movie = movie_embedding_processor(\n",
" layers.concatenate([movie_embedding, movie_genres_vector]))\n",
" return encoded_movie\n",
"\n",
" target_movie_id = inputs[\"target_movie_id\"]\n",
" encoded_target_movie = encode_movie(target_movie_id)\n",
" sequence_movies_ids = inputs[\"sequence_movie_ids\"]\n",
" encoded_sequence_movies = encode_movie(sequence_movies_ids)\n",
" position_embedding_encoder = layers.Embedding(\n",
" input_dim=sequence_length,\n",
" output_dim=movie_embedding_dims,\n",
" name=\"position_embedding\")\n",
" positions = tf.range(start=0, limit=sequence_length - 1, delta=1)\n",
" encodded_positions = position_embedding_encoder(positions)\n",
" sequence_ratings = tf.expand_dims(inputs[\"sequence_ratings\"], -1)\n",
" encoded_sequence_movies_with_poistion_and_rating = layers.Multiply()(\n",
" [(encoded_sequence_movies + encodded_positions), sequence_ratings])\n",
"\n",
" for encoded_movie in tf.unstack(\n",
" encoded_sequence_movies_with_poistion_and_rating, axis=1):\n",
" encoded_transformer_features.append(tf.expand_dims(encoded_movie, 1))\n",
" encoded_transformer_features.append(encoded_target_movie)\n",
"\n",
" encoded_transformer_features = layers.concatenate(\n",
" encoded_transformer_features, axis=1)\n",
"\n",
" return encoded_transformer_features, encoded_other_features"
],
"metadata": {
"id": "9IlB9_2ygD9Q"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Create a BST model"
],
"metadata": {
"id": "_GLB2U0GhI0W"
}
},
{
"cell_type": "code",
"source": [
"include_user_id = False\n",
"include_user_features = False\n",
"include_movie_features = False\n",
"\n",
"hidden_units = [256, 128]\n",
"dropout_rate = 0.1\n",
"num_heads = 3\n",
"\n",
"def create_model():\n",
" inputs = create_model_inputs()\n",
" transformer_features, other_features = encode_input_features(\n",
" inputs, include_user_id, include_user_features, include_movie_features)\n",
" attention_output = layers.MultiHeadAttention(\n",
" num_heads=num_heads,\n",
" key_dim=transformer_features.shape[2],\n",
" dropout=dropout_rate\n",
" )(transformer_features, transformer_features)\n",
"\n",
" attention_output = layers.Dropout(dropout_rate)(attention_output)\n",
" x1 = layers.Add()([transformer_features, attention_output])\n",
" x1 = layers.LayerNormalization()(x1)\n",
" x2 = layers.LeakyReLU()(x1)\n",
" x2 = layers.Dense(units=x2.shape[-1])(x2)\n",
" x2 = layers.Dropout(dropout_rate)(x2)\n",
" transformer_features = layers.Add()([x1, x2])\n",
" transformer_features = layers.LayerNormalization()(transformer_features)\n",
" features = layers.Flatten()(transformer_features)\n",
"\n",
" if other_features is not None:\n",
" features = layers.concatenate(\n",
" [features,\n",
" layers.Reshape([other_features.shape[-1]])(other_features)])\n",
"\n",
" for num_units in hidden_units:\n",
" features = layers.Dense(num_units)(features)\n",
" features = layers.BatchNormalization()(features)\n",
" features = layers.LeakyReLU()(features)\n",
" features = layers.Dropout(dropout_rate)(features)\n",
"\n",
" outputs = layers.Dense(units=1)(features)\n",
" model = keras.Model(inputs=inputs, outputs=outputs)\n",
" return model\n",
"\n",
"model = create_model()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "jOXrTaIFhIRd",
"outputId": "62a6bf4f-b08d-46db-a3dd-30fefbd44f1c"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stderr",
"text": [
"/usr/local/lib/python3.10/dist-packages/numpy/core/numeric.py:2449: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison\n",
" return bool(asarray(a1 == a2).all())\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### Run training and evaluation experiment."
],
"metadata": {
"id": "sQwhKi94hgq-"
}
},
{
"cell_type": "markdown",
"source": [
"Initialize early stopping callback."
],
"metadata": {
"id": "Hax8-hz5iwg5"
}
},
{
"cell_type": "code",
"source": [
"early_stopping = EarlyStopping(\n",
" monitor='val_loss',\n",
" patience=5,\n",
" min_delta=0.001,\n",
" mode='min')"
],
"metadata": {
"id": "W9iF29QNixCg"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"Compile the model with metrics."
],
"metadata": {
"id": "qjoZxX3Uhliv"
}
},
{
"cell_type": "code",
"source": [
"model.compile(\n",
" optimizer=keras.optimizers.Adam(learning_rate=0.001),\n",
" loss=keras.losses.MeanSquaredError(),\n",
" metrics=[keras.metrics.MeanSquaredError(),\n",
" keras.metrics.MeanAbsoluteError()])"
],
"metadata": {
"id": "9gmDtV0phi5Z"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"Read the training and test data."
],
"metadata": {
"id": "PL4eggkFhpqI"
}
},
{
"cell_type": "code",
"source": [
"train_dataset = get_dataset_from_csv(\"train_data.csv\",\n",
" shuffle=True,\n",
" batch_size=128)\n",
"test_dataset = get_dataset_from_csv(\"test_data.csv\",\n",
" batch_size=128)"
],
"metadata": {
"id": "14YPJoxqhrlw"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"Fit the model with the training data."
],
"metadata": {
"id": "OiFBHfJ7htTz"
}
},
{
"cell_type": "code",
"source": [
"history = model.fit(\n",
" train_dataset,\n",
" batch_size=128,\n",
" #epochs=100,\n",
" epochs=3,\n",
" verbose=1,\n",
" validation_data=(test_dataset),\n",
" callbacks=[early_stopping])"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "tNFTqrVchwpP",
"outputId": "e57d55d9-e349-4170-d969-3ab84ea38b7b"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Epoch 1/3\n",
"3114/3114 [==============================] - 77s 24ms/step - loss: 1.1704 - mean_squared_error: 1.1704 - mean_absolute_error: 0.8555 - val_loss: 0.9244 - val_mean_squared_error: 0.9244 - val_mean_absolute_error: 0.7716\n",
"Epoch 2/3\n",
"3114/3114 [==============================] - 71s 23ms/step - loss: 0.9403 - mean_squared_error: 0.9403 - mean_absolute_error: 0.7731 - val_loss: 0.9677 - val_mean_squared_error: 0.9677 - val_mean_absolute_error: 0.7975\n",
"Epoch 3/3\n",
"3114/3114 [==============================] - 71s 23ms/step - loss: 0.8993 - mean_squared_error: 0.8993 - mean_absolute_error: 0.7551 - val_loss: 0.9048 - val_mean_squared_error: 0.9048 - val_mean_absolute_error: 0.7536\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"Evaluate the model on the test data."
],
"metadata": {
"id": "MnoPMbajh2Qm"
}
},
{
"cell_type": "code",
"source": [
"loss, mse, mae = model.evaluate(test_dataset, verbose=1)\n",
"results = [[\"Test Loss\", loss], [\"Test MSE\", mse], [\"Test MAE\", mae]]\n",
"print(tabulate(results, headers=[\"Metric\", \"Value\"], tablefmt=\"grid\"))"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "keZuEIDlh46E",
"outputId": "4ca9353c-6d5c-41fa-f9fe-8ce3d965ef0a"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"782/782 [==============================] - 6s 8ms/step - loss: 0.9048 - mean_squared_error: 0.9048 - mean_absolute_error: 0.7536\n",
"+-----------+----------+\n",
"| Metric | Value |\n",
"+===========+==========+\n",
"| Test Loss | 0.904825 |\n",
"+-----------+----------+\n",
"| Test MSE | 0.904825 |\n",
"+-----------+----------+\n",
"| Test MAE | 0.753571 |\n",
"+-----------+----------+\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"Summarize the model."
],
"metadata": {
"id": "DoxmRawp27r_"
}
},
{
"cell_type": "code",
"source": [
"model.summary()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "tj9DhT_226Qm",
"outputId": "767cc249-f563-4fb6-de72-72e50ed9d70d"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Model: \"model\"\n",
"__________________________________________________________________________________________________\n",
" Layer (type) Output Shape Param # Connected to \n",
"==================================================================================================\n",
" sequence_movie_ids (InputLayer [(None, 3)] 0 [] \n",
" ) \n",
" \n",
" movie_index_lookup (StringLook multiple 0 ['target_movie_id[0][0]', \n",
" up) 'sequence_movie_ids[0][0]'] \n",
" \n",
" movie_embedding (Embedding) multiple 240746 ['movie_index_lookup[0][0]', \n",
" 'movie_index_lookup[1][0]'] \n",
" \n",
" sequence_ratings (InputLayer) [(None, 3)] 0 [] \n",
" \n",
" tf.__operators__.add (TFOpLamb (None, 3, 62) 0 ['movie_embedding[1][0]'] \n",
" da) \n",
" \n",
" tf.expand_dims (TFOpLambda) (None, 3, 1) 0 ['sequence_ratings[0][0]'] \n",
" \n",
" multiply (Multiply) (None, 3, 62) 0 ['tf.__operators__.add[0][0]', \n",
" 'tf.expand_dims[0][0]'] \n",
" \n",
" target_movie_id (InputLayer) [(None, 1)] 0 [] \n",
" \n",
" tf.unstack (TFOpLambda) [(None, 62), 0 ['multiply[0][0]'] \n",
" (None, 62), \n",
" (None, 62)] \n",
" \n",
" tf.expand_dims_1 (TFOpLambda) (None, 1, 62) 0 ['tf.unstack[0][0]'] \n",
" \n",
" tf.expand_dims_2 (TFOpLambda) (None, 1, 62) 0 ['tf.unstack[0][1]'] \n",
" \n",
" tf.expand_dims_3 (TFOpLambda) (None, 1, 62) 0 ['tf.unstack[0][2]'] \n",
" \n",
" concatenate (Concatenate) (None, 4, 62) 0 ['tf.expand_dims_1[0][0]', \n",
" 'tf.expand_dims_2[0][0]', \n",
" 'tf.expand_dims_3[0][0]', \n",
" 'movie_embedding[0][0]'] \n",
" \n",
" multi_head_attention (MultiHea (None, 4, 62) 46748 ['concatenate[0][0]', \n",
" dAttention) 'concatenate[0][0]'] \n",
" \n",
" dropout (Dropout) (None, 4, 62) 0 ['multi_head_attention[0][0]'] \n",
" \n",
" add (Add) (None, 4, 62) 0 ['concatenate[0][0]', \n",
" 'dropout[0][0]'] \n",
" \n",
" layer_normalization (LayerNorm (None, 4, 62) 124 ['add[0][0]'] \n",
" alization) \n",
" \n",
" leaky_re_lu (LeakyReLU) (None, 4, 62) 0 ['layer_normalization[0][0]'] \n",
" \n",
" dense (Dense) (None, 4, 62) 3906 ['leaky_re_lu[0][0]'] \n",
" \n",
" dropout_1 (Dropout) (None, 4, 62) 0 ['dense[0][0]'] \n",
" \n",
" add_1 (Add) (None, 4, 62) 0 ['layer_normalization[0][0]', \n",
" 'dropout_1[0][0]'] \n",
" \n",
" layer_normalization_1 (LayerNo (None, 4, 62) 124 ['add_1[0][0]'] \n",
" rmalization) \n",
" \n",
" flatten (Flatten) (None, 248) 0 ['layer_normalization_1[0][0]'] \n",
" \n",
" dense_1 (Dense) (None, 256) 63744 ['flatten[0][0]'] \n",
" \n",
" batch_normalization (BatchNorm (None, 256) 1024 ['dense_1[0][0]'] \n",
" alization) \n",
" \n",
" leaky_re_lu_1 (LeakyReLU) (None, 256) 0 ['batch_normalization[0][0]'] \n",
" \n",
" dropout_2 (Dropout) (None, 256) 0 ['leaky_re_lu_1[0][0]'] \n",
" \n",
" dense_2 (Dense) (None, 128) 32896 ['dropout_2[0][0]'] \n",
" \n",
" batch_normalization_1 (BatchNo (None, 128) 512 ['dense_2[0][0]'] \n",
" rmalization) \n",
" \n",
" leaky_re_lu_2 (LeakyReLU) (None, 128) 0 ['batch_normalization_1[0][0]'] \n",
" \n",
" dropout_3 (Dropout) (None, 128) 0 ['leaky_re_lu_2[0][0]'] \n",
" \n",
" age_group (InputLayer) [(None, 1)] 0 [] \n",
" \n",
" occupation (InputLayer) [(None, 1)] 0 [] \n",
" \n",
" sex (InputLayer) [(None, 1)] 0 [] \n",
" \n",
" user_id (InputLayer) [(None, 1)] 0 [] \n",
" \n",
" dense_3 (Dense) (None, 1) 129 ['dropout_3[0][0]'] \n",
" \n",
"==================================================================================================\n",
"Total params: 389,953\n",
"Trainable params: 389,185\n",
"Non-trainable params: 768\n",
"__________________________________________________________________________________________________\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"Evaluate sample fit."
],
"metadata": {
"id": "lk4LBenr3C9s"
}
},
{
"cell_type": "code",
"source": [
"train_history = pd.DataFrame(history.history)\n",
"train_history['epoch'] = history.epoch\n",
"sns.lineplot(x='epoch', y ='loss', data =train_history)\n",
"sns.lineplot(x='epoch', y ='val_loss', data =train_history)\n",
"plt.legend(labels=['train_loss', 'val_loss'])\n",
"plt.show()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 449
},
"id": "ghwSpXZg2-oX",
"outputId": "659879fb-3629-4895-8390-99b541b568be"
},
"execution_count": null,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 640x480 with 1 Axes>"
],
"image/png": "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\n"
},
"metadata": {}
}
]
},
{
"cell_type": "code",
"source": [
"model.save(filepath='/content/drive/MyDrive/Colab Notebooks/DSCI619/Project 7/model-save')"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "kIrhqbgFezBJ",
"outputId": "a97c8b70-c2c9-494e-cbe1-eaf6be2be2a8"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stderr",
"text": [
"WARNING:absl:Found untraced functions such as _update_step_xla, query_layer_call_fn, query_layer_call_and_return_conditional_losses, key_layer_call_fn, key_layer_call_and_return_conditional_losses while saving (showing 5 of 13). These functions will not be directly callable after loading.\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"## Recommend the Movies\n",
"\n",
"Above, we've built, trained, and evaluated a BST recommender model, as required by the assignment. However, I wanted to try to take it a step further and get it to spit out some recommendations."
],
"metadata": {
"id": "Di5F2SpL3ZNH"
}
},
{
"cell_type": "code",
"source": [
"test_users = test_data[\"user_id\"].unique()\n",
"user_id = np.random.choice(test_users, size=1)[0]\n",
"print(\"Recommendations for User:\", user_id)\n",
"predicted_ratings = model.predict((test_dataset))\n",
"predicted_ratings = predicted_ratings[0]\n",
"movie_ids = np.array(movie_ids)\n",
"predicted_ratings = np.squeeze(predicted_ratings)\n",
"top_ratings_indices = predicted_ratings.argsort()[-10:][::-1]\n",
"recommended_movie_ids = [movie_ids[i] for i in top_ratings_indices]\n",
"print(\"Top 10 Highest-Rated Movies\")\n",
"print(\"\\n\")\n",
"top_movies = ratings_df.loc[ratings_df[\"user_id\"] == user_id].sort_values(\"rating\",ascending=False).head(10)[[\"movie_id\",\"rating\"]]\n",
"top_movies = top_movies.join(movies_df.set_index(\"movie_id\"), on=\"movie_id\")[[\"title\",\"genres\"]]\n",
"print(tabulate(top_movies, headers=[\"Title\", \"Genres\"], tablefmt=\"grid\"))\n",
"print(\"\\n\")\n",
"print(\"Top 10 Movie Recommendations\")\n",
"print(\"\\n\")\n",
"recommendations = movies_df[movies_df[\"movie_id\"].isin(recommended_movie_ids)][[\"title\",\"genres\"]]\n",
"print(tabulate(recommendations, headers=[\"Title\", \"Genres\"], tablefmt=\"grid\"))"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "TLYgEbq1_HuT",
"outputId": "b93d4069-5a89-4278-c4b5-45c9689a3299"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Recommendations for User: user_584\n",
"782/782 [==============================] - 8s 10ms/step\n",
"Top 10 Highest-Rated Movies\n",
"\n",
"\n",
"+-------+-------------------------------------------+------------------------------------+\n",
"| | Title | Genres |\n",
"+=======+===========================================+====================================+\n",
"| 88439 | Star Wars: Episode IV - A New Hope (1977) | Action|Adventure|Fantasy|Sci-Fi |\n",
"+-------+-------------------------------------------+------------------------------------+\n",
"| 88461 | Secrets & Lies (1996) | Drama |\n",
"+-------+-------------------------------------------+------------------------------------+\n",
"| 88449 | Jerry Maguire (1996) | Drama|Romance |\n",
"+-------+-------------------------------------------+------------------------------------+\n",
"| 88445 | In the Name of the Father (1993) | Drama |\n",
"+-------+-------------------------------------------+------------------------------------+\n",
"| 88457 | Apollo 13 (1995) | Drama |\n",
"+-------+-------------------------------------------+------------------------------------+\n",
"| 88441 | American Beauty (1999) | Comedy|Drama |\n",
"+-------+-------------------------------------------+------------------------------------+\n",
"| 88440 | Pulp Fiction (1994) | Crime|Drama |\n",
"+-------+-------------------------------------------+------------------------------------+\n",
"| 88459 | Dead Man Walking (1995) | Drama |\n",
"+-------+-------------------------------------------+------------------------------------+\n",
"| 88450 | Big Chill, The (1983) | Comedy|Drama |\n",
"+-------+-------------------------------------------+------------------------------------+\n",
"| 88434 | Wizard of Oz, The (1939) | Adventure|Children's|Drama|Musical |\n",
"+-------+-------------------------------------------+------------------------------------+\n",
"\n",
"\n",
"Top 10 Movie Recommendations\n",
"\n",
"\n",
"+------+----------------------------------------+----------+\n",
"| | Title | Genres |\n",
"+======+========================================+==========+\n",
"| 1176 | One Flew Over the Cuckoo's Nest (1975) | Drama |\n",
"+------+----------------------------------------+----------+\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"This code has given me much trouble. As you can see above, I've only been able to output one movie recommendation instead of the expected 10. Other attempts failed to output any recommendations, output non-human-readable text, or error out completely. Future research should rebuild the code with the intentions of outputting recommendations. After that, I would recommend testing this in a development environment in which the dataset evolves over time and the model must reconsider new data as it arrives."
],
"metadata": {
"id": "3CCGFjZajQYj"
}
},
{
"cell_type": "code",
"source": [],
"metadata": {
"id": "X6_I8U8nj28u"
},
"execution_count": null,
"outputs": []
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment