Generación de un flyer de un festival a partir de datos de Spotify personales (artistas más escuchados)
Hace unos días se difundió el uso de una aplicación para generar un flyer de un festival ficticio a partir de datos de tus artistas más escuchados en Spotify. Esto me llevó a querer reproducir el análisis en R.
A continuación se presentan los pasos para generar el flyer de tu festival:
Definición de fuente a utilizar para el texto:
font_add_google(name = "Slackey", family = "custom_font")
showtext_auto()
Para poder acceder a datos de Spotify, se utilizará el paquete {spotifyr} 📦. Realizamos un taller desde RladiesBA sobre el tema: Creando tablas en {gt} con Spotify API & Bad Bunny e incluimos un Documento de configuración de credenciales
Definición de credenciales para la conexión a la API de Spotify:
credentials <- fromJSON(file = "credentials.json")
Sys.setenv(SPOTIFY_CLIENT_ID = credentials$SPOTIFY_CLIENT_ID)
Sys.setenv(SPOTIFY_CLIENT_SECRET = credentials$SPOTIFY_CLIENT_SECRET)
access_token <- get_spotify_access_token(
client_id = Sys.getenv("SPOTIFY_CLIENT_ID"),
client_secret = Sys.getenv("SPOTIFY_CLIENT_SECRET")
)
Se obtiendn datos de los 50 artistas más escuchados en el mediano plazo:
df <- get_my_top_artists_or_tracks(type = 'artists',
time_range = 'medium_term',
limit = 50)
Los artistas aparecen en orden de más a menos escuchado. Se visualizan los 🎶 5 artistas más escuchados 🎶 :
df %>%
select(name, genres, followers.total) %>%
janitor::clean_names() %>%
head(5) %>%
rowwise() %>%
mutate(genres = paste(genres, collapse = ', ')) %>%
ungroup %>%
gt() %>%
tab_header(title=md('**Artistas más escuchados** en el mediano plazo')) %>%
opt_align_table_header('left') %>%
fmt_number(all_numeric(), decimals=2)
Artistas más escuchados en el mediano plazo | ||
name | genres | followers_total |
---|---|---|
Placebo | alternative rock, britpop, permanent wave, pop rock, rock | 1683878 |
Las Ligas Menores | argentine alternative rock, argentine indie, latin rock | 78188 |
Intoxicados | argentine rock, latin alternative, latin rock, rock en espanol, rock nacional, ska argentino | 920220 |
The Smiths | madchester, new wave, permanent wave, rock, uk post-punk | 3669665 |
La Dispute | alternative emo, emo, grand rapids indie, progressive post-hardcore, screamo | 311203 |
Para poder generar el flyer, se busca clusterizar a los artistas en 3 grupos (días del festival). Se realizan algunas transformaciones iniciales para poder utilizar estos datos para generar la clusterización.
EL primer paso consiste en generar variables a partir de cada una de las listas de géneros incluidas en cada artista. Luego, se pivotean los datos para obtener un registro por artista. Habiendo obtenido la tabla en formato correcto, se imputan los valores faltantes con 0. De esta forma, se cuenta con una matriz, en donde cada columna es un genero y cada fila es un artista. Si el artista pertenece a un género aparece un 1, y un 0 en caso contrario.
df_clusters <- df %>%
select(artist = name, genres) %>%
unnest_longer(genres) %>%
mutate(value = 1) %>%
pivot_wider(id_cols = artist,
names_from = genres,
values_from = value) %>%
mutate(across(where(is.numeric), ~ ifelse(is.na(.), 0, 1))) %>%
left_join(df %>% select(followers.total, artist = name) %>%
rownames_to_column('relevance')) %>%
janitor::clean_names()
Visualizando las primeras 2 observaciones:
artist | alternative_rock | britpop | permanent_wave | pop_rock | rock | argentine_alternative_rock | argentine_indie | latin_rock | argentine_rock | latin_alternative | rock_en_espanol | rock_nacional | ska_argentino | madchester | new_wave | uk_post_punk | alternative_emo | emo | grand_rapids_indie | progressive_post_hardcore | screamo | candy_pop | pixie | pop_emo | pop_punk | anti_folk | folk_punk | olympia_wa_indie | garage_rock | modern_rock | sheffield_indie | alternative_dance | art_pop | chamber_psych | chillwave | dream_pop | electronica | indie_rock | la_indie | modern_alternative_rock | modern_dream_pop | new_rave | argentine_indie_rock | indie_platense | argentine_punk | latincore | art_rock | melancholia | oxford_indie | eau_claire_indie | indie_folk | baroque_pop | chamber_pop | indie_pop | scottish_indie | scottish_rock | singer_songwriter | twee_pop | post_punk | pop | argentine_hardcore | alternative_metal | grunge | spacegrunge | nu_gaze | reading_indie | shoegaze | rock_uruguayo | nu_metal | post_grunge | rap_metal | post_punk_argentina | chilean_indie | canadian_pop | dance_pop | post_teen_pop | electronica_argentina | latin_pop | pop_electronico | electropop | classical_tenor | italian_tenor | operatic_pop | slam_poetry | classic_rock | folk_rock | protopunk | psychedelic_rock | reggae_uruguayo | noise_pop | deep_latin_alternative | irish_rock | dance_rock | new_romantic | new_wave_pop | synthpop | metropopolis | nz_pop | garage_rock_revival | relevance | followers_total |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Placebo | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1683878 |
Las Ligas Menores | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 2 | 78188 |
Se observa que el dataframe final cuenta con 102 variables para las 50 observaciones que se intentará clusterizar:
dim(df_clusters)
[1] 50 102
Se utiliza {tidymodels} 📦 para generar una receta simple de preprocesamiento de datos. En este caso, eliminando variables con varianza cercana a 0:
Se transforman los datos con la receta de preproecsamiento:
Ahora se cuenta con solo 27 variables para las 50 observaciones:
dim(df_processed)
[1] 50 27
Se utiliza el algoritmo kmeans para segmentar a los artistas en 3 grupos (3 centroides):
set.seed(1234)
artists_clusters <- df_processed %>%
column_to_rownames('artist') %>%
kmeans(centers = 3)
Se utiliza la función augment() del paquete {broom} 📦, incluido en tidymodels, para concatenar la columna de cluster al df original:
df_final <- broom::augment(artists_clusters, df_clusters)
# A tibble: 3 × 2
.cluster n
<fct> <int>
1 1 14
2 2 17
3 3 19
Para la generación del flyer se utiliza {ggplot2} 📦. Antes de generar el gráfico, es necesario realizar ciertas transformaciones iniciales:
Notar que, por ejemplo, Las Ligas Menores es el segundo artista más escuchado pero aparecen con relevance = 1. Esto se da porque es el artista más escuchado dentro del cluster=2 (al cual se asignó la banda en el momento de la clusterización).
Luego de haber generado el orden de relevancia de cada artista en cada cluster, se definen los labels (tamaños del texto de cada artista en el flyer). Para ello, se considera que los artistas con relevance==1 serán los que aparezcan arriba de todo, con un tamaño mayor. Luego vendrá una fila con los siguientes 3 artistas más relevantes por clusters. Luego otra fila y así hasta incluir a todos. Dentro de esta sección, se utiliza html para generar los labels que correspondan.
df_flyer <- df_flyer %>%
mutate(label = case_when(
relevance==1~paste0(artist,'<br>'),
relevance==4~paste0(artist,'<br>'),
relevance==9~paste0(artist,'<br>'),
TRUE ~ paste0(artist,' ◇ '))
) %>%
mutate(label = case_when(
relevance<=1~paste0("<span style = 'color: white; font-size: 30px'>", label,' </span>'),
relevance<=4~paste0("<span style = 'color: white; font-size: 20px'>", label,' </span>'),
relevance<=9~paste0("<span style = 'color: white; font-size: 15px'>", label,' </span>'),
TRUE ~ paste0("<span style = 'color: white; font-size: 10px'>", label,' </span>'))
)
Finalmente, se agrupa por artista para colapsar los labels en un único registro por día (cluster):
Ahora sí, teniendo el dataframe en el formato necesario para el Flyer se crea el ggplot final ✨:
font_color = 'white'
p <- df_flyer %>%
ggplot(aes(x = 0.5, y = 0.5, label = label)) +
geom_richtext(label.color = NA, fill = NA) +
facet_wrap( ~ paste0('Día ', dia), nrow = 3) +
ylim(c(0, 1)) +
labs(title = 'GGFEST 2023',
caption = 'Invita R')
Se añaden ciertos aspectos de estilo al plot:
p <- p +
theme_void() +
theme(
plot.margin = unit(c(7, 0, 12, 0), "cm"),
strip.text = element_text(
colour = font_color, size = 20,
family = 'custom_font'
),
text = element_text(),
plot.title = element_text(
family = 'custom_font',
hjust = 0.5,
size = 40,
color = font_color,
margin = margin(0, 0, 30, 0)
),
plot.caption = element_text(
hjust = 0.8,
size = 10,
color = font_color
),
panel.spacing.y = unit(0, "lines")
)
Para visualizarlo, se colorea el fondo (dado que el color del texto es blanco):
p + theme(plot.background = element_rect(fill='#53358f'))
Se añade una imagen como fondo del gráfico:
plot_with_background <- p %>%
ggbackground(background='https://raw.githubusercontent.com/karbartolome/instafest/main/ggfest_blank.png')
Para guardar el flyer, se utiliza la funcíón ggsave() de {ggplot2} 📦:
showtext_auto(enable = TRUE)
showtext_opts(dpi=1000)
ggsave('ggfest.png',
plot=plot_with_background,
height=25, width=25, scale=1
)
En este post se mostró una alternativa de clusterización para generar el flyer. Opciones de clusterización más avanzadas podrían llevar a una mejor segmentación. Cualquier comentario es bienvenido!
Karina Bartolome, Linkedin, Twitter, Github, Blogpost
sessioninfo::package_info() %>%
filter(attached==TRUE) %>%
select(package, loadedversion, date, source) %>%
gt() %>%
tab_header(title='Paquetes utilizados',
subtitle='Versiones') %>%
opt_align_table_header('left')
Paquetes utilizados | |||
Versiones | |||
package | loadedversion | date | source |
---|---|---|---|
broom | 1.0.4 | 2023-03-11 | CRAN (R 4.2.3) |
dials | 0.1.1 | 2022-04-06 | CRAN (R 4.2.0) |
dplyr | 1.1.1 | 2023-03-22 | CRAN (R 4.2.3) |
forcats | 1.0.0 | 2023-01-29 | CRAN (R 4.2.3) |
ggimage | 0.3.1 | 2022-04-25 | CRAN (R 4.2.0) |
ggplot2 | 3.4.2 | 2023-04-03 | CRAN (R 4.2.0) |
ggtext | 0.1.2 | 2022-09-16 | CRAN (R 4.2.2) |
ggwordcloud | 0.5.0 | 2019-06-02 | CRAN (R 4.2.2) |
gt | 0.9.0 | 2023-03-31 | CRAN (R 4.2.3) |
infer | 1.0.0 | 2021-08-13 | CRAN (R 4.2.0) |
lubridate | 1.9.2 | 2023-02-10 | CRAN (R 4.2.3) |
modeldata | 0.1.1 | 2021-07-14 | CRAN (R 4.2.0) |
parsnip | 1.0.3 | 2022-11-11 | CRAN (R 4.2.2) |
purrr | 1.0.1 | 2023-01-10 | CRAN (R 4.2.3) |
readr | 2.1.4 | 2023-02-10 | CRAN (R 4.2.3) |
recipes | 1.0.3 | 2022-11-09 | CRAN (R 4.2.2) |
rjson | 0.2.21 | 2022-01-09 | CRAN (R 4.2.0) |
rsample | 0.1.1 | 2021-11-08 | CRAN (R 4.2.0) |
scales | 1.2.1 | 2022-08-20 | CRAN (R 4.2.3) |
showtext | 0.9-5 | 2022-02-09 | CRAN (R 4.2.0) |
showtextdb | 3.0 | 2020-06-04 | CRAN (R 4.2.0) |
spotifyr | 2.2.3 | 2021-11-02 | CRAN (R 4.2.1) |
stringr | 1.5.0 | 2022-12-02 | CRAN (R 4.2.3) |
sysfonts | 0.8.8 | 2022-03-13 | CRAN (R 4.2.0) |
tibble | 3.2.1 | 2023-03-20 | CRAN (R 4.2.3) |
tidymodels | 0.2.0 | 2022-03-19 | CRAN (R 4.2.0) |
tidyr | 1.3.0 | 2023-01-24 | CRAN (R 4.2.3) |
tidyverse | 2.0.0 | 2023-02-22 | CRAN (R 4.2.3) |
tune | 0.2.0 | 2022-03-19 | CRAN (R 4.2.0) |
workflows | 0.2.6 | 2022-03-18 | CRAN (R 4.2.0) |
workflowsets | 0.2.1 | 2022-03-15 | CRAN (R 4.2.0) |
yardstick | 1.0.0 | 2022-06-06 | CRAN (R 4.2.0) |
For attribution, please cite this work as
Bartolomé (2022, Nov. 29). Karina Bartolome: Crea tu propio festival. Retrieved from https://karbartolome-blog.netlify.app/posts/ggfestival/
BibTeX citation
@misc{bartolomé2022crea, author = {Bartolomé, Karina}, title = {Karina Bartolome: Crea tu propio festival}, url = {https://karbartolome-blog.netlify.app/posts/ggfestival/}, year = {2022} }