Vilken är Kents mest deprimerande låt?
Nov 7, 2017
Filip Wästberg
22 minute read

Charlie Thompson är en analytiker som räknat ut vilken som är Radioheads mest deprimerande låtar. Den blev ganska träffsäker så jag tänkte pröva en liknande metod på Sveriges största depp-rockband: Kent.

Sååå! Vilken är egentligen Kents mest deprimerande låt?

Till min hjälp har jag det statistiska programmeringsspråket R, med vilket jag kan hämta data från Spotify och låttexter från hemsidan Genius. Tanken är att jag i R skapar en modell som slår samman allt det här till ett depp-index.

Spotfiy delar upp sin musik i en rad olika variabler, bl.a. valence, som är ett psykologiskt begrepp för att bland annat mäta hur positiv en låt är. Valence tar dock (vad jag vet) inte hänsyn till texterna och alla vi som älskar deppig musik vet att texten är central. Därför kommer jag (precis som Charlie) även att analysera texterna.

Vill du bara veta svarat på frågan? Klicka här så slipper du allt det tekniska. Är du intresserad av hur jag tagit fram depp-indexet får du gärna läsa vidare.

Få tillgång från Spotify

För att komma åt Spotifys data behöver du gå in på Spotfiy och få nycklar för att komma åt deras API. När du loggat in och fått dem specificerar du dom i din environment:

Sys.setenv(SPOTIFY_CLIENT_ID = [YOUR_CLIENT_ID])
Sys.setenv(SPOTIFY_CLIENT_SECRET = [YOUR_CLIENT_SECRET])

Ladda ner data från Spotfiy

Paketet spotfiyr av Radiohead-analytikern Charlie Thompson gör det möjligt att ladda ner Spotifys data. Paketet funkade dock inte riktigt som jag tänkt mig utan jag var tvungen att modifiera en funktion för att kunna ladda ner data. Mer specifikt så tog originalfunktionen alltid den översta artisten i artistlistan. I det här fallet fick jag data för nån Kent Jones(!?) istället för Kent. Min funktionen filtrerar helt enkelt bort alla som inte är svenska Kent.

library(spotifyr)

## Här är min modifierade version av funktionen get_artist_audio_features() i paketet spotfiyr
get_artist_audio_features_ed <- function (artist_name, access_token = get_spotify_access_token()) 
{
  artist <- get_artists(artist_name) %>% 
    filter(artist_name == "kent") %>% 
    select(artist_uri) %>% 
    .[[1]]
  albums <- get_albums(artist) %>% 
    select(-c(base_album, num_albums, num_base_albums, album_rank))
  album_popularity <- get_album_popularity(albums)
  tracks <- get_album_tracks(albums)
  track_features <- get_track_audio_features(tracks)
  track_popularity <- get_track_popularity(tracks)
  tots <- albums %>% 
    left_join(album_popularity, by = "album_uri") %>% 
    left_join(tracks, by = "album_name") %>% 
    left_join(track_features, by = "track_uri") %>% 
    left_join(track_popularity, by = "track_uri")
  return(tots)
}

kent_df <- get_artist_audio_features_ed(artist_name = "kent")

Tada! Nu har jag 131 låtar av Kent uppdelat på album, valence m.m.

kent_df %>%
  select(album_name, track_name, valence) %>%
  head()
##   album_name              track_name valence
## 1       Kent                Blåjeans   0.419
## 2       Kent              Som vatten   0.637
## 3       Kent      Ingenting någonsin   0.265
## 4       Kent När det blåser på månen   0.148
## 5       Kent Jag vill inte vara rädd   0.553
## 6       Kent     Vad två öron klarar   0.135

Ett problem som dyker upp är att Kent i slutet av 90-talet gjorde en satsning utanför Sverige och släppte skivorna Isola och Hagnesta Hill på engelska. Av någon anledning får man av Spotfiy endast de engelska versionerna av de här albumen. Det här innebär tyvärr också att bland annat låten Ett tidsfördriv att dö för inte inkluderas i analysen. Den låten tror jag dessutom hade hamnat högt upp i vårt depp-index.

kent_df %>%
  filter(album_name %in% c("Hagnesta Hill", "Isola")) %>%
  select(album_name, track_name) %>%
  head(10)
##    album_name         track_name
## 1       Isola         Lifesavers
## 2       Isola   If You Were Here
## 3       Isola    Things She Said
## 4       Isola     Unprofessional
## 5       Isola                OWC
## 6       Isola            Celsius
## 7       Isola             Bianca
## 8       Isola Before It All Ends
## 9       Isola              Elvis
## 10      Isola             Velvet

Men eftersom vi senare ska analysera de svenska texterna och Spotifys valence inte borde påverkas av språk så ger jag bara låtarna deras ursprungstitlar.

kent_df$track_name[kent_df$track_name == "Lifesavers"] <- "livräddaren"
kent_df$track_name[kent_df$track_name == "If You Were Here"] <- "om du var här"
kent_df$track_name[kent_df$track_name == "Things She Said"] <- "saker man ser"
kent_df$track_name[kent_df$track_name == "Unprofessional"] <- "oprofessionell"
kent_df$track_name[kent_df$track_name == "Before It All Ends"] <- "innan allting tar slut"
kent_df$track_name[kent_df$track_name == "747 (We Ran Out Of Time)"] <- "747"
kent_df$track_name[kent_df$track_name == "The King Is Dead"] <- "kungen är död"
kent_df$track_name[kent_df$track_name == "Music Non Stop"] <- "musik non stop"
kent_df$track_name[kent_df$track_name == "Kevlar Soul"] <- "kevlarsjäl"
kent_df$track_name[kent_df$track_name == "Stop Me June (Little Ego)"] <- "stoppa mig juni (lilla ego)"
kent_df$track_name[kent_df$track_name == "Heavily Junkies"] <- "en himmelsk drog"
kent_df$track_name[kent_df$track_name == "Stay With Me"] <- "stanna hos mig"
kent_df$track_name[kent_df$track_name == "Rollercoaster"] <- "berg & dalvana"
kent_df$track_name[kent_df$track_name == "Protection"] <- "beskyddaren"
kent_df$track_name[kent_df$track_name == "Whistle song"] <- "visslaren"

Ladda ner Kents texter

Genius är en hemsida som samlar låttexter. Har jag förstått det rätt är det lite som ett Wikipedia för låttexter. Varför Genius och inte Kents officiella hemsida? Helt enkelt för att Genius har en API-lösning för smidig nedladdning av texter.

För att komma åt Genius API behöver man precis som för Spotify skaffa ett konto och en token hos deras developer-sida, det gör man här.

Likt Spotify finns det på Genius en del andra band som har Kent i artistnamnet. En sökning på “kent” generar därför fler resultat än vi vill ha. Därför behöver vi Kents Genius artist-ID. Radioehad-Charlie har redan skrivit en funktion för att söka mot Genius API, det enda vi behöver göra är att filtrera så att vi endast får texter från det svenska bandet Kent.

library(dplyr)
library(httr)
library(purrr)
token <- "xxxxxxxxxxxxxxxxxxx"

genius_get_artists <- function(artist_name, n_results = 10) {
    baseURL <- 'https://api.genius.com/search?q=' 
    requestURL <- paste0(baseURL, gsub(' ', '%20', artist_name),
                         '&per_page=', n_results,
                         '&access_token=', token)
    
    res <- GET(requestURL) %>% content %>%
      .$response %>% .$hits
    
    map_df(1:length(res), function(x) {
        tmp <- res[[x]]$result$primary_artist
        list(
            artist_id = tmp$id,
            artist_name = tmp$name
        )
    }) %>% unique
}

genius_artists <- genius_get_artists('kent') %>%
  filter(artist_name == "Kent")

Hitta URL:er till låtarna

När vi nu har Kents artist_id kan vi loopa igenom alla Kents låtar på Genius och ladda ner alla URL:s till låtarna. I bakgrunden använder vi paketet httr

library(httr)
baseURL <- 'https://api.genius.com/artists/' 
requestURL <- paste0(baseURL, genius_artists$artist_id[1], '/songs')

track_lyric_urls <- list()
i <- 1
while (i > 0) {
  tmp <- GET(requestURL,
             query = list(access_token = token,
                          per_page = 50, page = i)) %>% 
    content %>% 
    .$response
  track_lyric_urls <- c(track_lyric_urls, tmp$songs)
  if (!is.null(tmp$next_page)) {
    i <- tmp$next_page
  } else {
    break
  }
}

Ladda ner texterna

För att ladda ner texterna använder vi paketet rvest som är ett paket för så kallad web scraping, alltså att ladda ner innehåll från hemsidor. När texterna är nedladdade gör vi lite textbearbetning för att bli av med onödiga tomrum, punkter, radbrytning m.m. Att ladda ner texterna tar några minuter.

library(rvest)
library(stringr)

lyric_scraper <- function(url) {
  read_html(url) %>% 
    html_node('p') %>% 
    html_text()
}

genius_df <- map_df(1:length(track_lyric_urls), function(x) {
 
  lyrics <- try(lyric_scraper(track_lyric_urls[[x]]$url))
  if (class(lyrics) != 'try-error') {
    # Här görs textredigeringarna
    lyrics <- str_replace_all(lyrics, '\\[(Verse [[:digit:]]|Pre-Chorus [[:digit:]]|Hook [[:digit:]]|Chorus|Outro|Verse|Refrain|Hook|Bridge|Intro|Instrumental)\\]|[[:digit:]]|[\\.!?\\(\\)\\[\\],]', '')
    lyrics <- str_replace_all(lyrics, '\\n', ' ')
    lyrics <- str_replace_all(lyrics, '([A-Z])', ' \\1')
    lyrics <- str_replace_all(lyrics, ' {2,}', ' ')
    lyrics <- tolower(str_trim(lyrics))
  } else {
    lyrics <- NA
  }
  
  tots <- list(
    track_name = track_lyric_urls[[x]]$title,
    lyrics = lyrics
  )
  
  return(tots)
})

Voila! Nu har vi alla texter nedladdade. För att slå ihop texterna med data från Spotify behöver vi ta bort lite specialtecken (&,-) och låtnamnen ska (som i datan från Spotify) vara i gemener. Vi passar även på att filtrera bort dubletter.

genius_df_rens <- genius_df %>%
  mutate(track_name = tolower(str_replace(track_name, '[[:punct:]]', ''))) %>%
  filter(!duplicated(track_name))

Vi kan nu slå ihop data från Genius med data från Spotify. Då blir vi dessutom av med alla spår vi inte är intresserade av att analysera såsom demos och remixes.

track_df <- kent_df %>%
  mutate(track_name = tolower(
    str_replace(track_name, '[[:punct:]]', ''))) %>%
  left_join(genius_df_rens, by = 'track_name') %>%
  select(track_name, valence, duration_ms,
         lyrics, album_name, album_release_year, album_img)

Nu har vi alla låtar med Spotifys variabler och texter från Genius. Ett problem (till) som nu uppenbarar sig är att några av texterna som finns på Genius är på engelska trots att de bara finns på svenska. De är alltså översatta av medlemmar på Genius (gissningsvis) åt Kent-fans som inte kan svenska. Kul för fansen, men störigt för oss. Det här problemet finns dock bara på skivan Jag är inte rädd för mörkret. Lösningen på det här blev att ladda ner texterna från Kents hemsida. Den har inte samma smidiga API som Genius men för de här låtarna funkade det bra.

Jag använde återigen rvest och byggde en funktion för att ladda ner texterna.

kent_nu <- function(song){
  lyrics <- read_html(paste("http://kent.nu/latar/", song)) %>%
    html_nodes(xpath="//h2[contains(., 'Text')]/following-sibling::p") %>%
    html_text() %>%
    paste0(collapse = " ")
  lyrics <- str_replace_all(lyrics, '\\n', ' ')
  lyrics <- str_replace_all(lyrics, '([A-Z])', ' \\1')
  lyrics <- str_replace_all(lyrics, ' {2,}', ' ')
  lyrics <- tolower(str_trim(lyrics))
  return(lyrics)
}

track_df$lyrics[track_df$track_name == "isis  bast"] <- 
  kent_nu(song = "isis-bast")[[1]]
track_df$lyrics[track_df$track_name == "jag ser dig"] <- 
  kent_nu(song = "jag-ser-dig")[[1]]
track_df$lyrics[track_df$track_name == "tänd på"] <- 
  kent_nu(song = "tand-pa")[[1]]
track_df$lyrics[track_df$track_name == "beredd på allt"] <- 
  kent_nu(song = "beredd-pa-allt")[[1]]
track_df$lyrics[track_df$track_name == "färger på natten"] <- 
  kent_nu(song = "farger-pa-natten")[[1]]
track_df$lyrics[track_df$track_name == "låt dom komma"] <- 
  kent_nu(song = "lat-dom-komma")[[1]]
track_df$lyrics[track_df$track_name == "halka"] <- 
  kent_nu(song = "halka")[[1]]

Till sist har vi svenska texter där det ska vara svenska texter och dessutom har vi sett till att det är de svenska versionerna av Hagnesta Hill och Isola vi har. Dock finns det några låtar på de engelska skivorna som inte finns med på de svenska, exempelvis Velvet och Just Like Money. Dessa vill vi bli av med.

Vi använder paketet textcat som är ett paket för att kategorisera språk. Vi filtrerar helt enkelt bort alla låtar som inte är på svenska.

library(textcat)
track_df <- track_df %>%
  filter(textcat(lyrics) == "swedish")

Nu kan vi börja analysera!

Analys

För det första kan vi bara undersöka hur träffsäker Spotifys valence är.

track_df %>%
  select(track_name, valence) %>%
  arrange(valence) %>%
  head(10)
##                            track_name valence
## 1                               18294  0.0670
## 2           stoppa mig juni lilla ego  0.0703
## 3                         gravitation  0.0805
## 4                           lsd någon  0.0847
## 5                       berg  dalvana  0.0911
## 6                 den osynliga mannen  0.0940
## 7               vi är inte längre där  0.0955
## 8                     rosor  palmblad  0.0994
## 9  vals för satan din vän pessimisten  0.1030
## 10                        krossa allt  0.1190

Nummer ett är 18:29-4 som är ett introspår på skivan Röd. På låten sjunger inte Jocke Berg utan någon slags pensionärskör. Givet att låten med mening (tror jag) sjungs ganska falskt och har en ljudbild som skiljer sig från resten av skivan vet jag inte riktigt hur Spotify analyserar spåret. En låt som jag tror många Kent-fans skulle placera högt upp i ett depp-index är nummer två Stoppa mig juni (Lilla ego), mitt 14 åriga jag approves. Även Gravitation och LSD, någon? känns ganska givna. Men Den osynliga mannen?

För att vässa den här listan lite behöver vi nog analysera texterna.

Andel negativa ord

För analysera texterna använder jag mig av Språkbankens sentiment-lexikon. Det är, kort och gott, ett lexikon över en mängd svenska ord samt vilket sentiment de har, alltså om de är negativa eller positiva.

Språkbankens lexikon finns som ett R-paket. Paketet är skrivet av Måns Magnusson som tidigare uppmärksammats på den här bloggen för sitt arbete med olika paket i R. Paketet heter rlexswesent och finns på Github.

Jag filtrerar bort alla positiva ord (det är ju trots allt ett depp-index) och behåller strength och confidence.

library(rlexswesent)
data("swesent", package = "rlexswesent")

neg_ord <- swesent %>%
  filter(polarity == "neg") %>%
  mutate(neg = 1) %>%
  select(word, neg, strength, confidence)

Härefter använder jag paketet tidytext för att skapa en data frame med alla ord i kentlåtarna uppdelat på album och låtnamn tillsammans med de deppiga orden från Språkbanken och stoppord på svenska från paketet tm. Eftersom jag tidigare skapade variabeln neg kan jag enkelt se vilka ord som klassificierats som negativa.

library(tidytext)
library(tm)

stop_words_df <- data.frame(word = as.character(stopwords(kind = "sv")))
track_df$lyrics <- as.character(track_df$lyrics)

sent_df <- track_df %>% 
  unnest_tokens(word, lyrics) %>%
  anti_join(stop_words_df, by = 'word') %>%
  left_join(neg_ord, by = 'word')

Nästa steg är att räkna andelen deppiga ord i varje låt. Eftersom vi även har variabel för ordets styrka och confidence (d.v.s. hur säker Språkbanken är på att ordets innebörd) väger vi siffran med dessa variabler.

sent_df_neg <- sent_df %>%
  group_by(track_name) %>% 
  summarise(pct_depp = round(sum(neg*strength*confidence*-1,
                                na.rm = T)/ n(), 4),
            word_count = n()) %>% 
  ungroup

I fallet Radiohead tyckte Charlie Thompson att det var nödvändigt att ha med en variabel för text-densitet. D.v.s. antalet ord i låten dividerat med längden på låten. Text-densiteten var tänkt att vara en indikation på hur viktig texten var för låten.

Jag håller inte riktigt med om det. Många av Kents starkaste låtar har långa instrumentella delar, exempelvis Ensammast i Sverige. Därför använde jag endast Spotifys valence och andel deppiga ord i min modell och beräknade om det till ett index mellan 1-100.

\[DeppIndex=\frac{(1-valence) + procentDepp}{2}\]

library(scales)
track_df_depp <- track_df %>% 
  left_join(sent_df_neg, by = 'track_name') %>% 
  mutate_at(c('pct_depp', 'word_count'), funs(ifelse(is.na(.), 0, .))) %>% 
  mutate(deppindex = 
           round(rescale(1 - ((1 - valence) + (pct_depp)) / 2,
                         to = c(1, 100)), 2))

depp_table <- track_df_depp %>%
  select(deppindex, track_name) %>%
  arrange(deppindex) %>%
  head(10) %>%
  htmlTable::htmlTable(header = c("Depp-index", "Låt"))

Och vi har ett resultat…

depp_table
Depp-index Låt
1 1 stoppa mig juni lilla ego
2 2.79 gravitation
3 3.25 berg dalvana
4 6.74 rosor palmblad
5 7.9 hjärta
6 8.07 lsd någon
7 8.19 18294
8 10.08 vals för satan din vän pessimisten
9 10.26 vi är inte längre där
10 10.53 thinner

Jag använder paketet plotly för att visualisera resultatet. Innan själva visualiseringen skapar jag ett medelvärde för varje albums depp-index som jag också använder i visualiseringen.

depp_per_år <- track_df_depp %>%
  group_by(album_release_year, album_name) %>%
  summarise(Medeldepp = round(mean(deppindex), 2))

track_df_depp <- left_join(track_df_depp, depp_per_år)

library(plotly)
p_depp <- track_df_depp %>% 
  plot_ly(x = ~as.Date(substr(album_release_year, 1, 4), format = "%Y"),
          y = ~deppindex,
          hoverinfo = 'text',
          text = ~paste('Album: ', album_name,
                        '<br> Låt: ', track_name,
                        '<br> Depp-index: ', deppindex,
                        '<br>År: ', album_release_year)) %>%
  add_markers(color = ~album_name) %>%
  add_lines(y =~Medeldepp,
            text = ~paste('Album:', album_name,
                          '<br>Medeldepp: ', round(Medeldepp,2))) %>%
    layout(title = "Kents mest deprimerande låtar 1995-2016",
    showlegend = FALSE, 
         yaxis = list(title = "Depp-index"),
         xaxis = list(title = "", tickformat = "%Y"))

Och här har vi slutresultatet!