Spaces:
Sleeping
Sleeping
Commit
·
e0c55bb
1
Parent(s):
26e930a
Upload 28 files
Browse files- .gitattributes +1 -0
- app.py +69 -0
- conclusion.py +23 -0
- eda.py +190 -0
- functions/__init__.py +0 -0
- functions/text_preprocessed.py +46 -0
- images/Boxplot_of_Ratings_by_Category.png +0 -0
- images/Category_Popularity.png +0 -0
- images/Distribution_of_Sentiment_Polarity.png +0 -0
- images/Distribution_of_Sentiment_Subjectivity.png +0 -0
- images/Distribution_of_Text_Length_Character_by_Sentiment.png +0 -0
- images/Model_Evaluatio_GRU.png +0 -0
- images/Model_Evaluation_CNN.png +0 -0
- images/Model_Evaluation_LSTM.png +0 -0
- images/Negative_Sentiment_Words.png +0 -0
- images/Neutral_Sentiment_Words.png +0 -0
- images/Positive_Sentiment_Words.png +0 -0
- images/Price_Distribution_Among_Paid_Apps.png +0 -0
- images/Rating_vs_Reviews.png +0 -0
- images/Sentiment_Distribution.png +0 -0
- images/rating_distribution.png +0 -0
- images/reviews_distribution.png +0 -0
- lstm/fingerprint.pb +3 -0
- lstm/keras_metadata.pb +3 -0
- lstm/saved_model.pb +3 -0
- lstm/variables/variables.data-00000-of-00001 +3 -0
- lstm/variables/variables.index +0 -0
- model.py +101 -0
- requirements.txt +3 -0
.gitattributes
CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
lstm/variables/variables.data-00000-of-00001 filter=lfs diff=lfs merge=lfs -text
|
app.py
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import eda
|
3 |
+
import model
|
4 |
+
import conclusion
|
5 |
+
|
6 |
+
# Sidebar
|
7 |
+
st.sidebar.header("Choose Here!")
|
8 |
+
options = ['Home Page', 'Exploratory Data Analysis', 'Test our Model!', 'Conclusion']
|
9 |
+
page = st.sidebar.selectbox(label='Select Page:', options=options)
|
10 |
+
|
11 |
+
# Home Page
|
12 |
+
if page == 'Home Page':
|
13 |
+
st.header('Feedback to Foresight: Simplifying App Review Sentiment Analysis')
|
14 |
+
st.caption("This project was carried out as part of Hacktiv8's Data Science programme final collaborative project.")
|
15 |
+
st.caption('Please check our github repository [here!](https://github.com/devinlee14/FTDS-009-HCK-group-002)')
|
16 |
+
st.markdown('---')
|
17 |
+
|
18 |
+
st.markdown('''
|
19 |
+
#### Group members:
|
20 |
+
* Devin Yaung Lee — Data Analyst
|
21 |
+
* Fernaldy Aristo Wirjowerdojo — Data Engineer
|
22 |
+
* Muhammad Furqon Pakpahan — Data Engineer
|
23 |
+
* Sifra Hilda Juliana Siregar — Data Scientist
|
24 |
+
''')
|
25 |
+
st.write('')
|
26 |
+
|
27 |
+
st.caption('Please select another page in the `Select Page` on the left side of your screen to get started!')
|
28 |
+
st.write('')
|
29 |
+
|
30 |
+
with st.expander("Project Overview"):
|
31 |
+
st.caption('''
|
32 |
+
This project focuses on performing sentiment analysis on Google Play Store app reviews.
|
33 |
+
Utilizing Natural Language Processing (NLP), the goal is to analyse user feedback
|
34 |
+
to gain insights into satisfaction and app perception.
|
35 |
+
''')
|
36 |
+
|
37 |
+
with st.expander("Problem Statement"):
|
38 |
+
st.caption('''
|
39 |
+
In the competitive landscape of mobile applications, user feedback for app reviews is a
|
40 |
+
goldmine of insights that can inform product development and marketing strategies.
|
41 |
+
However, these reviews are often unstructured, making it challenging to efficiently extract,
|
42 |
+
categorize, and analyze sentiments and opinions. There is a need for an automated system
|
43 |
+
that can process this feedback to provide actionable insights, identify trends in user sentiment,
|
44 |
+
and highlight areas for improvement. This project aims to address the lack of structured
|
45 |
+
analysis of user-generated content in app reviews on the Google Play Store, which,
|
46 |
+
if leveraged correctly, can significantly enhance user satisfaction and app performance in the market.
|
47 |
+
''')
|
48 |
+
|
49 |
+
with st.expander("Objectives"):
|
50 |
+
st.caption('''
|
51 |
+
* **Develop an Automated Sentiment Analysis Model**
|
52 |
+
Build and train a TensorFlow model to classify app reviews into positive, negative, and neutral sentiments with high accuracy.
|
53 |
+
|
54 |
+
* **Understand the User Feedbacks in Depth**
|
55 |
+
Utilize the sentiment analysis model to delve into the nuances of user feedback on the Google Play Store.
|
56 |
+
''')
|
57 |
+
|
58 |
+
# EDA
|
59 |
+
elif page == 'Exploratory Data Analysis':
|
60 |
+
eda.run()
|
61 |
+
|
62 |
+
# Model
|
63 |
+
elif page == 'Test our Model!':
|
64 |
+
model.run()
|
65 |
+
|
66 |
+
# Conclusion
|
67 |
+
else:
|
68 |
+
conclusion.run()
|
69 |
+
|
conclusion.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
|
3 |
+
# Fill with project conclusion
|
4 |
+
def run():
|
5 |
+
st.header('Conclusion')
|
6 |
+
st.markdown('---')
|
7 |
+
# -------------------------------------------------------------------------
|
8 |
+
st.markdown('''
|
9 |
+
**Model Deployment**: Integrating the sentiment analysis model into Google's app review system can be leveraged to achieve several outcomes:
|
10 |
+
* Quality Control: Real-time sentiment scoring can be used to flag apps with consistently poor sentiment for quality review, ensuring that the apps offered on the Play Store maintain a high standard
|
11 |
+
* Trend Detection: Google can monitor sentiment trends for early detection of issues like bugs in recent app updates, or to identify apps that are suddenly gaining positive attention, which could then be featured or recommended
|
12 |
+
''')
|
13 |
+
st.markdown('---')
|
14 |
+
st.markdown('''
|
15 |
+
**Strategic Actions**: Providing sentiment analysis feedback to app developers can be expanded upon for further strategic initiatives:
|
16 |
+
* Automated Category Insights: An automated system could provide developers with real-time analytics on how their app's sentiment compares to the average within its category, including highlighting specific aspects like customer service, usability, or functionality that may need attention
|
17 |
+
* Benchmarking and Best Practices: Developers can receive benchmark reports comparing their apps with top-performing ones in the same category, offering insights into best practices and areas for improvement
|
18 |
+
* Predictive Analytics for Developers: By analyzing sentiment trends, Google can offer predictive insights to developers, helping them anticipate user needs and expectations, and guiding them on when to release updates or introduce new features
|
19 |
+
* Content Moderation Strategies: Using sentiment analysis to prioritize the review of content can help:
|
20 |
+
* Improve Moderation Efficiency: Focus human moderators' efforts on the most critical content first, improving the efficiency of the moderation process
|
21 |
+
* Enhance App Safety: Quickly address apps with negative sentiments that might be related to safety or compliance issues, maintaining a safe environment for all users
|
22 |
+
* Refine Automated Systems: Feed sentiment analysis data into automated content moderation systems to improve their accuracy and responsiveness
|
23 |
+
''')
|
eda.py
ADDED
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
|
3 |
+
def run():
|
4 |
+
st.title("Exploratory Data Analysis")
|
5 |
+
st.markdown('---')
|
6 |
+
|
7 |
+
# -----------------------------------------------------------------------------------------
|
8 |
+
# Rating Distribution
|
9 |
+
st.markdown('### Rating Distribution')
|
10 |
+
st.image('images/rating_distribution.png', caption='Figure 1')
|
11 |
+
with st.expander('Explanation'):
|
12 |
+
st.caption('From this Histogram of Rating Distribution, we know:')
|
13 |
+
st.caption('''
|
14 |
+
* The distribution is skewed towards higher ratings, with most apps receiving ratings between 4.0 and 4.7
|
15 |
+
* The highest frequency of ratings is at 4.4, followed closely by 4.3 and 4.5, indicating that a large number of apps are rated favorably
|
16 |
+
* Very few apps have ratings lower than 3.0, suggesting either a selection of generally well-received apps or a tendency for users
|
17 |
+
to rate apps more favorably
|
18 |
+
''')
|
19 |
+
st.caption('''
|
20 |
+
The drop in frequency for ratings 4.8 and above could indicate a standard where few apps
|
21 |
+
are rated as near-perfect. Overall, this distribution indicates a trend where users rate apps positively,
|
22 |
+
with few instances of very low ratings.
|
23 |
+
''')
|
24 |
+
st.write('')
|
25 |
+
st.write('')
|
26 |
+
|
27 |
+
# -----------------------------------------------------------------------------------------
|
28 |
+
# Review Distribution
|
29 |
+
st.markdown('### Review Distribution')
|
30 |
+
st.image('images/reviews_distribution.png', caption='Figure 2')
|
31 |
+
with st.expander('Explanation'):
|
32 |
+
st.caption('''
|
33 |
+
The distribution is highly right-skewed, indicating that a large number of apps have a small number of reviews,
|
34 |
+
This could be due to several reasons like new apps, or even unpopular apps, while only a few apps have a
|
35 |
+
very high number of reviews. This pattern suggests that a small subset of apps is receiving the majority
|
36 |
+
of the attention from users in terms of reviews.
|
37 |
+
''')
|
38 |
+
st.write('')
|
39 |
+
st.write('')
|
40 |
+
|
41 |
+
# -----------------------------------------------------------------------------------------
|
42 |
+
# Price distribution among paid apps
|
43 |
+
st.markdown('### Price Distribution among Paid Apps')
|
44 |
+
st.image('images/Price_Distribution_Among_Paid_Apps.png', caption='Figure 3')
|
45 |
+
with st.expander('Explanation'):
|
46 |
+
st.caption('''
|
47 |
+
Most paid apps are priced below $10, with peaks at around the $2 and $4 price points.
|
48 |
+
There are fewer apps at higher price points, indicating that lower-priced apps are
|
49 |
+
more common and potentially more popular among users.
|
50 |
+
''')
|
51 |
+
st.write('')
|
52 |
+
st.write('')
|
53 |
+
|
54 |
+
# -----------------------------------------------------------------------------------------
|
55 |
+
# Ratings vs Reviews
|
56 |
+
st.markdown('### Ratings vs Reviews')
|
57 |
+
st.image('images/Rating_vs_Reviews.png', caption='Figure 4')
|
58 |
+
with st.expander('Explanation'):
|
59 |
+
st.caption('''
|
60 |
+
There is a concentration of apps with high ratings and a moderate number of reviews,
|
61 |
+
very few apps have low ratings, and apps with near perfect rating are relatively rare.
|
62 |
+
This may indicate that well-rated apps tend to receive a good number of reviews, but not all popular apps
|
63 |
+
(in terms of the number of reviews) are necessarily high-rated.
|
64 |
+
''')
|
65 |
+
st.write('')
|
66 |
+
st.write('')
|
67 |
+
|
68 |
+
# -----------------------------------------------------------------------------------------
|
69 |
+
# Category popularity
|
70 |
+
st.markdown('### Category Popularity')
|
71 |
+
st.image('images/Category_Popularity.png', caption='Figure 5')
|
72 |
+
with st.expander('Explanation'):
|
73 |
+
st.caption('''
|
74 |
+
The 'Game' category is the most popular, followed by 'Family' and 'Health & Fitness',
|
75 |
+
suggesting these are the most common types of apps. Less populated categories like
|
76 |
+
'Events' and 'Comics' may represent more niche markets.
|
77 |
+
''')
|
78 |
+
st.write('')
|
79 |
+
st.write('')
|
80 |
+
|
81 |
+
# -----------------------------------------------------------------------------------------
|
82 |
+
# Ratings by Category
|
83 |
+
st.markdown('### Ratings by Category')
|
84 |
+
st.image('images/Boxplot_of_Ratings_by_Category.png', caption='Figure 6')
|
85 |
+
with st.expander('Explanation'):
|
86 |
+
st.caption('''
|
87 |
+
* Most categories have median ratings above 4.0, indicating generally positive reception of apps across all categories
|
88 |
+
* Some categories show a wide range of ratings (evidenced by longer boxes), indicating more variability in how users rate these apps
|
89 |
+
* Categories with tight boxes, where Q1 and Q3 are close together, indicate more consistency in ratings
|
90 |
+
* Outliers are present in many categories, both on the high and low ends, suggesting that there are a
|
91 |
+
few apps that are rated significantly differently than the majority in their category
|
92 |
+
''')
|
93 |
+
st.caption('''
|
94 |
+
Overall, this plot provides a comprehensive view of how apps are rated within each category,
|
95 |
+
showing general user satisfaction and highlighting categories with more diverse user opinions.
|
96 |
+
''')
|
97 |
+
st.write('')
|
98 |
+
st.write('')
|
99 |
+
|
100 |
+
# -----------------------------------------------------------------------------------------
|
101 |
+
# Sentiment Distribution
|
102 |
+
st.markdown('### Sentiment Distribution')
|
103 |
+
st.image('images/Sentiment_Distribution.png', caption='Figure 7')
|
104 |
+
with st.expander('Explanation'):
|
105 |
+
st.caption('''
|
106 |
+
* The 'positive' category has the highest count, exceeding 20,000 items
|
107 |
+
* The 'negative' category has a lower count, roughly around 7,500 items
|
108 |
+
* The 'neutral' category has the least, with just under 5,000 items
|
109 |
+
''')
|
110 |
+
st.caption('This suggests that the positive sentiment among the items being analyzed predominates significantly over negative and neutral sentiments.')
|
111 |
+
st.write('')
|
112 |
+
st.write('')
|
113 |
+
|
114 |
+
# -----------------------------------------------------------------------------------------
|
115 |
+
# Sentiment Polarity Distribution
|
116 |
+
st.markdown('### Sentiment Polarity Distribution')
|
117 |
+
st.image('images/Distribution_of_Sentiment_Polarity.png', caption='Figure 8')
|
118 |
+
with st.expander('Explanation'):
|
119 |
+
st.caption('''
|
120 |
+
The chart shows a large concentration of scores around 0, indicating a high frequency of neutral sentiments.
|
121 |
+
There is a notable spike at exactly 0, which is significantly higher than any other value, suggesting a
|
122 |
+
large number of entries with a perfectly neutral sentiment. The distribution is somewhat bimodal, with smaller peaks
|
123 |
+
in the positive range (around 0.5) and negative range (around -0.25 to -0.5), implying clusters of positive and
|
124 |
+
negative sentiments as well. However, the positive sentiments appear to have a slightly wider spread
|
125 |
+
with multiple smaller peaks, while negative sentiments are more concentrated around their peak.
|
126 |
+
Overall, this suggests that the data contains a high volume of neutral sentiments, with a presence of
|
127 |
+
both positive and negative sentiments, and a broader diversity of positive sentiment intensities.
|
128 |
+
''')
|
129 |
+
st.write('')
|
130 |
+
st.write('')
|
131 |
+
|
132 |
+
# -----------------------------------------------------------------------------------------
|
133 |
+
# Sentiment Subjectivity Distribution
|
134 |
+
st.markdown('### Sentiment Subjectivity Distribution')
|
135 |
+
st.image('images/Distribution_of_Sentiment_Subjectivity.png', caption='Figure 9')
|
136 |
+
with st.expander('Explanation'):
|
137 |
+
st.caption('Sentiment subjectivity shows the level of objectiveness of the user review where 0 indicates no subjectivity and 1 indicates high subjectivity.')
|
138 |
+
st.caption('''
|
139 |
+
* A high peak at 0, suggesting a significant number of texts are classified with no subjectivity, meaning they are likely to be factual or objective
|
140 |
+
* Several moderate peaks throughout, especially noticeable around 0.2, 0.5, 0.6, and towards the higher end at 1.0
|
141 |
+
* The peaks at 0.5 and higher indicate a considerable number of texts contain subjective opinions
|
142 |
+
''')
|
143 |
+
st.caption('''
|
144 |
+
The distribution is somewhat uneven, suggesting varying levels of opinion across the dataset,
|
145 |
+
with a notable amount of completely objective (or detected as such) texts and others
|
146 |
+
expressing different degrees of subjectivity. The presence of multiple peaks indicates that
|
147 |
+
texts do not conform to a single level of subjectivity but vary widely, which might be typical in
|
148 |
+
datasets containing both factual information and personal opinions.
|
149 |
+
''')
|
150 |
+
st.write('')
|
151 |
+
st.write('')
|
152 |
+
|
153 |
+
# -----------------------------------------------------------------------------------------
|
154 |
+
# Text Length by Sentiment
|
155 |
+
st.markdown('### Text Length by Sentiment')
|
156 |
+
st.image('images/Distribution_of_Text_Length_Character_by_Sentiment.png', caption='Figure 10')
|
157 |
+
with st.expander('Explanation'):
|
158 |
+
st.caption('''
|
159 |
+
The distribution of text length for reviews shows that neutral sentiment texts are generally shorter,
|
160 |
+
with a mean length of around 7 words and a median of 5 words. Positive sentiment texts are longer,
|
161 |
+
with a mean of approximately 19 words and a median of 17 words, while negative sentiment texts have a mean
|
162 |
+
length close to 17 words and a median of 14 words. This could indicate that users tend to be more
|
163 |
+
verbose when expressing positive or negative sentiments, while neutral comments are more concise.
|
164 |
+
''')
|
165 |
+
st.write('')
|
166 |
+
st.write('')
|
167 |
+
|
168 |
+
# -----------------------------------------------------------------------------------------
|
169 |
+
# Wordclouds by Sentiment
|
170 |
+
st.markdown('## Wordcloud by Sentiment')
|
171 |
+
|
172 |
+
st.markdown('### Positive')
|
173 |
+
st.image('images/Positive_Sentiment_Words.png', caption='Figure 11')
|
174 |
+
st.write('')
|
175 |
+
|
176 |
+
st.markdown('### Negative')
|
177 |
+
st.image('images/Negative_Sentiment_Words.png', caption='Figure 12')
|
178 |
+
st.write('')
|
179 |
+
|
180 |
+
st.markdown('### Neutral')
|
181 |
+
st.image('images/Neutral_Sentiment_Words.png', caption='Figure 13')
|
182 |
+
with st.expander('Explanation'):
|
183 |
+
st.caption('''
|
184 |
+
The word clouds for positive, negative, and neutral sentiments highlight the most frequently used
|
185 |
+
words in each category.
|
186 |
+
* Positive sentiments words like "love", "great", "good" and "best" dominate, reflecting strong satisfaction
|
187 |
+
* Negative sentiment texts frequently include words like "bad", "problem", "worst" and "annoying" pointing to dissatisfaction
|
188 |
+
* Neutral sentiment texts feature words like "update", "phone" and "app" which may relate to more factual
|
189 |
+
or inquiry-based content rather than opinion.
|
190 |
+
''')
|
functions/__init__.py
ADDED
File without changes
|
functions/text_preprocessed.py
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
// function.py //
|
3 |
+
This programme was created to store the function used through out this project.
|
4 |
+
"""
|
5 |
+
|
6 |
+
|
7 |
+
import re
|
8 |
+
from nltk.tokenize import word_tokenize
|
9 |
+
|
10 |
+
# Create A Function for Text Preprocessing
|
11 |
+
def text_preprocessing(text, lemmatizer, sw):
|
12 |
+
# Case folding
|
13 |
+
text = text.lower()
|
14 |
+
|
15 |
+
# Mention removal
|
16 |
+
text = re.sub("@[A-Za-z0-9_]+", " ", text)
|
17 |
+
|
18 |
+
# Hashtags removal
|
19 |
+
text = re.sub("#[A-Za-z0-9_]+", " ", text)
|
20 |
+
|
21 |
+
# Newline removal (\n)
|
22 |
+
text = re.sub(r"\\n", " ",text)
|
23 |
+
|
24 |
+
# Whitespace removal
|
25 |
+
text = text.strip()
|
26 |
+
|
27 |
+
# URL removal
|
28 |
+
text = re.sub(r"http\S+", " ", text)
|
29 |
+
text = re.sub(r"www.\S+", " ", text)
|
30 |
+
|
31 |
+
# Non-letter removal (such as emoticon, symbol (like μ, $, 兀), etc
|
32 |
+
text = re.sub("[^A-Za-z\s']", " ", text)
|
33 |
+
|
34 |
+
# Tokenization
|
35 |
+
tokens = word_tokenize(text)
|
36 |
+
|
37 |
+
# Stopwords removal
|
38 |
+
tokens = [word for word in tokens if word not in sw]
|
39 |
+
|
40 |
+
# Lemmatization
|
41 |
+
tokens = [lemmatizer.lemmatize(word) for word in tokens]
|
42 |
+
|
43 |
+
# Combining Tokens
|
44 |
+
text = ' '.join(tokens)
|
45 |
+
|
46 |
+
return text
|
images/Boxplot_of_Ratings_by_Category.png
ADDED
![]() |
images/Category_Popularity.png
ADDED
![]() |
images/Distribution_of_Sentiment_Polarity.png
ADDED
![]() |
images/Distribution_of_Sentiment_Subjectivity.png
ADDED
![]() |
images/Distribution_of_Text_Length_Character_by_Sentiment.png
ADDED
![]() |
images/Model_Evaluatio_GRU.png
ADDED
![]() |
images/Model_Evaluation_CNN.png
ADDED
![]() |
images/Model_Evaluation_LSTM.png
ADDED
![]() |
images/Negative_Sentiment_Words.png
ADDED
![]() |
images/Neutral_Sentiment_Words.png
ADDED
![]() |
images/Positive_Sentiment_Words.png
ADDED
![]() |
images/Price_Distribution_Among_Paid_Apps.png
ADDED
![]() |
images/Rating_vs_Reviews.png
ADDED
![]() |
images/Sentiment_Distribution.png
ADDED
![]() |
images/rating_distribution.png
ADDED
![]() |
images/reviews_distribution.png
ADDED
![]() |
lstm/fingerprint.pb
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:899dd0b94437c31a4a06dbace1fed8937a202069f7ce55f7f16759c4d657ad85
|
3 |
+
size 58
|
lstm/keras_metadata.pb
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:e5483175b5876789d632cc61e8bae4301be7fdd7c456ab687e3a007150e10333
|
3 |
+
size 39507
|
lstm/saved_model.pb
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:0b7f7cb37ce193381331ccb0f964861920d081dd67e1461f8acd57b712b2669e
|
3 |
+
size 6416295
|
lstm/variables/variables.data-00000-of-00001
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:44f4a0db29f0487af5f79c98389dc6d7225069c328f699908b4006ffcc490354
|
3 |
+
size 42873689
|
lstm/variables/variables.index
ADDED
Binary file (4.33 kB). View file
|
|
model.py
ADDED
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
import nltk
|
4 |
+
from nltk.corpus import stopwords
|
5 |
+
from nltk.stem import WordNetLemmatizer
|
6 |
+
from functions.text_preprocessed import text_preprocessing
|
7 |
+
from tensorflow.keras.models import load_model
|
8 |
+
|
9 |
+
nltk.download('punkt')
|
10 |
+
nltk.download('stopwords')
|
11 |
+
nltk.download('wordnet')
|
12 |
+
|
13 |
+
def run():
|
14 |
+
st.title('Predict')
|
15 |
+
st.write('You can use our model here by inputting your text (review) here:')
|
16 |
+
|
17 |
+
# -------------------------------------------------------------------------
|
18 |
+
# Dataframe
|
19 |
+
data = pd.DataFrame()
|
20 |
+
|
21 |
+
# -------------------------------------------------------------------------
|
22 |
+
# App name
|
23 |
+
data['app'] = [st.text_input('Application name')]
|
24 |
+
|
25 |
+
# -------------------------------------------------------------------------
|
26 |
+
# Category
|
27 |
+
data['category'] = [st.text_input('Application category')]
|
28 |
+
|
29 |
+
# -------------------------------------------------------------------------
|
30 |
+
# Rating
|
31 |
+
data['rating'] = [round(st.slider('Rating', min_value=0.0, max_value=5.0), 1)]
|
32 |
+
|
33 |
+
# -------------------------------------------------------------------------
|
34 |
+
# Reviews
|
35 |
+
data['reviews'] = [st.number_input('Total review count', min_value=0)]
|
36 |
+
|
37 |
+
# -------------------------------------------------------------------------
|
38 |
+
# Size
|
39 |
+
data['size'] = [st.text_input('File size')]
|
40 |
+
|
41 |
+
# -------------------------------------------------------------------------
|
42 |
+
# Installs
|
43 |
+
data['installs'] = [st.number_input('Total installs', min_value=0)]
|
44 |
+
|
45 |
+
# -------------------------------------------------------------------------
|
46 |
+
# Type
|
47 |
+
data['type'] = [st.text_input('Paid / Free')]
|
48 |
+
|
49 |
+
# -------------------------------------------------------------------------
|
50 |
+
# Price
|
51 |
+
data['price'] = [st.number_input('Application price', min_value = 0.00)]
|
52 |
+
|
53 |
+
# -------------------------------------------------------------------------
|
54 |
+
# Content rating
|
55 |
+
data['content_rating'] = [st.text_input('Age rating')]
|
56 |
+
|
57 |
+
# -------------------------------------------------------------------------
|
58 |
+
# Genres
|
59 |
+
data['genres'] = [st.text_input("Genres").split(',')]
|
60 |
+
st.caption("Separate by ',' if multiple genres")
|
61 |
+
# -------------------------------------------------------------------------
|
62 |
+
# Last updated
|
63 |
+
data['last_updated'] = [st.date_input('Last updated')]
|
64 |
+
|
65 |
+
# -------------------------------------------------------------------------
|
66 |
+
# Current version
|
67 |
+
data['current_ver'] = [st.text_input('Current version')]
|
68 |
+
|
69 |
+
# -------------------------------------------------------------------------
|
70 |
+
# Android version
|
71 |
+
data['android_ver'] = [st.text_input('Android version')]
|
72 |
+
|
73 |
+
# -------------------------------------------------------------------------
|
74 |
+
# Review
|
75 |
+
review = st.text_input('Application review (in English)')
|
76 |
+
## Stop words
|
77 |
+
stop_words = set(stopwords.words('english'))
|
78 |
+
## Lemmatizer
|
79 |
+
lemmatizer = WordNetLemmatizer()
|
80 |
+
## Processed text
|
81 |
+
text_processed = text_preprocessing(review, lemmatizer, stop_words)
|
82 |
+
|
83 |
+
data['translated_review'] = [review]
|
84 |
+
data['text_processed'] = [text_processed]
|
85 |
+
|
86 |
+
# -------------------------------------------------------------------------
|
87 |
+
# User data
|
88 |
+
st.dataframe(data.T, width=800, height=565)
|
89 |
+
|
90 |
+
# -------------------------------------------------------------------------
|
91 |
+
# Prediction
|
92 |
+
if st.button('Predict'):
|
93 |
+
model = load_model('lstm')
|
94 |
+
sentiment_pred = model.predict(data['text_processed'])
|
95 |
+
# st.write(sentiment_pred)
|
96 |
+
if sentiment_pred > 1.5:
|
97 |
+
st.write('Positive Review')
|
98 |
+
elif (sentiment_pred < 1.5) & (sentiment_pred >= 1.0):
|
99 |
+
st.write('Negative Review')
|
100 |
+
else:
|
101 |
+
st.write('Neutral Review')
|
requirements.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
nltk
|
2 |
+
tensorflow==2.14.0
|
3 |
+
pandas
|