---
title: "Spotify songs"
author: "your name"
date: "2025-06-30"
format: 
  html:
    embed-resources: true
image: https://static.vecteezy.com/ti/vecteur-libre/p1/6057992-logo-spotify-sur-fond-transparent-gratuit-vectoriel.jpg
---


```{r}
#| label: setup
#| include: false
library(tidyverse)
```

::: {.callout-note}
## Objective
Objective: explore songs using the Spotify API
:::

::: {.bonus}
##### Those squared questions are optional
:::

# More than 30k Spotify songs

This dataset comes from the project [TidyTuesday](https://github.com/rfordatascience/tidytuesday/) where
each week a contributor submit a dataset that participants can explore and analyze. 
Of course, this dataset was obtained from public access but transformed to meet the **tidy** principles.
Here, we are looking at the [Spotify dataset](https://github.com/rfordatascience/tidytuesday/blob/master/data/2020/2020-01-21/readme.md).

## Loading data from TidyTuesday

The already pre-processed dataset can be directly obtained [here](https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-01-21/spotify_songs.csv)


::: {.callout-tip}
## Tip
You don't even need to download the file, you can directly use the url.
:::

##### Assign the csv file to an object named `spotify_songs`

```{r}
# write answers
```


## First counting

##### How many songs are present?

```{r}
# write answers
```

##### How many songs per genre are present?

```{r}
# write answers
```

##### How many songs do one album contain?

```{r}
# write answers
```

##### Plot the density of the previous counting per genre

::: {.callout-tip}
## Tip

- A density of the an univariate visualization of the distribution of a numerical value. It is a curve which the area under it sum to 1.
The associate geometry is `geom_density()`
and only one mapped aesthetics is needed (since univariate), so use `geom_density(aes(x = n))` once you have computed the number of songs per album.
-By default, the density is not filled, I personally like `fill = "grey", alpha = 0.5` to have the feeling of the density with some transparency.
- For the **genre**, you have 2 choices, either you color the densities by it or you facet by it.

:::

::: {.callout-important}
## Warning

- We have a LOT of albums with less than 3 songs in a album, a suggestion is to filter them out
- Even after min 3 songs, the distribution will skewed by a few large values. You can use `scale_x_log10()` and `annotation_logticks(sides = "b")` to better visualize the values and distribution.

:::

```{r}
# write answers
```

## Songs key

Even if you don't know much about music, you know that most songs
are written in one key associated to a mode, such as _B minor_.
In the columns `key` and `mode`, _spotify_ encoded those information following  those [correspondences](https://help.spotontrack.com/article/what-do-the-audio-features-mean).

##### Create a barplot that display the counts of keys, filled by the mode (minor/major).

Optionally, one could add the percentages of the mode inside the bars.

::: {.callout-tip}
## Tip

The plot will look nicer if:

- Discard key / mode with no informative values assigned
- Keys are on the y-axis, counts on the x-axis
- Key are sorted by their occurrences, like the highest count on top. See the function `fct_reorder()` for that matter
- The `recode()` can help you to get the true note instead of the numerical encoding.
See below an example:

```{r}
#| fig-height: 2.5
mtcars |> 
  mutate(transmission = recode(am, `0` = "automatic",
                                   `1` = "manual")) |> 
  ggplot(aes(y = transmission)) +
  geom_bar()
```
:::


```{r}
# write answers
```

## Tracking the 'spotify' effect in rap songs

It was suggested that the arrival of streaming services has changed the way artists
are working. Especially, the length of tracks in rap music. Since we have the data, we can check this assumption.

##### Plot the length of tracks for the genre **rap** in function of the track album release date.

::: {.callout-tip}
## Tip

- Add a vertical line (`geom_vline()`) to highlight the starts of for example 1 million Spotify users.
- You will face over-plotting, one quick way to solve it is to reduce transparency (`alpha`).
But a recent package [`ggpoindensity`](https://cran.r-project.org/web/packages/ggpointdensity/index.html) proposes a neat solution.
- Add a trend line with for example `geom_smooth(method = "loess")`
- Release dates should be converted to a date to get a good x-axis labelling. Use `as.Date(track_album_release_date)` to coerce characters to R dates.

:::

```{r}
#| fig-height: 5
#| fig-width: 9
# write answers
```

##### Do you see an effect? Do you observe it for other genres?


## Songs characteristics


In the same [page](https://help.spotontrack.com/article/what-do-the-audio-features-mean), Spotify data scientists created several parameters, like _valence_, _speechiness_ or _energy_ and assigned a score to each song. Let's explore those parameters and compare the distribution to for example one of my playlist.
You can of course use your own playlist if you wish, I can help you with fetching your data.

- Let's define the columns we want to keep and compare

```{r}
col_to_keep <- c("track_name", "track_artist", "track_popularity", "track_album_name",
 "track_album_release_date", "danceability", "speechiness", "acousticness", 
 "instrumentalness", "liveness", "valence", "tempo", "duration_ms")
```

##### Subset only the above columns from the `spotify_songs` tibble. Assign it to the name `sub_spotify_songs`

```{r}
# write answers
```


## Comparing to another dataset

::: {.bonus}
##### Extract your own song features

If you wish to extract the song features, to compare with the 33k songs above, 
the Spotify data you can download don't contain those. However, you can track the number of plays etc, also
a nice project to explore those.

To get the features, we need to use the [`Spotifyr`](https://github.com/charlie86/spotifyr) package.
You need to get your client ids and secret, follow the docs on the website.

Here is the code I used once those info obtained:

```{.r}
# package not on CRAN
# remotes::install_github("charlie86/spotifyr")
library(spotifyr)
Sys.setenv(SPOTIFY_CLIENT_ID = 'xxxxxxxxxxxxxxxxxxxxxxxxxx')
Sys.setenv(SPOTIFY_CLIENT_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')

access_token <- get_spotify_access_token()
my_plists <- get_my_playlists()
# I used only one playlist, get the id of yours
ginolhac <- get_playlist_audio_features(playlist_uris = "xxxxxxxxxxxxxxxx")
filter(ginolhac, !is_local) |> 
  select(track_id = track.id, track_name = track.name, track_album_name = track.album.name,
         track_popularity = track.popularity, danceability,
        key = key_mode, loudness:tempo, duration_ms = track.duration_ms) |> 
  write_tsv("data/yourname_spotify.tsv.gz")
```
:::

##### Load my own Spotify songs from a playlist, from this [compressed tsv file](https://biostat2.uni.lu/projects/data/ginolhac_spotify.tsv.gz). Save as `spogino`

::: {.callout-tip}
## Tip

You could use your own data here. But, keep it mind, this requires some steps in the Spotify API, then some data cleaning in order to ease the merge step
:::

```{r}
# write answers
```

##### Merge the two datasets: `sub_spotify_songs` and `spogino`, assign the name `spomerge`

::: {.callout-important}
## Warning

One classic way would be to join (`inner_join()`) tables. But then all columns would be collated,
and even if renamed meaningfully, it won't be tidy.
A smarter way of doing this, would be add an `id` column to each tibble before binding the **rows**.
This will be working, only because ALL columns exist in both tables and are named the same.
:::


```{r}
# see this toy example
(t1 <- tibble(a = 1:3,
              b = c("a", "a", "a")))
(t2 <- tibble(a = 4:6,
              b = c("b", "b", "b")))
# names coupled with the .id argument does the job
bind_rows(first = t1, 
          second = t2,
          .id = "id")
```



```{r}
# write answers
```

##### Plot the densities of the different parameters, filling it by `id`

::: {.callout-tip}
## Tip

- This requires to get the parameters in a tidy longer format. to ease pivoting, you should keep only the 
necessary columns first. I advice you to use these parameters:

```{.r}
c("danceability", "speechiness", "acousticness", "duration_ms",
  "track_popularity", "liveness", "valence", "tempo")
```

- Faceting by parameters is the easiest, mind to use free scaling with `scales = "free"` as we have very different distributions.

:::


```{r}
# write answers
```

##### What can you conclude?

