File size: 9,539 Bytes
0ac9264
 
 
768bcdc
bb50616
 
0ac9264
 
 
 
 
bb50616
80af2a1
bb50616
 
a8cc4f6
 
 
12a4176
 
4ca73d1
 
 
 
 
0ac9264
0f8f9a1
768bcdc
0f8f9a1
b968a85
146c8e6
0ac9264
6a00b4d
e19bf91
 
 
 
 
0ac9264
4ca73d1
0ac9264
 
 
 
 
 
 
 
 
 
 
bb50616
 
 
 
4ca73d1
 
bb50616
 
 
 
 
 
 
b968a85
 
6a00b4d
768bcdc
4ca73d1
e19bf91
 
 
 
 
 
 
 
4ca73d1
b968a85
 
768bcdc
 
0ac9264
 
4349862
0ac9264
6a00b4d
0ac9264
 
4ca73d1
0ac9264
6a00b4d
0ac9264
6a00b4d
 
 
 
 
 
 
 
 
4ca73d1
6a00b4d
 
 
 
0ac9264
 
 
 
6a00b4d
0ac9264
4ca73d1
0ac9264
6a00b4d
a8cc4f6
 
4ca73d1
 
 
768bcdc
 
4ca73d1
0ac9264
 
b968a85
4ca73d1
 
0ac9264
 
0f8f9a1
a8cc4f6
 
 
0ac9264
b968a85
bb50616
 
6a00b4d
0ac9264
 
 
 
 
e19bf91
 
06b8d71
 
 
768bcdc
 
6a00b4d
768bcdc
b968a85
768bcdc
 
 
b968a85
 
bb50616
 
 
6a00b4d
0ac9264
 
 
 
 
 
768bcdc
0ac9264
 
2fd93e8
0ac9264
 
2fd93e8
0ac9264
 
 
b968a85
 
a8cc4f6
0ac9264
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bb50616
0f8f9a1
 
 
 
 
 
 
 
 
9052e90
a8cc4f6
0ac9264
a8cc4f6
0ac9264
bb50616
0f8f9a1
bb50616
 
0ac9264
 
bb50616
 
 
 
0f8f9a1
bb50616
 
 
 
 
6a00b4d
 
0ac9264
 
 
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
import streamlit as st
import pandas as pd
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from transformers import pipeline
from fuzzywuzzy import fuzz
from sklearn.feature_extraction.text import TfidfVectorizer
import torch.nn.functional as F
import torch
import io
import base64
from stqdm import stqdm
import nltk

from nltk.corpus import stopwords
nltk.download('stopwords')
import matplotlib.pyplot as plt
import numpy as np

stopwords_list = stopwords.words('english') + ['your_additional_stopwords_here']

# Define the model and tokenizer
model_name = 'nlptown/bert-base-multilingual-uncased-sentiment'
model = AutoModelForSequenceClassification.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
st.set_page_config(layout="wide")

# Import the new model and tokenizer

classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")

BATCH_SIZE = 20
#defs
def classify_reviews(reviews):
    probabilities = []
    for i in range(0, len(reviews), BATCH_SIZE):
        inputs = tokenizer(reviews[i:i+BATCH_SIZE], return_tensors='pt', truncation=True, padding=True, max_length=512)
        outputs = model(**inputs)
        probabilities.extend(F.softmax(outputs.logits, dim=1).tolist())
    return probabilities

def top_rating(scores):
    return scores.index(max(scores)) + 1  

def top_prob(scores):
    return max(scores)

def get_table_download_link(df):
    csv = df.to_csv(index=False)
    b64 = base64.b64encode(csv.encode()).decode()
    return f'<a href="data:file/csv;base64,{b64}" download="data.csv">Download csv file</a>'

def filter_dataframe(df, review_column, filter_words):
    # Return full DataFrame if filter_words is empty or contains only spaces
    if not filter_words or all(word.isspace() for word in filter_words):
        return df
    filter_scores = df[review_column].apply(lambda x: max([fuzz.token_set_ratio(x, word) for word in filter_words]))
    return df[filter_scores > 70]  # Adjust this threshold as necessary



def process_filter_words(filter_words_input):
    filter_words = [word.strip() for word in filter_words_input.split(',')]
    return filter_words


# Function for classifying with the new model
def classify_with_new_classes(reviews, class_names):
    class_scores = []

    for i in range(0, len(reviews), BATCH_SIZE):
        batch_reviews = reviews[i:i+BATCH_SIZE]
        for review in batch_reviews:
            result = classifier(review, class_names)
            scores_dict = dict(zip(result['labels'], result['scores']))
            # Reorder scores to match the original class_names order
            scores = [scores_dict[name] for name in class_names]
            class_scores.append(scores)

    return class_scores



def main():
    st.title('Sentiment Analysis')
    st.markdown('Upload an Excel file to get sentiment analytics')

    file = st.file_uploader("Upload an excel file", type=['xlsx'])
    review_column = None
    df = None
    class_names = None  # New variable for class names

    if file is not None:
        try:
            df = pd.read_excel(file)
            # Drop rows where all columns are NaN
            df = df.dropna(how='all')
            # Replace blank spaces with NaN, then drop rows where all columns are NaN again
            df = df.replace(r'^\s*$', np.nan, regex=True)
            df = df.dropna(how='all')
            review_column = st.selectbox('Select the column from your excel file containing text', df.columns)
            df[review_column] = df[review_column].astype(str)

            
            filter_words_input = st.text_input('Enter words to filter the data by, separated by comma (or leave empty)')  # New input field for filter words
            filter_words = [] if filter_words_input.strip() == "" else process_filter_words(filter_words_input)  # Process the filter words
            class_names = st.text_input('Enter the possible class names separated by comma')  # New input field for class names
            df = filter_dataframe(df, review_column, filter_words)  # Filter the DataFrame
        except Exception as e:
            st.write("An error occurred while reading the uploaded file. Please make sure it's a valid Excel file.")
            return

    start_button = st.button('Start Analysis')

    
    if start_button and df is not None:
        # Drop rows with NaN or blank values in the review_column
        df = df[df[review_column].notna()]
        df = df[df[review_column].str.strip() != '']
    
        class_names = [name.strip() for name in class_names.split(',')]  # Split class names into a list
        for name in class_names:  # Add a new column for each class name
            if name not in df.columns:
                df[name] = 0.0
    
        if review_column in df.columns:
            with st.spinner('Performing sentiment analysis...'):
                df, df_display = process_reviews(df, review_column, class_names)
    
            display_ratings(df, review_column)  # updated this line
            display_dataframe(df, df_display)
        else:
            st.write(f'No column named "{review_column}" found in the uploaded file.')







def process_reviews(df, review_column, class_names):
    with st.spinner('Classifying reviews...'):
        progress_bar = st.progress(0)
        total_reviews = len(df[review_column].tolist())
        review_counter = 0

        raw_scores = classify_reviews(df[review_column].tolist())
        for i in range(0, len(raw_scores), BATCH_SIZE):
            review_counter += min(BATCH_SIZE, len(raw_scores) - i)  # Avoids overshooting the total reviews count
            progress = min(review_counter / total_reviews, 1)  # Ensures progress does not exceed 1
            progress_bar.progress(progress)

    with st.spinner('Generating classes...'):
        class_scores = classify_with_new_classes(df[review_column].tolist(), class_names)
    
    class_scores_dict = {}  # New dictionary to store class scores
    for i, name in enumerate(class_names):
        df[name] = [score[i] for score in class_scores]
        class_scores_dict[name] = [score[i] for score in class_scores]

    # Add a new column with the class that has the highest score
    if class_names and not all(name.isspace() for name in class_names):
        df['Highest Class'] = df[class_names].idxmax(axis=1)


    df_new = df.copy()
    df_new['raw_scores'] = raw_scores
    scores_to_df(df_new)
    df_display = scores_to_percent(df_new.copy())

    # Get all columns excluding the created ones and the review_column
    remaining_columns = [col for col in df.columns if col not in [review_column, 'raw_scores', 'Weighted Rating', 'Rating', 'Probability', '1 Star', '2 Star', '3 Star', '4 Star', '5 Star', 'Highest Class'] + class_names]

    # Reorder the dataframe with selected columns first, created columns next, then the remaining columns
    df_new = df_new[[review_column, 'Weighted Rating', 'Rating', 'Probability', '1 Star', '2 Star', '3 Star', '4 Star', '5 Star'] + class_names + ['Highest Class'] + remaining_columns]

    # Reorder df_display as well
    df_display = df_display[[review_column, 'Weighted Rating', 'Rating', 'Probability', '1 Star', '2 Star', '3 Star', '4 Star', '5 Star'] + class_names + ['Highest Class'] + remaining_columns]

    return df_new, df_display




def scores_to_df(df):
    for i in range(1, 6):
        df[f'{i} Star'] = df['raw_scores'].apply(lambda scores: scores[i-1]).round(2)

    df['Rating'] = df['raw_scores'].apply(top_rating)
    df['Probability'] = df['raw_scores'].apply(top_prob).round(2)
    # Compute the Weighted Rating
    df['Weighted Rating'] = sum(df[f'{i} Star']*i for i in range(1, 6))
    
    df.drop(columns=['raw_scores'], inplace=True)

def scores_to_percent(df):
    for i in range(1, 6):
        df[f'{i} Star'] = df[f'{i} Star'].apply(lambda x: f'{x*100:.0f}%')

    df['Probability'] = df['Probability'].apply(lambda x: f'{x*100:.0f}%')

    return df

def convert_df_to_csv(df):
   return df.to_csv(index=False).encode('utf-8')

def display_dataframe(df, df_display):
    csv = convert_df_to_csv(df)

    col1, col2, col3, col4, col5, col6, col7, col8, col9 = st.columns(9)

    with col1:
        st.download_button(
            "Download CSV",
            csv,
            "data.csv",
            "text/csv",
            key='download-csv'
        )

    st.dataframe(df_display)
    
def important_words(reviews, num_words=5):
    if len(reviews) == 0:
        return []
    vectorizer = TfidfVectorizer(stop_words=stopwords_list, max_features=10000)
    vectors = vectorizer.fit_transform(reviews)
    features = vectorizer.get_feature_names_out()
    indices = np.argsort(vectorizer.idf_)[::-1]
    top_features = [features[i] for i in indices[:num_words]]
    return top_features

def display_ratings(df, review_column):
    cols = st.columns(5)
    
    for i in range(1, 6):
        rating_reviews = df[df['Rating'] == i][review_column]
        top_words = important_words(rating_reviews)

        rating_counts = rating_reviews.shape[0]
        cols[i-1].markdown(f"### {rating_counts}")
        cols[i-1].markdown(f"{'⭐' * i}")

        # Display the most important words for each rating
        cols[i-1].markdown(f"#### Most Important Words:")
        if top_words: 
            for word in top_words:
                cols[i-1].markdown(f"**{word}**")
        else: 
            cols[i-1].markdown("No important words to display")


        


if __name__ == "__main__":
    main()