<jupyter_start><jupyter_text>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 commonly be seen in online stores, movie databases, and job finders. In this blog post, we will explore **content-based** and **colaborative filtering** recommendation systems.The dataset we'll be working on has been acquired from [GroupLens](https://grouplens.org/datasets/movielens/). It consists of 27 million ratings and 1.1 million tag applications applied to 58,000 movies by 280,000 users.<jupyter_code># import libraries
import pandas as pd
from math import sqrt
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# store the movie information into a pandas dataframe
movies_df = pd.read_csv('movies1.csv')

# store the ratings information into a pandas dataframe
ratings_df = pd.read_csv('ratings.csv')
movies_df.head()<jupyter_output><empty_output><jupyter_text>Each movie has a unique ID, a title with its release year along with it (which may contain unicode characters) and several different genres in the same field.<jupyter_code># dimensions of the dataframes
print(movies_df.shape)
print(ratings_df.shape)<jupyter_output>(58097, 3)
(27753444, 4)<jupyter_text>Preprocessing the dataLet's remove the year from the 'title' column and store it in a new 'year' column.<jupyter_code># use regular expressions to find a year stored between parantheses
# we specify the parantheses 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)

# remove the parentheses
movies_df['year'] = movies_df.year.str.extract('(\d\d\d\d)', expand=False)

# remove the years from the 'title' column
movies_df['title'] = movies_df.title.str.replace('(\(\d\d\d\d\))', '')

# apply the strip finction 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()<jupyter_output><empty_output><jupyter_text>Let's also split the values in the 'genres' column into a 'list of genres' to simplify future use. Apply Python's split string function on the genres column.<jupyter_code># every genre is separated by a |. So call the split function on |.
movies_df['genres'] = movies_df.genres.str.split('|')
movies_df.head()<jupyter_output><empty_output><jupyter_text>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 differrent 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.<jupyter_code># copy the movie dataframe into a new one
moviesWithGenres_df = movies_df.copy()

# for every row in the dataframe, iterate through the list of genres and place a 1 in the corresponding column
for index, row in movies_df.iterrows():
    for genre in row['genres']:
        moviesWithGenres_df.at[index, genre] = 1
        
# fill 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()<jupyter_output><empty_output><jupyter_text>Now, let's focus on the ratings dataframe.<jupyter_code>ratings_df.head()<jupyter_output><empty_output><jupyter_text>Every row in the ratings dataframe has a userId  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.<jupyter_code>ratings_df = ratings_df.drop('timestamp',1)
ratings_df.head()<jupyter_output><empty_output><jupyter_text>Content-based recommendation systemThis technique attempts to figure out what a user's favorite aspects of an item are, 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. Advantages of content-based filtering:- it learns the user's preferences.- it's highly personalized for the user.   Disadvantages of content-based filtering:- it 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.Create an input to recommend movies to.<jupyter_code>userInput = [
    {'title':'Mission: Impossible - Fallout', 'rating':5},
    {'title':'Top Gun', 'rating':4.5},
    {'title':'<NAME>', 'rating':3},
    {'title':'Vanilla Sky', 'rating':2.5},
    {'title':'Minority Report', 'rating':4},
]
inputMovies = pd.DataFrame(userInput)
inputMovies<jupyter_output><empty_output><jupyter_text>Add movieId to input user.  Extract the input movie's ID from the movies dataframe and add it to the input.<jupyter_code># filter the movies by title
inputId = movies_df[movies_df['title'].isin(inputMovies['title'].tolist())]

# merge it to get the movieId
inputMovies = pd.merge(inputId, inputMovies)

# drop information we won't use from the input dataframe
inputMovies = inputMovies.drop('genres', 1).drop('year', 1)

# final input dataframe
inputMovies<jupyter_output><empty_output><jupyter_text>We will learn 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.<jupyter_code># filter out the movies from the input
userMovies = moviesWithGenres_df[moviesWithGenres_df['movieId'].isin(inputMovies['movieId'].tolist())]
userMovies<jupyter_output><empty_output><jupyter_text>We only need the actual genre table. Reset the index and drop the unnecessary columns.<jupyter_code># reset the index
userMovies = userMovies.reset_index(drop=True)

# drop unnecessary columns
userGenreTable = userMovies.drop('movieId', 1).drop('title', 1).drop('genres', 1).drop('year', 1)
userGenreTable<jupyter_output><empty_output><jupyter_text>Now we learn the input preferences.  We turn each genre into weights using the input's reviews and multiplying them into the input's genre table, and then summing up the resulting table by column.<jupyter_code>inputMovies['rating']
# dot product to get weights
userProfile = userGenreTable.transpose().dot(inputMovies['rating'])

# the user profile
userProfile<jupyter_output><empty_output><jupyter_text>Now we have the weights for each of the user's preferences. This is 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.<jupyter_code># get the genre of every movie in our original dataframe
genreTable = moviesWithGenres_df.set_index(moviesWithGenres_df['movieId'])

# drop unnecessary columns
genreTable = genreTable.drop('movieId', 1).drop('title', 1).drop('genres', 1).drop('year', 1)
genreTable.head()
genreTable.shape<jupyter_output><empty_output><jupyter_text>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.<jupyter_code># multiply the genre by the weights and then take the weighted average
recommendationTable_df = ((genreTable*userProfile).sum(axis=1)) / (userProfile.sum())
recommendationTable_df.head()<jupyter_output><empty_output><jupyter_text>Here is the recommendation table.<jupyter_code>movies_df.loc[movies_df['movieId'].isin(recommendationTable_df.head(20).keys())]<jupyter_output><empty_output><jupyter_text>These are the top 20 movies to recommend to the user based on a content-based recommendation system.  Collaborative FilteringThis technique uses other users to recommend items to the input user. It attempts to find users that have similar preferences and opinions as the input and then recommends items that they have liked to the input. there are several methods of finding similar users, and the one we will be using here is going to be based on the Pearson Correlation Function.The process for creating a user-based recommendation system is as follows:- Select a user with the movies the user has watched.- Based on his ratings of movies, find the top X neighbours.- Get the watched movie record of the user for each neighbour.- Calculate a similarity score using some formula.- Recommend the items with the highest score.Advantages of collaborative filtering:- It takes other user's ratings into consideration- It doesn't need to study or extract information from the recommended item- It adapts to the user's interestes which might change over timeDisadvantages of collaborative filtering:- The approximation function can be slow.- There might be a low amount of users to approximate- There might be privacy issues when trying to learn the user's experiences.Let's create an input user to recommend movies to.<jupyter_code>userInput = [
    {'title':'Mission: Impossible - Fallout', 'rating':5},
    {'title':'Top Gun', 'rating':4.5},
    {'title':'<NAME>', 'rating':3},
    {'title':'Vanilla Sky', 'rating':2.5},
    {'title':'Minority Report', 'rating':4},
]
inputMovies = pd.DataFrame(userInput)
inputMovies
# filter the movies by title
inputId = movies_df[movies_df['title'].isin(inputMovies['title'].tolist())]

# merge it to get the movieId
inputMovies = pd.merge(inputId, inputMovies)

# drop information we won't use from the input dataframe
inputMovies = inputMovies.drop('genres', 1).drop('year', 1)

# final input dataframe
inputMovies<jupyter_output><empty_output><jupyter_text>The users who have seen the same moviesNow, with the movie IDs in our input, we can get the subset of users that have watched and reviewd the movies in our input.<jupyter_code># filter out users that have watched movies that the input has watched and storing it
userSubset = ratings_df[ratings_df['movieId'].isin(inputMovies['movieId'].tolist())]
userSubset.head()<jupyter_output><empty_output><jupyter_text>Group the rows by userId.<jupyter_code># groupby creates several sub dataframes where they all have the same value in the column specified as the parameter
userSubsetGroup = userSubset.groupby(['userId'])<jupyter_output><empty_output><jupyter_text>Let's look at one of these users - userId = 4<jupyter_code>userSubsetGroup.get_group(4)<jupyter_output><empty_output><jupyter_text>Let's sort these groups so the users that share the most movies in common with the input have higher priority. This provides a richer recommendation since we won't go through every single user.<jupyter_code>userSubsetGroup = sorted(userSubsetGroup, key=lambda x: len(x[1]), reverse=True)<jupyter_output><empty_output><jupyter_text>Now let's look at the first user.<jupyter_code>userSubsetGroup[0:3]<jupyter_output><empty_output><jupyter_text>Next, we are going to compare users to our specified user and find the one that is most similar.  We're going to find out how similar each user is to the input through the Pearson Correlation Coefficient. It is used to measure the strength of a linear association between two variables.  We will select a subset of users to iterate through. The limit is imposed because we don't want to waste too much time going through every single user.<jupyter_code>userSubsetGroup = userSubsetGroup[0:100]<jupyter_output><empty_output><jupyter_text>Calculate the Pearson Correlation between the input user and the subset group, and store it in a dictionary, where the key is the userId and the value is the coefficient.<jupyter_code>pearsonCorrelationDict = {}

#For every user group in our subset
for name, group in userSubsetGroup:
    #Let's start by sorting the input and current user group so the values aren't mixed up later on
    group = group.sort_values(by='movieId')
    inputMovies = inputMovies.sort_values(by='movieId')
    #Get the N for the formula
    nRatings = len(group)
    #Get the review scores for the movies that they both have in common
    temp_df = inputMovies[inputMovies['movieId'].isin(group['movieId'].tolist())]
    #And then store them in a temporary buffer variable in a list format to facilitate future calculations
    tempRatingList = temp_df['rating'].tolist()
    #Let's also put the current user group reviews in a list format
    tempGroupList = group['rating'].tolist()
    #Now let's calculate the pearson correlation between two users, so called, x and y
    Sxx = sum([i**2 for i in tempRatingList]) - pow(sum(tempRatingList),2)/float(nRatings)
    Syy = sum([i**2 for i in tempGroupList]) - pow(sum(tempGroupList),2)/float(nRatings)
    Sxy = sum( i*j for i, j in zip(tempRatingList, tempGroupList)) - sum(tempRatingList)*sum(tempGroupList)/float(nRatings)
    
    #If the denominator is different than zero, then divide, else, 0 correlation.
    if Sxx != 0 and Syy != 0:
        pearsonCorrelationDict[name] = Sxy/sqrt(Sxx*Syy)
    else:
        pearsonCorrelationDict[name] = 0
pearsonCorrelationDict.items()
pearsonDF = pd.DataFrame.from_dict(pearsonCorrelationDict, orient='index')
pearsonDF.columns = ['similarityIndex']
pearsonDF['userId'] = pearsonDF.index
pearsonDF.index = range(len(pearsonDF))
pearsonDF.head()<jupyter_output><empty_output><jupyter_text>The top x similar users to the input userLet's get the top 50 users that are most similar to the input.<jupyter_code>topUsers = pearsonDF.sort_values(by='similarityIndex', ascending=False)[0:50]
topUsers.head()<jupyter_output><empty_output><jupyter_text>Now let's start recommending movies to the input user. Rating of selected users to all moviesWe're going to do this by taking the weighted average of the ratings of the movies using the Pearson Correlation as the weight. But to do this, we first need to get the movies watched by the users in our pearsonDF from the ratings dataframe, and then store their correlation in a new column called 'similarityIndex'.<jupyter_code># merge two tables
topUsersRating=topUsers.merge(ratings_df, left_on='userId', right_on='userId', how='inner')
topUsersRating.head()<jupyter_output><empty_output><jupyter_text>Now we multiply the movie rating by its weight (the similarity index), then sum up the new ratings and divide it by the sum of the weights.  We can easily do this by simply multiplying two columns, then grouping up the dataframe by movieId and then dividing two columns.  It shows the idea of all similar users to candidate movies for the input user.<jupyter_code># multiply the similarity by the user's ratings
topUsersRating['weightedRating'] = topUsersRating['similarityIndex']*topUsersRating['rating']
topUsersRating.head()
# apply a sum to the topUsers after grouping it by userId
tempTopUsersRating = topUsersRating.groupby('movieId').sum()[['similarityIndex','weightedRating']]
tempTopUsersRating.columns = ['sum_similarityIndex','sum_weightedRating']
tempTopUsersRating.head()
# create an empty dataframe
recommendation_df = pd.DataFrame()

# take the weighted average
recommendation_df['weighted average recommendation score'] = tempTopUsersRating['sum_weightedRating']/tempTopUsersRating['sum_similarityIndex']
recommendation_df['movieId'] = tempTopUsersRating.index
recommendation_df.head()<jupyter_output><empty_output><jupyter_text>Let's sort this and see the top 20 movies that the algorithm recommended.<jupyter_code>recommendation_df = recommendation_df.sort_values(by='weighted average recommendation score', ascending=False)
recommendation_df.head()
movies_df.loc[movies_df['movieId'].isin(recommendation_df.head(20)['movieId'].tolist())]<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.0854663228727753, 'fraction_numerical': 0.006100245267593233, 'mean_word_length': 5.9592997811816195, 'pattern_counts': {'":': 0, '<': 110, '<?xml version=': 0, '>': 110, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '27', 'dir': 'jupyter-structured-clean-dedup', 'id': '391891', 'license': 'MIT', 'n_tokens_mistral': 4750, 'n_tokens_neox': 4442, 'n_words': 1768, 'path': '_jupyterNotebooks/recommender_systems/Recommending_movies_to_user.ipynb', 'repo_name': 'PranavShirole/PranavShirole.github.io', 'size': '15915'}