Spaces:
Sleeping
Sleeping
File size: 7,253 Bytes
3a921f3 29c143e 8b49811 29c143e 450f90e 29c143e 8b49811 450f90e 8b49811 ca02dba 8b49811 29c143e 8b49811 29c143e 8b49811 29c143e 8b49811 29c143e 450f90e 29c143e 450f90e 29c143e ca02dba 29c143e 450f90e 8b49811 29c143e 450f90e 29c143e 8b49811 29c143e 8b49811 29c143e 8b49811 29c143e 8b49811 ca02dba 29c143e 8b49811 29c143e 8b49811 29c143e 8b49811 29c143e 450f90e 29c143e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | 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() |