The Mountain Goats with {trelliscopejs}

dataviz
music
r
spotifyr
trelliscope
trelliscopejs
Author
Published

June 20, 2019

tl;dr

I used {trelliscopejs} to make an interactive ‘small multiples’ display for The Mountain Goats discography. You can interact with an embedded version above or click here to open full screen.

Note

The {trelliscopejs} package has been superseded by {trelliscope}.

Small multiples

The {trelliscopejs} R package by Ryan Hafen harnesses the power of his trelliscopejs-lib JavaScript library.

What does it do? It provides an interactive interface for visualising, organising and exploring data visualisations in small multiples.

What are ‘small multiples’? Rather than over-plotting data for multiple levels of some variable, you can facet by them into separate ‘panels’ and display the outputs side by side for easy comparison.

Ryan has written documentation, an introductory post and has created some trelliscopes using using gapminder and Pokémon data, for example.1 His package is relatively simple to use and does a lot of legwork to provide a nice interface for your data.

Goat discography

In a previous post I used the {spotifyr}, {genius} and {markovifyR} packages to generate new lyrics for the band The Mountain Goats.

The data from Spotify is interesting. It has musical information like key and tempo, but also audio features like ‘danceability’ and ‘acousticness’ scaled from 0 to 1. We also got links to album art in that data set.

I’m going to use these data in this post to provide a trelliscope example. Each panel will be a track; the visualisation will be album artwork rather than a plot; and audio features will be available to sort and filter data.

Ready the data

We’ll load {trelliscopejs} and some tidyverse packages to help us out.

suppressPackageStartupMessages({
  library(trelliscopejs)
  library(dplyr)
  library(readr)
  library(tidyr)
})

Get data and simplify

You can follow the instructions in the previous post to get the data. I’m going to load those data from a pre-prepared RDS file.

# Read the file from a local source and check number of columns
raw_goat <- read_rds("resources/goat_discography.RDS")
length(raw_goat)
[1] 41

There’s 41 variables, so let’s simplify.

small_goat <- raw_goat %>% 
  unnest(available_markets) %>%  # unnest character vector
  filter(available_markets == "US") %>%  # releases from one country only
  select(
    track_name, album_name, track_n, album_release_year,  # track detail
    duration_ms, key_mode, time_signature, # musical info
    danceability, energy, speechiness, acousticness,  # audio features
    instrumentalness, liveness, valence, loudness  # audio features
  ) %>%
  arrange(desc(energy))  # order by 'energy' audio feature

glimpse(small_goat)
Rows: 313
Columns: 15
$ track_name         <chr> "Choked Out", "Pure Intentions", "If You See Light"…
$ album_name         <chr> "Beat the Champ", "Bitter Melon Farm", "Get Lonely"…
$ track_n            <int> 5, 18, 10, 16, 14, 20, 17, 8, 21, 4, 2, 13, 3, 9, 8…
$ album_release_year <dbl> 2015, 2002, 2006, 2011, 1996, 2012, 2002, 2002, 201…
$ duration_ms        <int> 102653, 134333, 118013, 167120, 234600, 165840, 137…
$ key_mode           <chr> "D major", "A major", "C major", "B minor", "D majo…
$ time_signature     <int> 4, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, …
$ danceability       <dbl> 0.565, 0.648, 0.566, 0.422, 0.281, 0.681, 0.592, 0.…
$ energy             <dbl> 0.951, 0.934, 0.927, 0.903, 0.899, 0.895, 0.884, 0.…
$ speechiness        <dbl> 0.0808, 0.0989, 0.0642, 0.0682, 0.0526, 0.0253, 0.0…
$ acousticness       <dbl> 0.000239, 0.646000, 0.002930, 0.000620, 0.262000, 0…
$ instrumentalness   <dbl> 2.78e-05, 2.14e-01, 8.52e-01, 1.01e-02, 2.72e-02, 4…
$ liveness           <dbl> 0.1320, 0.3890, 0.1990, 0.3420, 0.2080, 0.1390, 0.4…
$ valence            <dbl> 0.726, 0.424, 0.324, 0.860, 0.350, 0.970, 0.591, 0.…
$ loudness           <dbl> -3.970, -15.557, -8.047, -8.562, -9.177, -6.936, -1…

Note that I’ve ordered by ‘energy’. The trelliscope output being created will be sorted in this variable.

Album artwork

Unnesting the available_markets character vector removed the album_image variable, which is a nested data frame with URLs to different-sized album artwork. We can grab unique album image URLs and join them back to our data set.

goat_pics <- raw_goat %>%
  unnest(album_images) %>%  # unnest dataframe fo URLs
  filter(width == 640) %>%  # just the largest images
  select(album_name, url) %>%  # simplify dataset
  distinct(album_name, .keep_all = TRUE)  # one unique entry per album

glimpse(goat_pics)
Rows: 21
Columns: 2
$ album_name <chr> "In League with Dragons", "Goths", "Beat the Champ", "Trans…
$ url        <chr> "https://i.scdn.co/image/3896e2b47b548a33d0c9e9f662011a2a09…

And now to join the album image URLs back to our simplified data set.

small_goat_pics <- left_join(
  x = small_goat,
  y = goat_pics,
  by = "album_name"
)

glimpse(small_goat_pics)
Rows: 313
Columns: 16
$ track_name         <chr> "Choked Out", "Pure Intentions", "If You See Light"…
$ album_name         <chr> "Beat the Champ", "Bitter Melon Farm", "Get Lonely"…
$ track_n            <int> 5, 18, 10, 16, 14, 20, 17, 8, 21, 4, 2, 13, 3, 9, 8…
$ album_release_year <dbl> 2015, 2002, 2006, 2011, 1996, 2012, 2002, 2002, 201…
$ duration_ms        <int> 102653, 134333, 118013, 167120, 234600, 165840, 137…
$ key_mode           <chr> "D major", "A major", "C major", "B minor", "D majo…
$ time_signature     <int> 4, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, …
$ danceability       <dbl> 0.565, 0.648, 0.566, 0.422, 0.281, 0.681, 0.592, 0.…
$ energy             <dbl> 0.951, 0.934, 0.927, 0.903, 0.899, 0.895, 0.884, 0.…
$ speechiness        <dbl> 0.0808, 0.0989, 0.0642, 0.0682, 0.0526, 0.0253, 0.0…
$ acousticness       <dbl> 0.000239, 0.646000, 0.002930, 0.000620, 0.262000, 0…
$ instrumentalness   <dbl> 2.78e-05, 2.14e-01, 8.52e-01, 1.01e-02, 2.72e-02, 4…
$ liveness           <dbl> 0.1320, 0.3890, 0.1990, 0.3420, 0.2080, 0.1390, 0.4…
$ valence            <dbl> 0.726, 0.424, 0.324, 0.860, 0.350, 0.970, 0.591, 0.…
$ loudness           <dbl> -3.970, -15.557, -8.047, -8.562, -9.177, -6.936, -1…
$ url                <chr> "https://i.scdn.co/image/ecf370a7190fa0673b0aa2ff0c…

So the album artwork URL has been added to the url column.

Prep for trelliscope

Now we have a nice tidy data frame, but I’m going to make a couple more changes to prepare the data for trelliscoping2. First, I need to use the img_panel() function to declare that the album images should be the thing being visualised in each panel. Then I can rename the variables to make them look nicer when displayed.

prepared_goat <- small_goat_pics %>% 
  mutate(panel = img_panel(url)) %>%  # identify as viz for panel
  rename_all(tools::toTitleCase) %>%  # first letter capitalised
  rename(
    Track = Track_name,
    Album = Album_name,
    `Track #` = Track_n,
    Year = Album_release_year,
    `Duration (ms)` = Duration_ms,
    `Key mode` = Key_mode,
    `Time sig` = Time_signature
  ) %>% 
  select(-Url)  # discard unneeded variable

Generate trelliscope

Now we’re ready. The call to trelliscope() takes the data set and then a bunch of other arguments like a title and subtitle and the default state for the number of rows and columns of panels and the default data to show on the panel under the visualisation.

trelliscope(
  prepared_goat,
  name = "The Mountain Goats discography",
  desc = "Explore the Mountain Goats backcatalogue and filter and sort by audio features",
  md_desc = "[The Mountain Goats](http://www.mountain-goats.com/) are a band. Data were collected from [Genius](https://genius.com/) and [Spotify](https://www.spotify.com/) APIs using the [{genius}](https://github.com/josiahparry/genius) and [{spotifyr}](https://www.rcharlie.com/spotifyr/) R packages, respectively.",
  nrow = 2, ncol = 5,  # arrangement of panels
  state = list(labels = c("Track", "Album", "Track #", "Year", "Energy")),  # display on panels
)

I’ve embedded the trelliscope at the top of this post, but I recommend you click here to open it full screen.

Explore the data by altering the defaults in the grid, labels, filter and sort buttons in the left-hand navigation panel. Cycle through the panels with the arrows in the upper right. Hit the ‘?’ button in the upper right to get more information on trelliscope and its shortcuts.

Host your trelliscope

Use the argument path = "<file path to save to>" in the trelliscope() function to save the files (I learnt this from a GitHub issue). You can then host the folder’s contents somewhere. I put mine in a GitHub repo so it could be served via GitHub Pages.

Energy

I’ve ordered the panels of the trelliscope by the ‘energy’ of the tracks; most energetic first. The top track for energy is one of my favourites: ‘Choked Out’ from ‘Beat the Champ’. Here’s an embedded Spotify snippet.

Environment

Session info
Last rendered: 2023-08-02 16:18:25 BST
R version 4.3.1 (2023-06-16)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Ventura 13.2.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.11.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] tidyr_1.3.0         readr_2.1.4         dplyr_1.1.2        
[4] trelliscopejs_0.2.6

loaded via a namespace (and not attached):
 [1] gtable_0.3.3            jsonlite_1.8.7          compiler_4.3.1         
 [4] crayon_1.5.2            webshot_0.5.5           tidyselect_1.2.0       
 [7] progress_1.2.2          scales_1.2.1            yaml_2.3.7             
[10] fastmap_1.1.1           ggplot2_3.4.2           R6_2.5.1               
[13] autocogs_0.1.4          generics_0.1.3          knitr_1.43.1           
[16] backports_1.4.1         htmlwidgets_1.6.2       checkmate_2.2.0        
[19] tibble_3.2.1            munsell_0.5.0           xaringanExtra_0.7.0    
[22] tzdb_0.4.0              pillar_1.9.0            rlang_1.1.1            
[25] utf8_1.2.3              xfun_0.39               cli_3.6.1              
[28] withr_2.5.0             magrittr_2.0.3          digest_0.6.33          
[31] grid_4.3.1              rstudioapi_0.15.0       fontawesome_0.5.1      
[34] base64enc_0.1-3         DistributionUtils_0.6-0 hms_1.1.3              
[37] mclust_6.0.0            lifecycle_1.0.3         prettyunits_1.1.1      
[40] vctrs_0.6.3             evaluate_0.21           glue_1.6.2             
[43] fansi_1.0.4             colorspace_2.1-0        purrr_1.0.1            
[46] rmarkdown_2.23          tools_4.3.1             pkgconfig_2.0.3        
[49] htmltools_0.5.5        

Footnotes

  1. I also once explored the use of Trelliscope for UK education data and have been meaning to write about it ever since.↩︎

  2. Definitely a real verb.↩︎

Reuse

CC BY-NC-SA 4.0