Movie Recommender: A New Question for dsproj1-5's Own Dataset

Data Science & ML Projects (Intermediate)

Chapter 3 · Movie Recommender: A New Question for dsproj1-5's Own Dataset

dsproj1-5 asked "what can we learn about this data?" — a real EDA, on this exact MovieLens-style dataset. This chapter returns to the identical data and asks a fundamentally different kind of question: "given that someone liked this movie, what should we recommend next?" Not a label to predict, not a pattern to describe — a ranked, personalized output. Recommendation is a genuinely different problem shape than anything ml1 covered.

What We're Building

A "users who liked this also liked..." recommender, using item-based collaborative filtering — recommending movies not by their genre or plot, but by whether the same users tended to rate them similarly.

Step 1: Loading & Merging — dsproj1-5's Own First Step

import pandas as pd

movies = pd.read_csv("movies.csv")
ratings = pd.read_csv("ratings.csv")
df = ratings.merge(movies, on="movie_id")

Step 2: Reshaping Into a User-Item Matrix

matrix = df.pivot_table(index="user_id", columns="title", values="rating")
print(matrix.shape)
print(matrix.iloc[:5, :5])
A genuinely new data shape
Every earlier chapter kept data in long, "tidy" format — one row per rating. Recommendation needs the opposite: one row per user, one column per movie, the rating (or a missing value) at each intersection. .pivot_table() is the reshape this specific problem genuinely requires, not a stylistic choice.

Step 3: An Honest Simplifying Assumption

matrix_filled = matrix.fillna(0)
0 doesn't mean "disliked" — it means "unrated"
Most cells in this matrix are missing — no user has rated most movies. Filling with 0 is the standard, honest first approach at this scale, but it's a genuine approximation, not a claim that an unrated movie was actively disliked. Real production recommenders use more sophisticated handling of missing ratings; this project uses the simpler, more transparent version deliberately, so the actual mechanism stays visible.

Step 4: Cosine Similarity — nlp1-5's Own Idea, on Movies Instead of Words

from sklearn.metrics.pairwise import cosine_similarity
import numpy as np

similarity = cosine_similarity(matrix_filled.T)
similarity_df = pd.DataFrame(similarity, index=matrix_filled.columns, columns=matrix_filled.columns)

nlp1-5 used cosine similarity to say two words are similar when their vectors point in similar directions. Here, a movie's own "vector" is the column of every user's rating for it — two movies are similar when the same users tended to rate them similarly, regardless of whether those users happen to rate everything high or everything low overall. It's the identical mathematical idea, applied to a completely different kind of vector.

Step 5: Building the Recommender

def recommend(title, top_n=5):
    if title not in similarity_df.columns:
        return f"'{title}' not found in this dataset."
    scores = similarity_df[title].sort_values(ascending=False)
    return scores.iloc[1:top_n + 1]   # [0] is the movie itself, always similarity 1.0

scores.iloc[1:] skips the movie's own perfect self-similarity — a small but easy detail to overlook.

Step 6: Trying It on a Real Movie

print(recommend("The Matrix"))
# The Matrix Reloaded       0.71
# Terminator 2               0.64
# Inception                  0.61
# ...

Worth a sanity check against dsproj1-5's own genre data: recommendations clustering around genuinely similar genres, without genre ever being used to compute them, is a real, satisfying confirmation the similarity signal is picking up something meaningful in actual user behavior.

dsproj1-5's own statistical trap, back again in a new form
A movie rated by only two or three users can appear artificially "similar" to another niche movie purely by coincidence — the exact small-sample-extremes problem dsproj1-5 caught in raw averages resurfaces here in similarity scores. A movie with a genuinely tiny rating count showing up as a "top match" is worth the same skepticism dsproj1-5's own num_ratings filter applied.

The Complete Recommender

import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity

movies = pd.read_csv("movies.csv")
ratings = pd.read_csv("ratings.csv")
df = ratings.merge(movies, on="movie_id")

matrix = df.pivot_table(index="user_id", columns="title", values="rating").fillna(0)
similarity_df = pd.DataFrame(
    cosine_similarity(matrix.T), index=matrix.columns, columns=matrix.columns
)

def recommend(title, top_n=5):
    if title not in similarity_df.columns:
        return f"'{title}' not found."
    return similarity_df[title].sort_values(ascending=False).iloc[1:top_n + 1]

print(recommend("The Matrix"))
The cold-start problem — an honest limitation
A brand-new movie with zero ratings has an empty column in the matrix — nothing for this method to compute similarity from at all. Collaborative filtering fundamentally needs behavior data to work; it can say nothing about anything nobody has rated yet. This is a genuine, well-known limitation of the whole approach, not a bug in this specific implementation.

pivot_table()

Reshapes long ratings data into a wide user-item matrix.

Cosine similarity on columns

nlp1-5's own word-similarity idea, applied to movie rating patterns.

Item-based collaborative filtering

Recommends by shared rater behavior, not by genre or content.

The cold-start problem

No behavior data means no recommendation — a real, fundamental limit.

Extend This Project

Try these on your own:

  • Build a content-based alternative using genre overlap instead of ratings, and compare its recommendations for the same movie.
  • Add a minimum-rating-count filter (per dsproj1-5's own finding) before trusting any similarity score.
  • Build a simple user-based version: find the most similar users to a given one, and recommend movies they rated highly that this user hasn't seen.
  • Try Pearson correlation instead of cosine similarity and see whether the recommendations for the same movie change.

What's Next

Chapter 4: Digit Recognizer — this course's own first real CNN, applying nn1-7's own convolutional material to real image classification for the first time in any Projects course.

Chapter 3 Quick Reference

  • Same dataset as dsproj1-5, a genuinely different question — recommendation instead of exploration
  • .pivot_table() reshapes long ratings data into a wide user-item matrix — a real, necessary shape change
  • Cosine similarity applies nlp1-5's own "similar vectors" idea to rating-pattern vectors instead of word vectors
  • Filling missing ratings with 0 is an honest simplifying assumption, not a claim about genuine dislike
  • dsproj1-5's own small-sample-extremes trap resurfaces here as artificially high similarity for rarely-rated movies
  • The cold-start problem is a fundamental, well-known limitation of collaborative filtering, not a fixable bug