From 03fc9e3c9fde066e1bd26663769e28e33ff20686 Mon Sep 17 00:00:00 2001
From: Adrienucl <adrien.payen@student.uclouvain.be>
Date: Thu, 2 May 2024 10:01:18 +0200
Subject: [PATCH] git merge all branch to main

---
 analytics_small.ipynb | 303 ++---------------
 analytics_test.ipynb  | 449 +++++++++++++++++++++++++
 analytics_tiny.ipynb  | 306 ++---------------
 content_based.ipynb   |  22 +-
 loaders.py            |  51 +++
 models.py             | 181 ++++++++++
 user_based.ipynb      | 762 ++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 1499 insertions(+), 575 deletions(-)
 create mode 100644 analytics_test.ipynb
 create mode 100644 loaders.py
 create mode 100644 models.py
 create mode 100644 user_based.ipynb

diff --git a/analytics_small.ipynb b/analytics_small.ipynb
index b6f7494f..b41000c2 100644
--- a/analytics_small.ipynb
+++ b/analytics_small.ipynb
@@ -6,274 +6,15 @@
    "metadata": {},
    "outputs": [
     {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Display The Movies : \n"
-     ]
-    },
-    {
-     "data": {
-      "text/html": [
-       "<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>title</th>\n",
-       "      <th>genres</th>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>movieId</th>\n",
-       "      <th></th>\n",
-       "      <th></th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>Grumpier Old Men (1995)</td>\n",
-       "      <td>Comedy|Romance</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>15</th>\n",
-       "      <td>Cutthroat Island (1995)</td>\n",
-       "      <td>Action|Adventure|Romance</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>34</th>\n",
-       "      <td>Babe (1995)</td>\n",
-       "      <td>Children|Drama</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>59</th>\n",
-       "      <td>Confessional, The (Confessionnal, Le) (1995)</td>\n",
-       "      <td>Drama|Mystery</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>64</th>\n",
-       "      <td>Two if by Sea (1996)</td>\n",
-       "      <td>Comedy|Romance</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>...</th>\n",
-       "      <td>...</td>\n",
-       "      <td>...</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>148652</th>\n",
-       "      <td>The Ridiculous 6 (2015)</td>\n",
-       "      <td>Comedy|Western</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>151307</th>\n",
-       "      <td>The Lovers and the Despot</td>\n",
-       "      <td>(no genres listed)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>152173</th>\n",
-       "      <td>Michael Jackson's Thriller (1983)</td>\n",
-       "      <td>Horror</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>160440</th>\n",
-       "      <td>The Maid's Room (2014)</td>\n",
-       "      <td>Thriller</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>160656</th>\n",
-       "      <td>Tallulah (2016)</td>\n",
-       "      <td>Drama</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "<p>912 rows × 2 columns</p>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "                                                title  \\\n",
-       "movieId                                                 \n",
-       "3                             Grumpier Old Men (1995)   \n",
-       "15                            Cutthroat Island (1995)   \n",
-       "34                                        Babe (1995)   \n",
-       "59       Confessional, The (Confessionnal, Le) (1995)   \n",
-       "64                               Two if by Sea (1996)   \n",
-       "...                                               ...   \n",
-       "148652                        The Ridiculous 6 (2015)   \n",
-       "151307                      The Lovers and the Despot   \n",
-       "152173              Michael Jackson's Thriller (1983)   \n",
-       "160440                         The Maid's Room (2014)   \n",
-       "160656                                Tallulah (2016)   \n",
-       "\n",
-       "                           genres  \n",
-       "movieId                            \n",
-       "3                  Comedy|Romance  \n",
-       "15       Action|Adventure|Romance  \n",
-       "34                 Children|Drama  \n",
-       "59                  Drama|Mystery  \n",
-       "64                 Comedy|Romance  \n",
-       "...                           ...  \n",
-       "148652             Comedy|Western  \n",
-       "151307         (no genres listed)  \n",
-       "152173                     Horror  \n",
-       "160440                   Thriller  \n",
-       "160656                      Drama  \n",
-       "\n",
-       "[912 rows x 2 columns]"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Display The Ratings : \n"
+     "ename": "ImportError",
+     "evalue": "cannot import name 'Constant' from 'constants' (/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/constants.py)",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[0;31mImportError\u001b[0m                               Traceback (most recent call last)",
+      "Cell \u001b[0;32mIn[1], line 12\u001b[0m\n\u001b[1;32m      9\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mscipy\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01msparse\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m csr_matrix\n\u001b[1;32m     11\u001b[0m \u001b[38;5;66;03m# Constants and functions\u001b[39;00m\n\u001b[0;32m---> 12\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mconstants\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Constant \u001b[38;5;28;01mas\u001b[39;00m C\n\u001b[1;32m     13\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mloaders\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m load_ratings\n\u001b[1;32m     14\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mloaders\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m load_items\n",
+      "\u001b[0;31mImportError\u001b[0m: cannot import name 'Constant' from 'constants' (/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/constants.py)"
      ]
-    },
-    {
-     "data": {
-      "text/html": [
-       "<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>userId</th>\n",
-       "      <th>movieId</th>\n",
-       "      <th>rating</th>\n",
-       "      <th>timestamp</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>15</td>\n",
-       "      <td>34</td>\n",
-       "      <td>3.0</td>\n",
-       "      <td>997938310</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>15</td>\n",
-       "      <td>95</td>\n",
-       "      <td>1.5</td>\n",
-       "      <td>1093028331</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>15</td>\n",
-       "      <td>101</td>\n",
-       "      <td>4.0</td>\n",
-       "      <td>1134522072</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>15</td>\n",
-       "      <td>123</td>\n",
-       "      <td>4.0</td>\n",
-       "      <td>997938358</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>15</td>\n",
-       "      <td>125</td>\n",
-       "      <td>3.5</td>\n",
-       "      <td>1245362506</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>...</th>\n",
-       "      <td>...</td>\n",
-       "      <td>...</td>\n",
-       "      <td>...</td>\n",
-       "      <td>...</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>5291</th>\n",
-       "      <td>665</td>\n",
-       "      <td>3908</td>\n",
-       "      <td>1.0</td>\n",
-       "      <td>1046967201</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>5292</th>\n",
-       "      <td>665</td>\n",
-       "      <td>4052</td>\n",
-       "      <td>4.0</td>\n",
-       "      <td>992838277</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>5293</th>\n",
-       "      <td>665</td>\n",
-       "      <td>4351</td>\n",
-       "      <td>4.0</td>\n",
-       "      <td>992837743</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>5294</th>\n",
-       "      <td>665</td>\n",
-       "      <td>4643</td>\n",
-       "      <td>4.0</td>\n",
-       "      <td>997239207</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>5295</th>\n",
-       "      <td>665</td>\n",
-       "      <td>5502</td>\n",
-       "      <td>4.0</td>\n",
-       "      <td>1046967596</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "<p>5296 rows × 4 columns</p>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "      userId  movieId  rating   timestamp\n",
-       "0         15       34     3.0   997938310\n",
-       "1         15       95     1.5  1093028331\n",
-       "2         15      101     4.0  1134522072\n",
-       "3         15      123     4.0   997938358\n",
-       "4         15      125     3.5  1245362506\n",
-       "...      ...      ...     ...         ...\n",
-       "5291     665     3908     1.0  1046967201\n",
-       "5292     665     4052     4.0   992838277\n",
-       "5293     665     4351     4.0   992837743\n",
-       "5294     665     4643     4.0   997239207\n",
-       "5295     665     5502     4.0  1046967596\n",
-       "\n",
-       "[5296 rows x 4 columns]"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
     }
    ],
    "source": [
@@ -311,7 +52,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -330,7 +71,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -355,7 +96,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -407,7 +148,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -426,7 +167,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -445,7 +186,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -465,7 +206,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -488,7 +229,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -511,7 +252,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -545,7 +286,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -575,7 +316,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -608,7 +349,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -625,7 +366,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -688,7 +429,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
diff --git a/analytics_test.ipynb b/analytics_test.ipynb
new file mode 100644
index 00000000..86d849a9
--- /dev/null
+++ b/analytics_test.ipynb
@@ -0,0 +1,449 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [
+    {
+     "ename": "ImportError",
+     "evalue": "cannot import name 'Constant' from 'constants' (/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/constants.py)",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[0;31mImportError\u001b[0m                               Traceback (most recent call last)",
+      "Cell \u001b[0;32mIn[1], line 13\u001b[0m\n\u001b[1;32m     10\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01msklearn\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mlinear_model\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m LinearRegression\n\u001b[1;32m     12\u001b[0m \u001b[38;5;66;03m# Constants and functions\u001b[39;00m\n\u001b[0;32m---> 13\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mconstants\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Constant \u001b[38;5;28;01mas\u001b[39;00m C\n\u001b[1;32m     15\u001b[0m \u001b[38;5;66;03m# We use a pd.read_csv() so importing the loaders is not necessary\u001b[39;00m\n\u001b[1;32m     16\u001b[0m \u001b[38;5;66;03m# from loaders import load_ratings \u001b[39;00m\n\u001b[1;32m     17\u001b[0m \u001b[38;5;66;03m# from loaders import load_items\u001b[39;00m\n\u001b[1;32m     19\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtabulate\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m tabulate\n",
+      "\u001b[0;31mImportError\u001b[0m: cannot import name 'Constant' from 'constants' (/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/constants.py)"
+     ]
+    }
+   ],
+   "source": [
+    "# Reload modules automatically before entering the execution of code\n",
+    "%load_ext autoreload\n",
+    "%autoreload 2\n",
+    "\n",
+    "# Third-party imports\n",
+    "import numpy as np \n",
+    "import pandas as pd\n",
+    "import matplotlib.pyplot as plt\n",
+    "from scipy.sparse import csr_matrix\n",
+    "from sklearn.linear_model import LinearRegression\n",
+    "\n",
+    "# Constants and functions\n",
+    "from constants import Constant as C\n",
+    "\n",
+    "# We use a pd.read_csv() so importing the loaders is not necessary\n",
+    "# from loaders import load_ratings \n",
+    "# from loaders import load_items\n",
+    "\n",
+    "from tabulate import tabulate\n",
+    "\n",
+    "# Call the load_items() function and create a variable df_items\n",
+    "df_movies = pd.read_csv(\"../data/test/content/movies.csv\")\n",
+    "\n",
+    "# Display the DataFrame\n",
+    "print(\"Display The Movies : \")\n",
+    "display(df_movies)\n",
+    "\n",
+    "# Call the load_ratings() function and create a variable df_ratings\n",
+    "df_ratings = pd.read_csv(\"../data/test/evidence/ratings.csv\")\n",
+    "\n",
+    "# Display the DataFrame\n",
+    "print(\"Display The Ratings : \")\n",
+    "display(df_ratings)\n",
+    "\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Number of movies: 10\n"
+     ]
+    }
+   ],
+   "source": [
+    "# NUMBER OF MOVIES\n",
+    "n_movies = df_movies['title'].nunique()\n",
+    "print(f\"Number of movies: {n_movies}\")\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Minimum range: 1973\n",
+      "Maximum range: 2002\n"
+     ]
+    }
+   ],
+   "source": [
+    "# THE YEAR RANGE\n",
+    "df_movies['annee'] = df_movies['title'].str.extract(r'\\((.{4})\\)')\n",
+    "df_movies['annee'] = pd.to_numeric(df_movies['annee'], errors='coerce')\n",
+    "\n",
+    "min_range = int(df_movies['annee'].min())\n",
+    "max_range = int(df_movies['annee'].max())\n",
+    "print(\"Minimum range:\", min_range)\n",
+    "print(\"Maximum range:\", max_range)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "\n",
+      "List of all genres:\n",
+      "Action     |\n",
+      "Adventure  |\n",
+      "Animation  |\n",
+      "Children   |\n",
+      "Comedy     |\n",
+      "Drama      |\n",
+      "Fantasy    |\n",
+      "Horror     |\n",
+      "IMAX       |\n",
+      "Musical    |\n",
+      "Mystery    |\n",
+      "Romance    |\n",
+      "Sci-Fi     |\n",
+      "War        |\n"
+     ]
+    }
+   ],
+   "source": [
+    "# LIST OF MOVIE GENRES\n",
+    "def tabulate_genres(df_movies):\n",
+    "    \"\"\"Tabulate list of movie genres.\"\"\"\n",
+    "    # Split genres and explode\n",
+    "    df_movies['genres'] = df_movies['genres'].str.split('|')\n",
+    "    df_movies = df_movies.explode('genres')\n",
+    "    unique_genres = sorted(df_movies['genres'].unique())\n",
+    "\n",
+    "    # Tabulate\n",
+    "    print(\"\\nList of all genres:\")\n",
+    "    genres_table = [[genre, \"|\"] for genre in unique_genres]\n",
+    "    print(tabulate(genres_table, tablefmt=\"plain\", numalign=\"left\"))\n",
+    "\n",
+    "# Call the tabulate_genres function\n",
+    "tabulate_genres(df_movies)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Number of ratings: 30\n"
+     ]
+    }
+   ],
+   "source": [
+    "# THE TOTAL NUMBER OF RATINGS\n",
+    "n_ratings = df_ratings['rating'].count()\n",
+    "print(f\"Number of ratings: {n_ratings}\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Number of users: 6\n"
+     ]
+    }
+   ],
+   "source": [
+    "# THE NUMBER OF UNIQUE USERS\n",
+    "n_users = df_ratings['userId'].nunique()\n",
+    "print(f\"Number of users: {n_users}\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Number of unique movies : 10\n"
+     ]
+    }
+   ],
+   "source": [
+    "# THE NUMBER OF UNIQUE MOVIES (IN THE RATING MATRIX)\n",
+    "unique_movies = df_ratings[\"movieId\"].unique()\n",
+    "num_unique_movies = len(unique_movies)\n",
+    "print(f\"Number of unique movies : {num_unique_movies}\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Number of ratings of the most rated movie(s): 4\n"
+     ]
+    }
+   ],
+   "source": [
+    "# THE NUMBER OF RATINGS OF THE MOST RATED MOVIES\n",
+    "def most_rated_movies_ratings_count(df_ratings):\n",
+    "    movie_ratings_count = df_ratings.groupby('movieId')['rating'].count()\n",
+    "    most_rated_movies = movie_ratings_count[movie_ratings_count == movie_ratings_count.max()]\n",
+    "    print(f\"Number of ratings of the most rated movie(s): {most_rated_movies.max()}\")\n",
+    "\n",
+    "most_rated_movies_ratings_count(df_ratings)\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Number of ratings of the least rated movie(s): 2\n"
+     ]
+    }
+   ],
+   "source": [
+    "# THE NUMBER OF RATINGS OF THE LESS RATED MOVIES\n",
+    "def least_rated_movies_ratings_count(df_ratings):\n",
+    "    movie_ratings_count = df_ratings.groupby('movieId')['rating'].count()\n",
+    "    least_rated_movies = movie_ratings_count[movie_ratings_count == movie_ratings_count.min()]\n",
+    "    print(\"Number of ratings of the least rated movie(s):\", least_rated_movies.min())\n",
+    "\n",
+    "least_rated_movies_ratings_count(df_ratings)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "All possible rating values, from smallest to highest:\n",
+      "1.0\n",
+      "1.5\n",
+      "2.0\n",
+      "2.5\n",
+      "3.0\n",
+      "4.0\n",
+      "4.5\n",
+      "5.0\n"
+     ]
+    }
+   ],
+   "source": [
+    "# ALL THE POSSIBLE RATING VALUES; FROM THE SMALLEST VALUE TO THE VALUE HIGHEST\n",
+    "def all_possible_ratings(df_ratings):\n",
+    "    rating_values = sorted(df_ratings['rating'].unique())\n",
+    "    print(\"All possible rating values, from smallest to highest:\")\n",
+    "    for rating in rating_values:\n",
+    "        print(rating)\n",
+    "\n",
+    "all_possible_ratings(df_ratings)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Number of movies that were not rated at all: 10\n"
+     ]
+    }
+   ],
+   "source": [
+    "# THE NUMBER OF MOVIES THAT WERE NOT RATED AT ALL\n",
+    "def unrated_movies_count(df_ratings, df_movies):\n",
+    "    rated_movies = df_ratings['movieId'].unique() if 'movieId' in df_ratings.columns else []\n",
+    "    unrated_movies_count = df_movies[~df_movies.index.isin(rated_movies)].shape[0]\n",
+    "    print(\"Number of movies that were not rated at all:\", unrated_movies_count)\n",
+    "\n",
+    "unrated_movies_count(df_ratings, df_movies)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "LONG-TAIL PROPERTY"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAB8YAAAJOCAYAAADF3G1CAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAADmRklEQVR4nOzdeZhcZZn//89zTi29VnX2hYQQdpKA4ETW34AMoCIDjMrgimwjonxHZRk0DKCAKO7AAIFRJKAjKAKDKyKjwKhBYQARBUVkCyRk7apOL7Wc8/z+qKWrUpV0V9JPV6Xzfl0XV1fXqTp1qtMfGnL3fd/GWmsFAAAAAAAAAAAAAMAE5TX7AgAAAAAAAAAAAAAAcInCOAAAAAAAAAAAAABgQqMwDgAAAAAAAAAAAACY0CiMAwAAAAAAAAAAAAAmNArjAAAAAAAAAAAAAIAJjcI4AAAAAAAAAAAAAGBCozAOAAAAAAAAAAAAAJjQKIwDAAAAAAAAAAAAACY0CuMAAAAAAAAAAAAAgAmNwjgAAAAA7OCWLVsmY0z5n0gkolmzZuk973mPnnvuua0+7+c+9zn993//d839Dz74oIwxevDBB7f+ordS6bXr/XPSSSeN+/VMZJt+rWOxmKZNm6bDDjtM//7v/66XXnqp5jml78UXX3yxodfa3PfaltR7rTe/+c1atGhRQ+cZyU9+8hN95jOfqXtsl1120WmnnTamrwcAAAAAqC/S7AsAAAAAALSGW265RXvvvbeGhob061//WldeeaV++ctf6tlnn9WkSZMaPt/nPvc5nXTSSfqnf/qnqvvf+MY3avny5VqwYMEYXXnjPve5z+nII4+sum/KlClNupqJrfS1DoJA69at029/+1t985vf1Ne+9jV9/etf1/vf//7yY4877jgtX75cs2bNavg16n2vbcnWvlajfvKTn+j666+vWxy/5557lEgknL4+AAAAAKCAwjgAAAAAQJK0aNEiLV68WFKhczYIAn3605/Wf//3f+v0008fs9dJJBI6+OCDx+x8W2OPPfYY9TUEQaB8Pq94PO74qiamTb/WJ5xwgs4//3wdffTROu2007Tffvtp3333lSRNmzZN06ZNc3o9g4ODamtrG5fXGskBBxzQ1NcHAAAAgB0Jo9QBAAAAAHWViuSvv/56+b6hoSGdf/752n///ZVMJjV58mQdcsghuvfee6uea4xRf3+/br311vIo7Te/+c2S6o9SP+2009TV1aW//vWvevvb366uri7NnTtX559/vjKZTNW5V6xYoZNOOknd3d3q6enR+9//fj366KMyxmjZsmXb9J5ffPFFGWP0xS9+UZ/97Gc1f/58xeNx/fKXv5QkPfbYYzrhhBM0efJktbW16YADDtD3vve9mvM88sgjOuyww9TW1qbZs2dryZIl+vrXv14zutsYU7eTuN6I7VWrVunDH/6w5syZo1gspvnz5+uyyy5TPp+vuf4vf/nL+upXv6r58+erq6tLhxxyiB555JGa1/ntb3+r448/XlOmTFFbW5t22203feITn5Ak/e///q+MMbr99ttrnnfbbbfJGKNHH310FF/VWpMnT9ZNN92kfD6vr33ta+X76403f+KJJ/SP//iPmj59uuLxuGbPnq3jjjtOK1askLTl77XS+e6//36dccYZmjZtmjo6OpTJZLY4tv1///d/dfDBB6u9vV077bSTLrnkEgVBUD6+uXUApa9/6fvwtNNO0/XXX1++ztI/pdes9+f88ssv6wMf+ED5/e6zzz76yle+ojAMa15ntH/OAAAAAAA6xgEAAAAAm/HCCy9Ikvbcc8/yfZlMRuvXr9cFF1ygnXbaSdlsVg888IDe+c536pZbbtEHP/hBSdLy5cv1D//wDzryyCN1ySWXSNKII6NzuZxOOOEEnXnmmTr//PP18MMP64orrlAymdSll14qServ79eRRx6p9evX6wtf+IJ233133XfffXr3u9/d0HsLw7CqoCxJkcjw/yJfe+212nPPPfXlL39ZiURCe+yxh375y1/qbW97mw466CDdeOONSiaTuuOOO/Tud79bAwMD5QLnn/70Jx111FHaZZddtGzZMnV0dOiGG27Qd77znYausdKqVat04IEHyvM8XXrppdptt920fPlyffazn9WLL76oW265perx119/vfbee29dffXVkqRLLrlEb3/72/XCCy8omUxKkn72s5/p+OOP1z777KOvfvWr2nnnnfXiiy/q/vvvlyT9/d//vQ444ABdf/31eu9731t1/uuuu05vetOb9KY3vWmr39Ob3vQmzZo1Sw8//PBmH9Pf369jjjlG8+fP1/XXX68ZM2Zo1apV+uUvf6m+vj5Jo/teO+OMM3TcccfpW9/6lvr7+xWNRjf7mqtWrdJ73vMefepTn9Lll1+uH//4x/rsZz+rDRs26LrrrmvoPV5yySXq7+/X97//fS1fvrx8/+bGt69Zs0aHHnqostmsrrjiCu2yyy760Y9+pAsuuEDPP/+8brjhhqrHj+bPGQAAAABQQGEcAAAAACBpeGR4acf4Zz/7WR1++OE64YQTyo9JJpNVRdggCHTUUUdpw4YNuvrqq8uF8YMPPlie52natGmjHlmezWZ12WWX6Z//+Z8lSUcddZQee+wxfec73ykXxm+99Vb99a9/1U9/+lO97W1vkyS95S1v0cDAgG666aZRv9d6hfTnnnuuXBxva2vTz372s6oC6rHHHquFCxfqF7/4Rflxb33rW7V27VpddNFF+uAHPyjP83T55ZfLWqtf/OIXmjFjhqTCPutFixaN+vo29ZnPfEYbNmzQH//4R+28886SCl+f9vZ2XXDBBfq3f/u3qp3t3d3d+tGPfiTf9yVJs2fP1oEHHqif/vSnes973iNJOuecc7Tzzjvrt7/9rdra2srPrRyb/7GPfUynn366nnzySe2///6SpEcffVSPPvqobr311q1+PyU777yznnrqqc0ef/bZZ7Vu3TrdfPPNOvHEE8v3n3zyyeXbo/leO+qoo0b9/bFu3Trde++95e/7t7zlLRocHNTSpUt14YUXlr/+o7HbbruVvwdGk4OvfvWrevXVV/Xb3/5WBx54oKTC91gQBLrxxhv1iU98ouoXVUbz5wwAAAAAKGCUOgAAAABAUqFwF41G1d3drbe97W2aNGmS7r333qpOakm68847ddhhh6mrq0uRSETRaFQ333yznnnmmW16fWOMjj/++Kr79ttvP7300kvlzx966KHy9VXatKN5JF/4whfKBd7SP3Pnzi0fP+GEE6qK4n/961/17LPP6v3vf78kKZ/Pl/95+9vfrpUrV+rPf/6zJOmXv/yljjrqqHJBVJJ832+4q73Sj370Ix155JGaPXt21Wsfe+yxkgpfl0rHHXdcuVgqFb6Okspfy7/85S96/vnndeaZZ1YVxTf13ve+V9OnTy+PA5ek//iP/9C0adO26f2UWGu3eHz33XfXpEmT9MlPflI33nij/vSnP23V67zrXe8a9WO7u7urfhlEkt73vvcpDMMtdrePhV/84hdasGBBuShectppp5V/2aLSSH/OAAAAAIBhFMYBAAAAAJIKe6MfffRR/eIXv9CHP/xhPfPMMzUF57vvvlsnn3yydtppJ33729/W8uXL9eijj+qMM87Q0NDQNr1+R0dHTZE2Ho9XnXfdunVVBeeSevdtya677qrFixdX/ROPx8vHNx11XdqzfsEFFygajVb989GPflSStHbt2vI1zpw5s+Y16903Wq+//rp++MMf1rz2woULq167ZMqUKVWfl97b4OCgpMLIbkmaM2fOFl83Ho/rwx/+sL7zne+ot7dXa9as0fe+9z39y7/8S9XXa2u9/PLLmj179maPJ5NJPfTQQ9p///110UUXaeHChZo9e7Y+/elPK5fLjfp1Nje6vJ5630ulP7t169aN+jxbY926dXWvtfQ12vT1R/pzBgAAAAAMY5Q6AAAAAECStM8++2jx4sWSpCOPPFJBEOgb3/iGvv/97+ukk06SJH3729/W/Pnz9d3vflfGmPJzM5nMuFzjlClT9Lvf/a7m/lWrVo3p61S+N0maOnWqJGnJkiV65zvfWfc5e+21V/ka611Pvfvi8Xjdr92mBdCpU6dqv/3205VXXln3tbdUXK5n2rRpkqQVK1aM+NiPfOQjuuqqq/TNb35TQ0NDyufzOvvssxt6vXp+97vfadWqVTrzzDO3+Lh9991Xd9xxh6y1euqpp7Rs2TJdfvnlam9v16c+9alRvdamf55bUvoliEqlP7tSIbr0Cxyb/tlt+gsKjZoyZYpWrlxZc/9rr70mafj7EAAAAADQODrGAQAAAAB1ffGLX9SkSZN06aWXKgxDSYUCYywWqyo0rlq1Svfee2/N8+Px+Jh3rh5xxBHq6+vTT3/606r777jjjjF9nU3ttdde2mOPPfT73/++ptO89E93d7ekwi8V/M///E9VgTUIAn33u9+tOe8uu+xSs2P7F7/4hTZu3Fh13z/+4z/q6aef1m677Vb3tRstjO+5557abbfd9M1vfnPEX2qYNWuW/vmf/1k33HCDbrzxRh1//PEN7dmuZ/369Tr77LMVjUZ17rnnjuo5xhi94Q1v0Ne+9jX19PTo8ccfLx8by++1vr4+/eAHP6i67zvf+Y48z9Phhx8uqfDnJqnmz27T55WuTRpdF/dRRx2lP/3pT1XvTSpMczDG6Mgjjxz1+wAAAAAAVKNjHAAAAABQ16RJk7RkyRJdeOGF+s53vqMPfOAD+sd//Efdfffd+uhHP6qTTjpJr7zyiq644grNmjVLzz33XNXz9913Xz344IP64Q9/qFmzZqm7u7vcVb21Tj31VH3ta1/TBz7wAX32s5/V7rvvrp/+9Kf62c9+JknyPHe//33TTTfp2GOP1Vvf+laddtpp2mmnnbR+/Xo988wzevzxx3XnnXdKki6++GL94Ac/0D/8wz/o0ksvVUdHh66//nr19/fXnPOUU07RJZdcoksvvVRHHHGE/vSnP+m6665TMpmsetzll1+un//85zr00EP1sY99THvttZeGhob04osv6ic/+YluvPHGEceib+r666/X8ccfr4MPPljnnnuudt55Z7388sv62c9+pv/6r/+qeuzHP/5xHXTQQZKkW265paHXee655/TII48oDEOtW7dOv/3tb3XzzTcrnU7rtttuK4+Dr+dHP/qRbrjhBv3TP/2Tdt11V1lrdffdd6u3t1fHHHNM+XFj+b02ZcoUfeQjH9HLL7+sPffcUz/5yU/09a9/XR/5yEfKvxAwc+ZMHX300fr85z+vSZMmad68efqf//kf3X333TXn23fffSUV9tofe+yx8n1f++23n2KxWM1jzz33XN1222067rjjdPnll2vevHn68Y9/rBtuuEEf+chHtOeee27VewIAAAAAUBgHAAAAAGzBv/7rv+q6667T5Zdfrve+9706/fTTtXr1at1444365je/qV133VWf+tSntGLFCl122WVVz73mmmt0zjnn6D3veY8GBgZ0xBFH6MEHH9ym6+ns7NQvfvELfeITn9CFF14oY4ze8pa36IYbbtDb3/529fT0bNP5t+TII4/U7373O1155ZX6xCc+oQ0bNmjKlClasGCBTj755PLjFi1apAceeEDnn3++Tj31VE2aNEmnnHKK3vWud+mss86qOue//du/KZ1Oa9myZfryl7+sAw88UN/73vd04oknVj1u1qxZeuyxx3TFFVfoS1/6klasWKHu7m7Nnz9fb3vb2zRp0qSG389b3/pWPfzww7r88sv1sY99TENDQ5ozZ45OOOGEmsceeOCB2mWXXdTe3q6jjjqqode56KKLJEmRSETJZFJ77rmnzjjjDJ111lmaN2/eFp+7xx57qKenR1/84hf12muvKRaLaa+99tKyZct06qmnlh83lt9rM2fO1PXXX68LLrhAf/jDHzR58mRddNFFNd/f3/rWt/Sv//qv+uQnP6kgCHT8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XupAjzKmHdhq/BwD3CJjgFtkDHCLjAFukTHALTLWGppeGP/DH/6gQw45RENDQ+rq6tI999yjBQsWjPi8lStX6qc//am+853vVN2/9957a9myZdp3332VTqd1zTXX6LDDDtPvf/977bHHHnXP9fnPf16XXXbZmLwfbFkQBHr66ae1aNEi+X79Tj8AW4+MAVtmjFFn1Kgz6ml2Z+3x0FaOaS+MZq/sOu/PW2VDq9WDgVYPBnVfozNiinvNq7vNS/vNPca0A5vFzzHALTIGuEXGALfIGOAWGQPcImOtoemF8b322ktPPvmkent7ddddd+nUU0/VQw89NGJxfNmyZerp6dE//dM/Vd1/8MEH6+CDDy5/fthhh+mNb3yj/uM//kPXXntt3XMtWbJE5513XvnzdDqtuXPnbv2bwmZZazU4OChrbbMvBZiQyBiwbTxj1BMvFLXn1Tk+mM3pN088pbl7LFBfoPLI9kLneahMYNWft+rP5/XaQO2YdqPCmPZSt3m5eB73lYz56owY9ptjh8bPMcAtMga4RcYAt8gY4BYZA9wiY62h6YXxWCym3XffXZK0ePFiPfroo7rmmmt00003bfY51lp985vf1CmnnKJYLLbF83uepze96U167rnnNvuYeDyueDy+dW8AAADsMKKeUafNaddEVJFI7X9GDeVD9RY7zFOZ4a7z3mIXemBV3HUe6uU6548YFUeze3W7ztt8dhABAAAAAAAAwNZoemF8U9baqn3f9Tz00EP661//qjPPPHNU53vyySe17777jtUlAgAA1NUW8TQz4mlmR/395hvzYXmfefljNlAqEyqdC5W30rqhQOuGAkm1+83bfFM1mr3UbV4a0x5hvzkAAAAAAAAA1NXUwvhFF12kY489VnPnzlVfX5/uuOMOPfjgg7rvvvskFUacv/rqq7rtttuqnnfzzTfroIMO0qJFi2rOedlll+nggw/WHnvsoXQ6rWuvvVZPPvmkrr/++nF5T9gy3/e19957sz8BcISMAW5tS8aMMeqO+uqO+pqraM3xILRK5wqj2VObdJ33ZgMN5q2GAqtVg3mtGqz/Gt1RT8lyt3mp07zQgd4VZb85Wh8/xwC3yBjgFhkD3CJjgFtkDHCLjLWGphbGX3/9dZ1yyilauXKlksmk9ttvP91333065phjJEkrV67Uyy9XDxpNpVK66667dM0119Q9Z29vr8466yytWrVKyWRSBxxwgB5++GEdeOCBzt8PRmaMUU9PT7MvA5iwyBjglsuM+Z7RpLivSfH6/3GcCcLiGPZCt3mp07zUdZ4Lpb5cqL5cqBX9tfvNPaPhonmxcF7adZ6M+2r32W+O5uPnGOAWGQPcImOAW2QMcIuMAW6RsdZgLFvea6TTaSWTSaVSKSUSiWZfzoSSz+f1xBNP6IADDqi7mxXAtiFjgFutmjFrrQbztlAszxa6zitvp7OhwhHOEfNMeTR75V7zwsh2XzGfojnca9WMARMFGQPcImOAW2QMcIuMAW6RMXcaqevylce4C4Kg2ZcATGhkDHCrFTNmjFFH1Kgj6ml2Z+3x0Fr11YxpL3Wbh9qYC5UNrdYMBVozVP/9dURM1V7zUrd5T9xXd8yTT7c5xkgrZgyYSMgY4BYZA9wiY4BbZAxwi4w1H4VxAACACc4zRsli53c9udAqXRzRXiqWF0a2F25nAquBvNVAPq/XBmqfbyR1l7rMi8XynvKuc1+dEca0AwAAAAAAAGguCuMAAAA7uKhnNKUtoilt9Y8P5cOqYvmmXed5K6WzodLZ+gPbI0aFwnzcq9t13hbxHL47AAAAAAAAAGDHeF3sGHfHWqvBwUG1t7fTOQY4QMYAt8hYLWut+vO2Zq95qXjelw010n9sxn1THsteKpYXus0LHyMeX+sdBRkD3CJjgFtkDHCLjAFukTHALTLmDjvG0dJisVizLwGY0MgY4BYZq2aMUVfUqCvqaY6iNccDa5XOhkplake0p7KBBvJWmcDq9cFArw/W37PUFfXKxfJS13lP8XZ31JPH/0xMKGQMcIuMAW6RMcAtMga4RcYAt8hY81EYx7gKgkCPPfaYFi9erEiEbz9grJExwC0y1jjfGE2K+5oUr7/fPBvY4l7zwmj23tKO82LXeTa02pgLtTEXakV/vub5npES0dJe88ox7YUCejv7zbcrZAxwi4wBbpExwC0yBrhFxgC3yFhr4CsPAACApon5RtPaI5rWXvufpdZaDQa23G1etd+8OLY9tCocy4aScrXn94ySMU/JeO2I9p6Yr5hP0RwAAAAAAADYEVAYBwAAQEsyxqgjYtQR8TSrs/Z4aK36cmG507wwpr04rj1b6DLPhlZrhgKtGao/pr0jYob3mhe7zku3EzFPPt3mAAAAAAAAwIRAYRwAAADbJc8UitrJmK+d6+w3z4e23Fle2W1euj0UWA3krQbyea0cqD2/kdQd9Yb3mleMaE/GPXVFPMa0AwAAAAAAANsJY621zb6IVpNOp5VMJpVKpZRIJJp9OROKtVZBEMj3ff4iGXCAjAFukbGJZSgY7jYvFctTFTvO8yP8V7JvVB7NXtpvXtl13hbxxueNTCBkDHCLjAFukTHALTIGuEXGALfImDuN1HXpGMe4y2azam9vb/ZlABMWGQPcImMTR5vvqa3D04yO+vvN+/O2WDAfLpb3Fovn6WyowErrM4HWZwLV228e903FXvPhbvPSjvOIx/8E1UPGALfIGOAWGQPcImOAW2QMcIuMNR+FcYyrIAj01FNPafHixYpE+PYDxhoZA9wiYzsOY4y6okZdUU9z6oxpD6xVX3E0+/CO88LI9t5soIG8VSawen0w0OuD9febd0WGx7QnY95w8TzuqzvqydsBf3uYjAFukTHALTIGuEXGALfIGOAWGWsNfOUBAACABvnGqCde6ARXd+3xbFCx37xiVHvpYza02pgPtTEf6tX+fM3zPUmJcrd5aa+5X+5A74gYxm4BAAAAAAAADaAwDgAAAIyxmG80rT2iaXWmY1lrNRTYYsG8uNe89LFiTHtvNlRvNtRLG2vPEfVU7DT363adx332mwMAAAAAAACVKIxj3Pm+3+xLACY0Mga4RcawrYwxao8YtUc8zeqoPR5aq425sGKveXW3eV8uVC6U1gwFWjNUf0x7e8QMF8sriuc9cV+JqCe/hfebkzHALTIGuEXGALfIGOAWGQPcImPNZ6y1ttkX0WrS6bSSyaRSqZQSiUSzLwcAAAAoy4dW6dJ+82K3eeWu86Fgy/95byR1Rwv7zZMxv1gwL9321BX1GNMOAAAAAACA7UIjdV06xjGurLVKpVJKJpP8hSvgABkD3CJjaAURz2hym6/JbfV/yzgThBWj2Uud5sNd53krpXOh0rlQr6h2v7lvVC6SV+41L41pb/Pd7TcnY4BbZAxwi4wBbpExwC0yBrhFxloDhXGMqyAI9Oyzz2rx4sWKRPj2A8YaGQPcImPYHsR9TzM6PM3oqP0etdZqIF/ab14slld0m5f2m6/PBFqfCaS+XJ3zm+ER7eW95sNd59FtGNNOxgC3yBjgFhkD3CJjgFtkDHCLjLUGvvIAAADADsIYo86oUWfU006d0Zrjoa0c017acT7cdd6ft8oEVqsHA60erL/fvDNihovlm3SdJ2KePH4rGgAAAAAAAE1AYRwAAACAJMkzhaJ2T7z+mPZcaMvF8lSx67x0O5UJlQmt+vNW/fm8Xu2vHdPuSeoudpv3VOw4T8YL98W05f3oAAAAAAAAwNaiMI5xZYxRe3s7+xMAR8gY4BYZw44u6hlNbY9oanvtMWuthgJb3mXeW7HXPFUsngdWxduhXtpY5/xGisfn6ZUX+9XTVtF1Xiykx33P/ZsEJjB+jgFukTHALTIGuEXGALfIWGsw1lraMjaRTqeVTCaVSqWUSCSafTkAAADAds9aq75cWFUsLxTPA/VmQvXlwhHP0e6bqtHsPfHSrvPCvnN/G/abAwAAAAAAYPvTSF2XjnGMqzAMtXbtWk2dOlWeR8cPMNbIGOAWGQO2njFGiZivRMzX3K7a/eb50Ko3k9crazbItncpnbXqzRZHtWcCDQa28M9AXqsG6r9Gd9RTMuYVxsEXi+WFfeeeuqMev5WNHR4/xwC3yBjgFhkD3CJjgFtkrDVQGMe4CsNQf/vb3zR58mSCDzhAxgC3yBjgTsQz6oka/fW1v2nx4sWKRKr/VyUThBVj2od3nJfGtOdCqS9X6DxfUWe/uW+kRHm/ebFoXrHfvM03FM4x4fFzDHCLjAFukTHALTIGuEXGWgOFcQAAAAAtL+57mt7uaXp77f/CWGs1kLflseyVI9pT2UDpbKjAShsyoTZkQqkvV3t+zygZ94b3mhe7zku3o4xpBwAAAAAA2K5RGAcAAACwXTPGqDNq1Bn1NLuz9nhordKlLvPiaPbSjvPeTKD+vFUmtFo9GGj1YFD3NTojprjXvHpEe0/MVyLmyaPbHAAAAAAAoKVRGMe4MsYomUwyphJwhIwBbpExwC1XGfOMKewdj/uaV+d4Lix0m6cyw8XyUuE8lQ2VCaz681b9+bxeG6gd025UGNNeKJx7m+w399UZYUw7WgM/xwC3yBjgFhkD3CJjgFtkrDUYa61t9kW0mnQ6rWQyqVQqpUQi0ezLAQAAANBEQ/lQvcX95pVd56Vd58EI/0cVMSp3mCfr7Dhv89ktBgAAAAAAsDUaqevSMY5xFYahXnvtNc2ePVuex18AAmONjAFukTHArVbNWFvE08yIp5kd9febb8yF5SJ5aa95b7EDPZ0LlbfS2qFAa4cCSbX7zdt8Uy6SV3abl8a0R9hvjjHSqhkDJgoyBrhFxgC3yBjgFhlrDRTGMa7CMNSKFSs0c+ZMgg84QMYAt8gY4Nb2mDFjjLpjvrpjvuYqWnM8CK3SubBqPHvl7cG81VBgtWowr1WD9V+jO+pVdJuXOs199cQ8dUXZb47R2x4zBmxPyBjgFhkD3CJjgFtkrDVQGAcAAAAAR3zPaFLc16S4X/d4JgiVqug2L3Wal7rOc6HUlwvVlwu1or92v7lnNDyWvVg4T8YKRfNk3Fe7z35zAAAAAAAAicI4AAAAADRN3Pc0vd3T9Pb6Y9oH87bQZV7ea17sNs8ESmdDhVbakAm1IROq3pj2mGfKo9kr95qXCukxn6I5AAAAAADYMVAYx7jyPE/Tpk1jTATgCBkD3CJjgFtkrJoxRh1Ro46op9mdtcdDa9W3yZj2VEXX+cZ8qGxotWYo0JqhoO5rdERMzV7zUuG8O+bJp9t8QiFjgFtkDHCLjAFukTHALTLWGoy11jb7IlpNOp1WMplUKpVSIpFo9uUAAAAAQMNyoVW6OKI9Vew6LxTRC7czwZb/V9BI6i52mZdGs/eUd5376owwph0AAAAAADRXI3VdOsYxrsIw1AsvvKD58+fzWzGAA2QMcIuMAW6RsbEV9YymtEU0pa3+8aF8WCiWZwOlNuk6T2UD5a2UzoZKZ0O9XOf5EaOqYvnwyPbCrvM2nz/DVkPGALfIGOAWGQPcImOAW2SsNVAYx7gKw1Br1qzRvHnzCD7gABkD3CJjgFtkbHy1RTzNjHia2VF/v/nGfDg8mj1bMbI9E6gvFypvpXVDgdYNBaq337zNN9XF8opd58mYr4hHt/l4I2OAW2QMcIuMAW6RMcAtMtYaKIwDAAAAAKoYY9Qd9dUd9TVH0ZrjgbVKZ0OlMoWx7KlsoN6K2wN5q6HAamgw0OuD9febd0W9itHsxY/FHefdUU8eY9oBAAAAAMAYojAOAAAAAGiIb4wmxX1Nivt1j2cDW9xlXr3jvDSyPRtabcyF2pgLtaI/X/N8z0iJaKHDvKdqTHvhY7vPfnMAAAAAANAYCuMYV57nac6cOYyJABwhY4BbZAxwi4xNHDHfaFp7RNPa649pHwxs1Wj28n7z4tj20Kq4/zxUvTHtMa8wpr1yx3lP3CsW0X3FfIrm9ZAxwC0yBrhFxgC3yBjgFhlrDcZaa5t9Ea0mnU4rmUwqlUopkUg0+3IAAAAAYIcRWqu+XOV+8+qu8425cMRzdETMJnvNiwX0uK9EzJNPtzkAAAAAABNCI3VdOsYxroIg0F/+8hftueee8v36YxcBbD0yBrhFxgC3yBgkyTOFonYy5mvnOvvN86Etd5b3brLjPJUNNRRYDeStBvJ5rRyoPb+R1B2tHs2ejHnl/eZdEW/CjmknY4BbZAxwi4wBbpExwC0y1hoojGNcWWuVSqXEoALADTIGuEXGALfIGEYj4hlNaYtoSlv940PBcLd5qVheueM8b6V0LlR6M53nEaNCYb48mr00sr3Qdd4W2X7H3pExwC0yBrhFxgC3yBjgFhlrDRTGAQAAAAATRpvvqa3D04yO+vvN+/Ol/ebDxfLe4o7zvmyovJXWZQKtywSqt9887puKvebD3eY98cJ9EW9idpsDAAAAALC9ozAOAAAAANghGGPUFTXqinqaU2dMe2Ct+opF8k27znuzgQbyVpnA6vXBQK8PBnVfoyviDXebV3Sd98R9dUc9eRN0TDsAAAAAAK2OwjjGled52nXXXeV52+/4QaCVkTHALTIGuEXG0Gy+MeqJFzrB1V17PBtsut98+HYqGyobWm3Mh9qYD/Vqf77m+Z6kRM1ec7/cgd4RMU73m5MxwC0yBrhFxgC3yBjgFhlrDcY2cZj90qVLtXTpUr344ouSpIULF+rSSy/VscceW/fxDz74oI488sia+5955hntvffe5c/vuusuXXLJJXr++ee122676corr9Q73vGOUV9XOp1WMplUKpVSIpFo7E0BAAAAAHY41loNBbZYMC/uNc+U9psXCufhCP/3HfVU7DCv7TbvifmK+XSbAwAAAABQqZG6blM7xufMmaOrrrpKu+++uyTp1ltv1YknnqgnnnhCCxcu3Ozz/vznP1e9sWnTppVvL1++XO9+97t1xRVX6B3veIfuuecenXzyyfrVr36lgw46yN2bwagEQaCnn35aixYtku/7zb4cYMIhY4BbZAxwi4xhe2aMUXvEqD3iaVZn7fHQWm3MhRV7zau7zftyoXKhtGYo0Jqh+mPa2yNmuFheueM87isR9eSPsN+cjAFukTHALTIGuEXGALfIWGtoamH8+OOPr/r8yiuv1NKlS/XII49ssTA+ffp09fT01D129dVX65hjjtGSJUskSUuWLNFDDz2kq6++Wrff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ws8xwC0y1hoa/sofcMABdX8LzxijtrY27b777jrttNN05JFHjup8e+21l5588kn19vbqrrvu0qmnnqqHHnpoxOL47bffrs985jO69957NX369PL9Bx98sA4++ODy54cddpje+MY36j/+4z907bXX1j3X5z//eV122WWjul5suyAImn0JwIRGxgC3yBjgFhkD3CJjgFtkDGhMW8TTzIinmR3195v3522xYB5o/VBez7+2WtHEZKVyofqyofJWWpcJtC4TqN5+87hvymPZS8XyQrd54WPEo9scqMTPMcAtMtZ8DRfG3/a2t2np0qXad999deCBB8paq8cee0xPPfWUTjvtNP3pT3/S0UcfrbvvvlsnnnjiiOeLxWLafffdJUmLFy/Wo48+qmuuuUY33XTTZp/z3e9+V2eeeabuvPNOHX300Vs8v+d5etOb3qTnnntus49ZsmSJzjvvvPLn6XRac+fOHfHaAQAAAAAAAABjzxijrqhRV9TTHEWVz+cVfXm1Fu+6syKRiAJr1ZcN1ZupHdHemw00kLfKBFavDwZ6fbB+IaIr6pWL5aWu857i7e6oJ48x7QAATCgNF8bXrl2r888/X5dccknV/Z/97Gf10ksv6f7779enP/1pXXHFFaMqjG/KWls11nxTt99+u8444wzdfvvtOu6440Z1vieffFL77rvvZh8Tj8cVj8cbvlYAAAAAAAAAwPjzjVFPvLBrvJ5sUNxvng2UyhSK5aliIT2VDZUNrTbmQm3MhVrRn695vmekRLTUbV4smlfcbme/OQAA2x1jrbWNPCGZTOr//u//yl3eJX/961/1d3/3d0qlUnr22Wf1pje9SX19fVs810UXXaRjjz1Wc+fOVV9fn+644w5dddVVuu+++3TMMcdoyZIlevXVV3XbbbdJKhTFP/jBD+qaa67RO9/5zvJ52tvblUwmJUmXXXaZDj74YO2xxx5Kp9O69tpr9a1vfUu//vWvdeCBB47qPabTaSWTSaVSKSUSiUa+PBiBtVaDg4Nqb2/nPxwBB8gY4BYZA9wiY4BbZAxwi4wBbo1lxqy1GgysUlXd5qXieaFwHo7wt+ZRT8Xu8toR7T0xXzGffw9g+8LPMcAtMuZOI3XdhjvG29ra9Jvf/KamMP6b3/xGbW1tkqQwDEfVgf3666/rlFNO0cqVK5VMJrXffvuVi+KStHLlSr38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iUSzLwcAAAAAAAAAAAAYc5kgrBjNXuo0H+46z49QRfSNykXyyr3mPcXbbREKwXCrkbouHeMYV0EQ6Omnn9aiRYvk+36zLweYcMgY4BYZA9wiY4BbZAxwi4wBbpExwC0ytuOK+55mdHia0VFbMrTWaiBvi3vNi8Xyil3npf3m6zOB1mcCqS9X5/ymPKK9NJ69sus8uoOMaSdjrYHCOMaVtVaDg4NiUAHgBhkD3CJjgFtkDHCLjAFukTHALTIGuEXGUI8xRp1Ro86op506a8e0h7ZyTHtpx3lpVHug/rxVJrBaPRho9WD9/eadETNcLN+k6zwR8+RNkDHtZKw1jKow/sY3vlH/8z//o0mTJunyyy/XBRdcoI6ODtfXBgAAAAAAAAAAAKAFeaZQ1O6J1++AzoW2plheup3KhMqEVv15q/58Xq/252vPL6m72G0+vNfcL+8774gY9pujIaMqjD/zzDPq7+/XpEmTdNlll+nss8+mMA4AAAAAAAAAAACgrqhnNLU9oqnttcestRoKbHmXeW/FXvNUsXgeWBVvh3ppY73zF/abV41oL+849xT32W+OasaOomf/kEMOUVdXl/6//+//02WXXaYLLrhAXV1ddR976aWXjvlFjrdGlrSjMdZapVIpJZNJfosHcICMAW6RMcAtMga4RcYAt8gY4BYZA9wiY2g11lptzIXqrSiWF4rnhW7zdC4c8Rztvqkazd4TL+06LxTT/XHcb07G3Gmkrjuqwvif//xnffrTn9bzzz+vxx9/XAsWLFAkUttsbozR448/PuoLXbp0qZYuXaoXX3xRkrRw4UJdeumlOvbYYzf7nIceekjnnXee/vjHP2r27Nm68MILdfbZZ1c95q677tIll1yi559/XrvttpuuvPJKveMd7xj1dVEYBwAAAAAAAAAAAFpTPizsN09li93mmULhvLe463wwGHmXd3fUK3eb91R1nnvqjnpjVsAOrdUrG3Pqz1l1Ro3mdkUnzO70VjDmhfFKnudp1apVmj59+jZdpCT98Ic/lO/72n333SVJt956q770pS/piSee0MKFC2se/8ILL2jRokX60Ic+pA9/+MP69a9/rY9+9KO6/fbb9a53vUuStHz5cv393/+9rrjiCr3jHe/QPffco0svvVS/+tWvdNBBB43quiiMu5PP5/XEE0/ogAMOqPvLFQC2DRkD3CJj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wRvCrhoLXd+lRfCGsEu2UdP4OgbYRcbs6Updt8sd46effnrJ667r6sADD9RJJ52kww47rKtPVyKRSEhSlzrPf/7zn+vkk08uFMXztm/frpEjR8r3fU2aNEk33HCDmpqa9ut86B6RSKTSRwBqGhkD7CJjgF1kDLCLjAF2kTHAHmOMUk5I27a3tY86L3R+B2op1/Itqd5zFM/t+G6MeIpHc3/m9nyHGHeOPo6vY4BdZKzyutwxbosxRqeddpree+89Pfroo516zObNmzVixAjdfffd+vznP1+4/a9//av+/ve/a+LEiUomk5o/f75+//vf69lnn9WYMWM6PE8qlVIqlSq8nkwmNWLECDrGLchkMlq9erWmTJmiUKjbJvkDyCFjgF1kDLCLjAF2kTHALjIG7B9jjHb5pmS3d/vo8+ye73JN32F393Hnxfu+XUU9t2c+GKAX4usYYBcZs8dqx7gtF198sdauXatVq1Z1+jELFy5UY2Njhy72qVOnaurUqYXXjzvuOE2ePFm33Xabbr311g7PM2fOHF1//fX7fHYAAAAAAAAAwN61BabQ4Z0tgBfv+w6UDspUvo3JdXx7pSPPc53f/ULs+QYAAO+v04Vx1y2/Q8VxHGUymS4f4pJLLtGDDz6olStXavjw4Z16jDFGd911l84999yyowdc19VRRx2ll156aY9vnz17ti6//PLC6/mOcQAAAAAAAABA5/jGqKVQ9C4dd96c9rUzU354af+Qo8Zo+47v/OjzAZ7RC2vX6Ogj6LQDAAD7ptPfQSxduvR93/b444/rtttuU1enshtjdMkll2jp0qVavny5Ro0a1enHrlixQn//+991wQUXdOr9PPPMM5o4ceIe3x6NRhWNRjv9vgEAAAAAAACgrzHGaEfGZMeb57q9813fidy483I/IY56TnbUedHI83zXdzziKfw+e74zmYwYhA4AAPbHfu0Y37Bhg2bPnq3f/e53Ouecc3TDDTfo4IMP7vTjv/rVr+ruu+/WAw88oLFjxxZuj8fjqq+vl5Tt5n7zzTf1i1/8ouSx5557rl566SX99a9/7fC8119/vaZOnaoxY8YomUzq1ltv1S9/+Us99thjOvroo8ueqyuz6NE1xhj5vi/P8xhrBFhAxgC7yBhgFxkD7CJjgF1kDLVilx906PbO7vnOFsHLNX17jgq7vfPd3vGIlyuA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8kgtW7ZMvu/r61//ul588UW9+OKLOu+88/SpT32qMCb9pptuUiqV4noM6KR9yVhrayvXY0AXpFIpnXzyybr11lt1xBFH6JOf/KTeffddbdiwQePHj9eXv/xlXXDBBZLE9VgVCFX6AOj92tra1NzcrM9+9rOS2kc5jx49Wv/4xz8kqeQi8LjjjtOVV16pk08+mdADnbA/GcuPGAPw/hzH0cc+9rFCxiTpe9/7nv7whz9oy5Yt2rZtm8aPH69rrrlGTU1NWrNmjV577TXNmjVLAwcOrODJgd5hXzN28cUX64ADDqjgyYHexXVdHXjggTrqqKO0bt06ffrTn1Y0GtWXvvQlpVIpfeUrX5EknXDCCbriiit0yimncD0GdMG+ZIzrMaBzjj32WL3++ut64IEHdOeddyqTyejoo4/WxIkTde+992rdunX67ne/qyFDhujpp5/megzoon3JGNdjQOc1NzfrhRde0LZt23TllVdKkn76059q8+bNeuSRR3TNNdcoHo/rs5/9rE444QRdddVV+j//5/9wPVYhdIyjW7z00ksaM2aMpGwRLxwO69prr9WmTZv0i1/8onC/RCKheDxeqWMCvRYZA+xqaWlRQ0ODJGnJkiU6++yztXjxYp1yyilat26drrzySn384x/Xddddp02bNqmurk4HHXRQhU8N9B5kDOg5X/rSlzRs2DDNmTNHX/7yl3X//ffroIMO0tSpU3XBBRdo2rRplT4i0KuRMaD7bd68Wd/85jd177336oQTTtCSJUsKRe/f/va3uuiiizR//nydeeaZeuWVVxSNRvleEegCMgbYZYzR2WefrUGDBumVV17RxRdfrBkzZkiS3njjDc2ePVsDBgzQ/PnzFYlEKnxa8Kur6Bb5gl0QBAqHw5KyY8befvvtwn3mzJmjaDSqr33ta/zWNNBFZAywK1+wk7K/Sb169WpNnjxZkjR9+nQNGTJETz31lIwxGjVqVKWOCfRaZAywL79a56Mf/ag2btyor371q/r973+vp556Ss8884yuvPJKRSIRTZ48WdFolNHOQBeRMcCegw46SHPmzNHw4cN1yimnaODAgYVpeaeffrquvvpqrVy5UmeeeaY++MEPVvq4QK9DxgC7HMfRN77xDZ100knauXOnLrzwwsLbhg8friFDhuh///d/Cz/XR2VROUG3cl23cLHoOE5hFMR3vvMdfe9739OaNWso2AH7gYwB9o0cOVIjR46UlP0BaDqd1oABAzRhwgR+wAl0AzIG2JHPz6hRo3TeeedpyJAh+u///m+NGjVKo0aNkuM4OvLII1VXV1fhkwK9ExkD7Bo2bJiuuuoq1dfXS2r/+Udzc7MOOOAATZkypcInBHo3MgbYNWXKFD300EM68cQT9ZOf/ESjR4/W+PHjJWUnwB566KHKZDIUx6sAo9TR7fK/bXbddddp8+bNGjNmjK655ho9/vjjhc4gAPuOjAE96zvf+Y4WLVqkP//5z4XpDQC6DxkDuldbW5t++ctfasqUKTriiCMKv1QJoHuQMaBnfec739HixYv1pz/9iU5WwAIyBnSvlStX6qyzztLw4cM1ceJEpdNpPfjgg1q1apUmTJhQ6eNBdIzDAtd1JUnhcFg//elPFYvFtGrVKgp2QDchY0DP+M1vfqPly5dryZIl+tOf/kTBDuhmZAywIxwOa+bMmYXvGSnYAd2LjAE9Y8mSJVq+fLnuvfdePfzwwxTsgG5GxgA7pk+frkceeUS/+tWv9Ne//lVjxoyhKF5l3EofALVrxowZkqTHH3+cUSyABWQMsOvwww/X1q1btXLlSjU1NVX6OEDNIWOAPfmCHQA7yBhg37hx4/TGG2/o0Ucf5XtFwAIyBtgzduxY3XDDDfrDH/6g//zP/6QoXmUYpQ6rduzYof79+1f6GEDNImOAXW1tbez+ASwiYwAAAHg/6XRakUik0scAahYZA9AXURgHAAAAAAAAAAAAANQ0Zj8BAAAAAAAAAAAAAGoahXEAAAAAAAAAAAAAQE2jMA4AAAAAAAAAAAAAqGkUxgEAAAAAAAAAAAAANY3COAAAAAAAAAAAAACgplEYBwAAAAAAAAAAAADUNArjAAAAAAD0sFdeeUWO4+iZZ56p9FEKNmzYoKlTp6qurk6TJk2q9HEKHMfRb3/720ofAwAAAADQy1EYBwAAAAD0OTNnzpTjOPr+979fcvtvf/tbOY5ToVNV1rXXXqv+/fvrhRde0MMPP7zH++Q/bxdddFGHt331q1+V4ziaOXNmt55r8+bN+vjHP96tzwkAAAAA6HsojAMAAAAA+qS6ujrNnTtX7733XqWP0m3S6fQ+P/bll1/W8ccfr5EjR+qAAw543/uNGDFCS5YsUWtra+G2Xbt2afHixTr44IP3+f2/n6FDhyoajXb78wIAAAAA+hYK4wAAAACAPunkk0/W0KFDNWfOnPe9z3XXXddhrPi8efP0wQ9+sPD6zJkzdfrpp+vGG2/UkCFD1NjYqOuvv16ZTEZXXnmlBg4cqOHDh+uuu+7q8PwbNmzQtGnTVFdXp/Hjx2v58uUlb1+/fr0+8YlPaMCAARoyZIjOPfdcbdu2rfD2k046SRdffLEuv/xyDRo0SKeccsoeP44gCPTd735Xw4cPVzQa1aRJk7Rs2bLC2x3H0VNPPaXvfve7chxH11133ft+TiZPnqyDDz5Y999/f+G2+++/XyNGjFBTU1PJfVOplL72ta9p8ODBqqur0/HHH6///d//LZxp+PDhuvPOO0se8/TTT8txHG3cuLFwtuJR6m+++abOPPNMfeADH9ABBxyg0047Ta+88krh7cuXL9fRRx+t/v37q7GxUccdd5xeffXV9/14AAAAAAB9A4VxAAAAAECf5HmebrzxRt12221644039uu5HnnkEb311ltauXKlfvCDH+i6667Tpz71KX3gAx/Q//zP/+iiiy7SRRddpNdff73kcVdeeaW+8Y1vaM2aNZo2bZr+5V/+Rf/4xz8kZUeIn3jiiZo0aZJWr16tZcuW6e2339bnP//5kudYtGiRQqGQHnvsMf34xz/e4/nmz5+vW265RTfffLPWrl2rGTNm6F/+5V/00ksvFd7X+PHj9Y1vfEObN2/WFVdcsdeP97zzztOCBQsKr9911106//zzO9zvqquu0n333adFixbp6aef1iGHHKIZM2bo3Xffleu6+sIXvqBf//rXJY+5++67deyxx2r06NEdnm/nzp36yEc+ogEDBmjlypVatWqVBgwYoI997GNKp9PKZDI6/fTTdeKJJ2rt2rV64okndOGFF/bZ8fgAAAAAgHYUxgEAAAAAfdanP/1pTZo0Sddee+1+Pc/AgQN16623auzYsTr//PM1duxY7dy5U9/61rc0ZswYzZ49W5FIRI899ljJ4y6++GKdccYZOvzww3XHHXcoHo/r5z//uSTpjjvu0OTJk3XjjTfqsMMOU1NTk+666y795S9/0Ysvvlh4jkMOOUQ33XSTxo4dq8MOO2yP57v55pv1//1//5++8IUvaOzYsZo7d64mTZqkefPmScqOKw+FQhowYICGDh2qAQMG7PXjPffcc7Vq1Sq98sorevXVV/XYY4/p//7f/1tynx07duiOO+7Qf/zHf+jjH/+4xo0bp5/+9Keqr68vfIznnHOOHnvssUJHdxAEWrJkSYfnyluyZIlc19XPfvYzTZw4UYcffrgWLFig1157TcuXL1cymVQikdCnPvUpfehDH9Lhhx+uL33pS1ZGvAMAAAAAehcK4wAAAACAPm3u3LlatGiR1q9fv8/PMX78eLlu+yX2kCFDNHHixMLrnufpgAMO0DvvvFPyuGOPPbbwcigU0pQpU/T8889Lkp566in95S9/0YABAwr/5QvfL7/8cuFxU6ZM2evZksmk3nrrLR133HEltx933HGF99VVgwYN0ic/+UktWrRICxYs0Cc/+UkNGjSo5D4vv/yy2traSt5vOBzW0UcfXXi/TU1NOuyww7R48WJJ0ooVK/TOO+906IrPe+qpp/T3v/9dDQ0Nhc/JwIEDtWvXLr388ssaOHCgZs6cqRkzZuif//mfNX/+fG3evHmfPkYAAAAAQG2hMA4AAAAA6NOmT5+uGTNm6Fvf+laHt7muK2NMyW1tbW0d7hcOh0tedxxnj7cFQVD2PPmx30EQ6J//+Z/1zDPPlPz30ksvafr06YX79+/fv+xzFj9vnjFmv0aMn3/++Vq4cKEWLVq0xzHq+c9bufd7zjnn6O6775aUHaM+Y8aMDkX2vCAI9OEPf7jD5+TFF1/U2WefLUlasGCBnnjiCU2bNk333HOPDj30UP31r3/d548TAAAAAFAbKIwDAAAAAPq873//+/rd736nxx9/vOT2Aw88UFu2bCkpjj/zzDPd9n6LC7aZTEZPPfVUoSt88uTJeu655/TBD35QhxxySMl/nS2GS1IsFtOwYcO0atWqktsff/xxHX744ft89vxe73Q6rRkzZnR4+yGHHKJIJFLyftva2rR69eqS93v22Wfrb3/7m5566in95je/0TnnnPO+73Py5Ml66aWXNHjw4A6fk3g8XrhfU1OTZs+erccff1wTJkwoFN4BAAAAAH0XhXEAAAAAQJ83ceJEnXPOObrttttKbj/ppJO0detW3XTTTXr55Zd1++2366GHHuq293v77bdr6dKl2rBhg2bNmqX33nuv0H09a9YsvfvuuzrrrLP05JNPauPGjfrjH/+o888/X77vd+n9XHnllZo7d67uuecevfDCC/rmN7+pZ555Rpdeeuk+n93zPD3//PN6/vnn5Xleh7f3799f//Zv/6Yrr7xSy5Yt0/r16/WVr3xFO3fu1AUXXFC436hRozRt2jRdcMEFymQyOu200973fZ5zzjkaNGiQTjvtND366KPatGmTVqxYoUsvvVRvvPGGNm3apNmzZ+uJJ57Qq6++qj/+8Y968cUX9+sXAAAAAAAAtYHCOAAAAAAAkm644YYOY9MPP/xw/ehHP9Ltt9+uI488Uk8++aSuuOKKbnuf3//+9zV37lwdeeSRevTRR/XAAw8UxogPGzZMjz32mHzf14wZMzRhwgRdeumlisfjJfvMO+NrX/uavvGNb+gb3/iGJk6cqGXLlunBBx/UmDFj9uv8sVhMsVhsrx/fGWecoXPPPVeTJ0/W3//+d/3hD3/QBz7wgZL7nXPOOXr22Wf1mc98RvX19e/7fP369dPKlSt18MEH6zOf+YwOP/xwnX/++WptbVUsFlO/fv20YcMGnXHGGTr00EN14YUX6uKLL9a//uu/7tfHCQAAAADo/Ryz+1U/AAAAAAAAAAAAAAA1hI5xAAAAAAAAAAAAAEBNozAOAAAAAAAAAAAAAKhpFMYBAAAAAAAAAAAAADWNwjgAAAAAAAAAAAAAoKZRGAcAAAAAAAAAAAAA1DQK4wAAAAAAAAAAAACAmkZhHAAAAAAAAAAAAABQ0yiMAwAAAAAAAAAAAABqGoVxAAAAAAAAAAAAAEBNozAOAAAAAAAAAAAAAKhpFMYBAAAAAAAAAAAAADWNwjgAAAAAAAAAAAAAoKb9/54p7jmMibjyAAAAAElFTkSuQmCC",
+      "text/plain": [
+       "<Figure size 2000x600 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "# Rating Frequency Distribution\n",
+    "merged_df = pd.merge(df_ratings,df_movies, on='movieId')\n",
+    "rating_counts = merged_df['movieId'].value_counts()\n",
+    "value_counts = rating_counts.value_counts().sort_index()\n",
+    "\n",
+    "plt.figure(figsize=(20, 6))\n",
+    "plt.plot(value_counts.values, value_counts.index, marker='o', color='skyblue', linestyle='-')  # Swap x and y arguments\n",
+    "plt.title('Rating Frequency Distribution')\n",
+    "plt.xlabel('Number of Movies')  # Update x-label\n",
+    "plt.ylabel('Number of Ratings')  # Update y-label\n",
+    "plt.xticks(rotation=45)\n",
+    "plt.grid(axis='x', linestyle='--', alpha=0.7)  # Change grid to x-axis\n",
+    "plt.tight_layout()\n",
+    "plt.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAqYAAAHMCAYAAAAH/Go+AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA4EElEQVR4nO3deXwV1f3/8fclKwlJyhZIIKQRFBBkSygSQUDZwqIgrkVks0WECkWqUMSwCFFordYFhMpSJZIiSxEVZA2IoOwgIouAoIAs1YRFEpKc3x9+c39cE5YLCXPgvp6Pxzz0njkz8zn33IQ3c2cGlzHGCAAAAHBYCacLAAAAACSCKQAAACxBMAUAAIAVCKYAAACwAsEUAAAAViCYAgAAwAoEUwAAAFiBYAoAAAArEEwBAABgBYIpUMSmTZsml8ul4OBgffvttwXWN2/eXLVr13agMt/lcrk0YsQIp8u4amlpaapVq5ZKliwpl8ulzZs3O10SvPTRRx9d0WfxRvkMA5dCMAWKSVZWlp577jmny8AN4tixY+rWrZuqVq2qhQsXas2aNbrlllucLgte+uijjzRy5Eivt1uzZo0ef/zxYqgIsAvBFCgmbdu2VWpqqrZs2eJ0KddMbm6usrKynC7jhrRr1y6dO3dOjz76qJo1a6bbb79dISEhTpdVrM6dO6ecnByny3CMMUY///yzJOn2229X5cqVHa4IKH4EU6CYPPPMMypbtqyeffbZS/Y9e/ashg4dqri4OAUGBqpSpUrq16+ffvrpJ49+v/3tb9WhQwctXLhQDRo0UMmSJVWjRg1NmTLlsmpq3ry5XC5Xocu0adPc/Y4cOaI+ffqocuXKCgwMVFxcnEaOHOkREvbv3y+Xy6Vx48bphRdeUFxcnIKCgrR8+XJJ0vz589W4cWOFhIQoLCxMrVq10po1ay5a37FjxxQYGKjhw4cXWPf111/L5XLpn//8p7vvk08+qVtvvVWlSpVSZGSk7rrrLq1ateqS78OIESPkcrkKtOdfhrF//36P9rS0NDVu3FihoaEqVaqU2rRpo02bNnn02bt3rx5++GFFR0crKChIFSpU0N13331ZX7df6r3q0aOHmjRpIkl66KGH5HK51Lx58wvu72reG0latmyZmjdvrrJly6pkyZKqUqWKunTpojNnzkjynPsxY8aoSpUqCg4OVkJCgpYuXeqxrz179qhnz566+eabFRISokqVKqljx47atm2bR78VK1bI5XLpnXfe0dNPP61KlSopKChIe/bs0ZkzZzR48GDFxcUpODhYZcqUUUJCgt577z2Pfaxfv1733HOPypQpo+DgYNWvX1//+c9/Ljne/PGMHz9eL730kn7729+qZMmSat68ufsvBEOGDFF0dLQiIiLUuXNnHT161GMfaWlpat26taKiolSyZEnVrFlTQ4YM0enTp919evTooTfeeEOSPH728j9vLpdL/fv318SJE1WzZk0FBQVp+vTp7nX5X+UbY9SuXTuVLVtWBw4ccO//zJkzqlWrlmrWrOlxXOC6YgAUqalTpxpJZt26debVV181kszSpUvd65s1a2Zq1arlfp2Xl2fatGlj/P39zfDhw80nn3xi/va3v5nQ0FBTv359c/bsWXff2NhYU7lyZXPrrbeaf//732bRokXmgQceMJJMenr6JWvbvn27WbNmjcfSsmVL4+fnZz777DNjjDGHDx82MTExJjY21rz11ltmyZIlZvTo0SYoKMj06NHDva99+/YZSaZSpUqmRYsW5v333zeffPKJ2bdvn5kxY4aRZFq3bm3mzZtn0tLSTHx8vAkMDDSrVq26aI2dO3c2MTExJjc316P9mWeeMYGBgeb48ePGGGO+/vpr07dvXzNz5kyzYsUKs2DBAtO7d29TokQJs3z5co9tJZnk5GT36+TkZFPYr7/8udu3b5+7bcyYMcblcplevXqZBQsWmDlz5pjGjRub0NBQs337dne/6tWrm2rVqpl33nnHpKenm9mzZ5unn366QC2/djnv1Z49e8wbb7xhJJmxY8eaNWvWeBz717x5b35t3759Jjg42LRq1crMmzfPrFixwsyYMcN069bN/Pjjj+4+kkxMTIxp0qSJmT17tpk1a5Zp2LChCQgIcH+WjDEmPT3dPP300+b999836enpZu7cuaZTp06mZMmS5uuvv3b3W758ufvzdP/995v58+ebBQsWmBMnTpg+ffqYkJAQ8/LLL5vly5ebBQsWmBdffNG89tpr7u2XLVtmAgMDTdOmTU1aWppZuHCh6dGjh5Fkpk6deskxSzKxsbGmY8eOZsGCBebdd981FSpUMLfccovp1q2b6dWrl/n444/NxIkTTalSpUzHjh099jF69Gjzj3/8w3z44YdmxYoVZuLEiSYuLs60aNHC3WfPnj3m/vvvN5I8fgbzf8bzx1+nTh2Tmppqli1bZr788kv3uvM/w8ePHzeVK1c2jRo1MtnZ2cYYY7p3725Klixptm7detHxAjYjmAJF7PxgmpWVZW666SaTkJBg8vLyjDEFg+nChQuNJDNu3DiP/aSlpRlJZtKkSe622NhYExwcbL799lt3288//2zKlClj+vTp43Wt48ePL3CMPn36mFKlSnkcwxhj/va3vxlJ7kCU/4d51apV3X8wGmNMbm6uiY6ONrfddptHuDx58qSJjIw0iYmJF61p/vz5RpL55JNP3G05OTkmOjradOnS5YLb5eTkmHPnzpm7777bdO7c2WPdlQbTAwcOGH9/f/OnP/3Jo9/JkydNxYoVzYMPPmiM+SUkSDKvvPLKRcf2a968V/nBbdasWV4dw5iLvze/9v777xtJZvPmzRfskz/30dHR5ueff3a3Z2ZmmjJlypiWLVtetJbs7Gxz8803mz//+c/u9vzx3XnnnQW2qV27tunUqdNF665Ro4apX7++OXfunEd7hw4dTFRUVIG/6BQ2nrp163r0e+WVV4wkc88993j0HzhwoJFkMjIyCt1fXl6eOXfunElPTzeSzJYtW9zr+vXrV+hnz5hfPqcRERHmf//7X6Hrzv8MG2PMp59+avz9/c3AgQPNlClTjCTzr3/964LjBK4HfJUPFKPAwEC98MILWr9+/QW/Uly2bJmkX77mO98DDzyg0NDQAl+N1qtXT1WqVHG/Dg4O1i233OLxBICcnByPxRhT4LjvvfeennnmGT333HP6wx/+4G5fsGCBWrRooejoaI99JCUlSZLS09M99nPPPfcoICDA/Xrnzp06dOiQunXrphIl/v+vmFKlSqlLly5au3at+yvhwiQlJalixYqaOnWqu23RokU6dOiQevXq5dF34sSJatCggYKDg+Xv76+AgAAtXbpUO3bsuOD+vbFo0SLl5OToscce83gvgoOD1axZM61YsUKSVKZMGVWtWlXjx4/Xyy+/rE2bNikvL++S+7/a9+pirvS9qVevngIDA/XHP/5R06dP1969ey/Y97777lNwcLD7dVhYmDp27KiVK1cqNzdX0i+fxbFjx+rWW29VYGCg/P39FRgYqN27dxdaS5cuXQq0/e53v9PHH3+sIUOGaMWKFe7rLvPt2bNHX3/9tbp27eo+Zv7Srl07HT58WDt37rzouCWpXbt2HvNQs2ZNSVL79u09+uW3n/81+t69e/X73/9eFStWlJ+fnwICAtSsWTNJ8urzeNddd6l06dKX1feOO+7QmDFj9Morr6hv37569NFH1bt378s+FmAjgilQzB5++GE1aNBAw4YN07lz5wqsP3HihPz9/VW+fHmPdpfLpYoVK+rEiRMe7WXLli2wj6CgII8/rAMCAjyW/OvU8i1fvlw9evTQY489ptGjR3us++GHH/TBBx8U2EetWrUkScePH/foHxUVVWA8hbVLUnR0tPLy8vTjjz8WWJfP399f3bp109y5c93X2E6bNk1RUVFq06aNu9/LL7+svn37qlGjRpo9e7bWrl2rdevWqW3btgWCy5X64YcfJEkNGzYs8H6kpaW53wuXy6WlS5eqTZs2GjdunBo0aKDy5cvrqaee0smTJy+4/6t9ry7kat6bqlWrasmSJYqMjFS/fv1UtWpVVa1aVa+++mqBvhUrViy0LTs7W6dOnZIkDRo0SMOHD1enTp30wQcf6PPPP9e6detUt27dQmsp7L345z//qWeffVbz5s1TixYtVKZMGXXq1Em7d++W9P/nafDgwQXm6cknn5RU8HNbmDJlyni8DgwMvGj72bNnJUmnTp1S06ZN9fnnn+uFF17QihUrtG7dOs2ZM0eSvPo8Fjb+i+natasCAwOVlZWlv/zlL15tC9jI3+kCgBudy+XSSy+9pFatWmnSpEkF1pctW1Y5OTk6duyYRzg1xujIkSNq2LCh18dct26dx+u4uDj3/2/dulWdOnVSs2bNNHny5ALblitXTnXq1NGYMWMK3Xd0dLTH61/fRJQfnA8fPlxg20OHDqlEiRKXPCPUs2dPjR8/XjNnztRDDz2k+fPna+DAgfLz83P3effdd9W8eXNNmDDBY9uLBcF8+Wf5srKyFBQU5G7/dXgpV66cJOn9999XbGzsRfcZGxurt99+W9Ivd9D/5z//0YgRI5Sdna2JEycWuk1RvFeFuZr3RpKaNm2qpk2bKjc3V+vXr9drr72mgQMHqkKFCnr44Yfd/Y4cOVJg2yNHjigwMFClSpVy1/LYY49p7NixHv2OHz+u3/zmNwW2L+ymtNDQUI0cOVIjR47UDz/84D572rFjR3399dfueRo6dKjuu+++QsdUvXr1yxr7lVi2bJkOHTqkFStWuM+SSipw8+LlKGz8F5Kbm6uuXbuqdOnSCgoKUu/evbV69Wp3cAauR5wxBa6Bli1bqlWrVho1apT7TFK+u+++W9Ivf4Cfb/bs2Tp9+rR7vTcSEhI8lvwAdODAASUlJemmm27S7NmzPb6Cz9ehQwd9+eWXqlq1aoH9JCQkFAimv1a9enVVqlRJqampHpcQnD59WrNnz3bffX4xNWvWVKNGjTR16lSlpqYqKytLPXv29Ojjcrk8QqX0S+i+1J3/0i9PN8jvf74PPvjA43WbNm3k7++vb775ptD3IiEhodD933LLLXruued02223aePGjResoyjeq8JczXtzPj8/PzVq1Mh9J/mvxzJnzhz3WUPpl+D7wQcfqGnTpu6/RBRWy4cffqjvv//eq1ryVahQQT169NAjjzyinTt36syZM6pevbpuvvlmbdmy5YLzFBYWdkXHuxz5YfLX43zrrbcK9M3vUxRn9ZOTk7Vq1SrNmDFDaWlp2rJlC2dNcd3jjClwjbz00kuKj4/X0aNH3V+LS1KrVq3Upk0bPfvss8rMzNQdd9yhrVu3Kjk5WfXr11e3bt2KrIakpCT99NNPev3117V9+3aPdVWrVlX58uU1atQoLV68WImJiXrqqadUvXp1nT17Vvv379dHH32kiRMnXvR5iiVKlNC4cePUtWtXdejQQX369FFWVpbGjx+vn376SS+++OJl1dqrVy/16dNHhw4dUmJiYoEzXh06dNDo0aOVnJysZs2aaefOnRo1apTi4uIu+ezLdu3aqUyZMurdu7dGjRolf39/TZs2TQcPHvTo99vf/lajRo3SsGHDtHfvXrVt21alS5fWDz/8oC+++MJ9Jm/r1q3q37+/HnjgAd18880KDAzUsmXLtHXrVg0ZMqTY36tfu5r3ZuLEiVq2bJnat2+vKlWq6OzZs+7HkbVs2dKjr5+fn1q1aqVBgwYpLy9PL730kjIzMz0eIN+hQwdNmzZNNWrUUJ06dbRhwwaNHz/eq2dyNmrUSB06dFCdOnVUunRp7dixQ++8845HcH/rrbeUlJSkNm3aqEePHqpUqZL+97//aceOHdq4caNmzZp12cfzVmJiokqXLq0nnnhCycnJCggI0IwZMwp9hvFtt90m6ZffB0lJSfLz81OdOnW8Psu5ePFipaSkaPjw4e6/vKakpGjw4MFq3ry5OnfufPUDA5zg7L1XwI3n/Lvyf+33v/+9keRxV74xv9xZ/+yzz5rY2FgTEBBgoqKiTN++fd2P58kXGxtr2rdvX2C/zZo1M82aNbtkbZIuuJz/SJ1jx46Zp556ysTFxZmAgABTpkwZEx8fb4YNG2ZOnTpljPn/dzKPHz++0GPNmzfPNGrUyAQHB5vQ0FBz9913m9WrV1+yxnwZGRmmZMmSRpKZPHlygfVZWVlm8ODBplKlSiY4ONg0aNDAzJs3z3Tv3t3ExsYWGPev72j+4osvTGJiogkNDTWVKlUyycnJ5l//+leBx0Xlj6VFixYmPDzcBAUFmdjYWHP//febJUuWGGOM+eGHH0yPHj1MjRo1TGhoqClVqpSpU6eO+cc//mFycnIuOdbLea+8uSvfm/fm19asWWM6d+5sYmNjTVBQkClbtqxp1qyZmT9/vrtP/ty/9NJLZuTIkaZy5comMDDQ1K9f3yxatMhjfz/++KPp3bu3iYyMNCEhIaZJkyZm1apVBT6zFxvfkCFDTEJCgildurQJCgoyN910k/nzn//sfnRYvi1btpgHH3zQREZGmoCAAFOxYkVz1113mYkTJ150zBf6LF+opsJ+xj/77DPTuHFjExISYsqXL28ef/xxs3HjxgI/W1lZWebxxx835cuXNy6Xy+PzJsn069ev0BrP/wwfOnTIREZGmrvuusvjKQJ5eXmmY8eO5je/+U2BzzBwvXAZU8jtugAAXMD+/fsVFxen8ePHa/DgwU6XA+AGwjWmAAAAsALBFAAAAFbgq3wAAABYgTOmAAAAsALBFAAAAFYgmAIAAMAKBFMAAABYgWAKAAAAKxBMr9Cbb76puLg4BQcHKz4+XqtWrXK6JBSDlJQUNWzYUGFhYYqMjFSnTp20c+dOp8vCNZCSkiKXy6WBAwc6XQqK0ffff69HH31UZcuWVUhIiOrVq6cNGzY4XRaKQU5Ojp577jnFxcWpZMmSuummmzRq1Cjl5eU5XRrOQzC9AmlpaRo4cKCGDRumTZs2qWnTpkpKStKBAwecLg1FLD09Xf369dPatWu1ePFi5eTkqHXr1jp9+rTTpaEYrVu3TpMmTVKdOnWcLgXF6Mcff9Qdd9yhgIAAffzxx/rqq6/097//Xb/5zW+cLg3F4KWXXtLEiRP1+uuva8eOHRo3bpzGjx+v1157zenScB6eY3oFGjVqpAYNGmjChAnutpo1a6pTp05KSUlxsDIUt2PHjikyMlLp6em68847nS4HxeDUqVNq0KCB3nzzTb3wwguqV6+eXnnlFafLQjEYMmSIVq9ezTdePqJDhw6qUKGC3n77bXdbly5dFBISonfeecfBynA+zph6KTs7Wxs2bFDr1q092lu3bq3PPvvMoapwrWRkZEiSypQp43AlKC79+vVT+/bt1bJlS6dLQTGbP3++EhIS9MADDygyMlL169fX5MmTnS4LxaRJkyZaunSpdu3aJUnasmWLPv30U7Vr187hynA+f6cLuN4cP35cubm5qlChgkd7hQoVdOTIEYeqwrVgjNGgQYPUpEkT1a5d2+lyUAxmzpypjRs3at26dU6Xgmtg7969mjBhggYNGqS//vWv+uKLL/TUU08pKChIjz32mNPloYg9++yzysjIUI0aNeTn56fc3FyNGTNGjzzyiNOl4TwE0yvkcrk8XhtjCrThxtK/f39t3bpVn376qdOloBgcPHhQAwYM0CeffKLg4GCny8E1kJeXp4SEBI0dO1aSVL9+fW3fvl0TJkwgmN6A0tLS9O677yo1NVW1atXS5s2bNXDgQEVHR6t79+5Ol4f/QzD1Urly5eTn51fg7OjRo0cLnEXFjeNPf/qT5s+fr5UrV6py5cpOl4NisGHDBh09elTx8fHuttzcXK1cuVKvv/66srKy5Ofn52CFKGpRUVG69dZbPdpq1qyp2bNnO1QRitNf/vIXDRkyRA8//LAk6bbbbtO3336rlJQUgqlFuMbUS4GBgYqPj9fixYs92hcvXqzExESHqkJxMcaof//+mjNnjpYtW6a4uDinS0Ixufvuu7Vt2zZt3rzZvSQkJKhr167avHkzofQGdMcddxR4/NuuXbsUGxvrUEUoTmfOnFGJEp6xx8/Pj8dFWYYzpldg0KBB6tatmxISEtS4cWNNmjRJBw4c0BNPPOF0aShi/fr1U2pqqv773/8qLCzMfaY8IiJCJUuWdLg6FKWwsLAC1w6HhoaqbNmyXFN8g/rzn/+sxMREjR07Vg8++KC++OILTZo0SZMmTXK6NBSDjh07asyYMapSpYpq1aqlTZs26eWXX1avXr2cLg3n4XFRV+jNN9/UuHHjdPjwYdWuXVv/+Mc/eHzQDehC1w1PnTpVPXr0uLbF4Jpr3rw5j4u6wS1YsEBDhw7V7t27FRcXp0GDBukPf/iD02WhGJw8eVLDhw/X3LlzdfToUUVHR+uRRx7R888/r8DAQKfLw/8hmAIAAMAKXGMKAAAAKxBMAQAAYAWCKQAAAKxAMAUAAIAVCKYAAACwAsEUAAAAViCYAgAAwAoE0yuUlZWlESNGKCsry+lScA0w376F+fYtzLdvYb7txgP2r1BmZqYiIiKUkZGh8PBwp8tBMWO+fQvz7VuYb9/CfNuNM6YAAACwAsEUAAAAVvB3uoCrkZeXp0OHDiksLEwul+uaHjszM9Pjv7ixMd++hfn2Lcy3b2G+rz1jjE6ePKno6GiVKHHxc6LX9TWm3333nWJiYpwuAwAAAJdw8OBBVa5c+aJ9ruszpmFhYZJ+GSgXMAMAANgnMzNTMTEx7tx2Mdd1MM3/+j48PJxgCgAAYLHLueySm58AAABgBYIpAAAArEAwBQAAgBUIpgAAALACwRQAAABWIJgCAADACgRTAAAAWIFgCgAAACsQTAEAAGAFgikAAACsQDAFAACAFQimAAAAsALBFAAAAFYgmAIAAMAKBFMAAABYgWAKAAAAKxBMAQAAYAWCKQAAAKxAMAUAAIAVCKYAAACwAsEUAAAAViCYAgAAwAoEUwAAAFiBYAoAAAArOB5M33zzTcXFxSk4OFjx8fFatWqV0yUBAADAAY4G07S0NA0cOFDDhg3Tpk2b1LRpUyUlJenAgQNOlgUAAAAHuIwxxqmDN2rUSA0aNNCECRPcbTVr1lSnTp2UkpJyye0zMzMVERGhjIwMhYeHF2epAAAAuALe5DXHzphmZ2drw4YNat26tUd769at9dlnnxW6TVZWljIzMz0WAMUnef6Xih+9WMnzv3S6lGuKcTNu3Lh8db6vl3E7FkyPHz+u3NxcVahQwaO9QoUKOnLkSKHbpKSkKCIiwr3ExMRci1IBn7Vgy2GdOJ2tBVsOO13KNcW4GTduXL4639fLuB2/+cnlcnm8NsYUaMs3dOhQZWRkuJeDBw9eixIBn9WhbpTKhgaqQ90op0u5phg348aNy1fn+3oZt2PXmGZnZyskJESzZs1S586d3e0DBgzQ5s2blZ6efsl9cI0pAACA3a6La0wDAwMVHx+vxYsXe7QvXrxYiYmJDlUFAAAAp/g7efBBgwapW7duSkhIUOPGjTVp0iQdOHBATzzxhJNlAQAAwAGOBtOHHnpIJ06c0KhRo3T48GHVrl1bH330kWJjY50sCwAAAA5w9DmmV4trTAEAAOx2XVxjCgAAAJyPYAoAAAArEEwBAABgBYIpAAAArEAwBQAAgBUIpgAAALACwRQAAABWIJgCAADACgRTAAAAWIFgCgAAACsQTAEAAGAFgikAAACsQDAFAACAFQimAAAAsALBFAAAAFYgmAIAAMAKBFMAAABYgWAKAAAAKxBMAQAAYAWCKQAAAKxAMAUAAIAVCKYAAACwAsEUAAAAViCYAgAAwAoEUwAAAFiBYAoAAAArEEwBAABgBYIpAAAArEAwBQAAgBUIpgAAALACwRQAAABWIJgCAADACgRTAAAAWIFgCgAAACsQTAEAAGAFgikAAACsQDAFAACAFQimAAAAsALBFAAAAFYgmAIAAMAKBFMAAABYgWAKAAAAKxBMAQAAYAWCKQAAAKxAMAUAAIAVCKYAAACwAsEUAAAAViCYAgAAwAoEUwAAAFiBYAoAAAArEEwBAABgBUeD6cqVK9WxY0dFR0fL5XJp3rx5TpYDAAAABzkaTE+fPq26devq9ddfd7IMAAAAWMDfyYMnJSUpKSnJyRIAAABgievqGtOsrCxlZmZ6LE5Jnv+l4kcvVvL8Lx2rAdcO8w1fwOfctzDfvuV6me/rKpimpKQoIiLCvcTExDhWy4Ith3XidLYWbDnsWA24dphv+AI+576F+fYt18t8X1fBdOjQocrIyHAvBw8edKyWDnWjVDY0UB3qRjlWA64d5hu+gM+5b2G+fcv1Mt8uY4xxughJcrlcmjt3rjp16nTZ22RmZioiIkIZGRkKDw8vvuIAAABwRbzJa9fVGVMAAADcuBy9K//UqVPas2eP+/W+ffu0efNmlSlTRlWqVHGwMgAAAFxrjgbT9evXq0WLFu7XgwYNkiR1795d06ZNc6gqAAAAOMHRYNq8eXNZcokrAAAAHMY1pgAAALACwRQAAABWIJgCAADACgRTAAAAWIFgCgAAACsQTAEAAGAFgikAAACsQDAFAACAFQimAAAAsALBFAAAAFYgmAIAAMAKBFMAAABYgWAKAAAAKxBMAQAAYAWCKQAAAKxAMAUAAIAVCKYAAACwAsEUAAAAViCYAgAAwAoEUwAAAFiBYAoAAAArEEwBAABgBYIpAAAArEAwBQAAgBUIpgAAALACwRQAAABWIJgCAADACgRTAAAAWIFgCgAAACsQTAEAAGAFgikAAACsQDAFAACAFQimAAAAsALBFAAAAFYgmAIAAMAKBFMAAABYgWAKAAAAKxBMAQAAYAWCKQAAAKxAMAUAAIAVCKYAAACwAsEUAAAAViCYAgAAwAoEUwAAAFiBYAoAAAArEEwBAABgBYIpAAAArEAwBQAAgBUIpgAAALACwRQAAABW8DqYbt++/YLrFi5ceFXFAAAAwHd5HUwTEhL02muvebRlZWWpf//+6ty5s1f7SklJUcOGDRUWFqbIyEh16tRJO3fu9LYkAAAA3AC8DqYzZszQyJEjlZSUpCNHjmjz5s2qX7++li1bptWrV3u1r/T0dPXr109r167V4sWLlZOTo9atW+v06dPelgUAAIDrnMsYY7zd6NChQ+revbs2bdqk06dPq2fPnvr73/+ukiVLXlUxx44dU2RkpNLT03XnnXdesn9mZqYiIiKUkZGh8PDwqzo2AAAAip43ee2Kbn7Kzc1Vdna2cnNzlZubq4oVKyooKOiKij1fRkaGJKlMmTKFrs/KylJmZqbH4pTk+V8qfvRiJc//0rEanOCr4/ZVvjrfvjpuX8V8wxdcL59zr4PpzJkzVadOHUVERGjXrl368MMPNWnSJDVt2lR79+694kKMMRo0aJCaNGmi2rVrF9onJSVFERER7iUmJuaKj3e1Fmw5rBOns7Vgy2HHanCCr47bV/nqfPvquH0V8w1fcL18zr0Opr1799bYsWM1f/58lS9fXq1atdK2bdtUqVIl1atX74oL6d+/v7Zu3ar33nvvgn2GDh2qjIwM93Lw4MErPt7V6lA3SmVDA9WhbpRjNTjBV8ftq3x1vn113L6K+YYvuF4+515fY7pz505Vr1690HXvvPOOunXr5nURf/rTnzRv3jytXLlScXFxl70d15gCAADYrVivMa1evbpycnK0ZMkSvfXWWzp58qSkX26I8vZxUcYY9e/fX3PmzNGyZcu8CqUAAAC4sfh7u8G3336rtm3b6sCBA8rKylKrVq0UFhamcePG6ezZs5o4ceJl76tfv35KTU3Vf//7X4WFhenIkSOSpIiIiKu+wx8AAADXF6/PmA4YMEAJCQn68ccfPcJj586dtXTpUq/2NWHCBGVkZKh58+aKiopyL2lpad6WBQAAgOuc12dMP/30U61evVqBgYEe7bGxsfr++++92tcVPEIVAAAANyivz5jm5eUpNze3QPt3332nsLCwIikKAAAAvsfrYNqqVSu98sor7tcul0unTp1ScnKy2rVrV5S1AQAAwId4/bioQ4cOqUWLFvLz89Pu3buVkJCg3bt3q1y5clq5cqUiIyOLq9YCeFwUAACA3bzJa15fYxodHa3Nmzfrvffe08aNG5WXl6fevXura9eu3EkPAACAK+b1GVObcMYUAADAbkV+xnT+/PmXffB77rnnsvsCAAAA+S4rmHbq1MnjtcvlKvCoJ5fLJUmF3rEPAAAAXMpl3ZWfl5fnXj755BPVq1dPH3/8sX766SdlZGTo448/VoMGDbRw4cLirhcAAAA3KK9vfho4cKAmTpyoJk2auNvatGmjkJAQ/fGPf9SOHTuKtEAAAAD4Bq+fY/rNN98oIiKiQHtERIT2799fFDUBAADAB3kdTBs2bKiBAwfq8OHD7rYjR47o6aef1u9+97siLQ4AAAC+w+tgOmXKFB09elSxsbGqVq2aqlWrpipVqujw4cN6++23i6NGAAAA+ACvrzGtVq2atm7dqsWLF+vrr7+WMUa33nqrWrZs6b4zHwAAAPAWD9gHAABAsSnWf5JUkpYuXaqlS5fq6NGjysvL81g3ZcqUK9klAAAAfJzXwXTkyJEaNWqUEhISFBUVxdf3AAAAKBJeB9OJEydq2rRp6tatW3HUAwAAAB/l9V352dnZSkxMLI5aAAAA4MO8DqaPP/64UlNTi6MWAAAA+DCvv8o/e/asJk2apCVLlqhOnToKCAjwWP/yyy8XWXEAAADwHV4H061bt6pevXqSpC+//NJjHTdCAQAA4Ep5HUyXL19eHHUAAADAx3l9jSkAAABQHC77jOl99913Wf3mzJlzxcUAAADAd112MI2IiCjOOgAAAODjLjuYTp06tTjrAAAAgI/jGlMAAABYgWAKAAAAKxBMAQAAYAWCKQAAAKzgVTA9d+6cevbsqb179xZXPQAAAPBRXgXTgIAAzZ07t7hqAQAAgA/z+qv8zp07a968ecVQCgAAAHzZZT/HNF+1atU0evRoffbZZ4qPj1doaKjH+qeeeqrIigMAAIDvcBljjDcbxMXFXXhnLtc1vf40MzNTERERysjIUHh4+DU7LgAAAC6PN3nN6zOm+/btu+LCAAAAgAu54sdFZWdna+fOncrJySnKegAAAOCjvA6mZ86cUe/evRUSEqJatWrpwIEDkn65tvTFF18s8gIBAADgG7wOpkOHDtWWLVu0YsUKBQcHu9tbtmyptLS0Ii0OAAAAvsPra0znzZuntLQ03X777XK5XO72W2+9Vd98802RFgcAAADf4fUZ02PHjikyMrJA++nTpz2CKgAAAOANr4Npw4YN9eGHH7pf54fRyZMnq3HjxkVXGQAAAHyK11/lp6SkqG3btvrqq6+Uk5OjV199Vdu3b9eaNWuUnp5eHDUCAADAB3h9xjQxMVGrV6/WmTNnVLVqVX3yySeqUKGC1qxZo/j4+OKoEQAAAD7A63/5ySb8y08AAAB28yaveX3GdOPGjdq2bZv79X//+1916tRJf/3rX5Wdne19tQAAAICuIJj26dNHu3btkiTt3btXDz30kEJCQjRr1iw988wzRV4gAAAAfIPXwXTXrl2qV6+eJGnWrFlq1qyZUlNTNW3aNM2ePbuo6wMAAICP8DqYGmOUl5cnSVqyZInatWsnSYqJidHx48eLtjoAAAD4DK+DaUJCgl544QW98847Sk9PV/v27SVJ+/btU4UKFYq8QAAAAPgGr4PpK6+8oo0bN6p///4aNmyYqlWrJkl6//33lZiYWOQFAgAAwDcU2eOizp49Kz8/PwUEBFz2NhMmTNCECRO0f/9+SVKtWrX0/PPPKykp6bK253FRAAAAdivWx0VdSHBwsFehVJIqV66sF198UevXr9f69et111136d5779X27duLqiwAAABcJy47mJYoUUJ+fn4FltKlS+v222/XnDlzvD54x44d1a5dO91yyy265ZZbNGbMGJUqVUpr1671el8AAAC4vvlfbse5c+cW2v7TTz/piy++0KOPPqrp06frgQceuKJCcnNzNWvWLJ0+fVqNGzcutE9WVpaysrLcrzMzM6/oWLhyyfO/1IIth9WhbpRG3lPb6XIAAFeI3+ewUZFdY/rGG2/o3//+tz7//HOvttu2bZsaN26ss2fPqlSpUkpNTXU/gurXRowYoZEjRxZo5xrTayd+9GKdOJ2tsqGB2jC8ldPlAACuEL/Pca04co1p69at3f8ilDeqV6+uzZs3a+3aterbt6+6d++ur776qtC+Q4cOVUZGhns5ePDg1ZYNL3WoG6WyoYHqUDfK6VIAAFeB3+ewUZGdMd26davatGmjw4cPX9V+WrZsqapVq+qtt966ZF/uygcAALCbI2dMJ0+erPr1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",
+      "text/plain": [
+       "<Figure size 800x600 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "def create_X(df):\n",
+    "    \"\"\"\n",
+    "    Generates a sparse matrix from ratings dataframe.\n",
+    "\n",
+    "    Args:\n",
+    "        df: pandas dataframe containing 3 columns (userId, movieId, rating)\n",
+    "\n",
+    "    Returns:\n",
+    "        X: sparse matrix\n",
+    "        user_mapper: dict that maps user id's to user indices\n",
+    "        user_inv_mapper: dict that maps user indices to user id's\n",
+    "        movie_mapper: dict that maps movie id's to movie indices\n",
+    "        movie_inv_mapper: dict that maps movie indices to movie id's\n",
+    "    \"\"\"\n",
+    "    M = df['userId'].nunique()\n",
+    "    N = df['movieId'].nunique()\n",
+    "\n",
+    "    user_mapper = dict(zip(np.unique(df[\"userId\"]), list(range(M))))\n",
+    "    movie_mapper = dict(zip(np.unique(df[\"movieId\"]), list(range(N))))\n",
+    "\n",
+    "    user_inv_mapper = dict(zip(list(range(M)), np.unique(df[\"userId\"])))\n",
+    "    movie_inv_mapper = dict(zip(list(range(N)), np.unique(df[\"movieId\"])))\n",
+    "\n",
+    "    user_index = [user_mapper[i] for i in df['userId']]\n",
+    "    item_index = [movie_mapper[i] for i in df['movieId']]\n",
+    "\n",
+    "    X = csr_matrix((df[\"rating\"], (user_index,item_index)), shape=(M,N))\n",
+    "\n",
+    "    return X, user_mapper, movie_mapper, user_inv_mapper, movie_inv_mapper\n",
+    "\n",
+    "# Assuming df_ratings contains your ratings dataframe\n",
+    "\n",
+    "X, user_mapper, movie_mapper, user_inv_mapper, movie_inv_mapper = create_X(df_ratings)\n",
+    "\n",
+    "\n",
+    "# Plot the non-zero values of the sparse matrix\n",
+    "plt.figure(figsize=(8, 6))\n",
+    "plt.spy(X, markersize=1)\n",
+    "plt.title('Non-zero values of a sparse matrix')\n",
+    "plt.xlabel('Movie Index')\n",
+    "plt.ylabel('User Index')\n",
+    "plt.show()\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Matrix sparsity: 50.0%\n"
+     ]
+    }
+   ],
+   "source": [
+    "n_total = X.shape[0]*X.shape[1]\n",
+    "n_ratings = X.nnz\n",
+    "sparsity = n_ratings/n_total\n",
+    "print(f\"Matrix sparsity: {round(sparsity*100,2)}%\")"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "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.12.2"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/analytics_tiny.ipynb b/analytics_tiny.ipynb
index cbd97046..ccebba37 100644
--- a/analytics_tiny.ipynb
+++ b/analytics_tiny.ipynb
@@ -6,279 +6,15 @@
    "metadata": {},
    "outputs": [
     {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Display The Movies : \n"
-     ]
-    },
-    {
-     "data": {
-      "text/html": [
-       "<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>movieId</th>\n",
-       "      <th>title</th>\n",
-       "      <th>genres</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>3</td>\n",
-       "      <td>Grumpier Old Men (1995)</td>\n",
-       "      <td>Comedy|Romance</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>15</td>\n",
-       "      <td>Cutthroat Island (1995)</td>\n",
-       "      <td>Action|Adventure|Romance</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>34</td>\n",
-       "      <td>Babe (1995)</td>\n",
-       "      <td>Children|Drama</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>59</td>\n",
-       "      <td>Confessional, The (Confessionnal, Le) (1995)</td>\n",
-       "      <td>Drama|Mystery</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>64</td>\n",
-       "      <td>Two if by Sea (1996)</td>\n",
-       "      <td>Comedy|Romance</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>...</th>\n",
-       "      <td>...</td>\n",
-       "      <td>...</td>\n",
-       "      <td>...</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>907</th>\n",
-       "      <td>148652</td>\n",
-       "      <td>The Ridiculous 6 (2015)</td>\n",
-       "      <td>Comedy|Western</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>908</th>\n",
-       "      <td>151307</td>\n",
-       "      <td>The Lovers and the Despot</td>\n",
-       "      <td>(no genres listed)</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>909</th>\n",
-       "      <td>152173</td>\n",
-       "      <td>Michael Jackson's Thriller (1983)</td>\n",
-       "      <td>Horror</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>910</th>\n",
-       "      <td>160440</td>\n",
-       "      <td>The Maid's Room (2014)</td>\n",
-       "      <td>Thriller</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>911</th>\n",
-       "      <td>160656</td>\n",
-       "      <td>Tallulah (2016)</td>\n",
-       "      <td>Drama</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "<p>912 rows × 3 columns</p>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "     movieId                                         title  \\\n",
-       "0          3                       Grumpier Old Men (1995)   \n",
-       "1         15                       Cutthroat Island (1995)   \n",
-       "2         34                                   Babe (1995)   \n",
-       "3         59  Confessional, The (Confessionnal, Le) (1995)   \n",
-       "4         64                          Two if by Sea (1996)   \n",
-       "..       ...                                           ...   \n",
-       "907   148652                       The Ridiculous 6 (2015)   \n",
-       "908   151307                     The Lovers and the Despot   \n",
-       "909   152173             Michael Jackson's Thriller (1983)   \n",
-       "910   160440                        The Maid's Room (2014)   \n",
-       "911   160656                               Tallulah (2016)   \n",
-       "\n",
-       "                       genres  \n",
-       "0              Comedy|Romance  \n",
-       "1    Action|Adventure|Romance  \n",
-       "2              Children|Drama  \n",
-       "3               Drama|Mystery  \n",
-       "4              Comedy|Romance  \n",
-       "..                        ...  \n",
-       "907            Comedy|Western  \n",
-       "908        (no genres listed)  \n",
-       "909                    Horror  \n",
-       "910                  Thriller  \n",
-       "911                     Drama  \n",
-       "\n",
-       "[912 rows x 3 columns]"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Display The Ratings : \n"
+     "ename": "ImportError",
+     "evalue": "cannot import name 'Constant' from 'constants' (/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/constants.py)",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[0;31mImportError\u001b[0m                               Traceback (most recent call last)",
+      "Cell \u001b[0;32mIn[1], line 13\u001b[0m\n\u001b[1;32m     10\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01msklearn\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mlinear_model\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m LinearRegression\n\u001b[1;32m     12\u001b[0m \u001b[38;5;66;03m# Constants and functions\u001b[39;00m\n\u001b[0;32m---> 13\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mconstants\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Constant \u001b[38;5;28;01mas\u001b[39;00m C\n\u001b[1;32m     15\u001b[0m \u001b[38;5;66;03m# We use a pd.read_csv() so importing the loaders is not necessary\u001b[39;00m\n\u001b[1;32m     16\u001b[0m \u001b[38;5;66;03m# from loaders import load_ratings \u001b[39;00m\n\u001b[1;32m     17\u001b[0m \u001b[38;5;66;03m# from loaders import load_items\u001b[39;00m\n\u001b[1;32m     19\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtabulate\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m tabulate\n",
+      "\u001b[0;31mImportError\u001b[0m: cannot import name 'Constant' from 'constants' (/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/constants.py)"
      ]
-    },
-    {
-     "data": {
-      "text/html": [
-       "<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>userId</th>\n",
-       "      <th>movieId</th>\n",
-       "      <th>rating</th>\n",
-       "      <th>timestamp</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>15</td>\n",
-       "      <td>34</td>\n",
-       "      <td>3.0</td>\n",
-       "      <td>997938310</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>15</td>\n",
-       "      <td>95</td>\n",
-       "      <td>1.5</td>\n",
-       "      <td>1093028331</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>15</td>\n",
-       "      <td>101</td>\n",
-       "      <td>4.0</td>\n",
-       "      <td>1134522072</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>15</td>\n",
-       "      <td>123</td>\n",
-       "      <td>4.0</td>\n",
-       "      <td>997938358</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>15</td>\n",
-       "      <td>125</td>\n",
-       "      <td>3.5</td>\n",
-       "      <td>1245362506</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>...</th>\n",
-       "      <td>...</td>\n",
-       "      <td>...</td>\n",
-       "      <td>...</td>\n",
-       "      <td>...</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>5291</th>\n",
-       "      <td>665</td>\n",
-       "      <td>3908</td>\n",
-       "      <td>1.0</td>\n",
-       "      <td>1046967201</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>5292</th>\n",
-       "      <td>665</td>\n",
-       "      <td>4052</td>\n",
-       "      <td>4.0</td>\n",
-       "      <td>992838277</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>5293</th>\n",
-       "      <td>665</td>\n",
-       "      <td>4351</td>\n",
-       "      <td>4.0</td>\n",
-       "      <td>992837743</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>5294</th>\n",
-       "      <td>665</td>\n",
-       "      <td>4643</td>\n",
-       "      <td>4.0</td>\n",
-       "      <td>997239207</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>5295</th>\n",
-       "      <td>665</td>\n",
-       "      <td>5502</td>\n",
-       "      <td>4.0</td>\n",
-       "      <td>1046967596</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "<p>5296 rows × 4 columns</p>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "      userId  movieId  rating   timestamp\n",
-       "0         15       34     3.0   997938310\n",
-       "1         15       95     1.5  1093028331\n",
-       "2         15      101     4.0  1134522072\n",
-       "3         15      123     4.0   997938358\n",
-       "4         15      125     3.5  1245362506\n",
-       "...      ...      ...     ...         ...\n",
-       "5291     665     3908     1.0  1046967201\n",
-       "5292     665     4052     4.0   992838277\n",
-       "5293     665     4351     4.0   992837743\n",
-       "5294     665     4643     4.0   997239207\n",
-       "5295     665     5502     4.0  1046967596\n",
-       "\n",
-       "[5296 rows x 4 columns]"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
     }
    ],
    "source": [
@@ -320,7 +56,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -339,7 +75,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -364,7 +100,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -416,7 +152,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -435,7 +171,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -454,7 +190,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -474,7 +210,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -497,7 +233,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -520,7 +256,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -554,7 +290,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -584,7 +320,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -617,7 +353,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
@@ -680,7 +416,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
diff --git a/content_based.ipynb b/content_based.ipynb
index da98e312..df2d2bef 100644
--- a/content_based.ipynb
+++ b/content_based.ipynb
@@ -10,16 +10,20 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": 1,
    "id": "277473a3",
    "metadata": {},
    "outputs": [
     {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "The autoreload extension is already loaded. To reload it, use:\n",
-      "  %reload_ext autoreload\n"
+     "ename": "ImportError",
+     "evalue": "cannot import name 'Constant' from 'constants' (/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/constants.py)",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[0;31mImportError\u001b[0m                               Traceback (most recent call last)",
+      "Cell \u001b[0;32mIn[1], line 10\u001b[0m\n\u001b[1;32m      7\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01msurprise\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m AlgoBase\n\u001b[1;32m      8\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01msurprise\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mprediction_algorithms\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mpredictions\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m PredictionImpossible\n\u001b[0;32m---> 10\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mloaders\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m load_ratings\n\u001b[1;32m     11\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mloaders\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m load_items\n\u001b[1;32m     12\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mconstants\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Constant \u001b[38;5;28;01mas\u001b[39;00m C\n",
+      "File \u001b[0;32m~/vscodeworkspace/recomsys/loaders.py:7\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mos\u001b[39;00m\n\u001b[1;32m      6\u001b[0m \u001b[38;5;66;03m# Local imports\u001b[39;00m\n\u001b[0;32m----> 7\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mconstants\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Constant \u001b[38;5;28;01mas\u001b[39;00m C\n\u001b[1;32m      8\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01msurprise\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Reader, Dataset\n\u001b[1;32m     10\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mload_ratings\u001b[39m(surprise_format\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m):\n",
+      "\u001b[0;31mImportError\u001b[0m: cannot import name 'Constant' from 'constants' (/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/constants.py)"
      ]
     }
    ],
@@ -50,7 +54,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": null,
    "id": "e8378976",
    "metadata": {},
    "outputs": [
@@ -143,7 +147,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 16,
+   "execution_count": null,
    "id": "16b0a602",
    "metadata": {},
    "outputs": [],
@@ -245,7 +249,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
+   "execution_count": null,
    "id": "69d12f7d",
    "metadata": {},
    "outputs": [
diff --git a/loaders.py b/loaders.py
new file mode 100644
index 00000000..d4cc224f
--- /dev/null
+++ b/loaders.py
@@ -0,0 +1,51 @@
+# Third-party imports
+import pandas as pd
+import os
+
+
+# Local imports
+from constants import Constant as C
+from surprise import Reader, Dataset
+
+def load_ratings(surprise_format=False):
+    """Loads ratings data.
+
+    Parameters:
+        surprise_format (bool): If True, returns data in Surprise format.
+
+    Returns:
+        DataFrame or surprise_data: Ratings data.
+    """
+    df_ratings = pd.read_csv(C.EVIDENCE_PATH / C.RATINGS_FILENAME)
+    if surprise_format:
+        reader = Reader(rating_scale=C.RATINGS_SCALE) # on met 0.5 pcq c'est la plus petite note.
+        surprise_data = Dataset.load_from_df(df_ratings[['userId', 'movieId', 'rating']], reader)
+        return surprise_data
+    else:
+        return df_ratings
+
+
+def load_items():
+    """Loads items data.
+
+    Returns:
+        DataFrame: Items data.
+    """
+    df_items = pd.read_csv(C.CONTENT_PATH / C.ITEMS_FILENAME) # ce qui se trouve dans le movie csv
+    df_items = df_items.set_index(C.ITEM_ID_COL) # movie id
+    return df_items
+
+def export_evaluation_report(report):
+    """Exports evaluation report.
+
+    Parameters:
+        report: Evaluation report.
+
+    Returns:
+        DataFrame: Merged ratings and items data.
+    """
+    report_name = f"evaluation_report_{pd.Timestamp.now().strftime('%Y-%m-%d')}.csv"
+    export_path = os.path.join("data", "tiny", "evaluations", report_name)
+    report.to_csv(export_path, index=False)
+    print("The data has been exported to the evaluation report")
+    return report
\ No newline at end of file
diff --git a/models.py b/models.py
new file mode 100644
index 00000000..c288a5b8
--- /dev/null
+++ b/models.py
@@ -0,0 +1,181 @@
+# standard library imports
+from collections import defaultdict
+
+# third parties imports
+import pandas as pd
+import numpy as np
+import random as rd
+from surprise import AlgoBase, SVD, KNNWithMeans
+from surprise import PredictionImpossible
+
+# import local
+from loaders import load_items, load_ratings
+from constants import Constant as C
+from sklearn.linear_model import LinearRegression
+
+
+def get_top_n(predictions, n):
+    """Return the top-N recommendation for each user from a set of predictions.
+    Source: inspired by https://github.com/NicolasHug/Surprise/blob/master/examples/top_n_recommendations.py
+    and modified by cvandekerckh for random tie breaking
+
+    Args:
+        predictions(list of Prediction objects): The list of predictions, as
+            returned by the test method of an algorithm.
+        n(int): The number of recommendation to output for each user. Default
+            is 10.
+    Returns:
+    A dict where keys are user (raw) ids and values are lists of tuples:
+        [(raw item id, rating estimation), ...] of size n.
+    """
+
+    rd.seed(0)
+
+    # First map the predictions to each user.
+    top_n = defaultdict(list)
+    for uid, iid, true_r, est, _ in predictions:
+        top_n[uid].append((iid, est))
+
+    # Then sort the predictions for each user and retrieve the k highest ones.
+    for uid, user_ratings in top_n.items():
+        rd.shuffle(user_ratings)
+        user_ratings.sort(key=lambda x: x[1], reverse=True)
+        top_n[uid] = user_ratings[:n]
+
+    return top_n
+
+
+# First algorithm
+class ModelBaseline1(AlgoBase):
+    def __init__(self):
+        AlgoBase.__init__(self)
+
+    def estimate(self, u, i):
+        return 2
+
+
+# Second algorithm
+class ModelBaseline2(AlgoBase):
+    def __init__(self):
+        AlgoBase.__init__(self)
+
+    def fit(self, trainset):
+        AlgoBase.fit(self, trainset)
+        rd.seed(0)
+
+    def estimate(self, u, i):
+        return rd.uniform(self.trainset.rating_scale[0], self.trainset.rating_scale[1])
+
+
+# Third algorithm
+class ModelBaseline3(AlgoBase):
+    def __init__(self):
+        AlgoBase.__init__(self)
+
+    def fit(self, trainset):
+        AlgoBase.fit(self, trainset)
+        self.the_mean = np.mean([r for (_, _, r) in self.trainset.all_ratings()])
+
+        return self
+
+    def estimate(self, u, i):
+        return self.the_mean
+
+
+# Fourth Model
+class ModelBaseline4(SVD):
+    def __init__(self):
+        SVD.__init__(self, n_factors=100)
+
+
+class ContentBased(AlgoBase):
+    def __init__(self, features_method, regressor_method):
+        AlgoBase.__init__(self)
+        self.regressor_method = regressor_method
+        self.content_features = self.create_content_features(features_method)
+
+    def create_content_features(self, features_method):
+        """Content Analyzer"""
+        df_items = load_items()
+        if features_method is None:
+            df_features = None
+        elif features_method == "title_length": # a naive method that creates only 1 feature based on title length
+            df_features = df_items[C.LABEL_COL].apply(lambda x: len(x)).to_frame('n_character_title')
+        else: # (implement other feature creations here)
+            raise NotImplementedError(f'Feature method {features_method} not yet implemented')
+        return df_features
+    
+
+    def fit(self, trainset):
+        """Profile Learner"""
+        AlgoBase.fit(self, trainset)
+        
+        # Preallocate user profiles
+        self.user_profile = {u: None for u in trainset.all_users()}
+
+        if self.regressor_method == 'random_score':
+            for u in self.user_profile :
+                self.user_profile[u] = rd.uniform(0.5,5)
+            
+        elif self.regressor_method == 'random_sample':
+            for u in self.user_profile:
+                self.user_profile[u] = [rating for _, rating in self.trainset.ur[u]]
+        elif self.regressor_method == 'linear_regression' :
+            for u in self.user_profile:
+
+                user_ratings = [rating for _, rating in trainset.ur[u]]
+                item_ids = [iid for iid, _ in trainset.ur[u]]
+
+                df_user = pd.DataFrame({'item_id': item_ids, 'user_ratings': user_ratings})
+
+                df_user["item_id"] = df_user["item_id"].map(trainset.to_raw_iid)
+
+                df_user = df_user.merge(self.content_features, left_on = "item_id", right_index = True, how = 'left')
+
+                X = df_user['n_character_title'].values.reshape(-1,1)
+
+                y = df_user['user_ratings'].values
+
+                linear_regressor = LinearRegression(fit_intercept = False)
+
+                linear_regressor.fit(X,y)
+                
+                # Store the computed user profile
+                self.user_profile[u] = linear_regressor
+        else : 
+            pass
+
+            # (implement here the regressor fitting)  
+        
+    def estimate(self, u, i):
+        """Scoring component used for item filtering"""
+        # First, handle cases for unknown users and items
+        if not (self.trainset.knows_user(u) and self.trainset.knows_item(i)):
+            raise PredictionImpossible('User and/or item is unkown.')
+
+
+        if self.regressor_method == 'random_score':
+            rd.seed()
+            score = rd.uniform(0.5,5)
+
+        elif self.regressor_method == 'random_sample':
+            rd.seed()
+            score = rd.choice(self.user_profile[u])
+        
+        elif self.regressor_method == 'linear_regression':
+
+            raw_item_id = self.trainset.to_raw_iid(i)
+
+            item_features = self.content_features.loc[raw_item_id:raw_item_id, :].values
+
+            linear_regressor = self.user_profile[u]
+
+            score= linear_regressor.predict(item_features)[0]
+        else : 
+            score = None
+
+            # (implement here the regressor prediction)
+
+        return score
+
+
diff --git a/user_based.ipynb b/user_based.ipynb
new file mode 100644
index 00000000..28f2623a
--- /dev/null
+++ b/user_based.ipynb
@@ -0,0 +1,762 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "f4a8f664",
+   "metadata": {},
+   "source": [
+    "# Custom User-based Model\n",
+    "The present notebooks aims at creating a UserBased class that inherits from the Algobase class (surprise package) and that can be customized with various similarity metrics, peer groups and score aggregation functions. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "00d1b249",
+   "metadata": {},
+   "outputs": [
+    {
+     "ename": "ImportError",
+     "evalue": "cannot import name 'Constant' from 'constants' (/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/constants.py)",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[0;31mImportError\u001b[0m                               Traceback (most recent call last)",
+      "Cell \u001b[0;32mIn[1], line 14\u001b[0m\n\u001b[1;32m     10\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mpandas\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mpd\u001b[39;00m\n\u001b[1;32m     11\u001b[0m \u001b[38;5;66;03m# -- add new imports here --\u001b[39;00m\n\u001b[1;32m     12\u001b[0m \n\u001b[1;32m     13\u001b[0m \u001b[38;5;66;03m# local imports\u001b[39;00m\n\u001b[0;32m---> 14\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mconstants\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Constant \u001b[38;5;28;01mas\u001b[39;00m C\n\u001b[1;32m     15\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mloaders\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m load_ratings,load_items \n\u001b[1;32m     16\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01msurprise\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m KNNWithMeans, accuracy, AlgoBase, PredictionImpossible\n",
+      "\u001b[0;31mImportError\u001b[0m: cannot import name 'Constant' from 'constants' (/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/constants.py)"
+     ]
+    }
+   ],
+   "source": [
+    "# reloads modules automatically before entering the execution of code\n",
+    "%load_ext autoreload\n",
+    "%autoreload 2\n",
+    "\n",
+    "# standard library imports\n",
+    "# -- add new imports here --\n",
+    "\n",
+    "# third parties imports\n",
+    "import numpy as np \n",
+    "import pandas as pd\n",
+    "# -- add new imports here --\n",
+    "\n",
+    "# local imports\n",
+    "from constants import Constant as C\n",
+    "from loaders import load_ratings,load_items \n",
+    "from surprise import KNNWithMeans, accuracy, AlgoBase, PredictionImpossible\n",
+    "\n",
+    "import heapq"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "22716aa3",
+   "metadata": {},
+   "source": [
+    "# 1. Loading Data\n",
+    "Prepare a dataset in order to help implementing a user-based recommender system"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "aafd1712",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Computing the msd similarity matrix...\n",
+      "Done computing similarity matrix.\n",
+      "user: 11         item: 364        r_ui = 4.00   est = 3.42   {'was_impossible': True, 'reason': 'User and/or item is unknown.'}\n"
+     ]
+    }
+   ],
+   "source": [
+    "\n",
+    "# Create Surprise Dataset from the pandas DataFrame and Reader\n",
+    "surprise_data = load_ratings(surprise_format=True)\n",
+    "\n",
+    "trainset = surprise_data.build_full_trainset()\n",
+    "\n",
+    "\n",
+    "testset = trainset.build_anti_testset()\n",
+    "\n",
+    "\n",
+    "sim_options = {\n",
+    "    'name': 'msd',  # Mean Squared Difference (Mean Square Error)\n",
+    "    'user_based': True,  # User-based collaborative filtering\n",
+    "    'min_support': 3  # Minimum number of common ratings required\n",
+    "}\n",
+    "\n",
+    "\n",
+    "# Build an algorithm, and train it.\n",
+    "algo = KNNWithMeans(sim_options=sim_options, k=3, min_k=2)\n",
+    "algo.fit(trainset)\n",
+    "algo.test(testset)\n",
+    "\n",
+    "\n",
+    "uid = str(11)  # raw user id (as in the ratings file). They are **strings**!\n",
+    "iid = str(364) \n",
+    "\n",
+    "pred = algo.predict(uid, iid, r_ui=4, verbose=True)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "cf3ccdc0",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# -- load data, build trainset and anti testset --\n",
+    "# it depends on the tiny dataset\n",
+    "surprise_data = load_ratings(surprise_format=True)\n",
+    "df_movies = load_items()\n",
+    "\n",
+    "# Assuming you have a pandas DataFrame named 'df' with columns ['user_id', 'item_id', 'rating']\n",
+    "\n",
+    "# Build train set with all available ratings\n",
+    "trainset = surprise_data.build_full_trainset()\n",
+    "\n",
+    "# Build anti-test set\n",
+    "testset = trainset.build_anti_testset()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "94adf3a6",
+   "metadata": {},
+   "source": [
+    "# 2. Explore Surprise's user-based algorithm\n",
+    "Displays user-based predictions and similarity matrix on the test dataset using the KNNWithMeans class"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e6fb78b7",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Computing the msd similarity matrix...\n",
+      "Done computing similarity matrix.\n",
+      "3.4190898791540785\n"
+     ]
+    }
+   ],
+   "source": [
+    "# -- using surprise's user-based algorithm, explore the impact of different parameters and displays predictions --\n",
+    "\n",
+    "# Define the similarity options\n",
+    "sim_options = {\n",
+    "    'name': 'msd',  # Mean Squared Difference (Mean Square Error)\n",
+    "    'user_based': True,  # User-based collaborative filtering\n",
+    "    'min_support': 3  # Minimum number of common ratings required\n",
+    "}\n",
+    "\n",
+    "# Create an instance of KNNWithMeans with the specified options\n",
+    "knn_model = KNNWithMeans(k=3, min_k=2, sim_options=sim_options)\n",
+    "\n",
+    "# Train the algorithm on the trainset\n",
+    "knn_model.fit(trainset).test(testset)\n",
+    "\n",
+    "# Make an estimation for user 11 and item 364\n",
+    "prediction = knn_model.predict('11', '364')\n",
+    "print(prediction.est)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ffe89c56",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Computing the msd similarity matrix...\n",
+      "Done computing similarity matrix.\n",
+      "Predictions with min_k = 1:\n",
+      "User: 15, Item: 942, Rating: 3.7769516356699464\n",
+      "User: 15, Item: 2117, Rating: 2.9340004894942537\n",
+      "User: 15, Item: 2672, Rating: 2.371008709611413\n",
+      "User: 15, Item: 5054, Rating: 3.010328638497653\n",
+      "User: 15, Item: 6322, Rating: 1.711175832857413\n",
+      "User: 15, Item: 6323, Rating: 1.7645762379992287\n",
+      "User: 15, Item: 6757, Rating: 3.010328638497653\n",
+      "User: 15, Item: 7700, Rating: 3.561484741491386\n",
+      "User: 15, Item: 7981, Rating: 3.386000174210522\n",
+      "User: 15, Item: 8600, Rating: 3.320743223639117\n",
+      "User: 15, Item: 8620, Rating: 2.7538763809343654\n",
+      "User: 15, Item: 31952, Rating: 3.7409900837647396\n",
+      "User: 15, Item: 3, Rating: 2.222062601579949\n",
+      "User: 15, Item: 64, Rating: 0.9224387353614938\n",
+      "User: 15, Item: 206, Rating: 2.35668733389394\n",
+      "User: 15, Item: 249, Rating: 3.1290259851652826\n",
+      "User: 15, Item: 276, Rating: 2.1800017354806753\n",
+      "User: 15, Item: 369, Rating: 2.3082373858282694\n",
+      "User: 15, Item: 504, Rating: 2.2600496220227573\n",
+      "User: 15, Item: 515, Rating: 3.6575674086958188\n",
+      "User: 15, Item: 522, Rating: 2.4562020809509626\n",
+      "User: 15, Item: 580, Rating: 1.9073310817298395\n",
+      "User: 15, Item: 599, Rating: 2.780847470837928\n",
+      "User: 15, Item: 915, Rating: 2.761094249104645\n",
+      "User: 15, Item: 966, Rating: 3.0894953051643195\n",
+      "User: 15, Item: 1274, Rating: 2.9873500196382845\n",
+      "User: 15, Item: 1299, Rating: 3.0779327239728005\n",
+      "User: 15, Item: 1345, Rating: 2.2037629856623138\n",
+      "User: 15, Item: 1354, Rating: 2.001877412379849\n",
+      "User: 15, Item: 532, Rating: 2.7123071345260277\n",
+      "Computing the msd similarity matrix...\n",
+      "Done computing similarity matrix.\n",
+      "Predictions with min_k = 2:\n",
+      "User: 15, Item: 942, Rating: 3.7769516356699464\n",
+      "User: 15, Item: 2117, Rating: 2.9340004894942537\n",
+      "User: 15, Item: 2672, Rating: 2.371008709611413\n",
+      "User: 15, Item: 5054, Rating: 2.693661971830986\n",
+      "User: 15, Item: 6322, Rating: 1.711175832857413\n",
+      "User: 15, Item: 6323, Rating: 1.7645762379992287\n",
+      "User: 15, Item: 6757, Rating: 2.693661971830986\n",
+      "User: 15, Item: 7700, Rating: 3.561484741491386\n",
+      "User: 15, Item: 7981, Rating: 3.386000174210522\n",
+      "User: 15, Item: 8600, Rating: 3.320743223639117\n",
+      "User: 15, Item: 8620, Rating: 2.7538763809343654\n",
+      "User: 15, Item: 31952, Rating: 3.7409900837647396\n",
+      "User: 15, Item: 3, Rating: 2.222062601579949\n",
+      "User: 15, Item: 64, Rating: 0.9224387353614938\n",
+      "User: 15, Item: 206, Rating: 2.35668733389394\n",
+      "User: 15, Item: 249, Rating: 3.1290259851652826\n",
+      "User: 15, Item: 276, Rating: 2.1800017354806753\n",
+      "User: 15, Item: 369, Rating: 2.3082373858282694\n",
+      "User: 15, Item: 504, Rating: 2.2600496220227573\n",
+      "User: 15, Item: 515, Rating: 3.6575674086958188\n",
+      "User: 15, Item: 522, Rating: 2.4562020809509626\n",
+      "User: 15, Item: 580, Rating: 1.9073310817298395\n",
+      "User: 15, Item: 599, Rating: 2.780847470837928\n",
+      "User: 15, Item: 915, Rating: 2.761094249104645\n",
+      "User: 15, Item: 966, Rating: 2.693661971830986\n",
+      "User: 15, Item: 1274, Rating: 2.9873500196382845\n",
+      "User: 15, Item: 1299, Rating: 3.0779327239728005\n",
+      "User: 15, Item: 1345, Rating: 2.2037629856623138\n",
+      "User: 15, Item: 1354, Rating: 2.001877412379849\n",
+      "User: 15, Item: 532, Rating: 2.7123071345260277\n",
+      "Computing the msd similarity matrix...\n",
+      "Done computing similarity matrix.\n",
+      "Predictions with min_k = 3:\n",
+      "User: 15, Item: 942, Rating: 3.7769516356699464\n",
+      "User: 15, Item: 2117, Rating: 2.9340004894942537\n",
+      "User: 15, Item: 2672, Rating: 2.371008709611413\n",
+      "User: 15, Item: 5054, Rating: 2.693661971830986\n",
+      "User: 15, Item: 6322, Rating: 2.693661971830986\n",
+      "User: 15, Item: 6323, Rating: 1.7645762379992287\n",
+      "User: 15, Item: 6757, Rating: 2.693661971830986\n",
+      "User: 15, Item: 7700, Rating: 2.693661971830986\n",
+      "User: 15, Item: 7981, Rating: 3.386000174210522\n",
+      "User: 15, Item: 8600, Rating: 2.693661971830986\n",
+      "User: 15, Item: 8620, Rating: 2.7538763809343654\n",
+      "User: 15, Item: 31952, Rating: 2.693661971830986\n",
+      "User: 15, Item: 3, Rating: 2.222062601579949\n",
+      "User: 15, Item: 64, Rating: 0.9224387353614938\n",
+      "User: 15, Item: 206, Rating: 2.35668733389394\n",
+      "User: 15, Item: 249, Rating: 3.1290259851652826\n",
+      "User: 15, Item: 276, Rating: 2.1800017354806753\n",
+      "User: 15, Item: 369, Rating: 2.3082373858282694\n",
+      "User: 15, Item: 504, Rating: 2.2600496220227573\n",
+      "User: 15, Item: 515, Rating: 3.6575674086958188\n",
+      "User: 15, Item: 522, Rating: 2.4562020809509626\n",
+      "User: 15, Item: 580, Rating: 1.9073310817298395\n",
+      "User: 15, Item: 599, Rating: 2.780847470837928\n",
+      "User: 15, Item: 915, Rating: 2.761094249104645\n",
+      "User: 15, Item: 966, Rating: 2.693661971830986\n",
+      "User: 15, Item: 1274, Rating: 2.9873500196382845\n",
+      "User: 15, Item: 1299, Rating: 3.0779327239728005\n",
+      "User: 15, Item: 1345, Rating: 2.2037629856623138\n",
+      "User: 15, Item: 1354, Rating: 2.001877412379849\n",
+      "User: 15, Item: 532, Rating: 2.7123071345260277\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Playing with KNN\n",
+    "\n",
+    "# Define the similarity options\n",
+    "sim_options = {\n",
+    "    'name': 'msd',  # Mean Squared Difference (Mean Square Error)\n",
+    "    'user_based': True,  # User-based collaborative filtering\n",
+    "    'min_support': 3  # Minimum number of common ratings required. This data is\n",
+    "}\n",
+    "\n",
+    "# Create an instance of KNNWithMeans with the specified options\n",
+    "def predict_ratings(trainset, testset, min_k_values):\n",
+    "    for min_k in min_k_values:\n",
+    "        knn_model = KNNWithMeans(sim_options=sim_options, k=3, min_k=min_k)\n",
+    "        # Train the algorithm on the trainset\n",
+    "        knn_model.fit(trainset)\n",
+    "\n",
+    "        # Make predictions for all ratings in the anti testset\n",
+    "        predictions = knn_model.test(testset)\n",
+    "\n",
+    "        # Display 30 predictions\n",
+    "        print(f\"Predictions with min_k = {min_k}:\")\n",
+    "        for prediction in predictions[:30]:\n",
+    "            print(f\"User: {prediction.uid}, Item: {prediction.iid}, Rating: {prediction.est}\")\n",
+    "\n",
+    "# Assuming trainset and testset are already defined\n",
+    "predict_ratings(trainset, testset, min_k_values=[1, 2, 3])"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c5209097",
+   "metadata": {},
+   "source": [
+    "Quelque soit les neighbours (1,2,3) la valeur du ratings ne change pas "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c8890e11",
+   "metadata": {},
+   "source": [
+    "1).Predictions with min_k = 1: In this case, the model makes predictions without considering any minimum number of neighbors. Each prediction is made solely based on the similarity between the target user and other users who have rated the same items. Consequently, we observe varying prediction values for different items. For instance, for user 15 and item 942, the predicted rating is 3.777, while for item 64, the predicted rating is only 0.922. This indicates that the model heavily relies on the ratings from users who may have rated only a single item in common with the target user, leading to potentially erratic predictions.\n",
+    "\n",
+    "2). Predictions with min_k = 2: Here, a minimum of 2 neighbors are required to make a prediction. This introduces a bit of regularization, ensuring that predictions are made based on a slightly broader consensus. We notice that the predictions are somewhat similar to those with min_k = 1, but there are slight changes in some ratings. For example, the rating for item 5054 changes from 3.010 to 2.694. This suggests that the model is slightly more conservative in its predictions due to the requirement of at least two neighbors.\n",
+    "\n",
+    "3). Predictions with min_k = 3: With a minimum of 3 neighbors, the model becomes even more conservative. It requires a stronger consensus among users before making predictions. As a result, we see more uniformity in the predicted ratings compared to the previous cases. For example, for item 6322, the prediction changes from 1.711 (min_k = 1) to 2.694 (min_k = 2) and finally to 2.694 again (min_k = 3). This indicates that the model is increasingly cautious as it demands more agreement among neighbors before making predictions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "cc806424",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "\n",
+      "Prédictions avec min_support = 1:\n",
+      "User: 15, Item: 942, Actual_k: 3\n",
+      "User: 15, Item: 2117, Actual_k: 3\n",
+      "User: 15, Item: 2672, Actual_k: 3\n",
+      "User: 15, Item: 5054, Actual_k: 1\n",
+      "User: 15, Item: 6322, Actual_k: 2\n",
+      "User: 15, Item: 6323, Actual_k: 3\n",
+      "User: 15, Item: 6757, Actual_k: 1\n",
+      "User: 15, Item: 7700, Actual_k: 2\n",
+      "User: 15, Item: 7981, Actual_k: 3\n",
+      "User: 15, Item: 8600, Actual_k: 2\n",
+      "User: 15, Item: 8620, Actual_k: 3\n",
+      "User: 15, Item: 31952, Actual_k: 2\n",
+      "User: 15, Item: 3, Actual_k: 3\n",
+      "User: 15, Item: 64, Actual_k: 3\n",
+      "User: 15, Item: 206, Actual_k: 3\n",
+      "User: 15, Item: 249, Actual_k: 3\n",
+      "User: 15, Item: 276, Actual_k: 3\n",
+      "User: 15, Item: 369, Actual_k: 3\n",
+      "User: 15, Item: 504, Actual_k: 3\n",
+      "User: 15, Item: 515, Actual_k: 3\n",
+      "User: 15, Item: 522, Actual_k: 3\n",
+      "User: 15, Item: 580, Actual_k: 3\n",
+      "User: 15, Item: 599, Actual_k: 3\n",
+      "User: 15, Item: 915, Actual_k: 3\n",
+      "User: 15, Item: 966, Actual_k: 1\n",
+      "User: 15, Item: 1274, Actual_k: 3\n",
+      "User: 15, Item: 1299, Actual_k: 3\n",
+      "User: 15, Item: 1345, Actual_k: 3\n",
+      "User: 15, Item: 1354, Actual_k: 3\n",
+      "User: 15, Item: 532, Actual_k: 3\n",
+      "\n",
+      "Prédictions avec min_support = 2:\n",
+      "User: 15, Item: 942, Actual_k: 3\n",
+      "User: 15, Item: 2117, Actual_k: 3\n",
+      "User: 15, Item: 2672, Actual_k: 3\n",
+      "User: 15, Item: 5054, Actual_k: 1\n",
+      "User: 15, Item: 6322, Actual_k: 2\n",
+      "User: 15, Item: 6323, Actual_k: 3\n",
+      "User: 15, Item: 6757, Actual_k: 1\n",
+      "User: 15, Item: 7700, Actual_k: 2\n",
+      "User: 15, Item: 7981, Actual_k: 3\n",
+      "User: 15, Item: 8600, Actual_k: 2\n",
+      "User: 15, Item: 8620, Actual_k: 3\n",
+      "User: 15, Item: 31952, Actual_k: 2\n",
+      "User: 15, Item: 3, Actual_k: 3\n",
+      "User: 15, Item: 64, Actual_k: 3\n",
+      "User: 15, Item: 206, Actual_k: 3\n",
+      "User: 15, Item: 249, Actual_k: 3\n",
+      "User: 15, Item: 276, Actual_k: 3\n",
+      "User: 15, Item: 369, Actual_k: 3\n",
+      "User: 15, Item: 504, Actual_k: 3\n",
+      "User: 15, Item: 515, Actual_k: 3\n",
+      "User: 15, Item: 522, Actual_k: 3\n",
+      "User: 15, Item: 580, Actual_k: 3\n",
+      "User: 15, Item: 599, Actual_k: 3\n",
+      "User: 15, Item: 915, Actual_k: 3\n",
+      "User: 15, Item: 966, Actual_k: 1\n",
+      "User: 15, Item: 1274, Actual_k: 3\n",
+      "User: 15, Item: 1299, Actual_k: 3\n",
+      "User: 15, Item: 1345, Actual_k: 3\n",
+      "User: 15, Item: 1354, Actual_k: 3\n",
+      "User: 15, Item: 532, Actual_k: 3\n",
+      "\n",
+      "Prédictions avec min_support = 3:\n",
+      "User: 15, Item: 942, Actual_k: 3\n",
+      "User: 15, Item: 2117, Actual_k: 3\n",
+      "User: 15, Item: 2672, Actual_k: 3\n",
+      "User: 15, Item: 5054, Actual_k: 1\n",
+      "User: 15, Item: 6322, Actual_k: 2\n",
+      "User: 15, Item: 6323, Actual_k: 3\n",
+      "User: 15, Item: 6757, Actual_k: 1\n",
+      "User: 15, Item: 7700, Actual_k: 2\n",
+      "User: 15, Item: 7981, Actual_k: 3\n",
+      "User: 15, Item: 8600, Actual_k: 2\n",
+      "User: 15, Item: 8620, Actual_k: 3\n",
+      "User: 15, Item: 31952, Actual_k: 2\n",
+      "User: 15, Item: 3, Actual_k: 3\n",
+      "User: 15, Item: 64, Actual_k: 3\n",
+      "User: 15, Item: 206, Actual_k: 3\n",
+      "User: 15, Item: 249, Actual_k: 3\n",
+      "User: 15, Item: 276, Actual_k: 3\n",
+      "User: 15, Item: 369, Actual_k: 3\n",
+      "User: 15, Item: 504, Actual_k: 3\n",
+      "User: 15, Item: 515, Actual_k: 3\n",
+      "User: 15, Item: 522, Actual_k: 3\n",
+      "User: 15, Item: 580, Actual_k: 3\n",
+      "User: 15, Item: 599, Actual_k: 3\n",
+      "User: 15, Item: 915, Actual_k: 3\n",
+      "User: 15, Item: 966, Actual_k: 1\n",
+      "User: 15, Item: 1274, Actual_k: 3\n",
+      "User: 15, Item: 1299, Actual_k: 3\n",
+      "User: 15, Item: 1345, Actual_k: 3\n",
+      "User: 15, Item: 1354, Actual_k: 3\n",
+      "User: 15, Item: 532, Actual_k: 3\n",
+      "\n",
+      "Matrice de similarité:\n",
+      "[[1.         0.39130435 0.35942029 ... 0.24358974 0.28513238 0.21451104]\n",
+      " [0.39130435 1.         0.32786885 ... 0.30967742 0.42424242 0.21621622]\n",
+      " [0.35942029 0.32786885 1.         ... 0.36666667 0.72727273 0.34375   ]\n",
+      " ...\n",
+      " [0.24358974 0.30967742 0.36666667 ... 1.         0.6779661  0.37569061]\n",
+      " [0.28513238 0.42424242 0.72727273 ... 0.6779661  1.         0.83333333]\n",
+      " [0.21451104 0.21621622 0.34375    ... 0.37569061 0.83333333 1.        ]]\n",
+      "None\n"
+     ]
+    }
+   ],
+   "source": [
+    "def analyse_min_support(knn_model, testset):\n",
+    "    # Rétablir min_k à 2\n",
+    "    knn_model.min_k = 2\n",
+    "\n",
+    "    # Modifier min_support de 1 à 3 et observer actual_k\n",
+    "    for min_support in range(1, 4):\n",
+    "        knn_model.sim_options['min_support'] = min_support\n",
+    "        predictions_min_support = knn_model.test(testset[:30])  # Prendre les 30 premières prédictions pour l'affichage\n",
+    "        print(f\"\\nPrédictions avec min_support = {min_support}:\")\n",
+    "        for prediction in predictions_min_support:\n",
+    "            actual_k = prediction.details['actual_k']\n",
+    "            print(f\"User: {prediction.uid}, Item: {prediction.iid}, Actual_k: {actual_k}\")\n",
+    "\n",
+    "    # Visualiser la matrice de similarité\n",
+    "    similarity_matrix = knn_model.sim  # Algorithme de knn_model\n",
+    "    print(\"\\nMatrice de similarité:\")\n",
+    "    print(similarity_matrix)\n",
+    "\n",
+    "# Appel de la fonction et impression de l'analyse\n",
+    "result = analyse_min_support(knn_model, testset)\n",
+    "print(result)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2dd01f5b",
+   "metadata": {},
+   "source": [
+    "# 3. Implement and explore a customizable user-based algorithm\n",
+    "Create a self-made user-based algorithm allowing to customize the similarity metric, peer group calculation and aggregation function"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "d03ed9eb",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "[[3.  1.5 4.  ... nan nan nan]\n",
+      " [nan nan nan ... nan nan nan]\n",
+      " [4.  3.  3.  ... nan nan nan]\n",
+      " ...\n",
+      " [4.5 nan nan ... nan nan nan]\n",
+      " [nan nan nan ... nan nan nan]\n",
+      " [2.  nan nan ... nan nan nan]]\n"
+     ]
+    }
+   ],
+   "source": [
+    "class UserBased(AlgoBase):\n",
+    "    def __init__(self, k=3, min_k=1, sim_options={}, **kwargs):\n",
+    "        AlgoBase.__init__(self, sim_options=sim_options, **kwargs)\n",
+    "        self.k = k\n",
+    "        self.min_k = min_k\n",
+    "        self.sim_options = sim_options\n",
+    "\n",
+    "        \n",
+    "    def fit(self, trainset):\n",
+    "        AlgoBase.fit(self, trainset)\n",
+    "        self.compute_rating_matrix()\n",
+    "        self.compute_similarity_matrix()\n",
+    "        self.compute_mean_ratings()\n",
+    "    \n",
+    "    def estimate(self, u, i):\n",
+    "        if not (self.trainset.knows_user(u) and self.trainset.knows_item(i)):\n",
+    "            raise PredictionImpossible('User and/or item is unknown.')\n",
+    "\n",
+    "        estimate = self.mean_ratings[u]\n",
+    "\n",
+    "        # Step 1: Create the peer group of user u for item i\n",
+    "        peer_group = []\n",
+    "        for j, rating in enumerate(self.trainset.ir[i]):\n",
+    "            if rating is not None:\n",
+    "                similarity = self.sim[u, j]  # Similarity between user u and user j for item i\n",
+    "                peer_group.append((j, similarity, rating))\n",
+    "\n",
+    "        # Step 2: Pick up the top neighbors efficiently\n",
+    "        k_neighbors = heapq.nlargest(self.min_k, peer_group, key=lambda x: x[1])  # Top k neighbors based on similarity\n",
+    "\n",
+    "        # Step 3: Compute the weighted average\n",
+    "        actual_k = len(k_neighbors)\n",
+    "        if actual_k >= self.min_k:\n",
+    "            weighted_sum = 0\n",
+    "            total_similarity = 0\n",
+    "            for j, similarity, rating_list in k_neighbors:\n",
+    "                # Assuming rating_list is a list or array containing ratings\n",
+    "                rating = rating_list[0]  # Access the first element of the rating list\n",
+    "                weighted_sum += similarity * rating\n",
+    "                total_similarity += similarity\n",
+    "\n",
+    "            if total_similarity != 0:\n",
+    "                peer_group_average = weighted_sum / total_similarity\n",
+    "                estimate += peer_group_average\n",
+    "\n",
+    "        return estimate\n",
+    "\n",
+    "                    \n",
+    "    def compute_rating_matrix(self):\n",
+    "        # Get the number of users and items\n",
+    "        n_users = self.trainset.n_users\n",
+    "        n_items = self.trainset.n_items\n",
+    "    \n",
+    "        ratings_matrix = np.empty((n_users, n_items))\n",
+    "        ratings_matrix[:] = np.nan\n",
+    "\n",
+    "        # Fill in the ratings matrix with available ratings\n",
+    "        for user_id, user_ratings in self.trainset.ur.items():\n",
+    "            if user_ratings:  # Check if user has ratings\n",
+    "                for item_id, rating in user_ratings:\n",
+    "                    ratings_matrix[user_id, item_id] = rating\n",
+    "    \n",
+    "        # Set the computed ratings matrix to self.ratings_matrix\n",
+    "        self.ratings_matrix = ratings_matrix\n",
+    "    \n",
+    "    def compute_similarity_matrix(self):\n",
+    "        # Get the number of users\n",
+    "        n_users = self.trainset.n_users\n",
+    "        \n",
+    "        # Initialize the similarity matrix with zeros and ones in the diagonal\n",
+    "        similarity_matrix = np.eye(n_users)\n",
+    "        \n",
+    "        # Iterate through pairs of users to compute similarities\n",
+    "        for i in range(n_users):\n",
+    "            for j in range(i + 1, n_users):\n",
+    "                # Compute support\n",
+    "                support = np.sum(~np.isnan(self.ratings_matrix[i]) & ~np.isnan(self.ratings_matrix[j]))\n",
+    "                \n",
+    "                # Check if support is greater than or equal to min_k\n",
+    "                if support >= self.min_k:\n",
+    "                    # Compute similarity using Jaccard similarity\n",
+    "                    intersection = np.sum(~np.isnan(self.ratings_matrix[i]) & ~np.isnan(self.ratings_matrix[j]))\n",
+    "                    union = np.sum(~np.isnan(self.ratings_matrix[i]) | ~np.isnan(self.ratings_matrix[j]))\n",
+    "                    similarity = intersection / union\n",
+    "                    similarity_matrix[i, j] = similarity\n",
+    "                    similarity_matrix[j, i] = similarity  # Similarity matrix is symmetric\n",
+    "        \n",
+    "        # Set the computed similarity matrix to self.sim\n",
+    "        self.sim = similarity_matrix\n",
+    "    \n",
+    "    def compute_mean_ratings(self):\n",
+    "        # Compute the mean rating of every user\n",
+    "        mean_ratings = []\n",
+    "        for user_id, ratings in self.trainset.ur.items():\n",
+    "            if ratings:  # Check if user has ratings\n",
+    "                mean_rating = np.mean([rating[1] for rating in ratings])\n",
+    "                mean_ratings.append(mean_rating)\n",
+    "            else:\n",
+    "                mean_ratings.append(0)  # If no ratings available, set mean to 0\n",
+    "        \n",
+    "        # Set the computed mean ratings\n",
+    "        self.mean_ratings = mean_ratings\n",
+    "\n",
+    "    \n",
+    "user_based_instance = UserBased(trainset=trainset)\n",
+    "\n",
+    "# Appel de la méthode fit pour calculer les matrices des évaluations, de similarité et les moyennes des évaluations\n",
+    "user_based_instance.fit(trainset)\n",
+    "\n",
+    "# Affichage de la matrice des évaluations\n",
+    "print(user_based_instance.ratings_matrix)\n",
+    "\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "dfdc9cfe",
+   "metadata": {},
+   "source": [
+    "# 4. Compare KNNWithMeans with UserBased\n",
+    "Try to replicate KNNWithMeans with your self-made UserBased and check that outcomes are identical"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "be53ae27",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "UserBased MAE: 1.5398252671298895\n",
+      "UserBased RMSE: 1.5553141029705104\n",
+      "KNNWithMeans MAE: 0.5419110316300769\n",
+      "KNNWithMeans RMSE: 0.7019543155680094\n"
+     ]
+    }
+   ],
+   "source": [
+    "# 1. Obtain Predictions\n",
+    "# Using UserBased algorithm\n",
+    "user_based_predictions = []\n",
+    "for uid, iid, true_r in testset:\n",
+    "    user_based_pred = user_based_instance.predict(uid, iid)\n",
+    "    user_based_predictions.append((uid, iid, true_r, user_based_pred.est, {}))\n",
+    "\n",
+    "# Using KNNWithMeans algorithm\n",
+    "knn_predictions = []\n",
+    "for uid, iid, true_r in testset:\n",
+    "    knn_pred = knn_model.predict(uid, iid)\n",
+    "    knn_predictions.append((uid, iid, true_r, knn_pred.est, knn_pred.details))\n",
+    "\n",
+    "# 2. Calculate Metrics\n",
+    "# Calculate MAE and RMSE for UserBased algorithm\n",
+    "user_based_mae = accuracy.mae(user_based_predictions, verbose=False)\n",
+    "user_based_rmse = accuracy.rmse(user_based_predictions, verbose=False)\n",
+    "\n",
+    "# Calculate MAE and RMSE for KNNWithMeans algorithm\n",
+    "knn_mae = accuracy.mae(knn_predictions, verbose=False)\n",
+    "knn_rmse = accuracy.rmse(knn_predictions, verbose=False)\n",
+    "\n",
+    "# 3. Compare Results\n",
+    "print(\"UserBased MAE:\", user_based_mae)\n",
+    "print(\"UserBased RMSE:\", user_based_rmse)\n",
+    "print(\"KNNWithMeans MAE:\", knn_mae)\n",
+    "print(\"KNNWithMeans RMSE:\", knn_rmse)\n",
+    "\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "cced76d9",
+   "metadata": {},
+   "source": [
+    "# 5. Compare MSD and Jacard\n",
+    "Compare predictions made with MSD similarity and Jacard similarity\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c20d8e19",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Computing the msd similarity matrix...\n",
+      "Done computing similarity matrix.\n",
+      "Computing the cosine similarity matrix...\n",
+      "Done computing similarity matrix.\n",
+      "RMSE: 0.9799\n",
+      "RMSE: 0.9871\n",
+      "RMSE with MSD similarity: 0.9798533097556152\n",
+      "RMSE with Jaccard similarity: 0.9870653791755158\n"
+     ]
+    }
+   ],
+   "source": [
+    "from surprise import accuracy\n",
+    "from surprise.model_selection import train_test_split\n",
+    "from surprise import Dataset, Reader\n",
+    "from surprise import KNNBasic\n",
+    "\n",
+    "\n",
+    "# Split the dataset into training and testing sets\n",
+    "trainset, testset = train_test_split(surprise_data, test_size=0.2)\n",
+    "\n",
+    "# Initialize the model with MSD similarity\n",
+    "sim_options_msd = {'name': 'msd'}\n",
+    "user_based_msd = KNNBasic(sim_options=sim_options_msd)\n",
+    "user_based_msd.fit(trainset)\n",
+    "\n",
+    "# Initialize the model with Jacard similarity\n",
+    "sim_options_jaccard = {'name': 'cosine'}\n",
+    "user_based_jaccard = KNNBasic(sim_options=sim_options_jaccard)\n",
+    "user_based_jaccard.fit(trainset)\n",
+    "\n",
+    "# Make predictions with each model on the test set\n",
+    "predictions_msd = user_based_msd.test(testset)\n",
+    "predictions_jaccard = user_based_jaccard.test(testset)\n",
+    "\n",
+    "# Calculate and display the performances of the two models\n",
+    "rmse_msd = accuracy.rmse(predictions_msd)\n",
+    "rmse_jaccard = accuracy.rmse(predictions_jaccard)\n",
+    "\n",
+    "print(\"RMSE with MSD similarity:\", rmse_msd)\n",
+    "print(\"RMSE with Jaccard similarity:\", rmse_jaccard)\n"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "mon_environnement",
+   "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.12.2"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
-- 
GitLab