{
"cells": [
{
"cell_type": "code",
"execution_count": 46,
"source": [
"# This Notebook is created with VS Code on Windows\r\n",
"# Create python virtual environment\r\n",
"!python -m venv .venv\r\n",
"# If you want to use it on macOS/Linux\r\n",
"# You may need to run sudo apt-get install python3-venv first\r\n",
"#python3 -m venv .venv\r\n",
"\r\n",
"# Install Python Packages\r\n",
"!pip install --user --upgrade pip\r\n",
"!pip install --upgrade setuptools\r\n",
"!pip install --user seaborn\r\n",
"!pip install --user numpy\r\n",
"!pip install --user pandas\r\n",
"!pip install --user matplotlib\r\n",
"!pip install --user plotly\r\n",
"!pip install --user nbformat\r\n",
"!pip install --user surprise\r\n"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Requirement already satisfied: pip in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (21.2.4)\n",
"Requirement already satisfied: seaborn in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (0.11.2)\n",
"Requirement already satisfied: matplotlib>=2.2 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from seaborn) (3.3.4)\n",
"Requirement already satisfied: numpy>=1.15 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from seaborn) (1.19.5)\n",
"Requirement already satisfied: scipy>=1.0 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from seaborn) (1.6.1)\n",
"Requirement already satisfied: pandas>=0.23 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from seaborn) (1.2.2)\n",
"Requirement already satisfied: cycler>=0.10 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from matplotlib>=2.2->seaborn) (0.10.0)\n",
"Requirement already satisfied: kiwisolver>=1.0.1 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from matplotlib>=2.2->seaborn) (1.3.1)\n",
"Requirement already satisfied: pillow>=6.2.0 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from matplotlib>=2.2->seaborn) (8.1.0)\n",
"Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.3 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from matplotlib>=2.2->seaborn) (2.4.7)\n",
"Requirement already satisfied: python-dateutil>=2.1 in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from matplotlib>=2.2->seaborn) (2.8.1)\n",
"Requirement already satisfied: six in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from cycler>=0.10->matplotlib>=2.2->seaborn) (1.15.0)\n",
"Requirement already satisfied: pytz>=2017.3 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from pandas>=0.23->seaborn) (2021.1)\n",
"Requirement already satisfied: numpy in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (1.19.5)\n",
"Requirement already satisfied: pandas in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (1.2.2)\n",
"Requirement already satisfied: pytz>=2017.3 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from pandas) (2021.1)\n",
"Requirement already satisfied: python-dateutil>=2.7.3 in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from pandas) (2.8.1)\n",
"Requirement already satisfied: numpy>=1.16.5 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from pandas) (1.19.5)\n",
"Requirement already satisfied: six>=1.5 in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from python-dateutil>=2.7.3->pandas) (1.15.0)\n",
"Requirement already satisfied: matplotlib in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (3.3.4)\n",
"Requirement already satisfied: cycler>=0.10 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from matplotlib) (0.10.0)\n",
"Requirement already satisfied: kiwisolver>=1.0.1 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from matplotlib) (1.3.1)\n",
"Requirement already satisfied: numpy>=1.15 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from matplotlib) (1.19.5)\n",
"Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.3 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from matplotlib) (2.4.7)\n",
"Requirement already satisfied: python-dateutil>=2.1 in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from matplotlib) (2.8.1)\n",
"Requirement already satisfied: pillow>=6.2.0 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from matplotlib) (8.1.0)\n",
"Requirement already satisfied: six in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from cycler>=0.10->matplotlib) (1.15.0)\n",
"Requirement already satisfied: plotly in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (5.3.0)\n",
"Requirement already satisfied: six in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from plotly) (1.15.0)\n",
"Requirement already satisfied: tenacity>=6.2.0 in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from plotly) (8.0.1)\n",
"Requirement already satisfied: nbformat in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (5.1.3)\n",
"Requirement already satisfied: traitlets>=4.1 in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from nbformat) (5.0.5)\n",
"Requirement already satisfied: jsonschema!=2.5.0,>=2.4 in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from nbformat) (3.2.0)\n",
"Requirement already satisfied: ipython-genutils in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from nbformat) (0.2.0)\n",
"Requirement already satisfied: jupyter-core in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from nbformat) (4.7.1)\n",
"Requirement already satisfied: six>=1.11.0 in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from jsonschema!=2.5.0,>=2.4->nbformat) (1.15.0)\n",
"Requirement already satisfied: setuptools in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from jsonschema!=2.5.0,>=2.4->nbformat) (49.2.1)\n",
"Requirement already satisfied: pyrsistent>=0.14.0 in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from jsonschema!=2.5.0,>=2.4->nbformat) (0.18.0)\n",
"Requirement already satisfied: attrs>=17.4.0 in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from jsonschema!=2.5.0,>=2.4->nbformat) (21.2.0)\n",
"Requirement already satisfied: pywin32>=1.0 in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from jupyter-core->nbformat) (300)\n",
"Collecting surprise\n",
" Downloading surprise-0.1-py2.py3-none-any.whl (1.8 kB)\n",
"Collecting scikit-surprise\n",
" Downloading scikit-surprise-1.1.1.tar.gz (11.8 MB)\n",
"Requirement already satisfied: joblib>=0.11 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from scikit-surprise->surprise) (1.0.1)\n",
"Requirement already satisfied: numpy>=1.11.2 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from scikit-surprise->surprise) (1.19.5)\n",
"Requirement already satisfied: scipy>=1.0.0 in c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\lib\\site-packages (from scikit-surprise->surprise) (1.6.1)\n",
"Requirement already satisfied: six>=1.10.0 in c:\\users\\oli\\appdata\\roaming\\python\\python38\\site-packages (from scikit-surprise->surprise) (1.15.0)\n",
"Building wheels for collected packages: scikit-surprise\n",
" Building wheel for scikit-surprise (setup.py): started\n",
" Building wheel for scikit-surprise (setup.py): finished with status 'error'\n",
" Running setup.py clean for scikit-surprise\n",
"Failed to build scikit-surprise\n",
"Installing collected packages: scikit-surprise, surprise\n",
" Running setup.py install for scikit-surprise: started\n",
" Running setup.py install for scikit-surprise: finished with status 'error'\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
" ERROR: Command errored out with exit status 1:\n",
" command: 'c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\python.exe' -u -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '\"'\"'C:\\\\Users\\\\Oli\\\\AppData\\\\Local\\\\Temp\\\\pip-install-jibnz200\\\\scikit-surprise_07dc192d3cf347769fae216d5b05a944\\\\setup.py'\"'\"'; __file__='\"'\"'C:\\\\Users\\\\Oli\\\\AppData\\\\Local\\\\Temp\\\\pip-install-jibnz200\\\\scikit-surprise_07dc192d3cf347769fae216d5b05a944\\\\setup.py'\"'\"';f = getattr(tokenize, '\"'\"'open'\"'\"', open)(__file__) if os.path.exists(__file__) else io.StringIO('\"'\"'from setuptools import setup; setup()'\"'\"');code = f.read().replace('\"'\"'\\r\\n'\"'\"', '\"'\"'\\n'\"'\"');f.close();exec(compile(code, __file__, '\"'\"'exec'\"'\"'))' bdist_wheel -d 'C:\\Users\\Oli\\AppData\\Local\\Temp\\pip-wheel-dz4_1ymq'\n",
" cwd: C:\\Users\\Oli\\AppData\\Local\\Temp\\pip-install-jibnz200\\scikit-surprise_07dc192d3cf347769fae216d5b05a944\\\n",
" Complete output (49 lines):\n",
" running bdist_wheel\n",
" running build\n",
" running build_py\n",
" creating build\n",
" creating build\\lib.win-amd64-3.8\n",
" creating build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\accuracy.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\builtin_datasets.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\dataset.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\dump.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\reader.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\trainset.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\utils.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\__init__.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\__main__.py -> build\\lib.win-amd64-3.8\\surprise\n",
" creating build\\lib.win-amd64-3.8\\surprise\\model_selection\n",
" copying surprise\\model_selection\\search.py -> build\\lib.win-amd64-3.8\\surprise\\model_selection\n",
" copying surprise\\model_selection\\split.py -> build\\lib.win-amd64-3.8\\surprise\\model_selection\n",
" copying surprise\\model_selection\\validation.py -> build\\lib.win-amd64-3.8\\surprise\\model_selection\n",
" copying surprise\\model_selection\\__init__.py -> build\\lib.win-amd64-3.8\\surprise\\model_selection\n",
" creating build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\algo_base.py -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\baseline_only.py -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\knns.py -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\predictions.py -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\random_pred.py -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\__init__.py -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" running egg_info\n",
" writing scikit_surprise.egg-info\\PKG-INFO\n",
" writing dependency_links to scikit_surprise.egg-info\\dependency_links.txt\n",
" writing entry points to scikit_surprise.egg-info\\entry_points.txt\n",
" writing requirements to scikit_surprise.egg-info\\requires.txt\n",
" writing top-level names to scikit_surprise.egg-info\\top_level.txt\n",
" reading manifest file 'scikit_surprise.egg-info\\SOURCES.txt'\n",
" reading manifest template 'MANIFEST.in'\n",
" writing manifest file 'scikit_surprise.egg-info\\SOURCES.txt'\n",
" copying surprise\\similarities.c -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\similarities.pyx -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\prediction_algorithms\\co_clustering.c -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\matrix_factorization.c -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\optimize_baselines.c -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\slope_one.c -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\co_clustering.pyx -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\matrix_factorization.pyx -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\optimize_baselines.pyx -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\slope_one.pyx -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" running build_ext\n",
" building 'surprise.similarities' extension\n",
" error: Microsoft Visual C++ 14.0 is required. Get it with \"Build Tools for Visual Studio\": https://visualstudio.microsoft.com/downloads/\n",
" ----------------------------------------\n",
" ERROR: Failed building wheel for scikit-surprise\n",
" ERROR: Command errored out with exit status 1:\n",
" command: 'c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\python.exe' -u -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '\"'\"'C:\\\\Users\\\\Oli\\\\AppData\\\\Local\\\\Temp\\\\pip-install-jibnz200\\\\scikit-surprise_07dc192d3cf347769fae216d5b05a944\\\\setup.py'\"'\"'; __file__='\"'\"'C:\\\\Users\\\\Oli\\\\AppData\\\\Local\\\\Temp\\\\pip-install-jibnz200\\\\scikit-surprise_07dc192d3cf347769fae216d5b05a944\\\\setup.py'\"'\"';f = getattr(tokenize, '\"'\"'open'\"'\"', open)(__file__) if os.path.exists(__file__) else io.StringIO('\"'\"'from setuptools import setup; setup()'\"'\"');code = f.read().replace('\"'\"'\\r\\n'\"'\"', '\"'\"'\\n'\"'\"');f.close();exec(compile(code, __file__, '\"'\"'exec'\"'\"'))' install --record 'C:\\Users\\Oli\\AppData\\Local\\Temp\\pip-record-ivcio2vz\\install-record.txt' --single-version-externally-managed --user --prefix= --compile --install-headers 'C:\\Users\\Oli\\AppData\\Roaming\\Python\\Python38\\Include\\scikit-surprise'\n",
" cwd: C:\\Users\\Oli\\AppData\\Local\\Temp\\pip-install-jibnz200\\scikit-surprise_07dc192d3cf347769fae216d5b05a944\\\n",
" Complete output (49 lines):\n",
" running install\n",
" running build\n",
" running build_py\n",
" creating build\n",
" creating build\\lib.win-amd64-3.8\n",
" creating build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\accuracy.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\builtin_datasets.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\dataset.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\dump.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\reader.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\trainset.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\utils.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\__init__.py -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\__main__.py -> build\\lib.win-amd64-3.8\\surprise\n",
" creating build\\lib.win-amd64-3.8\\surprise\\model_selection\n",
" copying surprise\\model_selection\\search.py -> build\\lib.win-amd64-3.8\\surprise\\model_selection\n",
" copying surprise\\model_selection\\split.py -> build\\lib.win-amd64-3.8\\surprise\\model_selection\n",
" copying surprise\\model_selection\\validation.py -> build\\lib.win-amd64-3.8\\surprise\\model_selection\n",
" copying surprise\\model_selection\\__init__.py -> build\\lib.win-amd64-3.8\\surprise\\model_selection\n",
" creating build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\algo_base.py -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\baseline_only.py -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\knns.py -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\predictions.py -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\random_pred.py -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\__init__.py -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" running egg_info\n",
" writing scikit_surprise.egg-info\\PKG-INFO\n",
" writing dependency_links to scikit_surprise.egg-info\\dependency_links.txt\n",
" writing entry points to scikit_surprise.egg-info\\entry_points.txt\n",
" writing requirements to scikit_surprise.egg-info\\requires.txt\n",
" writing top-level names to scikit_surprise.egg-info\\top_level.txt\n",
" reading manifest file 'scikit_surprise.egg-info\\SOURCES.txt'\n",
" reading manifest template 'MANIFEST.in'\n",
" writing manifest file 'scikit_surprise.egg-info\\SOURCES.txt'\n",
" copying surprise\\similarities.c -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\similarities.pyx -> build\\lib.win-amd64-3.8\\surprise\n",
" copying surprise\\prediction_algorithms\\co_clustering.c -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\matrix_factorization.c -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\optimize_baselines.c -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\slope_one.c -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\co_clustering.pyx -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\matrix_factorization.pyx -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\optimize_baselines.pyx -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" copying surprise\\prediction_algorithms\\slope_one.pyx -> build\\lib.win-amd64-3.8\\surprise\\prediction_algorithms\n",
" running build_ext\n",
" building 'surprise.similarities' extension\n",
" error: Microsoft Visual C++ 14.0 is required. Get it with \"Build Tools for Visual Studio\": https://visualstudio.microsoft.com/downloads/\n",
" ----------------------------------------\n",
"ERROR: Command errored out with exit status 1: 'c:\\users\\oli\\appdata\\local\\programs\\python\\python38\\python.exe' -u -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '\"'\"'C:\\\\Users\\\\Oli\\\\AppData\\\\Local\\\\Temp\\\\pip-install-jibnz200\\\\scikit-surprise_07dc192d3cf347769fae216d5b05a944\\\\setup.py'\"'\"'; __file__='\"'\"'C:\\\\Users\\\\Oli\\\\AppData\\\\Local\\\\Temp\\\\pip-install-jibnz200\\\\scikit-surprise_07dc192d3cf347769fae216d5b05a944\\\\setup.py'\"'\"';f = getattr(tokenize, '\"'\"'open'\"'\"', open)(__file__) if os.path.exists(__file__) else io.StringIO('\"'\"'from setuptools import setup; setup()'\"'\"');code = f.read().replace('\"'\"'\\r\\n'\"'\"', '\"'\"'\\n'\"'\"');f.close();exec(compile(code, __file__, '\"'\"'exec'\"'\"'))' install --record 'C:\\Users\\Oli\\AppData\\Local\\Temp\\pip-record-ivcio2vz\\install-record.txt' --single-version-externally-managed --user --prefix= --compile --install-headers 'C:\\Users\\Oli\\AppData\\Roaming\\Python\\Python38\\Include\\scikit-surprise' Check the logs for full command output.\n"
]
}
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 3,
"source": [
"import numpy as np # maths\r\n",
"import pandas as pd # data processing\r\n",
"import matplotlib.pyplot as plt\r\n",
"import seaborn as sns\r\n",
"import os\r\n",
"import re\r\n",
"\r\n",
"from plotly.offline import init_notebook_mode, iplot\r\n",
"import plotly.graph_objs as go\r\n",
"import plotly.offline as py\r\n",
"py.init_notebook_mode(connected=True)\r\n",
"\r\n",
"import warnings\r\n",
"warnings.filterwarnings('ignore')\r\n",
"\r\n",
"plt.style.use('fivethirtyeight')\r\n",
"plt.rcParams['figure.figsize'] = [18, 8]"
],
"outputs": [
{
"output_type": "display_data",
"data": {
"text/html": [
" \n",
" "
]
},
"metadata": {}
}
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 4,
"source": [
"# Import Tables\r\n",
"reviews = pd.read_csv('./ml-1m/ratings.dat', names=['userId', 'movieId', 'rating', 'timestamp'], delimiter='::', engine='python')\r\n",
"movies = pd.read_csv('./ml-1m/movies.dat', names=['movieId', 'title', 'genres'], delimiter='::', engine='python')\r\n",
"users = pd.read_csv('./ml-1m/users.dat', names=['userId', 'gender', 'age', 'occupation', 'zip'], delimiter='::', engine='python')\r\n",
"\r\n",
"# Print Table shape\r\n",
"print('Reviews shape:', reviews.shape)\r\n",
"print('Users shape:', users.shape)\r\n",
"print('Movies shape:', movies.shape)"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Reviews shape: (1000209, 4)\n",
"Users shape: (6040, 5)\n",
"Movies shape: (3883, 3)\n"
]
}
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 5,
"source": [
"# Drop unused Attributes\r\n",
"reviews.drop(['timestamp'], axis=1, inplace=True) # Time\r\n",
"users.drop(['zip'], axis=1, inplace=True) # Zip-Code\r\n",
"\r\n",
"# Extract the movie year from title to extra attrbute\r\n",
"movies['release_year'] = movies['title'].str.extract(r'(?:\\((\\d{4})\\))?\\s*$', expand=False)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 6,
"source": [
"# Print movie table\r\n",
"movies.head()"
],
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" movieId title genres \\\n",
"0 1 Toy Story (1995) Animation|Children's|Comedy \n",
"1 2 Jumanji (1995) Adventure|Children's|Fantasy \n",
"2 3 Grumpier Old Men (1995) Comedy|Romance \n",
"3 4 Waiting to Exhale (1995) Comedy|Drama \n",
"4 5 Father of the Bride Part II (1995) Comedy \n",
"\n",
" release_year \n",
"0 1995 \n",
"1 1995 \n",
"2 1995 \n",
"3 1995 \n",
"4 1995 "
],
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" movieId | \n",
" title | \n",
" genres | \n",
" release_year | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 1 | \n",
" Toy Story (1995) | \n",
" Animation|Children's|Comedy | \n",
" 1995 | \n",
"
\n",
" \n",
" | 1 | \n",
" 2 | \n",
" Jumanji (1995) | \n",
" Adventure|Children's|Fantasy | \n",
" 1995 | \n",
"
\n",
" \n",
" | 2 | \n",
" 3 | \n",
" Grumpier Old Men (1995) | \n",
" Comedy|Romance | \n",
" 1995 | \n",
"
\n",
" \n",
" | 3 | \n",
" 4 | \n",
" Waiting to Exhale (1995) | \n",
" Comedy|Drama | \n",
" 1995 | \n",
"
\n",
" \n",
" | 4 | \n",
" 5 | \n",
" Father of the Bride Part II (1995) | \n",
" Comedy | \n",
" 1995 | \n",
"
\n",
" \n",
"
\n",
"
"
]
},
"metadata": {},
"execution_count": 6
}
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 7,
"source": [
"# Changed feature values based on README_users.txt\r\n",
"\r\n",
"ages_map = {1: 'Under 18',\r\n",
" 18: '18 - 24',\r\n",
" 25: '25 - 34',\r\n",
" 35: '35 - 44',\r\n",
" 45: '45 - 49',\r\n",
" 50: '50 - 55',\r\n",
" 56: '56+'}\r\n",
"\r\n",
"occupations_map = {0: 'Not specified',\r\n",
" 1: 'Academic / Educator',\r\n",
" 2: 'Artist',\r\n",
" 3: 'Clerical / Admin',\r\n",
" 4: 'College / Grad Student',\r\n",
" 5: 'Customer Service',\r\n",
" 6: 'Doctor / Health Care',\r\n",
" 7: 'Executive / Managerial',\r\n",
" 8: 'Farmer',\r\n",
" 9: 'Homemaker',\r\n",
" 10: 'K-12 student',\r\n",
" 11: 'Lawyer',\r\n",
" 12: 'Programmer',\r\n",
" 13: 'Retired',\r\n",
" 14: 'Sales / Marketing',\r\n",
" 15: 'Scientist',\r\n",
" 16: 'Self-Employed',\r\n",
" 17: 'Technician / Engineer',\r\n",
" 18: 'Tradesman / Craftsman',\r\n",
" 19: 'Unemployed',\r\n",
" 20: 'Writer'}\r\n",
"\r\n",
"gender_map = {'M': 'Male', 'F': 'Female'}\r\n",
"\r\n",
"users['age'] = users['age'].map(ages_map)\r\n",
"users['occupation'] = users['occupation'].map(occupations_map)\r\n",
"users['gender'] = users['gender'].map(gender_map)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 8,
"source": [
"# Plot age kategories\r\n",
"\r\n",
"age_reindex = ['Under 18', '18 - 24', '25 - 34', '35 - 44', '45 - 49', '50 - 55', '56+']\r\n",
"age_counts = users['age'].value_counts().reindex(age_reindex)\r\n",
"sns.barplot(x=age_counts.values,\r\n",
" y=age_counts.index,\r\n",
" palette='magma').set_title(\r\n",
" 'Users age', fontsize=12)\r\n",
"\r\n",
"plt.show()"
],
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
""
],
"image/svg+xml": "\r\n\r\n\r\n\r\n",
"image/png": "iVBORw0KGgoAAAANSUhEUgAABLkAAAICCAYAAAAj5h9BAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAA7p0lEQVR4nO3de5hVdd03/vcwo0KgDiKOnAkYQAFFQSV79PGAGKmQecBD2mMmd6gPeecBPOWxkDykpmKPilqpUai3h0wrj6SYlsfUmxvFAyKijkIBIsrM749+7asJBJQZNktfr+va1zVrfb97fT9rvmvP3vs9a69dMX/+/IYAAAAAQIG1KHcBAAAAALCmhFwAAAAAFJ6QCwAAAIDCE3IBAAAAUHhCLgAAAAAKT8gFAAAAQOEJuQAAAAAoPCEXAECZVFdXZ9asWY3WTZgwIaNHjy5TRQAAxSXkAgD4jPvoo4/KXQIAQLMTcgEArKPq6uoyatSodO3aNd27d8/w4cNTX1+fJJk7d24OO+yw9OzZM1tttVWuvPLK0v0mTJiQww8/PKNHj06XLl1y44035i9/+Ut22WWXdOnSJbW1tTnllFNWOOb8+fMzatSo9OzZM926dcuoUaMyZ86cUvsrr7yS4cOHp3Pnzhk5cmROOOGERmeePf744xk2bFi6du2aL3/5y5k2bVoz/XYAABoTcgEArKMuu+yydOzYMS+99FJmzpyZ008/PRUVFamvr89BBx2U/v3754UXXsjtt9+eSZMm5d577y3d96677sqIESPy6quv5oADDsj48ePzne98J7Nnz86TTz6Zfffdd4Vj1tfX55BDDsmzzz6bv/71r2nZsmVOPPHEUvtRRx2VQYMGZdasWRk/fnymTJlSanvjjTdy4IEH5oQTTsgrr7ySc889N4cffnjeeeed5vslAQD8/4RcAADrqKqqqrz55puZPXt21ltvvey4446pqKjIE088kbq6uowbNy7rr79+unfvnm9+85u5+eabS/fdbrvtsvfee6dFixZp1apVqqqqMmvWrNTV1aVNmzbZbrvtVjjmJptskpEjR+YLX/hCNtxwwxx//PF5+OGHkySzZ8/OE088kVNOOSXrr79+vvSlL2X48OGl+/7qV7/KHnvskWHDhqVFixbZdddds8022+R3v/td8/6iAAAi5AIAKJvKysp8+OGHjdZ99NFHqaqqSpKMHTs2PXr0yL777putt946P/7xj5P8I2yaO3duunbtWrpddNFFefvtt0vb6dy5c6PtXnbZZXnppZey3XbbZdddd83dd9+9wpoWL16c4447Lv3790+XLl2y1157ZcGCBVm2bFnefPPNtG3bNl/4whdK/Tt16lT6efbs2bntttsa1fXoo49m3rx5a/aLAgBYDVXlLgAA4POqc+fOee2119KnT5/SuldffTU9e/ZMkmy44Yb5wQ9+kB/84Ad5/vnnM2LEiGy77bbp1KlTunXrlieeeOJjt11RUdFouWfPnrnmmmtSX1+fO+64I9/85jcza9astG7dulG/yy67LDNnzsy9996bmpqaPPPMM9l5553T0NCQmpqavPfee1m8eHEp6PrX63V16tQpo0aNyqWXXrrGvxsAgE/KmVwAAGXy9a9/PRdccEHmzJmT+vr6PPDAA7n77rszcuTIJMndd9+dWbNmpaGhIRtttFEqKytTUVGRQYMGpU2bNrn44ovz/vvvZ9myZXn++edXGnpNmTIl77zzTlq0aJGNN944SdKixfIvBRcuXJhWrVpl4403znvvvZeJEyeW2rp27Zptttkm5513XpYuXZrHHnus0RlhBx54YO6+++7ce++9WbZsWZYsWZJp06Y1CsIAAJqLkAsAoExOOumkbL/99vnKV76S7t275/vf/37+3//7f9lyyy2TJC+99FJGjhyZTp06ZdiwYTnyyCOz8847p7KyMlOmTMmzzz6brbfeOj169MjYsWPzt7/97WPHuvfeezNkyJB06tQp48ePzzXXXJNWrVot12/MmDF5//3307NnzwwdOjRDhw5t1H7VVVfl8ccfT48ePXLuuedm3333zfrrr5/kH2em3XjjjbnwwgvTs2fP9OvXLz/5yU9K3wgJANCcKubPn99Q7iIAACimI444IrW1tTnllFPKXQoA8DnnTC4AAFbbE088kZdffjn19fX5wx/+kLvuuit77bVXucsCAHDheQAAVt+8efNy2GGH5d13303Hjh1z4YUXZuutty53WQAAPq4IAAAAQPH5uCIAAAAAhSfkAgAAAKDwhFwAAAAAFJ6QCwAAAIDCE3LBapo5c2a5S6CMzP/nm/nHMfD5Zv4/38z/55v5/3wz/8Uj5AIAAACg8IRcAAAAABSekAsAAACAwqsqdwFFtE2fr5W7BNbAkzP+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oPCEXAAAAAAUnpALAAAAgMITcgEAAABQeEIuAAAAAApPyAUAAABA4Qm5AAAAACg8IRcAAAAAhSfkAgAAAKDwhFwAAAAAFJ6QCwAAAIDCE3IBAAAAUHhCLgAAAAAKT8gFAAAAQOEJuQAAAAAoPCEXAAAAAIUn5AIAAACg8IRcAAAAABSekAsAAACAwmvWkOvhhx/OQQcdlC222CLV1dW54YYbluuzcOHCnHjiidlyyy2z+eabZ/Dgwbn88svXeOxXX301xx57bLbeeutsvvnm2XrrrXPWWWfl/fffX2H/urq6Up11dXVrPD4AAAAAa09Vc2580aJF2XLLLXPwwQfnO9/5zgr7nHrqqXnggQdy5ZVXplu3bnnkkUfy3e9+N+3atctBBx30qceeOXNmli1blosuuig9e/bMjBkzctxxx+Xdd9/NJZdcslz/o48+OgMGDMjcuXM/9ZgAAAAAlEeznsk1bNiwfP/738/IkSPTosWKh3rssccyatSo7LzzzunWrVsOPvjgDB48OH/5y1/WaOyhQ4dm0qRJ2X333dO9e/fsueeeOf7443P77bcv13fSpEl5//33c8wxx6zRmAAAAACUR9mvyTVkyJDcfffdef3115Mkf/rTn/LXv/41u+++e5OP9fe//z3V1dWN1j399NO55JJLcuWVV35sEAcAAADAuq3sqc7EiRPTv3//9O/fP5tuumn22muvnHnmmfnKV77SpOO89tpr+clPfpIjjzyytG7RokU58sgjM3HixHTs2LFJxwMAAABg7Sl7yPXTn/40jz32WG666aY88MAD+eEPf5jTTz89f/jDH1bYf/bs2enUqVPpduGFF65yjLfeeiv7779/dt1110YfSRw3blyGDBmSkSNHNtn+AAAAALD2NeuF51fl/fffz9lnn53rrrsuw4cPT5L0798/zz77bH7yk59k6NChy92nQ4cOmTZtWmm5bdu2Kx1j3rx5GTFiRLbYYov89Kc/TUVFRantwQcfzJw5c3LTTTclSRoaGpIkvXv3znHHHZfTTz99jfcRAAAAgOZX1pDrww8/zIcffpjKyspG6ysrK1NfX7/C+1RVVaVHjx6rtf0333wz++yzT/r27ZtrrrkmVVWNd/fWW2/N0qVLS8tPPPFEjj322Nx5553p2bPnJ9wbAAAAAMqlWUOuhQsXZtasWUmS+vr6vP7663nm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FwAAAACFJ+QCAAAAoPCEXAAAAAAUnpALAAAAgMITcgEAAABQeEIuAAAAAApPyAUAAABA4Qm5AAAAACg8IRcAAAAAhSfkAgAAAKDwhFwAAAAAFN5aC7kuuuiiVFdX58QTT2y0fsyYMamurm50Gzp0aJOOvWTJknz5y19OdXV1nnzyyUZtDz74YIYNG5bOnTund+/eOeOMM/LRRx816fgAAAAANK+1EnI9/vjjue6669KvX78Vtu+yyy6ZMWNG6fbrX/+6Scc//fTT06lTp+XWP/vssznggAOyyy675KGHHsrkyZPz29/+NmeeeWaTjg8AAABA82r2kGvBggU56qijctlll6W6unqFfTbYYIPU1NSUbm3btm2y8X/zm99k2rRpOeecc5Zru/XWW9OnT5+ccsop6dGjR/7X//pfOeuss3L11Vfn73//e5PVAAAAAEDzavaQ67jjjsvIkSOz8847f2yf6dOnp1evXhk0aFDGjh2bt99+u0nGnjNnTo4//vhcddVVadmy5XLtH3zwwXLrW7VqlSVLluSpp55qkhoAAAAAaH7NGnJdf/31mTVrVk477bSP7TN06NBceeWVue2223LuuefmL3/5S0aMGJEPPvhgjcZetmxZjjrqqBxzzDEZMGDACvvsvvvu+fOf/5wpU6bko48+yhtvvJGJEycmSebNm7dG4wMAAACw9jRbyDVz5sycffbZufrqq7Peeut9bL/99tsvX/3qV9OvX78MHz48U6dOzcyZM3PPPfessP/s2bPTqVOn0u3CCy9cYb8LL7ww66+/fo499tiPHXu33XbLOeeckxNPPDE1NTUZPHhwhg0bliRp0cIXTwIAAAAURVVzbfixxx5LXV1dhgwZUlq3bNmyPPLII5k8eXLeeOONbLDBBsvdr0OHDunYsWNmzZq1wu126NAh06ZNKy1/3PW7HnzwwUyfPj2bbrppo/VDhw7N17/+9Vx11VVJkmOPPTbHHHNM3nzzzVRXV+e1117LWWedle7du3/SXQYAAACgTJot5Nprr72yzTbbNFp3zDHHpGfPnvne976X9ddff4X3q6ury9y5c1NTU7PC9qqqqvTo0WOV419++eVZvHhxafnNN98shVs77LBDo74VFRXp0KFDkmTq1Knp3Llztt5661WOAQAAAMC6odlCrurq6uW+TfELX/hC2rZtmy233DJJsnDh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hFwAAAAAFJ6QCwAAAIDCE3IBAAAAUHhCLgAAAAAKT8gFAAAAQOFVlbuAImp5+KnlLoEymDlzZmpra8tdBgAAALACzuQCAAAAoPCEXAAAAAAUnpALAAAAgMITcgEAAABQeEIuAAAAAApPyAUAAABA4Qm5AAAAACg8IRcAAAAAhSfkAgAAAKDwhFwAAAAAFJ6QCwAAAIDCE3IBAAAAUHhV5S6giBr+elO5S6AMeiVp+Oufy13GOqGi/8HlLgEAAAAacSYXAAAAAIUn5AIAAACg8IRcAAAAABSekAsAAACAwhNyAQAAAFB4Qi4AAAAACk/IBQAAAEDhCbkAAAAAKDwhFwAAAACFJ+QCAAAAoPCEXAAAAAAUnpALAAAAgMITcgEAAABQeEIuAAAAAApPyAUAAABA4Qm5AAAAACg8IRcAAAAAhSfkAgAAAKDwhFwAAAAAFJ6QCwAAAIDCE3IBAAAAUHhCLgAAAAAKr1Ah14QJE1JdXd3o1rt370Z9XnzxxXzjG99I165d06FDh+y8886ZMWNGmSoGAAAAYG2oKncBn1RtbW3uvPPO0nJlZWXp51deeSV77rlnDjrooNx+++2prq7O//zP/6R169Yfu70BAwbkiiuuyE477dSsdQMAAADQfAoXclVVVaWmpmaFbeeee2522223/OAHPyit6969+1qqDAAAAIByKdTHFZN/nK3Vt2/fbLXVVvnWt76VV155JUlSX1+fu+++O3369Ml+++2Xnj17Ztddd80tt9xS3oIBAAAAaHaFCrkGDx6cK664IlOnTs2ll16aefPmZdiwYXn33Xfz9ttvZ+HChbnooouy66675tZbb81+++2Xo446Kvfcc0+5SwcAAACgGRXq44p77LFHo+XBgwdn4MCBufHGG7PffvslSb761a/m2GOPTZJstdVWeeqpp3LVVVdlzz33TJLsv//+mT59emkbixcvzgEHHNDo2l5z5sxp7l0BAAAAoAkVKuT6d23atEnfvn0za9astGvXLlVVVenTp0+jPr179270kcVLL700S5YsKS3vvffeOfPMMzN48OC1VjcAAAAATavQIdeSJUsyc+bM7LTTTll//fWz7bbbZubMmY36vPjii+nSpUtpuWPHjo3aKysr06FDh/To0WOt1AwAAABA0ytUyHXaaaflK1/5Sjp37px33nkn559/fhYvXpyDDz44STJ27NgcccQR2XHHHbPzzjtn2rRpueWWW3LDDTeUuXIAAAAAmlOhQq433ngj3/72t1NXV5dNN900gwcPzu9///t07do1yT8+enjxxRfnoosuyvjx49OjR49ceeWVpetxAQAAAPDZVKiQa/Lkyavsc+ihh+bQQw9d7W0+++yza1ISAAAAAOuAFuUuAAAAAADWlJALAAAAgMITcgEAAABQeEIuAAAAAApPyAUAAABA4Qm5AAAAACg8IRcAAAAAhSfkAgAAAKDwhFwAAAAAFJ6QCwAAAIDCE3IBAAAAUHhCLgAAAAAKT8gFAAAAQOEJuQAAAAAoPCEXAAAAAIUn5AIAAACg8IRcAAAAABSekAsAAACAwhNyAQAAAFB4Qi4AAAAACq+q3AUUUUX/g8tdAmUwc+bM1NbWlrsMAAAAYAWcyQUAAABA4Qm5AAAAACg8IRcAAAAAhSfkAgAAAKDwKubPn99Q7iIAAAAAYE04kwsAAACAwhNyAQAAAFB4Qi4AAAAACk/IBQAAAEDhCbkAAAAAKDwh12q6+uqrs9VWW6Wmpib/+3//7zzyyCPlLok1dNFFF2XXXXdNly5d0rNnz4waNSrPP/98oz5jxoxJdXV1o9vQoUMb9fnggw9y4oknpkePHunYsWMOOuigzJkzZ23uCp/ChAkTlpvb3r17l9obGhoyYcKE9O3bN5tvvnn22muvvPDCC422MX/+/IwePTpdu3ZN165dM3r06MyfP38t7wmfxoABA5ab/+rq6hx44IFJVn18JKt3jLDuePjhh3PQQQdliy22SHV1dW644YZG7U31mH/uuefy1a9+NZtvvnm22GKLTJw4MQ0Nvsi6nFY29x9++GHOOOOM7LjjjunYsWP69OmTb3/725k9e3ajbey1117L/U341re+1aiP54R116oe/031em/27NkZNWpUOnbsmB49euSkk07K0qVLm33/WLlVzf+KXg9UV1fnhBNOKPXxnqCYVuf9nuf/zx4h12q45ZZbMn78+Bx//PF56KGHsv322+eAAw5Y7gUQxfLHP/4xRx55ZO65557cfvvtqaqqyte+9rW89957jfrtsssumTFjRun261//ulH7ySefnDvuuCPXXHNN7rrrrvz973/PqFGjsmzZsrW5O3wKtbW1jeb2X8PrSy65JJdffnkmTpyY++67L+3bt8++++6bv//976U+3/72t/PMM89k6tSpmTp1ap555pn8x3/8Rzl2hU/o/vvvbzT3Dz74YCoqKvK1r32t1Gdlx0eyescI645FixZlyy23zHnnnZdWrVot194Uj/m//e1v2XfffbPZZpvlvvvuy3nnnZef/OQnueyyy9bKPrJiK5v7xYsX5+mnn84JJ5yQBx98MDfeeGPmzJmT/fffPx999FGjvoceemijvwk//vGPG7V7Tlh3rerxn6z5671ly5Zl1KhRWbhwYe66665cc801uf3223Pqqac2+/6xcqua/3+d9xkzZuSXv/xlkjR6TZB4T1BEq/N+z/P/Z0/F/PnzxYursPvuu6dfv3659NJLS+u23XbbjBw5MmeccUYZK6MpLVy4MF27ds0NN9yQ4cOHJ/nHf23efffdTJkyZYX3WbBgQXr16pXLL7+8dAbI66+/ngEDBmTq1KnZfffd11r9fDITJkzI7bffnunTpy/X1tDQkL59++aoo44q/Rfv/fffT21tbc4555wcccQRmTFjRnbYYYfcfffdGTJkSJJk+vTpGT58eB5//PHU1tau1f1hzVxwwQW59NJLM2PGjLRq1Wqlx0eyescI665OnTrlRz/6UQ499NAkTfeYv+aaa3LmmWfmf/7nf0pvpM4///xMnjw5zz//fCoqKsqzw5T8+9yvyH//939nyJAhefjhh9OvX78k/ziTa8stt8z555+/wvt4TiiOFR0DTfF67/e//30OPPDAPPvss+ncuXOSZMqUKRk7dmxmzpyZjTbaqPl3jlVanb8BY8eOzSOPPJI///nPpXXeE3w2/Pv7Pc//n03O5FqFpUuX5qmnnspuu+3WaP1uu+2WP/3pT2WqiuawcOHC1NfXp7q6utH66dOnp1evXhk0aFDGjh2bt99+u9T21FNP5cMPP2x0fHTu3Dl9+vRxfBTAK6+8kr59+2arrbbKt771rbzyyitJkldffTXz5s1rNK+tWrXKjjvuWJrXxx57LG3atMkOO+xQ6jNkyJC0bt3a3BdMQ0NDfv7zn2fUqFGN/sP7ccdHsnrHCMXRVI/5xx57LF/60pcaHUe777575s6dm1dffXUt7Q1r6p//vf/31wM333xzevTokSFDhuS0005r9F9+zwnFt6av9x577LH06dOnFHAl/3j8f/DBB3nqqafW2n6wZhYuXJhbbrkl3/zmN5dr856g+P79/Z7n/8+mqnIXsK6rq6vLsmXL0r59+0br27dvn7feeqtMVdEcxo8fnwEDBmT77bcvrRs6dGj22WefdOvWLa+99lrOPffcjBgxIg888EA22GCDvPXWW6msrEy7du0abcvxse4bPHhwrrjiitTW1uadd97J+eefn2HDhuXRRx/NvHnzkmSFj/u5c+cmSd566620a9eu0X9mKioqsummm5r7grn//vvz6quv5vDDDy+tW9nxsckmm6zWMUJxNNVj/q233krHjh2X28Y/27p3795cu0ATWbp0aU477bR85StfSadOnUrrDzjggHTp0iWbb755/vu//ztnnXVWnnvuudx6661JPCcUXVO83nvrrbeW+xvSrl27VFZWOgYKZOrUqVm6dGkOPvjgRuu9J/hs+Pf3e57/P5uEXJDklFNOyaOPPpq77747lZWVpfX77bdf6ed+/fpl4MCBGTBgQO65556MGDGiHKXSRPbYY49Gy4MHD87AgQNz4403ZrvttitTVZTD9ddfn2233TYDBgworVvZ8XHssceu7RKBteCjjz7K6NGjs2DBgtx0002N2v7P//k/pZ/79euX7t27Z/fdd89TTz2VgQMHrt1CaXJe7/FP119/fb761a9m0003bbTeMVJ8H/d+j88eH1dchX/+B+ZfT0dNkrfffjubbbZZmaqiKZ188sm5+eabc/vtt68yZe/QoUM6duyYWbNmJUk222yzLFu2LHV1dY36OT6Kp02bNunbt29mzZqVmpqaJFnp436zzTZLXV1do29NaWhoyDvvvGPuC+Ttt9/OXXfdtcKPJfyrfz0+kqzWMUJxNNVjfrPNNlvhNv7Zxrrro48+ypFHHpnnnnsut912WzbZZJOV9t9mm21SWVnZ6PWA54TPjk/zem9Fj/9/fiLEMVAMzzzzTJ588slVviZIvCcomo97v+f5/7NJyLUK66+/fgYOHJj777+/0fr777+/0edyKaZx48aV/uD17t17lf3r6uoyd+7c0h/EgQMHZr311mt0fMyZM6d0gUKKY8mSJZk5c2ZqamrSrVu31NTUNJrXJUuWZPr06aV53X777bNw4cI89thjpT6PPfZYFi1aZO4L5MYbb8wGG2zQ6D+0K/Kvx0eS1TpGKI6mesxvv/32mT59epYsWVLqc//996dDhw7p1q3bWtobPqkPP/wwRxxxRJ577rnccccdpcf5yjz33HNZtmxZqa/nhM+WT/N6b/vtt8+MGTMyZ86cUp/7778/G2ywgbP9CuL6669Pt27dsssuu6yyr/cExbGy93ue/z+bfFxxNRxzzDH5j//4jwwaNCg77LBDJk+enDfffNO3ZxXcCSeckClTpuQXv/hFqqurS5/Jbt26ddq0aZOFCxfmvPPOy4gRI1JTU5PXXnstZ599dtq3b5+99947SbLxxhvnsMMOyxlnnJH27dunbdu2OfXUU9OvX7/VeoKkfP55zZXOnTuXrrm0ePHiHHzwwamoqMiYMWNy0UUXpba2Nr169coFF1yQ1q1bZ//990+S9OnTJ0OHDs1//ud/5uKLL06S/Od//mf23HNP36JVEA0NDfnZz36Wr3/962nTpk2jtpUdH0lW6xhh3bJw4cLSf9zr6+vz+uuv55lnnknbtm3TpUuXJnnM77///pk4cWKOPvronHDCCXnxxRdz8cUX56STTvLNSmW0srnv0KFDvvnNb+bJJ5/MTTfdlIqKitLrgY022iitWrXKyy+/nF/96lcZNmxYNtlkk8yYMSOnnXZattpqq9I3bXlOWLet7Bho27Ztk7ze22233bLFFlvkO9/5Ts4999y89957+f73v5/DDz/cNyuW2ar+/ifJ4sWL8+tf/zpjx45d7u+19wTFtar3e031mt/z/7qlYv78+Q2r7sbVV1+dSy65JPPmzcsWW2yRH/7wh/nyl79c7rJYA//+rUn/NG7cuJx88sl5//33c+ihh+aZZ57JggULUlNTk5122imnnnpqo2/O+eCDD3Laaadl6tSpWbJkSXbeeedceOGFjfqw7vnWt76VRx55JHV1ddl0000zePDgnHrqqenbt2+SfwQg5513Xq677rrMnz8/gwYNygUXXJAtt9yytI358+fnpJNOym9/+9skyfDhw/OjH/3oY48t1i0PPfRQRowYkXvvvTeDBg1q1Laq4yNZvWOEdce0adOyzz77LLf+4IMPzqRJk5rsMf/cc8/lhBNOyBNPPJHq6uocccQRGTdunBe5ZbSyuR8/fny23nrrFd7v8ssvz6GHHprXX389o0ePzgsvvJBFixalU6dOGTZsWMaPH5+2bduW+ntOWHet7Bi46KKLmuz13uzZs3PCCSfkoYceSsuWLXPAAQfknHPOyQYbbLBW9pMVW9Xf/yT5xS9+ke9+97v561//mg4dOjTq5z1Bca3q/V7SdK/5Pf+vO4RcAAAAABSea3IBAAAAUHhCLgAAAAAKT8gFAAAAQOEJuQAAAAAoPCEXAAAAAIUn5AIAAACg8IRcAAAAABSekAsAAACAwhNyAQAAAFB4/x9nBGN4jbN+fgAAAABJRU5ErkJggg=="
},
"metadata": {}
}
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 9,
"source": [
"# Plot gender of users\r\n",
"gender_counts = users['gender'].value_counts()\r\n",
"colors1 = ['lightblue', 'pink']\r\n",
"pie = go.Pie(labels=gender_counts.index,\r\n",
" values=gender_counts.values,\r\n",
" marker=dict(colors=colors1),\r\n",
" hole=0.5)\r\n",
"layout = go.Layout(title='Gender Users', font=dict(size=12), legend=dict(orientation='h'))\r\n",
"\r\n",
"fig = go.Figure(data=[pie], layout=layout)\r\n",
"py.iplot(fig)"
],
"outputs": [
{
"output_type": "display_data",
"data": {
"text/html": [
""
],
"application/vnd.plotly.v1+json": {
"config": {
"linkText": "Export to plot.ly",
"plotlyServerURL": "https://plot.ly",
"showLink": false
},
"data": [
{
"hole": 0.5,
"labels": [
"Male",
"Female"
],
"marker": {
"colors": [
"lightblue",
"pink"
]
},
"type": "pie",
"values": [
4331,
1709
]
}
],
"layout": {
"font": {
"size": 12
},
"legend": {
"orientation": "h"
},
"template": {
"data": {
"bar": [
{
"error_x": {
"color": "#2a3f5f"
},
"error_y": {
"color": "#2a3f5f"
},
"marker": {
"line": {
"color": "#E5ECF6",
"width": 0.5
},
"pattern": {
"fillmode": "overlay",
"size": 10,
"solidity": 0.2
}
},
"type": "bar"
}
],
"barpolar": [
{
"marker": {
"line": {
"color": "#E5ECF6",
"width": 0.5
},
"pattern": {
"fillmode": "overlay",
"size": 10,
"solidity": 0.2
}
},
"type": "barpolar"
}
],
"carpet": [
{
"aaxis": {
"endlinecolor": "#2a3f5f",
"gridcolor": "white",
"linecolor": "white",
"minorgridcolor": "white",
"startlinecolor": "#2a3f5f"
},
"baxis": {
"endlinecolor": "#2a3f5f",
"gridcolor": "white",
"linecolor": "white",
"minorgridcolor": "white",
"startlinecolor": "#2a3f5f"
},
"type": "carpet"
}
],
"choropleth": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"type": "choropleth"
}
],
"contour": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1,
"#f0f921"
]
],
"type": "contour"
}
],
"contourcarpet": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"type": "contourcarpet"
}
],
"heatmap": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1,
"#f0f921"
]
],
"type": "heatmap"
}
],
"heatmapgl": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1,
"#f0f921"
]
],
"type": "heatmapgl"
}
],
"histogram": [
{
"marker": {
"pattern": {
"fillmode": "overlay",
"size": 10,
"solidity": 0.2
}
},
"type": "histogram"
}
],
"histogram2d": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1,
"#f0f921"
]
],
"type": "histogram2d"
}
],
"histogram2dcontour": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1,
"#f0f921"
]
],
"type": "histogram2dcontour"
}
],
"mesh3d": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"type": "mesh3d"
}
],
"parcoords": [
{
"line": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "parcoords"
}
],
"pie": [
{
"automargin": true,
"type": "pie"
}
],
"scatter": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scatter"
}
],
"scatter3d": [
{
"line": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scatter3d"
}
],
"scattercarpet": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scattercarpet"
}
],
"scattergeo": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scattergeo"
}
],
"scattergl": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scattergl"
}
],
"scattermapbox": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scattermapbox"
}
],
"scatterpolar": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scatterpolar"
}
],
"scatterpolargl": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scatterpolargl"
}
],
"scatterternary": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scatterternary"
}
],
"surface": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1,
"#f0f921"
]
],
"type": "surface"
}
],
"table": [
{
"cells": {
"fill": {
"color": "#EBF0F8"
},
"line": {
"color": "white"
}
},
"header": {
"fill": {
"color": "#C8D4E3"
},
"line": {
"color": "white"
}
},
"type": "table"
}
]
},
"layout": {
"annotationdefaults": {
"arrowcolor": "#2a3f5f",
"arrowhead": 0,
"arrowwidth": 1
},
"autotypenumbers": "strict",
"coloraxis": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"colorscale": {
"diverging": [
[
0,
"#8e0152"
],
[
0.1,
"#c51b7d"
],
[
0.2,
"#de77ae"
],
[
0.3,
"#f1b6da"
],
[
0.4,
"#fde0ef"
],
[
0.5,
"#f7f7f7"
],
[
0.6,
"#e6f5d0"
],
[
0.7,
"#b8e186"
],
[
0.8,
"#7fbc41"
],
[
0.9,
"#4d9221"
],
[
1,
"#276419"
]
],
"sequential": [
[
0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1,
"#f0f921"
]
],
"sequentialminus": [
[
0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1,
"#f0f921"
]
]
},
"colorway": [
"#636efa",
"#EF553B",
"#00cc96",
"#ab63fa",
"#FFA15A",
"#19d3f3",
"#FF6692",
"#B6E880",
"#FF97FF",
"#FECB52"
],
"font": {
"color": "#2a3f5f"
},
"geo": {
"bgcolor": "white",
"lakecolor": "white",
"landcolor": "#E5ECF6",
"showlakes": true,
"showland": true,
"subunitcolor": "white"
},
"hoverlabel": {
"align": "left"
},
"hovermode": "closest",
"mapbox": {
"style": "light"
},
"paper_bgcolor": "white",
"plot_bgcolor": "#E5ECF6",
"polar": {
"angularaxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": ""
},
"bgcolor": "#E5ECF6",
"radialaxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": ""
}
},
"scene": {
"xaxis": {
"backgroundcolor": "#E5ECF6",
"gridcolor": "white",
"gridwidth": 2,
"linecolor": "white",
"showbackground": true,
"ticks": "",
"zerolinecolor": "white"
},
"yaxis": {
"backgroundcolor": "#E5ECF6",
"gridcolor": "white",
"gridwidth": 2,
"linecolor": "white",
"showbackground": true,
"ticks": "",
"zerolinecolor": "white"
},
"zaxis": {
"backgroundcolor": "#E5ECF6",
"gridcolor": "white",
"gridwidth": 2,
"linecolor": "white",
"showbackground": true,
"ticks": "",
"zerolinecolor": "white"
}
},
"shapedefaults": {
"line": {
"color": "#2a3f5f"
}
},
"ternary": {
"aaxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": ""
},
"baxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": ""
},
"bgcolor": "#E5ECF6",
"caxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": ""
}
},
"title": {
"x": 0.05
},
"xaxis": {
"automargin": true,
"gridcolor": "white",
"linecolor": "white",
"ticks": "",
"title": {
"standoff": 15
},
"zerolinecolor": "white",
"zerolinewidth": 2
},
"yaxis": {
"automargin": true,
"gridcolor": "white",
"linecolor": "white",
"ticks": "",
"title": {
"standoff": 15
},
"zerolinecolor": "white",
"zerolinewidth": 2
}
}
},
"title": {
"text": "Gender Users"
}
}
}
},
"metadata": {}
}
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 10,
"source": [
"# Merge reviews, movie and user dataset\r\n",
"final_df = reviews.merge(movies, on='movieId', how='left').merge(users, on='userId', how='left')\r\n",
"print('final_df shape:', final_df.shape)\r\n",
"final_df.head()"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"final_df shape: (1000209, 9)\n"
]
},
{
"output_type": "execute_result",
"data": {
"text/plain": [
" userId movieId rating title \\\n",
"0 1 1193 5 One Flew Over the Cuckoo's Nest (1975) \n",
"1 1 661 3 James and the Giant Peach (1996) \n",
"2 1 914 3 My Fair Lady (1964) \n",
"3 1 3408 4 Erin Brockovich (2000) \n",
"4 1 2355 5 Bug's Life, A (1998) \n",
"\n",
" genres release_year gender age occupation \n",
"0 Drama 1975 Female Under 18 K-12 student \n",
"1 Animation|Children's|Musical 1996 Female Under 18 K-12 student \n",
"2 Musical|Romance 1964 Female Under 18 K-12 student \n",
"3 Drama 2000 Female Under 18 K-12 student \n",
"4 Animation|Children's|Comedy 1998 Female Under 18 K-12 student "
],
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" userId | \n",
" movieId | \n",
" rating | \n",
" title | \n",
" genres | \n",
" release_year | \n",
" gender | \n",
" age | \n",
" occupation | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 1 | \n",
" 1193 | \n",
" 5 | \n",
" One Flew Over the Cuckoo's Nest (1975) | \n",
" Drama | \n",
" 1975 | \n",
" Female | \n",
" Under 18 | \n",
" K-12 student | \n",
"
\n",
" \n",
" | 1 | \n",
" 1 | \n",
" 661 | \n",
" 3 | \n",
" James and the Giant Peach (1996) | \n",
" Animation|Children's|Musical | \n",
" 1996 | \n",
" Female | \n",
" Under 18 | \n",
" K-12 student | \n",
"
\n",
" \n",
" | 2 | \n",
" 1 | \n",
" 914 | \n",
" 3 | \n",
" My Fair Lady (1964) | \n",
" Musical|Romance | \n",
" 1964 | \n",
" Female | \n",
" Under 18 | \n",
" K-12 student | \n",
"
\n",
" \n",
" | 3 | \n",
" 1 | \n",
" 3408 | \n",
" 4 | \n",
" Erin Brockovich (2000) | \n",
" Drama | \n",
" 2000 | \n",
" Female | \n",
" Under 18 | \n",
" K-12 student | \n",
"
\n",
" \n",
" | 4 | \n",
" 1 | \n",
" 2355 | \n",
" 5 | \n",
" Bug's Life, A (1998) | \n",
" Animation|Children's|Comedy | \n",
" 1998 | \n",
" Female | \n",
" Under 18 | \n",
" K-12 student | \n",
"
\n",
" \n",
"
\n",
"
"
]
},
"metadata": {},
"execution_count": 10
}
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 39,
"source": [
"final_df[final_df['age'] == '18 - 24']['title'].value_counts()[:10].to_frame()"
],
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" title\n",
"American Beauty (1999) 715\n",
"Star Wars: Episode VI - Return of the Jedi (1983) 586\n",
"Star Wars: Episode V - The Empire Strikes Back ... 579\n",
"Matrix, The (1999) 567\n",
"Star Wars: Episode IV - A New Hope (1977) 562\n",
"Braveheart (1995) 544\n",
"Saving Private Ryan (1998) 543\n",
"Jurassic Park (1993) 541\n",
"Terminator 2: Judgment Day (1991) 529\n",
"Sixth Sense, The (1999) 514"
],
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" title | \n",
"
\n",
" \n",
" \n",
" \n",
" | American Beauty (1999) | \n",
" 715 | \n",
"
\n",
" \n",
" | Star Wars: Episode VI - Return of the Jedi (1983) | \n",
" 586 | \n",
"
\n",
" \n",
" | Star Wars: Episode V - The Empire Strikes Back (1980) | \n",
" 579 | \n",
"
\n",
" \n",
" | Matrix, The (1999) | \n",
" 567 | \n",
"
\n",
" \n",
" | Star Wars: Episode IV - A New Hope (1977) | \n",
" 562 | \n",
"
\n",
" \n",
" | Braveheart (1995) | \n",
" 544 | \n",
"
\n",
" \n",
" | Saving Private Ryan (1998) | \n",
" 543 | \n",
"
\n",
" \n",
" | Jurassic Park (1993) | \n",
" 541 | \n",
"
\n",
" \n",
" | Terminator 2: Judgment Day (1991) | \n",
" 529 | \n",
"
\n",
" \n",
" | Sixth Sense, The (1999) | \n",
" 514 | \n",
"
\n",
" \n",
"
\n",
"
"
]
},
"metadata": {},
"execution_count": 39
}
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 44,
"source": [
"# Print movie / user sum\r\n",
"n_movies = final_df['movieId'].nunique()\r\n",
"n_users = final_df['userId'].nunique()\r\n",
"\r\n",
"print('Number of movies:', n_movies)\r\n",
"print('Number of users:', n_users) "
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Number of movies: 3706\n",
"Number of users: 6040\n"
]
}
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 45,
"source": [
"# implement SVD with Python SurPRISE, a Python Recommendation Framework\r\n",
"\r\n",
"from surprise import Reader, Dataset, SVD, SVDpp\r\n",
"from surprise import accuracy\r\n",
"\r\n",
"reader = Reader(rating_scale=(1, 5))\r\n",
"dataset = Dataset.load_from_df(final_df[['userId', 'movieId', 'rating']], reader=reader)\r\n",
"\r\n",
"svd = SVD(n_factors=50)\r\n",
"svd_plusplus = SVDpp(n_factors=50)\r\n",
"\r\n",
"# train with SVD\r\n",
"trainset = dataset.build_full_trainset()\r\n",
"svd.fit(trainset)\r\n",
"# train with SVD++, ATTENTION this take a LONG TIME\r\n",
"# svd_plusplus.fit(trainset)\r\n"
],
"outputs": [
{
"output_type": "error",
"ename": "ModuleNotFoundError",
"evalue": "No module named 'surprise'",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;31m# implement SVD with Python SurPRISE, a Python Recommendation Framework\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[1;32mfrom\u001b[0m \u001b[0msurprise\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mReader\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mDataset\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mSVD\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mSVDpp\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 4\u001b[0m \u001b[1;32mfrom\u001b[0m \u001b[0msurprise\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0maccuracy\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;31mModuleNotFoundError\u001b[0m: No module named 'surprise'"
]
}
],
"metadata": {}
}
],
"metadata": {
"orig_nbformat": 4,
"language_info": {
"name": "python",
"version": "3.8.8",
"mimetype": "text/x-python",
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"pygments_lexer": "ipython3",
"nbconvert_exporter": "python",
"file_extension": ".py"
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3.8.8 64-bit"
},
"interpreter": {
"hash": "53e4db133e7a886bd36ef8c79c0b5519f0af174d53fdba9ad5d5d94e6d9f4b55"
}
},
"nbformat": 4,
"nbformat_minor": 2
}