Spaces:
Running
Running
harrychangjr
commited on
Commit
•
64e353a
1
Parent(s):
13bb6d5
clone to hf
Browse files- app.py +69 -0
- pages/content.py +583 -0
- pages/followers.py +141 -0
- pages/overview.py +227 -0
- requirements.txt +15 -0
app.py
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
#from st_pages import Page, show_pages, add_page_title
|
3 |
+
|
4 |
+
### Structure ###
|
5 |
+
# Streamlit file uploader to upload following files:
|
6 |
+
# Engagement - "Last 60 days.csv"
|
7 |
+
# Content - "Trending videos.csv", "Video Posts.csv"
|
8 |
+
# Followers - "Follower activity.csv", "Top territories.csv", "Gender.csv", "Total followers.csv"
|
9 |
+
|
10 |
+
# sidebar with 3 above-mentioned sections + overview + how-to guide
|
11 |
+
|
12 |
+
# for each section, try to replicate the respective dashboards + see if can add additional filters and sliders
|
13 |
+
|
14 |
+
# Optional -- adds the title and icon to the current page
|
15 |
+
# add_page_title()
|
16 |
+
|
17 |
+
# Set page title
|
18 |
+
st.set_page_config(page_title="Tiktok Analytics Dashboard", page_icon = "📊", layout = "centered", initial_sidebar_state = "auto")
|
19 |
+
|
20 |
+
st.header("Tiktok Analytics Dashboard")
|
21 |
+
|
22 |
+
|
23 |
+
|
24 |
+
def social_icons(width=24, height=24, **kwargs):
|
25 |
+
icon_template = '''
|
26 |
+
<a href="{url}" target="_blank" style="margin-right: 20px;">
|
27 |
+
<img src="{icon_src}" alt="{alt_text}" width="{width}" height="{height}">
|
28 |
+
</a>
|
29 |
+
'''
|
30 |
+
|
31 |
+
icons_html = ""
|
32 |
+
for name, url in kwargs.items():
|
33 |
+
icon_src = {
|
34 |
+
"youtube": "https://img.icons8.com/ios-filled/100/null/youtube-play.png",
|
35 |
+
"linkedin": "https://img.icons8.com/ios-filled/100/null/linkedin.png",
|
36 |
+
"github": "https://img.icons8.com/ios-filled/100/null/github--v2.png",
|
37 |
+
"wordpress": "https://img.icons8.com/ios-filled/100/null/wordpress--v1.png",
|
38 |
+
"email": "https://img.icons8.com/ios-filled/100/null/filled-message.png",
|
39 |
+
"website": "https://img.icons8.com/ios/100/null/domain--v1.png"
|
40 |
+
}.get(name.lower())
|
41 |
+
|
42 |
+
if icon_src:
|
43 |
+
icons_html += icon_template.format(url=url, icon_src=icon_src, alt_text=name.capitalize(), width=width, height=height)
|
44 |
+
|
45 |
+
return icons_html
|
46 |
+
st.subheader("So how do I use this?")
|
47 |
+
st.markdown("""
|
48 |
+
Designed to provide extra value to the existing dashboard available on TikTok Analytics, individual users or businesses can download the relevant csv/xlsx files before loading them
|
49 |
+
to this custom dashboard.
|
50 |
+
|
51 |
+
|Section|Description|Files required|
|
52 |
+
|--------|-----------|--------------|
|
53 |
+
|Overview| Number cards for video views, profile views, likes, comments and shares over past 60 days, with various plots for up to 3 variables at once | `Last 60 days` |
|
54 |
+
|Content| Analysis of trending videos over past 7 days, including investigating relationships between number of hashtags used and video views| `Trending videos` |
|
55 |
+
|Followers| Broad insights of follower activity by hour, as well as breakdown of demographics by country and gender| `Follower activity` `Top territories` `Gender` `Total followers` |
|
56 |
+
|
57 |
+
|
58 |
+
Dashboard will be updated as and whenever necessary. Enjoy!
|
59 |
+
""")
|
60 |
+
st.markdown("""""")
|
61 |
+
linkedin_url = "https://www.linkedin.com/in/harrychangjr/"
|
62 |
+
github_url = "https://github.com/harrychangjr"
|
63 |
+
email_url = "mailto:harrychang.work@gmail.com"
|
64 |
+
website_url = "https://harrychangjr.streamlit.app"
|
65 |
+
st.markdown(
|
66 |
+
social_icons(32, 32, LinkedIn=linkedin_url, GitHub=github_url, Email=email_url, Website=website_url),
|
67 |
+
unsafe_allow_html=True)
|
68 |
+
st.markdown("")
|
69 |
+
st.markdown("*Copyright © 2023 Harry Chang*")
|
pages/content.py
ADDED
@@ -0,0 +1,583 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
import numpy as np
|
4 |
+
import datetime
|
5 |
+
import plotly.express as px
|
6 |
+
import plotly.graph_objects as go
|
7 |
+
import statsmodels.api as sm
|
8 |
+
from millify import millify
|
9 |
+
from sklearn.linear_model import LinearRegression
|
10 |
+
from sklearn.metrics import r2_score
|
11 |
+
from st_aggrid import AgGrid
|
12 |
+
import io
|
13 |
+
import re
|
14 |
+
import emoji
|
15 |
+
from collections import Counter
|
16 |
+
import openpyxl
|
17 |
+
from gensim.models import Word2Vec
|
18 |
+
from sklearn.metrics.pairwise import cosine_similarity
|
19 |
+
from sklearn.linear_model import LinearRegression
|
20 |
+
from sklearn.ensemble import RandomForestRegressor
|
21 |
+
from xgboost import XGBRegressor
|
22 |
+
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
|
23 |
+
from sklearn.model_selection import train_test_split
|
24 |
+
import seaborn as sns
|
25 |
+
import matplotlib.pyplot as plt
|
26 |
+
from mpl_toolkits.mplot3d import Axes3D
|
27 |
+
|
28 |
+
#from st_pages import Page, show_pages, add_page_title
|
29 |
+
|
30 |
+
# Set page title
|
31 |
+
st.set_page_config(page_title="Content - Tiktok Analytics Dashboard", page_icon = "📊", layout = "centered", initial_sidebar_state = "auto")
|
32 |
+
|
33 |
+
st.header("Content")
|
34 |
+
st.markdown("""Upload your files here to load your data!
|
35 |
+
|
36 |
+
*'Trending videos' (xlsx or csv format)*
|
37 |
+
""")
|
38 |
+
|
39 |
+
def plot_chart(data, chart_type, x_var, y_var, z_var=None, show_regression_line=False, show_r_squared=False):
|
40 |
+
scatter_marker_color = 'green'
|
41 |
+
regression_line_color = 'red'
|
42 |
+
if chart_type == "line":
|
43 |
+
fig = px.line(data, x=x_var, y=y_var)
|
44 |
+
|
45 |
+
elif chart_type == "bar":
|
46 |
+
fig = px.bar(data, x=x_var, y=y_var)
|
47 |
+
|
48 |
+
elif chart_type == "scatter":
|
49 |
+
fig = px.scatter(data, x=x_var, y=y_var, color_discrete_sequence=[scatter_marker_color])
|
50 |
+
|
51 |
+
if show_regression_line:
|
52 |
+
X = data[x_var].values.reshape(-1, 1)
|
53 |
+
y = data[y_var].values.reshape(-1, 1)
|
54 |
+
model = LinearRegression().fit(X, y)
|
55 |
+
y_pred = model.predict(X)
|
56 |
+
r_squared = r2_score(y, y_pred) # Calculate R-squared value
|
57 |
+
|
58 |
+
fig.add_trace(
|
59 |
+
go.Scatter(x=data[x_var], y=y_pred[:, 0], mode='lines', name='Regression Line', line=dict(color=regression_line_color))
|
60 |
+
)
|
61 |
+
|
62 |
+
# Add R-squared value as a text annotation
|
63 |
+
fig.add_annotation(
|
64 |
+
x=data[x_var].max(),
|
65 |
+
y=y_pred[-1, 0],
|
66 |
+
text=f"R-squared: {r_squared:.4f}",
|
67 |
+
showarrow=False,
|
68 |
+
font=dict(size=14),
|
69 |
+
bgcolor='rgba(255, 255, 255, 0.8)',
|
70 |
+
bordercolor='black',
|
71 |
+
borderwidth=1,
|
72 |
+
borderpad=4
|
73 |
+
)
|
74 |
+
|
75 |
+
elif chart_type == "heatmap":
|
76 |
+
fig = px.imshow(data, color_continuous_scale='Inferno')
|
77 |
+
|
78 |
+
elif chart_type == "scatter_3d":
|
79 |
+
if z_var is not None:
|
80 |
+
fig = px.scatter_3d(data, x=x_var, y=y_var, z=z_var, color=data.columns[0])
|
81 |
+
else:
|
82 |
+
st.warning("Please select Z variable for 3D line plot.")
|
83 |
+
return
|
84 |
+
|
85 |
+
elif chart_type == "line_3d":
|
86 |
+
if z_var is not None:
|
87 |
+
fig = go.Figure(data=[go.Scatter3d(x=data[x_var], y=data[y_var], z=data[z_var], mode='lines')])
|
88 |
+
fig.update_layout(scene=dict(xaxis_title=x_var, yaxis_title=y_var, zaxis_title=z_var)) # Set the axis name
|
89 |
+
else:
|
90 |
+
st.warning("Please select Z variable for 3D line plot.")
|
91 |
+
return
|
92 |
+
|
93 |
+
elif chart_type == "surface_3d":
|
94 |
+
if z_var is not None:
|
95 |
+
fig = go.Figure(data=[go.Surface(z=data.values)])
|
96 |
+
fig.update_layout(scene=dict(xaxis_title=x_var, yaxis_title=y_var, zaxis_title=z_var)) # Set the axis name
|
97 |
+
else:
|
98 |
+
st.warning("Please select Z variable for 3D line plot.")
|
99 |
+
return
|
100 |
+
|
101 |
+
elif chart_type == "radar":
|
102 |
+
fig = go.Figure()
|
103 |
+
for col in data.columns[1:]:
|
104 |
+
fig.add_trace(go.Scatterpolar(r=data[col], theta=data[x_var], mode='lines', name=col))
|
105 |
+
fig.update_layout(polar=dict(radialaxis=dict(visible=True, range=[data[data.columns[1:]].min().min(), data[data.columns[1:]].max().max()])))
|
106 |
+
|
107 |
+
st.plotly_chart(fig)
|
108 |
+
|
109 |
+
def plot_radar_chart(data, columns):
|
110 |
+
df = data[columns]
|
111 |
+
fig = go.Figure()
|
112 |
+
|
113 |
+
for i in range(len(df)):
|
114 |
+
date_label = data.loc[i, 'Date']
|
115 |
+
fig.add_trace(go.Scatterpolar(
|
116 |
+
r=df.loc[i].values,
|
117 |
+
theta=df.columns,
|
118 |
+
fill='toself',
|
119 |
+
name=date_label
|
120 |
+
))
|
121 |
+
|
122 |
+
fig.update_layout(
|
123 |
+
polar=dict(
|
124 |
+
radialaxis=dict(
|
125 |
+
visible=True,
|
126 |
+
range=[0, df.max().max()]
|
127 |
+
)
|
128 |
+
),
|
129 |
+
showlegend=True
|
130 |
+
)
|
131 |
+
|
132 |
+
st.plotly_chart(fig)
|
133 |
+
|
134 |
+
uploaded_files = st.file_uploader(
|
135 |
+
"Choose CSV or Excel files to upload",
|
136 |
+
accept_multiple_files=True,
|
137 |
+
type=['csv', 'xlsx'])
|
138 |
+
|
139 |
+
if uploaded_files:
|
140 |
+
data_list = []
|
141 |
+
|
142 |
+
# read the file
|
143 |
+
with st.expander("View uploaded data - data covered over 7-day period only"):
|
144 |
+
for uploaded_file in uploaded_files:
|
145 |
+
st.write("▾ Filename:", uploaded_file.name)
|
146 |
+
bytes_data = uploaded_file.read()
|
147 |
+
data = None
|
148 |
+
if uploaded_file.type == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
149 |
+
data = pd.read_excel(io.BytesIO(bytes_data))
|
150 |
+
AgGrid(data)
|
151 |
+
else:
|
152 |
+
data = pd.read_csv(io.StringIO(bytes_data.decode('utf-8')))
|
153 |
+
AgGrid(data)
|
154 |
+
|
155 |
+
data_list.append(data)
|
156 |
+
#st.write(data_list)
|
157 |
+
|
158 |
+
#tab1, tab2 = st.tabs(["Trending Videos", "Video Posts"])
|
159 |
+
for data in data_list:
|
160 |
+
#st.write(data.columns)
|
161 |
+
#st.write(data)
|
162 |
+
#with tab1:
|
163 |
+
if 'video_view_within_days' in data.columns: #Trending Videos
|
164 |
+
# Extract hashtags using a regex pattern
|
165 |
+
def extract_hashtags(title):
|
166 |
+
return re.findall(r'#\w+', title)
|
167 |
+
|
168 |
+
# Extract emojis using the emoji library
|
169 |
+
#def extract_emojis(title):
|
170 |
+
#return [c for c in title if c in emoji.emojize(c)]
|
171 |
+
|
172 |
+
# Remove emojis and hashtags from the title
|
173 |
+
def clean_title(title):
|
174 |
+
#title_without_emojis = emoji.get_emoji_regexp().sub(u'', title)
|
175 |
+
title_without_hashtags = re.sub(r'#\w+', '', title)
|
176 |
+
return title_without_hashtags.strip()
|
177 |
+
|
178 |
+
data['Hashtags'] = data['Video title'].apply(extract_hashtags)
|
179 |
+
#data['Emojis'] = data['Video title'].apply(extract_emojis)
|
180 |
+
data['Cleaned_title'] = data['Video title'].apply(clean_title)
|
181 |
+
# Add a new column to store the hashtag count
|
182 |
+
data['Hashtag_count'] = data['Hashtags'].apply(len)
|
183 |
+
st.write(data)
|
184 |
+
options = ["Total views", "Total shares", "Total likes", "Total comments", "Number of Hashtags", "Hashtag Performance"]
|
185 |
+
selected_feature = st.selectbox(label="Select feature", options=options, index=0)
|
186 |
+
if selected_feature == "Total views":
|
187 |
+
data = data.sort_values(by='Total views', ascending=True)
|
188 |
+
fig = px.bar(data, x='Total views', y='Cleaned_title', title='Views of trending videos for the week',
|
189 |
+
color_discrete_sequence=px.colors.qualitative.Alphabet, hover_data={'Total views': ':.2f'})
|
190 |
+
st.plotly_chart(fig)
|
191 |
+
elif selected_feature == "Total shares":
|
192 |
+
data = data.sort_values(by='Total shares\xa0', ascending=True)
|
193 |
+
fig = px.bar(data, x='Total shares\xa0', y='Cleaned_title', title='Shares of trending videos for the week',
|
194 |
+
color_discrete_sequence=px.colors.qualitative.Set1, hover_data={'Total shares\xa0': ':.2f'})
|
195 |
+
st.plotly_chart(fig)
|
196 |
+
elif selected_feature == "Total likes":
|
197 |
+
data = data.sort_values(by='Total likes', ascending=True)
|
198 |
+
fig = px.bar(data, x='Total likes', y='Cleaned_title', title='Likes of trending videos for the week',
|
199 |
+
color_discrete_sequence=px.colors.qualitative.Antique, hover_data={'Total likes': ':.2f'})
|
200 |
+
st.plotly_chart(fig)
|
201 |
+
elif selected_feature == "Total comments":
|
202 |
+
data = data.sort_values(by='Total comments', ascending=True)
|
203 |
+
fig = px.bar(data, x='Total comments', y='Cleaned_title', title='Comments of trending videos for the week',
|
204 |
+
color_discrete_sequence=px.colors.qualitative.Vivid, hover_data={'Total comments': ':.2f'})
|
205 |
+
st.plotly_chart(fig)
|
206 |
+
elif selected_feature == "Number of Hashtags":
|
207 |
+
# Count the occurrences of each hashtag
|
208 |
+
hashtag_counts = Counter(hashtag for hashtags in data['Hashtags'] for hashtag in hashtags)
|
209 |
+
|
210 |
+
# Get the top N most common hashtags
|
211 |
+
N = 10
|
212 |
+
top_hashtags = hashtag_counts.most_common(N)
|
213 |
+
|
214 |
+
# Display the top hashtags
|
215 |
+
print(f"Top {N} hashtags:")
|
216 |
+
for hashtag, count in top_hashtags:
|
217 |
+
print(f"{hashtag}: {count}")
|
218 |
+
|
219 |
+
# Visualize the results with a Plotly bar chart
|
220 |
+
fig = go.Figure(go.Bar(
|
221 |
+
x=[t[0] for t in top_hashtags],
|
222 |
+
y=[t[1] for t in top_hashtags],
|
223 |
+
text=[t[1] for t in top_hashtags],
|
224 |
+
textposition='auto',
|
225 |
+
marker_color='rgba(58, 71, 80, 0.6)',
|
226 |
+
opacity=0.8
|
227 |
+
))
|
228 |
+
|
229 |
+
fig.update_layout(
|
230 |
+
title=f'Top {N} Hashtags',
|
231 |
+
xaxis_title='Hashtags',
|
232 |
+
yaxis_title='Count',
|
233 |
+
xaxis_tickangle=-45
|
234 |
+
)
|
235 |
+
|
236 |
+
st.plotly_chart(fig)
|
237 |
+
|
238 |
+
tab1, tab2, tab3, tab4 = st.tabs(["vs Views", "vs Shares", "vs Likes", "vs Comments"])
|
239 |
+
with tab1:
|
240 |
+
fig = px.scatter(data, x='Hashtag_count', y='Total views', hover_data=['Cleaned_title'])
|
241 |
+
fig.update_layout(
|
242 |
+
title='Hashtag Count vs. Views',
|
243 |
+
xaxis_title='Hashtag Count',
|
244 |
+
yaxis_title='Views'
|
245 |
+
)
|
246 |
+
st.plotly_chart(fig)
|
247 |
+
with tab2:
|
248 |
+
fig = px.scatter(data, x='Hashtag_count', y='Total shares\xa0', hover_data=['Cleaned_title'])
|
249 |
+
fig.update_layout(
|
250 |
+
title='Hashtag Count vs. Shares',
|
251 |
+
xaxis_title='Hashtag Count',
|
252 |
+
yaxis_title='Shares'
|
253 |
+
)
|
254 |
+
st.plotly_chart(fig)
|
255 |
+
with tab3:
|
256 |
+
fig = px.scatter(data, x='Hashtag_count', y='Total likes', hover_data=['Cleaned_title'])
|
257 |
+
fig.update_layout(
|
258 |
+
title='Hashtag Count vs. Likes',
|
259 |
+
xaxis_title='Hashtag Count',
|
260 |
+
yaxis_title='Likes'
|
261 |
+
)
|
262 |
+
st.plotly_chart(fig)
|
263 |
+
with tab4:
|
264 |
+
fig = px.scatter(data, x='Hashtag_count', y='Total comments', hover_data=['Cleaned_title'])
|
265 |
+
fig.update_layout(
|
266 |
+
title='Hashtag Count vs. Comments',
|
267 |
+
xaxis_title='Hashtag Count',
|
268 |
+
yaxis_title='Comments'
|
269 |
+
)
|
270 |
+
st.plotly_chart(fig)
|
271 |
+
elif selected_feature == "Hashtag Performance":
|
272 |
+
# Tokenize hashtags and create a list of unique hashtags
|
273 |
+
tokenized_hashtags = data["Hashtags"].tolist()
|
274 |
+
unique_hashtags = list(set([tag for tags in tokenized_hashtags for tag in tags]))
|
275 |
+
# Train a word2vec model
|
276 |
+
model = Word2Vec(tokenized_hashtags, size=50, window=5, min_count=1, workers=4)
|
277 |
+
|
278 |
+
# Create a hashtag vector dictionary
|
279 |
+
hashtag_vectors = {tag: model.wv[tag] for tag in unique_hashtags}
|
280 |
+
st.subheader("Explaining the concept of hashtag performance scores and cosine similarity scores")
|
281 |
+
st.markdown(
|
282 |
+
"""
|
283 |
+
So how are the **performance scores** calculated for each feature?
|
284 |
+
|
285 |
+
In each line, the code goes through each unique hashtag and selects all videos that use that hashtag. It then calculates the mean of the respective performance metric (views, shares, likes, or comments) for those videos.
|
286 |
+
|
287 |
+
This gives an average performance score for each hashtag, which can be used as an indication of how well videos with that hashtag tend to perform on average. However, this is a simplistic metric and there may be other factors influencing the performance of a video. It's also worth noting that the mean is sensitive to extreme values, so a few very popular or unpopular videos could skew the average performance for a given hashtag.
|
288 |
+
""")
|
289 |
+
|
290 |
+
st.markdown("""
|
291 |
+
How about **cosine similarity**?
|
292 |
+
|
293 |
+
Cosine similarity is a metric used to measure how similar two vectors are, irrespective of their size. In the context of Natural Language Processing (NLP), and in this case, it's used to measure the semantic similarity between two hashtags based on their embeddings (vectors) generated by the Word2Vec model.
|
294 |
+
|
295 |
+
In simple terms, it measures the cosine of the angle between two vectors. If the vectors are identical, the angle is 0, so the cosine is 1, indicating perfect similarity. If the vectors are orthogonal (i.e., the angle between them is 90 degrees), they're considered not similar, and the cosine similarity is 0. If the vectors point in opposite directions (i.e., the angle is 180 degrees), the cosine similarity is -1, indicating that they're diametrically dissimilar.
|
296 |
+
""")
|
297 |
+
# Calculate the average performance of each hashtag - views
|
298 |
+
hashtag_performance_views = {tag: data[data["Hashtags"].apply(lambda x: tag in x)]["Total views"].mean() for tag in unique_hashtags}
|
299 |
+
|
300 |
+
# Calculate the average performance of each hashtag - shares
|
301 |
+
hashtag_performance_shares = {tag: data[data["Hashtags"].apply(lambda x: tag in x)]["Total shares\xa0"].mean() for tag in unique_hashtags}
|
302 |
+
|
303 |
+
# Calculate the average performance of each hashtag - likes
|
304 |
+
hashtag_performance_likes = {tag: data[data["Hashtags"].apply(lambda x: tag in x)]["Total likes"].mean() for tag in unique_hashtags}
|
305 |
+
|
306 |
+
# Calculate the average performance of each hashtag - comments
|
307 |
+
hashtag_performance_comments = {tag: data[data["Hashtags"].apply(lambda x: tag in x)]["Total comments"].mean() for tag in unique_hashtags}
|
308 |
+
|
309 |
+
# Calculate the similarity between hashtags
|
310 |
+
similarity_matrix = cosine_similarity(list(hashtag_vectors.values()))
|
311 |
+
|
312 |
+
# Convert the similarity matrix into a DataFrame
|
313 |
+
similarity_df = pd.DataFrame(similarity_matrix, index=unique_hashtags, columns=unique_hashtags)
|
314 |
+
|
315 |
+
# Convert the performance dictionaries into DataFrames
|
316 |
+
perf_views_df = pd.DataFrame(list(hashtag_performance_views.items()), columns=["hashtag", "views"])
|
317 |
+
perf_shares_df = pd.DataFrame(list(hashtag_performance_shares.items()), columns=["hashtag", "shares"])
|
318 |
+
perf_likes_df = pd.DataFrame(list(hashtag_performance_likes.items()), columns=["hashtag", "likes"])
|
319 |
+
perf_comments_df = pd.DataFrame(list(hashtag_performance_comments.items()), columns=["hashtag", "comments"])
|
320 |
+
|
321 |
+
# Merge the performance DataFrames into a single DataFrame
|
322 |
+
perf_df = pd.merge(perf_views_df, perf_shares_df, on="hashtag")
|
323 |
+
perf_df = pd.merge(perf_df, perf_likes_df, on="hashtag")
|
324 |
+
perf_df = pd.merge(perf_df, perf_comments_df, on="hashtag")
|
325 |
+
|
326 |
+
# Convert the similarity matrix into a 1D series
|
327 |
+
similarity_series = similarity_df.unstack()
|
328 |
+
|
329 |
+
# Rename the series index
|
330 |
+
similarity_series.index.rename(["hashtag1", "hashtag2"], inplace=True)
|
331 |
+
|
332 |
+
# Convert the series into a DataFrame
|
333 |
+
similarity_df = similarity_series.to_frame("similarity").reset_index()
|
334 |
+
|
335 |
+
# Merge the similarity DataFrame with the performance DataFrame
|
336 |
+
merged_df = pd.merge(similarity_df, perf_df, left_on="hashtag1", right_on="hashtag")
|
337 |
+
|
338 |
+
# Calculate the correlation between hashtag similarity and performance
|
339 |
+
correlation = merged_df[["similarity", "views", "shares", "likes", "comments"]].corr()
|
340 |
+
st.subheader("Correlation matrix between hashtag cosine similarity values and performance values")
|
341 |
+
#st.write(correlation)
|
342 |
+
# Create a heatmap
|
343 |
+
plt.figure(figsize=(10, 8))
|
344 |
+
sns.heatmap(correlation, annot=True, fmt=".2f", cmap='coolwarm', cbar=True)
|
345 |
+
|
346 |
+
# Show the plot in Streamlit
|
347 |
+
st.pyplot(plt)
|
348 |
+
# Rename the 'value' columns to make them unique
|
349 |
+
df1 = pd.DataFrame(list(hashtag_performance_views.items()), columns=["hashtag", "value"])
|
350 |
+
df2 = pd.DataFrame(list(hashtag_performance_shares.items()), columns=["hashtag", "value"])
|
351 |
+
df3 = pd.DataFrame(list(hashtag_performance_likes.items()), columns=["hashtag", "value"])
|
352 |
+
df4 = pd.DataFrame(list(hashtag_performance_comments.items()), columns=["hashtag", "value"])
|
353 |
+
|
354 |
+
df1.rename(columns={'value': 'value_views'}, inplace=True)
|
355 |
+
df2.rename(columns={'value': 'value_shares'}, inplace=True)
|
356 |
+
df3.rename(columns={'value': 'value_likes'}, inplace=True)
|
357 |
+
df4.rename(columns={'value': 'value_comments'}, inplace=True)
|
358 |
+
|
359 |
+
# Merge the DataFrames on the 'hashtag' column
|
360 |
+
merged_df = df1.merge(df2, on='hashtag').merge(df3, on='hashtag').merge(df4, on='hashtag')
|
361 |
+
|
362 |
+
st.subheader("Hashtag Performance Scores based on Views, Shares, Likes and Comments - Calculated using average of all videos' metrics containing a particular hashtag")
|
363 |
+
st.write(merged_df)
|
364 |
+
|
365 |
+
# Create a pair plot with regression lines
|
366 |
+
st.write("**Pair Plots**")
|
367 |
+
sns.pairplot(merged_df, kind="reg", diag_kind="kde")
|
368 |
+
st.pyplot(plt)
|
369 |
+
|
370 |
+
# Split the data into train and test sets
|
371 |
+
X = merged_df[["value_shares", "value_likes", "value_comments"]]
|
372 |
+
y = merged_df["value_views"]
|
373 |
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
374 |
+
|
375 |
+
#st.write("**3D Scatterplot of Predictors with Regression Plane**")
|
376 |
+
# Create a 3D scatterplot with a regression plane
|
377 |
+
#fig = plt.figure()
|
378 |
+
#ax = fig.add_subplot(111, projection="3d")
|
379 |
+
|
380 |
+
#ax.scatter(merged_df["value_shares"], merged_df["value_likes"], merged_df["value_comments"], c="blue", marker="o")
|
381 |
+
|
382 |
+
# Create a meshgrid for the regression plane
|
383 |
+
#x_range = np.linspace(merged_df["value_shares"].min(), merged_df["value_shares"].max(), num=10)
|
384 |
+
#y_range = np.linspace(merged_df["value_likes"].min(), merged_df["value_likes"].max(), num=10)
|
385 |
+
|
386 |
+
#x_grid, y_grid = np.meshgrid(x_range, y_range)
|
387 |
+
|
388 |
+
# Calculate the regression plane
|
389 |
+
#X_grid = np.column_stack((x_grid.ravel(), y_grid.ravel()))
|
390 |
+
#plane = np.zeros(X_grid.shape[0])
|
391 |
+
|
392 |
+
#for i, (x, y) in enumerate(X_grid):
|
393 |
+
#plane[i] = model.predict(np.column_stack((x, y, merged_df["value_comments"].mean())).reshape(1, -1))
|
394 |
+
|
395 |
+
#z_grid = plane.reshape(x_grid.shape)
|
396 |
+
|
397 |
+
#ax.set_xlabel("value_shares")
|
398 |
+
#ax.set_ylabel("value_likes")
|
399 |
+
#ax.set_zlabel("value_comments")
|
400 |
+
|
401 |
+
#plt.show()
|
402 |
+
#st.pyplot(plt)
|
403 |
+
|
404 |
+
# Fit the models on the training data
|
405 |
+
lr_model = LinearRegression()
|
406 |
+
lr_model.fit(X_train, y_train)
|
407 |
+
|
408 |
+
rf_model = RandomForestRegressor()
|
409 |
+
rf_model.fit(X_train, y_train)
|
410 |
+
|
411 |
+
xgb_model = XGBRegressor()
|
412 |
+
xgb_model.fit(X_train, y_train)
|
413 |
+
|
414 |
+
# Make predictions on the testing data using the trained models
|
415 |
+
lr_pred = lr_model.predict(X_test)
|
416 |
+
rf_pred = rf_model.predict(X_test)
|
417 |
+
xgb_pred = xgb_model.predict(X_test)
|
418 |
+
|
419 |
+
# Calculate the evaluation metrics for each model
|
420 |
+
st.write("**Training regression models to predict value_views using value_shares, value_likes and value_comments**")
|
421 |
+
models = ["Linear Regression", "Random Forest", "XGBoost"]
|
422 |
+
predictions = [lr_pred, rf_pred, xgb_pred]
|
423 |
+
|
424 |
+
# Initialize a list to hold the model metrics
|
425 |
+
model_metrics = []
|
426 |
+
|
427 |
+
for model, pred in zip(models, predictions):
|
428 |
+
mse = mean_squared_error(y_test, pred)
|
429 |
+
mae = mean_absolute_error(y_test, pred)
|
430 |
+
r2 = r2_score(y_test, pred)
|
431 |
+
|
432 |
+
# Append a dictionary of the metrics to the list
|
433 |
+
model_metrics.append({"Model": model, "Mean Squared Error": mse, "Mean Absolute Error": mae, "R^2 Score": r2})
|
434 |
+
|
435 |
+
# Convert the list of dictionaries into a DataFrame
|
436 |
+
metrics_df = pd.DataFrame(model_metrics)
|
437 |
+
|
438 |
+
# Display the DataFrame in Streamlit
|
439 |
+
st.write(metrics_df)
|
440 |
+
|
441 |
+
|
442 |
+
x_var = st.sidebar.selectbox("Select X variable", merged_df.columns)
|
443 |
+
y_var = st.sidebar.selectbox("Select Y variable", merged_df.columns)
|
444 |
+
show_regression_line = False
|
445 |
+
|
446 |
+
z_var_options = ["None"] + list(merged_df.columns)
|
447 |
+
z_var = st.sidebar.selectbox("Select Z variable for 3D charts (if applicable)", z_var_options)
|
448 |
+
|
449 |
+
# Allow user to select time frequency for resampling
|
450 |
+
#time_frequency = st.sidebar.selectbox("Select time frequency", ["Day", "Week", "Month"])
|
451 |
+
|
452 |
+
#if time_frequency == "Week":
|
453 |
+
#data_resampled = data.resample('W').sum()
|
454 |
+
#elif time_frequency == "Month":
|
455 |
+
#data_resampled = data.resample('M').sum()
|
456 |
+
#else:
|
457 |
+
#data_resampled = data
|
458 |
+
st.subheader("Various plots to represent performance scores for views, shares, likes and comments")
|
459 |
+
tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs(["Line", "Bar", "Scatterplot", "Heatmap",
|
460 |
+
"3D Scatterplot", "3D Lineplot", "3D Surfaceplot"])
|
461 |
+
with tab1:
|
462 |
+
st.write("Lineplot")
|
463 |
+
plot_chart(merged_df, "line", x_var, y_var)
|
464 |
+
|
465 |
+
with tab2:
|
466 |
+
st.write("Barplot")
|
467 |
+
plot_chart(merged_df, "bar", x_var, y_var)
|
468 |
+
|
469 |
+
with tab3:
|
470 |
+
st.write("Scatterplot")
|
471 |
+
show_regression_line = st.checkbox("Show regression line")
|
472 |
+
plot_chart(merged_df, "scatter", x_var, y_var, show_regression_line=show_regression_line)
|
473 |
+
|
474 |
+
with tab4:
|
475 |
+
st.write("Heatmap")
|
476 |
+
plot_chart(merged_df, "heatmap", x_var, y_var)
|
477 |
+
|
478 |
+
with tab5:
|
479 |
+
st.write("3D Scatterplot")
|
480 |
+
if z_var != "None":
|
481 |
+
plot_chart(merged_df, "scatter_3d", x_var, y_var, z_var)
|
482 |
+
|
483 |
+
with tab6:
|
484 |
+
st.write("3D Lineplot")
|
485 |
+
if z_var != "None":
|
486 |
+
plot_chart(merged_df, "line_3d", x_var, y_var, z_var)
|
487 |
+
|
488 |
+
with tab7:
|
489 |
+
st.write("3D Surfaceplot")
|
490 |
+
if z_var != "None":
|
491 |
+
plot_chart(merged_df, "surface_3d", x_var, y_var, z_var)
|
492 |
+
|
493 |
+
#with tab8:
|
494 |
+
#st.write("Radar chart for 'Last 60 days'")
|
495 |
+
#radar_columns = ['value_views', 'value_shares', 'value_likes', 'value_comments']
|
496 |
+
#plot_radar_chart(data, radar_columns)
|
497 |
+
|
498 |
+
tab1, tab2, tab3, tab4 = st.tabs(["vs Views", "vs Shares", "vs Likes", "vs Comments"])
|
499 |
+
with tab1:
|
500 |
+
st.subheader("Hashtag Performance - Views:")
|
501 |
+
#for tag, perf in hashtag_performance.items():
|
502 |
+
#st.write(f"{tag}: {perf}")
|
503 |
+
#AgGrid(hashtag_performance_views)
|
504 |
+
#df1 = pd.DataFrame(list(hashtag_performance_views.items()), columns=["hashtag", "value"])
|
505 |
+
# Sort the DataFrame by the 'value' column in descending order
|
506 |
+
sorted_df1 = df1.sort_values(by="value_views", ascending=False)
|
507 |
+
st.write(sorted_df1)
|
508 |
+
# Highlight specific bars (use 'rgba' values for transparency)
|
509 |
+
highlighted_bars = ['#fyp', '#tiktok', '#foryou', '#trending', '#viral']
|
510 |
+
sorted_df1['color'] = sorted_df1['hashtag'].apply(lambda x: 'black' if x in highlighted_bars else 'red')
|
511 |
+
fig = px.bar(sorted_df1, x='hashtag', y='value_views', title='Hashtag performance for the week',
|
512 |
+
color='color', color_discrete_map='identity', hover_data={'value_views': ':.2f'})
|
513 |
+
|
514 |
+
fig.update_layout(title='Hashtag performance for the week - Views', xaxis_title='Hashtag', yaxis_title='Value')
|
515 |
+
st.plotly_chart(fig)
|
516 |
+
|
517 |
+
with tab2:
|
518 |
+
st.subheader("Hashtag Performance - Shares:")
|
519 |
+
#for tag, perf in hashtag_performance.items():
|
520 |
+
#st.write(f"{tag}: {perf}")
|
521 |
+
#AgGrid(hashtag_performance_views)
|
522 |
+
#df2 = pd.DataFrame(list(hashtag_performance_shares.items()), columns=["hashtag", "value"])
|
523 |
+
# Sort the DataFrame by the 'value' column in descending order
|
524 |
+
sorted_df2 = df2.sort_values(by="value_shares", ascending=False)
|
525 |
+
st.write(sorted_df2)
|
526 |
+
# Highlight specific bars (use 'rgba' values for transparency)
|
527 |
+
highlighted_bars = ['#fyp', '#tiktok', '#foryou', '#trending', '#viral']
|
528 |
+
sorted_df2['color'] = sorted_df2['hashtag'].apply(lambda x: 'black' if x in highlighted_bars else 'blue')
|
529 |
+
fig = px.bar(sorted_df2, x='hashtag', y='value_shares', title='Hashtag performance for the week',
|
530 |
+
color='color', color_discrete_map='identity', hover_data={'value_shares': ':.2f'})
|
531 |
+
|
532 |
+
fig.update_layout(title='Hashtag performance for the week - Shares', xaxis_title='Hashtag', yaxis_title='Value')
|
533 |
+
st.plotly_chart(fig)
|
534 |
+
with tab3:
|
535 |
+
st.subheader("Hashtag Performance - Likes:")
|
536 |
+
#for tag, perf in hashtag_performance.items():
|
537 |
+
#st.write(f"{tag}: {perf}")
|
538 |
+
#AgGrid(hashtag_performance_views)
|
539 |
+
#df3 = pd.DataFrame(list(hashtag_performance_likes.items()), columns=["hashtag", "value"])
|
540 |
+
# Sort the DataFrame by the 'value' column in descending order
|
541 |
+
sorted_df3 = df3.sort_values(by="value_likes", ascending=False)
|
542 |
+
st.write(sorted_df3)
|
543 |
+
# Highlight specific bars (use 'rgba' values for transparency)
|
544 |
+
highlighted_bars = ['#fyp', '#tiktok', '#foryou', '#trending', '#viral']
|
545 |
+
sorted_df3['color'] = sorted_df3['hashtag'].apply(lambda x: 'black' if x in highlighted_bars else 'green')
|
546 |
+
fig = px.bar(sorted_df3, x='hashtag', y='value_likes', title='Hashtag performance for the week',
|
547 |
+
color='color', color_discrete_map='identity', hover_data={'value_likes': ':.2f'})
|
548 |
+
|
549 |
+
fig.update_layout(title='Hashtag performance for the week - Likes', xaxis_title='Hashtag', yaxis_title='Value')
|
550 |
+
st.plotly_chart(fig)
|
551 |
+
with tab4:
|
552 |
+
st.subheader("Hashtag Performance - Comments:")
|
553 |
+
#for tag, perf in hashtag_performance.items():
|
554 |
+
#st.write(f"{tag}: {perf}")
|
555 |
+
#AgGrid(hashtag_performance_views)
|
556 |
+
#df4 = pd.DataFrame(list(hashtag_performance_views.items()), columns=["hashtag", "value"])
|
557 |
+
# Sort the DataFrame by the 'value' column in descending order
|
558 |
+
sorted_df4 = df4.sort_values(by="value_comments", ascending=False)
|
559 |
+
st.write(sorted_df4)
|
560 |
+
# Highlight specific bars (use 'rgba' values for transparency)
|
561 |
+
highlighted_bars = ['#fyp', '#tiktok', '#foryou', '#trending', '#viral']
|
562 |
+
sorted_df4['color'] = sorted_df4['hashtag'].apply(lambda x: 'black' if x in highlighted_bars else 'orange')
|
563 |
+
fig = px.bar(sorted_df4, x='hashtag', y='value_comments', title='Hashtag performance for the week',
|
564 |
+
color='color', color_discrete_map='identity', hover_data={'value_comments': ':.2f'})
|
565 |
+
|
566 |
+
fig.update_layout(title='Hashtag performance for the week - Comments', xaxis_title='Hashtag', yaxis_title='Value')
|
567 |
+
st.plotly_chart(fig)
|
568 |
+
|
569 |
+
|
570 |
+
# Calculate the similarity between hashtags
|
571 |
+
#similarity_matrix = cosine_similarity(list(hashtag_vectors.values()))
|
572 |
+
#st.write(similarity_matrix)
|
573 |
+
# Convert the similarity matrix into a DataFrame
|
574 |
+
#similarity_df = pd.DataFrame(similarity_matrix, index=unique_hashtags, columns=unique_hashtags)
|
575 |
+
#st.write(similarity_df)
|
576 |
+
# Calculate the correlation between hashtag similarity and performance
|
577 |
+
#correlations = np.corrcoef(similarity_df, data["Total views"], rowvar=False)
|
578 |
+
#st.write(correlations)
|
579 |
+
|
580 |
+
#with tab2:
|
581 |
+
#if 'video_view_within_days' not in data.columns: #Video Posts
|
582 |
+
#pass
|
583 |
+
|
pages/followers.py
ADDED
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
import numpy as np
|
4 |
+
import datetime
|
5 |
+
import plotly.express as px
|
6 |
+
import plotly.graph_objects as go
|
7 |
+
import statsmodels.api as sm
|
8 |
+
from millify import millify
|
9 |
+
from sklearn.linear_model import LinearRegression
|
10 |
+
from sklearn.metrics import r2_score
|
11 |
+
from st_aggrid import AgGrid
|
12 |
+
import io
|
13 |
+
import openpyxl
|
14 |
+
#from st_pages import Page, show_pages, add_page_title
|
15 |
+
|
16 |
+
# Set page title
|
17 |
+
st.set_page_config(page_title="Followers - Tiktok Analytics Dashboard", page_icon = "📊", layout = "centered", initial_sidebar_state = "auto")
|
18 |
+
|
19 |
+
st.header("Followers")
|
20 |
+
st.markdown("""Upload your files here to load your data!
|
21 |
+
|
22 |
+
*'Follower activity', 'Top territories', 'Gender', 'Total followers' (xlsx or csv format)*
|
23 |
+
""")
|
24 |
+
|
25 |
+
uploaded_files = st.file_uploader(
|
26 |
+
"Choose CSV or Excel files to upload",
|
27 |
+
accept_multiple_files=True,
|
28 |
+
type=['csv', 'xlsx'])
|
29 |
+
|
30 |
+
if uploaded_files:
|
31 |
+
data_list = []
|
32 |
+
# read the file
|
33 |
+
with st.expander("View uploaded data"):
|
34 |
+
for uploaded_file in uploaded_files:
|
35 |
+
st.write("▾ Filename:", uploaded_file.name)
|
36 |
+
bytes_data = uploaded_file.read()
|
37 |
+
data = None
|
38 |
+
if uploaded_file.type == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
39 |
+
data = pd.read_excel(io.BytesIO(bytes_data))
|
40 |
+
AgGrid(data)
|
41 |
+
else:
|
42 |
+
data = pd.read_csv(io.StringIO(bytes_data.decode('utf-8')))
|
43 |
+
AgGrid(data)
|
44 |
+
|
45 |
+
data_list.append(data)
|
46 |
+
|
47 |
+
tab1, tab2, tab3, tab4, tab5 = st.tabs(["Follower Activity", "Gender", "Top Territories", "Followers", "Difference in Daily Followers"])
|
48 |
+
for data in data_list:
|
49 |
+
#st.write(data.columns)
|
50 |
+
#st.write(data)
|
51 |
+
with tab1:
|
52 |
+
if 'Active followers' in data.columns:
|
53 |
+
# create a list of all unique dates in the data
|
54 |
+
unique_dates = data['Date'].unique()
|
55 |
+
|
56 |
+
# Add a date filter widget
|
57 |
+
filter_type = st.sidebar.radio("Select filter type for Follower Activity", ["Individual date", "Total date range"])
|
58 |
+
if filter_type == "Individual date":
|
59 |
+
selected_date = st.sidebar.selectbox("Select a date", unique_dates)
|
60 |
+
|
61 |
+
# Filter the data to only include the selected date
|
62 |
+
filtered_data = data[data['Date'] == selected_date]
|
63 |
+
|
64 |
+
# Calculate the average number of followers for the selected date
|
65 |
+
avg_followers = filtered_data['Active followers'].mean()
|
66 |
+
|
67 |
+
|
68 |
+
elif filter_type == "Total date range":
|
69 |
+
# Filter the data to include the entire date range
|
70 |
+
filtered_data = data
|
71 |
+
# Calculate the average number of followers for the entire date range
|
72 |
+
avg_followers = filtered_data['Active followers'].mean()
|
73 |
+
|
74 |
+
#else:
|
75 |
+
# Get the start and end dates using st.date_input
|
76 |
+
#start_date = st.sidebar.date_input("Select start date", data['Date'].min(), data['Date'].max())
|
77 |
+
#end_date = st.sidebar.date_input("Select end date", data['Date'].min(), data['Date'].max())
|
78 |
+
|
79 |
+
# Filter the data based on the start and end dates
|
80 |
+
#filtered_data = data[(data['Date'] >= start_date) & (data['Date'] <= end_date)]
|
81 |
+
|
82 |
+
# create a datetime column from the date and hour columns
|
83 |
+
filtered_data['Datetime'] = pd.to_datetime(filtered_data['Date'] + ' ' + (filtered_data['Hour'] - 1).astype(str) + ':00:00')
|
84 |
+
|
85 |
+
# group the data by the datetime column and calculate the sum of active followers
|
86 |
+
grouped_data = filtered_data.groupby("Datetime")["Active followers"].sum().reset_index()
|
87 |
+
|
88 |
+
# create a line chart using Plotly Express
|
89 |
+
fig = px.line(filtered_data, x="Datetime", y="Active followers", title="Follower Activity")
|
90 |
+
|
91 |
+
# Add average line
|
92 |
+
fig.add_shape(type='line', x0=filtered_data['Datetime'].min(), y0=avg_followers, x1=filtered_data['Datetime'].max(), y1=avg_followers, line=dict(color='red', width=3, dash='dash'))
|
93 |
+
|
94 |
+
# Annotate average value onto average line
|
95 |
+
fig.add_annotation(x=filtered_data['Datetime'].max(), y=avg_followers, text=f"Average: {avg_followers:.0f}", showarrow=True, arrowhead=2)
|
96 |
+
|
97 |
+
st.plotly_chart(fig)
|
98 |
+
with tab2:
|
99 |
+
if 'Gender' in data.columns:
|
100 |
+
#st.write("Pie chart for 'Gender'")
|
101 |
+
gender_data = data.groupby('Gender')['Distribution'].apply(lambda x: pd.to_numeric(x.str.replace('%', ''), errors='coerce').dropna().mean()).reset_index()
|
102 |
+
fig = px.pie(gender_data, values='Distribution', names='Gender', title='Gender Distribution (%)',
|
103 |
+
color_discrete_sequence=['#1f77b4', '#ff7f0e'])
|
104 |
+
st.plotly_chart(fig)
|
105 |
+
with tab3:
|
106 |
+
if 'Top territories' in data.columns:
|
107 |
+
territories_data = data.groupby('Top territories')['Distribution'].apply(lambda x: pd.to_numeric(x.str.replace('%', ''), errors='coerce').dropna().mean()).reset_index()
|
108 |
+
territories_data = territories_data.sort_values(by='Distribution', ascending=True)
|
109 |
+
#st.write("Top 5 territories by distribution")
|
110 |
+
fig = px.bar(territories_data, x='Distribution', y='Top territories', title='Distribution (%) of Top 5 Countries',
|
111 |
+
color_discrete_sequence=px.colors.qualitative.Dark2)
|
112 |
+
st.plotly_chart(fig)
|
113 |
+
with tab4:
|
114 |
+
if 'Followers' in data.columns:
|
115 |
+
fig = px.line(data, x="Date", y="Followers", title="Total Followers", markers=True,
|
116 |
+
hover_data={'Followers': ':.2f'})
|
117 |
+
st.plotly_chart(fig)
|
118 |
+
with tab5:
|
119 |
+
if 'Difference in followers from previous day' in data.columns:
|
120 |
+
# Create a custom color scale
|
121 |
+
def custom_color_scale(val):
|
122 |
+
if val >= 0:
|
123 |
+
return 'rgba(54, 164, 235, 0.8)'
|
124 |
+
else:
|
125 |
+
return 'rgba(255, 77, 77, 0.8)'
|
126 |
+
fig = px.bar(data, x="Date", y="Difference in followers from previous day", title="Difference in Daily Followers",
|
127 |
+
text='Difference in followers from previous day', color='Difference in followers from previous day',
|
128 |
+
hover_data={'Difference in followers from previous day': ':.2f'}, color_discrete_map={val: custom_color_scale(val) for val in data['Difference in followers from previous day']})
|
129 |
+
# Customize the layout
|
130 |
+
fig.update_layout(
|
131 |
+
title="Difference in Daily Followers",
|
132 |
+
xaxis_title="Date",
|
133 |
+
yaxis_title="Difference in Daily Followers",
|
134 |
+
showlegend=False,
|
135 |
+
plot_bgcolor="white",
|
136 |
+
yaxis=dict(zeroline=True, zerolinewidth=2, zerolinecolor="black"), # Add a line at y=0
|
137 |
+
)
|
138 |
+
st.plotly_chart(fig)
|
139 |
+
|
140 |
+
|
141 |
+
|
pages/overview.py
ADDED
@@ -0,0 +1,227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
import numpy as np
|
4 |
+
import plotly.express as px
|
5 |
+
import plotly.graph_objects as go
|
6 |
+
import statsmodels.api as sm
|
7 |
+
from millify import millify
|
8 |
+
from sklearn.linear_model import LinearRegression
|
9 |
+
from sklearn.metrics import r2_score
|
10 |
+
from st_aggrid import AgGrid
|
11 |
+
import io
|
12 |
+
import openpyxl
|
13 |
+
#from st_pages import Page, show_pages, add_page_title
|
14 |
+
from streamlit_extras.metric_cards import style_metric_cards
|
15 |
+
|
16 |
+
# Set page title
|
17 |
+
st.set_page_config(page_title="Overview - Tiktok Analytics Dashboard", page_icon = "📊", layout = "centered", initial_sidebar_state = "auto")
|
18 |
+
|
19 |
+
|
20 |
+
st.header("Overview")
|
21 |
+
st.markdown("""Upload your files here to load your data!
|
22 |
+
|
23 |
+
*'Last 60 days' (xlsx or csv format)*
|
24 |
+
""")
|
25 |
+
|
26 |
+
def plot_chart(data, chart_type, x_var, y_var, z_var=None, show_regression_line=False, show_r_squared=False):
|
27 |
+
scatter_marker_color = 'green'
|
28 |
+
regression_line_color = 'red'
|
29 |
+
if chart_type == "line":
|
30 |
+
fig = px.line(data, x=x_var, y=y_var)
|
31 |
+
|
32 |
+
elif chart_type == "bar":
|
33 |
+
fig = px.bar(data, x=x_var, y=y_var)
|
34 |
+
|
35 |
+
elif chart_type == "scatter":
|
36 |
+
fig = px.scatter(data, x=x_var, y=y_var, color_discrete_sequence=[scatter_marker_color])
|
37 |
+
|
38 |
+
if show_regression_line and x_var != 'Date':
|
39 |
+
X = data[x_var].values.reshape(-1, 1)
|
40 |
+
y = data[y_var].values.reshape(-1, 1)
|
41 |
+
model = LinearRegression().fit(X, y)
|
42 |
+
y_pred = model.predict(X)
|
43 |
+
r_squared = r2_score(y, y_pred) # Calculate R-squared value
|
44 |
+
|
45 |
+
fig.add_trace(
|
46 |
+
go.Scatter(x=data[x_var], y=y_pred[:, 0], mode='lines', name='Regression Line', line=dict(color=regression_line_color))
|
47 |
+
)
|
48 |
+
|
49 |
+
# Add R-squared value as a text annotation
|
50 |
+
fig.add_annotation(
|
51 |
+
x=data[x_var].max(),
|
52 |
+
y=y_pred[-1, 0],
|
53 |
+
text=f"R-squared: {r_squared:.4f}",
|
54 |
+
showarrow=False,
|
55 |
+
font=dict(size=14),
|
56 |
+
bgcolor='rgba(255, 255, 255, 0.8)',
|
57 |
+
bordercolor='black',
|
58 |
+
borderwidth=1,
|
59 |
+
borderpad=4
|
60 |
+
)
|
61 |
+
|
62 |
+
elif chart_type == "heatmap":
|
63 |
+
fig = px.imshow(data, color_continuous_scale='Inferno')
|
64 |
+
|
65 |
+
elif chart_type == "scatter_3d":
|
66 |
+
if z_var is not None:
|
67 |
+
fig = px.scatter_3d(data, x=x_var, y=y_var, z=z_var, color=data.columns[0])
|
68 |
+
else:
|
69 |
+
st.warning("Please select Z variable for 3D line plot.")
|
70 |
+
return
|
71 |
+
|
72 |
+
elif chart_type == "line_3d":
|
73 |
+
if z_var is not None:
|
74 |
+
fig = go.Figure(data=[go.Scatter3d(x=data[x_var], y=data[y_var], z=data[z_var], mode='lines')])
|
75 |
+
fig.update_layout(scene=dict(xaxis_title=x_var, yaxis_title=y_var, zaxis_title=z_var)) # Set the axis name
|
76 |
+
else:
|
77 |
+
st.warning("Please select Z variable for 3D line plot.")
|
78 |
+
return
|
79 |
+
|
80 |
+
elif chart_type == "surface_3d":
|
81 |
+
if z_var is not None:
|
82 |
+
fig = go.Figure(data=[go.Surface(z=data.values)])
|
83 |
+
fig.update_layout(scene=dict(xaxis_title=x_var, yaxis_title=y_var, zaxis_title=z_var)) # Set the axis name
|
84 |
+
else:
|
85 |
+
st.warning("Please select Z variable for 3D line plot.")
|
86 |
+
return
|
87 |
+
|
88 |
+
elif chart_type == "radar":
|
89 |
+
fig = go.Figure()
|
90 |
+
for col in data.columns[1:]:
|
91 |
+
fig.add_trace(go.Scatterpolar(r=data[col], theta=data[x_var], mode='lines', name=col))
|
92 |
+
fig.update_layout(polar=dict(radialaxis=dict(visible=True, range=[data[data.columns[1:]].min().min(), data[data.columns[1:]].max().max()])))
|
93 |
+
|
94 |
+
st.plotly_chart(fig)
|
95 |
+
|
96 |
+
def plot_radar_chart(data, columns):
|
97 |
+
df = data[columns]
|
98 |
+
fig = go.Figure()
|
99 |
+
|
100 |
+
for i in range(len(df)):
|
101 |
+
date_label = data.loc[i, 'Date']
|
102 |
+
fig.add_trace(go.Scatterpolar(
|
103 |
+
r=df.loc[i].values,
|
104 |
+
theta=df.columns,
|
105 |
+
fill='toself',
|
106 |
+
name=date_label
|
107 |
+
))
|
108 |
+
|
109 |
+
fig.update_layout(
|
110 |
+
polar=dict(
|
111 |
+
radialaxis=dict(
|
112 |
+
visible=True,
|
113 |
+
range=[0, df.max().max()]
|
114 |
+
)
|
115 |
+
),
|
116 |
+
showlegend=True
|
117 |
+
)
|
118 |
+
|
119 |
+
st.plotly_chart(fig)
|
120 |
+
|
121 |
+
|
122 |
+
uploaded_files = st.file_uploader(
|
123 |
+
"Choose CSV or Excel files to upload",
|
124 |
+
accept_multiple_files=True,
|
125 |
+
type=['csv', 'xlsx'])
|
126 |
+
|
127 |
+
if uploaded_files:
|
128 |
+
data_list = []
|
129 |
+
for uploaded_file in uploaded_files:
|
130 |
+
# read the file
|
131 |
+
with st.expander("View uploaded data"):
|
132 |
+
st.write("▾ Filename:", uploaded_file.name)
|
133 |
+
bytes_data = uploaded_file.read()
|
134 |
+
data = None
|
135 |
+
if uploaded_file.type == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
136 |
+
data = pd.read_excel(io.BytesIO(bytes_data))
|
137 |
+
AgGrid(data)
|
138 |
+
else:
|
139 |
+
data = pd.read_csv(io.StringIO(bytes_data.decode('utf-8')))
|
140 |
+
AgGrid(data)
|
141 |
+
|
142 |
+
# preview the data
|
143 |
+
#st.write('Preview of', uploaded_file.name)
|
144 |
+
# st.write(data)
|
145 |
+
|
146 |
+
# convert "Date" column to datetime object and set as index
|
147 |
+
#data['Date'] = pd.to_datetime(data['Date'])
|
148 |
+
#data.set_index('Date', inplace=True)
|
149 |
+
|
150 |
+
data_list.append(data)
|
151 |
+
|
152 |
+
|
153 |
+
# Replace "data" with your actual dataframe
|
154 |
+
sums = data.sum()
|
155 |
+
#st.write(sums) # To check table values for indexing
|
156 |
+
col1, col2, col3, col4, col5 = st.columns((5))
|
157 |
+
with col1:
|
158 |
+
st.metric(label="Video views", value=sums[1])
|
159 |
+
with col2:
|
160 |
+
st.metric(label="Profile views", value=sums[2])
|
161 |
+
with col3:
|
162 |
+
st.metric(label="Likes", value=sums[3])
|
163 |
+
with col4:
|
164 |
+
st.metric(label="Comments", value=sums[4])
|
165 |
+
with col5:
|
166 |
+
st.metric(label="Shares", value=sums[5])
|
167 |
+
#style_metric_cards()
|
168 |
+
|
169 |
+
# Generate specific charts based on the file name
|
170 |
+
if uploaded_file.name == "Last 60 days.xlsx" or uploaded_file.name == "Last 60 days.csv":
|
171 |
+
|
172 |
+
x_var = st.sidebar.selectbox("Select X variable for Last 60 days", data.columns)
|
173 |
+
y_var = st.sidebar.selectbox("Select Y variable for Last 60 days", data.columns)
|
174 |
+
show_regression_line = False
|
175 |
+
|
176 |
+
z_var_options = ["None"] + list(data.columns)
|
177 |
+
z_var = st.sidebar.selectbox("Select Z variable for 3D charts (if applicable)", z_var_options)
|
178 |
+
|
179 |
+
# Allow user to select time frequency for resampling
|
180 |
+
#time_frequency = st.sidebar.selectbox("Select time frequency", ["Day", "Week", "Month"])
|
181 |
+
|
182 |
+
#if time_frequency == "Week":
|
183 |
+
#data_resampled = data.resample('W').sum()
|
184 |
+
#elif time_frequency == "Month":
|
185 |
+
#data_resampled = data.resample('M').sum()
|
186 |
+
#else:
|
187 |
+
#data_resampled = data
|
188 |
+
|
189 |
+
tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8 = st.tabs(["Line", "Bar", "Scatterplot", "Heatmap",
|
190 |
+
"3D Scatterplot", "3D Lineplot", "3D Surfaceplot", "Radar chart"])
|
191 |
+
with tab1:
|
192 |
+
st.write("Lineplot for 'Last 60 days'")
|
193 |
+
plot_chart(data, "line", x_var, y_var)
|
194 |
+
|
195 |
+
with tab2:
|
196 |
+
st.write("Barplot for 'Last 60 days'")
|
197 |
+
plot_chart(data, "bar", x_var, y_var)
|
198 |
+
|
199 |
+
with tab3:
|
200 |
+
st.write("Scatterplot for 'Last 60 days'")
|
201 |
+
show_regression_line = st.checkbox("Show regression line for Last 60 days scatterplot (does not apply when X = Date)")
|
202 |
+
plot_chart(data, "scatter", x_var, y_var, show_regression_line=show_regression_line)
|
203 |
+
|
204 |
+
with tab4:
|
205 |
+
st.write("Heatmap for 'Last 60 days'")
|
206 |
+
plot_chart(data, "heatmap", x_var, y_var)
|
207 |
+
|
208 |
+
with tab5:
|
209 |
+
st.write("3D Scatterplot for 'Last 60 days'")
|
210 |
+
if z_var != "None":
|
211 |
+
plot_chart(data, "scatter_3d", x_var, y_var, z_var)
|
212 |
+
|
213 |
+
with tab6:
|
214 |
+
st.write("3D Lineplot for 'Last 60 days'")
|
215 |
+
if z_var != "None":
|
216 |
+
plot_chart(data, "line_3d", x_var, y_var, z_var)
|
217 |
+
|
218 |
+
with tab7:
|
219 |
+
st.write("3D Surfaceplot for 'Last 60 days'")
|
220 |
+
if z_var != "None":
|
221 |
+
plot_chart(data, "surface_3d", x_var, y_var, z_var)
|
222 |
+
|
223 |
+
with tab8:
|
224 |
+
st.write("Radar chart for 'Last 60 days'")
|
225 |
+
radar_columns = ['Video views', 'Profile views', 'Likes', 'Comments', 'Shares']
|
226 |
+
plot_radar_chart(data, radar_columns)
|
227 |
+
# Add more conditions for other specific file names if needed
|
requirements.txt
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit==1.22.0
|
2 |
+
pandas==1.3.5
|
3 |
+
numpy==1.21.6
|
4 |
+
plotly==5.14.1
|
5 |
+
statsmodels==0.13.5
|
6 |
+
millify==0.1.1
|
7 |
+
scikit-learn==1.0.2
|
8 |
+
streamlit-aggrid==0.3.2
|
9 |
+
emoji==2.2.0
|
10 |
+
openpyxl
|
11 |
+
gensim==3.3.0
|
12 |
+
xgboost==1.6.2
|
13 |
+
matplotlib==3.4.3
|
14 |
+
seaborn==0.12.2
|
15 |
+
|