Text analysis (Taylor’s Version)

Suggested answers

Application exercise
Answers
library(tidyverse)
library(tidytext)
library(taylor)
library(tayloRswift)
library(ggridges)
library(scales)

theme_set(theme_minimal(base_size = 13))

Taylor Swift is one of the most recognizable and popular recording artists on the planet. She is also a prolific songwriter, having written or co-written every song on each of her nine studio albums. Currently she is smashing records on her Eras concert tour.

Taylor Swift holding her hands up in a heart shape and then pointing at the camera.

In this application exercise we will use the taylor package to analyze the lyrics of Taylor Swift’s songs. The package contains a data frame taylor_albums with information about each of her studio albums, including the release date, the number of tracks, and the album cover art. The package also contains a data frame taylor_album_songs with the lyrics of each song from her official studio albums.1

1 This excludes singles released separately from an album as well as non-Taylor-owned albums that have a Taylor-owned alternative (e.g., Fearless is excluded in favor of Fearless (Taylor’s Version)).

Import Taylor Swift lyrics

We can load the relevant data files directly from the taylor package.

library(taylor)

data("taylor_album_songs")
data("taylor_albums")
taylor_album_songs
# A tibble: 209 × 29
   album_name   ep    album_release track_number track_name     artist featuring
   <chr>        <lgl> <date>               <int> <chr>          <chr>  <chr>    
 1 Taylor Swift FALSE 2006-10-24               1 Tim McGraw     Taylo… <NA>     
 2 Taylor Swift FALSE 2006-10-24               2 Picture To Bu… Taylo… <NA>     
 3 Taylor Swift FALSE 2006-10-24               3 Teardrops On … Taylo… <NA>     
 4 Taylor Swift FALSE 2006-10-24               4 A Place In Th… Taylo… <NA>     
 5 Taylor Swift FALSE 2006-10-24               5 Cold As You    Taylo… <NA>     
 6 Taylor Swift FALSE 2006-10-24               6 The Outside    Taylo… <NA>     
 7 Taylor Swift FALSE 2006-10-24               7 Tied Together… Taylo… <NA>     
 8 Taylor Swift FALSE 2006-10-24               8 Stay Beautiful Taylo… <NA>     
 9 Taylor Swift FALSE 2006-10-24               9 Should've Sai… Taylo… <NA>     
10 Taylor Swift FALSE 2006-10-24              10 Mary's Song (… Taylo… <NA>     
# ℹ 199 more rows
# ℹ 22 more variables: bonus_track <lgl>, promotional_release <date>,
#   single_release <date>, track_release <date>, danceability <dbl>,
#   energy <dbl>, key <int>, loudness <dbl>, mode <int>, speechiness <dbl>,
#   acousticness <dbl>, instrumentalness <dbl>, liveness <dbl>, valence <dbl>,
#   tempo <dbl>, time_signature <int>, duration_ms <int>, explicit <lgl>,
#   key_name <chr>, mode_name <chr>, key_mode <chr>, lyrics <list>
taylor_albums
# A tibble: 16 × 5
   album_name                    ep    album_release metacritic_score user_score
   <chr>                         <lgl> <date>                   <int>      <dbl>
 1 Taylor Swift                  FALSE 2006-10-24                  67        8.4
 2 The Taylor Swift Holiday Col… TRUE  2007-10-14                  NA       NA  
 3 Beautiful Eyes                TRUE  2008-07-15                  NA       NA  
 4 Fearless                      FALSE 2008-11-11                  73        8.4
 5 Speak Now                     FALSE 2010-10-25                  77        8.6
 6 Red                           FALSE 2012-10-22                  77        8.6
 7 1989                          FALSE 2014-10-27                  76        8.3
 8 reputation                    FALSE 2017-11-10                  71        8.3
 9 Lover                         FALSE 2019-08-23                  79        8.4
10 folklore                      FALSE 2020-07-24                  88        9  
11 evermore                      FALSE 2020-12-11                  85        8.9
12 Fearless (Taylor's Version)   FALSE 2021-04-09                  82        8.9
13 Red (Taylor's Version)        FALSE 2021-11-12                  91        8.9
14 Midnights                     FALSE 2022-10-21                  85        8.3
15 Speak Now (Taylor's Version)  FALSE 2023-07-07                  81        9.2
16 1989 (Taylor's Version)       FALSE 2023-10-27                  95       NA  

Convert to tidytext format

Currently, taylor_album_songs is stored as one-row-song, with the lyrics nested in a list-column where each element is a tibble with one-row-per-line. The definition of a single “line” is somewhat arbitrary. For substantial analysis, we will convert the corpus to a tidy-text data frame of one-row-per-token. Initially, we will use unnest_tokens() to tokenize all unigrams.

# tokenize taylor lyrics
taylor_lyrics <- taylor_album_songs |>
  # select relevant columns
  select(album_name, track_number, track_name, lyrics) |>
  # factor albums by release date
  mutate(album_name = fct(x = album_name, levels = taylor_albums$album_name)) |>
  # unnest the list-column to one-row-per-song-per-line
  unnest(col = lyrics) |>
  # now tokenize the lyrics
  unnest_tokens(output = word, input = lyric)
taylor_lyrics
# A tibble: 77,882 × 7
   album_name   track_number track_name  line element element_artist word  
   <fct>               <int> <chr>      <int> <chr>   <chr>          <chr> 
 1 Taylor Swift            1 Tim McGraw     1 Verse 1 Taylor Swift   he    
 2 Taylor Swift            1 Tim McGraw     1 Verse 1 Taylor Swift   said  
 3 Taylor Swift            1 Tim McGraw     1 Verse 1 Taylor Swift   the   
 4 Taylor Swift            1 Tim McGraw     1 Verse 1 Taylor Swift   way   
 5 Taylor Swift            1 Tim McGraw     1 Verse 1 Taylor Swift   my    
 6 Taylor Swift            1 Tim McGraw     1 Verse 1 Taylor Swift   blue  
 7 Taylor Swift            1 Tim McGraw     1 Verse 1 Taylor Swift   eyes  
 8 Taylor Swift            1 Tim McGraw     1 Verse 1 Taylor Swift   shined
 9 Taylor Swift            1 Tim McGraw     2 Verse 1 Taylor Swift   put   
10 Taylor Swift            1 Tim McGraw     2 Verse 1 Taylor Swift   those 
# ℹ 77,872 more rows

Remember that by default, unnest_tokens() automatically converts all text to lowercase and strips out punctuation.

Length of songs by words

An initial check reveals the length of each song in terms of the number of words in its lyrics.

taylor_lyrics |>
  count(album_name, track_number, track_name) |>
  ggplot(mapping = aes(x = n)) +
  geom_histogram() +
  labs(
    title = "Length of songs by Taylor Swift",
    x = "Song length (in words)",
    y = NULL,
    caption = "Source: {taylor}"
  )

Stop words

Generic stop words

Of course not all words are equally important. Consider the 10 most frequent words in the lyrics:

taylor_lyrics |>
  count(word, sort = TRUE)
# A tibble: 4,102 × 2
   word      n
   <chr> <int>
 1 you    3585
 2 i      3486
 3 the    2731
 4 and    2146
 5 to     1381
 6 me     1355
 7 a      1287
 8 it     1225
 9 in     1195
10 my     1085
# ℹ 4,092 more rows

These are not particularly informative. We can identify a list of stop words then remove them via anti_join().

# get a set of stop words
get_stopwords(source = "smart")
# A tibble: 571 × 2
   word        lexicon
   <chr>       <chr>  
 1 a           smart  
 2 a's         smart  
 3 able        smart  
 4 about       smart  
 5 above       smart  
 6 according   smart  
 7 accordingly smart  
 8 across      smart  
 9 actually    smart  
10 after       smart  
# ℹ 561 more rows
# remove stop words
taylor_tidy <- anti_join(x = taylor_lyrics, y = get_stopwords(source = "smart"))
taylor_tidy
# A tibble: 24,770 × 7
   album_name   track_number track_name  line element element_artist word   
   <fct>               <int> <chr>      <int> <chr>   <chr>          <chr>  
 1 Taylor Swift            1 Tim McGraw     1 Verse 1 Taylor Swift   blue   
 2 Taylor Swift            1 Tim McGraw     1 Verse 1 Taylor Swift   eyes   
 3 Taylor Swift            1 Tim McGraw     1 Verse 1 Taylor Swift   shined 
 4 Taylor Swift            1 Tim McGraw     2 Verse 1 Taylor Swift   put    
 5 Taylor Swift            1 Tim McGraw     2 Verse 1 Taylor Swift   georgia
 6 Taylor Swift            1 Tim McGraw     2 Verse 1 Taylor Swift   stars  
 7 Taylor Swift            1 Tim McGraw     2 Verse 1 Taylor Swift   shame  
 8 Taylor Swift            1 Tim McGraw     2 Verse 1 Taylor Swift   night  
 9 Taylor Swift            1 Tim McGraw     3 Verse 1 Taylor Swift   lie    
10 Taylor Swift            1 Tim McGraw     4 Verse 1 Taylor Swift   boy    
# ℹ 24,760 more rows
taylor_tidy |>
  count(word) |>
  slice_max(n = 20, order_by = n) |>
  mutate(word = fct_reorder(.f = word, .x = n)) |>
  ggplot(aes(x = n, y = word)) +
  geom_col() +
  labs(
    title = "Frequency of tokens in Taylor Swift lyrics",
    x = "Number of occurrences",
    y = NULL,
    caption = "Source: {taylor}"
  )

This removes 53,112 words from the corpus.

Domain-specific stop words

While this takes care of generic stop words, we can also identify domain-specific stop words. For example, Taylor Swift’s lyrics are full of interjections and exclamations that are not particularly informative. We can identify these and remove them from the corpus.

# domain-specific stop words
# source: https://rpubs.com/RosieB/642806
taylor_stop_words <- c(
  "oh", "ooh", "eh", "ha", "mmm", "mm", "yeah", "ah",
  "hey", "eeh", "uuh", "uh", "la", "da", "di", "ra",
  "huh", "hu", "whoa", "gonna", "wanna", "gotta", "em"
)

taylor_tidy <- taylor_lyrics |>
  anti_join(get_stopwords(source = "smart")) |>
  filter(!word %in% taylor_stop_words)
taylor_tidy
# A tibble: 23,107 × 7
   album_name   track_number track_name  line element element_artist word   
   <fct>               <int> <chr>      <int> <chr>   <chr>          <chr>  
 1 Taylor Swift            1 Tim McGraw     1 Verse 1 Taylor Swift   blue   
 2 Taylor Swift            1 Tim McGraw     1 Verse 1 Taylor Swift   eyes   
 3 Taylor Swift            1 Tim McGraw     1 Verse 1 Taylor Swift   shined 
 4 Taylor Swift            1 Tim McGraw     2 Verse 1 Taylor Swift   put    
 5 Taylor Swift            1 Tim McGraw     2 Verse 1 Taylor Swift   georgia
 6 Taylor Swift            1 Tim McGraw     2 Verse 1 Taylor Swift   stars  
 7 Taylor Swift            1 Tim McGraw     2 Verse 1 Taylor Swift   shame  
 8 Taylor Swift            1 Tim McGraw     2 Verse 1 Taylor Swift   night  
 9 Taylor Swift            1 Tim McGraw     3 Verse 1 Taylor Swift   lie    
10 Taylor Swift            1 Tim McGraw     4 Verse 1 Taylor Swift   boy    
# ℹ 23,097 more rows
taylor_tidy |>
  count(word, sort = TRUE)
# A tibble: 3,711 × 2
   word      n
   <chr> <int>
 1 love    402
 2 time    349
 3 back    287
 4 baby    226
 5 night   148
 6 stay    134
 7 red     133
 8 home    125
 9 made    124
10 feel    119
# ℹ 3,701 more rows

Words most relevant to each album

Since we know the songs for each album, we can examine the relative significance of different words to different albums. Term frequency-inverse document frequency (tf-idf) is a simple metric for measuring the importance of specific words to a corpus. Here let’s calculate the top ten words for each album.

taylor_tf_idf <- taylor_tidy |>
  count(album_name, word) |>
  bind_tf_idf(term = word, document = album_name, n = n)
taylor_tf_idf
# A tibble: 8,072 × 6
   album_name   word          n       tf   idf   tf_idf
   <fct>        <chr>     <int>    <dbl> <dbl>    <dbl>
 1 Taylor Swift a.m           1 0.000858 1.61  0.00138 
 2 Taylor Swift adore         1 0.000858 1.61  0.00138 
 3 Taylor Swift aisle         1 0.000858 1.20  0.00103 
 4 Taylor Swift album         1 0.000858 1.61  0.00138 
 5 Taylor Swift amen          3 0.00258  2.30  0.00593 
 6 Taylor Swift anymore       1 0.000858 0.357 0.000306
 7 Taylor Swift babies        1 0.000858 1.61  0.00138 
 8 Taylor Swift baby         15 0.0129   0     0       
 9 Taylor Swift back         25 0.0215   0     0       
10 Taylor Swift backroads     1 0.000858 2.30  0.00198 
# ℹ 8,062 more rows
# visualize the top N terms per character by tf-idf score
taylor_tf_idf |>
  group_by(album_name) |>
  slice_max(n = 10, order_by = tf_idf, with_ties = FALSE) |>
  ggplot(mapping = aes(x = tf_idf, y = word)) +
  geom_col() +
  facet_wrap(facets = vars(album_name), scales = "free")

Not very functional sorted alphabetically. Let’s sort all the facets from highest to lowest tf-idf scores.

taylor_tf_idf |>
  group_by(album_name) |>
  slice_max(n = 10, order_by = tf_idf, with_ties = FALSE) |>
  # create word as a factor column ordered by n
  mutate(word = fct_reorder(.f = word, .x = n)) |>
  ggplot(mapping = aes(x = tf_idf, y = word)) +
  geom_col() +
  facet_wrap(facets = vars(album_name), scales = "free")

Still does not look right. The problem is that some tokens appear in multiple facets but with different tf-idf scores (and different orders). We need to order the rows within each facet independently. But ggplot2 does not support this operation. How can we do so? Using tidytext::reorder_within() and tidytext::scale_y_reordered().

taylor_tf_idf |>
  group_by(album_name) |>
  slice_max(n = 10, order_by = tf_idf, with_ties = FALSE) |>
  # create word as a factor column ordered by n
  mutate(word = fct_reorder(.f = word, .x = n)) |>
  # resolve ambiguities when same word appears for different characters
  ungroup() |>
  mutate(word = reorder_within(x = word, by = tf_idf, within = album_name)) |>
  ggplot(mapping = aes(x = tf_idf, y = word)) +
  geom_col() +
  scale_y_reordered() +
  labs(
    title = "Most important words in each Taylor Swift album",
    x = "tf-idf",
    y = NULL,
    caption = "Source: {taylor}"
  ) +
  facet_wrap(
    facets = vars(album_name), scales = "free",
    # wrap each facet label so they fit in the plot
    labeller = label_wrap_gen(width = 20)
  )

Sentiment analysis

Sentiment analysis utilizes the text of the lyrics to classify content as positive or negative. Dictionary-based methods use pre-generated lexicons of words independently coded as positive/negative. We can combine one of these dictionaries with the Taylor Swift tidy-text data frame using inner_join() to identify words with sentimental affect, and further analyze trends.

Here we use the afinn dictionary which classifies 2,477 words on a scale of \([-5, +5]\).

# afinn dictionary
get_sentiments(lexicon = "afinn")
# A tibble: 2,477 × 2
   word       value
   <chr>      <dbl>
 1 abandon       -2
 2 abandoned     -2
 3 abandons      -2
 4 abducted      -2
 5 abduction     -2
 6 abductions    -2
 7 abhor         -3
 8 abhorred      -3
 9 abhorrent     -3
10 abhors        -3
# ℹ 2,467 more rows
# how many words for each value?
get_sentiments(lexicon = "afinn") |>
  count(value)
# A tibble: 11 × 2
   value     n
   <dbl> <int>
 1    -5    16
 2    -4    43
 3    -3   264
 4    -2   966
 5    -1   309
 6     0     1
 7     1   208
 8     2   448
 9     3   172
10     4    45
11     5     5
# join with sentiment dictionary, drop words which are not defined
taylor_afinn <- taylor_tidy |>
  inner_join(y = get_sentiments(lexicon = "afinn"))
taylor_afinn
# A tibble: 4,415 × 8
   album_name   track_number track_name  line element element_artist word  value
   <fct>               <int> <chr>      <int> <chr>   <chr>          <chr> <dbl>
 1 Taylor Swift            1 Tim McGraw     2 Verse 1 Taylor Swift   shame    -2
 2 Taylor Swift            1 Tim McGraw     5 Verse 1 Taylor Swift   stuck    -2
 3 Taylor Swift            1 Tim McGraw    10 Chorus  Taylor Swift   hope      2
 4 Taylor Swift            1 Tim McGraw    10 Chorus  Taylor Swift   favo…     2
 5 Taylor Swift            1 Tim McGraw    13 Chorus  Taylor Swift   happ…     3
 6 Taylor Swift            1 Tim McGraw    14 Chorus  Taylor Swift   hope      2
 7 Taylor Swift            1 Tim McGraw    18 Chorus  Taylor Swift   hope      2
 8 Taylor Swift            1 Tim McGraw    19 Verse 2 Taylor Swift   tears    -2
 9 Taylor Swift            1 Tim McGraw    20 Verse 2 Taylor Swift   god       1
10 Taylor Swift            1 Tim McGraw    25 Verse 2 Taylor Swift   hard     -1
# ℹ 4,405 more rows

Sentimental affect of each song

First, we can examine the sentiment of each song individually by calculating the average sentiment of each word in the song.

taylor_afinn |>
  summarize(sent = mean(value), .by = c(album_name, track_name)) |>
  mutate(track_name = fct_reorder(.f = track_name, .x = sent)) |>
  ggplot(mapping = aes(x = sent, y = track_name, fill = sent)) +
  geom_col() +
  scale_fill_viridis_c() +
  labs(
    title = "Sentimental affect of Taylor Swift songs",
    x = "Average sentiment",
    y = NULL,
    caption = "Source: {taylor}"
  ) +
  theme(
    legend.position = "none",
    plot.title.position = "plot"
  )

Overall Taylor Swift has a wide range of songs based purely on this definition of sentimental affect/emotional valence.

Shake It Off

However, this also illustrates some problems with dictionary-based sentiment analysis. Consider “Shake It Off (Taylor’s Version)” which is scored as a -2.25.

Taylor Swift singing 'Haters gonna hate'

Taylor Swift shaking it off
# what's up with shake it off?
taylor_afinn |>
  filter(track_name == "Shake It Off (Taylor's Version)") |>
  count(word, value) |>
  arrange(-value)
# A tibble: 12 × 3
   word   value     n
   <chr>  <dbl> <int>
 1 good       3     1
 2 haha       3     3
 3 god        1     1
 4 stop      -1     4
 5 dirty     -2     2
 6 miss      -2     1
 7 sick      -2     1
 8 cheats    -3     1
 9 fake      -3    18
10 hate      -3    16
11 haters    -3     4
12 liars     -3     1

Would most listeners classify this song as full of negativity? Probs not. Herein lies the problem with dictionary-based methods. The AFINN lexicon codes “fake” and “hate” as negative terms, even though in this context they are being used more positively – the singer is shaking off the haters and going her own way in life.

This is a common problem with dictionary-based methods, and why it is important to carefully consider the context of the text being analyzed.

Sentimental affect of each album

We could also examine the general disposition of each album based on their overall positive/negative affect.

# errorbar plot
taylor_afinn |>
  # calculate average sentiment by album with standard error
  summarize(
    sent = mean(value),
    se = sd(value) / sqrt(n()),
    .by = album_name
  ) |>
  # reverse album order for vertical plot
  mutate(album_name = fct_rev(f = album_name)) |>
  # generate plot
  ggplot(mapping = aes(y = album_name, x = sent)) +
  geom_pointrange(mapping = aes(
    xmin = sent - 2 * se,
    xmax = sent + 2 * se
  )) +
  labs(
    title = "Emotional affect in Taylor Swift albums",
    x = "Average sentiment",
    y = NULL,
    caption = "Source: {taylor}"
  )

# ggridge plot with tayloRswift color palette
taylor_afinn |>
  summarize(sent = mean(x = value), .by = c(album_name, track_name)) |>
  # reverse album order for vertical plot
  mutate(album_name = fct_rev(f = album_name)) |>
  ggplot(mapping = aes(x = sent, y = album_name, fill = after_stat(x))) +
  geom_density_ridges_gradient(quantile_lines = TRUE, quantiles = 0.5) +
  scale_fill_taylor_c(album = "Red", guide = "none") +
  labs(
    title = "Emotional affect in Taylor Swift albums",
    x = "Average sentiment",
    y = NULL,
    caption = "Source: {taylor}"
  )

Notice that we are visualizing the albums in chronological order, but the all the “Taylor’s Version” albums are re-recordings of albums she made early in her career. We can stan artists owning their own music, but what about her career arc? Has Taylor Swift gotten more positive or negative across her career?

In order to assess that, we have to go back to her original album recordings. We can do this by joining the taylor_afinn data frame with the taylor_all_songs data frame, which contains all of her original songs.

taylor_all_tidy <- taylor_all_songs |>
  select(album_name, track_number, track_name, lyrics) |>
  unnest(col = lyrics) |>
  unnest_tokens(output = word, input = lyric) |>
  # stop word removal
  anti_join(get_stopwords(source = "smart")) |>
  filter(!word %in% taylor_stop_words) |>
  # filter to full studio albums
  semi_join(y = taylor_albums |>
    filter(!ep)) |>
  # exclude rereleases
  filter(!str_detect(string = album_name, pattern = "Taylor's Version")) |>
  # order albums by release date
  mutate(album_name = factor(x = album_name, levels = taylor_albums$album_name) |>
           fct_rev())


# errorbar plot
taylor_all_tidy |>
  # join with sentiment dictionary
  inner_join(y = get_sentiments(lexicon = "afinn")) |>
  # calculate average sentiment by album with standard error
  summarize(
    sent = mean(value),
    se = sd(value) / sqrt(n()),
    .by = album_name
  ) |>
  # generate plot sorted from positive to negative
  ggplot(mapping = aes(y = album_name, x = sent)) +
  geom_pointrange(mapping = aes(
    xmin = sent - 2 * se,
    xmax = sent + 2 * se
  )) +
  labs(
    title = "Emotional affect in Taylor Swift albums",
    subtitle = "Original studio albums",
    x = "Average sentiment",
    y = NULL,
    caption = "Source: {taylor}"
  )

# ggridge plot with tayloRswift color palette
taylor_all_tidy |>
  # join with sentiment dictionary
  inner_join(y = get_sentiments(lexicon = "afinn")) |>
  # calculate average sentiment by album with standard error
  summarize(
    sent = mean(value),
    .by = c(album_name, track_name)
  ) |>
  ggplot(mapping = aes(x = sent, y = album_name, fill = after_stat(x))) +
  geom_density_ridges_gradient(quantile_lines = TRUE, quantiles = 0.5) +
  scale_fill_taylor_c(album = "Red", guide = "none") +
  labs(
    title = "Emotional affect in Taylor Swift albums",
    subtitle = "Original studio albums",
    x = "Average sentiment",
    y = NULL,
    caption = "Source: {taylor}"
  )

Depending on how you feel about her music, you might be surprised to see that her first album is the most positive. It’s also interesting to see that one of her most recent albums, evermore, is the most negative.

Varying types of sentiment

tidytext includes multiple sentiment dictionaries for different types of sentiment. We can use the nrc dictionary to examine the different types of sentiment in Taylor Swift’s lyrics.

taylor_all_tidy |>
  # join with sentiment dictionary
  inner_join(y = get_sentiments(lexicon = "nrc"), relationship = "many-to-many") |>
  filter(sentiment != "positive", sentiment != "negative") |>
  count(album_name, sentiment) |>
  # normalize for number of tokens per album
  left_join(y = taylor_all_tidy |>
    count(album_name, name = "n_all")) |>
  mutate(n_pct = n / n_all) |>
  # reverse album order for vertical plot
  mutate(album_name = fct_rev(f = album_name)) |>
  ggplot(mapping = aes(x = n_pct, y = album_name)) +
  geom_col() +
  scale_x_continuous(labels = percent_format()) +
  facet_wrap(
    facets = vars(sentiment)
  ) +
  labs(
    title = "Sentimental affect (by type) in Taylor Swift albums",
    subtitle = "Original studio albums",
    x = "Percentage of tokens in the album",
    y = NULL,
    caption = "Source: {taylor}"
  )

sessioninfo::session_info()
─ Session info ───────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.3.1 (2023-06-16)
 os       macOS Ventura 13.5.2
 system   aarch64, darwin20
 ui       X11
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       America/New_York
 date     2023-11-15
 pandoc   3.1.1 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/ (via rmarkdown)

─ Packages ───────────────────────────────────────────────────────────────────
 package     * version    date (UTC) lib source
 cli           3.6.1      2023-03-23 [1] CRAN (R 4.3.0)
 colorspace    2.1-0      2023-01-23 [1] CRAN (R 4.3.0)
 digest        0.6.33     2023-07-07 [1] CRAN (R 4.3.0)
 dplyr       * 1.1.3      2023-09-03 [1] CRAN (R 4.3.0)
 evaluate      0.22       2023-09-29 [1] CRAN (R 4.3.1)
 fansi         1.0.5      2023-10-08 [1] CRAN (R 4.3.1)
 farver        2.1.1      2022-07-06 [1] CRAN (R 4.3.0)
 fastmap       1.1.1      2023-02-24 [1] CRAN (R 4.3.0)
 forcats     * 1.0.0      2023-01-29 [1] CRAN (R 4.3.0)
 fs            1.6.3      2023-07-20 [1] CRAN (R 4.3.0)
 generics      0.1.3      2022-07-05 [1] CRAN (R 4.3.0)
 ggplot2     * 3.4.2      2023-04-03 [1] CRAN (R 4.3.0)
 ggridges    * 0.5.4      2022-09-26 [1] CRAN (R 4.3.0)
 glue          1.6.2      2022-02-24 [1] CRAN (R 4.3.0)
 gtable        0.3.3      2023-03-21 [1] CRAN (R 4.3.0)
 here          1.0.1      2020-12-13 [1] CRAN (R 4.3.0)
 hms           1.1.3      2023-03-21 [1] CRAN (R 4.3.0)
 htmltools     0.5.6.1    2023-10-06 [1] CRAN (R 4.3.1)
 htmlwidgets   1.6.2      2023-03-17 [1] CRAN (R 4.3.0)
 janeaustenr   1.0.0      2022-08-26 [1] CRAN (R 4.3.0)
 jsonlite      1.8.7      2023-06-29 [1] CRAN (R 4.3.0)
 knitr         1.44       2023-09-11 [1] CRAN (R 4.3.0)
 labeling      0.4.2      2020-10-20 [1] CRAN (R 4.3.0)
 lattice       0.21-8     2023-04-05 [1] CRAN (R 4.3.0)
 lifecycle     1.0.3      2022-10-07 [1] CRAN (R 4.3.0)
 lubridate   * 1.9.3      2023-09-27 [1] CRAN (R 4.3.1)
 magrittr      2.0.3      2022-03-30 [1] CRAN (R 4.3.0)
 Matrix        1.5-4.1    2023-05-18 [1] CRAN (R 4.3.0)
 munsell       0.5.0      2018-06-12 [1] CRAN (R 4.3.0)
 pillar        1.9.0      2023-03-22 [1] CRAN (R 4.3.0)
 pkgconfig     2.0.3      2019-09-22 [1] CRAN (R 4.3.0)
 purrr       * 1.0.2      2023-08-10 [1] CRAN (R 4.3.0)
 R6            2.5.1      2021-08-19 [1] CRAN (R 4.3.0)
 rappdirs      0.3.3      2021-01-31 [1] CRAN (R 4.3.0)
 Rcpp          1.0.10     2023-01-22 [1] CRAN (R 4.3.0)
 readr       * 2.1.4      2023-02-10 [1] CRAN (R 4.3.0)
 rlang         1.1.1      2023-04-28 [1] CRAN (R 4.3.0)
 rmarkdown     2.25       2023-09-18 [1] CRAN (R 4.3.1)
 rprojroot     2.0.3      2022-04-02 [1] CRAN (R 4.3.0)
 rstudioapi    0.14       2022-08-22 [1] CRAN (R 4.3.0)
 scales      * 1.2.1      2022-08-20 [1] CRAN (R 4.3.0)
 sessioninfo   1.2.2      2021-12-06 [1] CRAN (R 4.3.0)
 SnowballC     0.7.1      2023-04-25 [1] CRAN (R 4.3.0)
 stopwords     2.3        2021-10-28 [1] CRAN (R 4.3.0)
 stringi       1.7.12     2023-01-11 [1] CRAN (R 4.3.0)
 stringr     * 1.5.0      2022-12-02 [1] CRAN (R 4.3.0)
 taylor      * 2.0.1.9000 2023-11-01 [1] Github (wjakethompson/taylor@1ac675f)
 tayloRswift * 0.2.0      2023-11-02 [1] Github (asteves/tayloRswift@445a16a)
 textdata      0.4.4      2022-09-02 [1] CRAN (R 4.3.0)
 tibble      * 3.2.1      2023-03-20 [1] CRAN (R 4.3.0)
 tidyr       * 1.3.0      2023-01-24 [1] CRAN (R 4.3.0)
 tidyselect    1.2.0      2022-10-10 [1] CRAN (R 4.3.0)
 tidytext    * 0.4.1      2023-01-07 [1] CRAN (R 4.3.0)
 tidyverse   * 2.0.0      2023-02-22 [1] CRAN (R 4.3.0)
 timechange    0.2.0      2023-01-11 [1] CRAN (R 4.3.0)
 tokenizers    0.3.0      2022-12-22 [1] CRAN (R 4.3.0)
 tzdb          0.4.0      2023-05-12 [1] CRAN (R 4.3.0)
 utf8          1.2.4      2023-10-22 [1] CRAN (R 4.3.1)
 vctrs         0.6.4      2023-10-12 [1] CRAN (R 4.3.1)
 viridisLite   0.4.2      2023-05-02 [1] CRAN (R 4.3.0)
 withr         2.5.2      2023-10-30 [1] CRAN (R 4.3.1)
 xfun          0.40       2023-08-09 [1] CRAN (R 4.3.0)
 yaml          2.3.7      2023-01-23 [1] CRAN (R 4.3.0)

 [1] /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/library

──────────────────────────────────────────────────────────────────────────────