Thursday, September 10, 2026
HomeData ScienceBuilding Recommendation Systems in Python – Collaborative & Content-Based 2026

Building Recommendation Systems in Python – Collaborative & Content-Based 2026

Table of Content

Recommendation systems drive billions of dollars in e-commerce, streaming, and social media revenue. Netflix’s recommendations save $1 billion per year in prevented churn. Spotify’s Discover Weekly keeps users engaged. Amazon’s “customers also bought” drives 35% of its revenue. This guide builds recommendation systems from scratch using Python, covering collaborative filtering, content-based filtering, and matrix factorisation.

Types of Recommendation Systems

Collaborative filtering recommends items based on the behaviour of similar users or items — “users like you also liked.” It requires no item metadata but struggles with new users and items (cold start). Content-based filtering recommends items similar to what a user has liked before, based on item features. It handles cold start for items but requires good metadata. Hybrid systems combine both, overcoming individual weaknesses.

Data Preparation

The word DATA and a star symbol stenciled in dark dots on glass
Photo by Claudio Schwarz on Unsplash
import pandas as pd
import numpy as np
from scipy.sparse import csr_matrix
from sklearn.metrics.pairwise import cosine_similarity

# Movie rating dataset
ratings = pd.DataFrame({
    'user_id': [1,1,1,2,2,3,3,3,4,4,4,5,5],
    'movie_id': [1,2,3,1,4,2,3,5,1,3,5,2,4],
    'rating':  [5,4,3,4,5,3,4,5,2,5,4,4,3]
})

movies = pd.DataFrame({
    'movie_id': [1,2,3,4,5],
    'title':    ['Inception','Interstellar','The Dark Knight','Dunkirk','Tenet'],
    'genre':    ['Sci-Fi','Sci-Fi','Action','War','Sci-Fi']
})

print(f'Users: {ratings["user_id"].nunique()} | Movies: {ratings["movie_id"].nunique()}')
print(f'Ratings: {len(ratings)} | Sparsity: {1 - len(ratings)/(ratings["user_id"].nunique() * ratings["movie_id"].nunique()):.1%}')

User-Based Collaborative Filtering

# Create user-item matrix
user_item = ratings.pivot_table(index='user_id', columns='movie_id',
                                 values='rating', fill_value=0)
print(user_item)

# User similarity matrix (cosine similarity)
user_sim = cosine_similarity(user_item)
user_sim_df = pd.DataFrame(user_sim,
                             index=user_item.index,
                             columns=user_item.index)

def get_user_recommendations(user_id, n=3):
    # Find most similar users (exclude self)
    similar_users = (user_sim_df[user_id]
                     .drop(user_id)
                     .sort_values(ascending=False))

    top_users = similar_users.head(3).index.tolist()

    # Items the target user has NOT rated
    user_rated     = set(ratings[ratings['user_id'] == user_id]['movie_id'])
    candidate_movies = set(ratings[ratings['user_id'].isin(top_users)]['movie_id'])
    unrated        = candidate_movies - user_rated

    # Score by weighted average of similar users' ratings
    scores = {}
    for movie in unrated:
        weights, weighted_ratings = 0, 0
        for sim_user in top_users:
            sim_rating = ratings[
                (ratings['user_id'] == sim_user) &
                (ratings['movie_id'] == movie)
            ]['rating']
            if not sim_rating.empty:
                sim  = similar_users[sim_user]
                weighted_ratings += sim * sim_rating.values[0]
                weights          += sim
        if weights > 0:
            scores[movie] = weighted_ratings / weights

    top_movies = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:n]
    return [(movies[movies['movie_id']==mid]['title'].values[0], round(score, 2))
            for mid, score in top_movies]

print(f'
Recommendations for User 1:')
for title, score in get_user_recommendations(1):
    print(f'  {title}: predicted rating {score}')

Item-Based Collaborative Filtering

a colorful toy on a table
Photo by Shubham Dhage on Unsplash
# Item similarity (often more stable than user similarity)
item_sim = cosine_similarity(user_item.T)
item_sim_df = pd.DataFrame(item_sim,
                             index=user_item.columns,
                             columns=user_item.columns)

def get_item_recommendations(movie_id, n=3):
    similar = item_sim_df[movie_id].sort_values(ascending=False)
    similar = similar.drop(movie_id).head(n)
    result  = []
    for mid, score in similar.items():
        title = movies[movies['movie_id'] == mid]['title'].values[0]
        result.append((title, round(score, 3)))
    return result

print(f'
Movies similar to Inception:')
for title, score in get_item_recommendations(1):
    print(f'  {title}: similarity {score}')

Matrix Factorisation with SVD

from sklearn.decomposition import TruncatedSVD
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

# Full user-item matrix (with NaN for unrated)
user_item_full = ratings.pivot_table(index='user_id', columns='movie_id',
                                      values='rating')

# Fill NaN with row mean (user's average rating)
user_means = user_item_full.mean(axis=1)
user_item_filled = user_item_full.sub(user_means, axis=0).fillna(0)

# SVD: decompose into U × Σ × V^T
svd    = TruncatedSVD(n_components=2, random_state=42)
U      = svd.fit_transform(user_item_filled)
sigma  = np.diag(svd.singular_values_)
Vt     = svd.components_

# Reconstruct predictions
predictions = pd.DataFrame(
    np.dot(U, np.dot(sigma, Vt)) + user_means.values.reshape(-1, 1),
    index=user_item_full.index,
    columns=user_item_full.columns
).clip(1, 5)

print('
Predicted ratings matrix:')
print(predictions.round(1))

def svd_recommend(user_id, n=3):
    user_rated = set(ratings[ratings['user_id'] == user_id]['movie_id'])
    user_preds = predictions.loc[user_id].drop(index=list(user_rated), errors='ignore')
    top = user_preds.sort_values(ascending=False).head(n)
    return [(movies[movies['movie_id'] == mid]['title'].values[0], round(score, 2))
            for mid, score in top.items()]

print(f'
SVD Recommendations for User 4:')
for title, score in svd_recommend(4):
    print(f'  {title}: predicted {score}')

Content-Based Filtering

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import linear_kernel

# Movie metadata
movies_meta = pd.DataFrame({
    'movie_id': [1, 2, 3, 4, 5],
    'title':    ['Inception', 'Interstellar', 'The Dark Knight', 'Dunkirk', 'Tenet'],
    'description': [
        'A thief who steals corporate secrets through dream-sharing technology',
        'A team of explorers travel through a wormhole in space to ensure humanity survival',
        'Batman fights the menace known as the Joker in Gotham City',
        'Allied soldiers from Belgium Britain and Canada are surrounded by German Army in Dunkirk',
        'A CIA operative teams up to prevent a world war through time manipulation'
    ],
    'genre': ['sci-fi thriller', 'sci-fi drama', 'action thriller', 'war drama', 'sci-fi thriller']
})

movies_meta['soup'] = movies_meta['description'] + ' ' + movies_meta['genre']

tfidf = TfidfVectorizer(stop_words='english')
tfidf_matrix = tfidf.fit_transform(movies_meta['soup'])

cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)

title_to_idx = {row['title']: idx for idx, row in movies_meta.iterrows()}

def content_recommend(title, n=3):
    idx    = title_to_idx[title]
    scores = list(enumerate(cosine_sim[idx]))
    scores = sorted(scores, key=lambda x: x[1], reverse=True)[1:n+1]
    return [(movies_meta.iloc[i]['title'], round(score, 3)) for i, score in scores]

print(f'
Content-based: similar to Inception:')
for title, score in content_recommend('Inception'):
    print(f'  {title}: {score}')

Production with Surprise Library

pip install scikit-surprise

from surprise import SVD, Dataset, Reader, accuracy
from surprise.model_selection import cross_validate, train_test_split

reader   = Reader(rating_scale=(1, 5))
data     = Dataset.load_from_df(ratings[['user_id', 'movie_id', 'rating']], reader)

trainset, testset = train_test_split(data, test_size=0.2, random_state=42)

algo = SVD(n_factors=50, n_epochs=20, lr_all=0.005, reg_all=0.02)
algo.fit(trainset)
preds = algo.test(testset)

print(f'RMSE: {accuracy.rmse(preds):.4f}')
print(f'MAE:  {accuracy.mae(preds):.4f}')

# Cross-validate
cv_results = cross_validate(algo, data, measures=['RMSE', 'MAE'],
                             cv=5, verbose=True)
print(f'Mean RMSE: {cv_results["test_rmse"].mean():.4f}')

Conclusion

Start with item-based collaborative filtering — it is interpretable, stable, and effective even with moderate amounts of data. Move to matrix factorisation (SVD or ALS) when you need better accuracy and can tolerate a black box. Use content-based filtering to solve the cold-start problem for new items. In production, combine all three in a hybrid system and serve recommendations from pre-computed embeddings via approximate nearest neighbour search (Faiss, Annoy, HNSW) for millisecond response times at scale.

Leave feedback about this

  • Rating

Durgesh Kekare
Durgesh Kekarehttps://www.dataexpertise.in
Durgesh Kekare is a data science educator and founder of DataExpertise.in. With expertise in Python, machine learning, and analytics, he helps 10,000+ learners break into data careers.

Latest Posts

List of Categories