Skip to content
HomeProjectsHighlightsBlogPlaygroundRésuméAbout

MusicMap

by Michael Rusu

I built an artist-discovery prototype that groups artists by their track features and ranks candidates within those groups. A Flask interface connects the results with available previews and Spotify track information.

Code

Music discovery through clustering
Unsupervised learning · Flask

Explore the project ↓
The problemMy contributionArchitectureArtist representationsSimilarity and discoveryResultsInterface

The problem

Finding a related artist requires a representation of musical similarity. Track-level attributes offer one way to explore that relationship: energy, acousticness, tempo, and other features can describe patterns across an artist’s catalog.

MusicMap turns those attributes into an artist-level discovery workflow. A listener supplies a known artist, and the application returns candidates drawn from the same feature cluster.

My contribution

I built the clustering workflow, Flask service, artist lookup, Spotify integration, and asynchronous web experience. I connected the feature pipeline to an interface where recommendations could be inspected through track information and available previews.

Architecture

Track features are cleaned and averaged by artist, standardized for K-Means, ranked within a selected cluster, and returned with available track information.
Figure 1. Track attributes become an artist-discovery response. PNGEditable Excalidraw Download figure

The server constructs the clusterer when it starts. It loads the dataset, builds artist representations, and fits K-Means. A request to the prediction endpoint lowercases the artist name, retrieves candidates, and assembles the interface response.

The pinned server calls Spotify’s HTTP endpoints with the Requests library. It obtains a client-credentials token, searches for tracks by artist, and selects one of the returned tracks. Dataset preview URLs are handled through a separate lookup.

Artist representations

The preprocessing drops rows with missing required features and normalizes artist names to lowercase. It label-encodes playlist genre, groups tracks by artist, and averages the selected numeric fields. The encoded genre value is included in that aggregation.

StandardScaler then transforms the artist vectors for K-Means. The number of observed playlist genres determines the number of clusters, with a fixed random seed of 42. This produces a cluster assignment for each artist in the dataset.

Python · preprocessing and clusteringSource ↗
def load_and_preprocess_data(self):
    df = pd.read_csv(self.data_path)
    df.dropna(subset=self.feature_columns, inplace=True)
    df = df[self.feature_columns]

    df['track_artist'] = df['track_artist'].apply(lambda x: str(x).lower())
    df["playlist_genre"] = self.encoder.fit_transform(df["playlist_genre"])
    self.num_genres = df["playlist_genre"].nunique()
    self.artist_features = df.groupby('track_artist').mean()
    self.scaled_features = self.scaler.fit_transform(self.artist_features)

def fit_clusters(self):
    self.kmeans = KMeans(n_clusters=self.num_genres, random_state=self.random_state) 
    self.cluster_labels = self.kmeans.fit_predict(self.scaled_features)
    return self.cluster_labels
Five fixed two-dimensional artist vectors illustrate cluster membership and three within-cluster similarity scores.
Figure 2. Candidate membership and relative similarity scores. PNGEditable Excalidraw Download figure

The figure uses fixed two-dimensional vectors and illustrative memberships to expose the ranking calculation. The original project also uses t-SNE to project the standardized artist features into two dimensions for its cluster visualization.

Similarity and discovery

The lookup transforms the query artist for cluster prediction, selects artists with the same cluster label, and removes the query itself. It then computes cosine similarity using the original averaged feature vectors.

Within that candidate set, min-max scaling maps the smallest similarity to zero and the largest to one. Sorting those relative scores produces the recommendation order. The request handler returns the first five candidate names.

Python · candidate rankingSource ↗
def calculate_artists_probability(self, cluster_index):
    print("cluster #: ", cluster_index)

    if not self.cluster_artists:
        print("No similar artists found in the cluster")
        return None

    cluster_features = self.artist_features.loc[self.cluster_artists].values
    input_features = self.artist_features.loc[self.input_artist].values.reshape(1, -1)
    similarities = cosine_similarity(input_features, cluster_features)[0]
    probabilities = (similarities - np.min(similarities)) / (np.max(similarities) - np.min(similarities)) if len(similarities) > 1 else [1.0]
    artist_probabilities = dict(zip(self.cluster_artists, probabilities))
    sorted_artists = sorted(artist_probabilities.items(), key=lambda x: x[1], reverse=True)
    return sorted_artists
Artist lookup returns ranked candidates, optional dataset preview URLs, and Spotify track IDs and names for interface cards.
Figure 3. Recommendations are enriched with available track data. PNGEditable Excalidraw Download figure

A single candidate receives a score of one. Multiple candidates with identical similarities reach a zero denominator in the source formula, producing nonfinite scores. The offline fixtures preserve this observed edge case in the source records.

Preview availability depends on the dataset and external track data. The interface can still receive artist names and track identifiers when a preview URL is unavailable.

Results

MusicMap produced an artist-discovery prototype that connects an unsupervised model to browsable track cards. The saved screenshot shows the interface’s query artist and similar-artist sections.

The case-study fixtures exercise candidate exclusion, ranking, unknown artists, singleton clusters, equal similarities, and missing previews. Spotify and media playback remain outside the page’s execution path.

Interface

Archived MusicMap interface showing an AC/DC query and Green Day and Linkin Park track cards.
The original MusicMap discovery interface.
Explore the MusicMap source ↗

with by michael