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
<- read_rds("resources/goat_discography.RDS")
raw_goat length(raw_goat)
[1] 41
There’s 41 variables, so let’s simplify.
<- raw_goat %>%
small_goat unnest(available_markets) %>% # unnest character vector
filter(available_markets == "US") %>% # releases from one country only
select(
# track detail
track_name, album_name, track_n, album_release_year, # musical info
duration_ms, key_mode, time_signature, # audio features
danceability, energy, speechiness, acousticness, # audio features
instrumentalness, liveness, valence, loudness %>%
) 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.
<- raw_goat %>%
goat_pics 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.
<- left_join(
small_goat_pics 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.
<- small_goat_pics %>%
prepared_goat 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
I also once explored the use of Trelliscope for UK education data and have been meaning to write about it ever since.↩︎
Definitely a real verb.↩︎