import pandas as pd import numpy as np from transformers import AutoTokenizer, AutoModel import torch from sklearn.ensemble import IsolationForest from sklearn.preprocessing import StandardScaler from textblob import TextBlob from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer from sklearn.feature_extraction.text import CountVectorizer from sklearn.decomposition import PCA import warnings from typing import Dict, List, Tuple import logging from collections import Counter from detoxify import Detoxify import re from datetime import datetime import seaborn as sns import matplotlib.pyplot as plt from pathlib import Path import json class AdvancedYelpAnalyzer: def __init__(self, df: pd.DataFrame): """Initialize the analyzer with necessary models and configurations""" self.df = df.copy() self.bert_tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased') self.bert_model = AutoModel.from_pretrained('bert-base-uncased') self.vader = SentimentIntensityAnalyzer() self.toxic_model = Detoxify('original') self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') self.bert_model.to(self.device) # Configure logging logging.basicConfig(level=logging.INFO) self.logger = logging.getLogger(__name__) def get_bert_embeddings(self, texts: List[str], batch_size: int = 32) -> np.ndarray: """Generate BERT embeddings for text""" embeddings = [] for i in range(0, len(texts), batch_size): batch_texts = texts[i:i + batch_size] encoded = self.bert_tokenizer(batch_texts, padding=True, truncation=True, max_length=512, return_tensors='pt') with torch.no_grad(): encoded = {k: v.to(self.device) for k, v in encoded.items()} outputs = self.bert_model(**encoded) batch_embeddings = outputs.last_hidden_state[:, 0, :].cpu().numpy() embeddings.append(batch_embeddings) return np.vstack(embeddings) def analyze_sentiment(self) -> pd.DataFrame: """Perform comprehensive sentiment analysis using multiple tools""" self.logger.info("Starting sentiment analysis...") # Calculate BERT embeddings for reviews self.logger.info("Calculating BERT embeddings...") review_texts = self.df['review_text'].fillna('').tolist() bert_embeddings = self.get_bert_embeddings(review_texts) # Calculate review length using BERT tokenizer self.logger.info("Calculating tokenized lengths...") self.df['review_length'] = self.df['review_text'].apply( lambda x: len(self.bert_tokenizer.encode(str(x))) ) # Store BERT embeddings mean and std as features self.df['bert_embedding_mean'] = np.mean(bert_embeddings, axis=1) self.df['bert_embedding_std'] = np.std(bert_embeddings, axis=1) # TextBlob sentiment and subjectivity self.df['textblob_polarity'] = self.df['review_text'].apply( lambda x: TextBlob(str(x)).sentiment.polarity ) self.df['textblob_subjectivity'] = self.df['review_text'].apply( lambda x: TextBlob(str(x)).sentiment.subjectivity ) # VADER sentiment with custom negative phrase handling def get_enhanced_vader_scores(text): # Custom negative phrases negative_phrases = [ 'too long', 'way too long', 'waiting', 'changed our minds', 'too many', 'took forever', 'took too long', 'waste of time', 'not worth', 'disappointing', 'mediocre', 'needs improvement' ] # Get base VADER scores base_scores = self.vader.polarity_scores(str(text)) # Check for negative phrases text_lower = str(text).lower() neg_count = sum(1 for phrase in negative_phrases if phrase in text_lower) # Adjust scores if negative phrases are found if neg_count > 0: base_scores['neg'] = max(base_scores['neg'], min(0.7, neg_count * 0.2)) base_scores['compound'] *= (1 - (neg_count * 0.15)) # Readjust neutral score base_scores['neu'] = max(0, 1 - base_scores['neg'] - base_scores['pos']) return base_scores # Apply enhanced VADER scoring vader_scores = self.df['review_text'].apply(get_enhanced_vader_scores) self.df['vader_compound'] = vader_scores.apply(lambda x: x['compound']) self.df['vader_negative'] = vader_scores.apply(lambda x: x['neg']) self.df['vader_positive'] = vader_scores.apply(lambda x: x['pos']) self.df['vader_neutral'] = vader_scores.apply(lambda x: x['neu']) # Calculate sentiment extremity self.df['sentiment_extremity'] = self.df['vader_compound'].abs() return self.df def detect_anomalies(self) -> pd.DataFrame: """Detect anomalous reviews using Isolation Forest with BERT features""" self.logger.info("Detecting anomalies...") # Prepare features for anomaly detection features = [ 'review_stars', 'textblob_polarity', 'vader_compound', 'sentiment_extremity', 'review_length', 'bert_embedding_mean', 'bert_embedding_std' ] # Ensure all features exist missing_features = [f for f in features if f not in self.df.columns] if missing_features: self.analyze_sentiment() # Standardize features scaler = StandardScaler() X = scaler.fit_transform(self.df[features]) # Apply Isolation Forest iso_forest = IsolationForest( contamination=0.1, random_state=42, n_jobs=-1 ) # Fit and predict self.df['is_anomaly'] = iso_forest.fit_predict(X) self.df['anomaly_score'] = iso_forest.score_samples(X) return self.df def detect_ai_generated_text(self) -> pd.DataFrame: """Estimate likelihood of AI-generated content""" self.logger.info("Detecting AI-generated content...") # Ensure sentiment analysis has been run if 'textblob_subjectivity' not in self.df.columns: self.analyze_sentiment() # Use detoxify model to get toxicity scores texts = self.df['review_text'].fillna('').tolist() toxic_scores = self.toxic_model.predict(texts) # Add scores to DataFrame toxic_score_types = ['toxicity', 'severe_toxicity', 'obscene', 'identity_attack', 'insult', 'threat', 'sexual_explicit'] for score_type in toxic_score_types: if score_type in toxic_scores: self.df[f'toxic_{score_type}'] = toxic_scores[score_type] # Calculate AI generation likelihood based on various factors self.df['ai_generated_likelihood'] = ( (self.df['textblob_subjectivity'] < 0.3) & # Low subjectivity (self.df['sentiment_extremity'] > 0.8) & # Extreme sentiment (self.df['review_length'] > self.df['review_length'].quantile(0.95)) & # Unusually long (self.df['bert_embedding_std'] < self.df['bert_embedding_std'].quantile(0.25)) # Unusual language patterns ).astype(int) # Add additional AI detection features self.df['ai_detection_score'] = ( (self.df['textblob_subjectivity'] * -1) + # Lower subjectivity increases score (self.df['sentiment_extremity'] * 0.5) + # Extreme sentiment contributes somewhat (self.df['bert_embedding_std'] * -0.5) # Lower variation in embeddings increases score ).clip(0, 1) # Normalize between 0 and 1 return self.df def analyze_business_categories(self) -> Dict: """Analyze trends and patterns specific to business categories""" self.logger.info("Analyzing business categories...") # Extract categories categories = self.df['categories'].fillna('').str.split(', ') all_categories = [cat for cats in categories if isinstance(cats, list) for cat in cats] category_counts = Counter(all_categories) # Analyze reviews by category category_analysis = {} for category in set(all_categories): category_reviews = self.df[self.df['categories'].str.contains(category, na=False)] category_analysis[category] = { 'review_count': len(category_reviews), 'avg_rating': category_reviews['review_stars'].mean() if not category_reviews.empty else None, 'avg_sentiment': category_reviews['vader_compound'].mean() if 'vader_compound' in self.df.columns and not category_reviews.empty else None, 'avg_subjectivity': category_reviews['textblob_subjectivity'].mean() if 'textblob_subjectivity' in self.df.columns and not category_reviews.empty else None } return category_analysis def visualize_results(self, output_dir: str): """Create visualizations for analysis results""" plt.figure(figsize=(15, 10)) # Sentiment Distribution plt.subplot(2, 2, 1) sns.histplot(data=self.df, x='vader_compound', bins=50) plt.title('Sentiment Distribution') # Review Volume Over Time plt.subplot(2, 2, 2) daily_reviews = self.df.groupby('review_date').size() daily_reviews.plot() plt.title('Review Volume Over Time') # Anomaly Score Distribution plt.subplot(2, 2, 3) if 'anomaly_score' not in self.df.columns: self.detect_anomalies() sns.histplot(data=self.df, x='anomaly_score', bins=50) plt.title('Anomaly Score Distribution') # AI Generation Likelihood plt.subplot(2, 2, 4) if 'ai_generated_likelihood' not in self.df.columns: self.detect_ai_generated_text() sns.histplot(data=self.df, x='ai_generated_likelihood', bins=2) plt.title('AI Generation Likelihood') plt.tight_layout() plt.savefig(f'{output_dir}/analysis_results.png') plt.close() def run_full_analysis(self, output_dir: str) -> Tuple[pd.DataFrame, Dict]: """Run complete analysis pipeline with detailed outputs""" self.logger.info("Starting full analysis pipeline...") # Create output directory if it doesn't exist output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) try: # Run all analyses self.analyze_sentiment() self.detect_anomalies() self.detect_ai_generated_text() category_analysis = self.analyze_business_categories() # Create visualizations self.visualize_results(str(output_dir)) # Compile results analysis_results = { 'category_analysis': category_analysis, 'sentiment_summary': { 'avg_sentiment': self.df['vader_compound'].mean(), 'positive_reviews': len(self.df[self.df['vader_compound'] > 0.5]), 'negative_reviews': len(self.df[self.df['vader_compound'] < -0.5]), 'neutral_reviews': len(self.df[abs(self.df['vader_compound']) <= 0.5]) }, 'ai_detection_summary': { 'likely_ai_generated': len(self.df[self.df['ai_generated_likelihood'] == 1]), 'avg_ai_score': self.df['ai_detection_score'].mean() }, 'anomaly_summary': { 'anomalous_reviews': len(self.df[self.df['is_anomaly'] == -1]), 'avg_anomaly_score': self.df['anomaly_score'].mean() } } # Save results self.df.to_csv(output_dir / "analyzed_data.csv", index=False) with open(output_dir / "analysis_results.json", 'w') as f: json.dump(analysis_results, f, indent=4) return self.df, analysis_results except Exception as e: self.logger.error(f"Error during analysis: {str(e)}") raise # For testing if __name__ == "__main__": # Set up logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) try: # Read test data df = pd.read_csv("test_data.csv") # Initialize analyzer analyzer = AdvancedYelpAnalyzer(df) # Run analysis output_dir = "output" analyzed_df, results = analyzer.run_full_analysis(output_dir) logger.info("Analysis completed successfully!") except Exception as e: logger.error(f"Error during testing: {str(e)}") raise