DataNerd2021 commited on
Commit
6befa81
1 Parent(s): 3c9a789

add streamlit file

Browse files
Files changed (1) hide show
  1. app.py +222 -0
app.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ Python libraries allow users to extend the abilities of the language compiler. For this project, I will be using the following libraries:
3
+ - pandas and numpy (for data analysis and manipulation)
4
+ - streamlit and plotly (for UI design and data visualization)
5
+ - pyodbc and spotipy (for Spotify API and SQL Server connections)
6
+ '''
7
+
8
+ # import libraries
9
+
10
+
11
+
12
+ import pandas as pd
13
+ import numpy as np
14
+ import streamlit as st
15
+ import plotly.express as px
16
+ from random import seed
17
+ import spotipy
18
+ from spotipy.oauth2 import SpotifyClientCredentials
19
+
20
+ # define function to highlight output dataframe cells based on value
21
+
22
+
23
+ def highlight_colors(val, color_if_true, color_if_false):
24
+ color = color_if_true if val >= 0.75 and val <= 1.0 else color_if_false
25
+ return 'background-color: {}'.format(color)
26
+
27
+ # establish API connection
28
+
29
+ cid = '3fda75b7146a4769b207ee44017b3abe'
30
+ secret = '2a755cb04a18406b9394dbef2f8069dd'
31
+
32
+ client_credentials_manager = SpotifyClientCredentials(client_id=cid, client_secret=secret)
33
+ sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
34
+
35
+ # establish SQL Server connection
36
+
37
+
38
+ # read data from parquet file
39
+
40
+ query = pd.read_parquet("tracks.parquet.gzip")
41
+
42
+
43
+ # create metrics for analysis
44
+
45
+ query2 = pd.melt(query, id_vars=['uri'], var_name='metrics', value_name='score', value_vars=['instrumentalness', 'danceability', 'energy', 'acousticness', 'valence', 'liveness'])
46
+
47
+
48
+
49
+ # name the app
50
+
51
+ st.set_page_config(page_title='Song Recommendation App', layout='centered')
52
+
53
+ # create internal CSS
54
+
55
+ st.markdown(""" <style>
56
+ .title { font-size: 45px;
57
+ text-align: center;}
58
+ .track { font-size: 20px;
59
+ text-align: left;
60
+ padding-left: 50px;
61
+ padding-top: 30px;}
62
+ .artist { font-size: 15px;
63
+ text-align: left;
64
+ padding-left: 85px;}
65
+ .subheader { font-size: 22px;
66
+ text-align: left;}
67
+ .header { font-size: 20px;
68
+ padding-left: 35px;
69
+ font-weight: bold;}
70
+ .header2 { font-size: 20px;
71
+ padding-left: 33px;
72
+ font-weight: bold;}
73
+ .header3 { font-size: 20px;
74
+ padding-left: 25px;
75
+ font-weight: bold;}
76
+ .header4 { font-size: 20px;
77
+ padding-left: 0px;
78
+ font-weight: bold;}
79
+ .header5 { font-size: 25px;
80
+ padding-left: 225px;
81
+ margin-top: 50px;
82
+ margin-bottom: 50px;}
83
+ .ban-font { font-size: 30px;
84
+ padding-left: 25px;}
85
+ .ban-font2 { font-size: 30px;
86
+ padding-left: 25px;}
87
+ .ban-font3 { font-size: 30px;
88
+ padding-left: 0px;}
89
+ .ban-font4 { font-size: 30px;
90
+ padding-left: 30px;}
91
+ .image { padding-top: 10px;}
92
+ .streamlit-expanderHeader { font-size: 22px;}
93
+ .row_heading.level0 {display:none}
94
+ .blank {display:none}
95
+
96
+ </style>""", unsafe_allow_html=True)
97
+
98
+ # create sidebar menu
99
+
100
+ sidebar_title = st.sidebar.header('Pick Your Favorite Song')
101
+ artists = query['artist_name'].drop_duplicates()
102
+ artists = artists.sort_values()
103
+ artist_choice = st.sidebar.selectbox('Choose an Artist:', artists)
104
+ tracks = query['track_name'].loc[query['artist_name'] == artist_choice].drop_duplicates()
105
+ tracks = tracks.sort_values()
106
+ track_choice = st.sidebar.selectbox('Choose a Song', tracks)
107
+ empty = st.sidebar.text('')
108
+ output = query['uri'].loc[(query['track_name'] == track_choice) & (query['artist_name'] == artist_choice)].values
109
+ output_bpm = query['tempo'].loc[(query['track_name'] == track_choice) & (query['artist_name'] == artist_choice)].drop_duplicates().values
110
+ output_bpm = output_bpm.astype(float)
111
+ output_bpm = np.round(output_bpm, decimals=0)
112
+ output_bpm = output_bpm.astype(int)
113
+ uri_output = st.sidebar.selectbox('Select the URI:', options=(output))
114
+
115
+
116
+ viz_query = query2.loc[query2['uri'] == uri_output]
117
+
118
+ # create title for main interface
119
+
120
+ page_title = st.markdown(f'''<h1 class="title"">Song Recommendation Engine 2.0</h1>''', unsafe_allow_html=True)
121
+
122
+ # create dropdown menu for app description
123
+
124
+ st.markdown('<br>', unsafe_allow_html=True)
125
+ with st.expander('Description'):
126
+ st.markdown('''Have you ever wondered how Spotify's Song Recommendation Algorithm works? This app allows you to take a behind-the-scenes look at how Spotify uses your data to recommend songs based on various metrics.''', unsafe_allow_html=True)
127
+
128
+ # allow user to preview song and view album cover
129
+
130
+ st.markdown('<br><br><h4>Song Preview</h4><br><br>', unsafe_allow_html=True)
131
+
132
+ img_query = pd.json_normalize(sp.track(uri_output), record_path=['album', ['images']])
133
+ img_url = img_query['url'][0]
134
+ audio_query = pd.json_normalize(sp.track(uri_output))
135
+ audio_url = audio_query['preview_url'][0]
136
+ col1, col2, col3 = st.columns([1, 4, 1])
137
+ with col2:
138
+ if audio_url != None:
139
+ st.audio(audio_url)
140
+ else:
141
+ st.text('No Audio Available')
142
+ col1, col2, col3, col4, col5 = st.columns([1, 1, 1, 4, 1])
143
+ with col3:
144
+ album_image = st.markdown(f'<img class= "image" src={img_url} width="125" height="125"></img>', unsafe_allow_html=True)
145
+ with col4:
146
+ st.markdown(f'<p class="track">{track_choice}</p>\n<p class="artist">{artist_choice}</p>', unsafe_allow_html=True)
147
+
148
+ # create BANs for data visualizations
149
+
150
+ col1, col2, col3, col4, col5 = st.columns([1, 2, 1, 1, 1])
151
+ with col1:
152
+ st.text('')
153
+ st.text('')
154
+ st.text('')
155
+ st.text('')
156
+ filters_txt = st.markdown('<h4>Features</h4><br><br>', unsafe_allow_html=True)
157
+ col1, col2, col3, col4 = st.columns([1, 1, 1, 1])
158
+ with col1:
159
+ bpm_ban = st.markdown(f'''<p class="header">BPM</p><p class="ban-font">{output_bpm}</p>''', unsafe_allow_html=True)
160
+
161
+
162
+ # create data visualization using new query from uri output
163
+
164
+
165
+
166
+ fig = px.bar_polar(viz_query, theta='metrics', r='score', range_r=[0.0,1.0], hover_name='metrics', hover_data={'score':True, 'metrics':False}, width=750, height=600, color_continuous_scale='Sunset', color='score', range_color=[0.0,1.0], template='plotly', title='Song Metrics')
167
+ fig = fig.update_layout(polar_radialaxis_gridcolor="#e3ecf6", polar_angularaxis_gridcolor="#e3ecf6", polar= dict(radialaxis= dict(showticklabels= False)), hovermode="x")
168
+ fig = fig.update_traces(hovertemplate="<b>Metric: %{theta}<br>Score: %{r}</b>", hoverlabel= dict(bgcolor="#ffffff"))
169
+ st.plotly_chart(fig)
170
+
171
+ # create drop-down menu to display definitions for each metric
172
+
173
+ with st.expander('Metric Definitions'):
174
+ st.markdown(f'''<h4><b><u>Acousticness</u></b></h4>\nA confidence measure from 0.00 to 1.00 of whether a track is acoustic. 1.0 represents high confidence the track is acoustic.\n\n<h4><b><u>Danceability</u></b></h4>\nThis describes how suitable a track is for dancing based on a combination of musical elements including tempo (BPM), rhythm stability, beat strength, and overall regularity. A value of 0.00 is least danceable and 1.00 is most danceable.\n\n<h4><b><u>Energy</u></b></h4>\nA measure from 0.00 to 1.00 that represents a perceptual measure of intensity and activity. Typically, energetic tracks feel fast, loud, and noisy. Perceptual features contributing to this attribute include dynamic range, perceived loudness, timbre, onset rate, and general entropy.\n\n<h4><b><u>Instrumentalness</u></b></h4>\nPredicts whether a tracks contains no vocals. "Ooh" and "Aah" sounds are treated as instrumental in this context. The closer the value is to 1.00, the greater likelihood the track contains no vocal content.\n\n<h4><b><u>Liveness</u></b></h4>\nDetects the presence of an audience in the recoding. The great the value is to 1.00, the greater the likelihood that the track was performed live.\n\n<h4><b><u>Valence</u></b></h4>\nA measure from 0.00 to 1.00 describing the musical positiveness by a track. Tracks with high valence (> 0.50) sound more positive, whereas tracks with low valence (< 0.50) sound more negative.\n\n<br><i>* Web API Reference: Get Track Audio Features, Spotify, developer.spotify.com/documentation/web-api/reference/#/operations/get-audio-features.</i>''', unsafe_allow_html=True)
175
+
176
+ # create drop-down menu to display song recommendations based on user input
177
+
178
+ with st.expander('Song Recommendations'):
179
+ st.subheader('Your Song')
180
+ result_query = query.loc[query['track_uri'] == uri_output]
181
+ result_query = result_query.drop_duplicates()
182
+ result_query = result_query.reset_index()
183
+ result_df = pd.DataFrame(result_query)
184
+ result_df = result_df[['track_name', 'artist_name', 'album_name', 'acousticness', 'danceability', 'energy', 'instrumentalness', 'liveness', 'valence', 'artist_uri', 'uri']]
185
+ st.dataframe(result_df)
186
+
187
+
188
+ # get all artist data
189
+
190
+ result_list2 = pd.json_normalize(sp.recommendations(seed_tracks=[result_df['uri'][0]], seed_artists=[result_df['artist_uri'][0]], limit=25), record_path=['tracks', ['artists']])
191
+
192
+ result_list2 = result_list2.merge(query, left_on='uri', right_on='artist_uri')
193
+ result_list2 = result_list2.rename(columns={'name': 'Artist Name', 'uri_x': 'Artist URI'})
194
+ result_list2 = result_list2.rename(columns={'track_name': 'Track Name'})
195
+ result_list2 = result_list2[['Track Name', 'Artist Name', 'album_name', 'acousticness', 'danceability', 'energy', 'instrumentalness', 'liveness', 'valence']]
196
+ final_df = result_list2.head(25)
197
+
198
+ result_df = result_df.reset_index()
199
+ final_df = final_df.reset_index()
200
+
201
+ # create new field to calculate likeness for song metrics
202
+
203
+
204
+
205
+ # create new field to calculate likeness for song metrics
206
+
207
+ final_df['acousticness'] = round(final_df['acousticness'].astype(float), 3)
208
+ final_df['danceability'] = round(final_df['danceability'].astype(float), 3)
209
+ final_df['energy'] = round(final_df['energy'].astype(float), 3)
210
+ final_df['instrumentalness'] = round(final_df['instrumentalness'].astype(float), 3)
211
+ final_df['liveness'] = round(final_df['liveness'].astype(float), 3)
212
+ final_df['valence'] = round(final_df['valence'].astype(float), 3)
213
+ final_df = final_df[['Track Name', 'Artist Name', 'acousticness', 'danceability', 'energy', 'instrumentalness', 'liveness', 'valence']]
214
+ final_df = final_df.drop_duplicates()
215
+ final_df = final_df.style.applymap(highlight_colors, color_if_true='#5EFF33', color_if_false='white', subset=['acousticness', 'danceability', 'energy', 'instrumentalness', 'liveness', 'valence'])
216
+ st.subheader('Recommendations (by likeness)')
217
+ st.dataframe(final_df)
218
+
219
+
220
+
221
+
222
+