Recommendation_system / content_based.py
Tanishka14's picture
Fix pandas fillna warnings and update NLTK handling
3324993
#!/usr/bin/env python
# coding: utf-8
# In[1]:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics.pairwise import cosine_similarity # measure similarity between two vectors, to assess how alike two data points are
from sklearn.feature_extraction.text import TfidfVectorizer #TfidfVectorizer converts text data into a matrix of numerical features
import os #Importing the os module makes these functions available in your code.
from scipy.sparse import coo_matrix
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
# Download required resources if missing
try:
stop_words = set(stopwords.words('english'))
except LookupError:
nltk.download('stopwords')
stop_words = set(stopwords.words('english'))
# Punkt tokenizer
try:
word_tokenize("test sentence")
except LookupError:
nltk.download('punkt')
nltk.download('punkt_tab') # Add this line
# In[2]:
data = pd.read_csv("marketing_sample_data.tsv", sep = '\t') #seperated
# In[3]:
data.columns
# In[4]:
data = data[['Uniq Id','Crawl Timestamp','Product Id', 'Product Rating', 'Product Reviews Count', 'Product Category', 'Product Brand', 'Product Name', 'Product Image Url', 'Product Url', 'Product Description','Product Available Inventory', 'Product Tags']]
# In[5]:
data["Product Tags"]
# In[6]:
data.shape
# In[7]:
data["Product Rating"] = data["Product Rating"].fillna(0)
data["Product Reviews Count"] = data["Product Reviews Count"].fillna(0)
data["Product Category"] = data["Product Category"].fillna('')
data["Product Brand"] = data["Product Brand"].fillna('')
data["Product Description"] = data["Product Description"].fillna('')
data["Product Url"] = data["Product Url"].fillna('')
# In[8]:
data.isnull().sum()
# In[9]:
data.duplicated().sum() #no duplicate value in data
# In[10]:
column_name = {
'Uniq Id': 'ID',
'Crawl Timestamp' :'Time',
'Product Id': 'ProdID',
'Product Rating': 'Rating',
'Product Reviews Count': 'ReviewCount',
'Product Category': 'Category',
'Product Brand': 'Brand',
'Product Name': 'Name',
'Product Image Url': 'ImageURL',
'Product Url': 'ProductURL',
'Product Description': 'Description',
'Product Available Inventory' : 'ProductInventory',
'Product Tags': 'Tags',
'Product Contents': 'Contents'
}
data.rename(columns=column_name,inplace=True)
# In[11]:
data['ID'] = data['ID'].str.extract(r'(\d+)').astype(float)
data['ProdID'] = data['ProdID'].str.extract(r'(\d+)').astype(float)
# In[12]:
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
import string
# Download NLTK data if not already
#nltk.download('punkt')
#nltk.download('stopwords')
stop_words = set(stopwords.words('english'))
def clean_and_extract_tags_nltk(text):
# Lowercase, tokenize
tokens = word_tokenize(text.lower())
# Remove punctuation and stopwords, keep alphanumeric tokens only
tags = [token for token in tokens if token.isalnum() and token not in stop_words]
return ', '.join(tags)
columns_to_extract_tags_from = ['Category', 'Brand', 'Description']
for column in columns_to_extract_tags_from:
data[column] = data[column].fillna('')
data[column + '_tags'] = data[column].apply(clean_and_extract_tags_nltk)
# In[13]:
data['Tags'] = data[columns_to_extract_tags_from].apply(lambda row: ', '.join(row), axis=1)
# In[14]:
average_ratings =data.groupby(['Name', 'ReviewCount', 'Brand', 'ImageURL'])['Rating'].mean().reset_index()
top_rated_items = average_ratings.sort_values(by='Rating', ascending=False)
rating_base_recommendation = top_rated_items.head(10)
rating_base_recommendation.loc[:, 'Rating'] = rating_base_recommendation['Rating'].astype(int)
rating_base_recommendation.loc[:, 'ReviewCount'] = rating_base_recommendation['ReviewCount'].astype(int)
print("Rating Base Recommendation System: (Trending Products)")
rating_base_recommendation[['Name', 'Rating', 'ReviewCount', 'Brand', 'ImageURL']]
# In[15]:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import difflib
import pandas as pd
# 1. Prepare combined text features column (Name + Category + Brand + Description)
data['combined_features'] = (
data['Name'].fillna('') + ' ' +
data['Category'].fillna('') + ' ' +
data['Brand'].fillna('') + ' ' +
data['Description'].fillna('')
)
# 2. Create TF-IDF matrix and cosine similarity matrix
def create_tfidf_matrix(data, feature_column='combined_features'):
tfidf_vectorizer = TfidfVectorizer(stop_words='english')
tfidf_matrix = tfidf_vectorizer.fit_transform(data[feature_column])
cosine_sim = cosine_similarity(tfidf_matrix, tfidf_matrix)
return tfidf_vectorizer, tfidf_matrix, cosine_sim
tfidf_vectorizer, tfidf_matrix, cosine_sim = create_tfidf_matrix(data)
# 3. Fuzzy match function for close product names
def find_closest_match(name, choices, cutoff=0.6):
matches = difflib.get_close_matches(name, choices, n=1, cutoff=cutoff)
return matches[0] if matches else None
# 5. Content-based recommendation function with fallback to keyword search
def content_based_recommendations(data, item_name, cosine_sim, top_n=10):
if item_name not in data['Name'].values:
close_match = find_closest_match(item_name, data['Name'].tolist())
if close_match:
print(f"Item '{item_name}' not found. Did you mean '{close_match}'? Recommending based on '{close_match}'.")
item_name = close_match
else:
print(f"Showing top rated items matching keyword '{item_name}':")
return search_top_rated_by_keyword(data, item_name, top_n)
item_index = data[data['Name'] == item_name].index[0]
similarity_scores = list(enumerate(cosine_sim[item_index]))
similarity_scores = sorted(similarity_scores, key=lambda x: x[1], reverse=True)
top_similar = similarity_scores[1:top_n+1] # skip the item itself
indices = [i[0] for i in top_similar]
return data.iloc[indices][['Name', 'ReviewCount', 'Brand', 'ImageURL','ProductURL', 'Rating']]
def search_top_rated_by_keyword(data, keyword, top_n=10):
# Try exact substring match first (case-insensitive)
filtered = data[data['combined_features'].str.contains(keyword, case=False, na=False)]
if filtered.empty:
# If no exact substring found, try fuzzy match on unique words in combined_features
all_words = set()
for text in data['combined_features']:
all_words.update(text.lower().split())
close_match = find_closest_match(keyword.lower(), list(all_words))
if close_match:
print(f"No exact match found for '{keyword}'. Using closest keyword '{close_match}' for search.")
filtered = data[data['combined_features'].str.contains(close_match, case=False, na=False)]
else:
print(f"No matches found for '{keyword}' or similar keywords.")
return pd.DataFrame() # empty result
filtered_sorted = filtered.sort_values(by='Rating', ascending=False)
return filtered_sorted[['Name', 'ReviewCount', 'Brand', 'ImageURL','ProductURL', 'Rating']].head(top_n)
# Usage example:
# result = content_based_recommendations(data, "lipstick", cosine_sim, top_n=10)
# print(result)
# In[16]:
import gradio as gr
import html
import uuid
# --- Clean image slider function ---
def build_image_slider(image_urls, slider_id):
total = len(image_urls)
if total == 1:
return f'''
<div style="width:240px; height:240px; margin:auto;">
<img src="{image_urls[0]}" style="width:100%; height:100%; object-fit:contain; border-radius:8px; border:1px solid #ccc;" />
</div>
'''
# Radio buttons (hidden)
radios = ''.join([
f'<input type="radio" name="slider-{slider_id}" id="slide-{slider_id}-{i}" {"checked" if i == 0 else ""} style="display:none;">'
for i in range(total)
])
# Slides
slides_html = ''
for i, url in enumerate(image_urls):
prev = f"slide-{slider_id}-{(i - 1) % total}"
nxt = f"slide-{slider_id}-{(i + 1) % total}"
slides_html += f'''
<div class="slide-{slider_id}" id="content-{slider_id}-{i}">
<label for="{prev}" class="arrow arrow-left">❮</label>
<img src="{url}" />
<label for="{nxt}" class="arrow arrow-right">❯</label>
</div>
'''
# CSS to show selected slide
css_rules = ''.join([
f'#slide-{slider_id}-{i}:checked ~ .slider-{slider_id} #content-{slider_id}-{i} {{ display: flex; }}'
for i in range(total)
])
# Full HTML block
return '''
<div style="margin:auto; width:240px;">
{radios}
<div class="slider-{id}" style="position:relative; width:240px; height:240px; margin:auto;">
{slides}
</div>
<style>
.slider-{id} div {{
display: none;
justify-content: space-between;
align-items: center;
position: absolute;
top: 0;
left: 0;
width: 240px;
height: 240px;
}}
.slider-{id} img {{
width: 100%;
height: 100%;
object-fit: contain;
border-radius: 8px;
border: 1px solid #ccc;
}}
.arrow {{
font-size: 24px;
color: black;
background: rgba(255,255,255,0.7);
padding: 4px 8px;
cursor: pointer;
user-select: none;
z-index: 10;
}}
.arrow-left {{
position: absolute;
left: -12px;
top: 40%;
}}
.arrow-right {{
position: absolute;
right: -12px;
top: 40%;
}}
{rules}
</style>
</div>
'''.format(radios=radios, slides=slides_html, id=slider_id, rules=css_rules)
# --- Recommender Logic ---
def recommend_products(product_name):
try:
result_df = content_based_recommendations(data, product_name, cosine_sim, top_n=7)
if result_df.empty:
return "<b>No recommendations found. Try another product name.</b>"
recommendations = []
for _, row in result_df.iterrows():
# Image links
raw_links = str(row.get('ImageURL', '')).strip()
if raw_links.startswith('(') and raw_links.endswith(')'):
raw_links = raw_links[1:-1]
links = [link.strip() for link in raw_links.split('|')]
valid_links = [link for link in links if link.startswith('http')]
if not valid_links:
valid_links = ["https://via.placeholder.com/240?text=No+Image"]
slider_id = str(uuid.uuid4())[:8]
image_slider_html = build_image_slider(valid_links, slider_id)
# Metadata
product_name_escaped = html.escape(str(row.get('Name', '')))
brand = html.escape(str(row.get('Brand', '')))
rating = html.escape(str(row.get('Rating', '')))
review_count = html.escape(str(row.get('ReviewCount', '')))
product_url = str(row.get("ProductURL", "")).strip()
link_html = (
f'<a href="{product_url}" target="_blank" rel="noopener noreferrer" '
f'style="color:blue; text-decoration:underline;">🔗 View Product</a>'
if product_url.startswith("http") else
"<div style='color:gray;'>🔗 No Product URL</div>"
)
# HTML Block
info_html = f"""
<div style='margin-bottom:50px; text-align:center;'>
{image_slider_html}<br>
<strong>{product_name_escaped}</strong><br>
Brand: {brand}<br>
Rating: {rating}<br>
Reviews: {review_count}<br>
{link_html}
</div>
"""
recommendations.append(info_html)
return "<hr>".join(recommendations)
except Exception as e:
return f"<b>Error:</b> {str(e)}"
# --- Gradio Interface ---
demo = gr.Interface(
fn=recommend_products,
inputs=gr.Textbox(label="Enter a Product Name"),
outputs=gr.HTML(label="Top Recommendations"),
title="🛍️ Product Recommendation System",
description="Enter a product name to get personalized recommendations with product details and preview images."
)
# In[ ]: