Update app.py
Browse files
app.py
CHANGED
|
@@ -155,6 +155,7 @@ import streamlit as st
|
|
| 155 |
# Set page config as the first Streamlit command
|
| 156 |
st.set_page_config(layout="wide", page_title="AskReddit Data Explorer")
|
| 157 |
|
|
|
|
| 158 |
import pandas as pd
|
| 159 |
import matplotlib.pyplot as plt
|
| 160 |
from nltk.sentiment.vader import SentimentIntensityAnalyzer
|
|
@@ -164,73 +165,51 @@ import altair as alt
|
|
| 164 |
from datetime import datetime
|
| 165 |
import praw
|
| 166 |
|
| 167 |
-
# Custom CSS
|
| 168 |
st.markdown("""
|
| 169 |
<style>
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
color: #2196F3;
|
| 173 |
-
text-align: center;
|
| 174 |
-
font-size: 3em;
|
| 175 |
-
font-weight: bold;
|
| 176 |
-
padding: 20px 0;
|
| 177 |
-
margin-bottom: 30px;
|
| 178 |
}
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
.plot-container {
|
| 182 |
-
background: white;
|
| 183 |
padding: 20px;
|
| 184 |
border-radius: 10px;
|
| 185 |
-
box-shadow: 0 4px
|
| 186 |
-
margin: 10px;
|
| 187 |
-
border: 2px solid #2196F3;
|
| 188 |
}
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
|
|
|
|
|
|
| 196 |
padding-bottom: 10px;
|
| 197 |
-
border-bottom: 2px solid #E3F2FD;
|
| 198 |
}
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
.stButton > button {
|
| 202 |
-
background-color: #2196F3;
|
| 203 |
color: white;
|
| 204 |
-
border: none;
|
| 205 |
-
border-radius: 5px;
|
| 206 |
-
padding: 10px 20px;
|
| 207 |
-
font-weight: bold;
|
| 208 |
}
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
}
|
| 213 |
-
|
| 214 |
-
/* Selectbox styling */
|
| 215 |
-
.stSelectbox {
|
| 216 |
-
border: 2px solid #2196F3;
|
| 217 |
-
border-radius: 5px;
|
| 218 |
-
}
|
| 219 |
-
|
| 220 |
-
/* Chart border styling */
|
| 221 |
-
.chart-wrapper {
|
| 222 |
-
border: 2px solid #2196F3;
|
| 223 |
border-radius: 5px;
|
| 224 |
-
|
| 225 |
-
margin-
|
| 226 |
}
|
| 227 |
</style>
|
| 228 |
""", unsafe_allow_html=True)
|
| 229 |
|
| 230 |
-
#
|
| 231 |
-
st.
|
| 232 |
|
| 233 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
reddit = praw.Reddit(
|
| 235 |
client_id="dFhdA1_6NWavjmAEt83r2A",
|
| 236 |
client_secret="l4UsnweG9HdHLffJ9ZbAow0-HRocWA",
|
|
@@ -245,6 +224,8 @@ def fetch_askreddit_posts(limit=100):
|
|
| 245 |
posts.append({
|
| 246 |
'title': post.title,
|
| 247 |
'score': post.score,
|
|
|
|
|
|
|
| 248 |
'num_comments': post.num_comments,
|
| 249 |
'num_crossposts': post.num_crossposts,
|
| 250 |
'created_utc': datetime.fromtimestamp(post.created_utc),
|
|
@@ -255,40 +236,34 @@ def fetch_askreddit_posts(limit=100):
|
|
| 255 |
})
|
| 256 |
return pd.DataFrame(posts)
|
| 257 |
|
|
|
|
| 258 |
df = fetch_askreddit_posts()
|
| 259 |
|
| 260 |
-
# Create
|
| 261 |
row1_col1, row1_col2 = st.columns(2)
|
| 262 |
row2_col1, row2_col2 = st.columns(2)
|
| 263 |
|
| 264 |
-
# Plot 1: Time
|
| 265 |
with row1_col1:
|
| 266 |
st.markdown('<div class="plot-container">', unsafe_allow_html=True)
|
| 267 |
-
st.
|
| 268 |
days_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
| 269 |
selected_day = st.selectbox('Select Day of the Week:', days_order)
|
| 270 |
-
|
| 271 |
plot_data = df[df['day_of_week'] == selected_day].groupby('hour').size().reset_index(name='count')
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
color='#2196F3'
|
| 275 |
-
).encode(
|
| 276 |
x=alt.X('hour:Q', title='Hour of Day'),
|
| 277 |
y=alt.Y('count:Q', title='Number of Posts'),
|
| 278 |
tooltip=['hour', 'count']
|
| 279 |
-
).properties(height=300)
|
| 280 |
-
|
| 281 |
-
stroke='#E3F2FD'
|
| 282 |
-
)
|
| 283 |
-
st.markdown('<div class="chart-wrapper">', unsafe_allow_html=True)
|
| 284 |
st.altair_chart(chart, use_container_width=True)
|
| 285 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 286 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
| 287 |
|
| 288 |
-
# Plot 2: Scatter Plot
|
| 289 |
with row1_col2:
|
| 290 |
st.markdown('<div class="plot-container">', unsafe_allow_html=True)
|
| 291 |
-
st.
|
| 292 |
|
| 293 |
col1, col2 = st.columns(2)
|
| 294 |
with col1:
|
|
@@ -298,105 +273,59 @@ with row1_col2:
|
|
| 298 |
|
| 299 |
x_axis = 'num_crossposts' if crossposts_button else 'num_comments'
|
| 300 |
|
| 301 |
-
scatter_chart = alt.Chart(df).mark_circle(
|
| 302 |
-
color='#2196F3'
|
| 303 |
-
).encode(
|
| 304 |
x=alt.X(f'{x_axis}:Q', title=x_axis.replace('_', ' ').title()),
|
| 305 |
y=alt.Y('score:Q', title='Score'),
|
| 306 |
tooltip=['title', x_axis, 'score']
|
| 307 |
-
).properties(
|
| 308 |
-
|
| 309 |
-
|
| 310 |
)
|
| 311 |
|
| 312 |
-
st.markdown('<div class="chart-wrapper">', unsafe_allow_html=True)
|
| 313 |
st.altair_chart(scatter_chart, use_container_width=True)
|
| 314 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 315 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
| 316 |
|
| 317 |
-
# Plot 3: Word Cloud
|
| 318 |
with row2_col1:
|
| 319 |
st.markdown('<div class="plot-container">', unsafe_allow_html=True)
|
| 320 |
-
st.
|
| 321 |
-
|
| 322 |
analyzer = SentimentIntensityAnalyzer()
|
| 323 |
df['sentiment'] = df['title'].apply(lambda x: analyzer.polarity_scores(x)['compound'])
|
| 324 |
df['sentiment_category'] = df['sentiment'].apply(
|
| 325 |
lambda score: 'positive' if score > 0.05 else 'negative' if score < -0.05 else 'neutral'
|
| 326 |
)
|
| 327 |
-
|
| 328 |
selected_sentiment = st.selectbox("Select Sentiment", ['positive', 'negative', 'neutral'])
|
| 329 |
filtered_titles = " ".join(df[df['sentiment_category'] == selected_sentiment]['title'])
|
| 330 |
-
|
| 331 |
-
wordcloud = WordCloud(
|
| 332 |
-
width=800,
|
| 333 |
-
height=400,
|
| 334 |
-
background_color='white',
|
| 335 |
-
colormap='Blues'
|
| 336 |
-
).generate(filtered_titles)
|
| 337 |
-
|
| 338 |
fig, ax = plt.subplots(figsize=(10, 6))
|
| 339 |
ax.imshow(wordcloud, interpolation='bilinear')
|
| 340 |
ax.axis('off')
|
| 341 |
-
ax.set_facecolor('white')
|
| 342 |
-
fig.patch.set_facecolor('white')
|
| 343 |
-
|
| 344 |
-
st.markdown('<div class="chart-wrapper">', unsafe_allow_html=True)
|
| 345 |
st.pyplot(fig)
|
| 346 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 347 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
| 348 |
|
| 349 |
-
# Plot 4: Bubble Chart
|
| 350 |
with row2_col2:
|
| 351 |
st.markdown('<div class="plot-container">', unsafe_allow_html=True)
|
| 352 |
-
st.
|
| 353 |
-
|
| 354 |
bubble_chart = alt.Chart(df).mark_circle().encode(
|
| 355 |
x=alt.X('num_comments:Q', title='Number of Comments'),
|
| 356 |
y=alt.Y('score:Q', title='Score'),
|
| 357 |
-
size=alt.Size('upvote_ratio:Q', scale=alt.Scale(range=[20, 200])),
|
| 358 |
-
color=alt.Color('
|
| 359 |
tooltip=['author', 'num_comments', 'score', 'upvote_ratio']
|
| 360 |
).properties(
|
| 361 |
-
width=
|
| 362 |
-
height=
|
| 363 |
-
).configure_view(
|
| 364 |
-
strokeWidth=2,
|
| 365 |
-
stroke='#E3F2FD'
|
| 366 |
)
|
| 367 |
-
|
| 368 |
-
st.markdown('<div class="chart-wrapper">', unsafe_allow_html=True)
|
| 369 |
-
st.altair_chart(bubble_chart, use_container_width=True)
|
| 370 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
| 371 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 372 |
|
| 373 |
# Display raw data
|
| 374 |
if st.checkbox("Show Raw Data"):
|
| 375 |
st.write(df)
|
| 376 |
|
| 377 |
-
# Summary
|
| 378 |
st.markdown('<div class="plot-container">', unsafe_allow_html=True)
|
| 379 |
-
st.markdown(
|
| 380 |
-
st.markdown(""
|
| 381 |
-
|
| 382 |
-
the number of comments, upvote ratios, and timestamps, providing a comprehensive view of post performance
|
| 383 |
-
and user engagement. The dataset was explored through several interactive visualizations designed to reveal
|
| 384 |
-
key insights effectively.
|
| 385 |
-
|
| 386 |
-
A line graph was used to display the number of posts by the hour for each day of the week, with a dropdown
|
| 387 |
-
menu allowing users to select specific days, making it easy to identify and compare posting patterns such
|
| 388 |
-
as peak activity hours.
|
| 389 |
-
|
| 390 |
-
A scatter plot illustrated the correlation between post scores and other engagement metrics like the number
|
| 391 |
-
of comments and crossposts, with interactive buttons enabling users to switch the x-axis variable and examine
|
| 392 |
-
relationships influencing post success.
|
| 393 |
-
|
| 394 |
-
A word cloud incorporating sentiment analysis enriched the exploration by categorizing post titles as positive,
|
| 395 |
-
negative, or neutral using VADER sentiment analysis. Words were color-coded based on their sentiment, and users
|
| 396 |
-
could toggle between categories and explore different color palettes.
|
| 397 |
-
|
| 398 |
-
Lastly, a bubble chart provided a unique perspective on individual author performance, with bubbles representing
|
| 399 |
-
authors, their size reflecting the upvote ratio, and their position showing the number of comments and post scores,
|
| 400 |
-
offering an engaging way to analyze user contributions.
|
| 401 |
-
""")
|
| 402 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
|
| 155 |
# Set page config as the first Streamlit command
|
| 156 |
st.set_page_config(layout="wide", page_title="AskReddit Data Explorer")
|
| 157 |
|
| 158 |
+
import os
|
| 159 |
import pandas as pd
|
| 160 |
import matplotlib.pyplot as plt
|
| 161 |
from nltk.sentiment.vader import SentimentIntensityAnalyzer
|
|
|
|
| 165 |
from datetime import datetime
|
| 166 |
import praw
|
| 167 |
|
| 168 |
+
# Custom CSS to beautify the app
|
| 169 |
st.markdown("""
|
| 170 |
<style>
|
| 171 |
+
.reportview-container {
|
| 172 |
+
background: #f0f2f6
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
}
|
| 174 |
+
.main {
|
| 175 |
+
background: #ffffff;
|
|
|
|
|
|
|
| 176 |
padding: 20px;
|
| 177 |
border-radius: 10px;
|
| 178 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
|
| 179 |
}
|
| 180 |
+
h1 {
|
| 181 |
+
color: #1E88E5;
|
| 182 |
+
text-align: center;
|
| 183 |
+
padding-bottom: 20px;
|
| 184 |
+
border-bottom: 2px solid #1E88E5;
|
| 185 |
+
}
|
| 186 |
+
h2 {
|
| 187 |
+
color: #333;
|
| 188 |
+
border-bottom: 1px solid #ddd;
|
| 189 |
padding-bottom: 10px;
|
|
|
|
| 190 |
}
|
| 191 |
+
.stButton>button {
|
| 192 |
+
background-color: #1E88E5;
|
|
|
|
|
|
|
| 193 |
color: white;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
}
|
| 195 |
+
.plot-container {
|
| 196 |
+
background: #ffffff;
|
| 197 |
+
padding: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
border-radius: 5px;
|
| 199 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
| 200 |
+
margin-bottom: 20px;
|
| 201 |
}
|
| 202 |
</style>
|
| 203 |
""", unsafe_allow_html=True)
|
| 204 |
|
| 205 |
+
# Streamlit App Title
|
| 206 |
+
st.title("AskReddit Dashboard")
|
| 207 |
|
| 208 |
+
# Download NLTK resources
|
| 209 |
+
nltk.download('vader_lexicon', quiet=True)
|
| 210 |
+
nltk.download('stopwords', quiet=True)
|
| 211 |
+
|
| 212 |
+
# Initialize the Reddit API client
|
| 213 |
reddit = praw.Reddit(
|
| 214 |
client_id="dFhdA1_6NWavjmAEt83r2A",
|
| 215 |
client_secret="l4UsnweG9HdHLffJ9ZbAow0-HRocWA",
|
|
|
|
| 224 |
posts.append({
|
| 225 |
'title': post.title,
|
| 226 |
'score': post.score,
|
| 227 |
+
'id': post.id,
|
| 228 |
+
'url': post.url,
|
| 229 |
'num_comments': post.num_comments,
|
| 230 |
'num_crossposts': post.num_crossposts,
|
| 231 |
'created_utc': datetime.fromtimestamp(post.created_utc),
|
|
|
|
| 236 |
})
|
| 237 |
return pd.DataFrame(posts)
|
| 238 |
|
| 239 |
+
# Fetch data
|
| 240 |
df = fetch_askreddit_posts()
|
| 241 |
|
| 242 |
+
# Create two rows with two columns each
|
| 243 |
row1_col1, row1_col2 = st.columns(2)
|
| 244 |
row2_col1, row2_col2 = st.columns(2)
|
| 245 |
|
| 246 |
+
# Plot 1: Number of Posts vs Time of Day
|
| 247 |
with row1_col1:
|
| 248 |
st.markdown('<div class="plot-container">', unsafe_allow_html=True)
|
| 249 |
+
st.subheader("Number of Posts vs Time of Day")
|
| 250 |
days_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
| 251 |
selected_day = st.selectbox('Select Day of the Week:', days_order)
|
|
|
|
| 252 |
plot_data = df[df['day_of_week'] == selected_day].groupby('hour').size().reset_index(name='count')
|
| 253 |
+
|
| 254 |
+
chart = alt.Chart(plot_data).mark_line(point=True).encode(
|
|
|
|
|
|
|
| 255 |
x=alt.X('hour:Q', title='Hour of Day'),
|
| 256 |
y=alt.Y('count:Q', title='Number of Posts'),
|
| 257 |
tooltip=['hour', 'count']
|
| 258 |
+
).properties(title=f'Posts on {selected_day}', height=300)
|
| 259 |
+
|
|
|
|
|
|
|
|
|
|
| 260 |
st.altair_chart(chart, use_container_width=True)
|
| 261 |
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
|
| 262 |
|
| 263 |
+
# Plot 2: Scatter Plot: Comments vs Score
|
| 264 |
with row1_col2:
|
| 265 |
st.markdown('<div class="plot-container">', unsafe_allow_html=True)
|
| 266 |
+
st.subheader("Scatter Plot of Comments or Crossposts vs Score")
|
| 267 |
|
| 268 |
col1, col2 = st.columns(2)
|
| 269 |
with col1:
|
|
|
|
| 273 |
|
| 274 |
x_axis = 'num_crossposts' if crossposts_button else 'num_comments'
|
| 275 |
|
| 276 |
+
scatter_chart = alt.Chart(df).mark_circle().encode(
|
|
|
|
|
|
|
| 277 |
x=alt.X(f'{x_axis}:Q', title=x_axis.replace('_', ' ').title()),
|
| 278 |
y=alt.Y('score:Q', title='Score'),
|
| 279 |
tooltip=['title', x_axis, 'score']
|
| 280 |
+
).properties(
|
| 281 |
+
height=300,
|
| 282 |
+
title=f'Correlation between {x_axis.replace("_", " ").title()} and Score'
|
| 283 |
)
|
| 284 |
|
|
|
|
| 285 |
st.altair_chart(scatter_chart, use_container_width=True)
|
| 286 |
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
|
| 287 |
|
| 288 |
+
# Plot 3: Word Cloud by Sentiment
|
| 289 |
with row2_col1:
|
| 290 |
st.markdown('<div class="plot-container">', unsafe_allow_html=True)
|
| 291 |
+
st.subheader("Word Cloud by Sentiment")
|
|
|
|
| 292 |
analyzer = SentimentIntensityAnalyzer()
|
| 293 |
df['sentiment'] = df['title'].apply(lambda x: analyzer.polarity_scores(x)['compound'])
|
| 294 |
df['sentiment_category'] = df['sentiment'].apply(
|
| 295 |
lambda score: 'positive' if score > 0.05 else 'negative' if score < -0.05 else 'neutral'
|
| 296 |
)
|
|
|
|
| 297 |
selected_sentiment = st.selectbox("Select Sentiment", ['positive', 'negative', 'neutral'])
|
| 298 |
filtered_titles = " ".join(df[df['sentiment_category'] == selected_sentiment]['title'])
|
| 299 |
+
wordcloud = WordCloud(width=400, height=150, background_color='white').generate(filtered_titles)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
fig, ax = plt.subplots(figsize=(10, 6))
|
| 301 |
ax.imshow(wordcloud, interpolation='bilinear')
|
| 302 |
ax.axis('off')
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
st.pyplot(fig)
|
| 304 |
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
|
| 305 |
|
| 306 |
+
# Plot 4: Author Performance Bubble Chart
|
| 307 |
with row2_col2:
|
| 308 |
st.markdown('<div class="plot-container">', unsafe_allow_html=True)
|
| 309 |
+
st.subheader("Author Performance Bubble Chart")
|
|
|
|
| 310 |
bubble_chart = alt.Chart(df).mark_circle().encode(
|
| 311 |
x=alt.X('num_comments:Q', title='Number of Comments'),
|
| 312 |
y=alt.Y('score:Q', title='Score'),
|
| 313 |
+
size=alt.Size('upvote_ratio:Q', title='Upvote Ratio', scale=alt.Scale(range=[20, 200])),
|
| 314 |
+
color=alt.Color('author:N', legend=None),
|
| 315 |
tooltip=['author', 'num_comments', 'score', 'upvote_ratio']
|
| 316 |
).properties(
|
| 317 |
+
width=600,
|
| 318 |
+
height=350
|
|
|
|
|
|
|
|
|
|
| 319 |
)
|
| 320 |
+
st.altair_chart(bubble_chart, use_container_width=False)
|
|
|
|
|
|
|
|
|
|
| 321 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 322 |
|
| 323 |
# Display raw data
|
| 324 |
if st.checkbox("Show Raw Data"):
|
| 325 |
st.write(df)
|
| 326 |
|
| 327 |
+
# Summary
|
| 328 |
st.markdown('<div class="plot-container">', unsafe_allow_html=True)
|
| 329 |
+
st.markdown("### Project Summary")
|
| 330 |
+
st.markdown("This project used dataset from the r/AskReddit subreddit. It includes various fields such as post scores, the number of comments, upvote ratios, and timestamps, providing a comprehensive view of post performance and user engagement. The dataset was explored through several interactive visualizations designed to reveal key insights effectively. A line graph was used to display the number of posts by the hour for each day of the week, with a dropdown menu allowing users to select specific days, making it easy to identify and compare posting patterns such as peak activity hours. A scatter plot illustrated the correlation between post scores and other engagement metrics like the number of comments and crossposts, with interactive radio buttons enabling users to switch the x-axis variable and examine relationships influencing post success. A word cloud incorporating sentiment analysis enriched the exploration by categorizing post titles as positive, negative, or neutral using VADER sentiment analysis. Words were color-coded based on their sentiment, and users could toggle between categories and explore different color palettes, making the visualization both informative and visually appealing. Lastly, a bubble chart provided a unique perspective on individual author performance, with bubbles representing authors, their size reflecting the upvote ratio, and their position showing the number of comments and post scores, offering an engaging way to analyze user contributions.")
|
| 331 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|