import matplotlib matplotlib.use('Agg') # Use non-interactive backend import matplotlib.pyplot as plt import pandas as pd import numpy as np from nltk.sentiment.vader import SentimentIntensityAnalyzer import nltk from collections import Counter from utils.model_loader import load_sentiment_analyzer, load_emotion_classifier from utils.helpers import fig_to_html, df_to_html_table def sentiment_handler(text_input): """Show sentiment analysis capabilities.""" output_html = [] # Add result area container output_html.append('
') output_html.append('

Sentiment Analysis

') output_html.append("""
Sentiment analysis determines the emotional tone behind text to identify if it expresses positive, negative, or neutral sentiment.
""") # Model info output_html.append("""

Models Used:

""") try: # VADER Analysis output_html.append('

VADER Sentiment Analysis

') output_html.append('

VADER (Valence Aware Dictionary and sEntiment Reasoner) is a lexicon and rule-based sentiment analysis tool specifically attuned to sentiments expressed in social media.

') # Get VADER analyzer vader_analyzer = SentimentIntensityAnalyzer() vader_scores = vader_analyzer.polarity_scores(text_input) # Extract scores compound_score = vader_scores['compound'] pos_score = vader_scores['pos'] neg_score = vader_scores['neg'] neu_score = vader_scores['neu'] # Determine sentiment category if compound_score >= 0.05: sentiment_category = "Positive" sentiment_color = "#4CAF50" # Green sentiment_emoji = "😊" elif compound_score <= -0.05: sentiment_category = "Negative" sentiment_color = "#F44336" # Red sentiment_emoji = "😞" else: sentiment_category = "Neutral" sentiment_color = "#FFC107" # Amber sentiment_emoji = "😐" # Create sentiment gauge display output_html.append(f"""
{sentiment_emoji}

{sentiment_category}

Compound Score: {compound_score:.2f}

Negative (-1.0) Neutral (0.0) Positive (1.0)
""") # VADER score breakdown output_html.append('

VADER Score Breakdown

') # Create pie chart fig = plt.figure(figsize=(8, 8)) labels = ['Positive', 'Neutral', 'Negative'] sizes = [pos_score, neu_score, neg_score] colors = ['#4CAF50', '#FFC107', '#F44336'] explode = (0.1, 0, 0) if pos_score > neg_score and pos_score > neu_score else \ (0, 0.1, 0) if neu_score > pos_score and neu_score > neg_score else \ (0, 0, 0.1) plt.pie(sizes, explode=explode, labels=labels, colors=colors, autopct='%1.1f%%', shadow=True, startangle=90) plt.axis('equal') plt.title('VADER Sentiment Distribution') # Create detail table detail_df = pd.DataFrame({ 'Metric': ['Positive Score', 'Neutral Score', 'Negative Score', 'Compound Score'], 'Value': [pos_score, neu_score, neg_score, compound_score] }) # Layout with columns for VADER results output_html.append('
') # Column 1: Chart output_html.append('
') output_html.append(fig_to_html(fig)) output_html.append('
') # Column 2: Data output_html.append('
') output_html.append(df_to_html_table(detail_df)) # Add interpretation if compound_score >= 0.75: interpretation = "Extremely positive sentiment" elif compound_score >= 0.5: interpretation = "Moderately positive sentiment" elif compound_score >= 0.05: interpretation = "Slightly positive sentiment" elif compound_score > -0.05: interpretation = "Neutral sentiment" elif compound_score > -0.5: interpretation = "Slightly negative sentiment" elif compound_score > -0.75: interpretation = "Moderately negative sentiment" else: interpretation = "Extremely negative sentiment" output_html.append(f"""

Interpretation

{interpretation}

""") output_html.append('
') # Close column 2 output_html.append('
') # Close row # Transformer-based Sentiment Analysis output_html.append('

Transformer-based Sentiment Analysis

') output_html.append('

This analysis uses a DistilBERT model fine-tuned on the Stanford Sentiment Treebank dataset.

') try: # Load transformer model sentiment_model = load_sentiment_analyzer() # Maximum text length for transformer model (BERT has a 512 token limit) max_length = 512 # Get prediction truncated_text = text_input[:max_length * 4] # Rough character estimate transformer_result = sentiment_model(truncated_text) if len(text_input) > max_length * 4: output_html.append(f"""

⚠️ Note: Text was truncated for analysis as it exceeds the model's length limit.

""") # Extract prediction transformer_label = transformer_result[0]['label'] transformer_score = transformer_result[0]['score'] # Display transformer result sentiment_color = "#4CAF50" if transformer_label == "POSITIVE" else "#F44336" sentiment_emoji = "😊" if transformer_label == "POSITIVE" else "😞" output_html.append(f"""
{sentiment_emoji}

{transformer_label.capitalize()}

Confidence: {transformer_score:.2%}

""") # Confidence bar output_html.append(f"""
{transformer_score:.1%} Confidence
""") except Exception as e: output_html.append(f"""

Transformer Model Error

Failed to load or run transformer sentiment model: {str(e)}

Falling back to VADER results only.

""") # Emotion Analysis output_html.append('

Emotion Analysis

') output_html.append('

Identifying specific emotions in text using a RoBERTa model fine-tuned on the emotion dataset.

') try: # Load emotion classifier emotion_classifier = load_emotion_classifier() # Get predictions truncated_text = text_input[:max_length * 4] # Rough character estimate emotion_result = emotion_classifier(truncated_text) # Extract emotion scores emotion_scores = {} for item in emotion_result[0]: emotion_scores[item['label']] = item['score'] # Create emotion dataframe emotion_df = pd.DataFrame({ 'Emotion': list(emotion_scores.keys()), 'Score': list(emotion_scores.values()) }).sort_values('Score', ascending=False) # Get primary emotion primary_emotion = emotion_df.iloc[0]['Emotion'] primary_score = emotion_df.iloc[0]['Score'] # Emotion color map emotion_colors = { 'joy': '#FFD54F', 'anger': '#EF5350', 'sadness': '#42A5F5', 'fear': '#9C27B0', 'surprise': '#26C6DA', 'love': '#EC407A', 'disgust': '#66BB6A', 'optimism': '#FF9800', 'pessimism': '#795548', 'trust': '#4CAF50', 'anticipation': '#FF7043', 'neutral': '#9E9E9E' } # Emotion emoji map emotion_emojis = { 'joy': '😃', 'anger': '😠', 'sadness': '😢', 'fear': '😨', 'surprise': '😲', 'love': '❤️', 'disgust': '🤢', 'optimism': '🤩', 'pessimism': '😒', 'trust': '🤝', 'anticipation': '🤔', 'neutral': '😐' } # Create bar chart fig = plt.figure(figsize=(10, 6)) bars = plt.barh( emotion_df['Emotion'], emotion_df['Score'], color=[emotion_colors.get(emotion, '#9E9E9E') for emotion in emotion_df['Emotion']] ) plt.xlabel('Score') plt.title('Emotion Scores') # Add value labels for i, bar in enumerate(bars): plt.text(bar.get_width() + 0.01, bar.get_y() + bar.get_height()/2, f"{bar.get_width():.2f}", va='center') plt.xlim(0, 1) plt.tight_layout() # Chart section output_html.append('
') output_html.append('
') output_html.append(fig_to_html(fig)) output_html.append('
') output_html.append('
') # Primary emotion section primary_color = emotion_colors.get(primary_emotion, '#9E9E9E') primary_emoji = emotion_emojis.get(primary_emotion, '😐') output_html.append('
') output_html.append(f"""
{primary_emoji}

{primary_emotion.capitalize()}

Score: {primary_score:.2f}

""") # Show top emotions table output_html.append('

Top Emotions

') output_html.append(df_to_html_table(emotion_df.head(5))) output_html.append('
') # Close emotion result container except Exception as e: output_html.append(f"""

Emotion Analysis Error

Failed to load or run emotion classifier: {str(e)}

""") # Sentence-level Analysis output_html.append('

Sentence-level Analysis

') output_html.append('

Breaking down sentiment by individual sentences to identify sentiment variations throughout the text.

') # Split text into sentences sentences = nltk.sent_tokenize(text_input) # Minimum 2 sentences to do the analysis if len(sentences) >= 2: # Calculate sentiment for each sentence sentence_sentiments = [] for i, sentence in enumerate(sentences): vader_score = vader_analyzer.polarity_scores(sentence) sentence_sentiments.append({ 'Sentence': sentence, 'Index': i + 1, 'Compound': vader_score['compound'], 'Positive': vader_score['pos'], 'Negative': vader_score['neg'], 'Neutral': vader_score['neu'], 'Sentiment': 'Positive' if vader_score['compound'] >= 0.05 else 'Negative' if vader_score['compound'] <= -0.05 else 'Neutral' }) # Create DataFrame sent_df = pd.DataFrame(sentence_sentiments) # Create line graph of sentiment flow fig = plt.figure(figsize=(10, 6)) plt.plot(sent_df['Index'], sent_df['Compound'], 'o-', color='#1976D2', linewidth=2, markersize=8) plt.axhline(y=0, color='#9E9E9E', linestyle='-', alpha=0.3) plt.axhline(y=0.05, color='#4CAF50', linestyle='--', alpha=0.3) plt.axhline(y=-0.05, color='#F44336', linestyle='--', alpha=0.3) # Annotate with sentiment for i, row in sent_df.iterrows(): if row['Sentiment'] == 'Positive': color = '#4CAF50' elif row['Sentiment'] == 'Negative': color = '#F44336' else: color = '#9E9E9E' plt.scatter(row['Index'], row['Compound'], color=color, s=100, zorder=5) plt.grid(alpha=0.3) plt.xlabel('Sentence Number') plt.ylabel('Compound Sentiment Score') plt.title('Sentiment Flow Through Text') plt.ylim(-1.05, 1.05) plt.tight_layout() # Calculate statistics positive_count = sum(1 for score in sent_df['Compound'] if score >= 0.05) negative_count = sum(1 for score in sent_df['Compound'] if score <= -0.05) neutral_count = len(sent_df) - positive_count - negative_count # Chart section output_html.append('
') output_html.append('
') output_html.append(fig_to_html(fig)) output_html.append('
') output_html.append('
') # Sentence analysis section output_html.append('
') # Create sentence stats output_html.append(f"""
{positive_count}
Positive
{neutral_count}
Neutral
{negative_count}
Negative
""") # Display sentiment swings sentiment_changes = 0 prev_sentiment = None for sentiment in sent_df['Sentiment']: if prev_sentiment is not None and sentiment != prev_sentiment: sentiment_changes += 1 prev_sentiment = sentiment if sentiment_changes > 0: output_html.append(f"""

Sentiment Shifts: {sentiment_changes}

The text shows {sentiment_changes} shifts in sentiment between sentences.

""") # Show sentence breakdown table output_html.append('

Sentence-by-Sentence Analysis

') # Custom HTML table for better formatting output_html.append('
') output_html.append('') output_html.append('') output_html.append('') for i, row in sent_df.iterrows(): if row['Sentiment'] == 'Positive': bg_class = 'table-success' sentiment_html = f"""
😊 Positive ({row['Compound']:.2f})
""" elif row['Sentiment'] == 'Negative': bg_class = 'table-danger' sentiment_html = f"""
😞 Negative ({row['Compound']:.2f})
""" else: bg_class = 'table-warning' sentiment_html = f"""
😐 Neutral ({row['Compound']:.2f})
""" output_html.append(f'') output_html.append(f'') output_html.append(f'') output_html.append(f'') output_html.append('') output_html.append('
#SentenceSentiment
{i+1}{row["Sentence"]}{sentiment_html}
') output_html.append('
') output_html.append('
') # Close sentence analysis container else: output_html.append("""

Sentence-level analysis requires at least two sentences. The provided text doesn't have enough sentences for this analysis.

""") except Exception as e: output_html.append(f"""

Error

Failed to analyze sentiment: {str(e)}

""") # About Sentiment Analysis section output_html.append("""

About Sentiment Analysis

What is Sentiment Analysis?

Sentiment Analysis (also known as opinion mining) is a natural language processing technique that identifies and extracts subjective information from text. It determines whether a piece of text expresses positive, negative, or neutral sentiment.

Common Approaches:
  1. Lexicon-based (like VADER) - Uses dictionaries of words with pre-assigned sentiment scores
  2. Machine learning - Supervised techniques that learn from labeled data
  3. Deep learning (like our Transformer models) - Neural networks that can capture complex patterns and contexts
Applications:
  • Brand monitoring - Track public perception of a brand
  • Customer feedback analysis - Understand customer satisfaction
  • Market research - Analyze product reviews and consumer opinions
  • Social media monitoring - Track public sentiment on topics or events
  • Stock market prediction - Analyze news sentiment to predict stock movements
""") output_html.append('
') # Close result-area div return '\n'.join(output_html)