diff --git "a/DF_Construction.ipynb" "b/DF_Construction.ipynb" new file mode 100644--- /dev/null +++ "b/DF_Construction.ipynb" @@ -0,0 +1,3348 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 3, + "id": "39139b70", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Import relevant libraries\n", + "import pandas as pd\n", + "import numpy as np\n", + "import json\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "import seaborn as sns\n", + "from dotenv import load_dotenv\n", + "from os import environ\n", + "import requests\n", + "from time import sleep\n", + "import re\n", + "\n", + "load_dotenv() # Read local .env file" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "2fa58a90", + "metadata": {}, + "source": [ + "# Construct Item Dataframe\n", + "\n", + "The goal here is to construct a dataframe consisting of relevant information for each movie. For this, I'll be using only one of the original csv files:\n", + "\n", + "- **movies_metadata.csv**: The main Movies Metadata file. Contains information on 45,000 movies featured in the Full MovieLens dataset. Features include posters, backdrops, budget, revenue, release dates, languages, production countries and companies.\n", + "\n", + "> Data description based on [Kaggle](https://www.kaggle.com/datasets/rounakbanik/the-movies-dataset)\n", + "\n", + "First, I'll read the csv file and list the different columns in the dataframe:" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "03fa2e2a", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\Saqi\\AppData\\Local\\Temp\\ipykernel_34392\\3934408411.py:1: DtypeWarning: Columns (10) have mixed types. Specify dtype option on import or set low_memory=False.\n", + " meta_df = pd.read_csv('./data/movies_metadata.csv')\n" + ] + }, + { + "data": { + "text/plain": [ + "Index(['adult', 'belongs_to_collection', 'budget', 'genres', 'homepage', 'id',\n", + " 'imdb_id', 'original_language', 'original_title', 'overview',\n", + " 'popularity', 'poster_path', 'production_companies',\n", + " 'production_countries', 'release_date', 'revenue', 'runtime',\n", + " 'spoken_languages', 'status', 'tagline', 'title', 'video',\n", + " 'vote_average', 'vote_count'],\n", + " dtype='object')" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "meta_df = pd.read_csv('./data/movies_metadata.csv')\n", + "meta_df.columns" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "cb5a13bf", + "metadata": {}, + "source": [ + "Relevant columsn include:\n", + "\n", + "- `adult`: Whether or not a movie has been rated adult. While not relevant in the core recommendation algorithm, it could be useful in the final result such that the user would be able to filter adult content if they so wished.\n", + "- `budget`: The movie's budget. Should a user have a preference for high-budget movies, this column could be a good indicator of that.\n", + "- `genres`: The movie genre.\n", + "- `popularity`: Movie popularity. Will be relevant for the user interface.\n", + "- `revenue`: The amount of money the movie had made. Will be more relevant later during feature engineering.\n", + "- `runtime`: How long the movie was. Could also be useful for filtering purposes.\n", + "- `status`: Whether or not the movie has been released or not.\n", + "- `vote_average`: The average vote that viewers had given this specific movie.\n", + "- `production_companies` and `production_countries`: Which company made the movie in what countries. This could be useful if a user prefers movies made by a certain company or from a certain country\n", + "\n", + "Other columns such as `id`, `imdb_id`, `title` and `overview` will be useful for descriptive purposes later on." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "464a0be1", + "metadata": {}, + "outputs": [], + "source": [ + "relevant_columns = [\"adult\", \"budget\", \"genres\", \"id\", \"imdb_id\",\n", + " \"overview\", \"popularity\", \"revenue\", \"runtime\", \"status\", \"vote_average\", \"title\", \"overview\",\n", + " \"production_companies\", \"production_countries\"]\n", + "cols_to_drop = [col for col in meta_df.columns if col not in relevant_columns]\n", + "\n", + "meta_df.drop(cols_to_drop, axis=1, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "b0b309c9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
adultbudgetgenresidimdb_idoverviewpopularityproduction_companiesproduction_countriesrevenueruntimestatustitlevote_average
0False30000000[{'id': 16, 'name': 'Animation'}, {'id': 35, '...862tt0114709Led by Woody, Andy's toys live happily in his ...21.946943[{'name': 'Pixar Animation Studios', 'id': 3}][{'iso_3166_1': 'US', 'name': 'United States o...373554033.081.0ReleasedToy Story7.7
1False65000000[{'id': 12, 'name': 'Adventure'}, {'id': 14, '...8844tt0113497When siblings Judy and Peter discover an encha...17.015539[{'name': 'TriStar Pictures', 'id': 559}, {'na...[{'iso_3166_1': 'US', 'name': 'United States o...262797249.0104.0ReleasedJumanji6.9
2False0[{'id': 10749, 'name': 'Romance'}, {'id': 35, ...15602tt0113228A family wedding reignites the ancient feud be...11.7129[{'name': 'Warner Bros.', 'id': 6194}, {'name'...[{'iso_3166_1': 'US', 'name': 'United States o...0.0101.0ReleasedGrumpier Old Men6.5
3False16000000[{'id': 35, 'name': 'Comedy'}, {'id': 18, 'nam...31357tt0114885Cheated on, mistreated and stepped on, the wom...3.859495[{'name': 'Twentieth Century Fox Film Corporat...[{'iso_3166_1': 'US', 'name': 'United States o...81452156.0127.0ReleasedWaiting to Exhale6.1
4False0[{'id': 35, 'name': 'Comedy'}]11862tt0113041Just when George Banks has recovered from his ...8.387519[{'name': 'Sandollar Productions', 'id': 5842}...[{'iso_3166_1': 'US', 'name': 'United States o...76578911.0106.0ReleasedFather of the Bride Part II5.7
\n", + "
" + ], + "text/plain": [ + " adult budget genres id \n", + "0 False 30000000 [{'id': 16, 'name': 'Animation'}, {'id': 35, '... 862 \\\n", + "1 False 65000000 [{'id': 12, 'name': 'Adventure'}, {'id': 14, '... 8844 \n", + "2 False 0 [{'id': 10749, 'name': 'Romance'}, {'id': 35, ... 15602 \n", + "3 False 16000000 [{'id': 35, 'name': 'Comedy'}, {'id': 18, 'nam... 31357 \n", + "4 False 0 [{'id': 35, 'name': 'Comedy'}] 11862 \n", + "\n", + " imdb_id overview popularity \n", + "0 tt0114709 Led by Woody, Andy's toys live happily in his ... 21.946943 \\\n", + "1 tt0113497 When siblings Judy and Peter discover an encha... 17.015539 \n", + "2 tt0113228 A family wedding reignites the ancient feud be... 11.7129 \n", + "3 tt0114885 Cheated on, mistreated and stepped on, the wom... 3.859495 \n", + "4 tt0113041 Just when George Banks has recovered from his ... 8.387519 \n", + "\n", + " production_companies \n", + "0 [{'name': 'Pixar Animation Studios', 'id': 3}] \\\n", + "1 [{'name': 'TriStar Pictures', 'id': 559}, {'na... \n", + "2 [{'name': 'Warner Bros.', 'id': 6194}, {'name'... \n", + "3 [{'name': 'Twentieth Century Fox Film Corporat... \n", + "4 [{'name': 'Sandollar Productions', 'id': 5842}... \n", + "\n", + " production_countries revenue runtime \n", + "0 [{'iso_3166_1': 'US', 'name': 'United States o... 373554033.0 81.0 \\\n", + "1 [{'iso_3166_1': 'US', 'name': 'United States o... 262797249.0 104.0 \n", + "2 [{'iso_3166_1': 'US', 'name': 'United States o... 0.0 101.0 \n", + "3 [{'iso_3166_1': 'US', 'name': 'United States o... 81452156.0 127.0 \n", + "4 [{'iso_3166_1': 'US', 'name': 'United States o... 76578911.0 106.0 \n", + "\n", + " status title vote_average \n", + "0 Released Toy Story 7.7 \n", + "1 Released Jumanji 6.9 \n", + "2 Released Grumpier Old Men 6.5 \n", + "3 Released Waiting to Exhale 6.1 \n", + "4 Released Father of the Bride Part II 5.7 " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "meta_df.head()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "0304a62e", + "metadata": {}, + "source": [ + "Now that we've obtained the filtered dataframe, I'll do routine data pre-processing, such as:\n", + "- Checking for `NaN` data and filling where necessary\n", + "- Making sure that the data in columns is \"clean\", i.e. each quantitive column has the right type and there are no strings in said numerical columns.\n", + "\n", + "#### 1. Checking for NaN data" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "987d221f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "adult 0\n", + "budget 0\n", + "genres 0\n", + "id 0\n", + "imdb_id 17\n", + "overview 954\n", + "popularity 5\n", + "production_companies 3\n", + "production_countries 3\n", + "revenue 6\n", + "runtime 263\n", + "status 87\n", + "title 6\n", + "vote_average 6\n", + "dtype: int64" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Checking for NaN\n", + "meta_df.isna().sum()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "47e0f2f7", + "metadata": {}, + "source": [ + "For filling in the gaps, I'll be using the [OMDb API](https://www.omdbapi.com/). But first things first, I'll be focusing on the `status` column, as I'll be dropping all rows dataframe-wide that do not have a `status` of released.\n", + "- As this is a movie recommendation task, there is no point in recommending movies that have not been released yet." + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "0c8cc4fb", + "metadata": {}, + "outputs": [], + "source": [ + "# Only keep movies that have not been released yet.\n", + "meta_df = meta_df[meta_df['status'] == 'Released']\n", + "meta_df.drop('status', axis=1, inplace=True) # After this, we no longer need this column" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "8dc5e293", + "metadata": {}, + "source": [ + "I'll now recalculate the number of `Nan` values per column:" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "f0ea83cf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "adult 0\n", + "budget 0\n", + "genres 0\n", + "id 0\n", + "imdb_id 15\n", + "overview 920\n", + "popularity 0\n", + "production_companies 0\n", + "production_countries 0\n", + "revenue 0\n", + "runtime 251\n", + "status 0\n", + "title 0\n", + "vote_average 0\n", + "dtype: int64" + ] + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "meta_df.isna().sum() # No change in NaN values for other columns " + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "8758b1b9", + "metadata": {}, + "source": [ + "Here, I'll fill in the missing information as best I can with the OMDb API. I've put an .env file in the same location as this notebook containing my OMDb API key. For making the API requests, I'm using the incredibly well-known [requests](https://pypi.org/project/requests/) package.\n", + "\n", + "I'll first load that, and then define a function for fetching movies based on titles or IMDB ids:" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "29d0f249", + "metadata": {}, + "outputs": [], + "source": [ + "api_key = environ.get('OMDB_KEY')\n", + "\n", + "# We can either pass in imdb_id or movie title\n", + "def make_omdb_req(identifier, is_imdb_id=True):\n", + " if is_imdb_id:\n", + " query = f\"i={identifier}\"\n", + " else:\n", + " identifier = identifier.replace(\" \", \"\\ \")\n", + " query = f\"t={identifier}\"\n", + " \n", + " url = f\"http://www.omdbapi.com/?apikey={api_key}&{query}&type=movie\"\n", + " res = requests.get(url)\n", + " try:\n", + " if res.status_code == 200:\n", + " return json.loads(res.content)\n", + " except:\n", + " pass\n", + " return {'Response': 'False'}" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "d4998ee2", + "metadata": {}, + "source": [ + "We can then test this for the first element in `meta_df`:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "a3a6c508", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Title': 'Toy Story',\n", + " 'Year': '1995',\n", + " 'Rated': 'G',\n", + " 'Released': '25 Nov 1995',\n", + " 'Runtime': '81 min',\n", + " 'Genre': 'Animation, Adventure, Comedy',\n", + " 'Director': 'John Lasseter',\n", + " 'Writer': 'John Lasseter, Pete Docter, Andrew Stanton',\n", + " 'Actors': 'Tom Hanks, Tim Allen, Don Rickles',\n", + " 'Plot': \"A cowboy doll is profoundly threatened and jealous when a new spaceman action figure supplants him as top toy in a boy's bedroom.\",\n", + " 'Language': 'English',\n", + " 'Country': 'United States, Japan',\n", + " 'Awards': 'Nominated for 3 Oscars. 29 wins & 23 nominations total',\n", + " 'Poster': 'https://m.media-amazon.com/images/M/MV5BMDU2ZWJlMjktMTRhMy00ZTA5LWEzNDgtYmNmZTEwZTViZWJkXkEyXkFqcGdeQXVyNDQ2OTk4MzI@._V1_SX300.jpg',\n", + " 'Ratings': [{'Source': 'Internet Movie Database', 'Value': '8.3/10'},\n", + " {'Source': 'Rotten Tomatoes', 'Value': '100%'},\n", + " {'Source': 'Metacritic', 'Value': '96/100'}],\n", + " 'Metascore': '96',\n", + " 'imdbRating': '8.3',\n", + " 'imdbVotes': '1,018,595',\n", + " 'imdbID': 'tt0114709',\n", + " 'Type': 'movie',\n", + " 'DVD': '23 Mar 2010',\n", + " 'BoxOffice': '$223,225,679',\n", + " 'Production': 'N/A',\n", + " 'Website': 'N/A',\n", + " 'Response': 'True'}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "test_id = meta_df[\"imdb_id\"][0]\n", + "make_omdb_req(test_id)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "706d238f", + "metadata": {}, + "source": [ + "I'll now iterate over all rows with `NaN` values and fill in the gaps accordingly:\n", + "\n", + "> A thing to note here is that only the `imdb_id`, `overview` and `runtime` columns have missing data, so for each row, I only need to check these three columns." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d944b1ea", + "metadata": {}, + "outputs": [], + "source": [ + "invalid_movies = [] # For movies not found using the API" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "6b25b861", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Row with ID 30146 not found!\n", + "Row with ID 65256 not found!\n", + "Row with ID 342011 not found!\n", + "Row with ID 391438 not found!\n", + "Row with ID 416569 not found!\n", + "Row with ID 109861 not found!\n", + "Row with ID 362617 not found!\n", + "Row with ID 227964 not found!\n", + "Row with ID 342684 not found!\n", + "Row with ID 359413 not found!\n", + "Row with ID 77564 not found!\n", + "Row with ID 327909 not found!\n", + "Row with ID 449863 not found!\n", + "Row with ID 142478 not found!\n", + "Row with ID 41663 not found!\n", + "Row with ID 185180 not found!\n", + "Row with ID 428950 not found!\n", + "Row with ID 440508 not found!\n", + "Row with ID 152100 not found!\n", + "Row with ID 167330 not found!\n", + "Row with ID 220669 not found!\n", + "Row with ID 240992 not found!\n", + "Row with ID 38547 not found!\n", + "Row with ID 366759 not found!\n", + "Row with ID 148697 not found!\n", + "Row with ID 49833 not found!\n", + "Row with ID 452606 not found!\n", + "Row with ID 65010 not found!\n", + "Row with ID 101217 not found!\n", + "Row with ID 236053 not found!\n", + "Row with ID 123592 not found!\n", + "Row with ID 109671 not found!\n", + "Row with ID 327935 not found!\n", + "Row with ID 123601 not found!\n", + "Row with ID 123611 not found!\n", + "Row with ID 453596 not found!\n", + "Row with ID 142802 not found!\n", + "Row with ID 77534 not found!\n", + "Row with ID 143883 not found!\n", + "Row with ID 354133 not found!\n", + "Row with ID 191486 not found!\n", + "Row with ID 127803 not found!\n", + "Row with ID 271495 not found!\n", + "Row with ID 244575 not found!\n", + "Row with ID 246438 not found!\n", + "Row with ID 362844 not found!\n", + "Row with ID 36264 not found!\n", + "Row with ID 270908 not found!\n", + "Row with ID 14210 not found!\n", + "Row with ID 376934 not found!\n", + "Row with ID 213321 not found!\n", + "Row with ID 380438 not found!\n", + "Row with ID 41493 not found!\n", + "Row with ID 452922 not found!\n", + "Row with ID 93461 not found!\n", + "Row with ID 63838 not found!\n", + "Row with ID 197057 not found!\n", + "Row with ID 143005 not found!\n", + "Row with ID 336484 not found!\n", + "Row with ID 159810 not found!\n", + "Row with ID 51275 not found!\n", + "Row with ID 420481 not found!\n", + "Row with ID 69976 not found!\n", + "Row with ID 26792 not found!\n", + "Row with ID 37603 not found!\n", + "Row with ID 48209 not found!\n", + "Row with ID 57382 not found!\n", + "Row with ID 110131 not found!\n", + "Row with ID 41689 not found!\n", + "Row with ID 458808 not found!\n", + "Row with ID 400552 not found!\n", + "Row with ID 419601 not found!\n", + "Row with ID 14644 not found!\n", + "Row with ID 82495 not found!\n", + "Row with ID 64827 not found!\n", + "Row with ID 103301 not found!\n", + "Row with ID 301876 not found!\n", + "Row with ID 73545 not found!\n", + "Row with ID 448879 not found!\n", + "Row with ID 457307 not found!\n", + "Row with ID 396987 not found!\n", + "Row with ID 153561 not found!\n", + "Row with ID 366860 not found!\n", + "Row with ID 202865 not found!\n", + "Row with ID 9765 not found!\n", + "Row with ID 213683 not found!\n", + "Row with ID 57770 not found!\n", + "Row with ID 142320 not found!\n", + "Row with ID 430058 not found!\n", + "Row with ID 54309 not found!\n", + "Row with ID 445840 not found!\n", + "Row with ID 64043 not found!\n", + "Row with ID 73649 not found!\n", + "Row with ID 57996 not found!\n", + "Row with ID 63179 not found!\n", + "Row with ID 398295 not found!\n", + "Row with ID 353713 not found!\n", + "Row with ID 458335 not found!\n", + "Row with ID 298207 not found!\n", + "Row with ID 382995 not found!\n", + "Row with ID 439314 not found!\n", + "Row with ID 422005 not found!\n", + "Row with ID 26969 not found!\n", + "Row with ID 91673 not found!\n", + "Row with ID 68063 not found!\n", + "Row with ID 103344 not found!\n", + "Row with ID 275272 not found!\n", + "Row with ID 231216 not found!\n", + "Row with ID 79343 not found!\n", + "Row with ID 418757 not found!\n", + "Row with ID 369444 not found!\n", + "Row with ID 395767 not found!\n", + "Row with ID 199887 not found!\n", + "Row with ID 317389 not found!\n", + "Row with ID 468707 not found!\n", + "Row with ID 280422 not found!\n", + "Row with ID 449131 not found!\n" + ] + } + ], + "source": [ + "for idx, row in meta_df[meta_df.isnull().any(axis=1)].iterrows():\n", + "\n", + " # No use in re-trying movies that don't exist in OMDb's database\n", + " if row['id'] in invalid_movies:\n", + " continue\n", + "\n", + " # First fetch row data from API\n", + " api_data = make_omdb_req(row['title'], False)\n", + "\n", + " # Movie not found\n", + " if api_data['Response'] == 'False':\n", + " # Try and make request with imdb_id, if it exists\n", + " api_data = make_omdb_req(row['imdb_id'])\n", + "\n", + " # If movie still not found\n", + " if api_data['Response'] == 'False':\n", + " print(f\"Row with ID {row['id']} not found!\")\n", + " invalid_movies.append(row['id'])\n", + " continue\n", + " \n", + " # If API res was okay, start filling in data:\n", + " if pd.isnull(row['overview']):\n", + " row[\"overview\"] = api_data['Plot']\n", + " \n", + " if pd.isnull(row['imdb_id']):\n", + " row['imdb_id'] = api_data['imdbID']\n", + "\n", + " if pd.isnull(row['runtime']):\n", + " # API data needs to be parsed; response has a \"min\" at the end but runtime col is float\n", + " try:\n", + " row['runtime'] = float(api_data['Runtime'].replace('min', ''))\n", + " except:\n", + " pass\n", + "\n", + " meta_df[meta_df['id'] == row['id']] = row\n", + "\n", + " # To avoid sending too many requests to the API at once\n", + " sleep(1.0)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "d06e3248", + "metadata": {}, + "source": [ + "Now, I'll check to see if there are any more `NaN` values left:" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "1b245bbc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "adult 0\n", + "budget 0\n", + "genres 0\n", + "id 0\n", + "imdb_id 6\n", + "overview 114\n", + "popularity 0\n", + "production_companies 0\n", + "production_countries 0\n", + "revenue 0\n", + "runtime 51\n", + "status 0\n", + "title 0\n", + "vote_average 0\n", + "dtype: int64" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "meta_df.isna().sum()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "41edb363", + "metadata": {}, + "source": [ + "The remaining `NaN` values will be handled as such:\n", + "- `imdb_id`: `NaN` values will be replaced with `-1`, to indicate that this movie has no imdb id.\n", + "- `overview`: `NaN` values will be replaced with \"No description available\".\n", + "- `runtime`: `NaN` values will be replaced with _average_ runtime." + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "06d8691a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "adult 0\n", + "budget 0\n", + "genres 0\n", + "id 0\n", + "imdb_id 0\n", + "overview 0\n", + "popularity 0\n", + "production_companies 0\n", + "production_countries 0\n", + "revenue 0\n", + "runtime 0\n", + "status 0\n", + "title 0\n", + "vote_average 0\n", + "dtype: int64" + ] + }, + "execution_count": 79, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "meta_df['imdb_id'] = meta_df['imdb_id'].fillna(-1)\n", + "meta_df['overview'] = meta_df['overview'].fillna(\"No description available\")\n", + "meta_df['runtime'] = meta_df['runtime'].fillna(meta_df['runtime'].median())\n", + "\n", + "meta_df.isna().sum()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "a2bbae24", + "metadata": {}, + "source": [ + "#### 2. Making sure data is clean\n", + "\n", + "An issue that exists in this data is that while there are columns comprising solely of numerical data, these columns are sometimes dirty in the sense that there may be erronous string data in that specific column that prevents us from using that column properly. Here, I'll iterate over all of the numerical and boolean columns and make sure they've been casted to the correct type.\n", + "\n", + "> The columns I'll be checking are: `adult` (bool), `budget` (int), `popularity` (float), `revenue` (int), `runtime` (float), `vote_average` (float)" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "45c516bc", + "metadata": {}, + "outputs": [], + "source": [ + "meta_df['adult'] = meta_df['adult'].astype(bool)\n", + "meta_df['budget'] = meta_df['budget'].astype(int)\n", + "meta_df['popularity'] = meta_df['popularity'].astype(float)\n", + "meta_df['revenue'] = meta_df['revenue'].astype(int)\n", + "meta_df['runtime'] = meta_df['runtime'].astype(float)\n", + "meta_df['vote_average'] = meta_df['vote_average'].astype(float)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "3b7593f7", + "metadata": {}, + "source": [ + "---\n", + "\n", + "### Additional Feature Engineering\n", + "\n", + "#### Genre column" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "819aa271", + "metadata": {}, + "source": [ + "Here, I'll reconstruct the `meta_df` dataframe such that for each possible genre a movie could have, there is a column. If that movie falls under that specific genre, the value of the respective column is `1`, otherwise the value of that specific column in that specific row is `0`\n", + "\n", + "Next is to make a separate column for each genre. For this, I'll first need a list of all genres.\n", + "\n", + "What's important to note here is that elements in the genre column are json strings, thus I'll be using the `json` module to properly parse the string." + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "id": "4b33d433", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['Animation', 'Comedy', 'Family', 'Adventure', 'Fantasy', 'Romance', 'Drama', 'Action', 'Crime', 'Thriller', 'Horror', 'History', 'Science Fiction', 'Mystery', 'War', 'Foreign', 'Music', 'Documentary', 'Western', 'TV Movie']\n" + ] + } + ], + "source": [ + "\n", + "genre_col = meta_df['genres'].values.tolist() # Get list of every element in the genres column\n", + "\n", + "# Iterate over every element, generate list of *unique* genres.\n", + "genres = []\n", + "for item in genre_col:\n", + " item_gs = json.loads(item.replace('\\'', '\"')) # json.loads expects double quotes (\") for property names\n", + " for g in item_gs:\n", + " if g['name'] not in genres:\n", + " genres.append(g['name'])\n", + "\n", + "print(genres)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "b5a8a1c2", + "metadata": {}, + "source": [ + "Obviously, some data cleaning is needed here. Thankfully, there aren't very many candidate \"genres\", so I'll filter them by hand;" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "id": "9b43758f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idgenresAnimationComedyFamilyAdventureFantasyRomanceDramaAction...HorrorHistoryScience FictionMysteryWarForeignMusicDocumentaryWesternTV Movie
0862[{'id': 16, 'name': 'Animation'}, {'id': 35, '...11100000...0000000000
18844[{'id': 12, 'name': 'Adventure'}, {'id': 14, '...00111000...0000000000
215602[{'id': 10749, 'name': 'Romance'}, {'id': 35, ...01000100...0000000000
331357[{'id': 35, 'name': 'Comedy'}, {'id': 18, 'nam...01000110...0000000000
411862[{'id': 35, 'name': 'Comedy'}]01000000...0000000000
\n", + "

5 rows × 22 columns

\n", + "
" + ], + "text/plain": [ + " id genres Animation \n", + "0 862 [{'id': 16, 'name': 'Animation'}, {'id': 35, '... 1 \\\n", + "1 8844 [{'id': 12, 'name': 'Adventure'}, {'id': 14, '... 0 \n", + "2 15602 [{'id': 10749, 'name': 'Romance'}, {'id': 35, ... 0 \n", + "3 31357 [{'id': 35, 'name': 'Comedy'}, {'id': 18, 'nam... 0 \n", + "4 11862 [{'id': 35, 'name': 'Comedy'}] 0 \n", + "\n", + " Comedy Family Adventure Fantasy Romance Drama Action ... Horror \n", + "0 1 1 0 0 0 0 0 ... 0 \\\n", + "1 0 1 1 1 0 0 0 ... 0 \n", + "2 1 0 0 0 1 0 0 ... 0 \n", + "3 1 0 0 0 1 1 0 ... 0 \n", + "4 1 0 0 0 0 0 0 ... 0 \n", + "\n", + " History Science Fiction Mystery War Foreign Music Documentary \n", + "0 0 0 0 0 0 0 0 \\\n", + "1 0 0 0 0 0 0 0 \n", + "2 0 0 0 0 0 0 0 \n", + "3 0 0 0 0 0 0 0 \n", + "4 0 0 0 0 0 0 0 \n", + "\n", + " Western TV Movie \n", + "0 0 0 \n", + "1 0 0 \n", + "2 0 0 \n", + "3 0 0 \n", + "4 0 0 \n", + "\n", + "[5 rows x 22 columns]" + ] + }, + "execution_count": 97, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "genres = ['Animation', 'Comedy', 'Family', 'Adventure', 'Fantasy', 'Romance', 'Drama', 'Action', 'Crime', 'Thriller', 'Horror', 'History', 'Science Fiction', 'Mystery', 'War', 'Foreign', 'Music', 'Documentary', 'Western', 'TV Movie']\n", + "\n", + "# Only get a copy of relevant columns\n", + "genre_df = meta_df[['id', 'genres']].copy()\n", + "\n", + "# Make a column for every genre\n", + "for genre in genres:\n", + " genre_df[genre] = 0\n", + "\n", + "# Iterate over every row and set genres accordingly:\n", + "for idx, row in genre_df.iterrows():\n", + " g_json = json.loads(row['genres'].replace('\\'', '\"')) \n", + " for g in g_json:\n", + " if g['name'] in genres:\n", + " genre_df.at[idx, g['name']] = 1\n", + "\n", + "genre_df.head()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "2a84ca58", + "metadata": {}, + "source": [ + "Now not only can we safely merge `meta_df` and `genre_df` to contruct the new dataframe with the genre columns, but we can also safely drop the `genres` column as it is no longer needed:" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "id": "21afdde2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
adultbudgetidimdb_idoverviewpopularityproduction_companiesproduction_countriesrevenueruntime...HorrorHistoryScience FictionMysteryWarForeignMusicDocumentaryWesternTV Movie
0True30000000862tt0114709Led by Woody, Andy's toys live happily in his ...21.946943[{'name': 'Pixar Animation Studios', 'id': 3}][{'iso_3166_1': 'US', 'name': 'United States o...37355403381.0...0000000000
1True650000008844tt0113497When siblings Judy and Peter discover an encha...17.015539[{'name': 'TriStar Pictures', 'id': 559}, {'na...[{'iso_3166_1': 'US', 'name': 'United States o...262797249104.0...0000000000
2True015602tt0113228A family wedding reignites the ancient feud be...11.712900[{'name': 'Warner Bros.', 'id': 6194}, {'name'...[{'iso_3166_1': 'US', 'name': 'United States o...0101.0...0000000000
3True1600000031357tt0114885Cheated on, mistreated and stepped on, the wom...3.859495[{'name': 'Twentieth Century Fox Film Corporat...[{'iso_3166_1': 'US', 'name': 'United States o...81452156127.0...0000000000
4True011862tt0113041Just when George Banks has recovered from his ...8.387519[{'name': 'Sandollar Productions', 'id': 5842}...[{'iso_3166_1': 'US', 'name': 'United States o...76578911106.0...0000000000
\n", + "

5 rows × 32 columns

\n", + "
" + ], + "text/plain": [ + " adult budget id imdb_id \n", + "0 True 30000000 862 tt0114709 \\\n", + "1 True 65000000 8844 tt0113497 \n", + "2 True 0 15602 tt0113228 \n", + "3 True 16000000 31357 tt0114885 \n", + "4 True 0 11862 tt0113041 \n", + "\n", + " overview popularity \n", + "0 Led by Woody, Andy's toys live happily in his ... 21.946943 \\\n", + "1 When siblings Judy and Peter discover an encha... 17.015539 \n", + "2 A family wedding reignites the ancient feud be... 11.712900 \n", + "3 Cheated on, mistreated and stepped on, the wom... 3.859495 \n", + "4 Just when George Banks has recovered from his ... 8.387519 \n", + "\n", + " production_companies \n", + "0 [{'name': 'Pixar Animation Studios', 'id': 3}] \\\n", + "1 [{'name': 'TriStar Pictures', 'id': 559}, {'na... \n", + "2 [{'name': 'Warner Bros.', 'id': 6194}, {'name'... \n", + "3 [{'name': 'Twentieth Century Fox Film Corporat... \n", + "4 [{'name': 'Sandollar Productions', 'id': 5842}... \n", + "\n", + " production_countries revenue runtime ... \n", + "0 [{'iso_3166_1': 'US', 'name': 'United States o... 373554033 81.0 ... \\\n", + "1 [{'iso_3166_1': 'US', 'name': 'United States o... 262797249 104.0 ... \n", + "2 [{'iso_3166_1': 'US', 'name': 'United States o... 0 101.0 ... \n", + "3 [{'iso_3166_1': 'US', 'name': 'United States o... 81452156 127.0 ... \n", + "4 [{'iso_3166_1': 'US', 'name': 'United States o... 76578911 106.0 ... \n", + "\n", + " Horror History Science Fiction Mystery War Foreign Music Documentary \n", + "0 0 0 0 0 0 0 0 0 \\\n", + "1 0 0 0 0 0 0 0 0 \n", + "2 0 0 0 0 0 0 0 0 \n", + "3 0 0 0 0 0 0 0 0 \n", + "4 0 0 0 0 0 0 0 0 \n", + "\n", + " Western TV Movie \n", + "0 0 0 \n", + "1 0 0 \n", + "2 0 0 \n", + "3 0 0 \n", + "4 0 0 \n", + "\n", + "[5 rows x 32 columns]" + ] + }, + "execution_count": 98, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "meta_df = pd.merge(meta_df, genre_df)\n", + "meta_df.drop('genres', axis=1, inplace=True)\n", + "meta_df.head()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "f5f329bc", + "metadata": {}, + "source": [ + "#### Extracting useful information from revenue and budget\n", + "\n", + "As noted previously, the `revenue` and `budget` column represent the movie's total revenue upon release and the budget for the movie's production respectively. \n", + "\n", + "On their own, they might not be incredibly useful in the final recommender system, however, combining the two might yield useful information. For instance, I could add an extra column with the ratio between a movie's revenue vs. it's budget.\n", + "\n", + "- If this ratio is greater than 1, this indicated that a production studio has made a return on their investment. The higher this number, the better the movie did relative to it's production budget. If the revenue is less than the budget, pushing this ratio to be less than 1, that would be an indicator that the movie did not do very well in the box office.\n", + "\n", + "However, before I do that I need to check the values for `revenue` and budget to make sure no values for these columns are zero." + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "4e06d20a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
revenuebudget
count4.507400e+044.507400e+04
mean1.120213e+074.265201e+06
std6.407433e+071.749784e+07
min-2.147484e+090.000000e+00
25%0.000000e+000.000000e+00
50%0.000000e+000.000000e+00
75%0.000000e+000.000000e+00
max2.068224e+093.800000e+08
\n", + "
" + ], + "text/plain": [ + " revenue budget\n", + "count 4.507400e+04 4.507400e+04\n", + "mean 1.120213e+07 4.265201e+06\n", + "std 6.407433e+07 1.749784e+07\n", + "min -2.147484e+09 0.000000e+00\n", + "25% 0.000000e+00 0.000000e+00\n", + "50% 0.000000e+00 0.000000e+00\n", + "75% 0.000000e+00 0.000000e+00\n", + "max 2.068224e+09 3.800000e+08" + ] + }, + "execution_count": 100, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "meta_df[['revenue', 'budget']].describe()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "33ffa8e8", + "metadata": {}, + "source": [ + "This is bad, as there are movies with no budget, or no revenue (or both) recorded. A quick (albeit debatably \"dirty\") solution to this is to group by genre for _budget_ (as say, a fantasy movie is likely to need more of a budget than a drama or romance movie), and then by popularity for revenue.\n", + "\n", + "> However, we'll need to bin popularity values first, as the popularity column is continuous, and the possible values are too large to group the values properly. For this, I'll use [Pandas's qcut](https://pandas.pydata.org/docs/reference/api/pandas.qcut.html) to bin values based on sample quantiles.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 112, + "id": "b8014aaf", + "metadata": {}, + "outputs": [], + "source": [ + "# Bin popularity\n", + "meta_df['pop_bin'] = pd.qcut(meta_df['popularity'], q=10, labels=[i for i in range(10)])" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "e3796f09", + "metadata": {}, + "source": [ + "Next, I'll bring back the genre column from before, and pick the first genre in the list in the original genre column. For this, I'll reload the old unprocessed `movies_metadata.csv`." + ] + }, + { + "cell_type": "code", + "execution_count": 196, + "id": "760b33d0", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\Saqi\\AppData\\Local\\Temp\\ipykernel_34392\\1858458685.py:1: DtypeWarning: Columns (10) have mixed types. Specify dtype option on import or set low_memory=False.\n", + " old_meta_df = pd.read_csv('./data/movies_metadata.csv')\n" + ] + } + ], + "source": [ + "old_meta_df = pd.read_csv('./data/movies_metadata.csv')\n", + "\n", + "meta_df['main_genre'] = \"Unknown\"\n", + "\n", + "\n", + "for idx, row in meta_df.iterrows():\n", + " org_row = old_meta_df[old_meta_df['id'] == str(row['id'])]\n", + " old_gen = str(org_row['genres'].values[0].replace(\"'\", '\"'))\n", + " try:\n", + " meta_df.at[idx, 'main_genre'] = json.loads(old_gen)[0]['name']\n", + " except:\n", + " pass\n" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "9bab8729", + "metadata": {}, + "source": [ + "I'll quickly illustrate the difference in budget and revenue based on popularity using simple barplots;" + ] + }, + { + "cell_type": "code", + "execution_count": 233, + "id": "d5c4a46f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 233, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAGwCAYAAABVdURTAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAyu0lEQVR4nO3df1RUdcLH8c8w8sNfoIWAEsparj9SwSAI+2FbFJseXffZjFwNws196tFdddY2yZS0ktotok2SNKnW1qTMdi1L16Zf6yNFQbpSZqulkAXIsUAxoWbm+aPT7MOKNnMZvXB7v8655zjf+d7L5261fLz3O3NtHo/HIwAAAIsIMjsAAABAIFFuAACApVBuAACApVBuAACApVBuAACApVBuAACApVBuAACApXQzO8CZ5na79dlnn6l3796y2WxmxwEAAD7weDw6cuSIBgwYoKCgU1+b+cGVm88++0xxcXFmxwAAAAbU1NTonHPOOeWcH1y56d27t6Rv/8cJDw83OQ0AAPBFU1OT4uLivL/HT+UHV26+uxUVHh5OuQEAoIvxZUkJC4oBAIClUG4AAIClUG4AAIClUG4AAIClUG4AAIClUG4AAIClUG4AAIClUG4AAICldIpyU1RUpPj4eIWFhSk1NVXl5eWnnF9YWKihQ4eqe/fuiouL07x583T8+PEzlBYAAHRmppeb0tJSORwO5eXlqbKyUgkJCcrIyFB9fX2789euXasFCxYoLy9Pu3fv1urVq1VaWqrbb7/9DCcHAACdkenlpqCgQDNnzlROTo5GjBih4uJi9ejRQyUlJe3O3759uy6++GL98pe/VHx8vK6++mpNnTr1e6/2AACAHwZTy01ra6sqKiqUnp7uHQsKClJ6errKysra3Wfs2LGqqKjwlpmPP/5YL730ksaPH9/u/JaWFjU1NbXZAACAdZn64MyGhga5XC5FR0e3GY+OjtaHH37Y7j6//OUv1dDQoEsuuUQej0fffPONbr755pPelsrPz9eSJUsCnh0AAHROpt+W8tfrr7+uZcuW6ZFHHlFlZaU2bNigTZs26a677mp3fm5urhobG71bTU3NGU4MAADOJFOv3ERGRsput6uurq7NeF1dnWJiYtrdZ9GiRbrhhht00003SZJGjRql5uZm/frXv9bChQsVFNS2r4WGhio0NPT0nAAAAOh0TC03ISEhSkpK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hw4K0nnnnafExET97ne/07XXXqv8/Px254aGhio8PLzNBgAArMtQudm8ebMeeeQRDR8+3Ds2YsQIFRUV6eWXX/b5OCEhIUpKSpLT6fSOud1uOZ1OpaWl+Xwct9vdZl0NAAD44TL8+IX/fFim9O0X/Pn7aAaHw6Hs7GwlJycrJSVFhYWFam5u9n56KisrS7Gxsd4rM/n5+UpOTta5556rlpYWvfTSS1qzZo1WrFhh5FQAAIDFGCo3V1xxhebMmaOnn35aAwYMkCQdPHhQ8+bN05VXXunXsTIzM3Xo0CEtXrxYtbW1SkxM1ObNm72LjKurqxUU9O8LTM3Nzfqf//kfffrpp+revbuGDRump556SpmZmUZOBQAAWIzN4/F4/N2ppqZGkyZN0vvvv+9dnFtdXa1Ro0Zp48aNOueccwIeNFCampoUERGhxsZG1t8AAH7QEtZvMTvCSe28tu131/nz+9vQlZu4uDhVVlbK6XR6Pwo+fPhwpaenGzkcAABAwBj+nptXX31Vr776qurr6+V2u/Xee+9p7dq1kqSSkpKABQQAAPCHoXKzZMkSLV26VMnJyerfv79sNlugcwEAABhiqNwUFxfriSee0A033BDoPAAAAB1i6HtuWltbNXbs2EBnAQAA6DBD5eamm27yrq8BAADoTAzdljp+/LhWrlypV155RaNHjz7hC/0KCgoCEg4AAMBfhsrNP//5TyUmJkqSqqqq2rzH4mIAAGAmQ+XmtddeC3QOAACAgDC05gYAAKCzotwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABLodwAAABL6RTlpqioSPHx8QoLC1NqaqrKy8tPOnfVqlW69NJL1bdvX/Xt21fp6emnnA8AAH5YTC83paWlcjgcysvLU2VlpRISEpSRkaH6+vp257/++uuaOnWqXnvtNZWVlSkuLk5XX321Dh48eIaTAwCAzsjm8Xg8ZgZITU3VhRdeqOXLl0uS3G634uLi9Jvf/EYLFiz43v1dLpf69u2r5cuXKysr63vnNzU1KSIiQo2NjQoPD+9wfgAAuqqE9VvM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mlFuAHRqTz75pLp166by8nI99NBDKigo0GOPPXbS+UuWLNF1112nf/7znxo/frymTZumw4cPS5IWLVqkDz74QC+//LJ2796tFStWKDIy8kydCoAzhNtSADq1uLg4Pfjgg7LZbBo6dKh27dqlBx98UDNnzmx3/o033qipU6dKkpYtW6Y//elPKi8v109/+lNVV1drzJgxSk5OlvTtlR8A1sOVGwCd2kUXXSSbzeZ9nZaWpn/9619yuVztzh89erT3zz179lR4eLjq6+slSbfccovWrVunxMRE/f73v9f27dtPb3gApqDcALCU4ODgNq9tNpvcbrck6ZprrtGBAwc0b948ffbZZ7ryyis1f/58M2ICOI0oNwA6tbfffrvN67feektDhgyR3W43dLx+/fopOztbTz31lAoLC7Vy5cpAxATQibDmBkCnVl1dLYfDof/+7/9WZWWlHn74YT3wwAOGjrV48WIlJSXp/PPPV0tLi1588UUNHz48wIkBmI1yA6BTy8rK0ldffaWUlBTZ7XbNmTNHv/71rw0dKyQkRLm5udq/f7+6d++uSy+9VOvWrQtwYgBms3m++8IIAAAAC2DNDQAAsBTKDQAAsBTKDQAAsBTKDQAAsBTKDQAAsBTKDQAAsBTKDQAAsBTKDQAAsBTKDQAAsBTKDQAAsBTKDQAAsJT/AylzLaehRfT+AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Difference in revenue vs. popularity bins\n", + "pop_df = pd.DataFrame({\"means\": meta_df.groupby(meta_df['pop_bin'])['revenue'].mean(), \"bins\": [i for i in range(10)]}) # Will be useful later\n", + "pop_df['normalized_means'] = pop_df['means']/pop_df['means'].sum()\n", + "sns.barplot(data=pop_df, x='bins', y='normalized_means')\n" + ] + }, + { + "cell_type": "code", + "execution_count": 235, + "id": "c30759c4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,\n", + " 17, 18, 19, 20]),\n", + " [Text(0, 0, 'Animation'),\n", + " Text(1, 0, 'Adventure'),\n", + " Text(2, 0, 'Romance'),\n", + " Text(3, 0, 'Comedy'),\n", + " Text(4, 0, 'Action'),\n", + " Text(5, 0, 'Family'),\n", + " Text(6, 0, 'History'),\n", + " Text(7, 0, 'Drama'),\n", + " Text(8, 0, 'Crime'),\n", + " Text(9, 0, 'Fantasy'),\n", + " Text(10, 0, 'Science Fiction'),\n", + " Text(11, 0, 'Unknown'),\n", + " Text(12, 0, 'Thriller'),\n", + " Text(13, 0, 'Music'),\n", + " Text(14, 0, 'Horror'),\n", + " Text(15, 0, 'Documentary'),\n", + " Text(16, 0, 'Mystery'),\n", + " Text(17, 0, 'Western'),\n", + " Text(18, 0, 'TV Movie'),\n", + " Text(19, 0, 'War'),\n", + " Text(20, 0, 'Foreign')])" + ] + }, + "execution_count": 235, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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bi5lbmUa50GitxnnQSKi8kqr8yCtW7cOly9fhpGREdzd3dG8eXOYm5sjKysL69atUztpIiIiorJArQJp8ODBaN68OX777Tel9rS0NEU/HyIiIqLySu2ZtGfMmIGBAwdi+vTpEqZDREREpHtqddIGoFhmpGfPnrhw4QLWr18vZV5Uiq4v6yFJnFojf5ckDhERka6pdQZJJpMBAFq2bInjx4/jxo0baN26NW7fvi1lbkREREQ6oVaB9OrANycnJxw9ehTOzs547733JEuMiIiISFfUKpCmTZumNKTfzMwM27Ztw9ixYwtdgoSIiIioPFGrD9K0adMKbZ8xY4ZGyRARERGVBcUukHbs2IHOnTvD0NAQO3YUPTGiTCZDt27dJEmOiIiISBeKXSD5+/sjMTERVapUgb+/f5H7yWQy5OXlSZEbERERkU4Uu0CSy+WF/kxERET0plF7okgiIiKiN1WxzyAtWbKk2EE///xztZIhIiIiKguKXSAtWrSoWPvJZDIWSERERFSuFbtASkhI0GYeRERERGUG+yARERERqVB7sdr79+9jx44duHv3LnJycpRuW7hwocaJERERlcTayMeSxBnUq7Ikcah8U6tAio6ORvfu3eHq6oorV66gfv36uH37NoQQaNKkidQ5EhEREZUqtS6xTZo0CePGjcP58+dhYmKC3377Dffu3YOXlxf69OkjdY5EREREpUqtAuny5csIDAwEABgYGOD58+cwNzfHzJkz8e2330qaIBEREVFpU6tAqlChgqLfkYODA27evKm47cmTJ9JkRkRERKQjavVBatmyJQ4fPgw3Nzd06dIFX3zxBc6fP4/IyEi0bNlS6hyJiIiISpVaBdLChQuRkZEBAJgxYwYyMjKwefNm1KpViyPYiIiIqNxTq0BydXVV/FyhQgWsWLFCsoSIiIiIdE3teZDyZWRkQC6XK7VZWlpqGpaIiIhIZ9TqpJ2QkICuXbuiQoUKsLKygo2NDWxsbGBtbQ0bGxupcyQiIiIqVWqdQRowYACEEAgPD4ednR1kMpnUeRERERHpjFoF0tmzZxEXF4c6depInQ8RERGRzql1ia1Zs2a4d++e1LkQERERlQlqnUFatWoVPvnkEzx48AD169eHoaGh0u0eHh6SJEdERESkC2oVSI8fP8bNmzcRFBSkaJPJZBBCQCaTIS8vT7IEiYiIiEqb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bNiwoUR5EhER0dtB55fYiIiIiMoaFkhEREREKlggEREREalggURERESkggUSERERkQoWSEREREQqWCARERERqWCBRERERKSCBRIRERGRijIxkzaRuraHd5Yslv+QvySLRURUEhdWJkkWq/7HdpLFepvxDBIRERGRChZIRERERCpYIBERERGpYIFEREREpIKdtImIiEhtSWEnJYtlN6aZZLE0xTNIRERERCpYIBERERGpYIFEREREpIIFEhEREZEKFkhEREREKlggEREREalggURERESkgvMgEdFb5f3f1kgW64/eQZLFIqKyhWeQiIiIiFSwQCIiIiJSwUtspFWHfnpfsljvDvtDslhERESvwzNIRERERCpYIBERERGpYIFEREREpIJ9kIiICADQ+7eTksX6rXczyWIR6QLPIBERERGpYIFEREREpIIFEhEREZEKFkhEREREKlggEREREakoE6PYli9fju+++w6JiYlo2LAhli5diubNmxe5/5YtWzBlyhTcvn0btWrVwrfffosuXboAAHJzczF58mTs2rULt27dgpWVFXx8fDB37lxUrVpVEcPZ2Rl37txRihsaGoqJEydq50ESqfh2k69ksb7st1uyWEREVAbOIG3evBkhISGYNm0a4uPj0bBhQ/j6+iI5ObnQ/Y8ePYr+/fsjODgYp0+fhr+/P/z9/XHhwgUAQGZmJuLj4zFlyhTEx8cjMjISV69eRffu3QvEmjlzJh49eqTYRo0apdXHSkREROWDzgukhQsXYtiwYQgKCoK7uztWrFgBMzMzhIeHF7r/4sWL4efnh/Hjx8PNzQ2zZs1CkyZNsGzZMgCAlZUV9uzZgw8//BB16tRBy5YtsWzZMsTFxeHu3btKsSwsLGBvb6/YKlSooPXHS0RERGWfTi+x5eTkIC4uDpMmTVK06enpwcfHB7GxsYXeJzY2FiEhIUptvr6+2L59e5HHSUtLg0wmg7W1tVL73LlzMWvWLDg5OeGjjz7C2LFjYWBQJq46EhG9cT7fdk+yWEt6OkoWi6gwOq0Gnjx5gry8PNjZ2Sm129nZ4cqVK4XeJzExsdD9ExMTC90/KysLX375Jfr37w9LS0tF++eff44mTZqgYsWKOHr0KCZNmoRHjx5h4cKFhcbJzs5Gdna24vf09PRiPUYiIiIqf97o0yW5ubn48MMPIYTADz/8oHTb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cPnyZbWfG6p69eqFiIgIWFpaolevXq/dV9PpIQICAnDnzh2EhYWhffv22LZtG5KSkjB79mwsWLAAXbt21Sh+aWCBpCXPnj3DqFGjsG7dOsVlFn19fQQGBmLp0qUavckPGjQIjRo1wtixY6VKt4C0tDTk5eWhYsWKSu0pKSkwMDCApaVlieI1bty42HN4xMfHlyi2qgMHDmD+/PmKDzp3d3eMHz8e7777rtoxd+zYUex9NTm1K5fLYWJiorU5YbRJCIGtW7di//79hc5LIsV8PPHx8TA0NFRcSv7999+xZs0auLu7Y/r06TAyMlIrrrY+oEtD3759YWVlhR9//BEWFhY4d+4cKleujB49esDJyUntyzBLlizB8OHDYWJigiVLlrx2388//1ytY+Tr0qULhBD45ZdfFO85//zzDwYMGAA9PT38+eefasc2MTHB5cuXtfKFUtuv1/bt22PMmDHw9/eXJF5QUBCWLFkCCwsLrV++c3BwwO+//47mzZvD0tISp06dQu3atbFjxw7MmzdP49m6SwMLJC35+OOPsXfvXixbtgxt2rQB8PI69Oeff4733nsPP/zwg9qx8yvwjh07omnTpgUmhtP0zQoAOnfujG7duuGzzz5Tal+xYgV27NhR4llWZ8yYofg5KysL33//Pdzd3dGqVSsAwLFjx3Dx4kV89tlnCA0NVTvvn3/+GUFBQejVq5fi737kyBFs27YNERER+Oijj9SK+7q+Ta+Sop9TvXr1sHr1askmz1T18OFDHD58uNAiRpPnzujRo7Fy5Up4e3vDzs6uQEEsRX+JZs2aYeLEiejduzdu3bqFevXqoWfPnjh58iS6du2KsLAwteJq8wM6n5OTE9q3bw8vLy+0b99esqLr/v378PX1hRAC169fh6enJ65fvw5bW1scPHgQVapUUSuui4sLTp06hUqVKr22uJDJZLh165a66QMAKlSogGPHjhXoQ3n27Fm0adOmxH13XuXp6Ylvv/0WHTt21CjHomjz9frrr79i0qRJGDt2bKHv9Zr249EmS0tLnDt3Ds7OzqhevTo2bNiANm3aICEhAfXq1UNmZqZG8Ysq2mUyGUxMTFCzZk20a9dOszPAklyoowIqVaok9u/fX6B93759wtbWVqPYzs7ORW4uLi4axc5nY2MjLl26VKD98uXLomLFihrFDg4OLrQv09SpU0VQ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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Difference in budget based on genre\n", + "genre_df = pd.DataFrame({'genre': meta_df['main_genre'].unique(), 'means': meta_df.groupby(meta_df['main_genre'])['budget'].mean()})\n", + "genre_df['normalized_means'] = genre_df['means']/genre_df['means'].sum()\n", + "sns.barplot(data=genre_df, x='genre', y='normalized_means')\n", + "plt.xticks(rotation=90)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "35504723", + "metadata": {}, + "source": [ + "Finally, I'll set the budget and revenue based on the aforementioned metrics:" + ] + }, + { + "cell_type": "code", + "execution_count": 257, + "id": "0f04a7b9", + "metadata": {}, + "outputs": [], + "source": [ + "for idx, row in meta_df.iterrows():\n", + " if abs(row['budget']) < 100.0:\n", + " median_budget = genre_df[genre_df['genre'] == row['main_genre']]['means']\n", + " meta_df.at[idx, 'budget'] = median_budget.values[0]\n", + "\n", + " if abs(row['revenue']) < 100.0:\n", + " median_revenue = pop_df[pop_df['bins'] == row['pop_bin']]['means']\n", + " meta_df.at[idx, 'revenue'] = median_revenue.values[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 258, + "id": "7bb19ce3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
revenuebudget
count4.507400e+044.507400e+04
mean1.543365e+078.652409e+06
std6.519887e+071.683259e+07
min-2.147484e+091.000000e+02
25%1.584208e+053.198537e+06
50%5.501059e+054.500000e+06
75%3.308691e+068.524772e+06
max2.068224e+093.800000e+08
\n", + "
" + ], + "text/plain": [ + " revenue budget\n", + "count 4.507400e+04 4.507400e+04\n", + "mean 1.543365e+07 8.652409e+06\n", + "std 6.519887e+07 1.683259e+07\n", + "min -2.147484e+09 1.000000e+02\n", + "25% 1.584208e+05 3.198537e+06\n", + "50% 5.501059e+05 4.500000e+06\n", + "75% 3.308691e+06 8.524772e+06\n", + "max 2.068224e+09 3.800000e+08" + ] + }, + "execution_count": 258, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "meta_df[['revenue', 'budget']].describe()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "b130b8a6", + "metadata": {}, + "source": [ + "Now that these values have been fixed, we can finally add the engineered column." + ] + }, + { + "cell_type": "code", + "execution_count": 262, + "id": "a1a96923", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "count 4.507400e+04\n", + "mean 5.942046e+01\n", + "std 5.133602e+03\n", + "min -9.061112e+00\n", + "25% 3.124410e-02\n", + "50% 1.680270e-01\n", + "75% 1.000000e+00\n", + "max 1.018619e+06\n", + "Name: rb_ratio, dtype: float64" + ] + }, + "execution_count": 262, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "meta_df['rb_ratio'] = meta_df['revenue'] / meta_df['budget'].astype(np.float32)\n", + "meta_df['rb_ratio'].describe()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "cb79615a", + "metadata": {}, + "source": [ + "This column will be useful later on as an extra source of information when calculating similarity." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "69a5fa99", + "metadata": {}, + "source": [ + "## Finalizing Item Dataframe\n", + "\n", + "Before I finish off, I'll take one last look at the columns inside `meta_df` to see if there's anything irrelevant that I might want to drop to save on space:" + ] + }, + { + "cell_type": "code", + "execution_count": 345, + "id": "ce1b995e", + "metadata": {}, + "outputs": [], + "source": [ + "meta_df.to_csv('meta_bu_6.csv',index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 344, + "id": "53417423", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Index(['adult', 'budget', 'id', 'imdb_id', 'overview', 'popularity',\n", + " 'production_companies', 'production_countries', 'revenue', 'runtime',\n", + " 'title', 'vote_average', 'Animation', 'Comedy', 'Family', 'Adventure',\n", + " 'Fantasy', 'Romance', 'Drama', 'Action', 'Crime', 'Thriller', 'Horror',\n", + " 'History', 'Science Fiction', 'Mystery', 'War', 'Foreign', 'Music',\n", + " 'Documentary', 'Western', 'TV Movie', 'rb_ratio', 'pop_bin',\n", + " 'main_genre'],\n", + " dtype='object')" + ] + }, + "execution_count": 344, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "meta_df.columns" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "b6219f22", + "metadata": {}, + "source": [ + "Now that there are so many extra columns, it might be best to drop less informative columns such as `production_companies`. If needed, I'll re-add this column in future steps, much like how I had re-read the original `movies_metadata.csv` file to obtain all genre data in previous steps." + ] + }, + { + "cell_type": "code", + "execution_count": 346, + "id": "cc623a2c", + "metadata": {}, + "outputs": [], + "source": [ + "cols_to_drop = ['production_companies', 'production_countries', 'revenue', 'budget', 'main_genre']\n", + "meta_df.drop(cols_to_drop, axis=1, inplace=True)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "f77699da", + "metadata": {}, + "source": [ + "And the final item dataframe looks like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 348, + "id": "d4f5f74d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
adultidimdb_idoverviewpopularityruntimetitlevote_averageAnimationComedy...Science FictionMysteryWarForeignMusicDocumentaryWesternTV Movierb_ratiopop_bin
0True862tt0114709Led by Woody, Andy's toys live happily in his ...21.94694381.0Toy Story7.711...0000000012.4518019
1True8844tt0113497When siblings Judy and Peter discover an encha...17.015539104.0Jumanji6.900...000000004.0430359
2True15602tt0113228A family wedding reignites the ancient feud be...11.712900101.0Grumpier Old Men6.501...000000009.6090789
\n", + "

3 rows × 30 columns

\n", + "
" + ], + "text/plain": [ + " adult id imdb_id overview \n", + "0 True 862 tt0114709 Led by Woody, Andy's toys live happily in his ... \\\n", + "1 True 8844 tt0113497 When siblings Judy and Peter discover an encha... \n", + "2 True 15602 tt0113228 A family wedding reignites the ancient feud be... \n", + "\n", + " popularity runtime title vote_average Animation Comedy \n", + "0 21.946943 81.0 Toy Story 7.7 1 1 \\\n", + "1 17.015539 104.0 Jumanji 6.9 0 0 \n", + "2 11.712900 101.0 Grumpier Old Men 6.5 0 1 \n", + "\n", + " ... Science Fiction Mystery War Foreign Music Documentary Western \n", + "0 ... 0 0 0 0 0 0 0 \\\n", + "1 ... 0 0 0 0 0 0 0 \n", + "2 ... 0 0 0 0 0 0 0 \n", + "\n", + " TV Movie rb_ratio pop_bin \n", + "0 0 12.451801 9 \n", + "1 0 4.043035 9 \n", + "2 0 9.609078 9 \n", + "\n", + "[3 rows x 30 columns]" + ] + }, + "execution_count": 348, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "meta_df.head(3)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "b98e7683", + "metadata": {}, + "source": [ + "Here, I'll save `meta_df` in a csv file for later use in our recommendation algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 349, + "id": "ba5d21ea", + "metadata": {}, + "outputs": [], + "source": [ + "meta_df.to_csv('items.csv', index=False)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "376e9299", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Construct User Dataframe\n", + "\n", + "For this, I'll be using the full `ratings.csv` Dataframe. It's very big though.\n", + "\n", + "I'll construct a new dataframe here, with a column for each movie, and a row for each user. However, I won't be taking every movie into account; only movies that exist in the previously constructed item dataframe. Additionally, I won't be taking all the users into account either; only the first 500 users to avoid making the data too large." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "1edbca48", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
userIdmovieIdratingtimestamp
011101.01425941529
111474.51425942435
218585.01425941523
3112215.01425941546
4112465.01425941556
\n", + "
" + ], + "text/plain": [ + " userId movieId rating timestamp\n", + "0 1 110 1.0 1425941529\n", + "1 1 147 4.5 1425942435\n", + "2 1 858 5.0 1425941523\n", + "3 1 1221 5.0 1425941546\n", + "4 1 1246 5.0 1425941556" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ratings_df = pd.read_csv('./data/ratings.csv')\n", + "ratings_df.head()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "ff07831a", + "metadata": {}, + "source": [ + "To construct the final users dataframe, I'll first get a list of unique movie ids from the `meta_df` I had created in the previous step. I could also load `items.csv`:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "3ba8a2ab", + "metadata": {}, + "outputs": [], + "source": [ + "items_df = pd.read_csv('items.csv')\n", + "movie_ids = [str(x) for x in items_df['id'].unique().tolist()] # This will be our column names\n", + "col_names = [\"user_id\"] + movie_ids" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2b61d065", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_id862884415602313571186294911860453259091...844193909592899232228483084043905011110967758227506461257
\n", + "

0 rows × 44986 columns

\n", + "
" + ], + "text/plain": [ + "Empty DataFrame\n", + "Columns: [user_id, 862, 8844, 15602, 31357, 11862, 949, 11860, 45325, 9091, 710, 9087, 12110, 21032, 10858, 1408, 524, 4584, 5, 9273, 11517, 8012, 1710, 9691, 12665, 451, 16420, 9263, 17015, 902, 37557, 9909, 63, 78802, 9598, 47018, 687, 139405, 33689, 9603, 34615, 31174, 11443, 35196, 9312, 577, 11861, 807, 10530, 8391, 629, 117164, 11448, 49133, 26441, 97406, 124057, 9089, 11010, 99040, 11359, 17182, 2054, 10607, 19760, 9536, 11525, 40628, 4482, 10634, 755, 11859, 28387, 48750, 20927, 36929, 9102, 124626, 27526, 9623, 46785, 400, 880, 146599, 188588, 8447, 10534, 17414, 13997, 2086, 61548, 9095, 12158, 9283, 9208, 40154, 406, 45549, 63076, 11062, ...]\n", + "Index: []\n", + "\n", + "[0 rows x 44986 columns]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "user_df = pd.DataFrame(columns=col_names)\n", + "user_df.head()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "fd214104", + "metadata": {}, + "source": [ + "Next, I'll iterate over 500 users in `ratings_df`, and fill out `user_df`." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "60c41da7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "User 0\n", + "User 1\n", + "User 2\n", + "User 3\n", + "User 4\n", + "User 5\n", + "User 6\n", + "User 7\n", + "User 8\n", + "User 9\n", + "User 10\n", + "User 11\n", + "User 12\n", + "User 13\n", + "User 14\n", + "User 15\n", + "User 16\n", + "User 17\n", + "User 18\n", + "User 19\n", + "User 20\n", + "User 21\n", + "User 22\n", + "User 23\n", + "User 24\n", + "User 25\n", + "User 26\n", + "User 27\n", + "User 28\n", + "User 29\n", + "User 30\n", + "User 31\n", + "User 32\n", + "User 33\n", + "User 34\n", + "User 35\n", + "User 36\n", + "User 37\n", + "User 38\n", + "User 39\n", + "User 40\n", + "User 41\n", + "User 42\n", + "User 43\n", + "User 44\n", + "User 44\n", + "User 45\n", + "User 46\n", + "User 47\n", + "User 48\n", + "User 49\n", + "User 50\n", + "User 51\n", + "User 52\n", + "User 53\n", + "User 54\n", + "User 55\n", + "User 56\n", + "User 57\n", + "User 58\n", + "User 59\n", + "User 60\n", + "User 61\n", + "User 62\n", + "User 63\n", + "User 64\n", + "User 65\n", + "User 66\n", + "User 67\n", + "User 68\n", + "User 69\n", + "User 70\n", + "User 71\n", + "User 72\n", + "User 73\n", + "User 74\n", + "User 75\n", + "User 76\n", + "User 77\n", + "User 78\n", + "User 79\n", + "User 80\n", + "User 81\n", + "User 82\n", + "User 83\n", + "User 84\n", + "User 85\n", + "User 86\n", + "User 87\n", + "User 88\n", + "User 89\n", + "User 90\n", + "User 91\n", + "User 92\n", + "User 93\n", + "User 94\n", + "User 95\n", + "User 96\n", + "User 97\n", + "User 98\n", + "User 99\n", + "User 100\n", + "User 101\n", + "User 102\n", + "User 103\n", + "User 104\n", + "User 105\n", + "User 106\n", + "User 107\n", + "User 108\n", + "User 109\n", + "User 110\n", + "User 111\n", + "User 112\n", + "User 113\n", + "User 114\n", + "User 115\n", + "User 116\n", + "User 117\n", + "User 118\n", + "User 119\n", + "User 120\n", + "User 121\n", + "User 122\n", + "User 123\n", + "User 124\n", + "User 125\n", + "User 126\n", + "User 127\n", + "User 128\n", + "User 129\n", + "User 130\n", + "User 131\n", + "User 132\n", + "User 133\n", + "User 134\n", + "User 135\n", + "User 136\n", + "User 137\n", + "User 138\n", + "User 139\n", + "User 140\n", + "User 141\n", + "User 142\n", + "User 143\n", + "User 144\n", + "User 145\n", + "User 146\n", + "User 147\n", + "User 148\n", + "User 149\n", + "User 150\n", + "User 151\n", + "User 152\n", + "User 153\n", + "User 154\n", + "User 155\n", + "User 156\n", + "User 157\n", + "User 158\n", + "User 159\n", + "User 160\n", + "User 161\n", + "User 162\n", + "User 163\n", + "User 164\n", + "User 165\n", + "User 166\n", + "User 167\n", + "User 168\n", + "User 169\n", + "User 170\n", + "User 171\n", + "User 172\n", + "User 173\n", + "User 174\n", + "User 175\n", + "User 176\n", + "User 177\n", + "User 178\n", + "User 179\n", + "User 180\n", + "User 181\n", + "User 182\n", + "User 183\n", + "User 184\n", + "User 185\n", + "User 186\n", + "User 187\n", + "User 188\n", + "User 189\n", + "User 190\n", + "User 191\n", + "User 192\n", + "User 193\n", + "User 194\n", + "User 195\n", + "User 196\n", + "User 197\n", + "User 198\n", + "User 199\n", + "User 200\n", + "User 201\n", + "User 202\n", + "User 203\n", + "User 204\n", + "User 205\n", + "User 206\n", + "User 207\n", + "User 208\n", + "User 209\n", + "User 210\n", + "User 211\n", + "User 212\n", + "User 213\n", + "User 214\n", + "User 215\n", + "User 216\n", + "User 217\n", + "User 218\n", + "User 219\n", + "User 220\n", + "User 221\n", + "User 222\n", + "User 223\n", + "User 224\n", + "User 225\n", + "User 226\n", + "User 227\n", + "User 228\n", + "User 229\n", + "User 230\n", + "User 231\n", + "User 232\n", + "User 233\n", + "User 234\n", + "User 235\n", + "User 236\n", + "User 237\n", + "User 238\n", + "User 239\n", + "User 240\n", + "User 241\n", + "User 242\n", + "User 243\n", + "User 244\n", + "User 245\n", + "User 246\n", + "User 247\n", + "User 248\n", + "User 249\n", + "User 250\n", + "User 251\n", + "User 252\n", + "User 253\n", + "User 254\n", + "User 255\n", + "User 256\n", + "User 257\n", + "User 258\n", + "User 259\n", + "User 260\n", + "User 261\n", + "User 262\n", + "User 263\n", + "User 264\n", + "User 265\n", + "User 266\n", + "User 267\n", + "User 268\n", + "User 269\n", + "User 270\n", + "User 271\n", + "User 272\n", + "User 273\n", + "User 274\n", + "User 275\n", + "User 276\n", + "User 277\n", + "User 278\n", + "User 279\n", + "User 280\n", + "User 281\n", + "User 282\n", + "User 283\n", + "User 284\n", + "User 285\n", + "User 286\n", + "User 287\n", + "User 288\n", + "User 289\n", + "User 290\n", + "User 291\n", + "User 292\n", + "User 293\n", + "User 294\n", + "User 295\n", + "User 296\n", + "User 297\n", + "User 298\n", + "User 299\n", + "User 300\n", + "User 301\n", + "User 302\n", + "User 303\n", + "User 304\n", + "User 305\n", + "User 306\n", + "User 307\n", + "User 308\n", + "User 309\n", + "User 310\n", + "User 311\n", + "User 312\n", + "User 313\n", + "User 314\n", + "User 315\n", + "User 316\n", + "User 317\n", + "User 318\n", + "User 319\n", + "User 320\n", + "User 321\n", + "User 322\n", + "User 323\n", + "User 324\n", + "User 325\n", + "User 325\n", + "User 326\n", + "User 327\n", + "User 328\n", + "User 329\n", + "User 330\n", + "User 331\n", + "User 332\n", + "User 333\n", + "User 334\n", + "User 335\n", + "User 336\n", + "User 337\n", + "User 338\n", + "User 339\n", + "User 340\n", + "User 341\n", + "User 342\n", + "User 343\n", + "User 344\n", + "User 345\n", + "User 346\n", + "User 347\n", + "User 348\n", + "User 348\n", + "User 349\n", + "User 350\n", + "User 351\n", + "User 352\n", + "User 353\n", + "User 354\n", + "User 355\n", + "User 356\n", + "User 357\n", + "User 358\n", + "User 359\n", + "User 360\n", + "User 361\n", + "User 362\n", + "User 363\n", + "User 364\n", + "User 365\n", + "User 366\n", + "User 367\n", + "User 368\n", + "User 369\n", + "User 370\n", + "User 371\n", + "User 372\n", + "User 373\n", + "User 374\n", + "User 375\n", + "User 376\n", + "User 377\n", + "User 377\n", + "User 378\n", + "User 379\n", + "User 380\n", + "User 381\n", + "User 382\n", + "User 383\n", + "User 384\n", + "User 385\n", + "User 386\n", + "User 387\n", + "User 388\n", + "User 389\n", + "User 389\n", + "User 390\n", + "User 391\n", + "User 392\n", + "User 393\n", + "User 393\n", + "User 394\n", + "User 395\n", + "User 396\n", + "User 397\n", + "User 398\n", + "User 399\n", + "User 400\n", + "User 401\n", + "User 402\n", + "User 403\n", + "User 404\n", + "User 405\n", + "User 406\n", + "User 407\n", + "User 408\n", + "User 409\n", + "User 410\n", + "User 411\n", + "User 412\n", + "User 413\n", + "User 414\n", + "User 415\n", + "User 416\n", + "User 417\n", + "User 418\n", + "User 419\n", + "User 420\n", + "User 421\n", + "User 422\n", + "User 423\n", + "User 424\n", + "User 425\n", + "User 426\n", + "User 427\n", + "User 428\n", + "User 429\n", + "User 430\n", + "User 431\n", + "User 432\n", + "User 433\n", + "User 434\n", + "User 435\n", + "User 436\n", + "User 437\n", + "User 438\n", + "User 439\n", + "User 440\n", + "User 441\n", + "User 442\n", + "User 443\n", + "User 444\n", + "User 445\n", + "User 446\n", + "User 447\n", + "User 448\n", + "User 449\n", + "User 450\n", + "User 451\n", + "User 452\n", + "User 453\n", + "User 454\n", + "User 455\n", + "User 456\n", + "User 457\n", + "User 458\n", + "User 459\n", + "User 460\n", + "User 461\n", + "User 462\n", + "User 463\n", + "User 464\n", + "User 465\n", + "User 466\n", + "User 466\n", + "User 467\n", + "User 468\n", + "User 469\n", + "User 470\n", + "User 471\n", + "User 472\n", + "User 473\n", + "User 474\n", + "User 475\n", + "User 476\n", + "User 476\n", + "User 477\n", + "User 478\n", + "User 479\n", + "User 480\n", + "User 481\n", + "User 482\n", + "User 483\n", + "User 484\n", + "User 485\n", + "User 486\n", + "User 487\n", + "User 488\n", + "User 489\n", + "User 490\n", + "User 491\n", + "User 492\n", + "User 493\n", + "User 494\n", + "User 495\n", + "User 496\n", + "User 497\n", + "User 498\n", + "User 499\n" + ] + } + ], + "source": [ + "# Takes a while to run\n", + "\n", + "# Get 500 unique users that have given a rating for at least one of the movies\n", + "uuids = ratings_df['userId'].unique().tolist()\n", + "count = 0\n", + "n_users = 500\n", + "\n", + "for i, uuid in enumerate(uuids):\n", + " print(f\"User {count}\")\n", + " row = np.zeros_like(col_names)\n", + " row[0] = uuid # Set user id\n", + " # Get list of user reviews so I don't keep doing a search on the entire ratings_df dataframe every time\n", + " ratings_filtered_df = ratings_df[ratings_df['userId'] == uuid]\n", + " for i, movie_id in enumerate(movie_ids):\n", + " movie_rating = ratings_filtered_df[ratings_filtered_df['movieId'] == int(movie_id)]\n", + " if not movie_rating.empty:\n", + " row[i] = movie_rating.rating.values[0]\n", + " made_rating = True\n", + " \n", + " # Only add a specific user rating if the user has made a prediction for at least one of the listed movies (i.e. check to see row is not all 0)\n", + " row[row == ''] = '0'\n", + " row = row.astype(float)\n", + " \n", + " if np.any(row[1:]):\n", + " user_df.loc[len(user_df.index)] = row\n", + " count += 1\n", + " \n", + " if count >= 500:\n", + " break" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "f46f28fa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 0, 0, ..., 0, 0, 0])" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "row[row == ''] = '0'\n", + "row.astype(float)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "c7a55120", + "metadata": {}, + "source": [ + "Now that I've constructed this dataframe, I'll save it as a csv file;" + ] + }, + { + "cell_type": "code", + "execution_count": 393, + "id": "14dd70a3", + "metadata": {}, + "outputs": [], + "source": [ + "user_df.to_csv('users.csv', index=None)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "bff569a8", + "metadata": {}, + "source": [ + "And that concludes the data preprocessing step." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "95c48c10", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Making Smaller files\n", + "\n", + "However, the resulting `items.csv` and `users.csv` are quite large, so I'm splitting these csv files into 5 and 12 parts respectively, so I can upload them up onto hugging face." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "05811f9a", + "metadata": {}, + "outputs": [], + "source": [ + "# Import libraries\n", + "\n", + "import pandas as pd\n", + "import numpy as np\n", + "from math import ceil\n", + "from glob import glob\n", + "\n", + "\n", + "# Read dataframes\n", + "\n", + "items_df = pd.read_csv('items.csv')\n", + "users_df = pd.read_csv('users.csv')" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "32855011", + "metadata": {}, + "source": [ + "## Splitting users_df up into 12 parts" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "78eb378f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "n_parts_users = 12\n", + "\n", + "n_rows = ceil(users_df.shape[0]/n_parts_users)\n", + "n_rows" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "b7a07409", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving part 1/12\n", + "Saving part 2/12\n", + "Saving part 3/12\n", + "Saving part 4/12\n", + "Saving part 5/12\n", + "Saving part 6/12\n", + "Saving part 7/12\n", + "Saving part 8/12\n", + "Saving part 9/12\n", + "Saving part 10/12\n", + "Saving part 11/12\n", + "Saving part 12/12\n" + ] + } + ], + "source": [ + "for part in range(n_parts_users):\n", + " print(f\"Saving part {part+1}/{n_parts_users}\")\n", + " filename = \"users_{}.csv\".format(part)\n", + " start_idx = part * n_rows\n", + " end_idx = min((part+1) * n_rows, users_df.shape[0])\n", + " users_df.loc[start_idx:start_idx].to_csv(filename, index=None)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "e7653833", + "metadata": {}, + "source": [ + "## Splitting items_df up into 5 parts" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "101ade69", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "9015" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "n_parts_items = 5\n", + "\n", + "n_rows = ceil(items_df.shape[0]/n_parts_items)\n", + "n_rows" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "35266a30", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving part 1/5\n", + "Saving part 2/5\n", + "Saving part 3/5\n", + "Saving part 4/5\n", + "Saving part 5/5\n" + ] + } + ], + "source": [ + "for part in range(n_parts_items):\n", + " print(f\"Saving part {part+1}/{n_parts_items}\")\n", + " filename = \"items_{}.csv\".format(part)\n", + " start_idx = part * n_rows\n", + " end_idx = min((part+1) * n_rows, items_df.shape[0])\n", + " items_df.loc[start_idx:start_idx].to_csv(filename, index=None)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "3e3d3988", + "metadata": {}, + "source": [ + "## Reconstructing original DataFrame" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "9f3a5743", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_id862884415602313571186294911860453259091...844193909592899232228483084043905011110967758227506461257
01.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.0
043.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.0
\n", + "

2 rows × 44986 columns

\n", + "
" + ], + "text/plain": [ + " user_id 862 8844 15602 31357 11862 949 11860 45325 9091 ... \n", + "0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... \\\n", + "0 43.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... \n", + "\n", + " 84419 390959 289923 222848 30840 439050 111109 67758 227506 461257 \n", + "0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", + "0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", + "\n", + "[2 rows x 44986 columns]" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Reconstructor\n", + "\n", + "# users_df\n", + "users_df = pd.concat([pd.read_csv(f) for f in glob(\"users_*.csv\")])\n", + "users_df.head(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "ac68d277", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
adultidimdb_idoverviewpopularityruntimetitlevote_averageAnimationComedy...Science FictionMysteryWarForeignMusicDocumentaryWesternTV Movierb_ratiopop_bin
0True862tt0114709Led by Woody, Andy's toys live happily in his ...21.94694381.0Toy Story7.711...0000000012.4518019
0True27678tt0106356A television movie based upon the book by Brya...1.685697107.0Barbarians at the Gate6.801...000000000.3026615
\n", + "

2 rows × 30 columns

\n", + "
" + ], + "text/plain": [ + " adult id imdb_id overview \n", + "0 True 862 tt0114709 Led by Woody, Andy's toys live happily in his ... \\\n", + "0 True 27678 tt0106356 A television movie based upon the book by Brya... \n", + "\n", + " popularity runtime title vote_average Animation \n", + "0 21.946943 81.0 Toy Story 7.7 1 \\\n", + "0 1.685697 107.0 Barbarians at the Gate 6.8 0 \n", + "\n", + " Comedy ... Science Fiction Mystery War Foreign Music Documentary \n", + "0 1 ... 0 0 0 0 0 0 \\\n", + "0 1 ... 0 0 0 0 0 0 \n", + "\n", + " Western TV Movie rb_ratio pop_bin \n", + "0 0 0 12.451801 9 \n", + "0 0 0 0.302661 5 \n", + "\n", + "[2 rows x 30 columns]" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# items_df\n", + "items_df = pd.concat([pd.read_csv(f) for f in glob(\"items_*.csv\")])\n", + "items_df.head(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.8.10" + }, + "vscode": { + "interpreter": { + "hash": "e084279891f6f4db1ee843a72e2e91611a252795aeda8ffc8cf83a1802c1e7e8" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}