-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrecommender.py
More file actions
151 lines (120 loc) · 5.26 KB
/
recommender.py
File metadata and controls
151 lines (120 loc) · 5.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import datetime
import numpy as np
import pandas as pd
from scipy.spatial.distance import cdist
import requests
from requests.auth import HTTPBasicAuth
class Recommender(object):
""" Spotify Recommender """
def __init__(self, dataset_path):
print("Recommender Class Instantiated")
self.client_id = "Spotify Client ID"
self.client_secret = "Spotify Client Secret"
self.dataset = pd.read_csv(dataset_path)
self.exist_in_dataset = False
self._api_token = ""
self._api_timestamp = 0
self.cols = [
'danceability', 'energy', 'key', 'loudness', 'mode', 'speechiness',
'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo',
'duration_ms', 'year', 'popularity'
]
def _getApiToken(self):
ctime = datetime.datetime.now()
# if API token has been issued over half an hour, request a new one
if ctime.timestamp() - self._api_timestamp >= 1800:
res_api = requests.post(
"https://accounts.spotify.com/api/token",
data={"grant_type": "client_credentials"},
auth=HTTPBasicAuth(self.client_id, self.client_secret)
)
if res_api.status_code == 200:
token_object = res_api.json()
token = token_object["access_token"]
ctime = datetime.datetime.now()
self._api_token = token
self._api_timestamp = ctime.timestamp()
else:
raise Exception('API Token Request Failed')
print('Current API Token: {}'.format(self._api_token))
print('API Issue Timestamp: {}'.format(self._api_timestamp))
return self._api_token
def findSong(self, id):
token = self._getApiToken()
url_song_features = "https://api.spotify.com/v1/audio-features/"
url_tracks = "https://api.spotify.com/v1/tracks/"
metadata_cols = [
'danceability', 'energy', 'key', 'loudness', 'mode', 'speechiness',
'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo',
'duration_ms'
]
res_song_features = requests.get(
url_song_features + id,
headers={"Authorization": "Bearer " + token}
)
res_tracks = requests.get(
url_tracks + id,
headers={"Authorization": "Bearer " + token}
)
json_song_features = res_song_features.json()
json_tracks = res_tracks.json()
features = {}
features.update(dict(((key, json_song_features[key]) for key in metadata_cols)))
features["year"] = int(json_tracks["album"]["release_date"][:4])
features["popularity"] = json_tracks["popularity"]
features["name_track"] = json_tracks["name"]
features["name_album"] = json_tracks["album"]["name"]
# Artist might be multiple
features["name_artist"] = json_tracks["artists"][0]['name']
return pd.DataFrame(features, index=[0])
def collectSong(self, id, dataset):
try:
data = dataset[dataset['id'] == id].iloc[0]
self.exist_in_dataset = True
return data
except IndexError:
data = self.findSong(id)
return data
def getVector(self, song_lst, dataset):
self.exist_in_dataset = False
vectors = []
for song in song_lst:
data = self.collectSong(song, dataset=dataset)
if data is None:
print('{} is not found.'.format(data['name']))
continue
vectors.append(data[self.cols].values)
tmp = np.mean(np.array(list(vectors)), axis=0)
if tmp.shape != (1, 14):
tmp = tmp.reshape(1, 14)
return pd.DataFrame(tmp, columns=self.cols)
def changeDictList(self, dict_list):
new = {}
for key in dict_list[0].keys():
new[key] = []
for dictionary in dict_list:
for key, value in dictionary.items():
new[key].append(value)
return new
def minmaxTransform(self, data, dataset):
for column in data[self.cols].columns:
data[column] = (data[column] - dataset[column].min()) / (dataset[column].max() - dataset[column].min())
return data[self.cols]
def recommend(self, song_lst, specific_year=None, number=10):
require_data = ['name', 'year', 'artists', 'id']
song_lst_mean = self.getVector(song_lst, dataset=self.dataset)
if specific_year is None:
data_transform = self.minmaxTransform(self.dataset[self.cols], self.dataset[self.cols])
else:
dataset = self.dataset[self.dataset['year'].isin(specific_year)]
df = dataset[self.cols]
data_transform = self.minmax_transform(df, df)
song_lst_mean = self.minmaxTransform(song_lst_mean, self.dataset[self.cols])
d = cdist(song_lst_mean, data_transform, 'cosine')
i = list(np.argsort(d)[
:, 1:number + 1][0]) if self.exist_in_dataset else list(np.argsort(d)[:, :number][0])
results = self.dataset.iloc[i]
return results[require_data].id.to_list()
if __name__ == "__main__":
rc = Recommender('data.csv')
print(rc.recommend(["5HCyWlXZPP0y6Gqq8TgA20"]))