Spaces:
Build error
Build error
Just Upload
Browse files- README.md +0 -14
- analyzer.py +53 -0
- app.py +193 -0
- requirements.txt +6 -0
README.md
CHANGED
|
@@ -1,14 +0,0 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: Sentiment Analysis
|
| 3 |
-
emoji: 👁
|
| 4 |
-
colorFrom: purple
|
| 5 |
-
colorTo: blue
|
| 6 |
-
sdk: streamlit
|
| 7 |
-
sdk_version: 1.44.1
|
| 8 |
-
app_file: app.py
|
| 9 |
-
pinned: false
|
| 10 |
-
license: mit
|
| 11 |
-
short_description: A powerful web application for analyzing sentiment in text d
|
| 12 |
-
---
|
| 13 |
-
|
| 14 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
analyzer.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from textblob import TextBlob
|
| 2 |
+
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
|
| 3 |
+
|
| 4 |
+
class SentimentAnalyzer:
|
| 5 |
+
def __init__(self):
|
| 6 |
+
self.vader = SentimentIntensityAnalyzer()
|
| 7 |
+
|
| 8 |
+
def analyze_textblob(self, text):
|
| 9 |
+
"""Analyze text sentiment using TextBlob"""
|
| 10 |
+
analysis = TextBlob(text)
|
| 11 |
+
polarity = analysis.sentiment.polarity
|
| 12 |
+
|
| 13 |
+
# sentiment category
|
| 14 |
+
if polarity > 0.05:
|
| 15 |
+
sentiment = "Positive"
|
| 16 |
+
emoji = "😃"
|
| 17 |
+
elif polarity < -0.05:
|
| 18 |
+
sentiment = "Negative"
|
| 19 |
+
emoji = "😞"
|
| 20 |
+
else:
|
| 21 |
+
sentiment = "Neutral"
|
| 22 |
+
emoji = "😐"
|
| 23 |
+
|
| 24 |
+
return {
|
| 25 |
+
"sentiment": sentiment,
|
| 26 |
+
"polarity": polarity,
|
| 27 |
+
"emoji": emoji,
|
| 28 |
+
"subjectivity": analysis.sentiment.subjectivity
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
def analyze_vader(self, text):
|
| 32 |
+
"""Analyze text sentiment using VADER"""
|
| 33 |
+
scores = self.vader.polarity_scores(text)
|
| 34 |
+
|
| 35 |
+
# determine sentiment category based on compound score
|
| 36 |
+
if scores["compound"] >= 0.05:
|
| 37 |
+
sentiment = "Positive"
|
| 38 |
+
emoji = "😃"
|
| 39 |
+
elif scores["compound"] <= -0.05:
|
| 40 |
+
sentiment = "Negative"
|
| 41 |
+
emoji = "😞"
|
| 42 |
+
else:
|
| 43 |
+
sentiment = "Neutral"
|
| 44 |
+
emoji = "😐"
|
| 45 |
+
|
| 46 |
+
return {
|
| 47 |
+
"sentiment": sentiment,
|
| 48 |
+
"compound": scores["compound"],
|
| 49 |
+
"pos": scores["pos"],
|
| 50 |
+
"neu": scores["neu"],
|
| 51 |
+
"neg": scores["neg"],
|
| 52 |
+
"emoji": emoji
|
| 53 |
+
}
|
app.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Author: Jivan Jamdar
|
| 2 |
+
# Date: 2023-10-01 Time: 20:57:43PM
|
| 3 |
+
# Description: Streamlit app for sentiment analysis using TextBlob and VADER
|
| 4 |
+
|
| 5 |
+
import streamlit as st
|
| 6 |
+
import pandas as pd
|
| 7 |
+
import plotly.express as px
|
| 8 |
+
from analyzer import SentimentAnalyzer
|
| 9 |
+
|
| 10 |
+
def main():
|
| 11 |
+
st.set_page_config(
|
| 12 |
+
page_title="Sentiment Analysis App",
|
| 13 |
+
page_icon="📊",
|
| 14 |
+
layout="wide"
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
st.title("📊 Sentiment Analysis Tool")
|
| 18 |
+
st.write("Analyze the sentiment of tweets, product reviews, or any text!")
|
| 19 |
+
|
| 20 |
+
analyzer = SentimentAnalyzer()
|
| 21 |
+
|
| 22 |
+
# different tabs => different functionalities
|
| 23 |
+
tab1, tab2, tab3 = st.tabs(["Single Text Analysis", "Batch Analysis", "About"])
|
| 24 |
+
|
| 25 |
+
with tab1:
|
| 26 |
+
st.subheader("Single Text Analysis")
|
| 27 |
+
|
| 28 |
+
# tool selection
|
| 29 |
+
analysis_tool = st.radio(
|
| 30 |
+
"Select analysis tool:",
|
| 31 |
+
["TextBlob", "VADER"],
|
| 32 |
+
horizontal=True
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
# text input
|
| 36 |
+
text_input = st.text_area(
|
| 37 |
+
"Enter text to analyze:",
|
| 38 |
+
height=150,
|
| 39 |
+
placeholder="Type or paste your text here..."
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
if st.button("Analyze Sentiment"):
|
| 43 |
+
if text_input:
|
| 44 |
+
with st.spinner("Analyzing..."):
|
| 45 |
+
if analysis_tool == "TextBlob":
|
| 46 |
+
result = analyzer.analyze_textblob(text_input)
|
| 47 |
+
method = "TextBlob"
|
| 48 |
+
else:
|
| 49 |
+
result = analyzer.analyze_vader(text_input)
|
| 50 |
+
method = "VADER"
|
| 51 |
+
|
| 52 |
+
# display results with columns
|
| 53 |
+
st.subheader("Results")
|
| 54 |
+
col1, col2 = st.columns(2)
|
| 55 |
+
|
| 56 |
+
with col1:
|
| 57 |
+
st.markdown(f"### {result['emoji']} {result['sentiment']}")
|
| 58 |
+
st.write(f"Analysis method: {method}")
|
| 59 |
+
|
| 60 |
+
with col2:
|
| 61 |
+
if method == "TextBlob":
|
| 62 |
+
st.metric("Polarity", f"{result['polarity']:.2f}")
|
| 63 |
+
st.metric("Subjectivity", f"{result['subjectivity']:.2f}")
|
| 64 |
+
else: # VADER
|
| 65 |
+
st.metric("Compound Score", f"{result['compound']:.2f}")
|
| 66 |
+
|
| 67 |
+
# detailed breakdown for VADER
|
| 68 |
+
if method == "VADER":
|
| 69 |
+
st.subheader("Sentiment Breakdown")
|
| 70 |
+
vader_df = pd.DataFrame({
|
| 71 |
+
'Sentiment': ['Positive', 'Neutral', 'Negative'],
|
| 72 |
+
'Score': [result['pos'], result['neu'], result['neg']]
|
| 73 |
+
})
|
| 74 |
+
|
| 75 |
+
fig = px.bar(
|
| 76 |
+
vader_df,
|
| 77 |
+
x='Sentiment',
|
| 78 |
+
y='Score',
|
| 79 |
+
color='Sentiment',
|
| 80 |
+
color_discrete_map={
|
| 81 |
+
'Positive': '#2ECC71',
|
| 82 |
+
'Neutral': '#3498DB',
|
| 83 |
+
'Negative': '#E74C3C'
|
| 84 |
+
}
|
| 85 |
+
)
|
| 86 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 87 |
+
else:
|
| 88 |
+
st.warning("Please enter some text to analyze.")
|
| 89 |
+
|
| 90 |
+
with tab2:
|
| 91 |
+
st.subheader("Batch Analysis")
|
| 92 |
+
st.write("Upload a CSV or Excel file with a column containing text to analyze multiple entries at once.")
|
| 93 |
+
|
| 94 |
+
uploaded_file = st.file_uploader("Upload your file", type=["csv", "xlsx"])
|
| 95 |
+
|
| 96 |
+
if uploaded_file is not None:
|
| 97 |
+
try:
|
| 98 |
+
if uploaded_file.name.endswith('.csv'):
|
| 99 |
+
df = pd.read_csv(uploaded_file)
|
| 100 |
+
else:
|
| 101 |
+
df = pd.read_excel(uploaded_file)
|
| 102 |
+
|
| 103 |
+
st.write("Preview of uploaded data:")
|
| 104 |
+
st.dataframe(df.head())
|
| 105 |
+
|
| 106 |
+
text_column = st.selectbox("Select the column containing text to analyze:", df.columns)
|
| 107 |
+
analysis_tool = st.radio(
|
| 108 |
+
"Select analysis tool for batch processing:",
|
| 109 |
+
["TextBlob", "VADER"],
|
| 110 |
+
horizontal=True
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
if st.button("Run Batch Analysis"):
|
| 114 |
+
with st.spinner("Analyzing all entries..."):
|
| 115 |
+
results = []
|
| 116 |
+
|
| 117 |
+
for text in df[text_column]:
|
| 118 |
+
if pd.notna(text): # skip NaN values
|
| 119 |
+
if analysis_tool == "TextBlob":
|
| 120 |
+
result = analyzer.analyze_textblob(str(text))
|
| 121 |
+
else:
|
| 122 |
+
result = analyzer.analyze_vader(str(text))
|
| 123 |
+
results.append(result)
|
| 124 |
+
else:
|
| 125 |
+
# handle NaN values
|
| 126 |
+
results.append({
|
| 127 |
+
'sentiment': 'Unknown',
|
| 128 |
+
'emoji': '❓',
|
| 129 |
+
'compound' if analysis_tool == "VADER" else 'polarity': 0
|
| 130 |
+
})
|
| 131 |
+
|
| 132 |
+
# create results DataFrame
|
| 133 |
+
results_df = pd.DataFrame(results)
|
| 134 |
+
df_with_sentiment = pd.concat([df, results_df], axis=1)
|
| 135 |
+
|
| 136 |
+
st.subheader("Results")
|
| 137 |
+
st.dataframe(df_with_sentiment)
|
| 138 |
+
|
| 139 |
+
# download button for results
|
| 140 |
+
st.download_button(
|
| 141 |
+
label="Download Results",
|
| 142 |
+
data=df_with_sentiment.to_csv(index=False),
|
| 143 |
+
file_name="sentiment_analysis_results.csv",
|
| 144 |
+
mime="text/csv"
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
# show summary statistics
|
| 148 |
+
st.subheader("Sentiment Distribution")
|
| 149 |
+
sentiment_counts = results_df['sentiment'].value_counts().reset_index()
|
| 150 |
+
sentiment_counts.columns = ['Sentiment', 'Count']
|
| 151 |
+
|
| 152 |
+
fig = px.pie(
|
| 153 |
+
sentiment_counts,
|
| 154 |
+
names='Sentiment',
|
| 155 |
+
values='Count',
|
| 156 |
+
color='Sentiment',
|
| 157 |
+
color_discrete_map={
|
| 158 |
+
'Positive': '#2ECC71',
|
| 159 |
+
'Neutral': '#3498DB',
|
| 160 |
+
'Negative': '#E74C3C',
|
| 161 |
+
'Unknown': '#95A5A6'
|
| 162 |
+
}
|
| 163 |
+
)
|
| 164 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 165 |
+
|
| 166 |
+
except Exception as e:
|
| 167 |
+
st.error(f"Error processing the uploaded file: {str(e)}")
|
| 168 |
+
|
| 169 |
+
with tab3:
|
| 170 |
+
st.subheader("About This App")
|
| 171 |
+
st.write("""
|
| 172 |
+
This Sentiment Analysis App uses two popular techniques:
|
| 173 |
+
|
| 174 |
+
1. **TextBlob**: A simple NLP library that provides a simple API for diving into common NLP tasks.
|
| 175 |
+
- Polarity: Score from -1 (very negative) to +1 (very positive)
|
| 176 |
+
- Subjectivity: Score from 0 (objective) to 1 (subjective)
|
| 177 |
+
|
| 178 |
+
2. **VADER** (Valence Aware Dictionary and sEntiment Reasoner): A lexicon and rule-based sentiment analysis tool specifically attuned to sentiments expressed in social media.
|
| 179 |
+
- Compound: Normalized score from -1 (very negative) to +1 (very positive)
|
| 180 |
+
- Positive, Neutral, Negative: Proportions of text that fall in each category
|
| 181 |
+
|
| 182 |
+
### When to use each tool:
|
| 183 |
+
- TextBlob is simpler and works well for general text
|
| 184 |
+
- VADER is better for social media content, slang, and emoticons
|
| 185 |
+
|
| 186 |
+
### Limitations:
|
| 187 |
+
- Neither tool understands sarcasm well
|
| 188 |
+
- Context is often missed
|
| 189 |
+
- Language specific (works best with English)
|
| 190 |
+
""")
|
| 191 |
+
|
| 192 |
+
if __name__ == "__main__":
|
| 193 |
+
main()
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit>=1.24.0
|
| 2 |
+
textblob>=0.17.1
|
| 3 |
+
vaderSentiment>=3.3.2
|
| 4 |
+
pandas>=2.0.3
|
| 5 |
+
matplotlib==3.7.2
|
| 6 |
+
plotly>=5.15.0
|