Uma das minhas músicas favoritas nos últimos tempos tem sido Weird Fishes/Arpeggi do Radiohead. Ela tem sido tão favorita que estou usando as recomendações do Deezer para encontrar outras canções parecidas, mas sem sucesso. O software sugere muita coisa, mas nada que tenha chegado perto de um resultado que me agrade.

Pensando nisso, baixei um conjunto de dados do Kaggle com 1,2 milhão de canções retiradas do Spotify, a fim de encontrar aquelas mais parecidas com Weird Fishes/Arpeggi.

library(janitor)
## 
## Attaching package: 'janitor'
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.0     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
theme_set(theme_bw())

spotify <- read_csv(file = "dados/tracks_features.csv")
## Rows: 1204025 Columns: 24
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (7): id, name, album, album_id, artists, artist_ids, release_date
## dbl (16): track_number, disc_number, danceability, energy, key, loudness, mo...
## lgl  (1): explicit
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
glimpse(spotify)
## Rows: 1,204,025
## Columns: 24
## $ id               <chr> "7lmeHLHBe4nmXzuXc0HDjk", "1wsRitfRRtWyEapl0q22o8", "…
## $ name             <chr> "Testify", "Guerrilla Radio", "Calm Like a Bomb", "Mi…
## $ album            <chr> "The Battle Of Los Angeles", "The Battle Of Los Angel…
## $ album_id         <chr> "2eia0myWFgoHuttJytCxgX", "2eia0myWFgoHuttJytCxgX", "…
## $ artists          <chr> "['Rage Against The Machine']", "['Rage Against The M…
## $ artist_ids       <chr> "['2d0hyoQ5ynDBnkvAbJKORj']", "['2d0hyoQ5ynDBnkvAbJKO…
## $ track_number     <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5,…
## $ disc_number      <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
## $ explicit         <lgl> FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE,…
## $ danceability     <dbl> 0.470, 0.599, 0.315, 0.440, 0.426, 0.298, 0.417, 0.27…
## $ energy           <dbl> 0.978, 0.957, 0.970, 0.967, 0.929, 0.848, 0.976, 0.87…
## $ key              <dbl> 7, 11, 7, 11, 2, 2, 9, 11, 7, 9, 7, 6, 4, 7, 1, 7, 4,…
## $ loudness         <dbl> -5.399, -5.764, -5.424, -5.830, -6.729, -5.947, -6.03…
## $ mode             <dbl> 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,…
## $ speechiness      <dbl> 0.0727, 0.1880, 0.4830, 0.2370, 0.0701, 0.0727, 0.175…
## $ acousticness     <dbl> 0.026100, 0.012900, 0.023400, 0.163000, 0.001620, 0.0…
## $ instrumentalness <dbl> 1.09e-05, 7.06e-05, 2.03e-06, 3.64e-06, 1.05e-01, 1.5…
## $ liveness         <dbl> 0.3560, 0.1550, 0.1220, 0.1210, 0.0789, 0.2010, 0.107…
## $ valence          <dbl> 0.503, 0.489, 0.370, 0.574, 0.539, 0.194, 0.483, 0.61…
## $ tempo            <dbl> 117.906, 103.680, 149.749, 96.752, 127.059, 148.282, …
## $ duration_ms      <dbl> 210133, 206200, 298893, 213640, 205600, 280960, 20204…
## $ time_signature   <dbl> 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,…
## $ year             <dbl> 1999, 1999, 1999, 1999, 1999, 1999, 1999, 1999, 1999,…
## $ release_date     <chr> "1999-11-02", "1999-11-02", "1999-11-02", "1999-11-02…
# limpar nome dos artistas

spotify <- 
  spotify |> 
  mutate(artists = gsub("\\['|'\\]", "", artists))

Minha providência foi encontrar o que acho que deixa esta música tão especial. Para quem não a conhece, ela possui um número elevado de batidas por minuto (caraterística de canções alegres), mas é tocada com acordes menores (caraterística de canções tristes).

spotify |> 
  filter(name == "Weird Fishes/ Arpeggi") |> 
  select(name, artists, tempo, mode, key)
## # A tibble: 1 × 5
##   name                  artists   tempo  mode   key
##   <chr>                 <chr>     <dbl> <dbl> <dbl>
## 1 Weird Fishes/ Arpeggi Radiohead  153.     0    11

Pensando nisso, eu não simplesmente procurei a esmo as canções mais parecidas com Weird Fishes/Arpeggi baseando-me em todas as suas características. Eu primeiro filtrei o conjunto de dados, mantendo apenas

  • canções com batidas por minuto entre 153 ± 5%
  • em tom menor
  • com a mesma nota principal

Guardei o resultado em um novo conjunto de dados chamado candidatas.

p     <- 0.05
tempo <- 153

candidatas <- 
  spotify |> 
  mutate(song = paste(artists, name, sep = " - ")) |> 
  filter(mode == 0) |>
  filter(key == 11) |>
  filter(tempo >= (1-p)*tempo) |>
  filter(tempo <= (1+p)*tempo) |> 
  select(song | where(is.numeric)) |> 
  select(-track_number, -disc_number, -year, -mode, -key) |> 
  distinct(song, .keep_all = TRUE) |> 
  as.data.frame()

row.names(candidatas) <- candidatas$song

candidatas <- 
  candidatas |> 
  select(-song) |> 
  scale()

str(candidatas)
##  num [1:45196, 1:11] -0.4316 -1.2674 1.1118 1.2605 0.0606 ...
##  - attr(*, "dimnames")=List of 2
##   ..$ : chr [1:45196] "Rage Against The Machine - Mic Check" "Rage Against The Machine - Maria" "Will Young - Your Game" "Will Young - Dance The Night Away" ...
##   ..$ : chr [1:11] "danceability" "energy" "loudness" "speechiness" ...
##  - attr(*, "scaled:center")= Named num [1:11] 0.524 0.595 -10.463 0.102 0.323 ...
##   ..- attr(*, "names")= chr [1:11] "danceability" "energy" "loudness" "speechiness" ...
##  - attr(*, "scaled:scale")= Named num [1:11] 0.195 0.282 6.327 0.125 0.362 ...
##   ..- attr(*, "names")= chr [1:11] "danceability" "energy" "loudness" "speechiness" ...
candidatas <- as.data.frame(candidatas)

Em seguida, calculei a distiancia de Manhattan de todas as 45196 músicas que satisfazem estes critérios em relação a Weird Fishes/Arpeggi, guardando estas informações em um novo data frame chamado resultado.

weird_fishes <- candidatas[which(row.names(candidatas) %in% "Radiohead - Weird Fishes/ Arpeggi"), ]

distancia <- function(x){
  d <- dist(matrix(c(weird_fishes, x), nrow = 2, byrow = TRUE), 
            method = "manhattan")
  return(d)
  }

candidatas_distancia <- apply(candidatas, 1, distancia)

resultado <- 
  candidatas_distancia |> 
  bind_cols(str_split_fixed(row.names(candidatas), pattern = " - ", 2)) |> 
  clean_names() |> 
  select(artista = x2,
         cancao = x3,
         distancia = x1)
## New names:
## • `` -> `...1`
## • `` -> `...2`
## • `` -> `...3`

Portanto, estas são, de acordo com a minha análise, as 20 canções mais parecidas com Weird Fishes/Arpeggi:

ArtistaMúsicaDistância
MobyFalling Rain And Light1.813872
Perfume MonsterCastles Fall2.425636
Cone of ConfusionPattern of Ignorance2.580750
Matteo PalmerSouthern Hemisphere2.623682
Steffen ShackingerPerfect Waves2.635186
Travis Larson BandRoom To Breathe2.764531
Cody LeeNew Orleans2.766308
Joel JeromeTotally Confused2.774482
Key Of DreamsGatito2.775946
Alex SeifertWhat's Mine (Orchestral Version)2.791823
Trans-Siberian OrchestraA Mad Russian's Christmas - Instrumental2.826837
Mors SyphiliticaGalatea2.841396
Bryan CarriganSeventh Stone2.843053
Sufjan Stevens', 'Helado NegroAll of Me Wants All of You - Helado Negro Remix2.859638
Roman LeykamThe World as a Canvas2.873808
Rameses BNever Forget2.947742
RooftopsYear as Lift2.963960
LovespiralsOne of Those Days2.967784
Bella MorteSilver Crosses3.000960
TigerforestLonging For The Sun3.010751

Infelizmente, o arquivo que baixei não informa o gênero das canções. Uma informação como esta seria interessante para realizar mais uma pré-filtragem nos dados, mantendo apenas canções de gêneros como alternative, indie, electronica ou outros nos quais o Radiohead se encaixaria.

O resultado final pode ser ouvido encontrado na minha playlist weirder_fishes, no Deezer.