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).
library(tidyverse)
library(factoextra)
library(corrplot)
library(cluster)
library(mclust)
library(plotly)
# 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
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
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)
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.
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]
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.
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 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)")
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%).
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_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")
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
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 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.
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.
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.
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
W ramach projektu przeprowadzono klastrowanie dwoma algorytmami: k-means i EM.
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.