From 97c891f129fe56b9455fd3944d785aa16fb0a4af Mon Sep 17 00:00:00 2001 From: VemulapalliMukesh27 Date: Wed, 16 Oct 2024 21:20:05 +0530 Subject: [PATCH] #10 netflix-analysis-recommendation --- Ml-Ds/netflixRecommendation/Mukku27.ipynb | 667 ++++++++++++++++++++++ 1 file changed, 667 insertions(+) create mode 100644 Ml-Ds/netflixRecommendation/Mukku27.ipynb diff --git a/Ml-Ds/netflixRecommendation/Mukku27.ipynb b/Ml-Ds/netflixRecommendation/Mukku27.ipynb new file mode 100644 index 0000000..697839e --- /dev/null +++ b/Ml-Ds/netflixRecommendation/Mukku27.ipynb @@ -0,0 +1,667 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "gpuType": "T4" + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + }, + "accelerator": "GPU" + }, + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Netflix Movie Dataset Analysis and Recommendation System\n", + "\n", + "In this notebook, we will:\n", + "1. Download the Netflix movie dataset.\n", + "2. Perform exploratory data analysis (EDA).\n", + "3. Visualize data trends.\n", + "4. Draw deductions and insights.\n", + "5. Build a movie recommendation system using collaborative filtering.\n", + "\n", + "Let's begin by downloading the dataset and installing the necessary libraries.\n" + ], + "metadata": { + "id": "4MazqJvohoJi" + } + }, + { + "cell_type": "markdown", + "source": [ + "# Dataset Download\n" + ], + "metadata": { + "id": "ZVjNQZqSiXnW" + } + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "vo3N5GfIgrdF", + "outputId": "895537ea-0ad9-4545-fda3-d984fa68756f" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Downloading from https://www.kaggle.com/api/v1/datasets/download/netflix-inc/netflix-prize-data?dataset_version_number=2...\n" + ] + }, + { + "output_type": "stream", + "name": "stderr", + "text": [ + "100%|██████████| 683M/683M [00:06<00:00, 113MB/s]" + ] + }, + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Extracting files...\n" + ] + }, + { + "output_type": "stream", + "name": "stderr", + "text": [ + "\n" + ] + }, + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Path to dataset files: /root/.cache/kagglehub/datasets/netflix-inc/netflix-prize-data/versions/2\n" + ] + } + ], + "source": [ + "# Import KaggleHub to download Netflix dataset from Kaggle\n", + "import kagglehub\n", + "\n", + "# Download the Netflix dataset\n", + "path = kagglehub.dataset_download(\"netflix-inc/netflix-prize-data\")\n", + "\n", + "print(\"Path to dataset files:\", path)\n" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Install Necessary Libraries" + ], + "metadata": { + "id": "k2tLIC4Jih5f" + } + }, + { + "cell_type": "code", + "source": [ + "# Install Surprise library for collaborative filtering model\n", + "!pip install surprise\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "vQi8CvOriiMt", + "outputId": "ec6d8b96-e6a1-4d6a-df10-6ea525958e89" + }, + "execution_count": 2, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Collecting surprise\n", + " Downloading surprise-0.1-py2.py3-none-any.whl.metadata (327 bytes)\n", + "Collecting scikit-surprise (from surprise)\n", + " Downloading scikit_surprise-1.1.4.tar.gz (154 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m154.4/154.4 kB\u001b[0m \u001b[31m4.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h Installing build dependencies ... \u001b[?25l\u001b[?25hdone\n", + " Getting requirements to build wheel ... \u001b[?25l\u001b[?25hdone\n", + " Preparing metadata (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n", + "Requirement already satisfied: joblib>=1.2.0 in /usr/local/lib/python3.10/dist-packages (from scikit-surprise->surprise) (1.4.2)\n", + "Requirement already satisfied: numpy>=1.19.5 in /usr/local/lib/python3.10/dist-packages (from scikit-surprise->surprise) (1.26.4)\n", + "Requirement already satisfied: scipy>=1.6.0 in /usr/local/lib/python3.10/dist-packages (from scikit-surprise->surprise) (1.13.1)\n", + "Downloading surprise-0.1-py2.py3-none-any.whl (1.8 kB)\n", + "Building wheels for collected packages: scikit-surprise\n", + " Building wheel for scikit-surprise (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n", + " Created wheel for scikit-surprise: filename=scikit_surprise-1.1.4-cp310-cp310-linux_x86_64.whl size=2357269 sha256=d607d3f9006384ce44e05f113b4ccff8aaabfb05fb4da76ac25878ac1e6ecafc\n", + " Stored in directory: /root/.cache/pip/wheels/4b/3f/df/6acbf0a40397d9bf3ff97f582cc22fb9ce66adde75bc71fd54\n", + "Successfully built scikit-surprise\n", + "Installing collected packages: scikit-surprise, surprise\n", + "Successfully installed scikit-surprise-1.1.4 surprise-0.1\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Import Libraries and Set Up" + ], + "metadata": { + "id": "Hoyetrz6iibB" + } + }, + { + "cell_type": "code", + "source": [ + "# Import necessary libraries for analysis and modeling\n", + "import pandas as pd\n", + "import numpy as np\n", + "import math\n", + "import re\n", + "from scipy.sparse import csr_matrix\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "from surprise import Reader, Dataset, SVD\n", + "from surprise.model_selection import cross_validate\n", + "\n", + "# Set seaborn style for better visualization\n", + "sns.set_style(\"darkgrid\")\n" + ], + "metadata": { + "id": "ShetVtMTiioN" + }, + "execution_count": 3, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Load and Explore Datasets" + ], + "metadata": { + "id": "YSVGmgG9ii1Y" + } + }, + { + "cell_type": "code", + "source": [ + "# Load Netflix datasets and explore their structure\n", + "df1 = pd.read_csv(f'{path}/combined_data_1.txt', header=None, names=['Cust_Id', 'Rating'], usecols=[0, 1])\n", + "df1['Rating'] = df1['Rating'].astype(float)\n", + "\n", + "# Display dataset shape and sample rows\n", + "print('Dataset 1 shape: {}'.format(df1.shape))\n", + "print('-Dataset examples-')\n", + "print(df1.iloc[::5000000, :])\n", + "\n", + "# Repeat for datasets 2, 3, and 4\n", + "df2 = pd.read_csv(f'{path}/combined_data_2.txt', header=None, names=['Cust_Id', 'Rating'], usecols=[0,1])\n", + "df3 = pd.read_csv(f'{path}/combined_data_3.txt', header=None, names=['Cust_Id', 'Rating'], usecols=[0,1])\n", + "df4 = pd.read_csv(f'{path}/combined_data_4.txt', header=None, names=['Cust_Id', 'Rating'], usecols=[0,1])\n", + "\n", + "# Convert ratings to float for consistency\n", + "df2['Rating'] = df2['Rating'].astype(float)\n", + "df3['Rating'] = df3['Rating'].astype(float)\n", + "df4['Rating'] = df4['Rating'].astype(float)\n", + "\n", + "# Print shapes of datasets for verification\n", + "print('Dataset 2 shape: {}'.format(df2.shape))\n", + "print('Dataset 3 shape: {}'.format(df3.shape))\n", + "print('Dataset 4 shape: {}'.format(df4.shape))\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "1pJFzOZDijF_", + "outputId": "63200ab7-c16b-4780-b8a7-b6281f1af9be" + }, + "execution_count": 4, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Dataset 1 shape: (24058263, 2)\n", + "-Dataset examples-\n", + " Cust_Id Rating\n", + "0 1: NaN\n", + "5000000 2560324 4.0\n", + "10000000 2271935 2.0\n", + "15000000 1921803 2.0\n", + "20000000 1933327 3.0\n", + "Dataset 2 shape: (26982302, 2)\n", + "Dataset 3 shape: (22605786, 2)\n", + "Dataset 4 shape: (26851926, 2)\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Combine Datasets\n" + ], + "metadata": { + "id": "9W_XL7JFijbV" + } + }, + { + "cell_type": "code", + "source": [ + "# Combine all the datasets into one dataframe\n", + "df = pd.concat([df1, df2, df3, df4], ignore_index=True)\n", + "\n", + "# Reset the index after concatenation\n", + "df.index = np.arange(0, len(df))\n", + "\n", + "# Print combined dataset shape and examples\n", + "print('Full dataset shape: {}'.format(df.shape))\n", + "print('-Dataset examples-')\n", + "print(df.iloc[::5000000, :])\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "N6CLyelkijrA", + "outputId": "69deaac1-1c21-432f-f280-a73131210cbb" + }, + "execution_count": 5, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Full dataset shape: (100498277, 2)\n", + "-Dataset examples-\n", + " Cust_Id Rating\n", + "0 1: NaN\n", + "5000000 2560324 4.0\n", + "10000000 2271935 2.0\n", + "15000000 1921803 2.0\n", + "20000000 1933327 3.0\n", + "25000000 1465002 3.0\n", + "30000000 961023 4.0\n", + "35000000 1372532 5.0\n", + "40000000 854274 5.0\n", + "45000000 116334 3.0\n", + "50000000 768483 3.0\n", + "55000000 1331144 5.0\n", + "60000000 1609324 2.0\n", + "65000000 1699240 3.0\n", + "70000000 1776418 4.0\n", + "75000000 1643826 5.0\n", + "80000000 932047 4.0\n", + "85000000 2292868 4.0\n", + "90000000 932191 4.0\n", + "95000000 1815101 3.0\n", + "100000000 872339 4.0\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Exploratory Data Analysis (EDA) - Basic Overview\n", + "**Deductions:**\n", + "\n", + "1. Rating Distribution: The bar chart reveals that most ratings given by users are skewed toward higher values, indicating that users tend to give favorable ratings to movies.\n", + "2. User Behavior: Customers tend to be less critical and more positive in rating movies, with fewer low ratings being given.\n", + "\n" + ], + "metadata": { + "id": "7nezGI_Aij8J" + } + }, + { + "cell_type": "code", + "source": [ + "# EDA: Group by rating and count each rating occurrence\n", + "p = df.groupby('Rating')['Rating'].agg(['count'])\n", + "\n", + "# Calculate movie count (rows with NaN values in Rating)\n", + "movie_count = df.isnull().sum()[1]\n", + "\n", + "# Calculate customer count and rating count\n", + "cust_count = df['Cust_Id'].nunique() - movie_count\n", + "rating_count = df['Cust_Id'].count() - movie_count\n", + "\n", + "# Visualize the distribution of ratings\n", + "plt.figure(figsize=(15, 8))\n", + "ax = p.plot(kind='barh', legend=False, figsize=(15,10), color='skyblue')\n", + "plt.title('Total pool: {:,} Movies, {:,} customers, {:,} ratings given'.format(movie_count, cust_count, rating_count), fontsize=20)\n", + "plt.xlabel('Count of Ratings')\n", + "plt.ylabel('Rating Value')\n", + "plt.show()\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 952 + }, + "id": "0HgeRda5ikMV", + "outputId": "c63abd3a-f527-4e6a-a2a8-ba304a50337e" + }, + "execution_count": 6, + "outputs": [ + { + "output_type": "stream", + "name": "stderr", + "text": [ + ":5: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`\n", + " movie_count = df.isnull().sum()[1]\n" + ] + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ] + }, + "metadata": {} + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "iVBORw0KGgoAAAANSUhEUgAABNEAAANfCAYAAAD3sUtYAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAACIuklEQVR4nOzdd3yV5f0//nfYIsgUUcFiUXCARcSBQlGwigMFxboQqQMH2mptbdG66/rW1Q8O1NaBoCIKKCoiKA5UZKqACIoiIMjeICv5/cEvpwkZdxISEuX5fDx4kJxz3/e5zsl17vG6r5GWkZGREQAAAABAnsqVdgEAAAAAoKwTogEAAABAAiEaAAAAACQQogEAAABAAiEaAAAAACQQogEAAABAAiEaAAAAACQQogEAAABAAiEaAAAAACQQorHT6dOnTzRt2jSaNm1a2kUpE3we7Gw+/fTTVJ3/9NNPS7s4AADFYvDgwalznHnz5pV2cX72fJ7kpkJpF4CyYd68edGhQ4ft3s6MGTOKoTT8Ui1YsCC++OKLmDJlSnzxxRcxbdq0WLNmTUREXHXVVXH11Vfnu/6nn34a3bt3L9RrHnHEEfHcc88VuqwXXHBBjBs3rlDr9OvXL4488sjU70X5Xu29997x7rvv5vn8+vXro3///vHWW2/F3LlzY+PGjVG/fv049thj44ILLoi99967UK+3rcGDB0fv3r1Tvzds2DBGjRqVuN6CBQuiffv2kZ6ennrsnXfeiQYNGmxXeSh+zz//fNx2222p3+++++4444wz8l1n6dKl8cILL8SYMWPi22+/jbVr18Yuu+wS++yzTxx11FFx3nnnFehvvXnz5hg0aFAMGzYsvv3221i3bl3Uq1cvjj766Ljgggti//333+73t3Tp0vjiiy9S+5opU6bEihUrIiKiS5cucc899xR4W/PmzYsXXnghPvnkk5gzZ06sX78+dt111/j1r38dbdq0iXPPPTfq1KmTuJ3Ro0fHkCFD4rPPPotly5bFrrvuGr/61a/ixBNPjHPPPTeqVq1a1LcLZc72Huu39frrr8fgwYNjxowZsWrVqqhbt24cdthhcf7558ehhx5aoG2U9LEzP9dcc00MHz489XtBjo0zZ86MF198McaPHx8//PBDbNy4MapVqxb7779/tG/fPs4666yoVq1a4msvW7YsnnvuuRg1alT88MMPEbH1POP444+P7t27R61atbbrvRXmvKwgf/vtOUb8/e9/jyFDhhSq/AU5/gFsS4hGqct60e6i+5frhx9+iPbt2+/w19133313yOuUK1cuGjVqtN3bya+833//ffTs2TNmz56d7fHvvvsuvvvuuxg0aFDcd999cdxxx213OTLNnTs3Jk2aFC1btsx3uWHDhmUL0CibFi5cGPfff3+h1hkzZkz8+c9/jpUrV2Z7fPXq1TFt2rSYNm1aDBgwIG677bbo3LlznttZtmxZ9OzZM6ZMmZLt8blz58bAgQNjyJAhcfPNN8dZZ51VqPJt6+ijj96u9TMNHTo0brnllvjpp5+yPb5y5cqYPHlyTJ48OZ577rl44IEH4phjjsl1G2vWrIm//OUvMXr06GyPr1ixIlasWBGff/55DBw4MB577LFo3LhxsZT75ybrzQYXtD9/xXms/+mnn+KPf/xjvP/++9kenz9/fsyfPz/eeOON6NWrV1x11VX5bqc0jp2ZRo8enS1AK4gnnngiHnroodiyZUu2x5cvXx7jxo2LcePGxbPPPhuPPfZYHHjggXlu5/PPP49evXrF4sWLsz0+c+bMmDlzZgwaNCgeffTROOSQQwpVvpKyo44RWe2oc8TtlTWs3PaGLbDjCdGIiIg99tgjhg0blufznTp1ioiIZs2axd13372jisUvSEZGRurntLS02GeffaJevXoxfvz4Am+jefPm+dbTTHfccUeqFVl+F/X5ueuuu2L9+vX5LvPNN9/EtddeGxERrVu3jj322CPb80nfq0yPP/54vP766/mWd82aNdkuAn7/+9/HySefHFWqVIlPP/00Hn/88VizZk1ce+218cILL+R7Yl1QlStXjg0bNsSrr76aGKK9+uqr2dYpy4488sidttXsHXfcEWvWrIk6derE0qVLE5efO3du9OrVKxUkdejQIU4//fTYc889Y9GiRfHOO+/EkCFD4qefforevXtHw4YN47DDDsuxnS1btsRVV12Vujg64YQT4qyzzoqaNWvG559/Ho899lgsXbo0br755qhXr160a9euWN7vXnvtFb/+9a9jzJgxhVpv4sSJ0bt370hPT49y5cpF586do0OHDlGvXr1YsGBBDBkyJEaPHh0rVqyIK6+8Ml5//fVo2LBhtm1kZGTENddcEx9++GFERBx88MHRo0eP+PWvfx1r166N9957L/r37x/ff/99XHrppfHyyy9H7dq1i+V9Q2kpjmN9phtuuCEVoB155JHRvXv3qFevXsycOTMef/zxmDNnTvTp0yd23333OPvss3PdRmkcOzOtXbs2br/99oiIAu9zX3/99dSNjooVK8b5558fRx99dNSqVSvmzJkTzz//fEycODEWLFgQl1xySQwfPjx22223HNtZsGBBXH755bFs2bKoUKFC9OjRIxUSjh49Op555plYvHhxXH755TF48OCoX7/+dr/fu+66K5o3b57n8/m12i2OY8S1114bF110Ub5lXLVqVVxwwQWRnp4ejRo1KnBLxp+TM844w82IYuTzJDdCNCJi64G6SZMmictVrVq1QMvBtnbddde45ppr4pBDDolmzZpFjRo1Ct09syD1b9WqVfHZZ59FRMSvfvWrxPAnL9teEOcmMziKiDj99NNzPF+Q79WWLVtSgd+uu+4av/vd73Jd7r///W/qIuCvf/1rXHLJJannDj300DjiiCPiggsuiPXr18ddd91VpC6s22rfvn0MHz483nrrrbjxxhujUqVKuS43bdq0+OabbyJia8jy5ptvbvdrU/xGjRoVI0eOjNq1a8ell15aoG6NTz/9dCpAu/jii+P666/P9vzxxx8fBx10UPzzn/+M9PT0eOKJJ+Lxxx/PsZ0hQ4bExIkTIyLivPPOi1tuuSX13CGHHBK//e1v44wzzog1a9bEnXfeGcccc0xUqFC0U5RevXpF8+bNo3nz5lG3bt0idat+4oknUi0r//GPf8T555+frbwnnnhi3HPPPanP5+mnn46bb7452zZGjBiRCtCOOeaY6Nu3b7bv0JFHHhlt27aNSy65JH744Yd4+OGHc2wDfm6K41gfEfHJJ5/EG2+8ERERxx13XDzyyCNRvnz5iNj6HWzfvn2ceeaZMX/+/LjvvvuiY8eOUaNGjRzbKY1jZ6aHHnoo5s+fH61bt4769esXqKth3759Uz8//PDDceyxx6Z+P+SQQ+LUU0+Nq6++Ot5+++1YsmRJDBo0KC6++OIc23nwwQdj2bJlERFx3333xUknnZR6rlWrVnHwwQfHtddeG0uXLo2HHnqoUN3c89KgQYMiXyMUxzFijz32yHEzc1vPP/98at+e23kbQEGYWADYIWrVqhVXXHFFHHPMMbme6BaXN998MzZu3BgREaeddlqJ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VIhTTjklBgwYkLjNL7/8Mi677LJUC7xMCxcujBEjRsSIESPihBNOiPvuuy8qV66cer5Vq1ax1157xfz582PYsGGJJyyZ48wdeOCBhQ7WV65cGX/84x9j7Nix2R7fuHFjajD9559/Ph599NFo0aJFjvWHDx8ef/3rX3Nc4CxevDgWL14cX331VQwePDiGDRtW4l3kP/vss3jhhReiYsWKceuttxZq3X333Te+/vrrWLFiRaxZsybPk9jMad9zO8EraP3dfffdo1GjRjF79uxiq79Fte+++8a0adPync5+zZo1sXz58tTy28oMlJNm78zaYm78+PFFDtGWL18e1157bSp8yOqHH36IH374Id5+++2IiBIZ3+WZZ56Je++9N8cEIz/++GP8+OOPqe/MpEmTYtdddy3y6xTnOceCBQuiR48e2QacX7duXXzwwQfx0Ucfxf333x8nnXRSfPXVV9GzZ8/UjZOIrbM9vvvuuzFmzJh48skn46ijjsr1NdLT0+Pee++NZ599Ntt4YRFbL+pnz54dL7/8ctx8882JIcby5cvjqquuiunTp+e5zF133RXPPvtsjsczB+P//PPPY/DgwfHpp5/m+1qlacqUKal9Z377jEqVKkWLFi1izJgxqXUqVqyYer60jp1PP/10TJ8+PRo1apRqPVUQtWvXjpo1a8aKFSvy3ffMmTMn9XPSPjev84ptn5s0aVKphWg76hiR9bytOG+yvvDCC/HPf/4zNm/enOvzc+fOjeOPPz7X5zK/l8OHD4/TTjst7r777iIPZVAUEydOjF69eqWOZRERGzZsiDFjxsSYMWPi+uuvz7W7cMTWa68//elPqfPYTJkTVwwbNizVwjAvEyZMiMsuuyw1e2+mpUuXxtKlS2PmzJnxxhtvRK1atYo08UdxlDEvxXlOWpzHiZ9++ikuuuiiHOcCme/53XffjWeffdas4NtJiMZ22bJlS1x++eWp4OKII46I888/Pxo0aBCLFi2KV155JUaNGhWzZs2KHj16xNChQ1Mnz5mDxL/zzjvx0EMPRcTWsSvq1auX7TWyBiFbtmyJGjVqRIcOHaJVq1bxq1/9KqpWrRqLFi2KadOmxXPPPRfLly+PO+64I37961+X+J3W9957L6ZOnRr77rtvXHLJJdG0adNYs2ZNDB8+PAYNGhSrV6+Oyy67LF5//fXYc889s627PZ9dprVr10aPHj1i7ty5EbF1fKIzzzwz6tWrlwryxo0bFxMnTozLL788BgwYkKNLxC9JSZ0gbWvt2rUxatSoiNgafhV1lqQRI0akJi84/fTTU60ztpV5hzYi8u0uWaFChdhnn31ixowZ2dbZHu3atUud1A8fPjzOPffcbM9ndo1o27ZtgQZFX7hwYfTo0SNWrlwZaWlp0aVLlzjllFOiZs2a8c0338TTTz8dX331Vbz99tvx97//PR588MHUumlpaXHKKafEk08+GRMmTIiFCxfm2dLzq6++ipkzZ0bE/yZGKaiNGzfGH/7wh5g2bVqUL18+Tj311GjXrl00aNAgNm3aFBMmTIinn346li5dGj179owhQ4Zku1hfsmRJ3HDDDbFp06aoU6dOnH/++dGiRYuoVatW/PTTTzFnzpwYN25cjpO6krBp06ZsLSIKOwPkOeecE2+//XZkZGRE3759U92nshowYEAqMNq2fkQUvP5mPj979uxYsGBBrFu3rtRO8s4555y46aabYsWKFfHCCy/k+r4eeeSRbMtvq2rVqrFq1aocFwfbymwpExFF/t6uX78+unfvnqrzBx98cJx99tmx//77R6VKleLHH3+M8ePHl9h4hV999VUqQGvQoEF069YtDjjggKhZs2asXbs2vvvuuxg7dmy8++672dYbNmxYLFq0KHWRds011+RodZE1hCyO42ZWf/rTn2LhwoVx2WWXRdu2baNKlSoxceLE6NOnT6xZsyZuvPHGaNasWVx++eWxYcOGuPbaa+Pwww+PihUrxgcffBB9+/aNjRs3Ru/evWPEiBG5tn6744474vnnn4+IraFFly5domHDhlGlSpWYMWNGPPvss/H111/HzTffHHXr1s13/L4bb7wxZs6cGZ07d46TTz456tatGwsWLEgFR6NHj04FaE2bNo1zzz03GjduHNWrV49Vq1bFN998Ex9//HGxdVksKYXZZ+y7774xZsyY2Lx5c3z//ffZLk5L49g5d+7c1L7hlltuKXRL43POOSf69u0b06ZNiw8++CDX1kmPPvpoqtxdu3bN8XzmGKXVq1eP3XffPc/XqlevXqolX3GcMzz44IOxcOHCWLx4ceyyyy6x9957xxFHHBHnnntuvq3xdsQxIiMjI1577bWIiNhll13ixBNPLNB6SaZMmRKvvfZa1K9fPy6++OJo1qxZ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t1WlSpXUvjHrDYaIrS11M48VSfuSxo0bR61atSIie2uebSXtzzL/DuPHj882btbPTdbvQVKLsPz2GTvy2Ll8+fK45557ImLrjYftCau/+OKLeO2113LUqUwfffRRvPzyy9m6gmdVlPdd1POF5s2bx+jRo+Pmm2+OU045JQ455JA4+OCD4/jjj48777wznn/++VSrqieeeCLX8/KSPkZs2LAhRowYERFbb8YXZy+VPffcM89xj/OyadOm+PHHH2PWrFmpa5dFixalwu2SvnbJtNtuu+U5A2zE/1q55lYPM8fbOuigg3Lt6pkpv/PbrF2Ns95MKi7FUcaC2J5z0h19nMjacjm/cRdJpg0fRZY5DXdEJM5klPVO79dffx2HHnpokV932bJl8cwzz8SIESPi+++/zzEAY1Yl3RJt2zEJtpX1c5k5c2bqhL04Prus28jtTvq223jhhRdS6+V3R+znaMOGDfHWW29FxNYxPorShaKgXnvttdTA2dtz4M0MoCpVqpTnBVqmzIAqt1m4tpW11UVRWx7l5rTTTos+ffrEsGHD4qqrrooFCxakDvwF/RwKW2c/+OCDWL9+fcydOzfb7IannnpqPPDAA6kuvNu2BMq8G1ipUqVCj3uStXt5mzZtCrxe1pam7du3j9122y1WrVoVV111VRxxxBHRvn37aNWqVRx44IE7JOQYPHhwjB07NqpVq5YaR6UoZs2aFX/4wx9i4cKFUbNmzfj73/8e7du3jzp16sTKlStjzJgx8e9//ztGjx4d48ePj8ceeyzHwNBZ6+GmTZvyrZdZ629JttJLMmHChLj88stj9erVsffee8ef/vSnOOaYY6JGjRqxdOnSeOedd+L//u//4o033ojx48fHU089leMkvXr16vHoo49Gz549Y+nSpfHaa6+lupxndd1118XTTz8dy5YtK9KA+19++WXq56xdiHakU045JZ544onYuHFjnHvuudG2bdto165dHHbYYbH//vsXuct7ViVxzpHfrKmZLUtq1aqV7yy8mctt25Lnyy+/TB0r/vznP8ef//znfMucKWvrjG1tO5vptjp37hxDhw6NFStWxKmnnhodOnSINm3apMaQ/bnIuo9I6r2Q3z5jRx4777333li2bFkccsghuY6hWFBvvfVW/PWvf42NGzdG06ZN449//GO0atUqdt111/jxxx/jzTffjEcffTQGDhyYGp9z25YtlStXjvXr1xfqfRf1fCGptdshhxwSN910U1x//fWRkZER/fv3j3/+8585ypupJI4Ro0aNSo1NedpppxXrbPVJ38lMmzZtipdeeileffXV+PLLL/P92+yIXjQRW2eOzu+zyNzvbbtv27BhQ3z//fcRkT2UyU1+DQ0OO+ywaNiwYcydOzfuuuuuGDZsWPzud7+LVq1aRfPmzbdrOJLiKmNBbM85aUkcJ/Lr4py1FXLSeK3kT0s0iixzXKOIws0+tj1jcUydOjVOOumkePzxx2P27Nn5BmgRxdcSJy9Ffd/F8dll3UbSgO5Zt5F1vV+Kd955J9Uqr1OnTiUaUhQm/MrLF198kZpNNTNwyU/mhXVBui5ktkjJul5xyGwanjlFfGZLur333rvAF+5Z629h6v22dXavvfZK3fnNHGci06ZNm1KB6rHHHpv42W5r6dKlhVo+U9Y74rVq1YrHHnss9thjj8jIyIhPP/007r777jjzzDPjiCOOiKuuuipGjx5dpNcpiGXLlsW9994bEVu7pBamO+u2rr/++tSU988//3ycd955Ub9+/ahYsWLUrVs3OnfunBoDa82aNXHdddfluOjNWg+Tugxlrb+lNanAxo0b489//nOsXr06dt999xg4cGCcfvrpUbdu3ahYsWLUr18/zj///Ojfv39Urlw5Fi1aFH/7299y3VazZs1SLc62bZnSvHnzePzxx6Nnz56pz6Ww9TUi+wVXad0gady4cdx///1Ro0aN2Lx5c4wePTpuvfXW6NSpU7Ru3Tr++te/5trdsTBK4pwjv+EhMi8u81sm63LbzkpaHPuSbeUX5kVsHePn5ptvjipVqsSGDRvizTffjBtuuCFOOOGE+O1vfxs333zzDmvlsj2y7jOSjnv57TN21LHzk08+iSFDhkT58uXjtttuK3JIs2TJkujdu3ds3Lgx9t9//3jxxRfj+OOPj5o1a0bFihWjYcOGcdlll0Xfvn0jLS0tZs2alSOQylr+wrzv4jxf2NYpp5ySajmaOYxFViV9jCjJSacKss9esWJFnH322XH77bfH559/nhhulvS1S6ai7tuyjquZdP2R3/MVK1aMvn37psb1mzJlSjzwwANx3nnnxeGHHx4XX3xxDBs2LEdvhIIorjIWxPack5bEcSK/cDnrDa1t/64UjpZoFIviuMucZOPGjXHNNdfEihUromLFitGtW7fo0KFDNGrUKGrUqJG6Y5F1KumkkG17Fcf7Livb+DnbUV05p0yZkuqydtxxxyVe0OSlsCd09evXj88//zzWrVsXq1atyvekbcGCBRGx9aRgeycVyKphw4bRsmXLmDRpUrz66qup8RhOO+20Uql/nTp1ivHjx6dmb8psaTJmzJjURXNhu3JGRLaTtSFDhhR40NVtB3Bu1apVjBw5MkaMGBHvv/9+TJgwIX788cdYs2ZNjBw5MkaOHBlt2rSJhx9+OPFEtrAGDRoUK1asiN122y1q1qyZGosjq88//zzbz5l3/o866qhUQPHVV1+lWuZ16tQpz5k969WrF926dYuHHnooFi1aFB988EFqHxyR/bNZuHBhviesmfU3LS2tSLONFYcPPvgg1UWzW7duec5ut//++8dpp50WgwYNimnTpsVXX30VBxxwQI7l6tWrFzfddFPcdNNNsXjx4lizZk3UrVs31cXpxx9/TF005dflpKw78cQT4+ijj44333wzxowZExMmTIhly5bF8uXLU63wunTpEnfdddd2twT5ORzzsl6g3H777QVugZ/fcaUgn9v5558fHTt2jGHDhsXHH38ckyZNitWrV8fChQtj4MCB8dJLL8Vll11W5BnpdoRt9xn5tfrP3GdERI5Z0HfUsfM///lPRGwNzb/77rv47rvvciyTtevU6NGjU/vBrF2K33jjjVTwddlll+UZErVu3Tpat24dH3/8cYwaNSpWrlyZrd7ssccesWTJkgJNFpD5vktyf1uhQoVo1KhRTJ06Ndfu7yV5jFiyZEl89NFHEbG1RVLWiSeKQ0Fu2t55552pbuXHH398nHnmmdG0adOoU6dOVK5cObU/O/bYY2PBggUlfu1Sluy3334xbNiwGD16dLz77rsxYcKE+P777+Onn36KMWPGxJgxY+Lpp5+OJ598MvHmSWkq6jlpSRwn2DGEaBRZ1i9wfs1Kt32+qAPajh07NtUv/5Zbbslz8MUdOetUUd93cXx2WbexdOnSfMfmyrqNX9qOd+nSpTFmzJiI2HqCVBwzFuWlOMK6TZs2pUKNOnXqRNu2bRPXyRpefPvtt9GiRYtcl9u8eXPqO5JX4LE9OnfuHJMmTYpXXnklddFf0Fk5I7LX36VLl+a44Mkqqc527Ngx7rjjjti0aVMMGzYsdeKReRewevXq+Y71kZfM8SYitl5Mbc+FReXKleO0006L0047LSK2Bvzvv/9+PPfcczF79uwYM2ZMPPjgg9vV3TI3mS3BVq1ale/MhJlefPHF1AxW/fr1S52oZh3b46CDDsp3G1m7S2S2ssy0bf098MAD89xO5rp77rlnqbVEy1r+grzvQYMGpdbLLUTLavfdd88RymXtQpw0REBustbZRYsWRcOGDQu1/rZ3pvMKarK2AMlL9erV4+yzz46zzz47IrbWoXfeeSeee+65WLRoUQwZMiQOPPDAuPDCCwtVxogdf86xvbK+bpUqVUr02LStOnXqRI8ePaJHjx6Rnp4e06dPj5EjR8aAAQNi1apV0bdv32jevHm2sLss2XafkZ/MwKpChQo5uqzuqGNn5j73888/L1B3rKytx7KGaIXd53788ceRnp4es2fPztaFeb/99otp06bF6tWrY/HixXneCFi0aFGqS1dJnDNklV/wXZLHiKwtmYp7QoGCWLNmTWrW3E6dOsV9992X57I/l54iWcPoZcuW5bts0vMRW4PI448/PrU/WrRoUXz44YcxYMCAmDZtWkybNi1uvvnmeOSRR0qtjEmKek5amscJto/unBRZ1jvmSQMJZ31+2zvtBb2jnHXQ6syB8nOT9YKkpE2ZMqXAz2d938Xx2WX9OWurksJs45dg2LBhsXnz5ogoXKBTWFnDr9q1axd5oPb3338/FfSeeuqpBWrplLW7ZNYJKrY1derU1F3sli1bFql8+TnppJOiUqVKqQDtN7/5TaEmVihKnd1ll11yDQVq1KiR+hsMHz48Nm/eHOvWrYt33nknIra2iilKS7ysJ++TJk0q9Pr5adiwYXTr1i1eeeWVVDiXeXJdFmW9w57UnSLzOxgROep0Qevv4sWLY/bs2RFRMvW3oIr6vovajTyzq0dEFKmLeNYAsyhdJrMO0J/fRVzm36YwGjduHD179oyXXnopdcG7bZ0v6DlAcZ1z7CgHHnhg6r0V976kMMqVKxcHH3xwXHPNNfHMM8+kHi/L+57mzZunBpjPb5+xcePG1IQnWdfJVFaOnQWVdd+5PfuerO87t+6TuT1Xku978+bNqf1Hbl3OS/IYkdnyv2LFivlOJFJSZs+eneq+md/+fdasWfl2vy1LrW8rV66cmq04abK4olyT1atXL84888wYOHBg6vj23nvvFWqyoZIu47aKek5aVo4TFJ4QjSLbY489UnePhg8fnuc4Blu2bIkhQ4ZExNadzLYDPBZ0ANmsJwx53RFPT09PtQrYEWbOnJltUOdtZc42U758+WyDbRfHZ9esWbPUnZahQ4fm2bc9612w/fbb7xc3qUBm67CKFSsWqfteQX3wwQepu1UFDb9yU5Rp1o844ohU16+hQ4fm2dR/8ODBqZ9LooXBbrvtFscff3xUqlQpKlWqVOjWeEcccUTqZD+/mZjmz5+fmuI96zrbyvx7L1u2LD766KMYNWpUat9Q1Lpw9NFHp7pX9uvXr0S6VVSrVi3V4qgkBhC++uqrUzPq5vXv7rvvTi1/9913px4/8sgjU483aNAg9XNSOJP1YizrehER++67b2p/99Zbb+W5/87c10WUTP0tqOJ63wXxzTffpPbPRx99dJFm+z3yyCNTAdVzzz1X6PFjspY7v4uJ3LoFF9See+6ZGsR/2zpf0HOA4jrn2FFq166davn0+uuvF0trh+118MEHp1r07chW+4VVrVq11AyKn3zySZ7dEkeOHJlqSZXbPmNHHTufe+65xH1u1uP9O++8k3o8q6Lse9LS0mLvvffO9lz79u1TLUrzO9Zmvu9y5cpF+/btC/BOi+bNN99MzSSaOat1ViV1jJgxY0ZqDMDf/va32z32VVFk3R/n15o3szV4Xgoz2caOkPn9/PLLL7NN+rKtrOe8hVWxYsVUfdm8eXO2cc7KShmzKso5aVk8TlAwQjS2y/nnnx8RW3cYuQ1uGhHx8MMPp1qRnXXWWTmS+KzNzPOazjsi+yxaWQ+kWd1///2JdxyK20033ZTr3aNhw4bF+++/HxFbD/bbhlfb+9lVqlQpunbtGhFbw7xHH300x/oZGRlxxx13pC5aMl+zMObNmxdNmzaNpk2bxgUXXFDo9UvS119/nQox27RpU6gTpL///e+p95U5vld+iqMr54oVK+K9996LiIgmTZrk22Uhq0qVKqU++1mzZsV///vfHMtMnjw5dbJ8xBFHJM5eV1QPPvhgTJkyJaZMmRLnnXdeodbdY489Uie+H3zwQa7f440bN8YNN9yQunObX51t3759qhXNsGHDUs3m99hjjxwzRBbUbrvtlnrNyZMnx1133ZXv4KtLlizJEdx/+OGHsWjRojzXWb16daqlTG7By+DBg1N1s0+fPkV5G8XioIMOSk1K8Pbbb6emi9/WtGnTUhcAu+yySxx11FE5lrnooosiYut34F//+leO5+fMmROPP/54RGydMex3v/tdrq9V2O9tUbRu3ToVpL7wwgs5LnQzvf/++zFy5MiI2Frncvs+5zb+T6YFCxbElVdeGZs3b45KlSrFP/7xjyKVd7fddkt1n5w2bVrcddddeYYFmzZtyjGQ8aGHHpq6KfDMM8/kuu5//vOffFt/jRo1Kt8LnAULFqS6YW1b5zMHTY/YWg/yUxznHDvSFVdcERFbb2b98Y9/zPcz2rhxYwwYMGC7BhV/8803822tMWXKlFRrw22Dl4gd8/0qqMx9xubNm+O2227LEQ4vW7Ys1TVut912y3WIj+I8dl5wwQWpzybr+GbFqV27dqlWKX379s1z/zFw4MBU4N2iRYtsXbojtp5XZ160jxkzJltr10zDhw9PDYVx+umn59nls3379qn3va2VK1cm1pMvvvgi7rjjjojYGvjlNXNpcR0jsirJCQUKap999kn9TYcMGZLr/vXdd9+NAQMG5LudrH+fpP3kjvD73/8+9b5uuummXPc7I0aMSB0jc5M5/lleNm7cmAqLq1atWugQtDjKWBhFPSfd0ccJiocx0dgu55xzTgwbNiwmT54cgwcPjvnz58d5550XDRo0iMWLF8crr7wSb7/9dkRsPZBceeWVObZx4IEHRuXKlWPDhg3x73//OypUqBB77bVX6i7aHnvsEVWqVIk2bdpEnTp1YunSpfHQQw/FvHnz4ne/+13UqlUr5syZEy+99FJ88sknqcHPd4RmzZrF1KlT48wzz4xLL700mjRpEqtXr44RI0bEwIEDI2LrrEPXX399jnWL47Pr1atXjBw5MubOnRt9+vSJmTNnxhlnnBG77757zJs3L/r3759qGn/ooYemLrRKywcffJBtrJqsY51Mnz49293gqlWrRseOHfPdXtYQpiTHuli5cmVqNsUmTZoUuWXDG2+8kQqHClveiy++ON58882YPXt2/Otf/4o5c+bEySefHFWqVIlPP/00+vbtG5s3b44qVaoU+xhbxemGG26IsWPHxsqVK+OGG26IiRMnxsknnxy77bZbfPvtt/HUU0/F9OnTI2Jr99F27drlua3KlSvHCSecEIMHD4533nkn9dmecsop2zVw+Z/+9KcYP358fP7559GvX78YN25c/P73v48DDjggqlatGitXroxvvvkmPv744/jggw+iSZMm2S7g3njjjbjiiivi6KOPjmOOOSaaNGkSNWrUiLVr18bMmTNjwIABqYujc845p8jlLGnlypWLP//5z/G3v/0ttmzZEpdeemmcffbZcdxxx0WdOnVixYoV8dFHH0X//v1Td1t79uyZ6+DdXbp0iVdeeSUmTZoUAwYMiCVLlsRZZ50VNWrUiC+++CIeffTRWLNmTZQrVy5uvPHGIrf0jNh6Yp71IiNry6fvv/8+234mIuKMM87I9vtuu+0Wl156afzf//1frF27Ns4555y44IIL4uijj44aNWrEkiVL4p133olBgwalAtbrrrsu1zp3yy23xLJly+KEE05ItR5etmxZfPLJJ/Hiiy+m3vMdd9yxXWMS/elPf4qPPvooZs6cGf3794/JkyfHOeecE02aNImKFSvGjz/+GBMmTIg33ngjrrnmmmzvuU6dOtGxY8d4/fXXY8yYMXHFFVfEeeedF3Xr1o358+fHa6+9FiNGjIhDDz00Jk+enOvrP/vss/GXv/wl2rVrF0cddVQ0btw4qlevHitXroypU6dG//79Uxcw215EV6hQIZo3b54ab/Gggw6KAw88MFUHatSokRo3pjiOmztSu3btonv37tGvX78YP358nHzyyXHOOefEYYcdFjVr1ox169bFnDlzYsKECTFy5MhYuXJldO7cOVurk8K477774pZbbokOHTpEq1atolGjRlG1atVYsWJFTJw4Mfr37x8RW1vH5zWubHEojmN969at45RTTok33ngj3n333fjDH/4QF154YdSrVy9mzpwZffv2jfnz50dExF/+8pc8x3v9OR07GzduHGeccUa88sorsXDhwujcuXNceOGF0apVq9h1111jwYIF8cYbb6QuzsuXL5/nBBHXXnttfPjhh7Fs2bK47rrrYurUqakxmd577714+umnI2JrS5hrrrmmSOVdvXp1dO/ePZo2bRrHH398HHzwwbH77rtH+fLlY8GCBTF69Oh49dVXU8fmiy66KJo1a5brtor7GLFly5YYNmxYRGwN6osyRmpxqFWrVrRr1y7ee++9+PDDD+Oiiy6Kc889N/baa69YunRpvP322zFkyJBo2LBhrFq1Ks+WSHvttVfUr18/fvzxx3jqqaeifv36se+++6Za6tepUydb1/yS1qxZs/j9738fAwcOjMmTJ0fXrl3j4osvjv333z81gdILL7wQhxxySOoGzLZdUj/55JN49NFHo1WrVtGuXbto2rRp1K5dO3766aeYPXt2vPjii6mGEV27di30eUFxlLEwinpOuqOPExQPIRrbpXz58tG3b9+44oorYtKkSTF27NgYO3ZsjuUaN24cTz75ZK5TaFerVi0uuOCC+M9//hPTpk1L3Y3K1K9fv1R3lXvvvTd69eoVGzZsiIEDB6aCqkxHHHFE3HzzzXHqqacW7xvNw7HHHhvHHntsPPzww9G7d+8cz1erVi0ee+yxXFubFNdn98wzz8Sll14a3377bYwYMSJGjBiRY7mWLVvGY489VuQxe4rLk08+med4F++8805q7ICIrXfJ8wvR0tPTUydINWrUiOOOO65QZcl6Rypp4Ok333wz1Xx+e8Zdy2zNVr58+UJ3N6xWrVo88cQT0bNnz5g9e3au9b9atWpx3333FbiFW2moX79+PPPMM3HZZZfFokWLYtCgQbl2wT7hhBPi3nvvTdxep06dYvDgwdlag2YO5F9UlSpViqeeeip69+4db7/9dnz11Vdx++2357l8bieumzZtivfffz/VGjU355xzTnTv3j3H44WpmyWtc+fOsXTp0njwwQdj06ZN0b9//9SFeFZpaWlx4YUXpu6obqt8+fLxyCOPRM+ePWPKlCm57qsqVaoUN998c77BaUE+m5dffjnP1sqTJk3KcZNl2xAtIuLKK6+MlStXRr9+/WLdunXx+OOPp1pBZFWxYsW49tpr89wvZGRkxOeff57nGIA1a9aMW265pUhjoWW1yy67xLPPPht//OMfY/z48TFt2rS46aabCrx+7969Y+rUqTF79uwYPXp06qZBplNOOSXOOuus6NGjR57bWL9+fbz11lu5tnqJ2BrKXn311bl2w7rsssvi8ssvjxUrVsR1112X7bmrrroqrr766ogonuPmjnbDDTdEjRo14rHHHovFixfn27q0atWq232cXrVqVQwZMiTP70ClSpXitttuy3USi+La9xTXsf6uu+6KNWvWxPvvvx+ffvppjlZP5cqViyuvvDLfG4TFdezM/GwqVqxYomHFrbfeGuvXr48333wzli1bFg8++GCuy1WtWjVuv/32bF3ws9pzzz2jb9++0atXr1i8eHE8+eST8eSTT2ZbZvfdd49HHnkk3wl0Mt93fvUht66pWZUvXz6uvPLK6NWrV77LFMcxItNHH30UixcvjoitY5GVZovUW2+9Nc4777zUUBWZw1Vk2muvvVLvPT+XXXZZ3HbbbTFv3rwcNwjuvvvuXI9lJekf//hHLFq0KEaPHh1ff/11/P3vf8/2fIMGDeL+++9PtRrM7W+Qnp4e48aNy3csvA4dOuQ4LuzIMhZGUc9Jd/Rxgu0nRGO71axZMwYMGBCvvfZavP766zF9+vRYuXJl7LrrrtGkSZPo2LFjYpeKv/zlL9GoUaMYOnRofPPNN7F69epcx3Vp27ZtvPLKK/HEE0/E2LFjY/ny5VG9evXYb7/9olOnTtG1a9fUnckd5eqrr44WLVpE//79Y+rUqbFy5cqoV69etGvXLi677LJ8T06K47Nr0KBBvPrqqzFo0KB46623YubMmbF27dqoUaNGHHjggdGpU6fo1KnTdrXMKYs++eSTVJe5zAHvCyPzorZ169a5dlPIanvCr0yzZ89OvebRRx+dZ9eJ/PzqV7+KIUOGxIABA+Ktt96KOXPmxKZNm6J+/fqpO1m5ddEpaw466KB46623YsCAATFq1Kj47rvvYv369VGrVq1o0aJFdOnSpcDjsxx11FGx++67p06W99tvv2IJEatVqxZ9+vSJCRMmxNChQ2PChAmxaNGi2LBhQ1SrVi0aNmwYhxxySLRr1y7atGmTbd3evXvH0UcfHWPHjo0ZM2bE4sWLY9myZVG+fPmoX79+HHroodG1a9do1apVrq+dOVB2jRo1SmU2sW1dfPHFcdxxx8VLL70U48aNizlz5sS6deuiSpUqsffee0fLli3jrLPOyrOFQabatWvHiy++GC+99FK8/vrrMWvWrFi/fn3Uq1cvWrduHd27d08cBL4w39vtkZaWFjfccEOcdtppMWjQoJg0aVL88MMP8dNPP0XVqlVjn332iSOOOCLOPvvsfMcx69mzZ+y7774xYcKE+PHHH2PFihVRvXr12GeffaJDhw7RtWvXYhunp3bt2tG/f/8YOXJkDBs2LD7//PNYtmxZpKWlRb169eLggw+O448/Pk488cQc69atWzdeeumlePLJJ2PkyJExf/78qFq1auy///7x+9//Pk477bR8u23df//98d5778Wnn34as2bNiiVLlsTy5cujUqVKsffee0erVq3inHPOyXP20mOPPTaeeeaZ6Nev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6NevXzRq1CjuvPPOOOaYY6J3794xadKkkiwfAAAAAJS6Qo2Jdvjhh8c999wTH330Udx4443x/fffx3nnnRcnnXRS/Pe//40lS5aUVDkBAAAAoNQUemKBiIiqVatG165d4/nnn48RI0bECSecEE888UQce+yxxVw8AAAAACh9RQrRMq1bty4mTJgQ48aNi5UrV0bDhg2Lq1wAAAAAUGYUeGKBrCZMmBCvvPJKjBgxIjIyMqJjx47xl7/8JQ477LDiLh8AAAAAlLoCh2iLFi2KoUOHxuDBg2P27NnRokWL6N27d5x88smx6667lmQZAQAAAKBUFThEO/bYY6NmzZpx+umnR9euXaNx48YlWS4AAAAAKDMKHKI99NBD0b59+6hQoUg9QAEAAADgZ6vAEwuccMIJxR6gPf/889GpU6do2bJltGzZMs4+++x4//33811n+PDh0bFjx2jevHl06tQpcXkAAAAA2F7bNTvn9qpfv3785S9/icGDB8crr7wSRx11VPTq1Su+/vrrXJefNGlSXHfdddG1a9cYOnRodOjQIXr16hUzZ87cwSUHAAAAYGdSqiFa+/bto127dtGoUaPYd99949prr42qVavGZ599luvy/fr1i7Zt28Yll1wSjRs3jmuuuSYOOuig6N+//44tOAAAAAA7lTIzwNmWLVvirbfeinXr1sWhhx6a6zKfffZZ9OjRI9tjbdq0iVGjRu2AEgIAAEDpSUsr7RJkl1meslYudk7bUx8Luk6ph2gzZsyIc845JzZs2BBVq1aNRx55JPbbb79cl12yZEnUrVs322N16tSJJUuW7IiiAgAAQKmoVWvX0i5CnurUqV7aRYCUkqyPhQ7RevXqFWm5RHRpaWlRqVKl+NWvfhWnnnpq/PrXvy7Q9vbdd98YOnRorF69OkaMGBF/+9vfon///nkGaQAAALCzWb58bWzZkl7axcgmLW1rYLF06erIyCjt0rCz2576mLlukkKPiVa9evUYO3ZsfPnll5GWlhZpaWnx5ZdfxtixY2PLli3x5ptvxumnnx4TJ04s0PYyg7dmzZrFddddFwcccED069cv12Xr1q2bo9XZ0qVLc7ROAwAAgF+ajIyy96+slsu/nfPf9tTHgih0iFa3bt049dRTY9SoUdGnT5/o06dPjBo1Kk477bTYZ599Yvjw4dGlS5e47777CrvpiIhI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BEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEggRAMAAACABEI0AAAAAEhQobQLUFrKlSsX5USIlBHly6uMlB3b1sf09IxIT88opdIAAACUDTttiFar1q6lXQRIUR8pS7atj+npGbF8+VpBGgAAsFPbaUO0N79fHQvXby7tYgCUaXWqVIjTGlWPcuXShGgAAMBObacN0ZZt2BwL128p7WIAAAAA8DNgICYAAAAASCBEAwAAAIAEQjQAAAAASCBEAwAAAIAEQjQAAAAASCBEAwAAAIAEQjQAAAAASCBEAwAAAIAEQjQAAAAASCBEAwAAAIAEQjQAAAAASCBEAwAAAIAEQjQAAAAASCBEAwAAAIAEQjQAAAAASCBEAwAAAIAEQjQAAAAASCBEAwAAAIAEQjQAAAAASCBEAwAAAIAEQjQAAAAASCBEAwAAAIAEQjQAAAAASCBEAwAAAIAEQjQAAAAASCBEAwAAAIAEQjQAAAAASCBEAwAAAIAEQjQAAAAASCBEAwAAAIAEQjQAAAAASCBEAwAAAIAEpRqiPf7443HmmWfGoYceGq1bt44rr7wyvv3228T1hg8fHh07dozmzZtHp06d4v33398BpQUAAABgZ1WqIdq4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\n" + }, + "metadata": {} + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Handle Missing Data" + ], + "metadata": { + "id": "6ezIjBwHikrz" + } + }, + { + "cell_type": "code", + "source": [ + "# Identify and handle rows with missing ratings (NaN values)\n", + "df_nan = pd.DataFrame(pd.isnull(df.Rating))\n", + "df_nan = df_nan[df_nan['Rating'] == True].reset_index()\n", + "\n", + "# Generate movie IDs using numpy array to fill NaN spaces\n", + "movie_np = []\n", + "movie_id = 1\n", + "\n", + "# Fill the gaps between movie IDs in the dataset\n", + "for i, j in zip(df_nan['index'][1:], df_nan['index'][:-1]):\n", + " temp = np.full((1, i-j-1), movie_id)\n", + " movie_np = np.append(movie_np, temp)\n", + " movie_id += 1\n", + "\n", + "# Fill for the last record\n", + "last_record = np.full((1, len(df) - df_nan.iloc[-1, 0] - 1), movie_id)\n", + "movie_np = np.append(movie_np, last_record)\n", + "\n", + "# Remove NaN rows and assign movie IDs\n", + "df = df[pd.notnull(df['Rating'])]\n", + "df['Movie_Id'] = movie_np.astype(int)\n", + "df['Cust_Id'] = df['Cust_Id'].astype(int)\n", + "\n", + "# Display dataset with movie IDs added\n", + "print('-Dataset examples-')\n", + "print(df.iloc[::5000000, :])\n" + ], + "metadata": { + "id": "DLRVDpxgilD8" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Advanced EDA - Movie and Customer Statistics" + ], + "metadata": { + "id": "oF4ASo8BilcP" + } + }, + { + "cell_type": "code", + "source": [ + "# Generate summary statistics for each movie and customer\n", + "df_movie_summary = df.groupby('Movie_Id')['Rating'].agg(['count', 'mean'])\n", + "df_movie_summary.index = df_movie_summary.index.map(int)\n", + "movie_benchmark = round(df_movie_summary['count'].quantile(0.7), 0)\n", + "drop_movie_list = df_movie_summary[df_movie_summary['count'] < movie_benchmark].index\n", + "\n", + "print('Movie minimum times of review: {}'.format(movie_benchmark))\n", + "\n", + "df_cust_summary = df.groupby('Cust_Id')['Rating'].agg(['count', 'mean'])\n", + "df_cust_summary.index = df_cust_summary.index.map(int)\n", + "cust_benchmark = round(df_cust_summary['count'].quantile(0.7), 0)\n", + "drop_cust_list = df_cust_summary[df_cust_summary['count'] < cust_benchmark].index\n", + "\n", + "print('Customer minimum times of review: {}'.format(cust_benchmark))\n" + ], + "metadata": { + "id": "ziNr8lE4ilpP" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Visualizing Movie Ratings Count\n", + "**Deductions:**\n", + "\n", + "1. The histogram shows a right-skewed distribution where most movies receive fewer reviews, while a small number of popular movies attract many reviews. This highlights the significant disparity in movie popularity, a common pattern in recommendation systems.\n" + ], + "metadata": { + "id": "cz5paPBOil4b" + } + }, + { + "cell_type": "code", + "source": [ + "# Visualizing the distribution of movie rating counts\n", + "plt.figure(figsize=(15, 8))\n", + "sns.histplot(df_movie_summary['count'], bins=50, kde=False, color='blue')\n", + "plt.title('Distribution of Movie Rating Counts', fontsize=20)\n", + "plt.xlabel('Number of Ratings')\n", + "plt.ylabel('Frequency')\n", + "plt.show()\n" + ], + "metadata": { + "id": "L_OMPfxkimiC" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Visualizing Customer Rating Counts\n", + "**Deductions:**\n", + "\n", + "\n", + "1. Most users rate a limited number of movies, with only a few users rating a large number of movies. This implies that the majority of customers are casual raters, while a small fraction of power users rate more frequently." + ], + "metadata": { + "id": "aJsXWmiqinEI" + } + }, + { + "cell_type": "code", + "source": [ + "# Visualizing the distribution of customer rating counts\n", + "plt.figure(figsize=(15, 8))\n", + "sns.histplot(df_cust_summary['count'], bins=50, kde=False, color='green')\n", + "plt.title('Distribution of Customer Rating Counts', fontsize=20)\n", + "plt.xlabel('Number of Ratings')\n", + "plt.ylabel('Frequency')\n", + "plt.show()\n" + ], + "metadata": { + "id": "m_iNgMNVk0Mo" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Trimming Dataset Based on Benchmarks" + ], + "metadata": { + "id": "iZwX7h7ClCbv" + } + }, + { + "cell_type": "code", + "source": [ + "# Trim the dataset by removing less frequently reviewed movies and customers\n", + "print('Original Shape: {}'.format(df.shape))\n", + "df = df[~df['Movie_Id'].isin(drop_movie_list)]\n", + "df = df[~df['Cust_Id'].isin(drop_cust_list)]\n", + "print('After Trim Shape: {}'.format(df.shape))\n" + ], + "metadata": { + "id": "qF-jcVXPlCp0" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Create Pivot Table" + ], + "metadata": { + "id": "ZBMxZidWlC4b" + } + }, + { + "cell_type": "code", + "source": [ + "# Create a pivot table for movies and customer ratings\n", + "df_p = pd.pivot_table(df, values='Rating', index='Cust_Id', columns='Movie_Id')\n", + "\n", + "# Display pivot table shape\n", + "print(df_p.shape)\n" + ], + "metadata": { + "id": "hPSuWXkVlDGK" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Load Movie Titles" + ], + "metadata": { + "id": "nTmRZY-BleZ-" + } + }, + { + "cell_type": "code", + "source": [ + "# Load movie titles dataset and set Movie_Id as index\n", + "df_title = pd.read_csv('../input/movie_titles.csv', encoding=\"ISO-8859-1\", header=None, names=['Movie_Id', 'Year', 'Name'])\n", + "df_title.set_index('Movie_Id', inplace=True)\n", + "\n", + "# Display first 10 rows of movie titles\n", + "print(df_title.head(10))\n" + ], + "metadata": { + "id": "4fNnCXK4lev8" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Collaborative Filtering - Matrix Factorization Using SVD\n" + ], + "metadata": { + "id": "6gUWKnx9lgNh" + } + }, + { + "cell_type": "code", + "source": [ + "# Using collaborative filtering to build a recommendation system\n", + "reader = Reader()\n", + "\n", + "# Load dataset from DataFrame\n", + "data = Dataset.load_from_df(df[['Cust_Id', 'Movie_Id', 'Rating']], reader)\n", + "\n", + "# Use Singular Value Decomposition (SVD) model\n", + "svd = SVD()\n", + "\n", + "# Perform cross-validation on the dataset\n", + "cross_validate(svd, data, measures=['RMSE', 'MAE'], cv=5, verbose=True)\n" + ], + "metadata": { + "id": "wFZoSX9Wlggw" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Movie Recommendation System" + ], + "metadata": { + "id": "MpEDpZ2ql2Wy" + } + }, + { + "cell_type": "code", + "source": [ + "# Build movie recommendation system based on user input and correlation\n", + "def recommend(movie_title, min_count):\n", + " print(\"Top 10 recommended movies are:\")\n", + "\n", + " # Filter based on minimum count of ratings\n", + " movie_id = df_title.index[df_title['Name'] == movie_title][0]\n", + " target_movie_ratings = df_p[movie_id]\n", + " similar_to_target = df_p.corrwith(target_movie_ratings)\n", + "\n", + " corr_target = pd.DataFrame(similar_to_target, columns=['PearsonR'])\n", + " corr_target.dropna(inplace=True)\n", + "\n", + " corr_summary = corr_target.join(df_movie_summary['count'])\n", + " recommendations = corr_summary[corr_summary['count'] > min_count].sort_values('PearsonR', ascending=False).head(10)\n", + "\n", + " print(recommendations)\n", + "\n", + "# Call the recommendation function for a sample movie\n", + "recommend(\"What the #$*! Do We Know!?\", 100)\n" + ], + "metadata": { + "id": "iFAeOSbel3DQ" + }, + "execution_count": null, + "outputs": [] + } + ] +} \ No newline at end of file