Gaara52/spotify-recommender-system
0
1import spotipy2from spotipy.oauth2 import SpotifyClientCredentials3import streamlit as st4import streamlit.components.v1 as components5import pandas as pd6from sklearn.preprocessing import StandardScaler7from sklearn.pipeline import Pipeline8import numpy as np9from sklearn.cluster import KMeans10from sklearn.decomposition import PCA11import plotly.graph_objects as go12from collections import defaultdict13from scipy.spatial.distance import cdist14import time15 16cid = 'cdb0a1aa1fc24842b9d98603fab657be'17secret = 'e421b4cb445b45dd8ea9635ba9892c22'18client_credentials_manager = SpotifyClientCredentials(client_id=cid, client_secret=secret)19sp = spotipy.Spotify(client_credentials_manager20=21client_credentials_manager)22 23df = pd.read_csv('fourtet.csv')24 25def find_song(name, artist):26 # Initialize an empty dictionary to store features and values27 song_data = defaultdict()28 29 # Using Spotipy search function for track and artist, returning None if cannot be found in Spotify30 results = sp.search(q='track: {} artist: {}'.format(name,31 artist), limit=1)32 if results['tracks']['items'] == []:33 return None34 35 # Isolating track information and ID from results36 results = results['tracks']['items'][0]37 track_id = results['id']38 39 # Obtaining audio features40 audio_features = sp.audio_features(track_id)[0]41 42 # Preparing columns and converting to DataFrame43 song_data['name'] = [name]44 song_data['artist'] = [artist]45 song_data['explicit'] = [int(results['explicit'])]46 song_data['duration_ms'] = [results['duration_ms']]47 song_data['popularity'] = [results['popularity']]48 49 for key, value in audio_features.items():50 song_data[key] = value51 52 return pd.DataFrame(song_data)53 54 55def get_song_data(song, spotify_data):56 # Function will attempt to find ID track name and artist from dataset to return track data57 try:58 song_data = spotify_data[(spotify_data['track_name'] == song['name'])59 & (spotify_data['artist_name'] == song['artist'])].iloc[0]60 return song_data61 62 except IndexError:63 return find_song(song['name'], song['artist'])64 65 66def get_mean_vector(song_list, spotify_data):67 # Initialize empty list to store vectors68 song_vectors = []69 70 # Identify audio features columns71 number_cols = ['valence', 'acousticness', 'danceability', 'duration_ms', 'energy', 'instrumentalness', 'liveness',72 'loudness', 'speechiness', 'tempo']73 74 # Append list of values to list75 for song in song_list:76 song_data = get_song_data(song, spotify_data)77 if song_data is None:78 print('Warning: {} not found in Spotify or database'.format(song['name']))79 continue80 song_vector = song_data[number_cols].values81 song_vectors.append(song_vector)82 83 # Convert to single array and return mean84 song_matrix = np.array(list(song_vectors))85 return np.mean(song_matrix, axis=0)86 87 88def recommend_songs(song_list, spotify_data, n_songs=10):89 # Establishing metadata and numerical columns90 metadata_cols = ['track_name', 'artist_name']91 number_cols = ['valence', 'acousticness', 'danceability', 'duration_ms', 'energy', 'instrumentalness', 'liveness',92 'loudness', 'speechiness', 'tempo']93 94 # Getting mean vector95 song_center = get_mean_vector(song_list, spotify_data)96 97 # Dropping extra columns98 spotify_data = spotify_data.drop(['popularity', 'Unnamed: 0'], axis=1)99 100 # Using KMeans to cluster data, fitting and adding labels to dataset101 X = spotify_data.select_dtypes(np.number)102 cluster_pipeline = Pipeline([('scaler', StandardScaler()), ('kmeans', KMeans(n_clusters=3))])103 cluster_pipeline.fit(X.values)104 cluster_labels = cluster_pipeline.predict(X.values)105 spotify_data['cluster'] = cluster_labels106 107 # Scaling and transforming numerical columns of data and reshaped song center108 scaler = cluster_pipeline.steps[0][1]109 scaled_data = scaler.transform(spotify_data[number_cols])110 scaled_song_center = scaler.transform(song_center.reshape(1, -1))111 112 # Computing cosine distance on transformed arrays113 distances = cdist(scaled_song_center, scaled_data, 'cosine')114 115 # Return sorted list of top n indices116 index = list(np.argsort(distances)[:, :n_songs][0])117 118 # Converting to DataFrame and returning track and artist name119 rec_songs = spotify_data.iloc[index]120 df_recs = pd.DataFrame(rec_songs[metadata_cols])121 return df_recs122 123st.title('Recommendations from Four Tet')124st.write('Generate song recommendations from DJ and producer Four Tet, '125 'based on his popular Spotify playlist.')126 127components.iframe("https://open.spotify.com/embed/playlist/2uzbATYxs9V8YQi5lf89WG", width=700, height=300)128 129st.write('## How It Works')130st.write('Fill in up to three songs and artists of your choice, or use the sidebar to adjust audio features '131 'on your own. You will be able to listen in your browser to the recommended songs! ')132st.write('Tip: Try entering hip-hop, dance, R&B or jazz tracks - the playlist has plenty of them.')133 134def user_input_features():135 danceability = st.sidebar.slider('Danceability', 0.000000, 0.980000, 0.000000, 0.01)136 energy = st.sidebar.slider('Energy', 0.000281, 0.999000, 0.000281, 0.01)137 acousticness = st.sidebar.slider('Acousticness', 0.000002, 0.996000, 0.000002, 0.01)138 instrumentalness = st.sidebar.slider('Instrumentalness', 0.000000, 0.984000, 0.000000, 0.01)139 liveness = st.sidebar.slider('Liveness', 0.020600, 0.993000, 0.020600, 0.01)140 loudness = st.sidebar.slider('Loudness', -37.114000, -1.987000, -37.114000, 0.1)141 speechiness = st.sidebar.slider('Speechiness', 0.000000, 0.947000, 0.000000, 0.01)142 tempo = st.sidebar.slider('Tempo', 0.000000, 210.029000, 0.000000, 1.0)143 valence = st.sidebar.slider('Valence', 0.000000, 0.996000, 0.000000, 0.01)144 145 user_data = {'danceability': danceability,146 'energy': energy,147 'acousticness': acousticness,148 'instrumentalness': instrumentalness,149 'liveness': liveness,150 'loudness': loudness,151 'speechiness': speechiness,152 'tempo': tempo,153 'valence': valence}154 155 features = pd.DataFrame(user_data, index=[0])156 return features157 158 159 160df_user = user_input_features()161button1 = st.sidebar.button('Recommend Songs')162if button1:163 df3 = pd.DataFrame()164 for k, v in df_user.iterrows():165 i = ((df['danceability']-v['danceability']) * \166 (df['energy']-v['energy']) * \167 (df['acousticness']-v['acousticness']) * \168 (df['instrumentalness'] - v['instrumentalness']) * \169 (df['liveness'] - v['liveness']) * \170 (df['loudness'] - v['loudness']) * \171 (df['speechiness'] - v['speechiness']) * \172 (df['tempo'] - v['tempo']) * \173 (df['valence'] - v['valence'])).abs().idxmin()174 df3 = df3.append(df.loc[i])175 df3 = df3.drop(['Unnamed: 0', 'popularity'], axis=1)176 new_song_list = [{'name': df3.iloc[0]['track_name'], 'artist': df3.iloc[0]['artist_name']}]177 new_song_recs = recommend_songs(new_song_list, df, 5)178 for i, j in new_song_recs.itertuples(index=False):179 embed_string = 'https://open.spotify.com/embed/track/'180 id_list = []181 try:182 results = sp.search(q='track: {} artist: {}'.format(i, j), limit=1)183 id_list.append(results['tracks']['items'][0]['id'])184 except IndexError:185 continue186 concat_list = [embed_string + k for k in id_list]187 try:188 components.iframe(concat_list[0], width=700, height=300)189 except IndexError:190 continue191 192song_list = []193title = st.text_input('Song #1')194artist = st.text_input('Artist #1')195song_list.append({'name': title, 'artist': artist})196title2 = st.text_input('Song #2')197artist2 = st.text_input('Artist #2')198song_list.append({'name': title2, 'artist': artist2})199title3 = st.text_input('Song #3')200artist3 = st.text_input('Artist #3')201song_list.append({'name': title3, 'artist': artist3})202button2 = st.button('Go')203if button2:204 try:205 song_recs = recommend_songs(song_list, df, 5)206 except ValueError:207 st.markdown('**Song not found in Spotify, please try again**')208 for i, j in song_recs.itertuples(index=False):209 embed_string = 'https://open.spotify.com/embed/track/'210 id_list = []211 try:212 results = sp.search(q='track: {} artist: {}'.format(i, j), limit=1)213 id_list.append(results['tracks']['items'][0]['id'])214 except IndexError:215 continue216 concat_list = [embed_string + k for k in id_list]217 try:218 components.iframe(concat_list[0], width=700, height=300)219 except IndexError:220 continue221 222 223 224st.markdown('**Feature Descriptions**')225st.markdown('**Danceability** describes how suitable a track is for dancing based '226 'on a combination of musical elements including tempo, rhythm stability, beat strength, and '227 'overall regularity. A value of 0.0 is least danceable and 1.0 is most danceable.')228st.markdown('**Energy** is a measure from 0.0 to 1.0 and represents a perceptual measure of intensity and '229 'activity. Typically, energetic tracks feel fast, loud, and noisy. For example, death metal '230 'has high energy, while a Bach prelude scores low on the scale. Perceptual features '231 'contributing to this attribute include dynamic range, perceived loudness, timbre, onset rate, '232 'and general entropy.')233st.markdown('**Acousticness** is a confidence measure from 0.0 to 1.0 of whether the track is acoustic. 1.0 '234 'represents high confidence the track is acoustic.')235st.markdown("**Instrumentalness** predicts whether a track contains no vocals. 'Ooh' and 'ahh' sounds are treated "236 "as instrumental in this context. Rap or spoken word tracks are clearly 'vocal'. The closer the "237 "instrumentalness value is to 1.0, the greater likelihood the track contains no vocal content. "238 "Values above 0.5 are intended to represent instrumental tracks, but confidence is higher as the "239 "value approaches 1.0.")240st.markdown('**Liveness** detects the presence of an audience in the recording. Higher liveness values represent '241 'an increased probability that the track was performed live. A value above 0.8 provides strong '242 'likelihood that the track is live.')243st.markdown('**Loudness** is the overall loudness of a track in decibels (dB). Loudness values are averaged '244 'across the entire track and are useful for comparing relative loudness of tracks. Loudness is '245 'the quality of a sound that is the primary psychological correlate of physical strength (amplitude). '246 'Values typical range between -60 and 0 db.')247st.markdown('**Speechiness** detects the presence of spoken words in a track. The more exclusively speech-like '248 'the recording (e.g. talk show, audio book, poetry), the closer to 1.0 the attribute value. Values '249 'above 0.66 describe tracks that are probably made entirely of spoken words. Values between 0.33 and '250 '0.66 describe tracks that may contain both music and speech, either in sections or layered, including '251 'such cases as rap music. Values below 0.33 most likely represent music and other non-speech-like tracks.')252st.markdown('**Tempo** is the overall estimated tempo of a track in beats per minute (BPM). In musical terminology, '253 'tempo is the speed or pace of a given piece and derives directly from the average beat duration.')254st.markdown('**Valence** is a measure from 0.0 to 1.0 describing the musical positiveness conveyed by a track. '255 'Tracks with high valence sound more positive (e.g. happy, cheerful, euphoric), while tracks with '256 'low valence sound more negative (e.g. sad, depressed, angry).')257st.write('Tip: After setting your parameters, try adjusting just one or two to see if your results are different. '258 'Energy has a surprisingly large effect on the results!')259 