The Mountain Goats with {trelliscopejs}

tl;dr

I used {trelliscopejs} to make an interactive display for The Mountain Goats discography. You can interact with it below or click here to open full screen.

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 overplotting 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 Pokemon 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.

library(trelliscopejs)
library(dplyr)  # manipulate data
library(readr)  # read data
library(tidyr) # get data into tidy form

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 an RDS file on my machine.

raw_goat <- read_rds("~/Desktop/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)
## Observations: 313
## Variables: 15
## $ track_name         <chr> "Choked Out", "Pure Intentions", "If You See …
## $ album_name         <chr> "Beat the Champ", "Bitter Melon Farm", "Get L…
## $ track_n            <int> 5, 18, 10, 16, 14, 20, 17, 8, 21, 4, 2, 13, 3…
## $ album_release_year <dbl> 2015, 2002, 2006, 2011, 1996, 2012, 2002, 200…
## $ duration_ms        <int> 102653, 134333, 118013, 167120, 234600, 16584…
## $ key_mode           <chr> "D major", "A major", "C major", "B minor", "…
## $ time_signature     <int> 4, 4, 3, 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.5…
## $ energy             <dbl> 0.951, 0.934, 0.927, 0.903, 0.899, 0.895, 0.8…
## $ speechiness        <dbl> 0.0808, 0.0989, 0.0642, 0.0682, 0.0526, 0.025…
## $ acousticness       <dbl> 0.000239, 0.646000, 0.002930, 0.000620, 0.262…
## $ instrumentalness   <dbl> 2.78e-05, 2.14e-01, 8.52e-01, 1.01e-02, 2.72e…
## $ liveness           <dbl> 0.1320, 0.3890, 0.1990, 0.3420, 0.2080, 0.139…
## $ valence            <dbl> 0.726, 0.424, 0.324, 0.860, 0.350, 0.970, 0.5…
## $ loudness           <dbl> -3.970, -15.557, -8.047, -8.562, -9.177, -6.9…

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)
## Observations: 21
## Variables: 2
## $ album_name <chr> "In League with Dragons", "Goths", "Beat the Champ", …
## $ url        <chr> "https://i.scdn.co/image/3896e2b47b548a33d0c9e9f66201…

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)
## Observations: 313
## Variables: 16
## $ track_name         <chr> "Choked Out", "Pure Intentions", "If You See …
## $ album_name         <chr> "Beat the Champ", "Bitter Melon Farm", "Get L…
## $ track_n            <int> 5, 18, 10, 16, 14, 20, 17, 8, 21, 4, 2, 13, 3…
## $ album_release_year <dbl> 2015, 2002, 2006, 2011, 1996, 2012, 2002, 200…
## $ duration_ms        <int> 102653, 134333, 118013, 167120, 234600, 16584…
## $ key_mode           <chr> "D major", "A major", "C major", "B minor", "…
## $ time_signature     <int> 4, 4, 3, 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.5…
## $ energy             <dbl> 0.951, 0.934, 0.927, 0.903, 0.899, 0.895, 0.8…
## $ speechiness        <dbl> 0.0808, 0.0989, 0.0642, 0.0682, 0.0526, 0.025…
## $ acousticness       <dbl> 0.000239, 0.646000, 0.002930, 0.000620, 0.262…
## $ instrumentalness   <dbl> 2.78e-05, 2.14e-01, 8.52e-01, 1.01e-02, 2.72e…
## $ liveness           <dbl> 0.1320, 0.3890, 0.1990, 0.3420, 0.2080, 0.139…
## $ valence            <dbl> 0.726, 0.424, 0.324, 0.860, 0.350, 0.970, 0.5…
## $ loudness           <dbl> -3.970, -15.557, -8.047, -8.562, -9.177, -6.9…
## $ url                <chr> "https://i.scdn.co/image/ecf370a7190fa0673b0a…

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 here the output, but you can click here to open 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.

Bonus content!

Energy

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

Host your trelliscope

Use the argument path = "~/Desktop/goat-scope" 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 the static/ directory of this site’s structure so it’s hosted on its own page at https://www.rostrum.blog/output/goat_scope/ (see Tim Mastny’s post).


Session info

## [1] "Last updated 2019-06-20"
## R version 3.5.2 (2018-12-20)
## Platform: x86_64-apple-darwin15.6.0 (64-bit)
## Running under: macOS High Sierra 10.13.6
## 
## Locale: en_GB.UTF-8 / en_GB.UTF-8 / en_GB.UTF-8 / C / en_GB.UTF-8 / en_GB.UTF-8
## 
## Package version:
##   assertthat_0.2.1        autocogs_0.1.2         
##   backports_1.1.4         base64enc_0.1-3        
##   BH_1.69.0.1             blogdown_0.11          
##   bookdown_0.9            broom_0.5.2            
##   callr_3.2.0             checkmate_1.9.1        
##   cli_1.1.0               clipr_0.6.0            
##   colorspace_1.4-1        compiler_3.5.2         
##   crayon_1.3.4            digest_0.6.19          
##   diptest_0.75.7          DistributionUtils_0.6-0
##   dplyr_0.8.1             emo_0.0.0.9000         
##   evaluate_0.14           fansi_0.4.0            
##   generics_0.0.2          ggplot2_3.1.1          
##   glue_1.3.1              graphics_3.5.2         
##   grDevices_3.5.2         grid_3.5.2             
##   gtable_0.3.0            hexbin_1.27.2          
##   highr_0.8               hms_0.4.2              
##   htmltools_0.3.6         htmlwidgets_1.3        
##   httpuv_1.5.1            jsonlite_1.6           
##   knitr_1.23              labeling_0.3           
##   later_0.8.0             lattice_0.20.38        
##   lazyeval_0.2.2          lubridate_1.7.4        
##   magrittr_1.5            markdown_1.0           
##   MASS_7.3.51.1           Matrix_1.2.16          
##   mclust_5.4.1            methods_3.5.2          
##   mgcv_1.8.27             mime_0.7               
##   moments_0.14            munsell_0.5.0          
##   nlme_3.1.137            pillar_1.4.1           
##   pkgconfig_2.0.2         plogr_0.2.0            
##   plyr_1.8.4              prettyunits_1.0.2      
##   processx_3.3.1          progress_1.2.2         
##   promises_1.0.1          ps_1.3.0               
##   purrr_0.3.2             R6_2.4.0               
##   RColorBrewer_1.1.2      Rcpp_1.0.1             
##   readr_1.3.1             reshape2_1.4.3         
##   rlang_0.3.4             rmarkdown_1.13         
##   rstudioapi_0.10         scales_1.0.0           
##   servr_0.13              splines_3.5.2          
##   stats_3.5.2             stringi_1.4.3          
##   stringr_1.4.0           tibble_2.1.3           
##   tidyr_0.8.3             tidyselect_0.2.5       
##   tinytex_0.13            tools_3.5.2            
##   trelliscopejs_0.1.18    utf8_1.1.4             
##   utils_3.5.2             vctrs_0.1.0            
##   viridisLite_0.3.0       webshot_0.5.1          
##   withr_2.1.2             xfun_0.7               
##   yaml_2.2.0              zeallot_0.1.0

  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.