Collaborative Filtering

Build recommender systems with user-based and item-based collaborative filtering

beginner45 min
On this page

The Big Picture

Collaborative Filtering is built on a deceptively simple premise: "Users who agreed in the past will agree in the future." If Alice and Bob both loved the same movie, then a movie that Bob loves (but Alice hasn't seen) is probably something Alice would like too.

The entire field rests on this idea, and it leads to three major paradigms:

Content-Based

Use item features (genre, category, keywords) to recommend items similar to those the user liked.

Features Item Similarity

User-Based CF

Find users similar to the target user, then recommend items those similar users liked.

User Similarity Neighborhoods

Item-Based CF

Find items similar to items the user liked, then recommend those similar items.

Item Similarity Stable

Matrix Factorization

Learn latent factors that capture both user preferences and item attributes. Fast, scalable, modern.

Latent Factors Scalable

The Foundation: User-Item Interaction Matrix

All collaborative filtering methods start with the same data structure — a matrix where rows are users, columns are items, and entries are interactions (ratings, clicks, purchases, etc.):

User-Item Interaction Matrix
Item 1 Item 2 Item 3 Item 4 Item 5 User A User B User C User D 5 ? 4 ? 3 ? 5 ? 4 ? 4 ? 5 ? 3 ? 4 ? 5 ? Known Interaction (1-5 rating) Unknown (to be predicted)
Key Insight: In real-world recommendation systems, this matrix is extremely sparse. For an e-commerce platform with 1M users and 100K items, only ~0.001% of entries are filled. CF methods must intelligently fill these gaps.

Recommendation Example: E-Commerce Platform

Imagine an e-commerce recommendation engine. Users browse and rate products. We want to predict which products they'll rate highly next, so we can show those products in their feed.

The Scenario

We have:

  • 6 Users: Alice, Bob, Charlie, Diana, Eve, Frank
  • 5 Products: Laptop, Phone, Tablet, Monitor, Keyboard
  • Ratings: 1-5 scale (1 = hate, 5 = love)
  • Task: Predict Alice's rating for Monitor (currently unknown)
E-Commerce Ratings Matrix (1-5 scale)
Laptop Phone Tablet Monitor Keyboard Alice 5 ? 4 ? 3 Bob ? 5 ? 4 ? Charlie 4 ? 5 ? 2 Known Rating Missing (Sparse) Target Prediction
The Problem: Alice rated Laptop (5), Tablet (4), and Keyboard (3). The Monitor is highlighted — we need to predict her rating for it. Based on similar users or similar items, can we predict she'd like it?

Content-Based Filtering

Content-Based Filtering does not use other users' opinions. Instead, it extracts features from items and matches them against a user's preferences.

How It Works

  1. Extract Item Features: For products, features might be category, brand, price range, keywords in description
  2. Build User Profile: Based on items the user liked, construct a profile of preferred features
  3. Score Candidates: Compute similarity between user profile and new item features
  4. Rank & Recommend: Return highest-scoring candidates
Content-Based Filtering Pipeline
User Liked Laptop (5★) Tablet (4★) Extract Features Category, Brand Price Range Build User Profile Preferred: Tech Premium Price Candidates Monitor, Phone Keyboard... Cosine Similarity user_profile = TF-IDF(liked_items) item_features = TF-IDF(candidate_item) score = cos_sim(user_profile, item_features) Recommendations Monitor (0.92) → Phone (0.88) → Keyboard (0.75)

Strengths

  • No Cold Start for Items: Can recommend a new product immediately if you have its features
  • Transparent: "You liked X features; this item has similar features"
  • Handles Niche Tastes: Works well for users with unique preferences (doesn't require similar users)
  • No Privacy Issues: Doesn't use other users' data

Weaknesses

  • Limited Diversity: Tends to recommend items very similar to what users already liked (filter bubble)
  • Cold Start for Users: New users with no history have no profile yet
  • Feature Engineering: Requires manual or careful extraction of item features
  • Can't Discover: Won't recommend unexpected items that break the pattern

When to Use Content-Based Filtering

Scenario Fit
Rich metadata available for items (taxonomy, keywords, attributes) Excellent
Need interpretable recommendations ("why this item?") Excellent
Many new items but few users Excellent
Long tail, niche products dominate catalog Good
Users with very personal, unique tastes Good
New user bootstrapping (use as fallback) Good
Want to encourage serendipitous discoveries Fair

User-Based Collaborative Filtering

User-Based CF finds users similar to the target user, then recommends items those similar users rated highly.

Algorithm Steps

  1. Build the user-item rating matrix from historical data
  2. For target user u, compute similarity to all other users using a similarity metric (Pearson, Cosine)
  3. Select top-K most similar users (neighbors)
  4. For an unseen item i, predict user u's rating as a weighted average of neighbors' ratings

Prediction Formula

r̂(u,i) = r̄_u + Σ_{v ∈ N(u)} sim(u,v) · (r_{v,i} - r̄_v)
          ────────────────────────────────────
                Σ_{v ∈ N(u)} |sim(u,v)|

Where:
  r̂(u,i)     = predicted rating for user u, item i
  r̄_u        = mean rating of user u (baseline)
  N(u)       = set of K similar neighbors of u
  sim(u,v)   = similarity between users u and v
  r_{v,i}    = actual rating by neighbor v for item i
  r̄_v        = mean rating of neighbor v

Concrete Numerical Example

Let's predict Alice's rating for Monitor using user-based CF:

User-Based CF Example: Predict Alice → Monitor
Step 1: User Similarity Matrix User Sim(Alice, X) Bob 0.92 Charlie 0.88 Diana 0.45 Eve 0.32 Step 2: Select Top K=2 Neighbors Top-2 Neighbors 1. Bob (sim=0.92) Monitor: 4 2. Charlie (sim=0.88) Monitor: 5 Step 3: Weighted Average r̂(Alice, Monitor) = (0.92 × 4) + (0.88 × 5) ────────────────────── 0.92 + 0.88 = 3.68 + 4.4 = 8.08 = 4.42 1.8 1.8 Prediction: Alice would rate Monitor ≈ 4.4 / 5 Alice's Similarity Network Alice

User Similarity Network

User Similarity Network Visualization
Alice Bob 0.92 Charlie 0.88 Diana 0.45 Eve 0.32 Similarity Strength High (> 0.8) Low (< 0.6)

Strengths & Weaknesses

✓ Strengths

  • Simple to understand
  • Can explain recommendations
  • Works well for tight user communities
  • Captures emergent tastes

✗ Weaknesses

  • O(n²) similarity computation for n users
  • Doesn't scale to millions of users
  • Sparse data makes similarity unreliable
  • Popularity bias (all neighbors like popular items)

Item-Based Collaborative Filtering

Item-Based CF is the transpose of User-Based: instead of finding similar users, we find items similar to what the user liked, then recommend those.

Algorithm Steps

  1. Build user-item rating matrix
  2. For each pair of items, compute similarity based on user rating patterns
  3. For target user u and candidate item i, find items the user rated that are similar to i
  4. Predict user u's rating as weighted average of similar items' ratings

Prediction Formula

r̂(u,i) = Σ_{j ∈ N(i)} sim(i,j) · r_{u,j}
          ────────────────────────────────
             Σ_{j ∈ N(i)} |sim(i,j)|

Where:
  r̂(u,i)     = predicted rating for user u, item i
  N(i)       = set of items similar to i (that u has rated)
  sim(i,j)   = similarity between items i and j
  r_{u,j}    = actual rating by user u for item j

Why Item-Based Often Works Better

  • More Stable: Item similarities are more stable over time than user similarities. A laptop stays similar to a tablet, but user preferences shift.
  • Scales Better: Typically fewer unique items than users (100K items vs 1M users)
  • Faster Computation: Item-item similarities can be pre-computed offline; at prediction time, only a lookup
  • Real-World Success: Amazon's recommendation engine was built on item-based CF

Item Similarity Network

Item Similarity Graph for E-Commerce
Laptop Tablet Monitor Phone Keyboard 0.89 0.85 0.92 0.54 0.61 0.28 Similarity Strength High (>0.8) Medium (0.5-0.8)

Concrete Example: Predicting Alice → Monitor

Alice's history: Laptop (5), Tablet (4), Keyboard (3)

Find similar items to Monitor:

  • Laptop ← Monitor (sim = 0.85) ✓ Alice rated it 5
  • Tablet ← Monitor (sim = 0.92) ✓ Alice rated it 4
  • Keyboard ← Monitor (sim = 0.28) ✓ Alice rated it 3 (weak connection)

r̂(Alice, Monitor) = (0.85×5 + 0.92×4 + 0.28×3) / (0.85 + 0.92 + 0.28)
                             = (4.25 + 3.68 + 0.84) / 2.05 ≈ 4.37

Comparison: Item-Based vs User-Based

Aspect User-Based Item-Based
Scalability O(n²) users — doesn't scale O(m²) items — usually better
Computation Timing Compute at prediction time (slow) Pre-compute similarities (fast at inference)
Stability User tastes change frequently Item similarity is stable over time
Data Sparsity Suffers more (each user rates few items) More resilient (items rated by many)
Diversity of Recommendations Potentially more diverse Can be repetitive (always similar items)

Similarity Measures

The choice of similarity metric is crucial. Different metrics work better for different data distributions and use cases.

1. Cosine Similarity

sim(u, v) = u · v / (||u|| × ||v||)
           = Σ(u_i × v_i) / (√Σu_i² × √Σv_i²)

Geometric interpretation: angle between vectors
Range: [-1, 1] for vectors (typically [0, 1] for ratings)
Ignores magnitude — only direction matters
Example: Users u = [5, 4, 3] and v = [5, 4, 2]
u·v = 25 + 16 + 6 = 47
||u|| = √(25+16+9) = √50 ≈ 7.07
||v|| = √(25+16+4) = √45 ≈ 6.71
sim = 47 / (7.07 × 6.71) ≈ 0.99 (very similar)

2. Pearson Correlation

sim(u, v) = Σ(u_i - ū)(v_i - v̄) / √[Σ(u_i-ū)² × Σ(v_i-v̄)²]

Where ū = mean of user u's ratings, v̄ = mean of user v's ratings

Accounts for rating bias (e.g., one user is more generous)
Subtracts mean before computing similarity
More robust to different rating scales
Why it matters: Bob always rates high (mean 4.5), Alice is critical (mean 2.5). They might still have similar preferences! Pearson correlation accounts for this bias.

3. Jaccard Similarity

sim(u, v) = |I_u ∩ I_v| / |I_u ∪ I_v|

Where I_u = set of items rated/clicked by user u

Binary similarity: did they interact or not?
Ignores ratings — only presence/absence
Good for implicit feedback (clicks, purchases)
Example: Alice interacted with {A, B, C}, Bob with {B, C, D}
Intersection = {B, C}, Union = {A, B, C, D}
sim = 2 / 4 = 0.5

4. Adjusted Cosine (for Item-Based CF)

sim(i, j) = Σ_u (r_{u,i} - r̄_u)(r_{u,j} - r̄_u) / √[Σ(r_{u,i}-r̄_u)² × Σ(r_{u,j}-r̄_u)²]

Cosine similarity after subtracting user mean from each rating
Removes user bias before computing item similarity
Standard choice for collaborative filtering

Comparison Table

Metric Best For Pros Cons
Cosine Dense, normalized data; item-based CF Fast to compute; geometric interpretation Ignores magnitude; sensitive to sparsity
Pearson Explicit ratings; user-based CF Handles rating bias well; intuitive Requires enough common items; can be unstable with few data points
Jaccard Binary/implicit feedback; clicks, purchases Simple; works for implicit data Ignores strength of preference; binary only
Adjusted Cosine Item-based CF with explicit ratings Removes user bias; stable More computation; requires mean centering

Computational Complexity Comparison

For n users, m items, k average interactions per user:

  • Cosine: O(k) per pair → O(n² × k) user-based, O(m² × k) item-based
  • Pearson: O(k) per pair → same complexity but slower constant
  • Jaccard: O(k) per pair → fastest in practice (simple intersection/union)

How Learning & Prediction Happens

There are two fundamental approaches to collaborative filtering: memory-based and model-based. They differ in when the "learning" happens.

Memory-Based CF (Lazy Learning)

Memory-based methods store all interaction data and compute at prediction time.

Pipeline

Memory-Based CF Pipeline
Collect & Store Data (Matrix) User Query (Item X?) Compute Similarities (On-the-fly) Predict & Rank (Return top-K) Key Property: Lazy Learning • No training phase — simply store the matrix • All computation happens at prediction time (slow but accurate)
Pros:
  • No training needed — can use immediately
  • New data instantly reflected in recommendations
  • Transparent — can explain why (found similar users/items)
Cons:
  • Prediction is slow (O(n) or O(m) similarity computations)
  • Doesn't scale to millions of users/items
  • Can't capture complex latent patterns
  • Struggles with sparse data

Model-Based CF (Learning Offline)

Model-based methods learn a compressed representation offline, then use it for fast predictions.

Key Nuances

Aspect Consideration
Explicit vs Implicit Feedback Explicit = ratings (1-5); Implicit = clicks/purchases (binary). Implicit is more common; handle missing data carefully.
Popularity Bias CF tends to recommend popular items. Fix: popularity-aware loss, item regularization, hybrid approaches.
Temporal Dynamics Preferences and popularity change over time. Some methods use time-aware weighting.
Data Sparsity Most matrix entries are zero. Model-based CF (matrix factorization) handles this better.

Matrix Factorization

Matrix Factorization is the dominant model-based approach. The core idea: decompose the user-item matrix into two lower-rank matrices capturing latent factors.

The Decomposition

R ≈ P × Q^T

Where:
  R       = user-item rating matrix (n_users × n_items)
  P       = user latent factor matrix (n_users × k)
  Q       = item latent factor matrix (n_items × k)
  k       = number of latent factors (typically 20-200)

Prediction: r̂_{ui} = p_u · q_i^T (dot product)

Learning via SGD

Minimize: L = Σ_{(u,i)} (r_{ui} - r̂_{ui})^2 + λ(||P||² + ||Q||²)

Update rules (SGD):
  e_{ui} = r_{ui} - p_u · q_i^T
  p_u ← p_u + α(e_{ui} · q_i - λ · p_u)
  q_i ← q_i + α(e_{ui} · p_u - λ · q_i)

With Biases

r̂_{ui} = μ + b_u + b_i + p_u · q_i^T

Where:
  μ    = global mean rating
  b_u  = user bias
  b_i  = item bias
Bridge to Factorization Machines: Matrix Factorization learns two matrices. Factorization Machines (FM) extend this to handle arbitrary feature interactions and multiple feature types. For detailed treatment of FM and DeepFM, see the Factorization Machines & DeepFM guide.

Cold Start & Sparsity Problems

Real-world systems face two major challenges: cold start (new users/items) and sparsity (mostly missing data).

Cold Start Solutions

Scenario Challenge Solutions
New User No history Ask for preferences • Show popular items • Hybrid approach
New Item No interactions Use item features • Content-based • Hybrid

Sparsity Solutions

Solution How It Works
Matrix Factorization Learn latent factors that interpolate missing values
Implicit Feedback Use clicks, purchases (more abundant than ratings)
Hybrid Approach Combine CF with content-based features
Side Information Use user/item features (age, category, price)

When to Use What: Decision Framework

Comparison Table

Approach Data Requirements Scalability Cold Start Best For
Content-Based Rich item features Excellent Good for items Known metadata; interpretability
User-Based CF User ratings Poor (O(n²)) Fair Small communities
Item-Based CF User ratings Good (O(m²)) Fair Stable items; fewer unique items
Matrix Factorization Sparse interactions Excellent Poor Large-scale; offline training feasible
Hybrid Multiple sources Good Excellent Production systems

Hybrid Approaches

In production, hybrid systems combine multiple methods for best results:

Content + CF

Use content-based for new items, blend with CF for existing items.

Weighted Ensemble

Combine scores with learned weights from multiple methods.

Python Implementation

Complete implementations using NumPy and Pandas.

User-Based Collaborative Filtering

import numpy as np

class UserBasedCF:
    def __init__(self, k=5):
        self.k = k
        self.ratings = {}

    def fit(self, ratings_df):
        for _, row in ratings_df.iterrows():
            uid = row['user_id']
            if uid not in self.ratings:
                self.ratings[uid] = {}
            self.ratings[uid][row['item_id']] = row['rating']
        self.user_ids = list(self.ratings.keys())

    def _pearson_sim(self, user1, user2):
        items = (set(self.ratings[user1].keys()) &
                 set(self.ratings[user2].keys()))
        if len(items) < 2:
            return 0
        r1 = np.array([self.ratings[user1][i] for i in items])
        r2 = np.array([self.ratings[user2][i] for i in items])
        m1, m2 = r1.mean(), r2.mean()
        num = ((r1 - m1) * (r2 - m2)).sum()
        denom = np.sqrt(((r1-m1)**2).sum() *
                        ((r2-m2)**2).sum())
        return num / denom if denom > 0 else 0

    def predict(self, user_id, item_id):
        if user_id not in self.ratings:
            return 3.0
        if item_id in self.ratings[user_id]:
            return self.ratings[user_id][item_id]

        sims = []
        for other_user in self.user_ids:
            if other_user == user_id:
                continue
            if item_id in self.ratings[other_user]:
                sim = self._pearson_sim(user_id, other_user)
                sims.append((sim, self.ratings[other_user][item_id]))

        if not sims:
            return 3.0

        sims.sort(key=lambda x: -abs(x[0]))
        neighbors = sims[:self.k]
        total_sim = sum(abs(s) for s, _ in neighbors)
        return sum(s * r for s, r in neighbors) / total_sim

Item-Based Collaborative Filtering

import numpy as np

class ItemBasedCF:
    def __init__(self, k=5):
        self.k = k
        self.ratings = {}
        self.item_sim = {}

    def fit(self, ratings_df):
        for _, row in ratings_df.iterrows():
            uid = row['user_id']
            if uid not in self.ratings:
                self.ratings[uid] = {}
            self.ratings[uid][row['item_id']] = row['rating']

        items = set()
        for user_ratings in self.ratings.values():
            items.update(user_ratings.keys())
        items = list(items)

        for i, item1 in enumerate(items):
            for item2 in items[i+1:]:
                sim = self._item_similarity(item1, item2)
                self.item_sim[(item1, item2)] = sim
                self.item_sim[(item2, item1)] = sim

    def _item_similarity(self, item1, item2):
        users_both = [u for u in self.ratings
                      if item1 in self.ratings[u] and
                      item2 in self.ratings[u]]
        if len(users_both) < 2:
            return 0

        r1 = np.array([self.ratings[u][item1]
                       for u in users_both])
        r2 = np.array([self.ratings[u][item2]
                       for u in users_both])
        means = np.array([np.mean(list(self.ratings[u].values()))
                          for u in users_both])

        r1c = r1 - means
        r2c = r2 - means
        num = (r1c * r2c).sum()
        denom = np.sqrt((r1c**2).sum() * (r2c**2).sum())
        return num / denom if denom > 0 else 0

    def predict(self, user_id, item_id):
        if user_id not in self.ratings:
            return 3.0
        user_items = self.ratings[user_id]
        sims = []
        for user_item in user_items:
            key = (item_id, user_item)
            if key not in self.item_sim:
                key = (user_item, item_id)
            if key in self.item_sim:
                sim = self.item_sim[key]
                if sim > 0:
                    sims.append((sim, user_items[user_item]))

        if not sims:
            return 3.0

        sims.sort(key=lambda x: -x[0])
        neighbors = sims[:self.k]
        total_sim = sum(s for s, _ in neighbors)
        return sum(s * r for s, r in neighbors) / total_sim

Matrix Factorization (SGD)

import numpy as np

class MatrixFactorization:
    def __init__(self, factors=50, lr=0.01, lam=0.01, epochs=100):
        self.factors = factors
        self.lr = lr
        self.lam = lam
        self.epochs = epochs

    def fit(self, ratings_df):
        users = ratings_df['user_id'].unique()
        items = ratings_df['item_id'].unique()
        self.user_map = {u: i for i, u in enumerate(users)}
        self.item_map = {i: j for j, i in enumerate(items)}

        n_u, n_i = len(users), len(items)
        self.P = np.random.normal(0, 0.1, (n_u, self.factors))
        self.Q = np.random.normal(0, 0.1, (n_i, self.factors))
        self.bu = np.zeros(n_u)
        self.bi = np.zeros(n_i)
        self.mean = ratings_df['rating'].mean()

        for _ in range(self.epochs):
            for _, row in ratings_df.iterrows():
                u = self.user_map[row['user_id']]
                i = self.item_map[row['item_id']]
                r = row['rating']
                r_hat = (self.mean + self.bu[u] + self.bi[i] +
                         np.dot(self.P[u], self.Q[i]))
                e = r - r_hat
                self.bu[u] += self.lr * (e - self.lam * self.bu[u])
                self.bi[i] += self.lr * (e - self.lam * self.bi[i])
                self.P[u] += self.lr * (e * self.Q[i] - self.lam * self.P[u])
                self.Q[i] += self.lr * (e * self.P[u] - self.lam * self.Q[i])

    def predict(self, user_id, item_id):
        if user_id not in self.user_map or item_id not in self.item_map:
            return self.mean
        u = self.user_map[user_id]
        i = self.item_map[item_id]
        return (self.mean + self.bu[u] + self.bi[i] +
                np.dot(self.P[u], self.Q[i]))

Quick Test

import pandas as pd

# Create sample data
data = {
    'user_id': [0, 0, 0, 1, 1, 1, 2, 2, 2],
    'item_id': [0, 1, 2, 1, 2, 3, 0, 2, 3],
    'rating': [5, 4, 3, 5, 4, 2, 4, 5, 3]
}
df = pd.DataFrame(data)

# Test User-Based CF
cf = UserBasedCF(k=2)
cf.fit(df)
pred = cf.predict(user_id=0, item_id=3)
print(f"User-Based prediction: {pred:.2f}")

# Test Item-Based CF
icf = ItemBasedCF(k=2)
icf.fit(df)
pred = icf.predict(user_id=0, item_id=3)
print(f"Item-Based prediction: {pred:.2f}")

# Test Matrix Factorization
mf = MatrixFactorization(factors=5, epochs=20)
mf.fit(df)
pred = mf.predict(user_id=0, item_id=3)
print(f"Matrix Factorization prediction: {pred:.2f}")
End of Collaborative Filtering