Prédiction du Top Spotify 2023

Estimation et interprétation de modèles de Machine Learning

Author

Louis Lemercier

Abstract
Dans ce projet, nous allons reproduire un travail qui utilise des modèles en Machine Learning pour prédire quels morceaux sont susceptible d’être dans le Top de la plateforme de streaming musical Spotify.

Introduction

L’objectif de ce projet est de reproduire Lei Zhao publié en juin 2024 (lien vers l’article) où il est question de développer des modèles d’apprentissage automatique (Machine Learning) pour identifier les morceaux Spotify qui sont le plus écoutés, faisant partie du Top, en se basant sur des données recueillies exclusivement auprès de la plateforme de diffusion de jeux de données Kaggle. L’idée est d’appliquer plusieurs techniques d’apprentissage automatique pour utiliser le modèle de prédiction le plus précis possible à cet effet, mais aussi d’apporter un aspect pédogique à la compréhesion du Machine Learning.

Pourquoi les données de 2023 ?

La plateforme Spotify a restreint l’accès à ses données en novembre 2024, notamment les métriques de base (bpm, tonalités, dançabilité, énergie, etc.) et les données sur la présence des titres dans les tops des plateformes Deezer et Apple Music, ainsi que les playlistes. L’entreprise, consciente de l’importance de la valeurs de ses données, ne permets plus l’accès a son API (application programming interface) pour récupérer les métriques et ne diffuse plus ce type de jeux de données. Pour ce travail il est important d’avoir un jeu de données complet.

Description des données

Les données sont exclusivement collectés depuis la plafeforme Kaggle et contient une liste des sons les plus célèbre en 2023 de la plateforme Spotify. Cet ensemble de données met à disposition une multitude de fonctionnalités qui ne sont plus disponible actuellement (tel que, le titre du morceaux, le nom de l’artiste, la date de sortie, listes des playlistes et des tops des plateformes les plus utilisées et diverses attribut édités par Spotify. Le jeu de données est disponible à l’adresse suivante : https://www.kaggle.com/datasets/nelgiriyewithana/top-spotify-songs-2023

Plan

Dans un premier temps il s’agira d’explorer les données et de faire des viusalistions graphiques pour donner un aperçu du jeu de données à notre disposition. Ensuite il sera question d’appliquer les modèles et de calculer la fiabilité de chacun par le score de ROC AUC afin de choisir le modèle le plus robuste. Enfin dans un dernier temps, ce travail s’intéressera à savoir si les tonalités musicales ont une relation significative avec le fait qu’un morceau soit dans le Top de Spotify, avec un peu de statistique.

Exploration des données

Le jeu de données à tout d’abord subit un premier nettoyage sur Excel, dans le but de reconstituer certaines données à la main. C’est un travail plus important et fastidieux, mais cela garantie une meilleur conservation des données du fichier.

Chargement, nettoyage et description des données

#Chargement de données

library(readxl)
Spotify_Most_Streamed_Songs <- read_excel("C:/Users/lemer/Desktop/r_projet/Donnnée/Spotify Most Streamed Songs.xlsx")

#Nettoyage du jeu de données
sp = Spotify_Most_Streamed_Songs

spclean=na.omit(sp)

#Visualisation en tableau simple
datatable(
  head(spclean, 25),
  options = list(
    pageLengh = 10,
    autoWidth = TRUE,
    scrollX= TRUE
  ),
  class = "display compact"
)

Mise en forme et conversion en facteur

Transformation de données :

Conversion de la variable in_spotify_charts en facteur avec comme libellé “No” et “Yes”.

Conversion de la variable in_apple_charts and in_deezer_charts en binaire (0 et 1).

Conversion de la variable mode en binaire où “Major” est 0 et “Minor” est 1.

Réduction des streams en divisant par un million.

Sélection de l’ensemble des variables prédictives pour créer une nouvelle base de données sur laquelle s’appuyer.

#Formatage en facteur et sélection des variables

spclean = spclean %>%
  mutate(
    in_spotify_charts = as.factor(ifelse(in_spotify_charts == 0,"No","Yes")),
    in_apple_charts = as.factor(ifelse(in_apple_charts == 0,0,1)),
    in_deezer_charts = as.factor(ifelse(in_deezer_charts == 0,0,1)),
    mode = as.factor(ifelse(mode == "Major", 0,1)),
    streams = as.numeric(streams) / 1000000
  ) %>%
  filter(complete.cases(.))

# Sélection des variables

predictor_of_sp = c("in_spotify_playlists","in_spotify_charts","streams","in_apple_playlists",
                    "in_apple_charts","in_deezer_playlists","in_deezer_charts","in_shazam_charts",
                    "bpm","mode","danceability_%","valence_%","energy_%","acousticness_%",
                    "instrumentalness_%", "liveness_%","speechiness_%")

spclean2 = spclean %>%
  select(any_of(predictor_of_sp))

Vue de la nouvelle base de données en tableau simple

datatable(
  head(spclean2, 25),
  options = list(
    pageLengh = 10,
    autoWidth = TRUE,
    scrollX= TRUE
  ),
  class = "display compact"
)

Distribution des morceaux ayant le plus de streams

spclean2%>%
  ggplot(aes(x=in_spotify_charts))+
  geom_bar()+
  labs(x="Si dans le top spotify", y="Nombre de morceau")

Matrice des corrélations avec “Corrplot”

spclean2 %>%
  select(where(is.numeric)) %>%
  cor() %>%
  corrplot(type = 'full', diag = F,
           method = 'number')

Nous pouvons voir que les variables concernant les écoutent et la présence dans les tops et les playlistes sont corrélés avec un pourcentage plutôt élevé. Ce qui n’est pas surprenant d’un certain point de vue, où plus les morceaux sont écoutés plus ils seront présent dans des playlistes et les tops des plateformes. De plus on peut voir que certaines métriques sont aussi corrélées comme la “Danceability” et “Valence”, qui calcule le pourcentage de dançabilité et l’émotion positive ou mélancolique des morceaux.

Visualisation de la distribution des morceaux par BPM

Pour mieux visualiser la distribution des morceaux par BPM, un graphique en pyramide paraît plus pertinent.

pyramid_data = spclean2 %>%
  group_by(bpm, in_spotify_charts) %>%
  summarise(count= n(), .groups = "drop") %>%
  mutate(count = ifelse(in_spotify_charts == "No", -count, count))

ggplot(pyramid_data, aes(x = bpm, y = count, fill = in_spotify_charts))+
  geom_col() +
  coord_flip() +
  scale_y_continuous(labels = abs) +
  labs(title="Pyramide des morceaux présents dans le top Spotify",
       x = "BPM",
       y = "Nombre de morceaux",
       fill = "Présence dans le top")+
  theme_minimal()

Le jeu de données montre que les valeurs de BPM entre 90-110 et 120-140 sont les plus fréquentes. Cela indique que peu importe la popularité du morceau ou de l’artiste, les morceaux ont tendance à se situer dans ces tranches de tempo. Mais ça me représente pas non plus un indicateur de succès à lui seul. Les BPM varies selon les genres de musique.

Visualisation de la distribution des morceaux par “Danceablity”

pyramid_data_dance = spclean2 %>%
  group_by(`danceability_%`, in_spotify_charts) %>%
  summarise(count= n(), .groups = "drop") %>%
  mutate(count = ifelse(in_spotify_charts == "No", -count, count))

ggplot(pyramid_data_dance, aes(x = `danceability_%`, y = count, fill = in_spotify_charts)) +
  geom_col() +
  coord_flip() +
  scale_y_continuous(labels = abs) +
  labs(
    title = "Pyramide des morceaux selon la danceabilité et leur présence dans le top Spotify",
    x = "Danceabilité",
    y = "Nombre de morceaux",
    fill = "Présence dans le top"
  ) +
  theme_minimal()

Le pourcentage de “Danceability” le plus fréquent du jeu de données est entre 70% et 80%. Cette tranche contient le plus grand nombre de chansons, ce qui indique une préférence pour ce niveau de dansabilité dans la production musicale générale. Cette caractéristique est cruciale pour que les chansons attirent un public plus large et se comportent bien sur les classements musicaux

Visualisation des streams

ggplot(spclean2, aes(x = in_spotify_charts, y = as.numeric(streams), fill = in_spotify_charts)) +
  geom_boxplot() +
  labs(x = "In Spotify Charts", y = "Streams", title = "Distribution des streams selon la présence dans le top") +
  scale_fill_manual(values = c("#0072B2", "#D55E00"))+
  theme_minimal()

Le box plot pour les chansons dans les classements de Spotify (« oui ») est plus grand que pour les chansons qui ne sont pas dans les classements (« non »), ce qui est plutôt cohérent. La médiane (représentée par la ligne à l’intérieur de la boîte) pour la catégorie « oui » est plus élevée que celle de « non », ce qui montre que les chansons de classement ont généralement un plus grand nombre de streams. Il y a plus de valeurs extrêmes (aberrantes) pour la catégorie « oui ». Ce sont des chansons avec un nombre exceptionnellement élevé d’écoutes, ce sont des super hit.

Construction des modèles et de la procédure

Nous allons à présent entamer la phase de modélisation. Celle-ci commence par une division aléatoire des données en deux ensembles : un ensemble d’entraînement et un ensemble de test. Ensuite, nous mettrons en place une recette de traitement des données, avant d’instaurer une procédure de validation croisée pour évaluer nos modèles.

Séparation des données en ensembles d’entraînement et de test

La première étape consiste à scinder notre jeu de données en deux parties distinctes. L’ensemble d’entraînement servira à construire et ajuster les modèles, tandis que l’ensemble de test ne sera utilisé qu’à la toute fin, pour évaluer les performances sur des données jamais vues.

Pour garantir la reproductibilité de cette division, nous commençons par fixer une graine aléatoire (set seed). Ensuite, nous effectuons la séparation tout en stratifiant selon la variable cible in_spotify_charts. Cette stratification permet de conserver la même proportion de classes dans les deux sous-ensembles, assurant ainsi une répartition équilibrée.

# Séparation du jeu de donnée en "train" et "test" pour machine learning

set.seed(0926)
spotify_split = initial_split(spclean2, prop=0.75, strata = in_spotify_charts)
spotify_train=training(spotify_split)
spotify_test=testing(spotify_split)

Pour le split des données, une proportion de 0.75 paraît être convenable pour avoir assez de données en sorties à analyser.

Construction de la recette

Nous allons maintenant rassembler nos prédicteurs et notre variable de réponse pour construire notre recette, que nous utiliserons pour tous les modèles. Cette recette garantira que nos données sont prétraitées de manière cohérente pour l’entraînement et l’évaluation des modèles.

# Formatage des données (construction de la recette)

set.seed(0926)
spotify_recette = recipe(in_spotify_charts~ ., data=spotify_train) %>%
  step_dummy(all_nominal(), -all_outcomes()) %>%
  step_scale(all_numeric_predictors()) %>%
  step_center(all_numeric_predictors())

Validation croisée (K-Fold)

Pour que les modèles puissent être efficace de manière générale, c’est-à-dire au delà des données d’entraînements, il est nécessaire de diviser les données en sous-ensembles (ici dans ce cas, nous allons diviser en 10) avec la même proportion d’observations sur la variable cible.

# K-Fold cross Validation
set.seed(0926)
spotify_folds=vfold_cv(spclean2,v=10,strata=in_spotify_charts)
save(spotify_recette, spotify_folds, spotify_recette, spotify_train, spotify_test, 
     file = "spotify.rda")

Paramétrage des modèles

Dans cette partie nous allons paramétrer les modèles pour l’analyse prédictive. Pour mesurer la performance des modèles, nous nous intéresserons la courbe ROC accuracy. C’est la nature déséquilibrée de notre ensemble de données qui est la raison pour laquelle nous mettons l’accent sur la courbe. Dans notre ensemble de données, le nombre de chansons qui sont sur les charts Spotify (Oui) est probablement beaucoup plus élevé que le nombre de chansons qui n’y sont pas (Non). Ce déséquilibre peut faire de la précision une mesure trompeuse car un modèle pourrait atteindre une précision élevée en prédisant simplement la classe majoritaire.

Mise en place des modèles

Le processus de construction des modèles de machine learning suit généralement une structure bien définie. Toutefois, certains modèles comme la régression logistique, LDA (Linear Discriminant Analysis) et QDA (Quadratic Discriminant Analysis) étant plus simples et rapides à entraîner, leur mise en place diffère légèrement, car ils ne nécessitent pas de réglages complexes.

Spécification du modèle

Dans un premier temps, nous devons spécifier le modèle que nous souhaitons entraîner. Cela consiste à définir :

le type de modèle utilisé (par exemple, régression logistique ou forêt aléatoire),

le moteur (ou engine) qui l’exécutera,

et le mode du modèle, qui, dans notre cas, sera toujours classification, puisque notre objectif est de prédire si un morceau apparaîtra ou non dans les Spotify charts.

Construction du workflow

Ensuite, nous construisons un workflow. Celui-ci permet de regrouper l’ensemble des éléments nécessaires à l’entraînement du modèle :

la recette de prétraitement (qui contient par exemple la transformation des variables catégorielles ou la normalisation des variables numériques),

et le modèle spécifié précédemment.

Le workflow assure ainsi une exécution cohérente et ordonnée de toutes les étapes de préparation et d’entraînement.

Modèles simples

Pour certains modèles plus simples, comme la régression logistique, LDA ou QDA, nous n’effectuons pas d’étapes de tuning, car ces modèles n’ont pas ou peu d’hyperparamètres à optimiser. Leur mise en place est donc plus directe et rapide.

Tuning grid (pour les modèles complexes)

En revanche, pour les modèles plus complexes tels que les forêts aléatoires ou les modèles boostés, nous devons explorer différents réglages possibles. Nous construisons alors une grille de tuning, qui détermine les combinaisons d’hyperparamètres à tester (par exemple, le nombre d’arbres ou la profondeur maximale).

Tuning du modèle

Une fois cette grille définie, nous procédons à un tuning du modèle à l’aide de la validation croisée. L’objectif est d’entraîner plusieurs versions du modèle avec des réglages différents afin d’identifier la configuration la plus performante.

Sélection du meilleur modèle

Une fois le tuning terminé, nous sélectionnons le meilleur modèle sur la base d’un critère de performance, comme l’AUC (aire sous la courbe ROC). Ce critère permet de quantifier la capacité du modèle à bien distinguer les morceaux qui iront ou non dans les charts.

Finalisation du workflow

Le modèle optimal est ensuite réintégré dans notre workflow finalisé, avec les meilleurs réglages trouvés lors du tuning. Cela permet de figer la configuration du modèle avant l’entraînement définitif.

Entraînement final

Enfin, nous entraînons notre modèle finalisé sur l’ensemble des données d’apprentissage. Ce modèle sera ensuite utilisé pour faire des prédictions sur l’ensemble test, ou pour être appliqué à de nouvelles données.

Configuration des modèles

Ici nous configurons les modèles et nous les ferons tourner (Ils ont bien sûr été déjà entraîner dans un autre fichier)

#===========================
# Regression logistique
#===========================

log_model <- logistic_reg(mode = "classification") %>%
  set_engine("glm")

log_workflow <- workflow() %>%
  add_recipe(spotify_recette) %>%
  add_model(log_model)

log_fit = fit_resamples(log_workflow, resamples = spotify_folds,
                        control = control_resamples(save_pred = TRUE))

save(log_fit, file = "C:/Users/lemer/Desktop/r_projet/logistic_regression.rda")

#===========================
#Random Forest
#===========================

# 1. Définir le modèle avec hyperparamètres à tuner
rf_model <- rand_forest(
  mode = "classification",
  mtry = tune(),
  min_n = tune()
) %>%
  set_engine("ranger")

# 2. Créer le workflow
rf_workflow <- workflow() %>%
  add_recipe(spotify_recette) %>%
  add_model(rf_model)

# 3. Extraire et finaliser les hyperparamètres avec le jeu d'entraînement
rf_params <- parameters(rf_model)
Warning: `parameters.model_spec()` was deprecated in tune 0.1.6.9003.
ℹ Please use `hardhat::extract_parameter_set_dials()` instead.
rf_params <- finalize(rf_params, spotify_train)

# 4. Créer une grille aléatoire de 10 combinaisons
set.seed(123)
rf_grid <- grid_random(rf_params, size = 10)

# 5. Lancer la recherche d’hyperparamètres avec validation croisée
rf_tuned <- tune_grid(
  rf_workflow,
  resamples = spotify_folds,
  grid = rf_grid,
  metrics = metric_set(roc_auc, accuracy)
)

# 6. Sélectionner la meilleure combinaison d’hyperparamètres
best_rf <- select_best(rf_tuned, metric = "roc_auc")

# 7. Finaliser le workflow avec les meilleurs hyperparamètres
final_rf_workflow <- finalize_workflow(rf_workflow, best_rf)

# 8. Entraîner le modèle final sur le jeu d’entraînement
rf_fit <- fit(final_rf_workflow, data = spotify_train)

# 9. Sauvegarder le modèle entraîné
save(rf_fit, file = "C:/Users/lemer/Desktop/r_projet/random_forest_spotify.rda")

#===========================
#Gradient Boosted Trees
#===========================

# Définir le modèle GBT avec des paramètres à tuner
gbt_model = boost_tree(
  mode = "classification",
  trees = 1000,  # On fixe le nombre total d'arbres
  tree_depth = tune(),  # profondeur des arbres à tuner
  learn_rate = tune(),  # taux d’apprentissage à tuner
  loss_reduction = tune(),  # complexité minimale pour split
  sample_size = tune(),  # fraction d’échantillon utilisée
  mtry = tune()  # nombre de variables testées à chaque split
) %>%
  set_engine("xgboost")

# Créer le workflow avec la recette
gbt_workflow = workflow() %>%
  add_recipe(spotify_recette) %>%
  add_model(gbt_model)

# Définir l’espace des hyperparamètres à tuner
gbt_params = parameters(
  tree_depth(),
  learn_rate(),
  loss_reduction(),
  sample_size = sample_prop(),
  mtry = finalize(mtry(), spotify_train)
)

# Créer une grille aléatoire de 10 combinaisons
gbt_grid = grid_random(gbt_params, size = 10)

# Appliquer le tuning avec validation croisée
gbt_tuned = tune_grid(
  gbt_workflow,
  resamples = spotify_folds,
  grid = gbt_grid,
  metrics = metric_set(roc_auc, accuracy)
)

# Sélectionner les meilleurs hyperparamètres
best_gbt = select_best(gbt_tuned, metric = "roc_auc")

# Finaliser le workflow avec les meilleurs paramètres
final_gbt = finalize_workflow(gbt_workflow, best_gbt)

# Entraîner le modèle sur l’ensemble d'entraînement
final_gbt_fit = fit(final_gbt, data = spotify_train)

save(final_gbt_fit, file="C:/Users/lemer/Desktop/r_projet/gradient_boosted_trees.rda")

#===========================
# LDA (Analyse discriminante linéaire)
#===========================

lda_model = discrim_linear() %>%
  set_engine("MASS") %>%
  set_mode("classification")

lda_workflow = workflow() %>%
  add_model(lda_model) %>%
  add_recipe(spotify_recette)

lda_fit = fit_resamples(lda_workflow, spotify_folds)

save(lda_fit, file = "C:/Users/lemer/Desktop/r_projet/LDA.rda")

#===========================
# QDA (Analyse discriminante quadratique)
#===========================

qda_model = discrim_quad() %>%
  set_engine("MASS") %>%
  set_mode("classification")

qda_workflow = workflow() %>%
  add_model(qda_model) %>%
  add_recipe(spotify_recette)

qda_fit =fit_resamples(qda_workflow, spotify_folds)

save(qda_fit, file = "C:/Users/lemer/Desktop/r_projet/QDA.rda")

#===========================
# KNN (K-Nearest Neighbors)
#===========================

# Définir le modèle KNN avec paramètre à tuner
knn_model = nearest_neighbor(
  mode = "classification",
  neighbors = tune()  # nombre de voisins à tester
) %>%
  set_engine("kknn")

# Construire le workflow
knn_workflow = workflow() %>%
  add_recipe(spotify_recette) %>%
  add_model(knn_model)

# Définir les hyperparamètres à tuner
knn_params = parameters(knn_model)

# Générer une grille aléatoire de 10 combinaisons
knn_grid = grid_random(knn_params, size = 10)

# Tuning du modèle avec validation croisée
knn_tuned = tune_grid(
  knn_workflow,
  resamples = spotify_folds,
  grid = knn_grid,
  metrics = metric_set(roc_auc, accuracy)
)

# Sélection des meilleurs hyperparamètres
best_knn = select_best(knn_tuned, metric = "roc_auc")

# Finalisation du workflow avec les meilleurs paramètres
final_knn = finalize_workflow(knn_workflow, best_knn)

# Entraînement final sur les données d'entraînement
final_knn_fit = fit(final_knn, data = spotify_train)

Valeurs ROC AUC des modèles

Pour résumer les meilleures valeurs ROC AUC de nos sept modèles, nous allons créer un tibble pour afficher la valeur ROC AUC finale estimée pour chaque modèle ajusté.

#===========================
# Courbe ROC
#===========================

# Regression logistique

log_roc_auc_score = log_fit %>%
  collect_metrics() %>%
  filter(.metric=="roc_auc") %>%
  pull(mean)

# Random Forest

rf_metrics = collect_metrics(rf_tuned)
roc_auc_scores_1 = rf_metrics %>%
  filter(.metric =="roc_auc") %>%
  arrange(desc(mean)) %>%
  pull(mean)

rf_roc_auc_score = roc_auc_scores_1[1]

#Gradient Boosted Trees

gbt_metrics = collect_metrics(gbt_tuned)
roc_auc_score_2 = gbt_metrics %>%
  filter(.metric =="roc_auc") %>%
  arrange(desc(mean)) %>%
  pull(mean)

gbt_roc_auc_score = roc_auc_score_2[1]

#LDA

lda_roc_auc_score = lda_fit %>%
  collect_metrics() %>%
  filter(.metric=="roc_auc") %>%
  pull(mean)

# QDA

qda_roc_auc_score = qda_fit %>%
  collect_metrics() %>%
  filter(.metric=="roc_auc") %>%
  pull(mean)

#KNN

knn_metrics = collect_metrics(knn_tuned)
roc_auc_score_3 = knn_metrics %>%
  filter(.metric == "roc_auc") %>%
  arrange(desc(mean)) %>%
  pull(mean)

knn_roc_auc_score = roc_auc_score_3[1]

# Visualisation

spotify_roc_aucs = c(log_roc_auc_score,
                     rf_roc_auc_score,
                     gbt_roc_auc_score,
                     knn_roc_auc_score,
                     lda_roc_auc_score,
                     qda_roc_auc_score)

spotify_mod_names = c("Regression logistique",
                      "Random Forest",
                      "Boosted Trees",
                      "KNN",
                      "LDA","QDA")

spotify_tibble = tibble("Model"=spotify_mod_names,
                        "Values"=spotify_roc_aucs) %>%
  dplyr::arrange(-spotify_roc_aucs) %>% print()
# A tibble: 6 × 2
  Model                 Values
  <chr>                  <dbl>
1 Random Forest          0.872
2 Boosted Trees          0.864
3 Regression logistique  0.852
4 LDA                    0.839
5 QDA                    0.807
6 KNN                    0.767

Courbe ROC

Plus la courbe est proche du coin supérieur gauche, meilleur est le score AUC du modèle. Bien que notre courbe ROC n’atteigne pas parfaitement le coin supérieur gauche, elle tend vers cette direction, ce qui indique une bonne performance du modèle. Cela confirme le score AUC que nous avons calculé précédemment et montre que notre modèle est performant.

# ROC Gbt score

best_gbt_class = select_best(gbt_tuned, metric = "roc_auc")
gbt_workflow = finalize_workflow(gbt_workflow, best_gbt_class)
final_gbt_fit = fit(gbt_workflow, data = spotify_train)
gbt_predict_augmented = augment(final_gbt_fit, new_data = spotify_test, type = 'prob')
gbt_roc_auc_score = gbt_predict_augmented %>%
  roc_auc(truth = in_spotify_charts, .pred_No)

print(gbt_roc_auc_score$.estimate)
[1] 0.8222222
# ROC plot

augment(final_gbt_fit, new_data = spotify_test, type = 'prob')%>%
  roc_curve(in_spotify_charts, .pred_No) %>%
  autoplot()

Utilisation des modèles pour prédiction

Dans cette partie nous allons utiliser le modèle Gradient Boosted Trees. Nous ajoutons deux nouvelles colonnes avec les valeurs prédites par la modèle à savoir une variable avec deux modalités “Yes” et “No”. On fait un entraînement sur les données de teste, puis sur toutes les données. Nous ajoutons également les probabilités des prédictions pour meilleurs lecture.

# Predictine YES

# 1. Ajustement du modèle sur tout l'ensemble d'entraînement avec les meilleurs paramètres
best_gbt_params = tibble(mtry = 3, trees = 500, min_n = 4) # création d'un tibble avec les hyperparamètres, nombre de variables aléatoires, nombre total d'arbres ou d'itérations et le nombre minimal d'observations 

final_gbt_wkflow = finalize_workflow(gbt_workflow, best_gbt_params)

#final_boost_fit = fit(final_boost_wkflow, data = spotify_train)

# 2. Faire les prédictions sur spotify_test
spotify_test = spotify_test %>%
  mutate(
    .pred_class = predict(final_gbt_fit, new_data = spotify_test)$.pred_class
  )

#probabilité

gbt_probs = predict(final_gbt_fit, new_data = spotify_test, type = "prob")

spotify_test = spotify_test %>%
  mutate(
    .pred_class = predict(final_gbt_fit, new_data = spotify_test)$.pred_class,
    prob_yes = gbt_probs$.pred_Yes,
    prob_no = gbt_probs$.pred_No
  )
# Predictions sur tout le dataset

# On entraîne le modèle sur tout le dataset
final_boost_fit = fit(final_gbt_wkflow, data = spclean)

# On prédit les classes et les probabilités
boost_preds = predict(final_boost_fit, new_data = spclean)
boost_probs = predict(final_boost_fit, new_data = spclean, type = "prob")

# On ajoute les colonnes dans le dataset
spclean = spclean %>%
  mutate(
    .pred_class = boost_preds$.pred_class,
    prob_yes = boost_probs$.pred_Yes,
    prob_no = boost_probs$.pred_No
  )

Les tonalités musicales ont-elles une relation significative avec le fait d’être dans le Top Spotify ?

Dans cette section, nous cherchons à déterminer si la tonalité musicale (key) d’un morceau influence sa probabilité d’intégrer le classement Top Spotify. Cette variable catégorielle indique la tonalité principale du morceau (ex : C, D#, A#, etc.). Pour répondre à cette question, nous avons utilisé deux approches statistiques complémentaires : un test du Chi² pour explorer l’association entre la tonalité et la présence dans le classement, puis une régression logistique pour modéliser l’effet de chaque tonalité sur la probabilité d’être classé.

Teste du Chi²

# Chi² avec les variables Key et in_spotify_charts

spclean$key = as.factor(spclean$key)

table_key_charts = table(spclean$key, spclean$in_spotify_charts)
chisq.test(table_key_charts)

    Pearson's Chi-squared test

data:  table_key_charts
X-squared = 12.771, df = 11, p-value = 0.3085

Nous avons d’abord réalisé un test du Chi² sur un tableau croisé entre la tonalité (key) et la variable cible (in_spotify_charts). Le test retourne une p-value de 0.3085, ce qui ne permet pas de rejeter l’hypothèse d’indépendance au seuil classique de 5%. Autrement dit, d’un point de vue purement statistique, il n’existe pas de lien significatif entre la tonalité et le fait qu’un morceau figure dans le Top Spotify.

Régression logistique

# Regression logistique sur les variables

logit_model_key = glm(in_spotify_charts ~ key, data=spclean, family = "binomial")
summary(logit_model_key)

Call:
glm(formula = in_spotify_charts ~ key, family = "binomial", data = spclean)

Coefficients:
            Estimate Std. Error z value Pr(>|z|)
(Intercept)  0.39304    0.24907   1.578    0.115
keyA#        0.04521    0.37986   0.119    0.905
keyB         0.18232    0.34628   0.527    0.599
keyC         0.06479    0.33868   0.191    0.848
keyC#       -0.08883    0.31726  -0.280    0.779
keyD        -0.31300    0.33979  -0.921    0.357
keyD#       -0.53614    0.45346  -1.182    0.237
keyE        -0.01835    0.37248  -0.049    0.961
keyF         0.31780    0.33955   0.936    0.349
keyF#        0.23557    0.35486   0.664    0.507
keyG        -0.20646    0.33008  -0.625    0.532
keyG#       -0.49068    0.33307  -1.473    0.141

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 1165.2  on 857  degrees of freedom
Residual deviance: 1152.4  on 846  degrees of freedom
AIC: 1176.4

Number of Fisher Scoring iterations: 4

Voici les résultats du modèle de régression logistique où la variable key est introduite comme facteur explicatif. Ce modèle permet d’estimer, pour chaque tonalité, l’effet sur la log-probabilité d’apparition dans le classement, comparé à une tonalité de référence (ici la tonalité “A”). Les résultats montrent que certaines tonalités comme F, B ou F# présentent des coefficients positifs, suggérant une légère augmentation de probabilité, tandis que d’autres comme D# ou G# présentent des effets négatifs. Toutefois, aucun de ces effets n’est statistiquement significatif (p-value > 0.05), ce qui confirme l’intuition du test du Chi² : la tonalité seule n’est pas un facteur discriminant fort pour prédire l’apparition dans le Top. Il pourrait être intéressant de croiser la tonalité avec d’autres métriques musicales de Spotify (comme l’énergie, le tempo, ou la valence) pour détecter des interactions. Dans ce cas, une approche par modèle logit multinomial ou par régression logistique avec interactions pourrait permettre de mieux comprendre l’influence combinée de ces variables sur le succès d’un morceau.

Conclusion

Ce projet a permis de développer avec succès des modèles prédictifs capables d’identifier les morceaux ayant le plus de chances d’apparaître dans les classements Spotify à partir des données de 2023. Cette capacité de prédiction offre des enseignements précieux sur les facteurs de succès d’un titre musical, utiles aussi bien pour les artistes que pour les professionnels de l’industrie. Les résultats peuvent ainsi être mobilisés pour affiner les stratégies de promotion, optimiser les placements dans les playlists ou encore orienter les collaborations artistiques afin de maximiser la visibilité et les performances en streaming.

Au-delà de l’aspect analytique, ce travail a été une excellente opportunité de mettre en pratique les concepts de machine learning dans un cadre concret, à travers un jeu de données réel et une problématique contemporaine. Dans le cadre de l’unité d’enseignement “Fouille de données” du Master Mégadonnées et Analyse Sociales du Conservatoire National des Arts et Métiers (CNAM), ce projet a permis d’approfondir les techniques de modélisation supervisée (logistic regression, random forest, gradient boosted trees), d’interprétation des résultats, et de comparaison des performances des modèles.

Une partie du travail a également exploré si certaines variables, comme la tonalité musicale (key), pouvaient être liées à la présence dans le Top Spotify. Les analyses statistiques (test du Chi², régression logistique) ont montré qu’aucune tonalité n’avait d’effet significatif sur le classement, ce qui suggère que le succès d’un morceau ne dépend pas directement de sa tonalité musicale.

Cette observation renforce l’idée que les déterminants de la popularité sont plus complexes, et probablement influencés par des facteurs externes, notamment la viralité sur les réseaux sociaux (TikTok, Instagram, etc.), la stratégie marketing, ou encore le placement dans des playlists populaires. Par ailleurs les variables ayant une relation significative avec la présence dans le Top spotify, sont bien sûr les variables sur la présence des playlistes et les tops des autres plateformes de streaming musicale (Apple et Deezer)

En somme, ce projet constitue une base solide pour des analyses prédictives plus approfondies, en croisant données musicales, comportementales et sociales, pour mieux comprendre les mécanismes du succès musical à l’ère du streaming.

Citation

source des données : https://www.kaggle.com/datasets/nelgiriyewithana/top-spotify-songs-2023

Lei Zhao : 131 FInal Project: Predicting 2023 Spotify Charts (https://rpubs.com/zhaolei0926/1196679)