Add Content-Based Filtering
Add content-based filtering — Applied AI engineering.
Focus: add content-based filtering
So you've built a recommendation screen that shows the same five items to every user. It's the "cold start" problem in its purest form: zero interaction data, zero personalization, zero chance of keeping anyone engaged. But here's the trick — you don't need user history to start recommending well. Content-based filtering uses the attributes of the items themselves — tags, descriptions, categories — to recommend things that look like what the user already liked. In this lesson, you'll learn how to add content-based filtering to a Python application, from the core math to a production-ready implementation.
The problem this lesson solves
Every recommendation system starts with a gap. Collaborative filtering — the "people who bought this also bought that" approach — is powerful, but it needs mountains of historical interaction data before it can recommend anything. For a new app, a new user, or a catalog of niche items, that data simply doesn't exist yet. The result? A bland, generic experience that drives users away.
Content-based filtering attacks this from a different angle. Instead of relying on what other users did, it analyzes the content of every item and matches it to the user's expressed or inferred preferences. If a user liked a Python tutorial about decorators, you can recommend other Python tutorials, even if that user has only interacted with a single item. It works from day one, with no historical data, and it's the backbone of many real-world systems from news readers to job boards.
By the end of this lesson, you'll be able to add content-based filtering to your own pipeline, handle vectorization and similarity scoring, and know exactly when to choose this approach over alternatives.
Core concept / mental model
Think of content-based filtering as a picture-matching game. Every item in your catalog gets a "picture" — a list of numerical features that capture its essence. A movie might be described by its genre, director, and keywords. A product might be described by its color, size, and brand. A user's profile is also a picture: the average of all the pictures of items they've liked.
The core idea is similarity. When a user wants a recommendation, you compare the user's picture to every item's picture and rank by closeness. The item with the most similar picture wins. This is mathematically expressed as cosine similarity:
similarity = (A · B) / (||A|| * ||B||)
where A and B are feature vectors. The result ranges from -1 (completely opposite) to 1 (identical). For text based features, we often use TF-IDF to turn raw strings into vectors, then compute cosine similarity.
Why cosine similarity? Because it ignores the magnitude of the vectors — it only cares about the angle between them. Two documents that are about the same topic but wildly different lengths will still get a high similarity score. That's exactly what we want.
Key terms
- Feature vector — a mathematical representation of an item's attributes
- TF-IDF — a scoring technique that weights words by how common they are across a corpus
- Cosine similarity — a measure of directional match between two vectors
- Cold start — the problem of recommending when you have no user history
How it works step by step
To add content-based filtering, you'll follow a repeatable sequence:
-
Select features: Choose which attributes of your items matter for recommendation. For a book, that's genre, author, and keywords. For a product, that's categories and descriptive text.
-
Vectorize: Convert the raw attribute texts into numerical vectors. The most common tool is TF-IDF (Term Frequency-Inverse Document Frequency). It assigns high weights to words that appear often in one document but rarely in others — those are the words that best distinguish topics.
-
Build a user profile: If you have explicit likes or implicit signals, combine the vectors of items the user has shown interest in — e.g., the average vector. If you have no history yet, you can ask the user to pick a few seed items or use their search queries.
-
Score all items: For each catalog item, compute the cosine similarity between its vector and the user profile vector.
-
Rank & return: Sort items by similarity score, take the top-N, and serve them.
This pipeline is easy to implement with scikit-learn and pandas in Python, as you'll see in the next section.
Hands-on walkthrough
Now let's get your hands dirty. We'll build a mini content-based recommender for a list of Python tutorials. We'll use TF-IDF to vectorize the descriptions and cosine similarity to rank.
Prerequisites
Make sure you have scikit-learn and pandas installed:
pip install scikit-learn pandas
Step 1: Prepare the data
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
# Sample item catalog data
data = {
'id': [1, 2, 3, 4],
'title': ['Async Python', 'Decorators Deep Dive', 'FastAPI Crash Course', 'Data Science with Pandas'],
'description': [
'Learn asyncio and await patterns for concurrent Python',
'Master decorators, closures, and higher-order functions',
'Build REST APIs with FastAPI and Python async',
'Data analysis and cleaning with pandas and numpy'
]
}
df = pd.DataFrame(data)
print(df)
Expected output:
id title description
0 1 Async Python Learn asyncio and await patterns for concurrent Python
1 2 Decorators Deep Dive Master decorators, closures, and higher-order functions
2 3 FastAPI Crash Course Build REST APIs with FastAPI and Python async
3 4 Data Science with Pandas Data analysis and cleaning with pandas and numpy
Step 2: Vectorize the descriptions
# Convert text to TF-IDF feature vectors
tfidf = TfidfVectorizer(stop_words='english')
tfidf_matrix = tfidf.fit_transform(df['description'])
print(f"Feature matrix shape: {tfidf_matrix.shape}")
Expected output:
Feature matrix shape: (4, 14)
Each row is an item, each column is a TF-IDF weighted term.
Step 3: Compute similarity for a user profile
Let's say a user liked the "Async Python" tutorial. We'll build a profile as the average of the liked item's vector (here, just one).
# User profile: average vector of liked items
liked_index = 0 # row index for "Async Python"
user_profile = tfidf_matrix[liked_index]
# Compute cosine similarity between profile and all items
similarities = cosine_similarity(user_profile, tfidf_matrix).flatten()
# Rank items by similarity, excluding the already liked item
ranked_indices = similarities.argsort()[::-1][1:]
print("Recommendations for a user who liked 'Async Python':")
for idx in ranked_indices:
print(f"- {df.iloc[idx]['title']} (similarity {similarities[idx]:.2f})")
Expected output:
Recommendations for a user who liked 'Async Python':
- FastAPI Crash Course (similarity 0.22)
- Decorators Deep Dive (similarity 0.10)
- Data Science with Pandas (similarity 0.00)
Notice how "FastAPI Crash Course" scores highest because it shares "async" and "Python" with the liked item — that's content-based filtering in action.
Step 4: Make it reusable as a function
def recommend_items(item_id, df, tfidf_matrix):
"""Return top-3 recommendations for a given item ID."""
idx = df.index[df['id'] == item_id][0]
user_profile = tfidf_matrix[idx]
sims = cosine_similarity(user_profile, tfidf_matrix).flatten()
# rank, exclude the input item
top = sims.argsort()[::-1][1:4]
return [(df.iloc[i]['title'], round(sims[i], 2)) for i in top]
print(recommend_items(1, df, tfidf_matrix))
Expected output:
[('FastAPI Crash Course', 0.22), ('Decorators Deep Dive', 0.1), ('Data Science with Pandas', 0.0)]
Pro tip: When your dataset grows to thousands of items, precompute the TF-IDF matrix and even the pairwise similarities once, then reuse them. Don't re-vectorize on every request — that's a classic performance killer.
Compare options / when to choose what
| Approach | Data needed | Pros | Cons | Best for |
|---|---|---|---|---|
| Content-based filtering | Item attributes | No user history needed; transparent; works for new items | Limited to what's in the metadata; can over-specialize | Cold-start catalogs, niche products, news feeds |
| Collaborative filtering | User-item interactions | Can find hidden patterns; no item metadata needed | Cold start problem; sparse data; item cold start | Mature platforms with heavy user activity |
| Hybrid approaches | Both | Combines strengths; more accurate | Complex; more infrastructure | Large-scale systems like Netflix |
When to choose content-based? If you have a rich description of your items (even if you have no user interactions), content-based filtering is the fastest way to offer personalization. If you already have millions of user clicks, collaborative filtering may give better serendipity. In many real systems, you'll start with content-based and layer on collaborative as you gather data.
Variations to consider
- Using
TfidfVectorizeris the standard go-to for text, but for unstructured text you could useCountVectorizeror more advanced embeddings from language models. - Instead of a simple average profile, you can weight liked items by recency or explicit ratings — that's a minor tweak with big impact.
- For structured numeric features, use cosine similarity directly (no TF-IDF needed), or switch to Euclidean distance if magnitudes matter.
Troubleshooting & edge cases
- Sparse or empty descriptions: If an item has no description, its TF-IDF vector becomes all zeros, and every similarity with it will be 0. Fix: fall back to other fields like category or title, or use a default vector.
- Stop words stripping too aggressively:
stop_words='english'removes common words — that's usually good, but if your domain uses words like "server" as a key differentiator, test whether removing them hurts your rankings. - Zero similarity everywhere: If your catalog is homogeneous (all items about the same topic), cosine similarities will all be near 1.0, and ranking becomes meaningless. Solution: add more distinguishing features or use a different metric.
- Performance at scale: Computing cosine similarity against a million-item catalog on every request is slow. Precompute the TF-IDF matrix and use an approximate nearest neighbor library like
AnnoyorFAISS. - Cold start for items: A brand-new item with no interactions is still recommendable with content-based filtering because you only need its metadata — that's a major advantage over collaborative methods.
What you learned & what's next
You've now learned how to add content-based filtering to your Python applications. You can:
- Explain the core concept: represent items as feature vectors, then rank by similarity.
- Implement a working recommender using TF-IDF and cosine similarity.
- Decide when content-based filtering is the right tool vs. collaborative filtering or hybrid techniques.
Next lesson: You'll take this recommender to the next level by combining it with collaborative filtering to build a hybrid system that leverages both item attributes and user behavior. You'll also learn how to evaluate your recommendations offline using precision and recall metrics. Stay tuned!
Key insight: Content-based filtering is your fastest path to personalization — it works from the very first user interaction. Master it, and you'll have a powerful tool for any cold-start scenario.
Practice recap
Now try extending the example: add more items to the df DataFrame and give a user a 'liked' list of multiple tutorials. Build a user profile as the average of the TF-IDF vectors for those liked items, then generate top-3 recommendations. Experiment with removing stop_words='english' and observe how the scores change.
Common mistakes
- Forgetting to exclude the item the user already liked from the recommendations — you'll end up recommending the exact same item back to them.
- Using raw text without removing stop words or applying TF-IDF — simple term frequency will over-weight common words like 'the' and 'and'.
- Building the TF-IDF matrix on every request instead of caching it — this creates avoidable latency in production.
- Applying content-based filtering when item metadata is too sparse — if descriptions are nearly empty, the similarity scores become meaningless.
- Assuming all features are equally important — not weighting key attributes (like category vs. full description) can dilute recommendations.
Variations
- Instead of TF-IDF, use word embeddings like
word2vecorsentence-transformersto capture semantic meaning beyond exact keywords. - For structured numeric features (e.g., price ranges, ratings), skip text vectorization and compute cosine similarity directly on the numeric vectors.
- Use a weighted average for the user profile — recent interactions or explicit ratings get higher weight than older ones.
Real-world use cases
- News websites recommend articles based on the categories and keywords of what a user has read.
- E-commerce sites suggest products by matching product descriptions and tags to items the shopper viewed.
- Job boards recommend new postings to candidates based on the skills and keywords from their profile and past applications.
Key takeaways
- Content-based filtering uses item attributes, not user history, so it's the go-to choice for cold-start scenarios.
- The pipeline is: select features, vectorize with TF-IDF, build a user profile, score by cosine similarity, rank.
- TF-IDF converts text into numerical vectors where important, distinguishing words get higher weights.
- Cosine similarity measures directionality, not magnitude — ideal for comparing documents of different lengths.
- Choose content-based filtering when you have rich item metadata but little user interaction data.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.