Spaces:
Sleeping
Sleeping
| 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 --- | |
| 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 --- | |
| 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() |