Eh oui, 2017 est déjà (presque) fini… À l’heure qu’il est, on boucle les derniers dossiers de l’année, et on se prépare psychologiquement à aller célébrer la nouvelle année comme il se doit. Bref, « pas le temps de niaiser » comme le disent si bien nos amis d’outre-Atlantique.
Histoire de se changer les idées avant de décoller, nous vous avons concocté un deuxième volet de R de jeu, avec la thématique fin d’année !
Sommaire
All I want for Christmas is data
Se connecter à Spotify, on l’a déjà fait, on ne va pas vous en reparler 😉
library(httr)
app_id <- "app_spotify"
client_id <- "***"
client_secret <- "***"
spoti_ep <- httr::oauth_endpoint(
authorize = "https://accounts.spotify.com/authorize",
access = "https://accounts.spotify.com/api/token")
spoti_app <- httr::oauth_app(app_id, client_id, client_secret)
access_token <- httr::oauth2.0_token(spoti_ep, spoti_app, scope = "user-read-private")
Hop, une bonne chose de faite. On va se faire une petite recherche avec la thématique New Year, vous en pensez quoi ?
tracks <- GET("https://api.spotify.com/v1/search?q=new%20year&type=track",
config(token = access_token), query = list(limit = 50))
content(tracks)$tracks$total
[1] 35890
Est-ce que Spotify va nous laisser aller chercher tout ça ? On a le droit d'y aller 50 par 50... lançons le test 🙂
library(tidyverse)
library(jsonlite)
get_tracks <- function(offset){
print(offset)
api_res <- GET("https://api.spotify.com/v1/search?q=new%20year&type=track",
config(token = access_token), query = list(limit = 50, offset = offset))
played <- api_res$content %>% rawToChar() %>% fromJSON(flatten = TRUE)
return(as_tibble(played$tracks$items))
}
safe_tracks <- safely(get_tracks)
new_year_songs <- map(seq(0, 35890, 50), safe_tracks)
clean_songs <- new_year_songs %>% map("result") %>% compact() %>% bind_rows()
dim(clean_songs)
[1] 35890 25
Il semblerait qu'on ait réussi à toutes les avoir !
clean_songs <- select(clean_songs, artists, duration_ms, explicit,
name, popularity, type, album.name)
Visualisation
Il nous faudrait une petite palette de couleurs spécialement prévue pour le Nouvel An, non ? Aller, on va emprunter celles de Color-Hex.
library(rvest)
library(glue)
get_palettes_list <- function(search){
search <- URLencode(search)
read_html(glue("http://www.color-hex.com/color-palettes/?keyword={search}")) %>%
html_nodes(".palettecontainerlist a") %>%
html_attr("href") %>%
stringr::str_extract_all("([0-9]+)") %>%
as_vector()
}
list_palettes <- get_palettes_list("new year")
get_palette <- function(page){
Sys.sleep(0.5)
read_html(glue("http://www.color-hex.com/color-palette/{page}")) %>%
html_table() %>%
modify_depth(1, "Hex") %>%
as_vector()
}
full_palette <- map(list_palettes, get_palette) %>% as_vector() %>%
unique() %>% discard( ~ .x == "ffffff")
full_palette
[1] "#000934" "#1624a1" "#a2bdf2" "#fc5b8d" "#a8026e"
[6] "#f90000" "#9a1010" "#f4eb1a" "#e1d921" "#e79516"
[11] "#000000" "#cfcfcf" "#14054c" "#fffff0" "#ffd376"
[16] "#ffe700" "#c900ff" "#2f00ff" "#ff00c1" "#7cff00"
On a de quoi faire...
Maintenant, allons regarder rapidement ce qu'il se trame dans ce dataset ? Des tracks plutôt populaires ?
library(ggthemes)
ggplot(clean_songs) +
aes(x = popularity) +
geom_density(fill = sample(palette, 1)) +
labs(title = "Popularité des tracks du nouvel an",
subtitle = "données via Spotify",
x = "Popularité",
y = "Densité",
caption = "thinkr.fr") +
theme_few()
À première vue, quelques morceaux populaires, et un paquet de morceaux moins populaires. Lesquels sont en top de liste ?
clean_songs %>%
select(name, popularity) %>%
top_n(10) %>%
knitr::kable()
name | popularity |
---|---|
New Year’s Day | 70 |
Happy New Year | 56 |
Happy New Year | 65 |
Please Come Home For Christmas | 69 |
My Dear Acquaintance [A Happy New Year] - iTunes Live Session Performance | 52 |
What Are You Doing New Year's Eve? | 58 |
All I Want for Christmas Is New Year's Day | 61 |
All I Want for Christmas Is New Year's Day | 65 |
What Are You Doing New Year's Eve? | 59 |
New Year's Day | 54 |
Et les artistes les plus présents dans notre jeu de données ?
clean_songs %>%
count(artists, sort = TRUE) %>%
top_n(10) %>%
ggplot() +
aes(reorder(artists, n), n) +
geom_col(fill = sample(full_palette, 1)) +
coord_flip() +
labs(title = "Artistes et tracks du nouvel an",
subtitle = "données via Spotify",
x = "Artistes",
y = "Nombre de morceaux",
caption = "thinkr.fr") +
theme_few()
Maintenant, on se ferait bien une fonction de recommandation de playlist, non ? Disons, une fonction qui permet de créer une playlist adaptée au temps qu'on veut y passer. Commençons par une fonction qui va sampler jusqu'à atteindre un certain niveau :
sample_until<- function(tbl, col, threshold){
col <- enquo(col)
vec <- pull(tbl, !! col)
if ( min(vec) > threshold ) stop("Impossible de trouver une combinaison pour ce threshold")
res <- sample_n(tbl, 1)
while( sum( pull(res, !! col) ) < threshold) {
res <- bind_rows(res, sample_n(tbl, 1)) %>% distinct()
}
res
}
sample_until(iris, Sepal.Length, 30) %>% knitr::kable()
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species |
---|---|---|---|---|
6.4 | 3.2 | 5.3 | 2.3 | virginica |
4.9 | 3.1 | 1.5 | 0.2 | setosa |
4.8 | 3.4 | 1.9 | 0.2 | setosa |
5.8 | 2.7 | 5.1 | 1.9 | virginica |
4.6 | 3.1 | 1.5 | 0.2 | setosa |
5.4 | 3.0 | 4.5 | 1.5 | versicolor |
Et voilà ! Combinons maintenant ça avec notre jeu de données de chansons :
clean_songs$artists <- modify_depth(clean_songs$artists, 1, ~ .x$name) %>%
modify_depth(1, ~ paste(.x, collapse = ', ')) %>%
as_vector()
make_me_a_playlist <- function(duree_s, popularite_max, explicit_filter = FALSE){
duree_milli <- duree_s * 1000
good <- clean_songs %>%
filter(popularity <= popularite_max)
if(explicit_filter) good <- filter(good, ! explicit)
sample_until(good, duration_ms, duree_milli) %>%
select(artists, name, duration_ms) %>% knitr::kable()
}
make_me_a_playlist(600, 50)
artists | name | duration_ms |
---|---|---|
Dj Luca Projet | Down with the Trumpets | 188186 |
Sofi Maeda | Merry Christmas & Happy New Year | 77468 |
DJ Lucas | Survival - The Champions Olympionic Music - Karaoke Version Originally Performed By Muse | 320261 |
Saturday Mix Dj. | Royals | 190918 |
\o/
À la recherche de la soirée parfaite
C'est bien beau d'avoir une belle playlist, mais il serait mieux de trouver un endroit pour jouer ces morceaux. Qu'est-ce que nous propose Facebook ?
get_event <- function(fb_url){
res <- GET(fb_url)
res <- content(res)
return(list(id = map(res$data, "id"), paging = res$paging))
}
# La première page :
# Le token est temporaire, ça ne marchera pas chez vous ;)
res <- get_event("https://graph.facebook.com/v2.11/search?q=new%20year&type=event&access_token=EAACEdEose0cBAEwhntvjdDENCoU19y3He5q7L6cHoyL9J3RkpxuZCHGxXxN2UZCfFO5d7Ng90MnMh8lbnSP8cZA71iked9ZBLr6wrredSmGCZCY7BgwIXUGEEd0PRdyuD8lhymPdEJjB413T6fqGra0rQ4Wzb3l23auncQDE2xl5cVpgqyIiBAcazYzbFVo8ZD")
next_page <- try(res$paging$`next`)
id_list <- res$id
while( length(next_page) != 0 ) {
res <- get_event(next_page)
next_page <- try(res$paging$`next`)
id_list <- c(id_list, res$id)
}
76 événements, c'est plutôt pas mal. On va scraper tout ça !
event_details <- function(id){
fromJSON(glue("https://graph.facebook.com/v2.11/{id}?access_token=EAACEdEose0cBAEwhntvjdDENCoU19y3He5q7L6cHoyL9J3RkpxuZCHGxXxN2UZCfFO5d7Ng90MnMh8lbnSP8cZA71iked9ZBLr6wrredSmGCZCY7BgwIXUGEEd0PRdyuD8lhymPdEJjB413T6fqGra0rQ4Wzb3l23auncQDE2xl5cVpgqyIiBAcazYzbFVo8ZD"))
}
all_events <- map(id_list, event_details)
Comme souvent lorsque l'on appelle des API, nous avons une liste irrégulière (en JSON, non-tabulaire), que nous avons besoin de transformer en tableau. Go pour une fonction qui réglera ça !
library(lubridate)
tibble_that <- function(list){
tibble(desc = list$description %||% NA,
start_time = ymd_hms(list$start_time) %||% NA,
end_time = ymd_hms(list$end_time) %||% NA,
name = list$name %||% NA,
place = list$place$name %||% NA,
city = list$place$location$city %||% NA,
country = list$place$location$country %||% NA,
street = list$place$location$street %||% NA,
zip = list$place$location$zip %||% NA,
placeid = list$place$id %||% NA
)
}
events <- map_df(all_events, tibble_that)
Alors, quels pays peut-on viser ?
library(lubridate)
tibble_that <- function(list){
tibble(desc = list$description %||% NA,
start_time = ymd_hms(list$start_time) %||% NA,
end_time = ymd_hms(list$end_time) %||% NA,
name = list$name %||% NA,
place = list$place$name %||% NA,
city = list$place$location$city %||% NA,
country = list$place$location$country %||% NA,
street = list$place$location$street %||% NA,
zip = list$place$location$zip %||% NA,
placeid = list$place$id %||% NA
)
}
events <- map_df(all_events, tibble_that)
events %>%
count(city) %>%
na.omit() %>%
top_n(5) %>%
arrange(desc(n)) %>%
ggplot() +
aes(reorder(city, n), n) +
geom_col(fill = sample(full_palette, 1)) +
coord_flip() +
labs(title = "Villes avec des événements 'New Year'",
subtitle = "données via Facebook",
x = "Ville",
y = "Nombre d'événements",
caption = "thinkr.fr") +
theme_few()
Il va falloir qu'on voit ce qu'on a de plus proche. Petit tour du côté de l'API distance24 ?
get_route <- function(from, to){
if (is.na(to)) {
return(NULL)
}
from_clean <- URLencode(from)
to_clean <- URLencode(to)
a <- GET(glue("http://fr.distance24.org/route.json?stops={from_clean}|{to_clean}"))
tibble(from = from,
to = to,
dist = content(a)$distances[[1]])
}
travel <- map_df(events$city, ~ get_route("Rennes", .x))
Rendez-vous au plus proche :
travel %>%
top_n(- 5) %>%
arrange(dist) %>%
knitr::kable()
from | to | dist |
---|---|---|
Rennes | London | 394 |
Rennes | London | 394 |
Rennes | Gent | 509 |
Rennes | Brussels | 532 |
Rennes | Liverpool | 597 |
Direction Londres alors 😉
Les douze développeurs de minuit
Enfin, on a prévu de bouger... mais il se passe peut-être trop de choses sur GitHub ? Alerte #FOMO
github_app <- oauth_app("github", "***", "***")
access_token <- oauth2.0_token(oauth_endpoints("github"), github_app)
res <- GET(url = "https://api.github.com/search/repositories?q=new+year&sort=stars&order=desc", config(token = access_token))
content(res)$total_count
[1] 1527
get_search <- function(page, search){
Sys.sleep(2)
search <- URLencode(search)
res <- GET(url = glue("https://api.github.com/search/repositories?q={search}&per_page=100&page={page}"),
config(token = access_token))
# Surveillons le rate limit
message(res$headers$`x-ratelimit-remaining`)
res$content %>% rawToChar() %>% fromJSON() %>% .$items %>% discard(is.data.frame)
}
all_github_ny <- map_df(.x = 1:15, .f = ~ get_search(.x, "New Year"))
dim(all_github_ny)
[1] 1000 70
Ah, c'est étrange, 1500 résultats et des bananes, et nous n'avons que les 1000 premiers résultats... On ne peut pas avoir la page 15 ?
GET(url = glue("https://api.github.com/search/repositories?q=New%20year&per_page=100&page=15"),
config(token = access_token))
Response [https://api.github.com/search/repositories?q=New%20year&per_page=100&page=15]
Date: 2017-12-27 16:35
Status: 422
Content-Type: application/json; charset=utf-8
Size: 134 B
{
"message": "Only the first 1000 search results are available",
"documentation_url": "https://developer.github.com/v3/search/"
}
Ceci explique cela ! Seulement les 1000 premiers résultats sont renvoyés par l'API de Github !
Regardons un peu ce qu'il se trame dans ce jeu de données.
all_github_ny %>%
count(language) %>%
na.omit() %>%
top_n(10) %>%
arrange(desc(n)) %>%
ggplot() +
aes(reorder(language, n), n) +
geom_col(fill = sample(full_palette, 1)) +
coord_flip() +
labs(title = "Langages sur Github pour 'New Year'",
subtitle = "données via GitHub",
x = "Langages",
y = "Nombre de repos",
caption = "thinkr.fr") +
theme_few()
Pas beaucoup de R, donc... et plutôt des langages orientés web...
all_github_ny %>%
filter(language == "R") %>%
nrow()
[1] 5
Alors, de quoi ça parle tout ça ?
library(tidytext)
all_github_ny %>%
unnest_tokens(word, description) %>%
anti_join(stop_words) %>%
count(word) %>%
na.omit() %>%
top_n(10) %>%
knitr::kable()
word | n |
---|---|
2016 | 32 |
2017 | 33 |
app | 39 |
countdown | 34 |
final | 35 |
game | 22 |
happy | 91 |
project | 77 |
year's | 42 |
新年 | 23 |
的 | 22 |
Au final, pas mal d'application web en JavaScript, HTML et CSS pour le final countdown...
Allez, c'est pas tout, mais on a un Nouvel An à développer nous 😉
Laisser un commentaire