Le but principal de ce projet est d'étudier les préférences musicales et la popularité des musiques Spotify (entre 1921 et 2020) en fonction de différentes variables (telles que la dansabilité de la musique, le mode, l'octave ...).
Ainsi, nous chercherons à:
Nous aborderons notre analyse en deux parties:
La plateforme Spotify est un service de musique (ou streaming musical) suédois créé en 2008. Il permet l'écoute quasi instantanée de fichiers musicaux en parcourant un catalogue par artiste ou par album.
Spotify est un service gratuit à condition d'accepter publicités et écoute seulement en ligne. Pour éviter cela, il faut acheter un abonnement premium. Différents abonnements existent moyennene entre 5 et 16 euros par mois: étudiant, solo, duo, famille.
Spotify a comme concurrents premiers Deezer, YouTube et Apple Music.
Notre choix s'est porté sur un jeu de données musical car nous sommes passionnés de musiques et avions envie de nous intéresser aux préférences musicales des individus au fil du temps.
Les trois jeux de données utilisés sont disponibles au lien suivant: https://www.kaggle.com/yamaerenay/spotify-dataset-19212020-160k-tracks.
Il s'agit des jeux de données "data.csv", "data_by_year.csv" et "data_by_genres.csv" tirés donc de la plateforme Kaggle.
Nous avions un jeu de données général, un trié par années, l'autre trié par genres.
# Import packages nécessaires
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import pylab as pl
import os
import seaborn as sns
import plotly.express as px
import pymannkendall as mk
import statsmodels.api as sm
from pylab import rcParams
%matplotlib inline
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA
from sklearn.metrics import euclidean_distances, mean_squared_error
from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestClassifier
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis as QDA
from sklearn.model_selection import cross_val_score, train_test_split, GridSearchCV
from scipy.spatial.distance import cdist
import plotly.offline as py
import plotly.graph_objs as go
import warnings
warnings.filterwarnings("ignore")
import itertools
import math
from collections import Counter
# Lecture des fichiers
df = pd.read_csv("data.csv")
df_year = pd.read_csv("data_by_year.csv")
df_genres = pd.read_csv("data_by_genres.csv")
# Aperçu d'une partie du jeu de données général
df.head()
df.shape
Nous avons ici un jeu de données général qui comporte 174 389 lignes (donc musiques) et 19 variables (une variable cible et 18 variables explicatives).
Certaines variables paraissent plus ou moins pertinentes (nous ferons le tri par la suite).
# Aperçu d'une partie du jeu de données "années"
df_year.head()
df_year.shape
On remarque ici que le jeu de données est trié par date.
Nous n'avons ici plus que 14 variables et 102 années différentes (de 1920 à 2021).
# Aperçu d'une partie du jeu de données "genres"
df_genres.head()
df_genres.shape
On remarque ici que le jeu de données est trié par genre.
Nous avons ici 3232 genres différents et 14 variables.
Nous allons donner un descriptif rapide de chacune.
df.describe()
Au niveau des caractéristiques descriptives, on remarque différentes choses.
75 % des musiques ont un "instrumentalness" inférieur à 0.25. La plupart des musiques n'ont ainsi pas beaucoup de contenu instrumental.
75 % des musiques ont un "liveness" inférieur à 0.27. La plupart des musiques n'ont ainsi pas été enregistrées en direct.
50 % des musiques datent d'avant 1977 et 75% datent d'avant 1999.
Nous faisons le choix de ne pas étudier toutes les variables car certaines nous semblent plus pertinentes. Nous restreignons notre étude aux variables suivantes:
acousticness
danceability
duration_ms
energy
instrumentalness
liveness
loudness
mode
popularity
speechiness
tempo
valence
year
Nous étudierons deux variables cibles: year et popularity.
# Nouveaux jeux de données
df_new = df[['acousticness','danceability','duration_ms','energy','instrumentalness','liveness','loudness','mode','popularity','speechiness','tempo','valence','year']]
df_new.head(10)
df_new_year = df_year[['acousticness','danceability','duration_ms','energy','instrumentalness','liveness','loudness','mode','popularity','speechiness','tempo','valence','year']]
Regardons la corrélation qu'il peut exister entre nos différentes variables et surtout entre nos variables explicatives et nos deux variables cibles.
df_new.corr()
plt.subplots(figsize=(12, 8))
sns.heatmap(df_new.corr(), annot=True, square=True)
plt.show()
En prenant comme variable cible popularity, nous remarquons que cette dernière est corrélée positivement avec year et négativement avec acousticness. C'est à dire que plus la musique est récente, plus elle est populaire (en sachant que la notion de popularité est prise en compte en 2021) et que plus la musique est acoustique, moins elle est populaire.
En prenant comme variable cible year, nous remarquons que cette dernière est corrélée négativement avec acousticness (logique car les musieurs récentes sont moins acoustiques) et corrélée positivement avec energy, loudness (et popularity). Ainsi, plus une musique est récente, plus elle est intense et forte (en db).
Concernant les autres variables explicatives, on remarque notamment que:
Nous représentons sur ce prochain graphique dynamique l'évolution de 6 variables en fonction du temps. Il permet de comparer plusieurs variables en même temps.
# Evolutions globales de différentes variables en fonction du temps
features = ['acousticness', 'danceability', 'energy', 'instrumentalness', 'liveness', 'valence']
fig = px.line(df_new_year, x='year', y=features)
fig.show()
Nous remarquons notamment que les variables acousticness et energy évoluent de manière inverse au fil des années. Les musiques sont de plus en plus énergiques et de moins en moins acoustiques.
Le niveau de dansabilité est globalement stable depuis 1920 tout comme le niveau de liveness et le niveau de valence.
Pour observer et appuyer nos analyses, nous allons utiliser le test de Mann-Kendall servant à déterminer à l'aide d'un test non paramétrique si une tendance est identifiable dans une série temporelle ou non. L'hypothèse nulle de ce test est qu'il n'y a pas de tendance.
mk.original_test(df_new_year['acousticness'])
mk.original_test(df_new_year.iloc[80:102,0])
Nous remarquons, pour la variable "acousticness", une tendance très claire: une tendance à la baisse. Entre 1920 et 1965 environ, toutes les musiques avaient une valeur d'acousticness supérieure à 0.6. Depuis, ces valeurs ne cessent de décroître avec une valeur moyenne près des 0.20 depuis 1980.
Cette tendance que nous pouvons voir est confirmée par le test de tendance de Mann-Kendall. Avec une p-valeur pour ce test extrêmement proche de 0, on peut en conclure qu'il y a bien une tendance à la baisse si on prend la totalité de nos données. Toutefois, nous avons refait ce test en prenant seulement les 20 dernières années et on voit alors cette fois qu'il n'y a pas de tendance puisque la p-valeur est supérieure à 0.05.
En effet, la tendance moderne n'est plus aux versions acoustiques et c'est ce que fait clairement apparaître ce graphique.
Autre chose intéressante, on remarque qu'en 2021, la note "d'acousticness" a doublé par rapport à 2020 quasiment. Peut-être que l'acoustique revient à la mode, à voir sur des prédictions sur les années futures.
mk.original_test(df_new_year['danceability'])
mk.original_test(df_new_year.iloc[0:40,1])
mk.original_test(df_new_year.iloc[80:102,1])
Là ici, nous voyons deux éléments intéressants. Entre 1920 et 1960, la dansabilité d'une musique était très inégale (beaucoup de pics). On peut globalement dire que ces musiques étaient dansantes avant 1940 et beaucoup moins dansantes entre 1940 et 1960.
Depuis 1960, la dansabilité des musiques ne cesse d'augmenter (tendance à la hausse). En effet, en 2021, nous retrouvons un niveau très haut (qui était d'ailleurs celui des années 30). Le niveau le plus faible de dansabilité se retrouvait en 1945.
Ces tendances sont confirmées par le test de Mann-Kendall puisqu'on remarque un décroissement les 40 premières années (p valeur inférieure à 5 pcts) et un acroissement les 20 dernières années (p valeur inférieure à 5 pcts).
mk.original_test(df_new_year['valence'])
Nous rappelons que la valence est le degré de bonheur que procure une musique.
Nous avons du mal à ressortir une tendance claire de ce graphique mais nous pouvons tout de même remarquer que les musiques des années 20-30 rendaient globalement plus heureux que celles des années 40-50. Le degré de bonheur des musiques récentes (depuis 2015) est similaire à celui des musiques des années 40-50, c'est le niveau le plus bas.
Ainsi, les musiques récentes sont globalement (hors années 40-50) plus tristes que les musiques anciennes.
Le test de Mann-Kendall montre cependant un décroissement global significatif.
# Transformation des ms en s
df_new_year['duration_ms'] = df_new_year['duration_ms']/1000
# Evolution de la durée en secondes des musiques en fonction du temps
fig = go.Figure()
fig.add_trace(go.Scatter(x=df_new_year['year'], y=df_new_year['duration_ms'], mode='markers + lines',name="duration_s",showlegend=True))
fig.update_layout(title='Evolution de la durée des musiques en fonction du temps',height=300)
fig.show()
mk.original_test(df_new_year['duration_ms'])
mk.original_test(df_new_year.iloc[90:102,2])
Nous avons tout d'abord transformé les ms en s pour que cela soit plus parlant pour nous.
Nous remarquons, de manière globale, une légère tendance à la hausse entre 1922 et 2015 et une légère baisse depuis 2015. On remarque aussi qu'entre 1980 et 2000 environ, les durées de musiques étaient à peu de choses près les mêmes.
En 1921, les musiques semblent être les moins longues (moins de 2mn30s) et les plus longues aux alentours des années 2010 (près de 5mn).
Cette croissance globale puis décroissance les dernières années sont confirmées par des tests de Mann-Kendall significatifs.
# Evolution de la popularité des musiques en fonction du temps
fig = go.Figure()
fig.add_trace(go.Scatter(x=df_new_year['year'], y=df_new_year['popularity'], mode='markers + lines',name="popularity",showlegend=True))
fig.update_layout(title='Evolution de la popularité des musiques en fonction du temps',height=300)
fig.show()
mk.original_test(df_new_year['popularity'])
mk.original_test(df_new_year.iloc[80:102,8])
Voici notre variable cible: la popularité (la popularité en 2021).
Nous remarquons, qu'en 2021, les musiques entre 1920 et 1955 ne sont absolument pas populaires ni écoutées. Entre 1955 et 2000, la popularité ne cesse d'augmenter au fil du temps. Depuis 2001, la popularité baisse en flèche pour atteindre en 2020 un niveau quasi similaire au début de la croissance de popularité début années 60.
Le niveau de popularité de 2021 est très faible mais à prendre avec précaution car l'année vient de commencer et donc nous n'avons pas assez de recul pour l'interpréter.
Les années les plus écoutées sur Spotify sont les années 1990-2000.
En effet, avec le test de Mann-Kendall, on voit apparaître une croissance signicative globale mais une décroissance les 20 dernières années. Les musiques modernes sont de moins en moins écoutées.
# Valeurs de popularité en fonction de différentes variables pour les 5 genres les plus présents
top5 = df_genres.nlargest(5, 'popularity')
fig = px.bar(top5, x='genres', y=['acousticness', 'danceability', 'speechiness', 'valence'], barmode='group')
fig.show()
Sur ce graphique sont représentés les 5 genres les plus présents dans notre jeu de données.
Pour chacun de ces genres est représentée la valeur de différentes variables.
Nous remarquons tout d'abord que le genre le plus présent est chinese electropop.
Les genres chinese electropop et yaoi ne sont pas du tout acoustiques. Aucun des genres les plus présents n'a de valeur importante pour speechiness.
Le dong-yo est un genre musical très dansant qui rend plutôt bien heureux. Le yaoi est moins dansant mais rend plus heureux.
artists = []
for i in df['artists']:
for j in i.split(", "):
artist = j.replace('[', '').replace(']', '')
if artist[0] == "'":
artist = artist[1:]
if artist[-1] == "'":
artist = artist[:-1]
artists.append(artist)
fig, ax = plt.subplots(1, 1, figsize=(15, 7))
count = pd.Series(Counter(artists)).sort_values(ascending=False)[:20]
bars = sns.barplot(count.keys(), count, palette='twilight')
for index in range(0, len(bars.patches), 2):
bar = bars.patches[index]
bars.annotate(format(bar.get_height(), '.0f'), (bar.get_x()+bar.get_width()/2.,
bar.get_height()), ha='center', va='bottom', size=8)
plt.title('Most common artists')
plt.xlabel('Artists')
plt.ylabel('Number of artists')
plt.xticks(rotation=90, size=8)
plt.show()
Nous remarquons que l'artiste le plus présent dans ce jeu de données est Francisco Canaro (tango). Ensuite, viennent les artistes Tadeusz Dolega Mostowicz, deux artistes coréens puis les compositeurs de musiques classiques (Chopin, Bach, Mozart, Beethoven).
Au niveau artistes, la musique classique semble devancer les autres genres musicaux dans ce jeu de données.
Nous cherchons dans cette partie à dégager des genres qui se ressemblent, qui ont des caractéristiques musicales communes.
Nous utilisons un algorithme de clustering K-means afin de diviser les genres de cet ensemble de données en dix groupes seulement.
cluster_pipeline = Pipeline([('scaler', StandardScaler()), ('kmeans', KMeans(n_clusters=10, n_jobs=-1))])
X = df_genres.select_dtypes(np.number)
cluster_pipeline.fit(X)
df_genres['cluster'] = cluster_pipeline.predict(X)
Nous réalisons ensuite une ACP en ne retenant que 2 composantes principales. Nous projetons ainsi sur le premier plan factoriel.
PCA_pipeline = Pipeline([('scaler', StandardScaler()), ('PCA', PCA(n_components=2))])
genre_embedding = PCA_pipeline.fit_transform(X)
projection = pd.DataFrame(columns=['x', 'y'], data=genre_embedding)
projection['genres'] = df_genres['genres']
projection['cluster'] = df_genres['cluster']
fig = px.scatter(
projection, x='x', y='y', color='cluster', hover_data=['x', 'y', 'genres'])
fig.show()
Nous voyons ici que les clusters sont, pour la plupart, globalement séparés.
On remarque notamment que le cluster jaune moutarde (8) regroupe les musiques rap/pop, (plutôt énergiques).
A l'opposé, on trouve le cluster violet (1) qui lui regroupe les musiques classiques ou opéras (plutôt lentes).
Il est plutôt difficile d'analyser les autres clusters.
Nous allons ainsi tenter d'utiliser une autre technique de réduction de dimension pour y voir plus clair: t-SNE.
Distributed Stochastic Neighbor Embedding(t-SNE) est une technique de réduction de dimension et est particulièrement bien adaptée à la visualisation d'ensembles de données de grande dimension. Contrairement à l'ACP, ce n'est pas une technique mathématique mais probabiliste.
tsne_pipeline = Pipeline([('scaler', StandardScaler()), ('tsne', TSNE(n_components=2, verbose=2))])
genre_embedding = tsne_pipeline.fit_transform(X)
projection = pd.DataFrame(columns=['x', 'y'], data=genre_embedding)
projection['genres'] = df_genres['genres']
projection['cluster'] = df_genres['cluster']
fig = px.scatter(
projection, x='x', y='y', color='cluster', hover_data=['x', 'y', 'genres'])
fig.show()
Nous voyons ici des clusters plus distincts.
Nous retrouvons le cluster 8 correspondant à la musique classique opposé opposé au cluster 5 où on trouve du rock ou de la pop principalement.
Près du cluster 8, on trouve le cluster 1 qui représente les musiques jazz. On retrouve une certaine logique avec le cluster 8 contenant la musique classique (principalement piano).
Le cluster 2 est proche du 5. C'est en effet logique car le cluster 2 contient les musiques métal qui ont par logique des caractéristiques similaires au rock.
Même chose avec le cluster 9, proche du 2, qui contient les musiques électroniques.
song_cluster_pipeline = Pipeline([('scaler', StandardScaler()),
('kmeans', KMeans(n_clusters=20,
verbose=2, n_jobs=4))],verbose=True)
X = df.select_dtypes(np.number)
number_cols = list(X.columns)
song_cluster_pipeline.fit(X)
song_cluster_labels = song_cluster_pipeline.predict(X)
df['cluster_label'] = song_cluster_labels
pca_pipeline = Pipeline([('scaler', StandardScaler()), ('PCA', PCA(n_components=2))])
song_embedding = pca_pipeline.fit_transform(X)
projection = pd.DataFrame(columns=['x', 'y'], data=song_embedding)
projection['title'] = df['name']
projection['cluster'] = df['cluster_label']
fig = px.scatter(
projection, x='x', y='y', color='cluster', hover_data=['x', 'y', 'title'])
fig.show()