ArticleRecommendationSystem / src /streamlit_app.py
handex's picture
Update src/streamlit_app.py
450f90e verified
Raw
History Blame Contribute Delete
7.25 kB
import streamlit as st
import pandas as pd
import joblib
import os
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
# --- Configuration and Constants ---
# File paths for model persistence
VECTORIZER_PATH = 'src/article_vectorizer.joblib'
TFIDF_MATRIX_PATH = 'src/article_matrix.joblib'
DATA_FILE_PATH = 'src/articles.csv'
# Number of recommendations to display
NUM_RECOMMENDATIONS = 5
# --- Data Loading and Preparation ---
@st.cache_data
def load_and_prepare_data():
"""
Loads data ONLY from articles.csv and prepares the DataFrame.
If the file is not found or reading fails, it returns an empty DataFrame.
"""
df = pd.DataFrame() # Initialize an empty DataFrame
if os.path.exists(DATA_FILE_PATH):
try:
df = pd.read_csv(DATA_FILE_PATH,encoding='latin1')
# Removed st.success(f"Data loaded successfully...")
except Exception as e:
st.error(f"Error reading {DATA_FILE_PATH}. Please check file integrity and format (CSV). Error: {e}")
else:
st.error(f"CRITICAL ERROR: {DATA_FILE_PATH} not found. Please upload the file to your application directory.")
if df.empty or 'Article' not in df.columns or 'Title' not in df.columns:
# If loading failed or columns are missing, return empty or incomplete DataFrame
return pd.DataFrame()
# 1. Ensure the index is a clean, contiguous 0-based positional index
df = df.reset_index(drop=True)
# Ensure the article column is string type
df['Article'] = df['Article'].astype(str)
return df
# --- Model Fitting/Loading Logic ---
@st.cache_data
def fit_or_load_models(df):
"""
Fits the TF-IDF model and calculates Cosine Similarity,
or loads them from disk if available.
"""
# Check if the necessary columns exist before proceeding
if df.empty or 'Article' not in df.columns or 'Title' not in df.columns:
return None, None, None
if os.path.exists(VECTORIZER_PATH) and os.path.exists(TFIDF_MATRIX_PATH):
try:
# Load existing models
tfidf = joblib.load(VECTORIZER_PATH)
tfidf_matrix = joblib.load(TFIDF_MATRIX_PATH)
# Recalculate cosine similarity (This is fast on the loaded matrix)
cosine_sim = cosine_similarity(tfidf_matrix)
# Removed st.success("Models loaded successfully...")
return tfidf, tfidf_matrix, cosine_sim
except Exception as e:
st.error(f"Error loading models. Recalculating: {e}")
# Fallback to calculation below
pass
# Calculate and save models if they don't exist or loading failed
with st.spinner('Calculating TF-IDF and Cosine Similarity (First run/Model not found)...'):
articles = df["Article"].tolist()
# 1. TF-IDF Vectorizer Setup
tfidf = TfidfVectorizer(stop_words='english')
tfidf_matrix = tfidf.fit_transform(articles)
# 2. Cosine Similarity Calculation
cosine_sim = cosine_similarity(tfidf_matrix)
# Save models for future runs
joblib.dump(tfidf, VECTORIZER_PATH)
joblib.dump(tfidf_matrix, TFIDF_MATRIX_PATH)
# Removed st.success("Model calculation complete...")
return tfidf, tfidf_matrix, cosine_sim
# --- Recommendation Function ---
def get_recommendations(article_index, cosine_sim_matrix, df, num_recommendations=NUM_RECOMMENDATIONS):
"""
Returns the top N article recommendations for a given article index.
"""
if article_index >= len(df) or article_index < 0:
return []
# Get the similarity scores for the article
similarity_scores = list(enumerate(cosine_sim_matrix[article_index]))
# Sort the scores in descending order
similarity_scores = sorted(similarity_scores, key=lambda x: x[1], reverse=True)
# Exclude the article itself (index 0, as similarity is 1.0) and take the top N
top_n_recommendations = similarity_scores[1:num_recommendations + 1]
# Get the indices of the top N recommended articles (these are positional indices)
top_n_indices = [i[0] for i in top_n_recommendations]
# Use list indexing instead of DataFrame .iloc to guarantee positional lookup
title_list = df["Title"].tolist()
recommended_titles = [title_list[i] for i in top_n_indices]
# Get the similarity scores for display (rounded)
recommended_scores = [round(i[1] * 100, 2) for i in top_n_recommendations]
return list(zip(recommended_titles, recommended_scores))
# --- Streamlit App Layout ---
def app():
st.set_page_config(page_title="Article Recommendation System", layout="wide")
st.title("📄 Content-Based Article Recommender")
st.markdown("Select an article from the sidebar to see the top **5** most relevant recommendations.")
# 1. Load Data
df = load_and_prepare_data()
# Check if data loading was successful and essential columns exist
if df.empty or 'Article' not in df.columns or 'Title' not in df.columns:
st.error("Application cannot start: Please ensure 'articles.csv' is uploaded and contains 'Title' and 'Article' columns.")
return
# 2. Load/Fit Models
tfidf, tfidf_matrix, cosine_sim = fit_or_load_models(df)
# Check if models were loaded/calculated successfully
if cosine_sim is None:
st.error("Application cannot start: Model calculation failed.")
return
# --- Sidebar for Selection ---
# Create a list of titles for the selectbox
article_titles = df['Title'].tolist()
st.sidebar.header("Select an Article")
selected_title = st.sidebar.selectbox(
"Which article is the reader currently viewing?",
article_titles
)
# Find the index of the selected article
if selected_title:
# Use .tolist().index() to guarantee a 0-based positional index
try:
selected_index = df['Title'].tolist().index(selected_title)
except ValueError:
st.error("Error finding article index. Check data consistency.")
return
else:
# Should not happen if article_titles is non-empty
st.error("No article selected.")
return
# --- Main Content Display ---
st.header(f"Recommendations for: **{selected_title}**")
# 3. Get Recommendations
recommendations_list = get_recommendations(selected_index, cosine_sim, df)
# 4. Display Results
if recommendations_list:
st.subheader(f"Top {NUM_RECOMMENDATIONS} Most Similar Articles:")
# Create a visually appealing table or list
col1, col2 = st.columns([1, 4])
with col1:
st.markdown("### Rank")
for i in range(1, NUM_RECOMMENDATIONS + 1):
st.write(f"**#{i}**")
with col2:
st.markdown("### Article Title (Similarity Score)")
for title, score in recommendations_list:
st.markdown(f"**{title}** - *({score}%)*")
if __name__ == "__main__":
app()