Data & Vinyles — Exploration d’une bibliothèque Discogs avec R

7 minute(s) read

Amoureux de données et de vinyles, je me suis amusé à envoyer quelques requêtes sur l’API Discogs avec R, pour en savoir un peu plus sur ma collection.

Réseau social incontournable des amateurs du disque microsillon, Discogs offre une API permettant de jongler entre musique et données en quelques lignes de code.

Note : pour retrouver les données utilisées dans ce billet, vous pouvez télécharger le document JSON ici, ou directement dans R :

collection_complete <- jsonlite::fromJSON(txt = "http://colinfay.me/data/collection_complete.json", simplifyDataFrame = TRUE)

Major Tom to Discogs API

Avant de se lancer, chargeons dans l’environnement deux fonctions qui me seront indispensables : _%>% et %||%.

library(magrittr)
`%||%` <- function(a,b) if(is.null(a)) b else a

Bien bien, commençons par faire venir le profil Discogs :

user <- "_colin"
content <- httr::GET(paste0("https://api.discogs.com/users/", user, "/collection/folders"))
content <- rjson::fromJSON(rawToChar(content$content))$folders
content
## [[1]]
## [[1]]$count
## [1] 308
## 
## [[1]]$resource_url
## [1] "https://api.discogs.com/users/_colin/collection/folders/0"
## 
## [[1]]$id
## [1] 0
## 
## [[1]]$name
## [1] "All"

Cette première requête nous permet d’obtenir les informations basiques sur l’utilisateur (en l’occurrence “_colin“, principal intéressé de ce billet de blog).

$count
``` nous apprend notamment que la collection compte 308 entrées. L’élément de la liste ```r 
$id
``` renvoie le nombre de “folders” Discogs créées par l’utilisateur – ici, 0 correspond à l’ensemble de la collection, sans spécification de liste.
### Créer un data.frame avec l’ensemble des vinyles présents dans la collection
L’API Discogs permet d'accéder à des pages de 100 résultats maximum. Ma collection contenant 308 entrées, nous devons réaliser une boucle ```r 
repeat
``` qui collectera l’ensemble des données, avant de créer un tableau final contenant l’ensemble de la collection.
```r 
collec_url <- httr::GET(paste0("https://api.discogs.com/users/", user, "/collection/folders/", content[[1]]$id, "/releases?page=1&amp;per_page=100"))
if (collec_url$status_code == 200){
  collec <- rjson::fromJSON(rawToChar(collec_url$content))
  collecdata <- collec$releases
    if(!is.null(collec$pagination$urls$`next`)){
      repeat{
        url <- httr::GET(collec$pagination$urls$`next`)
        collec <- rjson::fromJSON(rawToChar(url$content))
        collecdata <- c(collecdata, collec$releases)
        if(is.null(collec$pagination$urls$`next`)){
          break
        }
      }
    }
}
  collection <- lapply(collecdata, function(obj){
    data.frame(release_id = obj$basic_information$id %||% NA,
               label = obj$basic_information$labels[[1]]$name %||% NA,
               year = obj$basic_information$year %||% NA,
               title = obj$basic_information$title %||% NA, 
               artist_name = obj$basic_information$artists[[1]]$name %||% NA,
               artist_id = obj$basic_information$artists[[1]]$id %||% NA,
               artist_resource_url = obj$basic_information$artists[[1]]$resource_url %||% NA, 
               format = obj$basic_information$formats[[1]]$name %||% NA,
               resource_url = obj$basic_information$resource_url %||% NA)
  }) %>% do.call(rbind, .) %>% 
    unique()

Et pour un aperçu du tableau obtenu :

library(pander)
pander(head(collection))
Table continues below
release_id label year title
5181773 A&M Records 1982 Night And Day
3690646 A&M Records (2) 2012 God Save The Queen
944917 Alexi Delano Limited 2007 The Acid Sessions Vol. 4
906983 Alphabet City 2007 Urban Minds / Skattered
8112758 Amerilys 1986 Follement Vôtre
5800664 Anette Records 2014 And The Dead Shall Lie There
Table continues below
artist_name artist_id artist_resource_url format
Joe Jackson 75280 https://api.discogs.com/artists/75280 Vinyl
Sex Pistols 31753 https://api.discogs.com/artists/31753 Vinyl
Alexi Delano 26 https://api.discogs.com/artists/26 Vinyl
Pacjam 488187 https://api.discogs.com/artists/488187 Vinyl
Diane Dufresne 647100 https://api.discogs.com/artists/647100 Vinyl
Ancient Mith 302464 https://api.discogs.com/artists/302464 Vinyl
resource_url
https://api.discogs.com/releases/5181773
https://api.discogs.com/releases/3690646
https://api.discogs.com/releases/944917
https://api.discogs.com/releases/906983
https://api.discogs.com/releases/8112758
https://api.discogs.com/releases/5800664

I can’t see, I can’t see I’m going blind

Bon, il est temps de mettre tout cela en forme, non ?

Les labels les plus représentés

library(ggplot2)
ggplot(as.data.frame(head(sort(table(collection$label), decreasing = TRUE), 10)), aes(x = reorder(Var1, Freq), y = Freq)) + 
  geom_bar(stat = "identity", fill = "#B79477") + 
  coord_flip() + 
  xlab("Label") +
  ylab("Fréquence") +
  ggtitle("Labels les plus fréquents")

labels les plus représentés dans la collection discogs

Philips et Polydor, what a surprise!

Les artistes les plus représentés

ggplot(as.data.frame(head(sort(table(collection$artist_name), decreasing = TRUE), 10)), aes(x = reorder(Var1, Freq), y = Freq)) + 
  geom_bar(stat = "identity", fill = "#B79477") + 
  coord_flip() + 
  xlab("Artistes") +
  ylab("Fréquence") +
  ggtitle("Artistes les plus fréquents")

Artistes les plus représentés

Bon, voilà, vous le savez… j’aime beaucoup Serge Gainsbourg et Georges Brassens (j’assume). La présence forte de Various était prévisible : il s’agit d’un terme générique faisant référence aux compilations, il est donc plus facile de gonfler ce chiffre que celui d’un artiste “solo”.

Date de sortie des vinyles de la collection

ggplot(dplyr::filter(collection, year != 0), aes(x = year)) + 
  geom_bar(stat = "count", fill = "#B79477") + 
  xlab("Année de sortie") +
  ylab("Fréquence") +
  ggtitle("Date de sorties des vinyles de la collection")

Dates de sorties

Woaw, j’ai comme l’impression que je ne suis pas un super fan des années 90… Pourtant, il y a de super titres, non ? Ma collection se concentre sur un gros pic autour des années 80 et 00, avec un mode en 1980.

It’s time to go deeper

Bien, ces infos un temps soit peu basiques nous offrent déjà une première vision des vinyles dans ma collection. Et si l’on allait plus loin ?

Hello, it’s me again

_Note: depuis l’écriture de cet article, Discogs semble avoir intégré une limitation au volume de calls par minute. Afin de créer collection_2, vous devez faire à Sys.sleep(). En savoir plus.

collection_2 <- lapply(as.list(collection$release_id), function(obj){
  url <- httr::GET(paste0("https://api.discogs.com/releases/", obj))
  url <- rjson::fromJSON(rawToChar(url$content))
  data.frame(release_id = obj, 
             label = url$label[[1]]$name %||% NA,
             year = url$year %||% NA, 
             title = url$title %||% NA, 
             artist_name = url$artist[[1]]$name %||% NA, 
             styles = url$styles[[1]] %||% NA,
             genre = url$genre[[1]] %||% NA,
             average_note = url$community$rating$average %||% NA, 
             votes = url$community$rating$count %||% NA, 
             want = url$community$want %||% NA, 
             have = url$community$have %||% NA, 
             lowest_price = url$lowest_price %||% NA, 
             country = url$country %||% NA)
}) %>% do.call(rbind, .) %>% 
  unique()

Ici, nous utilisons le release_id pour aller à la pêche aux informations pour toutes les entrées de la base.

Les genres les plus représentés

ggplot(as.data.frame(head(sort(table(collection_2$genre), decreasing = TRUE), 10)), aes(x = reorder(Var1, Freq), y = Freq)) + 
  geom_bar(stat = "identity", fill = "#B79477") + 
  coord_flip() + 
  xlab("Genre") +
  ylab("Fréquence") +
  ggtitle("Genres les plus fréquents")

Genres les plus fréquentsOH GOD, quelle surprise ! Eh oui, presque la moitié de ma collection contient des vinyles de rock (tous styles confondus). Qui aurait pu s’en douter ?

Pays d’origine des vinyles

ggplot(as.data.frame(head(sort(table(collection_2$country), decreasing = TRUE), 10)), aes(x = reorder(Var1, Freq), y = Freq)) + 
  geom_bar(stat = "identity", fill = "#B79477") + 
  coord_flip() + 
  xlab("Pays d'origine") +
  ylab("Fréquence") +
  ggtitle("Pays les plus fréquents")

Pays d'origine des vinylesBon, une large partie des vinyles venus de France, ça se tient :)

Notes moyennes des vinyles

ggplot(collection_2, aes(x = average_note)) + 
  geom_histogram(fill = "#B79477") +
  xlab("Note moyenne") +
  ylab("Fréquence") +
  ggtitle("Notes moyennes des vinyles de la collection")

D’après la communauté Discogs j’ai plutôt de bons goûts musicaux… Merci pour cet ego boost ! (Oui, je compte ignorer la présence de 10 vinyles notés 0… Après tout, on a tous les droits à ses petits guilty pleasure…)

Money Money Money

ggplot(collection_2, aes(x = lowest_price)) + 
  geom_histogram(fill = "#B79477") +
  xlab("Prix le plus bas") +
  ylab("Fréquence") +
  ggtitle("Prix le plus bas des vinyles de la collection")

densite-prix-bas

Bon… Ce n’est visiblement pas en vendant ma collection que je vais faire fortune. Cela dit, je ne comptais pas m’en séparer, cela tombe plutôt bien.

Let’s finish!

collection_complete <- merge(collection, collection_2, by = c("release_id","label", "year", "title", "artist_name"))

Prix des vinyles en fonction des “want”

lm_want <- lm(formula = lowest_price ~ want, data = collection_complete)
summary(lm_want)
##Residuals:
##   Min     1Q Median     3Q    Max 
##-8.043 -4.628 -2.224  2.179 49.608 

##Coefficients:
##            Estimate Std. Error t value Pr(>|t|)    
##(Intercept) 6.715418   0.450582  14.904  &lt; 2e-16 ***
##want        0.005004   0.001788   2.799  0.00546 ** 
##---
##Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

##Residual standard error: 7.306 on 301 degrees of freedom
##  (5 observations deleted due to missingness)
##Multiple R-squared:  0.02536,    Adjusted R-squared:  0.02213 
##F-statistic: 7.833 on 1 and 301 DF,  p-value: 0.005461

Ici, nous voyons que le coefficient de want est significatif, avec une probabilité critique de 0.005. Nous pouvons donc conclure que, pour cette discothèque, le volume de want est significativement lié à la variable prix.

ggplot(collection_complete, aes(x = lowest_price, y = want)) + 
  geom_point(size = 3, color = "#B79477") +
  geom_smooth(method = "lm") + 
  xlab("Prix le plus bas") +
  ylab("Nombre de \"want\"") +
  ggtitle("Prix et \"want\" des vinyles de la collection")

Prix en fonction du nombre de want

En clair : sur Discogs, il est possible d’entrer des vinyles dans une “liste d’envie”, labellisée “want” dans notre population. Ici, nous avons dessiné la régression linéaire du prix le plus bas en fonction du nombre de personnes ayant listé cette sortie dans leur liste d’envie. La tendance est légère, avec beaucoup de bruit lorsque nous nous rapprochons des prix les plus élevés.

Prix des vinyles en fonction des notes

lm_note <- lm(formula = lowest_price ~ average_note, data = collection_complete)
lm_note$coefficients
##  (Intercept) average_note 
##    -1.504767     2.207834

Ici, le coefficient étant de 2.2, nous ne pouvons pas lier la variable note à la variable prix. Visualisons les données pour afficher la dispersion.

ggplot(collection_complete, aes(x = lowest_price, y = average_note)) + 
  geom_point(size = 3, color = "#B79477") +
  xlab("Prix le plus bas") +
  ylab("Note moyenne") +
  ylim(c(0,5)) +
  ggtitle("Prix et notes des vinyles de la collection")

Prix en fonction des notes

And to conclude…

La prochaine étape ? Faire un package pour accéder à l’API… et pourquoi pas ? Je mets ça sur ma to-do !

What do you think?