CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
DanielBarnes18

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

GitHub Repository: DanielBarnes18/IBM-Data-Science-Professional-Certificate
Path: blob/main/09. Machine Learning with Python/05. Recommender Systems/01. Content-based/01. Content-based.ipynb
Views: 4607
Kernel: Python 3 (ipykernel)
cognitiveclass.ai logo

Content Based Filtering

Objectives

After completing this lab you will be able to:

  • Create a recommendation system using collaborative filtering

Recommendation systems are a collection of algorithms used to recommend items to users based on information taken from the user. These systems have become ubiquitous, and can be commonly seen in online stores, movies databases and job finders. In this notebook, we will explore Content-based recommendation systems and implement a simple version of one using Python and the Pandas library.

Acquiring the Data

To acquire and extract the data, simply run the following Bash scripts: Dataset acquired from GroupLens.

import requests, zipfile, io zip_file_url = "https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMDeveloperSkillsNetwork-ML0101EN-SkillsNetwork/labs/Module%205/data/moviedataset.zip" r = requests.get(zip_file_url) z = zipfile.ZipFile(io.BytesIO(r.content)) z.extractall()

Note that the files are too large to store on GitHub, but this code will run perfectly well with the above lines downloading the files locally to a /ml-latest folder.

Preprocessing

First, let's get all of the imports out of the way:

#Dataframe manipulation library import pandas as pd #Math functions, we'll only need the sqrt function so let's import only that from math import sqrt import numpy as np import matplotlib.pyplot as plt %matplotlib inline

Now let's read each file into their Dataframes:

#Storing the movie information into a pandas dataframe movies_df = pd.read_csv('ml-latest/movies.csv') #Storing the user information into a pandas dataframe ratings_df = pd.read_csv('ml-latest/ratings.csv') #Head is a function that gets the first N rows of a dataframe. N's default is 5. movies_df.head()

Let's also remove the year from the title column by using pandas' replace function and store in a new year column.

#Using regular expressions to find a year stored between parentheses #We specify the parentheses so we don't conflict with movies that have years in their titles movies_df['year'] = movies_df.title.str.extract('(\(\d\d\d\d\))',expand=False) #Removing the parentheses movies_df['year'] = movies_df.year.str.extract('(\d\d\d\d)',expand=False) #Removing the years from the 'title' column movies_df['title'] = movies_df.title.str.replace('(\(\d\d\d\d\))', '') #Applying the strip function to get rid of any ending whitespace characters that may have appeared movies_df['title'] = movies_df['title'].apply(lambda x: x.strip()) movies_df.head()
C:\Users\Dan\anaconda3\lib\site-packages\ipykernel_launcher.py:7: FutureWarning: The default value of regex will change from True to False in a future version. import sys

With that, let's also split the values in the Genres column into a list of Genres to simplify for future use. This can be achieved by applying Python's split string function on the correct column.

#Every genre is separated by a | so we simply have to call the split function on | movies_df['genres'] = movies_df.genres.str.split('|') movies_df.head()

Since keeping genres in a list format isn't optimal for the content-based recommendation system technique, we will use the One Hot Encoding technique to convert the list of genres to a vector where each column corresponds to one possible value of the feature. This encoding is needed for feeding categorical data. In this case, we store every different genre in columns that contain either 1 or 0. 1 shows that a movie has that genre and 0 shows that it doesn't. Let's also store this dataframe in another variable since genres won't be important for our first recommendation system.

#Copying the movie dataframe into a new one since we won't need to use the genre information in our first case. moviesWithGenres_df = movies_df.copy() #For every row in the dataframe, iterate through the list of genres and place a 1 into the corresponding column for index, row in movies_df.iterrows(): for genre in row['genres']: moviesWithGenres_df.at[index, genre] = 1 #Filling in the NaN values with 0 to show that a movie doesn't have that column's genre moviesWithGenres_df = moviesWithGenres_df.fillna(0) moviesWithGenres_df.head()

Next, let's look at the ratings dataframe.

ratings_df.head()

Every row in the ratings dataframe has a user id associated with at least one movie, a rating and a timestamp showing when they reviewed it. We won't be needing the timestamp column, so let's drop it to save memory.

#Drop removes a specified row or column from a dataframe ratings_df = ratings_df.drop('timestamp', 1) ratings_df.head()
C:\Users\Dan\anaconda3\lib\site-packages\ipykernel_launcher.py:2: FutureWarning: In a future version of pandas all arguments of DataFrame.drop except for the argument 'labels' will be keyword-only

Content-Based recommendation system

Now, let's take a look at how to implement Content-Based or Item-Item recommendation systems. This technique attempts to figure out what a user's favourite aspects of an item is, and then recommends items that present those aspects. In our case, we're going to try to figure out the input's favorite genres from the movies and ratings given.

Let's begin by creating an input user to recommend movies to:

Notice: To add more movies, simply increase the amount of elements in the userInput. Feel free to add more in! Just be sure to write it in with capital letters and if a movie starts with a "The", like "The Matrix" then write it in like this: 'Matrix, The' .

userInput = [ {'title':'Breakfast Club, The', 'rating':5}, {'title':'Toy Story', 'rating':3.5}, {'title':'Jumanji', 'rating':2}, {'title':"Pulp Fiction", 'rating':5}, {'title':'Akira', 'rating':4.5} ] inputMovies = pd.DataFrame(userInput) inputMovies

Add movieId to input user

With the input complete, let's extract the input movie's ID's from the movies dataframe and add them into it.

We can achieve this by first filtering out the rows that contain the input movie's title and then merging this subset with the input dataframe. We also drop unnecessary columns for the input to save memory space.

#Filtering out the movies by title inputId = movies_df[movies_df['title'].isin(inputMovies['title'].tolist())] #Then merging it so we can get the movieId. It's implicitly merging it by title. inputMovies = pd.merge(inputId, inputMovies) #Dropping information we won't use from the input dataframe inputMovies = inputMovies.drop('genres', 1).drop('year', 1) #Final input dataframe #If a movie you added in above isn't here, then it might not be in the original #dataframe or it might spelled differently, please check capitalisation. inputMovies
C:\Users\Dan\anaconda3\lib\site-packages\ipykernel_launcher.py:6: FutureWarning: In a future version of pandas all arguments of DataFrame.drop except for the argument 'labels' will be keyword-only

We're going to start by learning the input's preferences, so let's get the subset of movies that the input has watched from the Dataframe containing genres defined with binary values.

#Filtering out the movies from the input userMovies = moviesWithGenres_df[moviesWithGenres_df['movieId'].isin(inputMovies['movieId'].tolist())] userMovies

We'll only need the actual genre table, so let's clean this up a bit by resetting the index and dropping the movieId, title, genres and year columns.

#Resetting the index to avoid future issues userMovies = userMovies.reset_index(drop=True) #Dropping unnecessary issues due to save memory and to avoid issues userGenreTable = userMovies.drop('movieId', 1).drop('title', 1).drop('genres', 1).drop('year', 1) userGenreTable
C:\Users\Dan\anaconda3\lib\site-packages\ipykernel_launcher.py:4: FutureWarning: In a future version of pandas all arguments of DataFrame.drop except for the argument 'labels' will be keyword-only after removing the cwd from sys.path.

Now we're ready to start learning the input's preferences!

To do this, we're going to turn each genre into weights. We can do this by using the input's reviews and multiplying them into the input's genre table and then summing up the resulting table by column. This operation is actually a dot product between a matrix and a vector, so we can simply accomplish by calling the Pandas "dot" function.

inputMovies['rating']
0 3.5 1 2.0 2 5.0 3 4.5 4 5.0 Name: rating, dtype: float64
#Dot product to get weights userProfile = userGenreTable.transpose().dot(inputMovies['rating']) #The user profile userProfile
Adventure 10.0 Animation 8.0 Children 5.5 Comedy 13.5 Fantasy 5.5 Romance 0.0 Drama 10.0 Action 4.5 Crime 5.0 Thriller 5.0 Horror 0.0 Mystery 0.0 Sci-Fi 4.5 IMAX 0.0 Documentary 0.0 War 0.0 Musical 0.0 Western 0.0 Film-Noir 0.0 (no genres listed) 0.0 dtype: float64

Now, we have the weights for every of the user's preferences. This is known as the User Profile. Using this, we can recommend movies that satisfy the user's preferences.

Let's start by extracting the genre table from the original dataframe:

#Now let's get the genres of every movie in our original dataframe genreTable = moviesWithGenres_df.set_index(moviesWithGenres_df['movieId']) #And drop the unnecessary information genreTable = genreTable.drop('movieId', 1).drop('title', 1).drop('genres', 1).drop('year', 1) genreTable.head()
C:\Users\Dan\anaconda3\lib\site-packages\ipykernel_launcher.py:4: FutureWarning: In a future version of pandas all arguments of DataFrame.drop except for the argument 'labels' will be keyword-only after removing the cwd from sys.path.
genreTable.shape
(34208, 20)

With the input's profile and the complete list of movies and their genres in hand, we're going to take the weighted average of every movie based on the input profile and recommend the top twenty movies that most satisfy it.

#Multiply the genres by the weights and then take the weighted average recommendationTable_df = ((genreTable*userProfile).sum(axis=1))/(userProfile.sum()) recommendationTable_df.head()
movieId 1 0.594406 2 0.293706 3 0.188811 4 0.328671 5 0.188811 dtype: float64
#Sort our recommendations in descending order recommendationTable_df = recommendationTable_df.sort_values(ascending=False) #Just a peek at the values recommendationTable_df.head()
movieId 5018 0.748252 26093 0.734266 27344 0.720280 148775 0.685315 6902 0.678322 dtype: float64

Now here's the recommendation table!

#The final recommendation table movies_df.loc[movies_df['movieId'].isin(recommendationTable_df.head(20).keys())]

Advantages and Disadvantages of Content-Based Filtering

Advantages
  • Learns user's preferences

  • Highly personalized for the user

Disadvantages
  • Doesn't take into account what others think of the item, so low quality item recommendations might happen

  • Extracting data is not always intuitive

  • Determining what characteristics of the item the user dislikes or likes is not always obvious