INSA

Projet Open Data: Spotify

Objectifs

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 à:

  • Etudier l'évolution de différentes caractéristiques musicales au fil du temps (1920-2021)
  • Mettre en évidence un lien entre une variable dite de popularité et des caractéristiques musicales
  • Réduire notre jeu de données afin de regrouper des genres de musiques ayant des caractéristiques musicales communes
  • Mettre en place une méthode de prédiction pertinente afin d'être capable de prédire année et popularité le mieux possible
  • Etre capable de prédire dans les années à venir l'évolution de certaines caractéristiques musicales

Plan

Nous aborderons notre analyse en deux parties:

  • Introduction: présentation de la plateforme Spotify
  • Analyse descriptive: présentation des jeux de données, tendances des variables et tests de Mann-Kendall, clustering et ACP vs TSNE
  • Prédiction: comportements de variables dans les années à venir (séries chronologiques) et prédictions popularité/année
  • Conclusion et Intérêts

Introduction

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.

PARTIE 1: Analyse descriptive

Présentation des jeux de données

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.

In [14]:
# 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
In [15]:
# 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")
In [16]:
# Aperçu d'une partie du jeu de données général
df.head()
Out[16]:
acousticness artists danceability duration_ms energy explicit id instrumentalness key liveness loudness mode name popularity release_date speechiness tempo valence year
0 0.991000 ['Mamie Smith'] 0.598 168333 0.224 0 0cS0A1fUEUd1EW3FcF8AEI 0.000522 5 0.3790 -12.628 0 Keep A Song In Your Soul 12 1920 0.0936 149.976 0.6340 1920
1 0.643000 ["Screamin' Jay Hawkins"] 0.852 150200 0.517 0 0hbkKFIJm7Z05H8Zl9w30f 0.026400 5 0.0809 -7.261 0 I Put A Spell On You 7 1920-01-05 0.0534 86.889 0.9500 1920
2 0.993000 ['Mamie Smith'] 0.647 163827 0.186 0 11m7laMUgmOKqI3oYzuhne 0.000018 0 0.5190 -12.098 1 Golfing Papa 4 1920 0.1740 97.600 0.6890 1920
3 0.000173 ['Oscar Velazquez'] 0.730 422087 0.798 0 19Lc5SfJJ5O1oaxY0fpwfh 0.801000 2 0.1280 -7.311 1 True House Music - Xavier Santos & Carlos Gomi... 17 1920-01-01 0.0425 127.997 0.0422 1920
4 0.295000 ['Mixe'] 0.704 165224 0.707 1 2hJjbsLCytGsnAHfdsLejp 0.000246 10 0.4020 -6.036 0 Xuniverxe 2 1920-10-01 0.0768 122.076 0.2990 1920
In [17]:
df.shape
Out[17]:
(174389, 19)

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).

In [18]:
# Aperçu d'une partie du jeu de données "années"
df_year.head()
Out[18]:
year acousticness danceability duration_ms energy instrumentalness liveness loudness speechiness tempo valence popularity key mode
0 1920 0.631242 0.515750 238092.997135 0.418700 0.354219 0.216049 -12.654020 0.082984 113.226900 0.498210 0.610315 2 1
1 1921 0.862105 0.432171 257891.762821 0.241136 0.337158 0.205219 -16.811660 0.078952 102.425397 0.378276 0.391026 2 1
2 1922 0.828934 0.575620 140135.140496 0.226173 0.254776 0.256662 -20.840083 0.464368 100.033149 0.571190 0.090909 5 1
3 1923 0.957247 0.577341 177942.362162 0.262406 0.371733 0.227462 -14.129211 0.093949 114.010730 0.625492 5.205405 0 1
4 1924 0.940200 0.549894 191046.707627 0.344347 0.581701 0.235219 -14.231343 0.092089 120.689572 0.663725 0.661017 10 1
In [19]:
df_year.shape
Out[19]:
(102, 14)

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).

In [20]:
# Aperçu d'une partie du jeu de données "genres"
df_genres.head()
Out[20]:
genres acousticness danceability duration_ms energy instrumentalness liveness loudness speechiness tempo valence popularity key mode
0 21st century classical 0.754600 0.284100 3.525932e+05 0.159580 0.484374 0.168580 -22.153400 0.062060 91.351000 0.143380 6.600000 4 1
1 432hz 0.485515 0.312000 1.047430e+06 0.391678 0.477250 0.265940 -18.131267 0.071717 118.900933 0.236483 41.200000 11 1
2 8-bit 0.028900 0.673000 1.334540e+05 0.950000 0.630000 0.069000 -7.899000 0.292000 192.816000 0.997000 0.000000 5 1
3 [] 0.535793 0.546937 2.495312e+05 0.485430 0.278442 0.220970 -11.624754 0.101511 116.068980 0.486361 12.350770 7 1
4 a cappella 0.694276 0.516172 2.018391e+05 0.330533 0.036080 0.222983 -12.656547 0.083627 105.506031 0.454077 39.086248 7 1
In [21]:
df_genres.shape
Out[21]:
(3232, 14)

On remarque ici que le jeu de données est trié par genre.

Nous avons ici 3232 genres différents et 14 variables.

Description des différentes variables

Nous allons donner un descriptif rapide de chacune.

  • acousticness représente une mesure de confiance (entre 0 et 1) indiquant si la piste est acoustique (sans instrument électrique moderne). 1 indique que la piste est très acoustique.
  • artists représente le nom de l'artiste.
  • danceability représente une mesure de confiance (entre 0 et 1) indiquant à quel point une piste est dansante. Cette mesure est basée sur une combinaison d'éléments musicaux (tempo, stabilité du rythme, force du battement, régularité générale). 1 indique que la piste est très dansante.
  • duration_ms représente la durée de la piste en millisecondes.
  • energy représente une mesure de confiance (entre 0 et 1) indiquant l'intensité/activité de la piste. 1 est une piste très intense (rapide, bruyante...).
  • explicit représente une mesure binaire indiquant si un contenu explicite est présent dans la piste ou non.
  • id représente l'identifiant de la piste, généré par la plateforme Spotify.
  • instrumentalness permet de prédire si une piste possède un contenu vocal ou non. Les son "oh" et "ah" sont traités comme de l'instrumental. Plus la valeur de cette variable est proche de 1, moins la piste est susceptible de contenir de la voix. Globalement, les valeurs supérieures à 0.5 indiquent une piste instrumentale.
  • liveness permet de prédire si la piste enregistrée a été jouée en direct ou non (devant un public). Plus la valeur est proche de 1, plus il y a de chances que la piste ait été enregistrée en direct.
  • loudness représente le volume relatif de la piste en décibels.
  • mode permet de savoir si la piste est en mode majeur ou mineur.
  • name représente le nom de la piste.
  • popularity est une mesure entre 0 et 100 indiquant à quel point une piste est populaire (écoutée). 100 étant une piste extrêmement populaire. Il s'agit d'un classement basé sur le nombre de lectures de la piste et la date de ces lectures.
  • release_date est la date de publication de la piste.
  • speechiness permet de détecter à quel point des mots sont présents dans la piste (VS instrumentalness). Plus l'enregistrement est vocal, plus la valeur sera proche de 1. On dira que les valeurs supérieures à 0.66 décrivent des pistes probablement seulement constituée de mots prononcés.
  • tempo représente le tempo global estimé d'un morceau en battements par minute.
  • valence représente une mesure de confiance (entre 0 et 1) indiquant la positivité musicale véhiculée par la piste. Plus la valence de la piste est élevée, plus la musique semble positive (joyeuse, gaie...). Une mesure de 1 indiquera ainsi une positivité musicale maximale.
  • year est l'année de publication de la piste.

Caractéristiques descriptives du jeu de données général

In [22]:
df.describe()
Out[22]:
acousticness danceability duration_ms energy explicit instrumentalness key liveness loudness mode popularity speechiness tempo valence year
count 174389.000000 174389.000000 1.743890e+05 174389.000000 174389.000000 174389.000000 174389.000000 174389.000000 174389.000000 174389.000000 174389.000000 174389.000000 174389.000000 174389.000000 174389.000000
mean 0.499228 0.536758 2.328100e+05 0.482721 0.068135 0.197252 5.205305 0.211123 -11.750865 0.702384 25.693381 0.105729 117.006500 0.524533 1977.061764
std 0.379936 0.176025 1.483958e+05 0.272685 0.251978 0.334574 3.518292 0.180493 5.691591 0.457211 21.872740 0.182260 30.254178 0.264477 26.907950
min 0.000000 0.000000 4.937000e+03 0.000000 0.000000 0.000000 0.000000 0.000000 -60.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1920.000000
25% 0.087700 0.414000 1.661330e+05 0.249000 0.000000 0.000000 2.000000 0.099200 -14.908000 0.000000 1.000000 0.035200 93.931000 0.311000 1955.000000
50% 0.517000 0.548000 2.057870e+05 0.465000 0.000000 0.000524 5.000000 0.138000 -10.836000 1.000000 25.000000 0.045500 115.816000 0.536000 1977.000000
75% 0.895000 0.669000 2.657200e+05 0.711000 0.000000 0.252000 8.000000 0.270000 -7.499000 1.000000 42.000000 0.076300 135.011000 0.743000 1999.000000
max 0.996000 0.988000 5.338302e+06 1.000000 1.000000 1.000000 11.000000 1.000000 3.855000 1.000000 100.000000 0.971000 243.507000 1.000000 2021.000000

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.

Restriction aux variables pertinentes

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.

In [23]:
# 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']]

Caractéristiques descriptives du jeu de données général après délimitation de l'étude

Regardons la corrélation qu'il peut exister entre nos différentes variables et surtout entre nos variables explicatives et nos deux variables cibles.

In [24]:
df_new.corr()
Out[24]:
acousticness danceability duration_ms energy instrumentalness liveness loudness mode popularity speechiness tempo valence year
acousticness 1.000000 -0.263217 -0.089169 -0.750852 0.221956 -0.029654 -0.546639 0.064633 -0.396744 -0.022437 -0.223840 -0.166968 -0.607515
danceability -0.263217 1.000000 -0.100757 0.204838 -0.215589 -0.110033 0.249541 -0.048358 0.123746 0.239962 0.005479 0.536713 0.159095
duration_ms -0.089169 -0.100757 1.000000 0.060516 0.103621 0.028942 0.019791 -0.046849 0.024717 -0.097838 -0.008182 -0.183199 0.105661
energy -0.750852 0.204838 0.060516 1.000000 -0.177750 0.134815 0.779267 -0.056160 0.328939 -0.112616 0.266448 0.326418 0.540850
instrumentalness 0.221956 -0.215589 0.103621 -0.177750 1.000000 -0.047941 -0.317562 -0.056731 -0.300625 -0.133966 -0.068656 -0.219188 -0.114259
liveness -0.029654 -0.110033 0.028942 0.134815 -0.047941 1.000000 0.062695 0.001677 -0.078959 0.122034 0.008586 -0.005781 -0.011852
loudness -0.546639 0.249541 0.019791 0.779267 -0.317562 0.062695 1.000000 -0.019250 0.337194 -0.213504 0.217914 0.302520 0.465189
mode 0.064633 -0.048358 -0.046849 -0.056160 -0.056731 0.001677 -0.019250 1.000000 0.007652 -0.040711 0.002438 0.021592 -0.048922
popularity -0.396744 0.123746 0.024717 0.328939 -0.300625 -0.078959 0.337194 0.007652 1.000000 -0.195329 0.094985 0.063471 0.513227
speechiness -0.022437 0.239962 -0.097838 -0.112616 -0.133966 0.122034 -0.213504 -0.040711 -0.195329 1.000000 -0.033530 0.050600 -0.215630
tempo -0.223840 0.005479 -0.008182 0.266448 -0.068656 0.008586 0.217914 0.002438 0.094985 -0.033530 1.000000 0.163118 0.161729
valence -0.166968 0.536713 -0.183199 0.326418 -0.219188 -0.005781 0.302520 0.021592 0.063471 0.050600 0.163118 1.000000 -0.049578
year -0.607515 0.159095 0.105661 0.540850 -0.114259 -0.011852 0.465189 -0.048922 0.513227 -0.215630 0.161729 -0.049578 1.000000
In [25]:
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:

  • acousticness et energy sont corrélées négativement. Plus une piste est acoustique, moins elle est énergique.
  • energy et loudness sont corrélées positivement. Plus une piste est énergique, plus elle est forte.
  • valence et danceability sont corrélées positivement. Plus une piste est "dansante", plus elle procure du plaisir.

Evolution des tendances des variables au fil du temps/ Utilisation du test de Mann-Kendall

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.

In [26]:
# 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.

In [27]:
mk.original_test(df_new_year['acousticness'])
Out[27]:
Mann_Kendall_Test(trend='decreasing', h=True, p=0.0, z=-11.36304919996874, Tau=-0.763152785866822, s=-3931.0, var_s=119617.66666666667, slope=-0.008001401258480756, intercept=0.862411096588602)
In [28]:
mk.original_test(df_new_year.iloc[80:102,0])
Out[28]:
Mann_Kendall_Test(trend='no trend', h=False, p=0.055179616323168146, z=-1.9174592253113267, Tau=-0.2987012987012987, s=-69.0, var_s=1257.6666666666667, slope=-0.0027285885383981226, intercept=0.2789558722913319)

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.

In [29]:
mk.original_test(df_new_year['danceability'])
Out[29]:
Mann_Kendall_Test(trend='increasing', h=True, p=1.1857382853364129e-08, z=5.701764127821464, Tau=0.38303242088914774, s=1973.0, var_s=119617.66666666667, slope=0.0011786978023203708, intercept=0.48179098542634846)
In [30]:
mk.original_test(df_new_year.iloc[0:40,1])
Out[30]:
Mann_Kendall_Test(trend='decreasing', h=True, p=5.829532079704158e-05, z=-4.0196069234446945, Tau=-0.44358974358974357, s=-346.0, var_s=7366.666666666667, slope=-0.003246622250753502, intercept=0.5780121055229314)
In [31]:
mk.original_test(df_new_year.iloc[80:102,1])
Out[31]:
Mann_Kendall_Test(trend='increasing', h=True, p=0.02076499853542102, z=2.312230242287188, Tau=0.3593073593073593, s=83.0, var_s=1257.6666666666667, slope=0.0017856205289654245, intercept=0.5599584062229275)

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).

In [32]:
mk.original_test(df_new_year['valence'])
Out[32]:
Mann_Kendall_Test(trend='decreasing', h=True, p=0.006456250914354422, z=-2.7236621746489957, Tau=-0.18307124830130073, s=-943.0, var_s=119617.66666666667, slope=-0.0005923873447754641, intercept=0.5716998095492933)

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.

In [33]:
# Transformation des ms en s
df_new_year['duration_ms'] = df_new_year['duration_ms']/1000
In [34]:
# 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()
In [35]:
mk.original_test(df_new_year['duration_ms'])
Out[35]:
Mann_Kendall_Test(trend='increasing', h=True, p=1.2643843749771122e-09, z=6.071858351128334, Tau=0.40788196466705495, s=2101.0, var_s=119617.66666666667, slope=0.5975693022382497, intercept=208.12314374979425)
In [36]:
mk.original_test(df_new_year.iloc[90:102,2])
Out[36]:
Mann_Kendall_Test(trend='decreasing', h=True, p=0.0007792699724176178, z=-3.360054858486334, Tau=-0.7575757575757576, s=-50.0, var_s=212.66666666666666, slope=-5.811227678390712, intercept=288.6081726700702)

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.

In [37]:
# 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()
In [38]:
mk.original_test(df_new_year['popularity'])
Out[38]:
Mann_Kendall_Test(trend='increasing', h=True, p=0.0, z=9.795931473153713, Tau=0.6579304989322462, s=3389.0, var_s=119617.66666666667, slope=0.5163634058249441, intercept=3.08381545928221)
In [39]:
mk.original_test(df_new_year.iloc[80:102,8])
Out[39]:
Mann_Kendall_Test(trend='decreasing', h=True, p=1.637754825978277e-06, z=-4.793648063278317, Tau=-0.7402597402597403, s=-171.0, var_s=1257.6666666666667, slope=-1.2594616771944784, intercept=45.82941769144478)

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.

In [40]:
# 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.

In [41]:
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.

Clustering sur les genres

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.

In [42]:
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.

In [43]:
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.

In [44]:
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()
[t-SNE] Computing 91 nearest neighbors...
[t-SNE] Indexed 3232 samples in 0.006s...
[t-SNE] Computed neighbors for 3232 samples in 0.334s...
[t-SNE] Computed conditional probabilities for sample 1000 / 3232
[t-SNE] Computed conditional probabilities for sample 2000 / 3232
[t-SNE] Computed conditional probabilities for sample 3000 / 3232
[t-SNE] Computed conditional probabilities for sample 3232 / 3232
[t-SNE] Mean sigma: 0.789973
[t-SNE] Computed conditional probabilities in 0.104s
[t-SNE] Iteration 50: error = 82.7771988, gradient norm = 0.0276140 (50 iterations in 0.909s)
[t-SNE] Iteration 100: error = 76.4241028, gradient norm = 0.0051018 (50 iterations in 0.898s)
[t-SNE] Iteration 150: error = 76.2318115, gradient norm = 0.0014055 (50 iterations in 1.006s)
[t-SNE] Iteration 200: error = 76.2014618, gradient norm = 0.0005793 (50 iterations in 0.861s)
[t-SNE] Iteration 250: error = 76.1927032, gradient norm = 0.0002143 (50 iterations in 0.935s)
[t-SNE] KL divergence after 250 iterations with early exaggeration: 76.192703
[t-SNE] Iteration 300: error = 1.8918065, gradient norm = 0.0010421 (50 iterations in 0.925s)
[t-SNE] Iteration 350: error = 1.6416910, gradient norm = 0.0003934 (50 iterations in 0.963s)
[t-SNE] Iteration 400: error = 1.5466586, gradient norm = 0.0002195 (50 iterations in 0.890s)
[t-SNE] Iteration 450: error = 1.4998701, gradient norm = 0.0001521 (50 iterations in 0.781s)
[t-SNE] Iteration 500: error = 1.4735575, gradient norm = 0.0001244 (50 iterations in 0.796s)
[t-SNE] Iteration 550: error = 1.4578538, gradient norm = 0.0001106 (50 iterations in 0.803s)
[t-SNE] Iteration 600: error = 1.4488715, gradient norm = 0.0001125 (50 iterations in 0.801s)
[t-SNE] Iteration 650: error = 1.4413568, gradient norm = 0.0000998 (50 iterations in 0.779s)
[t-SNE] Iteration 700: error = 1.4362098, gradient norm = 0.0000909 (50 iterations in 0.780s)
[t-SNE] Iteration 750: error = 1.4316840, gradient norm = 0.0000893 (50 iterations in 0.790s)
[t-SNE] Iteration 800: error = 1.4281582, gradient norm = 0.0000787 (50 iterations in 0.789s)
[t-SNE] Iteration 850: error = 1.4250349, gradient norm = 0.0000685 (50 iterations in 0.797s)
[t-SNE] Iteration 900: error = 1.4219530, gradient norm = 0.0000645 (50 iterations in 0.807s)
[t-SNE] Iteration 950: error = 1.4195485, gradient norm = 0.0000618 (50 iterations in 0.840s)
[t-SNE] Iteration 1000: error = 1.4169645, gradient norm = 0.0000624 (50 iterations in 0.780s)
[t-SNE] KL divergence after 1000 iterations: 1.416965

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.

Clustering sur les musiques

In [ ]:
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
In [46]:
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()

Il est très difficile d'interpréter ce clustering au vu du nombre de chansons mais on remarque cependant 20 clusters distinguables.

Le cluster 18 par exemple regroupe apparemment toutes les musiques récentes, remixées par un DJ et dansantes.

Le cluster 19 semble lui regrouper toutes les musiques coréennes.

Le cluster 12 regroupe lui la musique classique. Ce cluster est éloigné du cluster 18, on le retrouve par logique.

Partie 2: Prédictions

Prédictions sur la variable "year"

In [210]:
# Découpage apprentissage-test
X = df_new.drop(columns=["year"])
y = df_new['year']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
In [211]:
# Fonction erreur quadratique moyenne
def rmse(predictions, targets):
    return np.sqrt(((predictions - targets) ** 2).mean())
In [ ]:
# Recherche paramètres optimaux de l'arbre de décision
n_features = X.shape[1]
n_samples = X.shape[0]
 
grid = GridSearchCV(DecisionTreeRegressor(random_state=0), cv=3, n_jobs=-1, verbose=5,
                    param_grid ={
                    'max_depth': [None,5,6,7,8,9,10,11],
                    'max_features': [None, 'sqrt', 'auto', 'log2', 0.3,0.5,0.7, n_features//2, n_features//3, ],
                    'min_samples_split': [2,0.3,0.5, n_samples//2, n_samples//3, n_samples//5],
                    'min_samples_leaf':[1, 0.3,0.5, n_samples//2, n_samples//3, n_samples//5]},)
grid.fit(X_train, y_train)
print('Train R^2 Score : %.3f'%grid.best_estimator_.score(X_train, y_train))
print('Test R^2 Score : %.3f'%grid.best_estimator_.score(X_test,y_test))
print('Best R^2 Score Through Grid Search : %.3f'%grid.best_score_)
print('Best Parameters : ',grid.best_params_)
In [213]:
err0, err1, err2, err3, err4 = [],[],[],[],[]
reg = LinearRegression()
add = DecisionTreeRegressor(max_depth=9, max_features=None, min_samples_leaf=1, min_samples_split=2)
faa = RandomForestClassifier(n_estimators=10,criterion='gini')
lda = LDA()
qda = QDA()
rid = Ridge(alpha=400,solver='cholesky')
las = Lasso(alpha = 0.05)
ela = ElasticNet(l1_ratio=0.8,alpha=0.05)
for b in range(0,10):
    xtrain, xtest, ytrain, ytest = train_test_split(X, y, test_size=0.3)
    pred = reg.fit(xtrain, ytrain).predict(xtest)
    err0.append(rmse(pred,ytest))
    pred = add.fit(xtrain, ytrain).predict(xtest)
    err1.append(rmse(pred,ytest))
    pred = faa.fit(xtrain, ytrain).predict(xtest)
    err2.append(rmse(pred,ytest))
    pred = lda.fit(xtrain, ytrain).predict(xtest)
    err3.append(rmse(pred,ytest))
    pred = qda.fit(xtrain, ytrain).predict(xtest)
    err4.append(rmse(pred,ytest))
plt.boxplot([err0,err1,err2,err3,err4], labels=('reg','add','faa','lda','qda'))
plt.ylim(14,22)
plt.title("Erreur quadratique moyenne")
plt.show()
In [214]:
pred = add.fit(xtrain, ytrain).predict(xtest)
pred = np.around(pred)
val = np.asarray(ytest)
print(pred)
print(val)
[1987. 2009. 1937. ... 1969. 1974. 2012.]
[2020 1981 1925 ... 1954 1985 1954]

Nous remarquons dans cette première partie que la méthode semblant la meilleure est l'arbre de décision optimal. Les autres méthodes ont des résultats sensiblement similaires, excepté pour les analyses discriminantes qui ont de bien plus mauvais résultats.

Nous retenons ainsi l'arbre optimal pour la prédiction.

Prédictions sur la variable "popularity"

In [215]:
X = df_new.drop(columns=["popularity"])
y = df_new['popularity']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
In [ ]:
# Recherche paramètres optimaux de l'arbre de décision
n_features = X.shape[1]
n_samples = X.shape[0]
 
grid = GridSearchCV(DecisionTreeRegressor(random_state=0), cv=3, n_jobs=-1, verbose=5,
                    param_grid ={
                    'max_depth': [None,5,6,7,8,9,10,11],
                    'max_features': [None, 'sqrt', 'auto', 'log2', 0.3,0.5,0.7, n_features//2, n_features//3, ],
                    'min_samples_split': [2,0.3,0.5, n_samples//2, n_samples//3, n_samples//5],
                    'min_samples_leaf':[1, 0.3,0.5, n_samples//2, n_samples//3, n_samples//5]},)
grid.fit(X_train, y_train)
print('Train R^2 Score : %.3f'%grid.best_estimator_.score(X_train, y_train))
print('Test R^2 Score : %.3f'%grid.best_estimator_.score(X_test,y_test))
print('Best R^2 Score Through Grid Search : %.3f'%grid.best_score_)
print('Best Parameters : ',grid.best_params_)
In [217]:
err0, err1, err2 = [],[],[]
reg = LinearRegression()
add = DecisionTreeRegressor(max_depth=11, max_features=None, min_samples_leaf=1, min_samples_split=2)
faa = RandomForestClassifier(n_estimators=10,criterion='gini')
lda = LDA()
for b in range(0,10):
    xtrain, xtest, ytrain, ytest = train_test_split(X, y, test_size=0.3)
    pred = reg.fit(xtrain, ytrain).predict(xtest)
    err0.append(rmse(pred,ytest))
    pred = add.fit(xtrain, ytrain).predict(xtest)
    err1.append(rmse(pred,ytest))
    pred = faa.fit(xtrain, ytrain).predict(xtest)
    err2.append(rmse(pred,ytest))
plt.boxplot([err0,err1,err2], labels=('reg','add','faa'))
plt.ylim(13,20)
plt.title("Erreur quadratique moyenne")
plt.show()
In [218]:
pred = add.fit(xtrain, ytrain).predict(xtest)
pred = np.around(pred)
val = np.asarray(ytest)
print(pred)
print(val)
[ 4.  0.  8. ... 47. 44. 27.]
[ 0  0  0 ... 43 36 18]

Ici, comme auparavant, c'est l'arbre de décision optimal qui semble le meilleur. Les autres méthodes semblent relativement avoir les mêmes résultats, excepté l'analyse discriminante linéaire qui a véritablement de très mauvais résultats.

On gardera alors l'arbre de décision optimal comme méthode de prédiction.

Modèles ARIMA

Séries non stationnaires

Exemple avec la variable Acousticness
Nous avons effectué le même processus pour chaque variable.

In [219]:
df = df_new_year[['acousticness','year']].set_index('year')
df.index = pd.to_datetime(df_acoustic.index, format="%Y")
rolling_mean = df.rolling(window = 12).mean()
rolling_std = df.rolling(window = 12).std()
plt.plot(df, color = 'blue', label = 'Origine')
plt.plot(rolling_mean, color = 'red', label = 'Moyenne mobile')
plt.plot(rolling_std, color = 'black', label = 'Ecart-type mobile')
plt.legend(loc = 'best')
plt.title('Moyenne et Ecart-type mobiles')
plt.show()

En observant la moyenne mobile et l'écart-type de la série, on voit bien qu'elle est non stationnaire.
Par la suite, on va chercher grâce au critère AIC le meilleur modèle ARIMA pour faire nos prédictions.

In [ ]:
# Test avec le critère AIC pour connaître le modèle
p = d = q = range(0, 2)
pdq = list(itertools.product(p, d, q))
seasonal_pdq = [(x[0], x[1], x[2], 12) for x in list(itertools.product(p, d, q))]
for param in pdq:
   for param_seasonal in seasonal_pdq:
       try:
           mod = sm.tsa.statespace.SARIMAX(df['acousticness'],
                                           order=param,
                                           seasonal_order=param_seasonal,
                                           enforce_stationarity=False,
                                           enforce_invertibility=False)
           results = mod.fit()
           print('ARIMA{}x{}12 - AIC:{}'.format(param, param_seasonal, results.aic))
       except:
           continue

Le meilleur modèle est donc le modèle ARIMA(1, 1, 0)x(0, 0, 0, 12), puisqu'il s'agit de celui avec le plus petit AIC.

In [221]:
mdl = sm.tsa.statespace.SARIMAX(df['acousticness'],order=(1, 1, 0),seasonal_order=(0, 0, 0, 12),enforce_stationarity=True,enforce_invertibility=True)
res = mdl.fit()
print(res.summary())
                               SARIMAX Results                                
==============================================================================
Dep. Variable:           acousticness   No. Observations:                  102
Model:               SARIMAX(1, 1, 0)   Log Likelihood                 112.319
Date:                Tue, 23 Feb 2021   AIC                           -220.637
Time:                        21:20:28   BIC                           -215.407
Sample:                    01-01-1920   HQIC                          -218.520
                         - 01-01-2021                                         
Covariance Type:                  opg                                         
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
ar.L1         -0.4531      0.058     -7.824      0.000      -0.567      -0.340
sigma2         0.0063      0.001     12.410      0.000       0.005       0.007
===================================================================================
Ljung-Box (Q):                       53.82   Jarque-Bera (JB):                79.60
Prob(Q):                              0.07   Prob(JB):                         0.00
Heteroskedasticity (H):               0.08   Skew:                            -0.77
Prob(H) (two-sided):                  0.00   Kurtosis:                         7.06
===================================================================================

Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).
In [222]:
res.plot_diagnostics(figsize=(16, 10))
plt.tight_layout()
plt.show()
In [223]:
w = pd.DataFrame(df['acousticness'])
res = sm.tsa.statespace.SARIMAX(w,
                                order=(1, 0, 1),
                                seasonal_order=(0, 0, 0, 12),
                                enforce_stationarity=False,
                                enforce_invertibility=False).fit()
pred = res.get_prediction(start = 30, 
                          end = 105,
                          dynamic = False, 
                          full_results=True)
pred_ci = pred.conf_int()
ax = w[0:].plot(label = "observed", figsize=(15, 7))
pred.predicted_mean.plot(ax = ax, label = "One-step ahead Forecast", alpha = 1)
ax.fill_between(pred_ci.index, 
                pred_ci.iloc[:, 0], 
                pred_ci.iloc[:, 1], 
                color = "k", alpha = 0.05)
ax.set_xlabel("Date")
ax.set_ylabel("Acousticness")
plt.legend
plt.show()
train_arima_forecasted = pred.predicted_mean
train_arima_truth = w[20:]

Pour les autres variables, nous avons obtenus les modèles suivants:

  • Danceability : ARIMA(0, 1, 1)x(0, 0, 0, 12)
  • Tempo : ARIMA(1, 1, 1)x(1, 1, 1, 12)
  • Valence : ARIMA(0, 1, 1)x(1, 0, 0, 12)
  • Instrumentalness : ARIMA(1, 1, 0)x(1, 0, 1, 12)
  • Liveness : ARIMA(1, 1, 1)x(0, 0, 0, 12)
  • Duration_ms : ARIMA(0, 1, 1)x(0, 1, 1, 12)
  • Loudness : ARIMA(1, 1, 1)x(0, 1, 1, 12)
  • Speechiness : ARIMA(1, 1, 1)x(0, 1, 1, 12)
  • Energy : ARIMA(1, 0, 1)x(0, 0, 0, 12)
Séries stationnaires

Pour rendre la série stationnaire, nous allons soustraire chaque point par le point qui le précède, puis on va vérifier l'efficacité de cette méthode avec le test de Dickey-Fuller.

In [224]:
def get_stationarity(timeseries):
    rolling_mean = timeseries.rolling(window=12).mean()
    rolling_std = timeseries.rolling(window=12).std()
    original = plt.plot(timeseries, color='blue', label='Origine')
    mean = plt.plot(rolling_mean, color='red', label='Moyenne Mobile')
    std = plt.plot(rolling_std, color='black', label='Ecart-type Mobile')
    plt.legend(loc='best')
    plt.title('Moyenne et écart-type Mobiles')
    plt.show(block=False)
In [225]:
df_log = np.log(df['acousticness'])
df_log_shift = df_log - df_log.shift()
df_log_shift.dropna(inplace=True)
get_stationarity(df_log_shift)
In [226]:
from statsmodels.tsa.stattools import adfuller
result = adfuller(pd.DataFrame(df_log_shift))
print('Statistiques ADF : {}'.format(result[0]))
print('p-value : {}'.format(result[1]))
print('Valeurs Critiques :')
for key, value in result[4].items():
    print('\t{}: {}'.format(key, value))
Statistiques ADF : -12.445684526928943
p-value : 3.66915061045359e-23
Valeurs Critiques :
	1%: -3.498198082189098
	5%: -2.891208211860468
	10%: -2.5825959973472097
In [ ]:
# Test avec le critère AIC pour connaître le modèle
y = pd.DataFrame(df_log_shift)
p = d = q = range(0, 2)
pdq = list(itertools.product(p, d, q))
seasonal_pdq = [(x[0], x[1], x[2], 12) for x in list(itertools.product(p, d, q))]
for param in pdq:
   for param_seasonal in seasonal_pdq:
       try:
           mod = sm.tsa.statespace.SARIMAX(y['acousticness'],
                                           order=param,
                                           seasonal_order=param_seasonal,
                                           enforce_stationarity=False,
                                           enforce_invertibility=False)
           results = mod.fit()
           print('ARIMA{}x{}12 - AIC:{}'.format(param, param_seasonal, results.aic))
       except:
           continue
In [228]:
mdl = sm.tsa.statespace.SARIMAX(y['acousticness'],order=(1, 0, 1),seasonal_order=(0, 0, 0, 12),enforce_stationarity=True,enforce_invertibility=True)
res = mdl.fit()
print(res.summary())
                               SARIMAX Results                                
==============================================================================
Dep. Variable:           acousticness   No. Observations:                  101
Model:               SARIMAX(1, 0, 1)   Log Likelihood                  64.839
Date:                Tue, 23 Feb 2021   AIC                           -123.678
Time:                        21:20:37   BIC                           -115.833
Sample:                    01-01-1921   HQIC                          -120.502
                         - 01-01-2021                                         
Covariance Type:                  opg                                         
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
ar.L1         -0.1905      0.158     -1.205      0.228      -0.500       0.119
ma.L1         -0.4179      0.168     -2.491      0.013      -0.747      -0.089
sigma2         0.0162      0.002      8.604      0.000       0.012       0.020
===================================================================================
Ljung-Box (Q):                       33.92   Jarque-Bera (JB):                 8.49
Prob(Q):                              0.74   Prob(JB):                         0.01
Heteroskedasticity (H):               0.73   Skew:                             0.08
Prob(H) (two-sided):                  0.37   Kurtosis:                         4.41
===================================================================================

Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).
In [229]:
w = y
res = sm.tsa.statespace.SARIMAX(w,
                                order=(1, 0, 1),
                                seasonal_order=(0, 0, 0, 12),
                                enforce_stationarity=False,
                                enforce_invertibility=False).fit()
pred = res.get_prediction(start = 30, 
                          end = 105,
                          dynamic = False, 
                          full_results=True)
pred_ci = pred.conf_int()
ax = w[0:].plot(label = "observed", figsize=(15, 7))
pred.predicted_mean.plot(ax = ax, label = "One-step ahead Forecast", alpha = 1)
ax.fill_between(pred_ci.index, 
                pred_ci.iloc[:, 0], 
                pred_ci.iloc[:, 1], 
                color = "k", alpha = 0.05)
ax.set_xlabel("Date")
ax.set_ylabel("Acousticness")
plt.legend
plt.show()
train_arima_forecasted = pred.predicted_mean
train_arima_truth = w[20:]

Test du modèle de l'arbre de décision pour une nouvelle donnée

Avec les prédictions données par les modèles ARIMA de chaque variables, nous avons essayé de prédire la popularité qu'aurait à ce jour des musiques de demain. Nous avons fait varier

In [230]:
# Avec les prédictions données par le modèle ARIMA pour les variables en 1995
XTRAIN = df_new.drop(columns=["popularity"])
YTRAIN = df_new['popularity']
XTEST = [(0.2712,0.6251,224.54,0.6242,0.3067,0.1983,-8.77,1,0.1085,122.23,0.4598,2022)]
add = DecisionTreeRegressor(max_depth=10, max_features=None, min_samples_leaf=1, min_samples_split=2)
pred = add.fit(XTRAIN, YTRAIN).predict(XTEST)
np.around(pred)
Out[230]:
array([1.])
In [231]:
# Avec les prédictions données par le modèle ARIMA pour les variables en 1995
XTRAIN = df_new.drop(columns=["popularity"])
YTRAIN = df_new['popularity']
XTEST = [(0.2712,0.6251,224.54,0.6242,0.3067,0.1983,-8.77,1,0.1085,122.23,0.4598,1995)]
add = DecisionTreeRegressor(max_depth=10, max_features=None, min_samples_leaf=1, min_samples_split=2)
pred = add.fit(XTRAIN, YTRAIN).predict(XTEST)
np.around(pred)
Out[231]:
array([40.])

On voit alors que cette musique aurait très peu de popularité, cependant cette musique, si elle était sortie en 1995, serait bien plus populaire. Cela signifie donc que l'année de sortie a énormément d'influence sur la popularité du morceau.

Conclusion

Pour rappel, les objectifs de notre étude étaient ceux-ci:

  • Etudier l’évolution de différentes caractéristiques musicales au fil du temps (entre 1920 et 2021)

  • Mettre en évidence un lien entre une variable dite de popularité et des caractéristiques musicales

  • Réduire notre jeu de données afin de regrouper les genres de musiques ayant des caractéristiques communes

  • Mettre en place une méthode de prédiction pertinente afin d’être capable de prédire année et popularité

  • Etre capable de prédire dans les années à venir l’évolution de certaines caractéristiques musicales

Ces objectifs ont été pour la plupart atteints. Dans la première partie, nous avons bien mis en évidence l'évolution des caractéristiques des musiques au fil du temps. Nous avons également découvert le test de Mann-Kendall grâce à cette analyse. Durant la seconde partie, nous avons pu constater de la difficultés de créer des modèles pour prédire la popularité et l'année de sortie des musiques avec précision. En effet, la variable année de sortie influe bien trop sur la popularité du morceau.

Nous avons trouvé ce sujet très intéréssant, notamment car les résultats de nos différents tests et analyses était très différent de ce que nous attendions. De plus, ce projet nous a permis d'utiliser dans une même étude beaucoup de techniques vues dans différents cours, mais aussi de chercher par nous-même d'autres méthodes et tests.

Par la suite, nous aurions pu essayer de créer une application interactive permettant aux utilisateurs d'entrer les caractéristiques d'un morceau pour qu'il puisse essayer d'estimer la popularité hypothétique du morceau.