Chapter 23 — Recommender Systems

Recommender systems suggest items users may like. This chapter builds the two classic approaches — collaborative filtering with similarity and model-based matrix factorization with SVD — on a small synthetic ratings matrix.

Learning Objectives

Prerequisites / Imports

In [1]:
import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.decomposition import TruncatedSVD

1 A Ratings Matrix

Rows are users, columns are movies; NaN marks unrated items.

In [1]:
ratings = pd.DataFrame({
    'Matrix': [5, 5, np.nan, 1, np.nan, 4],
    'Inception': [4, np.nan, 5, 1, 4, 5],
    'Titanic': [1, 2, 5, 5, np.nan, np.nan],
    'Avatar': [np.nan, 4, 4, 4, 5, 3],
    'Interstellar': [5, 5, 4, 2, 4, 5],
    'Notebook': [np.nan, 1, 5, 5, 1, np.nan],
}, index=['Alice','Bob','Carol','Dave','Eve','Frank'])
ratings
Matrix Inception Titanic Avatar Interstellar Notebook
Alice 5.0 4.0 1.0 NaN 5 NaN
Bob 5.0 NaN 2.0 4.0 5 1.0
Carol NaN 5.0 5.0 4.0 4 5.0
Dave 1.0 1.0 5.0 4.0 2 5.0
Eve NaN 4.0 NaN 5.0 4 1.0
Frank 4.0 5.0 NaN 3.0 5 NaN

2 User-Based Collaborative Filtering

Find users most similar to a target, then predict unrated items from their ratings.

In [1]:
filled = ratings.fillna(0)
user_sim = cosine_similarity(filled)
user_sim_df = pd.DataFrame(user_sim, index=ratings.index, columns=ratings.index)
np.fill_diagonal(user_sim_df.values, 0)
user_sim_df.round(2)
Alice Bob Carol Dave Eve Frank
Alice 0.00 0.75 0.53 0.35 0.58 0.92
Bob 0.75 0.00 0.59 0.64 0.64 0.78
Carol 0.53 0.59 0.00 0.90 0.77 0.64
Dave 0.35 0.64 0.90 0.00 0.57 0.42
Eve 0.58 0.64 0.77 0.57 0.00 0.83
Frank 0.92 0.78 0.64 0.42 0.83 0.00

3 Predict a Rating

Predict Alice's rating for Avatar using a similarity-weighted average of other users.

In [1]:
target = 'Alice'
item = 'Avatar'
sim = user_sim_df.loc[target]
item_ratings = ratings[item].dropna()
weights = sim.loc[item_ratings.index]
pred = np.average(item_ratings, weights=weights)
print(f'Predicted {target} rating for {item}: {pred:.2f}')
Predicted Alice rating for Avatar: 3.89

4 Item-Based Collaborative Filtering

Instead, compare items by who rated them; predict from similar items the user rated.

In [1]:
item_sim = cosine_similarity(filled.T)
item_sim_df = pd.DataFrame(item_sim, index=ratings.columns, columns=ratings.columns)
target = 'Alice'
item = 'Avatar'
sim_items = item_sim_df[item].drop(item).sort_values(ascending=False).head(3)
alice_ratings = ratings.loc[target, sim_items.index].dropna()
if len(alice_ratings):
    pred = np.average(alice_ratings, weights=sim_items.loc[alice_ratings.index])
    print(f'Item-based prediction for {target}->{item}: {pred:.2f} (from {list(alice_ratings.index)}')
else:
    print(f'{target} has no ratings for items similar to {item}')
Item-based prediction for Alice->Avatar: 4.54 (from ['Interstellar', 'Inception']

5 Model-Based: Matrix Factorization with SVD

SVD learns latent factors for users and items, filling the whole matrix at once.

In [1]:
svd = TruncatedSVD(n_components=2, random_state=42)
latent = svd.fit_transform(filled)
print('user latent factors shape:', latent.shape)
pred_all = svd.inverse_transform(latent).dot(np.eye(len(ratings.columns)))
pred_df = pd.DataFrame(pred_all, index=ratings.index, columns=ratings.columns).round(1)
pred_df
user latent factors shape: (6, 2)
Matrix Inception Titanic Avatar Interstellar Notebook
Alice 4.4 3.5 -0.1 2.0 5.0 -0.6
Bob 3.3 3.4 1.3 2.9 4.6 1.0
Carol 0.8 3.5 4.9 5.1 3.9 5.0
Dave -0.4 2.3 4.4 4.1 2.3 4.6
Eve 2.2 2.9 2.0 3.0 3.7 1.8
Frank 4.5 3.9 0.6 2.7 5.5 0.1

6 Top-N Recommendations

Recommend items a user hasn't rated, ranked by predicted rating.

In [1]:
target = 'Alice'
unrated = ratings.loc[target].isna()
recs = pred_df.loc[target, unrated].sort_values(ascending=False).head(3)
print(f'Recommendations for {target}:')
for item, score in recs.items():
    print(f'  {item}: predicted {score:.2f}')
Recommendations for Alice:
  Avatar: predicted 2.00
  Notebook: predicted -0.60

7 The Cold-Start Problem

New users or items have no ratings, breaking similarity-based methods. Remedies include content-based features, popularity baselines, and hybrid recommenders.

Case Study: Movie Recommendations for a New User

A new user rates two sci-fi films; we recommend more via item-based similarity.

In [1]:
new_user = pd.Series({'Matrix': 5, 'Inception': 5, 'Titanic': np.nan, 'Avatar': np.nan, 'Interstellar': np.nan, 'Notebook': np.nan})
new_filled = new_user.fillna(ratings.mean().round(1))
sim_items = item_sim_df['Matrix'].sort_values(ascending=False)
print('Items most similar to Matrix (that the new user can be recommended):')
print(sim_items.drop('Matrix').head(3).round(2))
Items most similar to Matrix (that the new user can be recommended):
Interstellar    0.83
Inception       0.55
Avatar          0.49
Name: Matrix, dtype: float64

Exercises

  1. Build a 4x5 ratings matrix with some NaNs and display it.
  2. Compute user–user cosine similarity and identify each user's nearest neighbor.
  3. Predict a missing rating using user-based CF.
  4. Compute item–item similarity and predict via item-based CF.
  5. Fit TruncatedSVD with 2 components and print the latent factors.
  6. Produce top-3 recommendations for one user.
  7. Explain the difference between user-based and item-based CF.
  8. Describe the cold-start problem and one solution.
  9. Discuss how a popularity baseline helps new users.
  10. Add a new user and recommend items using item-based similarity.

Python Data Science: From Foundations to Applications — Chapter 23