Wstęp

W projekcie wykorzystano dane z: Kaggle Spotify Dataset. Zbiór danych zawiera 114 000 rekordów danych dla osobnych utworów z serwisu Spotify.

Koncepcja pracy oparta jest o badanie przedstawione w artykule: Tsai, C.-F., Hu, Y.-H., & Lu, Y.-H. Customer segmentation issues and strategies for an automobile dealership with two clustering techniques.

W pracy wykorzystano: 1. Algorytm k-means. 2. Algorytm EM (Expectation-Maximization).

Wykorzystanie narzędzi AI: W pracy wykorzystano częściowo chat Gemini do wygenerowania fragmentów kodu R (szczególnie przy wykresach oraz poprawkach kodu profilującego klastry). Ponadto, chat Gemini został wykorzystany w aspekcie technicznej konwersji kodu źródłowego z formatu (.R) do niniejszego raportu w formacie R Markdown (.Rmd).

Ładowanie pakietów

library(tidyverse)
library(factoextra)
library(corrplot)
library(cluster)
library(mclust)
library(plotly)

Pobranie i przegląd danych

# Wczytanie danych
spotify_raw <- read.csv("dataset.csv", sep = ",", dec = ".", header = TRUE) 

# Podstawowy przegląd
head(spotify_raw)
##   X               track_id                artists
## 1 0 5SuOikwiRyPMVoIQDJUgSV            Gen Hoshino
## 2 1 4qPNDBW1i3p13qLCt0Ki3A           Ben Woodward
## 3 2 1iJBSr7s7jYXzM8EGcbK5b Ingrid Michaelson;ZAYN
## 4 3 6lfxq3CG4xtTiEg7opyCyx           Kina Grannis
## 5 4 5vjLSffimiIP26QG5WcN2K       Chord Overstreet
## 6 5 01MVOl9KtVTNfFiBU9I7dc           Tyrone Wells
##                                               album_name
## 1                                                 Comedy
## 2                                       Ghost (Acoustic)
## 3                                         To Begin Again
## 4 Crazy Rich Asians (Original Motion Picture Soundtrack)
## 5                                                Hold On
## 6                                   Days I Will Remember
##                   track_name popularity duration_ms explicit danceability
## 1                     Comedy         73      230666    False        0.676
## 2           Ghost - Acoustic         55      149610    False        0.420
## 3             To Begin Again         57      210826    False        0.438
## 4 Can't Help Falling In Love         71      201933    False        0.266
## 5                    Hold On         82      198853    False        0.618
## 6       Days I Will Remember         58      214240    False        0.688
##   energy key loudness mode speechiness acousticness instrumentalness liveness
## 1 0.4610   1   -6.746    0      0.1430       0.0322         1.01e-06   0.3580
## 2 0.1660   1  -17.235    1      0.0763       0.9240         5.56e-06   0.1010
## 3 0.3590   0   -9.734    1      0.0557       0.2100         0.00e+00   0.1170
## 4 0.0596   0  -18.515    1      0.0363       0.9050         7.07e-05   0.1320
## 5 0.4430   2   -9.681    1      0.0526       0.4690         0.00e+00   0.0829
## 6 0.4810   6   -8.807    1      0.1050       0.2890         0.00e+00   0.1890
##   valence   tempo time_signature track_genre
## 1   0.715  87.917              4    acoustic
## 2   0.267  77.489              4    acoustic
## 3   0.120  76.332              4    acoustic
## 4   0.143 181.740              3    acoustic
## 5   0.167 119.949              4    acoustic
## 6   0.666  98.017              4    acoustic
glimpse(spotify_raw)
## Rows: 114,000
## Columns: 21
## $ X                <int> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,…
## $ track_id         <chr> "5SuOikwiRyPMVoIQDJUgSV", "4qPNDBW1i3p13qLCt0Ki3A", "…
## $ artists          <chr> "Gen Hoshino", "Ben Woodward", "Ingrid Michaelson;ZAY…
## $ album_name       <chr> "Comedy", "Ghost (Acoustic)", "To Begin Again", "Craz…
## $ track_name       <chr> "Comedy", "Ghost - Acoustic", "To Begin Again", "Can'…
## $ popularity       <int> 73, 55, 57, 71, 82, 58, 74, 80, 74, 56, 74, 69, 52, 6…
## $ duration_ms      <int> 230666, 149610, 210826, 201933, 198853, 214240, 22940…
## $ explicit         <chr> "False", "False", "False", "False", "False", "False",…
## $ danceability     <dbl> 0.676, 0.420, 0.438, 0.266, 0.618, 0.688, 0.407, 0.70…
## $ energy           <dbl> 0.4610, 0.1660, 0.3590, 0.0596, 0.4430, 0.4810, 0.147…
## $ key              <int> 1, 1, 0, 0, 2, 6, 2, 11, 0, 1, 8, 4, 7, 3, 2, 4, 2, 1…
## $ loudness         <dbl> -6.746, -17.235, -9.734, -18.515, -9.681, -8.807, -8.…
## $ mode             <int> 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0,…
## $ speechiness      <dbl> 0.1430, 0.0763, 0.0557, 0.0363, 0.0526, 0.1050, 0.035…
## $ acousticness     <dbl> 0.0322, 0.9240, 0.2100, 0.9050, 0.4690, 0.2890, 0.857…
## $ instrumentalness <dbl> 1.01e-06, 5.56e-06, 0.00e+00, 7.07e-05, 0.00e+00, 0.0…
## $ liveness         <dbl> 0.3580, 0.1010, 0.1170, 0.1320, 0.0829, 0.1890, 0.091…
## $ valence          <dbl> 0.7150, 0.2670, 0.1200, 0.1430, 0.1670, 0.6660, 0.076…
## $ tempo            <dbl> 87.917, 77.489, 76.332, 181.740, 119.949, 98.017, 141…
## $ time_signature   <int> 4, 4, 4, 3, 4, 4, 3, 4, 4, 4, 4, 3, 4, 4, 4, 3, 4, 4,…
## $ track_genre      <chr> "acoustic", "acoustic", "acoustic", "acoustic", "acou…
summary(spotify_raw)
##        X            track_id           artists           album_name       
##  Min.   :     0   Length:114000      Length:114000      Length:114000     
##  1st Qu.: 28500   Class :character   Class :character   Class :character  
##  Median : 57000   Mode  :character   Mode  :character   Mode  :character  
##  Mean   : 57000                                                           
##  3rd Qu.: 85499                                                           
##  Max.   :113999                                                           
##   track_name          popularity      duration_ms        explicit        
##  Length:114000      Min.   :  0.00   Min.   :      0   Length:114000     
##  Class :character   1st Qu.: 17.00   1st Qu.: 174066   Class :character  
##  Mode  :character   Median : 35.00   Median : 212906   Mode  :character  
##                     Mean   : 33.24   Mean   : 228029                     
##                     3rd Qu.: 50.00   3rd Qu.: 261506                     
##                     Max.   :100.00   Max.   :5237295                     
##   danceability        energy            key            loudness      
##  Min.   :0.0000   Min.   :0.0000   Min.   : 0.000   Min.   :-49.531  
##  1st Qu.:0.4560   1st Qu.:0.4720   1st Qu.: 2.000   1st Qu.:-10.013  
##  Median :0.5800   Median :0.6850   Median : 5.000   Median : -7.004  
##  Mean   :0.5668   Mean   :0.6414   Mean   : 5.309   Mean   : -8.259  
##  3rd Qu.:0.6950   3rd Qu.:0.8540   3rd Qu.: 8.000   3rd Qu.: -5.003  
##  Max.   :0.9850   Max.   :1.0000   Max.   :11.000   Max.   :  4.532  
##       mode         speechiness       acousticness    instrumentalness  
##  Min.   :0.0000   Min.   :0.00000   Min.   :0.0000   Min.   :0.00e+00  
##  1st Qu.:0.0000   1st Qu.:0.03590   1st Qu.:0.0169   1st Qu.:0.00e+00  
##  Median :1.0000   Median :0.04890   Median :0.1690   Median :4.16e-05  
##  Mean   :0.6376   Mean   :0.08465   Mean   :0.3149   Mean   :1.56e-01  
##  3rd Qu.:1.0000   3rd Qu.:0.08450   3rd Qu.:0.5980   3rd Qu.:4.90e-02  
##  Max.   :1.0000   Max.   :0.96500   Max.   :0.9960   Max.   :1.00e+00  
##     liveness         valence           tempo        time_signature 
##  Min.   :0.0000   Min.   :0.0000   Min.   :  0.00   Min.   :0.000  
##  1st Qu.:0.0980   1st Qu.:0.2600   1st Qu.: 99.22   1st Qu.:4.000  
##  Median :0.1320   Median :0.4640   Median :122.02   Median :4.000  
##  Mean   :0.2136   Mean   :0.4741   Mean   :122.15   Mean   :3.904  
##  3rd Qu.:0.2730   3rd Qu.:0.6830   3rd Qu.:140.07   3rd Qu.:4.000  
##  Max.   :1.0000   Max.   :0.9950   Max.   :243.37   Max.   :5.000  
##  track_genre       
##  Length:114000     
##  Class :character  
##  Mode  :character  
##                    
##                    
## 
dim(spotify_raw)
## [1] 114000     21

Czyszczenie danych

Sprawdzenie występowania brakujących danych (N/A). Dane są już oczyszczone.

colSums(is.na(spotify_raw))
##                X         track_id          artists       album_name 
##                0                0                0                0 
##       track_name       popularity      duration_ms         explicit 
##                0                0                0                0 
##     danceability           energy              key         loudness 
##                0                0                0                0 
##             mode      speechiness     acousticness instrumentalness 
##                0                0                0                0 
##         liveness          valence            tempo   time_signature 
##                0                0                0                0 
##      track_genre 
##                0

Standaryzacja i Analiza Korelacji

Wybieramy 11 cech numerycznych do analizy. Dokonuję standaryzacji zmiennych, co jest krokiem wymaganym przed przejściem do analizy PCA.

# Wybór kolumn
spotify_metaData <- spotify_raw[,c("track_id", "artists", "track_name", "track_genre")]
features_names <- c("popularity", "duration_ms", "danceability", "energy", "loudness", 
                    "speechiness", "acousticness", "instrumentalness", "liveness", 
                    "valence", "tempo")

spotify_analysis <- spotify_raw[,features_names]

# Standaryzacja
spotify_analysis_scaled <- scale(spotify_analysis)

Macierz korelacji

Poniżej wstępna ocena graficzna korelacji cech.

cor_matrix <- cor(spotify_analysis_scaled)
corrplot(cor_matrix, method = "color", type = "full", addCoef.col = "black", 
         number.cex = 0.7, tl.col = "black", 
         title = "Korelacja cech utworów na Spotify", mar = c(0,0,5,0))

Wnioski ze wstępnej analizy: Na podstawie analizy graficznej możemy wstępnie wytypować zmienne, które powinny wzbudzić nasze zainteresowanie. W naszym przypadku będzie to np. silna korelacja między zmiennymi acousticness oraz energy, a także loudness i energy oraz acousticness i loudness.

Wymienione pary zmiennych cechują się korelacją > 0.5, z najwyższą wartością dla pary loudness-energy = |0,76|, a dla pary acousticness-energy = |0,73|. Ponadto umiarkowanie silną korelację można odczytać między valence-danceability = |0,48|.

Wniosek: wymienione pary zmiennych mogą okazać się kandydatami do scalenia w ramach nowej składowej (PCx) w procesie redukcji wymiarów.

Analiza Głównych Składowych (PCA)

spotify_PCA <- prcomp(spotify_analysis_scaled, center = FALSE, scale. = FALSE)

fviz_eig(spotify_PCA, addlabels = TRUE, ylim = c(0,30), 
         main="PCA - wyjaśniona wariancja przez składowe")

fviz_pca_var(spotify_PCA, col.var = "cos2", 
             gradient.cols = c("steelblue", "blue", "black"), repel = F)

summary(spotify_PCA)
## Importance of components:
##                           PC1    PC2    PC3     PC4     PC5     PC6     PC7
## Standard deviation     1.6958 1.2346 1.1124 1.02428 0.98075 0.92947 0.91217
## Proportion of Variance 0.2614 0.1386 0.1125 0.09538 0.08744 0.07854 0.07564
## Cumulative Proportion  0.2614 0.4000 0.5125 0.60788 0.69532 0.77386 0.84950
##                            PC8    PC9    PC10    PC11
## Standard deviation     0.85841 0.6732 0.57041 0.37425
## Proportion of Variance 0.06699 0.0412 0.02958 0.01273
## Cumulative Proportion  0.91649 0.9577 0.98727 1.00000

Wybór liczby składowych: Analizując uzyskane wyniki: PC1 opisuje 26% zmienności, PC2 13,9% itd. Przyjmując kryterium zachowania wyjaśnienia wariancji na poziomie > 80%, mogę dokonać zmniejszenia liczby wymiarów z 11 do 8 bez istotnej utraty informacji (finalnie uzyskując 91,7% zmienności cech, co widać w wierszu Cumulative Proportion).

spotify_reduced <- spotify_PCA$x[,1:8]

Klastrowanie metodą K-means

Wyznaczenie optymalnej liczby klastrów (Silhouette)

Wyznaczam liczbę Silhouette dla próbki 10 000 obserwacji (w celu przyspieszenia obliczeń).

set.seed(123)
sample <- sample(1:nrow(spotify_reduced), 10000)
spotify_SH_sample <- spotify_reduced[sample,]

fviz_nbclust(spotify_SH_sample, kmeans, method = "silhouette")

Z otrzymanego wykresu wynika, że powinniśmy założyć 2 klastry aby zmaksymalizować wartość Silhouette.

Uruchomienie algorytmu K-means (k=2)

set.seed(123)
km_spotify <- kmeans(spotify_reduced, centers = 2, nstart = 25)

# Podstawowe statystyki klastrów
km_spotify$size
## [1] 86460 27540
km_spotify$tot.withinss #suma kwadratów klastrów
## [1] 928938.8

Wizualizacja K-means

Wizualizacja dla dwóch pierwszych składowych PCA (PC1 oraz PC2).

fviz_cluster(km_spotify, data = spotify_reduced, geom = "point", palette = "Set1", 
             ellipse.type = "norm", pointsize = 0.5, 
             ggtheme = theme_minimal(base_size = 12)) +
  labs(title = "Klastrowanie metodą K-means (k=2)", 
       subtitle = "Wykorzystano 8 składowych PCA, wizualizacja dla PC1 oraz PC2", 
       x = "PC1", y = "PC2")

Reprezentacja 3D (PC1, PC2, PC3): W ramach reprezentacji danych w trzech wymiarach widać wyraźniej podział danych na dwa sety klastrów.

plot_3d_data <- as.data.frame(spotify_reduced[, 1:3])
plot_3d_data$Cluster <- as.factor(km_spotify$cluster)

plot_ly(plot_3d_data, x = ~PC1, y = ~PC2, z = ~PC3, color = ~Cluster, 
        colors = "Set1", type = 'scatter3d', mode = 'markers', 
        marker = list(size = 2)) %>% 
  layout(title = "Klastry Spotify w 3D (PC1, PC2, PC3)")

Analiza Silhouette dla K-means

set.seed(123)
sample <- sample(1:nrow(spotify_reduced), 10000)
sample_sh <- spotify_reduced[sample,]
clusters_sample_km <- km_spotify$cluster[sample]

km_spotify_sh <- silhouette(clusters_sample_km, dist(sample_sh))

fviz_silhouette(km_spotify_sh, print.summary = TRUE, palette = "Set1", 
                ggtheme = theme_minimal()) + 
  labs(title = "Analiza Silhouette dla K-means dla próby", 
       subtitle = "Wartości dodatnie oznaczają dobre dopasowanie, ujemne błędne.")
##   cluster size ave.sil.width
## 1       1 7576          0.26
## 2       2 2424          0.18

sil_val <- km_spotify_sh[,3]
negative_count <- as.numeric(sum(sil_val < 0))
cat("Liczba ujemnych wartości Silhouette:", negative_count)
## Liczba ujemnych wartości Silhouette: 198

Uwaga: W powyższym przykładzie widzimy, że dopasowanie obserwacji w wybranej próbie nie jest najlepsze dla klastra 2. W ramach próby 10 000 dla 88 obserwacji miara Silhouette jest ujemna (0,88%).

Algorytm EM (Expectation-Maximization)

Metoda ta różni się od K-means tym, że K-means jest deterministyczny i minimalizuje sumę kwadratów odległości od centroidów. Metoda EM jest probabilistyczna (maksymalizuje funkcję wiarygodności) - powstaje “soft clustering”, gdzie obserwacja ma prawdopodobieństwo przynależności do klastra.

EM dla k=2 (dla porównania)

em_spotify_k2 <- Mclust(spotify_reduced, G=2)

summary(em_spotify_k2)
## ---------------------------------------------------- 
## Gaussian finite mixture model fitted by EM algorithm 
## ---------------------------------------------------- 
## 
## Mclust VVV (ellipsoidal, varying volume, shape, and orientation) model with 2
## components: 
## 
##  log-likelihood      n df      BIC      ICL
##        -1180221 114000 89 -2361478 -2367962
## 
## Clustering table:
##     1     2 
## 42976 71024
# Wizualizacja
fviz_cluster(list(data=spotify_reduced, cluster=em_spotify_k2$classification),
             geom = "point", palette = "Set2", ellipse.type = "norm", pointsize = 0.5, 
             ggtheme = theme_minimal(base_size = 12)) + 
  labs(title = "Klastrowanie metodą EM (k=2)", 
       subtitle = "Model EM na 8 składowych PCA", x = "PC1", y = "PC2")

Silhouette dla EM

clusters_sample_em <- em_spotify_k2$classification[sample]
em_spotify_k2_sh <- silhouette(clusters_sample_em, dist(sample_sh))

fviz_silhouette(em_spotify_k2_sh, print.summary = TRUE, palette = "Set2", 
                ggtheme = theme_minimal()) + 
  labs(title = "Analiza Silhouette dla EM dla próby", 
       subtitle = "Wartości dodatnie oznaczają dobre dopasowanie, ujemne błędne.")
##   cluster size ave.sil.width
## 1       1 3858          -0.1
## 2       2 6142           0.3

EM dla optymalnej liczby klastrów (BIC)

Do oceny jakości modelu EM wykorzystuje się kryterium BIC (Bayesian Information Criterion). Funkcja Mclust bez parametru G sama wyznacza optymalną liczbę klastrów.

em_spotify_kX <- Mclust(spotify_reduced) # dla optymalnej liczby klastrów
summary(em_spotify_kX)
## ---------------------------------------------------- 
## Gaussian finite mixture model fitted by EM algorithm 
## ---------------------------------------------------- 
## 
## Mclust VVV (ellipsoidal, varying volume, shape, and orientation) model with 9
## components: 
## 
##  log-likelihood      n  df      BIC      ICL
##        -1034611 114000 404 -2073925 -2116901
## 
## Clustering table:
##     1     2     3     4     5     6     7     8     9 
##  4798 15733 16787  9253  9310 12287 12434 13524 19874
fviz_cluster(list(data=spotify_reduced, cluster=em_spotify_kX$classification),
             geom = "point", palette = "Set3", ellipse.type = "norm", pointsize = 0.5, 
             ggtheme = theme_minimal(base_size = 12)) + 
  labs(title = "Klastrowanie metodą EM (k=X)", 
       subtitle = "Model EM na 8 składowych PCA", x = "PC1", y = "PC2")

Porównanie BIC

Porównanie BIC dla wariantu \(k=2\) oraz \(k=optymalne\). Im wartość BIC jest bliższa zera (lub większa w implementacji mclust, zależnie od konwencji, tutaj mclust maksymalizuje BIC), tym lepiej.

bic_comparison <- data.frame(
  Model = c("Model k=2", "Model k=X (Optymalny)"),
  BIC_Value = c(em_spotify_k2$bic, em_spotify_kX$bic)
)

ggplot(bic_comparison, aes(x = Model, y = BIC_Value)) + 
  geom_point(size = 5, color = "darkgreen") + 
  geom_line(group = 1, linetype = "dashed") + 
  theme_light() + 
  labs(title = "Różnica w BIC między modelami")

Wnioski techniczne: Wykorzystanie miary Silhouette dla EM nie jest miarodajne (faworyzuje klastry kuliste). W tym celu należy wykorzystać BIC. Kryterium BIC wskazało, że zwiększenie liczby klastrów z \(k=2\) do \(k=9\) pozwala na znacznie lepsze dopasowanie modelu do danych.

Analiza Muzyczna i Profilowanie Klastrów

1. K-means (k=2)

spotify_results <- spotify_raw %>% mutate(Cluster = as.factor(km_spotify$cluster))

cluster_profile <- spotify_results %>%
  group_by(Cluster) %>%
  summarise(
    Liczba_Utworow = n(),
    Popularity = mean(popularity, na.rm = TRUE),
    Danceability = mean(danceability, na.rm = TRUE),
    Energy = mean(energy, na.rm = TRUE),
    Loudness = mean(loudness, na.rm = TRUE),
    Speechiness = mean(speechiness, na.rm = TRUE),
    Acousticness = mean(acousticness, na.rm = TRUE),
    Instrumentalness = mean(instrumentalness, na.rm = TRUE),
    Liveness = mean(liveness, na.rm = TRUE),
    Valence = mean(valence, na.rm = TRUE),
    Tempo = mean(tempo, na.rm = TRUE)
  )

print(cluster_profile)
## # A tibble: 2 × 12
##   Cluster Liczba_Utworow Popularity Danceability Energy Loudness Speechiness
##   <fct>            <int>      <dbl>        <dbl>  <dbl>    <dbl>       <dbl>
## 1 1                86460       33.8        0.598  0.750    -6.35      0.0930
## 2 2                27540       31.3        0.470  0.302   -14.2       0.0585
## # ℹ 5 more variables: Acousticness <dbl>, Instrumentalness <dbl>,
## #   Liveness <dbl>, Valence <dbl>, Tempo <dbl>
top10_genres_km <- spotify_results %>%
  group_by(Cluster, track_genre) %>%
  summarise(Liczba = n()) %>%
  arrange(Cluster, desc(Liczba)) %>%
  group_by(Cluster) %>%
  slice_head(n = 10)

print(top10_genres_km)
## # A tibble: 20 × 3
## # Groups:   Cluster [2]
##    Cluster track_genre       Liczba
##    <fct>   <chr>              <int>
##  1 1       hardstyle           1000
##  2 1       reggaeton           1000
##  3 1       house                998
##  4 1       party                997
##  5 1       drum-and-bass        996
##  6 1       trance               996
##  7 1       edm                  995
##  8 1       metalcore            995
##  9 1       happy                994
## 10 1       progressive-house    994
## 11 2       sleep                973
## 12 2       classical            934
## 13 2       new-age              912
## 14 2       romance              910
## 15 2       ambient              905
## 16 2       opera                869
## 17 2       tango                813
## 18 2       guitar               785
## 19 2       disney               775
## 20 2       honky-tonk           764

Wnioski dla K-means (k=2): * Gatunki: W klastrze 1 najpopularniejsze to hardstyle i reggaeton. W klastrze 2: sleep i classical. * Charakterystyka: Klaster 1 jest nieznacznie bardziej popularny, szybszy i bardziej energiczny. Występuje duża różnica w głośności. Klastrowanie wydaje się słusznie oddzielać muzykę spokojną (sleep, ambient) od energicznej.

2. EM (k=2)

spotify_results_em2 <- spotify_raw %>% mutate(Cluster = as.factor(em_spotify_k2$classification))

cluster_profile_em2 <- spotify_results_em2 %>%
  group_by(Cluster) %>%
  summarise(
    Liczba_Utworow = n(),
    Popularity = mean(popularity, na.rm = TRUE),
    Danceability = mean(danceability, na.rm = TRUE),
    Energy = mean(energy, na.rm = TRUE),
    Loudness = mean(loudness, na.rm = TRUE),
    Speechiness = mean(speechiness, na.rm = TRUE),
    Acousticness = mean(acousticness, na.rm = TRUE),
    Instrumentalness = mean(instrumentalness, na.rm = TRUE),
    Liveness = mean(liveness, na.rm = TRUE),
    Valence = mean(valence, na.rm = TRUE),
    Tempo = mean(tempo, na.rm = TRUE)
  )

print(cluster_profile_em2)
## # A tibble: 2 × 12
##   Cluster Liczba_Utworow Popularity Danceability Energy Loudness Speechiness
##   <fct>            <int>      <dbl>        <dbl>  <dbl>    <dbl>       <dbl>
## 1 1                42976       31.2        0.547  0.628   -10.0       0.134 
## 2 2                71024       34.5        0.579  0.649    -7.18      0.0546
## # ℹ 5 more variables: Acousticness <dbl>, Instrumentalness <dbl>,
## #   Liveness <dbl>, Valence <dbl>, Tempo <dbl>
top10_genres_em2 <- spotify_results_em2 %>%
  group_by(Cluster, track_genre) %>%
  summarise(Liczba = n(), .groups = 'drop') %>%
  arrange(Cluster, desc(Liczba)) %>%
  group_by(Cluster) %>%
  slice_head(n = 10)

print(top10_genres_em2)
## # A tibble: 20 × 3
## # Groups:   Cluster [2]
##    Cluster track_genre       Liczba
##    <fct>   <chr>              <int>
##  1 1       study                977
##  2 1       minimal-techno       959
##  3 1       detroit-techno       942
##  4 1       sleep                942
##  5 1       idm                  906
##  6 1       comedy               905
##  7 1       new-age              898
##  8 1       grindcore            888
##  9 1       classical            838
## 10 1       chicago-house        826
## 11 2       mandopop             970
## 12 2       country              937
## 13 2       rock                 931
## 14 2       acoustic             922
## 15 2       j-pop                903
## 16 2       cantopop             902
## 17 2       singer-songwriter    900
## 18 2       songwriter           900
## 19 2       honky-tonk           892
## 20 2       salsa                884

Wnioski dla EM (k=2): * W przypadku EM podział gatunków wydaje się być bardziej przypadkowy (spokojna muzyka do nauki łączy się z techno). * EM z dwoma klastrami okazał się mniej intuicyjnym wynikiem klastrowania niż K-means.

3. EM Optymalny (k=9)

Sprawdźmy, co kryje się w klastrach dla optymalnego modelu EM (\(k=9\)).

spotify_results_emX <- spotify_raw %>% mutate(Cluster_EMX = as.factor(em_spotify_kX$classification))

cluster_profile_emX <- spotify_results_emX %>%
  group_by(Cluster_EMX) %>%
  summarise(
    Liczba_Utworow = n(),
    Popularity = mean(popularity, na.rm = TRUE),
    Danceability = mean(danceability, na.rm = TRUE),
    Energy = mean(energy, na.rm = TRUE),
    Loudness = mean(loudness, na.rm = TRUE),
    Speechiness = mean(speechiness, na.rm = TRUE),
    Acousticness = mean(acousticness, na.rm = TRUE),
    Instrumentalness = mean(instrumentalness, na.rm = TRUE),
    Liveness = mean(liveness, na.rm = TRUE),
    Valence = mean(valence, na.rm = TRUE),
    Tempo = mean(tempo, na.rm = TRUE)
  )

print(cluster_profile_emX)
## # A tibble: 9 × 12
##   Cluster_EMX Liczba_Utworow Popularity Danceability Energy Loudness Speechiness
##   <fct>                <int>      <dbl>        <dbl>  <dbl>    <dbl>       <dbl>
## 1 1                     4798       27.8        0.488  0.547   -13.1       0.303 
## 2 2                    15733       32.2        0.573  0.867    -4.63      0.0802
## 3 3                    16787       34.7        0.538  0.420    -9.58      0.0372
## 4 4                     9253       25.9        0.515  0.861    -6.45      0.0832
## 5 5                     9310       37.7        0.502  0.684    -7.39      0.0582
## 6 6                    12287       33.2        0.683  0.679    -6.74      0.230 
## 7 7                    12434       30.4        0.520  0.422   -15.0       0.0481
## 8 8                    13524       35.2        0.601  0.521    -9.70      0.0502
## 9 9                    19874       36.0        0.595  0.746    -5.87      0.0449
## # ℹ 5 more variables: Acousticness <dbl>, Instrumentalness <dbl>,
## #   Liveness <dbl>, Valence <dbl>, Tempo <dbl>
top5_genres_emX <- spotify_results_emX %>%
  group_by(Cluster_EMX, track_genre) %>%
  summarise(Liczba = n(), .groups = 'drop') %>%
  arrange(Cluster_EMX, desc(Liczba)) %>%
  group_by(Cluster_EMX) %>%
  slice_head(n = 5)

print(top5_genres_emX)
## # A tibble: 45 × 3
## # Groups:   Cluster_EMX [9]
##    Cluster_EMX track_genre Liczba
##    <fct>       <chr>        <int>
##  1 1           comedy         824
##  2 1           sleep          466
##  3 1           study          255
##  4 1           new-age        172
##  5 1           iranian        160
##  6 2           metalcore      558
##  7 2           party          497
##  8 2           j-idol         434
##  9 2           heavy-metal    354
## 10 2           punk-rock      347
## # ℹ 35 more rows

Podsumowanie Projektu

W ramach projektu przeprowadzono klastrowanie dwoma algorytmami: k-means i EM.

  1. Porównanie obu algorytmów wskazuje, że dla segmentacji na dwa klastry algorytm k-means radzi sobie lepiej (bardziej intuicyjny podział na muzykę energiczną i spokojną).
  2. Jednakże algorytm EM przeprowadzony na 9-ciu klastrach (wskazanych przez kryterium BIC) pozwolił na dokładniejszą i istotnie ciekawszą identyfikację nurtów muzycznych.
    • Algorytm poprawnie oddzielił muzykę spokojną (sleep, study, comedy) od bardziej ekspresyjnej (metal, pop).
    • Klaster 5: Algorytm zgrupował muzykę brazylijską (pagode, sertanejo, samba), wykrywając cechy geograficzne.
    • Klaster 8: Wyodrębnił muzykę, którą można zaklasyfikować jako romantyczną (tango, romance).

Zastosowanie biznesowe: Wykorzystanie klastrowania muzyki może mieć zastosowanie w systemach rekomendacji. Kiedy użytkownik Spotify słucha w danym momencie muzyki zaklasyfikowanej do konkretnego klastra, system może proponować mu podobne utwory z tej samej grupy, tym samym zwiększając średni czas trwania sesji oraz retencję użytkownika.