Last time, I did some basic frequency analyses of Kanye West lyrics. I've been using the
tidytext package at work a lot recently, and I thought I would apply some of the package's sentiment dictionaries to the Kanye West corpus I have already made (discussed here). The corpus is not big enough to do the analyses by song or by a very specific emotion (such as joy), so I will stick to tracking positive and negative sentiment of Kanye's lyrical content over the course of his career.
For each song, I removed duplicate words (for reasons like so that the song “Amazing” doesn't have an undue influence on the analyses, given that he says “amazing"—a positive word—about 50 times). I allowed duplicate words from the same album, but not from the same song.
tidytext package includes three different sentiment analysis dictionaries. One of the dictionaries assigns each word a score from -5 (very negative) to +5 (very positive). Using this dictionary, I simply took the average score for the album:
afinn <- kanye %>% # starting with full data set unnest_tokens(word, lyrics) %>% # what was long string of text, now becomes one word per row inner_join(get_sentiments("afinn"), by="word") %>% # joining sentiments with these words group_by(song) %>% # grouping dataset by song subset(!duplicated(word)) %>% # dropping duplicates (by song, since we are grouped by song) ungroup() %>% # ungrouping real quick... group_by(album) %>% # and now grouping by album mutate(sentiment=mean(score)) %>% # getting mean by album slice(1) %>% # taking one row per every album ungroup() %>% # ungrouping subset(select=c(album,sentiment)) # only including the album and sentiment columns
The other two dictionaries tag each word as negative or positive. For these, I tallied up how many positive and negative words were being used per album and subtracted the negative count from the positive count:
bing <- kanye %>% # taking data set unnest_tokens(word, lyrics) %>% # making long list of lyrics into one word per row inner_join(get_sentiments("bing"), by="word") %>% # joining words with sentiment group_by(song) %>% # grouping by song subset(!duplicated(word)) %>% # getting rid of duplicated words (within each song) ungroup() %>% # ungrouping count(album, sentiment) %>% # counting up negative and positive sentiments per album spread(sentiment, n, fill=0) %>% # putting negative and positive into different columns mutate(sentiment=positive-negative) # subtracting negative from positive # all of this is same as above, but with a different dictionary nrc <- kanye %>% unnest_tokens(word, lyrics) %>% inner_join(get_sentiments("nrc"), by="word") %>% group_by(song) %>% subset(!duplicated(word)) %>% ungroup() %>% count(album, sentiment) %>% spread(sentiment, n, fill=0) %>% mutate(sentiment=positive-negative)
I then merged all these data frames together, standardized the sentiment scores, and averaged the three z-scores together to get an overall sentiment rating for each album:
colnames(afinn) <- "sentiment_afinn" # renaming column will make it easier upon joining bing <- ungroup(subset(bing, select=c(album,sentiment))) # getting rid of unnecessary columns colnames(bing) <- "sentiment_bing" nrc <- ungroup(subset(nrc, select=c(album,sentiment))) colnames(nrc) <- "sentiment_nrc" suppressMessages(library(plyr)) # getting plyr temporarily because I like join_all() album_sent <- join_all(list(afinn, bing, nrc), by="album") # joining all three datasets by album suppressMessages(detach("package:plyr")) # getting rid of plyr, because it gets in the way of dplyr # creating composite sentiment score: album_sent$sent <- (scale(album_sent$sentiment_afinn) + scale(album_sent$sentiment_bing) + scale(album_sent$sentiment_nrc))/3 album_sent <- album_sent[-7,c(1,5)] # subsetting data to not include watch throne and only include composite # reordering the albums in chronological order album_sent$album <- factor(album_sent$album, levels=levels(album_sent$album)[c(2,4,3,1,5,7,8,6)])
And then I plotted the sentiment scores by album, in chronological order. Higher scores represent more positive lyrics:
ggplot(album_sent, aes(x=album, y=sent))+ geom_point()+ geom_line(group=1, linetype="longdash", size=.8, alpha=.5)+ labs(x="Album", y="Sentiment")+ theme_fivethirtyeight()+ scale_x_discrete(labels=c("The College Dropout", "Late Registration", "Graduation", "808s & Heartbreak", "MBDTF", "Yeezus", "The Life of Pablo"))+ theme(axis.text.x=element_text(angle=45, hjust=1), text = element_text(size=12))
Looks like a drop in happiness after his mother passed as well as after starting to date Kim Kardashian…
This plot jibes with what we know: 808s is the "sad” album. Graduation—with all the talk about achievement, being strong, making hit records, and having a big ego—peaks at the album with the most positive words. This plot seems to lend some validity to the method of analyzing sentiments using all three dictionaries from the
Some issues with this “bag-of-words” approach, however, was that it was prone to error in the finer level of analysis, like song. “Street Lights,” perhaps one of Kanye's saddest songs, came out with one of the most positive scores. Why? It was reading words like “fair” as positive, neglecting to realize that a negation word (i.e., “not”) always preceded it. One could get around this with maybe n-grams or natural language processing.
Nevertheless, there's the trajectory of Kanye's lyrical positivity over time!