本文概述
在由三部分组成的教程系列的第二部分A中, 使用R进行情感分析, 以探索艺术家王子的生平和时代中的感伤之旅, 以洞悉艺术家的职业生涯和社会影响力。这三个教程涵盖以下内容:
- 第一部分:文本挖掘和探索性分析
- 第二部分:使用NLP进行情感分析和主题建模
- 第三部分:使用机器学习进行预测分析
如果你想了解更多有关情绪分析的信息, 请务必阅读我们的《 R:整洁方式》中的情绪分析课程。
你是否知道王子在舞台上预测9/11, 比这发生早了三年?他可能还预言了自己的死亡。这是真的吗?自己检查歌词并进行有教养的判断会不会很有趣? 1985年4月21日, 他录制了《四月的下雪天》。恰好在31年后的2016年4月21日, 他去世了。在这首歌的情感分析中, 他的语言选择如下:
“四月有时下雪”的情感分析
这为预测分析赋予了新的含义!
内容:
- 介绍
- 准备工作
- 描述性统计
- Lexicons和歌词
- 详细分析
- 总结
结构体:
每一段代码后面都有一个洞察力, 通常是主观的。如果你愿意, 可以根据自己的假设在各部分之间随意移动!我建议阅读以下介绍, 但是如果你想直接跳转到代码, 请单击此处。如果你已经了解了词典背后的概念, 则可以直接进入详细的分析部分, 以快速进行研究。
介绍
三元组:三部分总结
我刚刚救出了两只小猫和一只小狗, 所以现在我想三分之二。另外, 三分法则是一条写作原则, 表明三分法本身比其他数字或事物更有趣, 更令人满意或更有效-这就是为什么我将其分为三个部分…所以第一部分, 由传奇艺术家Prince用数百个歌曲歌词的数据集向你介绍了文本挖掘和探索性分析。在第二部分中, 你将重点介绍使用NLP进行的情感分析(Two-A)和主题建模(Two-B)。第三部分将使用机器学习技术解决诸如以下问题之类的其他预测性分析任务, 这些问题包括是否可以确定歌曲发行的十年, 或更有趣的是, 根据歌曲的歌词预测歌曲是否会进入Billboard排行榜。在这些教程中, 你将利用不同的机器学习算法-每个算法在下图中以红色突出显示。
根据此处显示的图形改编。请注意, 建模和机器学习技术的不同方面未必适合如上所述的单个框。这只是为了让你对期望的想法有所了解。
分手很难做
是的, 我只介绍了三个部分, 但我决定将第二部分分为2A(情感分析)和2B(主题建模-即将推出), 产生了四个截然不同的教程。关于情感分析背后的过程有很多很棒的文章和课程, 因此第二部分-A的目标是着重于从分析中得出见解, 而不仅仅是编写代码。你将以Prince的歌词为例, 但是你可以将这些步骤应用于自己喜欢的艺术家。在整个教程系列中, 你的旅程将有望使人们对抒情分析所提供的机会感到惊讶, 同时将其引入自然语言处理(NLP)和机器学习技术。
先决条件
本教程系列的第2部分至第A部分要求对整洁的数据有基本的了解-特别是软件包, 例如dplyr进行数据转换, ggplot2进行可视化, 以及%>%管道运算符(最初来自magrittr软件包)。你会注意到, 通常使用%>%运算符将几个步骤组合在一起, 以使代码紧凑且可重复使用。
由于本教程也是一个案例研究, 因此除了常用的图形和图表以外, 它还包含许多可视化内容。因此, 另一个先决条件是能够在你自己的环境中安装新软件包。为了专注于获取见解, 我提供了旨在在你自己的数据集上重用的代码, 但是详细的解释将需要你自己进行进一步的研究。每个包装都将提供支持链接。这将使你在专注于它们的应用程序的同时, 接触到新工具。
提示:首先阅读第1部分, 以便为该系列教程奠定基础并深入了解Prince歌词的丰富词汇。
情绪分析概述
- 方法:情感分析是一种文本挖掘, 旨在确定其内容的观点和主观性。当应用于歌词时, 结果不仅可以代表艺术家的态度, 而且可以揭示普遍的文化影响。有多种用于情感分析的方法, 包括训练已知数据集, 使用规则创建自己的分类器以及使用预定义的词典(词典)。在本教程中, 你将使用基于词典的方法, 但我鼓励你研究其他方法及其相关的取舍。
- 级别:正如用于情感分析的方法不同, 基于文本的分析级别也不同。这些级别通常标识为文档, 句子和单词。在歌词中, 文档可以定义为每十年, 一年, 图表级别或歌曲中的情感。句子级别通常不是歌词的选项, 因为标点符号会降低韵律和模式。词级分析提供了详细的信息, 并且可以用作主题建模中更高级实践的基础知识。
准备你的问题!
每个数据科学项目都需要探索一系列问题。在学习本教程时, 请牢记以下几点:是否可以编写一个程序来确定歌词中表达的心情?预定义词典是否足够?需要准备多少数据?你可以将结果链接到现实生活中吗?情绪会随着时间变化吗?热门歌曲比未知歌曲更正面或负面吗?在据说普林斯预测9/11的那一年里, 歌词中哪些词特别醒目?他有预言自己会死吗?
提示:抒情分析与演讲或书籍非常不同, 通常比演讲或书籍更为复杂, 因此很难获取见解, 因此请记住要谨慎!在本教程中, 我将提出的问题多于答案, 因此请准备好在四边形平行四边形之外思考!
准备工作
库和功能
要开始分析Prince的歌词, 请加载下面的库。这些一开始看起来似乎很艰巨, 但其中大多数仅用于图形和图表。考虑到经常使用视觉效果, 最好定义一个标准的配色方案以保持一致性。我使用特定的ANSI颜色代码创建了一个列表。 ggplot2中的theme()函数允许自定义各个图形, 因此你还将创建自己的函数theme_lyrics(), 该函数将修改默认设置。 Knitr软件包是使用R生成动态报告的引擎。将其与kableExtra和formattable一起使用可创建带有颜色的有吸引力的文本表。再次创建自己的函数my_kable_styling()以标准化这些库的结果输出。我会提到其他使用的软件包。
library(dplyr) #Data manipulation (also included in the tidyverse package)
library(tidytext) #Text mining
library(tidyr) #Spread, separate, unite, text mining (also included in the tidyverse package)
library(widyr) #Use for pairwise correlation
#Visualizations!
library(ggplot2) #Visualizations (also included in the tidyverse package)
library(ggrepel) #`geom_label_repel`
library(gridExtra) #`grid.arrange()` for multi-graphs
library(knitr) #Create nicely formatted output tables
library(kableExtra) #Create nicely formatted output tables
library(formattable) #For the color_tile function
library(circlize) #Visualizations - chord diagram
library(memery) #Memes - images with plots
library(magick) #Memes - images with plots (image_read)
library(yarrr) #Pirate plot
library(radarchart) #Visualizations
library(igraph) #ngram network diagrams
library(ggraph) #ngram network diagrams
#Define some colors to use throughout
my_colors <- c("#E69F00", "#56B4E9", "#009E73", "#CC79A7", "#D55E00", "#D65E00")
#Customize ggplot2's default theme settings
#This tutorial doesn't actually pass any parameters, but you may use it again in future tutorials so it's nice to have the options
theme_lyrics <- function(aticks = element_blank(), pgminor = element_blank(), lt = element_blank(), lp = "none")
{
theme(plot.title = element_text(hjust = 0.5), #Center the title
axis.ticks = aticks, #Set axis ticks to on or off
panel.grid.minor = pgminor, #Turn the minor grid lines on or off
legend.title = lt, #Turn the legend title on or off
legend.position = lp) #Turn the legend on or off
}
#Customize the text tables for consistency using HTML formatting
my_kable_styling <- function(dat, caption) {
kable(dat, "html", escape = FALSE, caption = caption) %>%
kable_styling(bootstrap_options = c("striped", "condensed", "bordered"), full_width = FALSE)
}
寿司数据
与往常一样, 你将从读取原始数据到数据帧开始。在第一部分中, 你对原始数据集执行了一些数据条件处理, 例如扩展收缩, 删除转义序列以及将文本转换为小写。你已将该干净数据集保存到CSV文件中, 以用于本教程。使用read.csv()创建prince_data并记住要使歌词成为字符串, 因此请确保将stringsAsFactors设置为FALSE。由于不需要编号的行, 因此设置row.names = 1。
请注意, 可以通过使用read_csv()将其读入称为tibble的现代数据帧来避免使用这些参数, 但是为了与第1部分保持一致, 请使用read.csv()。
prince_data <- read.csv('prince_new.csv', stringsAsFactors = FALSE, row.names = 1)
快速浏览一下数据:
glimpse(prince_data) #Transposed version of `print()`
## Observations: 824
## Variables: 10
## $ lyrics <chr> "all 7 and we will watch them fall they stand in t...
## $ song <chr> "7", "319", "1999", "2020", "3121", "7779311", "u"...
## $ year <int> 1992, NA, 1982, NA, 2006, NA, NA, NA, NA, NA, NA, ...
## $ album <chr> "Symbol", NA, "1999", "Other Songs", "3121", NA, N...
## $ peak <int> 3, NA, 2, NA, 1, NA, NA, NA, NA, NA, NA, NA, NA, N...
## $ us_pop <chr> "7", NA, "12", NA, "1", NA, NA, NA, NA, NA, NA, NA...
## $ us_rnb <chr> "61", NA, "4", NA, "1", NA, NA, NA, NA, NA, NA, NA...
## $ decade <chr> "1990s", NA, "1980s", NA, "2000s", NA, NA, NA, NA, ...
## $ chart_level <chr> "Top 10", "Uncharted", "Top 10", "Uncharted", "Top...
## $ charted <chr> "Charted", "Uncharted", "Charted", "Uncharted", "C...
你可以看到prince_data是824首歌曲和10列的数据帧。从字面上看, 这意味着唱片是一首歌!
好清洁好玩:prince_tidy
以下是一些剩余的数据整理步骤:
- 删除不需要的单词(手动列出不必要的单词)
- 删除停用词(过于常见的词, 例如” and”, ” the”, ” a”, ” of”等)
- 删除少于三个字符的单词(通常用于音乐中的语音效果)
- 将歌词拆分为单个单词
为了将原始数据转换为整齐的格式, 请使用tidytext中的unnest_tokens()创建prince_tidy, 它将歌词分解成单个单词, 每行一个单词。然后, 可以使用dplyr中的anti_join()和filter()进行其余的清洁步骤。 (有关详细说明, 请参见第一部分。)
#Created in the first tutorial
undesirable_words <- c("prince", "chorus", "repeat", "lyrics", "theres", "bridge", "fe0f", "yeah", "baby", "alright", "wanna", "gonna", "chorus", "verse", "whoa", "gotta", "make", "miscellaneous", "2", "4", "ooh", "uurh", "pheromone", "poompoom", "3121", "matic", " ai ", " ca ", " la ", "hey", " na ", " da ", " uh ", " tin ", " ll", "transcription", "repeats", "la", "da", "uh", "ah")
#Create tidy text format: Unnested, Unsummarized, -Undesirables, Stop and Short words
prince_tidy <- prince_data %>%
unnest_tokens(word, lyrics) %>% #Break the lyrics into individual words
filter(!word %in% undesirable_words) %>% #Remove undesirables
filter(!nchar(word) < 3) %>% #Words like "ah" or "oo" used in music
anti_join(stop_words) #Data provided by the tidytext package
glimpse(prince_tidy) #From `dplyr`, better than `str()`.
## Observations: 76, 116
## Variables: 10
## $ song <chr> "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", ...
## $ year <int> 1992, 1992, 1992, 1992, 1992, 1992, 1992, 1992, 19...
## $ album <chr> "Symbol", "Symbol", "Symbol", "Symbol", "Symbol", ...
## $ peak <int> 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, ...
## $ us_pop <chr> "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", ...
## $ us_rnb <chr> "61", "61", "61", "61", "61", "61", "61", "61", "6...
## $ decade <chr> "1990s", "1990s", "1990s", "1990s", "1990s", "1990...
## $ chart_level <chr> "Top 10", "Top 10", "Top 10", "Top 10", "Top 10", ...
## $ charted <chr> "Charted", "Charted", "Charted", "Charted", "Chart...
## $ word <chr> "watch", "fall", "stand", "love", "smoke", "intell...
你的新数据集prince_tidy现在采用了标记化格式, 每行一个单词以及它来自的歌曲。现在, 你具有76116个字和10列的数据框。
描述性统计
如果你还没有阅读第1部分, 则可能需要快速浏览整个数据集的一些摘要图。你将使用ggplot2, circlize和yarrr软件包中的创意图形来完成此操作。
造形:每首歌曲的字数
当一切都井井有条, 整洁干净时, 海盗会说船形。因此, 这里有一个有趣而干净的数据视图, 显示了一段时间内歌词的词法多样性, 或者换句话说, 是词汇量。海盗图是一种根据连续自变量(如十进制)绘制连续因变量(例如字数)的高级方法。它将原始数据点, 描述性和推断性统计信息合并到一个有效图中。查看这个很棒的博客, 以获取有关yarrr包中pirateplot()的更多详细信息。
创建word_summary数据框架, 该数据框架计算每首歌曲的独立单词数。歌词越多样化, 词汇量就越大。以这种方式考虑数据可帮助你做好词级分析的准备。对于没有发行日期的歌曲, 重置十年字段以使其包含值” NONE”, 然后使用select()使用更清晰的标签重新标记这些字段。
word_summary <- prince_tidy %>%
mutate(decade = ifelse(is.na(decade), "NONE", decade)) %>%
group_by(decade, song) %>%
mutate(word_count = n_distinct(word)) %>%
select(song, Released = decade, Charted = charted, word_count) %>%
distinct() %>% #To obtain one record per song
ungroup()
pirateplot(formula = word_count ~ Released + Charted, #Formula
data = word_summary, #Data frame
xlab = NULL, ylab = "Song Distinct Word Count", #Axis labels
main = "Lexical Diversity Per Decade", #Plot title
pal = "google", #Color scheme
point.o = .2, #Points
avg.line.o = 1, #Turn on the Average/Mean line
theme = 0, #Theme
point.pch = 16, #Point `pch` type
point.cex = 1.5, #Point size
jitter.val = .1, #Turn on jitter to see the songs better
cex.lab = .9, cex.names = .7) #Axis label size
此海盗剧情中的每个彩色圆圈代表一首歌。带有” NONE”值的红色密集区域表示数据集中的大量歌曲没有发布日期。在最初的几十年中, 每首歌曲的独特单词数略有上升:水平实线显示了该十年的平均单词数。重要的是要知道何时开始分析情绪。这句话在整个普林斯的职业生涯中都更具启发性。
我会挑战你更详细地探索海盗区, 因为你只接触了这个海盗区!
全年:每年歌曲数
圆形图是一种可视化几种类别之间复杂(或简单!)关系的独特方法。 (此外, 相册曾经是圆形的!)。下图只是使用ggplot2中的coord_polar()的圆形条形图, 它显示了每年的相对歌曲数。生成如此简单的图形需要大量的代码, 但这是完全值得的。在此处的” R图图库”中可以找到更多类似的示例。与海盗图一样, 这只是圆形图的介绍。
每年歌曲数
songs_year <- prince_data %>%
select(song, year) %>%
group_by(year) %>%
summarise(song_count = n())
id <- seq_len(nrow(songs_year))
songs_year <- cbind(songs_year, id)
label_data = songs_year
number_of_bar = nrow(label_data) #Calculate the ANGLE of the labels
angle = 90 - 360 * (label_data$id - 0.5) / number_of_bar #Center things
label_data$hjust <- ifelse(angle < -90, 1, 0) #Align label
label_data$angle <- ifelse(angle < -90, angle + 180, angle) #Flip angle
ggplot(songs_year, aes(x = as.factor(id), y = song_count)) +
geom_bar(stat = "identity", fill = alpha("purple", 0.7)) +
geom_text(data = label_data, aes(x = id, y = song_count + 10, label = year, hjust = hjust), color = "black", alpha = 0.6, size = 3, angle = label_data$angle, inherit.aes = FALSE ) +
coord_polar(start = 0) +
ylim(-20, 150) + #Size of the circle
theme_minimal() +
theme(axis.text = element_blank(), axis.title = element_blank(), panel.grid = element_blank(), plot.margin = unit(rep(-4, 4), "in"), plot.title = element_text(margin = margin(t = 10, b = -10)))
看到顶部的缝隙了吗?这是在海盗情节中确定的数百首没有发行日期的歌曲的指示。包含大量未发行的歌曲, 如果包含这些歌曲, 则该图表将无用。缺少的年份表示没有歌曲发行的年份。最多产的年份是1996年和1998年。
你想知道为什么吗?继续阅读!
和弦:按十年计的歌曲
下图显示了歌曲发行的十年与歌曲是否达到Billboard图表之间的关系。使用chordDiagram()进行音乐分析似乎很合适!该图形工具来自Zuguang Gu的精美circlize软件包。该图分为两类:图表(上图)和十年(下图)。这两个类别之间的差距较大, 而值之间的差距较小。下面代码中的高级注释可以在此处进行补充。
decade_chart <- prince_data %>%
filter(decade != "NA") %>% #Remove songs without release dates
count(decade, charted) #Get SONG count per chart level per decade. Order determines top or bottom.
circos.clear() #Very important - Reset the circular layout parameters!
grid.col = c("1970s" = my_colors[1], "1980s" = my_colors[2], "1990s" = my_colors[3], "2000s" = my_colors[4], "2010s" = my_colors[5], "Charted" = "grey", "Uncharted" = "grey") #assign chord colors
# Set the global parameters for the circular layout. Specifically the gap size
circos.par(gap.after = c(rep(5, length(unique(decade_chart[[1]])) - 1), 15, rep(5, length(unique(decade_chart[[2]])) - 1), 15))
chordDiagram(decade_chart, grid.col = grid.col, transparency = .2)
title("Relationship Between Chart and Decade")
上面的圆圈图乍看起来似乎很复杂, 但是很好地说明了每个图表级别每十年的歌曲计数。你可以看到Prince在1970年代开始了他的职业生涯, 其中只有少数发行, 其中有些发行是有计划的。如果将1980年代与1990年代进行比较, 你会发现1990年代发行的歌曲更多, 而1980年代发行的歌曲更多。在2000年代, 只有几首商业上成功的歌曲, 而在2010年代, 没有流行歌曲。
Lexicons和歌词
在本节中, 你将:
- 探索情感词典及其字数统计
- 从词典中确定歌词中有多少个单词
- 查看特定的单词和单词形式
- 考虑其他数据条件
探索情感词汇表
tidytext包包括一个称为情感的数据集, 它提供了几个不同的词典。这些词典是具有指定情感类别或价值的单词词典。 tidytext提供了三个通用词典:
- AFINN:分配分数在-5到5之间的单词, 负分数表示负面情绪, 正分数表示正面情绪
- 必应:将单词分为正面和负面类别
- NRC:将单词分为以下十个类别中的一个或多个:正面, 负面, 愤怒, 预期, 厌恶, 恐惧, 喜悦, 悲伤, 惊奇和信任
为了检查词典, 创建一个名为new_sentiments的数据框。过滤掉一个金融词典, 通过将数字分数转换为正数或负数来为AFINN词典创建一个二进制(也称为极性)情感字段, 并添加一个字段来保存每个词典的不同字数。
new_sentiments的一列包含不同的情感类别, 因此, 为了更好地了解每个词典, 每个类别的字数, 请使用tidyr中的spread()将这些类别分为不同的字段。利用你先前创建的my_kable_styling()函数中的knitr和kableExtra软件包。使用formattable中的color_tile()和color_bar()向图表添加颜色, 以创建格式正确的表格。打印表并检查每个词典之间的差异。
new_sentiments <- sentiments %>% #From the tidytext package
filter(lexicon != "loughran") %>% #Remove the finance lexicon
mutate( sentiment = ifelse(lexicon == "AFINN" & score >= 0, "positive", ifelse(lexicon == "AFINN" & score < 0, "negative", sentiment))) %>%
group_by(lexicon) %>%
mutate(words_in_lexicon = n_distinct(word)) %>%
ungroup()
new_sentiments %>%
group_by(lexicon, sentiment, words_in_lexicon) %>%
summarise(distinct_words = n_distinct(word)) %>%
ungroup() %>%
spread(sentiment, distinct_words) %>%
mutate(lexicon = color_tile("lightblue", "lightblue")(lexicon), words_in_lexicon = color_bar("lightpink")(words_in_lexicon)) %>%
my_kable_styling(caption = "Word Counts Per Lexicon")
词典 | words_in_lexicon | 愤怒 | 预期 | 厌恶 | 恐惧 | 喜悦 | 负 | 正 | 悲伤 | 吃惊 | 信任 |
---|---|---|---|---|---|---|---|---|---|---|---|
祖父 | 2476 | NA | NA | NA | NA | NA | 1597 | 879 | NA | NA | NA |
bing | 6785 | NA | NA | NA | NA | NA | 4782 | 2006 | NA | NA | NA |
NRC | 6468 | 1247 | 839 | 1058 | 1476 | 689 | 3324 | 2312 | 1191 | 534 | 1231 |
上表使你对每个词典的大小和结构有所了解。
匹配点常见
为了确定哪种词典更适用于歌词, 你需要查看词典和歌词中通用的单词的匹配率。提醒一下, prince_tidy中共有76116个单词和7851个不同的单词。
那么词典中实际上有多少个单词呢?
在prince_tidy和new_sentiments之间使用inner_join(), 然后按词典进行分组。 NRC词典有10个不同的类别, 一个单词可能出现在多个类别中:也就是说, 单词可能是负面的也可能是悲伤的。这意味着你将要在summarise()中使用n_distinct()来获得每个词典的不同字数。
prince_tidy %>%
mutate(words_in_lyrics = n_distinct(word)) %>%
inner_join(new_sentiments) %>%
group_by(lexicon, words_in_lyrics, words_in_lexicon) %>%
summarise(lex_match_words = n_distinct(word)) %>%
ungroup() %>%
mutate(total_match_words = sum(lex_match_words), #Not used but good to have
match_ratio = lex_match_words / words_in_lyrics) %>%
select(lexicon, lex_match_words, words_in_lyrics, match_ratio) %>%
mutate(lex_match_words = color_bar("lightpink")(lex_match_words), lexicon = color_tile("lightgreen", "lightgreen")(lexicon)) %>%
my_kable_styling(caption = "Lyrics Found In Lexicons")
词典 | lex_match_words | words_in_lyrics | match_ratio |
---|---|---|---|
祖父 | 770 | 7851 | 0.0980767 |
bing | 1185 | 7851 | 0.1509362 |
NRC | 1678 | 7851 | 0.2137307 |
与AFINN或Bing相比, NRC词典中歌词中包含的独特单词更多。请注意, 匹配率之和很低。词典不可能拥有所有单词, 也不应拥有所有单词。许多单词被认为是中立的, 没有相关的情感。例如, 2000通常是一个中性词, 因此在词典中不存在。但是, 如果你还记得的话, 那年人们会预测飞机会从天上掉下来, 而计算机只会停止工作。因此, 歌曲中存在相关联的恐惧, 但是使用典型词典在情感分析中并未捕获到恐惧。
以下是单词可能不会出现在词典中的一些原因:
- 并非每个词都有情感。
- 这些词典是为其他类型的文本创建的, 因此不是为歌词创建的。
- 该单词的实际形式可能不会出现。例如, 可能会出现强, 但可能不会强。数据可能需要更多清洁! (这将在后面的部分中进行介绍。)
不要相信我的话
看一下Prince歌词中的一些特定单词, 这些单词似乎会对情感产生影响。它们是否在所有词典中?
new_sentiments %>%
filter(word %in% c("dark", "controversy", "gangster", "discouraged", "race")) %>%
arrange(word) %>% #sort
select(-score) %>% #remove this field
mutate(word = color_tile("lightblue", "lightblue")(word), words_in_lexicon = color_bar("lightpink")(words_in_lexicon), lexicon = color_tile("lightgreen", "lightgreen")(lexicon)) %>%
my_kable_styling(caption = "Specific Words")
字 | 情绪 | 词典 | words_in_lexicon |
---|---|---|---|
争议 | 负 | NRC | 6468 |
争议 | 负 | bing | 6785 |
暗 | 悲伤 | NRC | 6468 |
暗 | 负 | bing | 6785 |
泄气 | 负 | 祖父 | 2476 |
gang徒 | 负 | bing | 6785 |
NRC和Bing中出现争议和黑暗, 但Bing中仅出现黑帮。种族根本没有出现, 这是Prince歌词中的关键话题。但这很容易与情绪相关吗?请注意, AFINN小得多, 只有这些词之一。
字形
现在来看一个更复杂的例子。性是普林斯音乐中的常见主题。基于预定义词典的情感分析将如何受到不同形式的单词的影响?例如, 以下是歌词中对词根sex的所有引用。将它们与Bing和NRC进行比较, 看看哪里有匹配项。
my_word_list <- prince_data %>%
unnest_tokens(word, lyrics) %>%
filter(grepl("sex", word)) %>% #Use `grepl()` to find the substring `"sex"`
count(word) %>%
select(myword = word, n) %>% #Rename word
arrange(desc(n))
new_sentiments %>%
#Right join gets all words in `my_word_list` to show nulls
right_join(my_word_list, by = c("word" = "myword")) %>%
filter(word %in% my_word_list$myword) %>%
mutate(word = color_tile("lightblue", "lightblue")(word), instances = color_tile("lightpink", "lightpink")(n), lexicon = color_tile("lightgreen", "lightgreen")(lexicon)) %>%
select(-score, -n) %>% #Remove these fields
my_kable_styling(caption = "Dependency on Word Form")
字 | 情绪 | 词典 | words_in_lexicon | 实例 |
---|---|---|---|---|
性感的 | 正 | bing | 6785 | 220 |
性感的 | 正 | 祖父 | 2476 | 220 |
性别 | 预期 | NRC | 6468 | 185 |
性别 | 喜悦 | NRC | 6468 | 185 |
性别 | 正 | NRC | 6468 | 185 |
性别 | 信任 | NRC | 6468 | 185 |
超级笨蛋 | NA | NA | NA | 19 |
性爱 | NA | NA | NA | 16 |
性的 | NA | NA | NA | 11 |
性欲 | NA | NA | NA | 11 |
性感 | NA | NA | NA | 2 |
性爱 | NA | NA | NA | 2 |
性爱 | NA | NA | NA | 1 |
有性 | NA | NA | NA | 1 |
更性感 | NA | NA | NA | 1 |
超级朋克 | NA | NA | NA | 1 |
超级时髦 | NA | NA | NA | 1 |
请注意, Prince经常使用性感, 但在NRC中并不以这种形式存在。 “性”一词在NRC中找到, 但在Bing中却找不到。如果你查看字词的词根或词根怎么办?真是个难题!你的文本可以包含根词的过去式, 复数或副词, 但它可能不存在于任何词典中。你如何处理?
需要更多数据准备?
在某些情况下, 你可能需要更多的数据准备步骤。在执行情感分析之前, 需要考虑以下三种技术:
- 词干:通常是指从单词中删除后缀以获得共同的来源
- 词法化:将变形的(或有时衍生的)单词减少为其词干, 基数或词根形式
- 单词替换:用更常用的同义词替换单词
情感分析中的高级概念是同义词(在语义上类似的同伴)和上位词(共同的父母)的替代。这些词比歌词中的相关词更常用, 并且实际上确实出现在词典中, 因此匹配率更高。本教程中没有足够的空间来解决其他数据准备工作, 但这绝对是要考虑的事情!
挑战:对词典及其创建方式进行一些研究。已经存在一种更适合音乐歌词的音乐了吗?如果你真的很感兴趣, 也许考虑一下构建自己的词典所需要的内容。基于分类器的情感分析和基于词典的情感分析有什么区别?
详细分析
现在你已经对数据集和词典有了基本的了解, 可以通过将它们结合在一起进行分析来应用这些知识。以下是你将执行的高级步骤:
- 创建特定于词典的数据集
- 看所有歌曲的极地情感
- 检查情绪随时间的变化
- 根据王子生活中的特定事件验证你的结果
- 学习歌声水平
- 复习单词对如何影响情绪
创建情感数据集
首先, 通过对get_sentiments()函数执行inner_join()为每个词典创建Prince情感数据集。传递每个调用的词典名称。在本练习中, 将Bing用于二进制文件, 将NRC用于分类情绪。由于单词可以在NRC中出现在多个类别中, 例如”负面/恐惧”或”正面/欢乐”, 因此你还将创建一个不包含正面和负面类别的子集, 以便以后使用。
prince_bing <- prince_tidy %>%
inner_join(get_sentiments("bing"))
prince_nrc <- prince_tidy %>%
inner_join(get_sentiments("nrc"))
prince_nrc_sub <- prince_tidy %>%
inner_join(get_sentiments("nrc")) %>%
filter(!sentiment %in% c("positive", "negative"))
心情:整体情绪
在详细分析歌词时, 你需要检查文本的不同级别, 例如所有歌曲, 图表级别, 十年级别和单词级别。首先绘制整个数据集的NRC情绪分析图。
(只是为了好玩, 我使用了memery和magick软件包将图形(模因)添加到图形中。)
nrc_plot <- prince_nrc %>%
group_by(sentiment) %>%
summarise(word_count = n()) %>%
ungroup() %>%
mutate(sentiment = reorder(sentiment, word_count)) %>%
#Use `fill = -word_count` to make the larger bars darker
ggplot(aes(sentiment, word_count, fill = -word_count)) +
geom_col() +
guides(fill = FALSE) + #Turn off the legend
theme_lyrics() +
labs(x = NULL, y = "Word Count") +
scale_y_continuous(limits = c(0, 15000)) + #Hard code the axis limit
ggtitle("Prince NRC Sentiment") +
coord_flip()
img <- "prince_background2.jpg" #Load the background image
lab <- "" #Turn off the label
#Overlay the plot on the image and create the meme file
meme(img, lab, "meme_nrc.jpg", inset = nrc_plot)
#Read the file back in and display it!
nrc_meme <- image_read("meme_nrc.jpg")
plot(nrc_meme)
看起来, 对于Prince的歌词, NRC强烈赞成积极的态度。但是, 所有带有厌恶或愤怒情绪的单词也都属于否定类别吗?可能值得一试。
现在看看Bing的整体情绪。在Bing词典中出现的Prince歌词中1185个不同的单词中, 有多少是肯定的, 有多少是否定的?
bing_plot <- prince_bing %>%
group_by(sentiment) %>%
summarise(word_count = n()) %>%
ungroup() %>%
mutate(sentiment = reorder(sentiment, word_count)) %>%
ggplot(aes(sentiment, word_count, fill = sentiment)) +
geom_col() +
guides(fill = FALSE) +
theme_lyrics() +
labs(x = NULL, y = "Word Count") +
scale_y_continuous(limits = c(0, 8000)) +
ggtitle("Prince Bing Sentiment") +
coord_flip()
img1 <- "prince_background1.jpg"
lab1 <- ""
meme(img1, lab1, "meme_bing.jpg", inset = bing_plot)
x <- image_read("meme_bing.jpg")
plot(x)
对于Bing来说, 普林斯音乐中的积极情绪和消极情绪是否相等?当查看过大的数据集时, 整体情绪会抵消吗?很难知道, 但是尝试分批查看它, 看看会发生什么。
在声学方面, 有一种称为相位抵消的方法, 其中同一波的两个实例的频率完全异相并相互抵消, 例如, 当你录制带有两个麦克风的鼓时, 声音需要更长的时间才能消除。拿一个麦克风比另一个麦克风。这导致在该频率下完全静音!这如何适用于大型数据集的情感分析?想一想。
语言学教授:”在英语中, 双重否定构成肯定。在俄语中, 双重否定仍然构成否定。但是, 没有语言可以使双重肯定构成否定。”意见不同的学生:”是的, 对。”
加速:图表级别
使用Bing词典将其分解为图表级别, 以增加分析的音量。创建每个图表级别的极度情绪图表。对于不同的观点, 可以使用spread()将情感分为几列, 并使用mutate()创建一个极性(正-负)字段和一个percent_positive字段($ posit /总情感* 100 $)。对于极性图, 使用geom_hline()添加yintercept。与grid.arrange()并排绘制图形。
prince_polarity_chart <- prince_bing %>%
count(sentiment, chart_level) %>%
spread(sentiment, n, fill = 0) %>%
mutate(polarity = positive - negative, percent_positive = positive / (positive + negative) * 100)
#Polarity by chart
plot1 <- prince_polarity_chart %>%
ggplot( aes(chart_level, polarity, fill = chart_level)) +
geom_col() +
scale_fill_manual(values = my_colors[3:5]) +
geom_hline(yintercept = 0, color = "red") +
theme_lyrics() + theme(plot.title = element_text(size = 11)) +
xlab(NULL) + ylab(NULL) +
ggtitle("Polarity By Chart Level")
#Percent positive by chart
plot2 <- prince_polarity_chart %>%
ggplot( aes(chart_level, percent_positive, fill = chart_level)) +
geom_col() +
scale_fill_manual(values = c(my_colors[3:5])) +
geom_hline(yintercept = 0, color = "red") +
theme_lyrics() + theme(plot.title = element_text(size = 11)) +
xlab(NULL) + ylab(NULL) +
ggtitle("Percent Positive By Chart Level")
grid.arrange(plot1, plot2, ncol = 2)
这是否表示录制的歌曲通常比否定更积极?如果是这样, 这如何告诉你社会想要听到什么?你甚至可以做出这些假设吗?从正面情绪相对于整体情绪的角度来看, 歌曲似乎比负面情绪稍微积极一些。鉴于Bing词典本身具有更多的否定词而不是肯定的词, 因此这很有趣。
极地融化:如此蓝
由于你是从极端的角度看待情绪, 因此你可能希望查看天气是否随时间变化(极客幽默)。这次将geom_smooth()与黄土方法结合使用, 以获得更平滑的曲线, 将另一个geom_smooth()与方法= lm结合使用, 获得线性平滑的曲线。
prince_polarity_year <- prince_bing %>%
count(sentiment, year) %>%
spread(sentiment, n, fill = 0) %>%
mutate(polarity = positive - negative, percent_positive = positive / (positive + negative) * 100)
polarity_over_time <- prince_polarity_year %>%
ggplot(aes(year, polarity, color = ifelse(polarity >= 0, my_colors[5], my_colors[4]))) +
geom_col() +
geom_smooth(method = "loess", se = FALSE) +
geom_smooth(method = "lm", se = FALSE, aes(color = my_colors[1])) +
theme_lyrics() + theme(plot.title = element_text(size = 11)) +
xlab(NULL) + ylab(NULL) +
ggtitle("Polarity Over Time")
relative_polarity_over_time <- prince_polarity_year %>%
ggplot(aes(year, percent_positive , color = ifelse(polarity >= 0, my_colors[5], my_colors[4]))) +
geom_col() +
geom_smooth(method = "loess", se = FALSE) +
geom_smooth(method = "lm", se = FALSE, aes(color = my_colors[1])) +
theme_lyrics() + theme(plot.title = element_text(size = 11)) +
xlab(NULL) + ylab(NULL) +
ggtitle("Percent Positive Over Time")
grid.arrange(polarity_over_time, relative_polarity_over_time, ncol = 2)
上面的第二张图中调整了一些极端情况, 但两种情况下总体极性随时间的变化趋势均为负。
情绪环
你将再次使用chordDiagram()的功能来检查NRC情绪和数十年之间的关系。请注意, 情绪类别出现在圆环的顶部, 而数十年出现在底部。
grid.col = c("1970s" = my_colors[1], "1980s" = my_colors[2], "1990s" = my_colors[3], "2000s" = my_colors[4], "2010s" = my_colors[5], "anger" = "grey", "anticipation" = "grey", "disgust" = "grey", "fear" = "grey", "joy" = "grey", "sadness" = "grey", "surprise" = "grey", "trust" = "grey")
decade_mood <- prince_nrc %>%
filter(decade != "NA" & !sentiment %in% c("positive", "negative")) %>%
count(sentiment, decade) %>%
group_by(decade, sentiment) %>%
summarise(sentiment_sum = sum(n)) %>%
ungroup()
circos.clear()
#Set the gap size
circos.par(gap.after = c(rep(5, length(unique(decade_mood[[1]])) - 1), 15, rep(5, length(unique(decade_mood[[2]])) - 1), 15))
chordDiagram(decade_mood, grid.col = grid.col, transparency = .2)
title("Relationship Between Mood and Decade")
这显示了每十年NRC类别的字数。一张小图需要花费很多, 但是它提供了大量有关类别和时间之间关系的信息。这些图是可定制的, 可以根据需要简单或丰富。
实时情绪
考虑到你在srcmini上所学的所有精彩课程, 你可能会想将这些技能应用于现实世界。现在, 通过将你对Prince的歌词的分析映射到一段时间后的真实内容, 现在可以这样做。但是, 在对如此复杂的主题进行简化分析时, 请保持怀疑和谨慎的态度。
我创建了Prince的生活事件列表, 这些列表是从Rolling Stone Magazine, Biography.com等流行资源收集的。我选择了一些公开年份, 这些年份与我们数据集中发布日期的歌曲相匹配。立即从princeEvents.csv中读取这些事件。然后使用prince_bing和spread()每年创建一个极性得分。加入事件数据框并创建一个情感字段, 以便你可以在条形图上填写颜色。与往常一样, 当显示大文本标签时, 请使用coord_flip()。
events <- read.csv('princeEvents.csv', stringsAsFactors = FALSE)
year_polarity_bing <- prince_bing %>%
group_by(year, sentiment) %>%
count(year, sentiment) %>%
spread(sentiment, n) %>%
mutate(polarity = positive - negative, ratio = polarity / (positive + negative)) #use polarity ratio in next graph
events %>%
#Left join gets event years with no releases
left_join(year_polarity_bing) %>%
filter(event != " ") %>% #Account for bad data
mutate(event = reorder(event, year), #Sort chart by desc year
sentiment = ifelse(positive > negative, "positive", "negative")) %>%
ggplot(aes(event, polarity, fill = sentiment)) +
geom_bar(stat = "identity") +
theme_minimal() + theme(legend.position = "none") +
xlab(NULL) +
ggtitle("Sentiment by Events") +
coord_flip()
提示:你可以在此处找到princeEvents.csv。
我认为将事件与情感进行比较很有趣。尽管很主观, 但我发现它们之间存在很大的关联。现在被授予了, 我自己创建了事件数据集。我本来可以很容易地将其与歌词匹配。但是, 我每年确实使用多个来源来确定最常报告的事实。这些结果将激励你更深入地进行词级分析, 以了解Prince在说什么。
黑色专辑:1994年-1996年
1994年, 普林斯发行了《黑色专辑》, 以重获非裔美国听众。考虑到1994年及其后两年在分析中脱颖而出的事实, 你可以使用ggrepel包中的geom_label_repel()和geom_point()来查找与此期间与NRC词典匹配的关键词。这是一个棘手的图形, 因此我建议你使用以下代码的配置。
plot_words_94_96 <- prince_nrc %>%
filter(year %in% c("1994", "1995", "1996")) %>%
group_by(sentiment) %>%
count(word, sort = TRUE) %>%
arrange(desc(n)) %>%
slice(seq_len(8)) %>% #consider top_n() from dplyr also
ungroup()
plot_words_94_96 %>%
#Set `y = 1` to just plot one variable and use word as the label
ggplot(aes(word, 1, label = word, fill = sentiment )) +
#You want the words, not the points
geom_point(color = "transparent") +
#Make sure the labels don't overlap
geom_label_repel(force = 1, nudge_y = .5, direction = "y", box.padding = 0.04, segment.color = "transparent", size = 3) +
facet_grid(~sentiment) +
theme_lyrics() +
theme(axis.text.y = element_blank(), axis.text.x = element_blank(), axis.title.x = element_text(size = 6), panel.grid = element_blank(), panel.background = element_blank(), panel.border = element_rect("lightgray", fill = NA), strip.text.x = element_text(size = 9)) +
xlab(NULL) + ylab(NULL) +
ggtitle("1994 - 1996 NRC Sentiment") +
coord_flip()
1994年, 普林斯引用《滚石杂志》,
“当你阻止一个男人做梦时, 他会成为一个奴隶。那是我去过的地方。”
因此, 他在公开场合露面, 脸上显着地写下了”奴隶”一词, 以他当前的音乐标签来宣告他的反企业情绪。他还把他的名字改成了一个象征。以上单词与这些事件的匹配程度如何?注意图表中的”奴隶”, “解放”, “黑色”和”变化”吗?
(请记住, 这不仅与王子有关。你可以将其应用于任何文本!例如, 特朗普总统就职第一年所讲的最信任的情感词是什么?)
这次是个人:1998
上面的”事件感悟”图表明, 1996年至1998年之间, 王子结婚, 失去了两个孩子, 据说在舞台上预测为9/11。 (通过YouTube搜索他在1998年在荷兰举行的音乐会, 以听自己说话!)使用与上一张图表相同的步骤, 查看1998年每个NRC类别中排名前10位的单词。
plot_words_1998 <- prince_nrc %>%
filter(year == "1998") %>%
group_by(sentiment) %>%
count(word, sort = TRUE) %>%
arrange(desc(n)) %>%
slice(seq_len(10)) %>%
ungroup()
#Same comments as previous graph
plot_words_1998 %>%
ggplot(aes(word, 1, label = word, fill = sentiment )) +
geom_point(color = "transparent") +
geom_label_repel(force = 1, nudge_y = .5, direction = "y", box.padding = 0.05, segment.color = "transparent", size = 3) +
facet_grid(~sentiment) +
theme_lyrics() +
theme(axis.text.y = element_blank(), axis.text.x = element_blank(), axis.title.x = element_text(size = 6), panel.grid = element_blank(), panel.background = element_blank(), panel.border = element_rect("lightgray", fill = NA), strip.text.x = element_text(size = 9)) +
xlab(NULL) + ylab(NULL) +
ggtitle("1998 NRC Sentiment") +
coord_flip()
考虑到个人损失和恐怖主义迫在眉睫的威胁, 该图中未显示的”自杀”, “浪费”, “疯狂”, “受伤”等字眼都非常说明实际事件。这些话很有力, 可以真正洞悉那个时期的情绪。
在雷达上:雷达图
比较不同类别情绪的另一种很好的方法是使用雷达图, 也称为蜘蛛图。你可以使用radarchart软件包制作此类图表。这些对于查看哪些变量具有相似的值或每个变量是否存在异常值很有用。
你将把此分析分为三个不同的级别:年, 图表和十年。 (为了节省空间, 我只包含特定年份的代码。)
- 使用不包含正面情绪和负面情绪的prince_nrc_sub数据集, 以便其他可见。这次, 你将首先按每年的情感来计算单词总数, 以及全年的总情感量, 并获得一个百分比(每年的情感单词数/每年总计* 100 $)。
- 过滤1978、1994、1995年的特定年份, 并使用select()删除不需要的字段。
- 最后, 你需要将(年份)和百分比值(键/值对)spread()到多列中, 以便每种情绪有一行, 而每年有一行。然后使用chartJSRadar()生成一个交互式HTML小部件。你可以传递参数以在鼠标悬停时显示数据集标签。 (仅供参考, 有时J和Y在Radarchart的” joy”一词中被裁掉, 看起来像” iov”。)
#Get the count of words per sentiment per year
year_sentiment_nrc <- prince_nrc_sub %>%
group_by(year, sentiment) %>%
count(year, sentiment) %>%
select(year, sentiment, sentiment_year_count = n)
#Get the total count of sentiment words per year (not distinct)
total_sentiment_year <- prince_nrc_sub %>%
count(year) %>%
select(year, year_total = n)
#Join the two and create a percent field
year_radar_chart <- year_sentiment_nrc %>%
inner_join(total_sentiment_year, by = "year") %>%
mutate(percent = sentiment_year_count / year_total * 100 ) %>%
filter(year %in% c("1978", "1994", "1995")) %>%
select(-sentiment_year_count, -year_total) %>%
spread(year, percent) %>%
chartJSRadar(showToolTipLabel = TRUE, main = "NRC Years Radar")
你必须上下滚动才能比较这些图, 因为我想让它们保持全尺寸。有趣的是, 使用像Year这样的较小数据集, 你可以使用这种类型的可视化更加清晰地看到每个情绪的变化。你可能还会注意到, 与以前的练习相反, 使用百分比, “欢乐”在所有图表中得分最高(尤其是在1978年Prince事业开始之初)。
时报
Spotify使用机器学习为其1.4亿活跃用户创建歌曲推荐。它尚未使用歌词内容。试想一下是否可以……所以现在, 让我们更深入地研究特定歌曲的心情。
1987年, 普林斯(Prince)写了一首歌, 名为” Sign O’the Times”。肯尼斯·帕特里奇(Kenneth Partridge)的广告牌文章指出:
“当他处理艾滋病, 帮派, 毒品, 自然灾害甚至挑战者爆炸等话题时, 普林斯表现得很冷静和超然。’泰晤士报’是状态的更新, 而不是行动的呼吁。他的解决方案是不要在街上游行或打电话给国会议员。”
那么, 机器将如何诠释这首歌的心情?它是高度情绪化的还是仅仅是提供信息?它代表王子作为一个人还是整个社会?用ggplot2绘制NRC类别的图形。
prince_nrc %>%
filter(song %in% "sign o the times") %>%
group_by(sentiment) %>%
summarise(word_count = n()) %>%
ungroup() %>%
mutate(sentiment = reorder(sentiment, word_count)) %>%
ggplot(aes(sentiment, word_count, fill = -word_count)) +
geom_col() +
guides(fill = FALSE) +
theme_minimal() + theme_lyrics() +
labs(x = NULL, y = "Word Count") +
ggtitle("Sign O' The Times NRC Sentiment") +
coord_flip()
尽管再次主观, 但你的结果似乎证实了Prince的”演奏酷而超脱”, 正如Partridge所说。这可以通过以下观察来解释, 即预期单词比诸如悲伤, 恐惧和愤怒之类的情感类别更为普遍。这是一篇文章, 指出这三类是情感, 而期望是一种情感。广告牌作者还说这首歌只是”状态更新, 几乎没有情感”。机器似乎同意了。你也这样解释吗?我知道这是R教程, 但情绪分析并非纯粹是技术性的。据说这是AI与心理学相遇的地方。
使用ggplot2创建略有不同的图表, 查看每个类别的词。
prince_tidy %>%
filter(song %in% 'sign o the times') %>%
distinct(word) %>%
inner_join(get_sentiments("nrc")) %>%
ggplot(aes(x = word, fill = sentiment)) +
facet_grid(~sentiment) +
geom_bar() + #Create a bar for each word per sentiment
theme_lyrics() +
theme(panel.grid.major.x = element_blank(), axis.text.x = element_blank()) + #Place the words on the y-axis
xlab(NULL) + ylab(NULL) +
ggtitle("Sign O' The Times Sentiment Words") +
coord_flip()
你可以将这些单词与”广告牌”文章中提到的主题进行匹配吗?很容易看到这种联系:挑战者灾难->”火箭”, 艾滋病->”疾病”, 毒品->”裂纹”, 自然灾害->”飓风”, 帮派->”帮派”, 等等。
这几乎就像Partridge在写文章之前写了一些R代码并创建了这张图表!
更多歌曲
查看其他几首具有独特标题的歌曲的情感类别, 并查看它们是否相关。
prince_nrc_sub %>%
filter(song %in% c("so blue", "controversy", "raspberry beret", "when doves cry", "the future", "1999")) %>%
count(song, sentiment, year) %>%
mutate(sentiment = reorder(sentiment, n), song = reorder(song, n)) %>%
ggplot(aes(sentiment, n, fill = sentiment)) +
geom_col() +
facet_wrap(year ~ song, scales = "free_x", labeller = label_both) +
theme_lyrics() +
theme(panel.grid.major.x = element_blank(), axis.text.x = element_blank()) +
labs(x = NULL, y = NULL) +
ggtitle("NRC Sentiment Song Analysis") +
coord_flip()
NRC的情绪显示出对未来之歌的高度期待和恐惧, 同一件事加上对争议之歌的高度信任。悲伤的歌曲似乎也与它们的标题匹配。
每十年二元组
到目前为止, 你只关注单字或单个单词。但是, 如果”爱”是一个常用词, 它的前面是什么?还是跟随它?脱离上下文查看单个单词可能会产生误导。因此, 现在该看一些双字母组或单词对了。
方便地, tidytext包提供了取消单词对和单个单词嵌套的功能。在这种情况下, 你将调用unnest_tokens()传递令牌参数ngrams。由于你只是看二元组(两个连续的单词), 因此传递n =2。使用prince_bigrams存储结果。
tidyr程序包提供了使用split()函数将双字母组合成单个单词的功能。为了删除停用词和不需要的词, 你需要将二元组分开并过滤掉不需要的词, 然后使用unite()将单词对放回原处。这样可以很容易地可视化每十年最常见的二元组。 (请参阅第一部分, 以了解slice()和row_number()的信息)
prince_bigrams <- prince_data %>%
unnest_tokens(bigram, lyrics, token = "ngrams", n = 2)
bigrams_separated <- prince_bigrams %>%
separate(bigram, c("word1", "word2"), sep = " ")
bigrams_filtered <- bigrams_separated %>%
filter(!word1 %in% stop_words$word) %>%
filter(!word2 %in% stop_words$word) %>%
filter(!word1 %in% undesirable_words) %>%
filter(!word2 %in% undesirable_words)
#Because there is so much repetition in music, also filter out the cases where the two words are the same
bigram_decade <- bigrams_filtered %>%
filter(word1 != word2) %>%
filter(decade != "NA") %>%
unite(bigram, word1, word2, sep = " ") %>%
inner_join(prince_data) %>%
count(bigram, decade, sort = TRUE) %>%
group_by(decade) %>%
slice(seq_len(7)) %>%
ungroup() %>%
arrange(decade, n) %>%
mutate(row = row_number())
## Joining, by = c("song", "year", "album", "peak", "us_pop", "us_rnb", "decade", "chart_level", "charted")
bigram_decade %>%
ggplot(aes(row, n, fill = decade)) +
geom_col(show.legend = FALSE) +
facet_wrap(~decade, scales = "free_y") +
xlab(NULL) + ylab(NULL) +
scale_x_continuous( # This handles replacement of row
breaks = bigram_decade$row, # Notice need to reuse data frame
labels = bigram_decade$bigram) +
theme_lyrics() +
theme(panel.grid.major.x = element_blank()) +
ggtitle("Bigrams Per Decade") +
coord_flip()
使用二元词, 你几乎可以看到常见的短语从性, 舞蹈和浪漫转向宗教和(彩虹)孩子。如果你不知道, 有时父母会使用”彩虹宝贝”一词, 他们在失去孩子流产后会期待另一个孩子。有趣的是, 我找不到真正记录下来的Prince’s Rainbow Children专辑的评论。
情绪与二元论
那么二元论如何影响情绪?这次使用AFINN词典对单词对执行情感分析, 查看与情感相关的单词前面有” not”或其他否定单词的频率。
AFINN <- get_sentiments("afinn")
not_words <- bigrams_separated %>%
filter(word1 == "not") %>%
inner_join(AFINN, by = c(word2 = "word")) %>%
count(word2, score, sort = TRUE) %>%
ungroup()
not_words %>%
mutate(contribution = n * score) %>%
arrange(desc(abs(contribution))) %>%
head(20) %>%
mutate(word2 = reorder(word2, contribution)) %>%
ggplot(aes(word2, n * score, fill = n * score > 0)) +
geom_col(show.legend = FALSE) +
theme_lyrics() +
xlab("Words preceded by \"not\"") +
ylab("Sentiment score * Number of Occurrences") +
ggtitle("Polar Sentiment of Words Preceded by Not") +
coord_flip()
在图表的第一行, 会给人一种错误的肯定情绪, 因为单字分析会忽略” not”。误报的二元组是否抵消了误报的二元组?
我无法回答另一个问题, 但这是进一步探索的一个好话题。提示:你还可以将参数” trigrams”和” n = 3″传递给unnest_tokens()来查看更多连续的单词!
还需要考虑其他否定词。这次, 你将使用ggraph和igraph包创建网络图。你将把单词排列在连接的节点上, 并把否定单词放在中心。使用graph_from_data_frame()从整洁的数据集中创建第一个对象, 然后使用ggraph()对其进行绘制。你可以通过调用geom_edge_density()突出显示主要节点。你可以在Julia Silge和David Robinson的《 Tidy Text Mining》一书中获得类似示例的更多详细信息。
negation_words <- c("not", "no", "never", "without")
negation_bigrams <- bigrams_separated %>%
filter(word1 %in% negation_words) %>%
inner_join(AFINN, by = c(word2 = "word")) %>%
count(word1, word2, score, sort = TRUE) %>%
mutate(contribution = n * score) %>%
arrange(desc(abs(contribution))) %>%
group_by(word1) %>%
slice(seq_len(20)) %>%
arrange(word1, desc(contribution)) %>%
ungroup()
bigram_graph <- negation_bigrams %>%
graph_from_data_frame() #From `igraph`
set.seed(123)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))
ggraph(bigram_graph, layout = "fr") +
geom_edge_link(alpha = .25) +
geom_edge_density(aes(fill = score)) +
geom_node_point(color = "purple1", size = 1) + #Purple for Prince!
geom_node_text(aes(label = name), repel = TRUE) +
theme_void() + theme(legend.position = "none", plot.title = element_text(hjust = 0.5)) +
ggtitle("Negation Bigram Network")
在这里, 你可以看到与否定词关联的词对。因此, 如果你的分析是基于单字组的, 而”单独”返回为否定, 则如上所示, 二元组”并非单独”将产生相反的效果。有些单词会跨越多个节点, 在这样的视觉效果中很容易看到:例如, “从不伤害”和”不伤害”。
成对比较
既然你已经研究了n-gram, 请看一下单词之间的相关性。哪些词相关性最高?使用widyr包中的pairwise_count()函数来识别共现计数。也就是说, 你要计算每对单词在歌曲中一起出现的次数。 widyr软件包采用了整齐的数据集, 并在将其返回整齐的结构以进行可视化和进一步分析之前暂时扩大了数据集。
为了简单起见, 我在Prince的歌词中选择了四个有趣的词。
pwc <- prince_tidy %>%
filter(n() >= 20) %>% #High counts
pairwise_count(word, song, sort = TRUE) %>%
filter(item1 %in% c("love", "peace", "gangster", "hate")) %>%
group_by(item1) %>%
slice(seq_len(7)) %>%
ungroup() %>%
mutate(row = -row_number()) #Descending order
pwc %>%
ggplot(aes(row, n, fill = item1)) +
geom_bar(stat = "identity", show.legend = FALSE) +
facet_wrap(~item1, scales = "free") +
scale_x_continuous( #This handles replacement of row
breaks = pwc$row, #Notice need to reuse data frame
labels = pwc$item2) +
theme_lyrics() + theme(panel.grid.major.x = element_blank()) +
xlab(NULL) + ylab(NULL) +
ggtitle("Pairwise Counts") +
coord_flip()
比较成对相关。这是指单词出现的频率相对于单词分别出现的频率。使用pairwise_cor()可以根据单词在同一首歌曲中出现的频率来确定单词之间的相关性。
prince_tidy %>%
group_by(word) %>%
filter(n() >= 20) %>%
pairwise_cor(word, song, sort = TRUE) %>%
filter(item1 %in% c("love", "peace", "gangster", "hate")) %>%
group_by(item1) %>%
top_n(7) %>%
ungroup() %>%
mutate(item2 = reorder(item2, correlation)) %>%
ggplot(aes(item2, correlation, fill = item1)) +
geom_bar(stat = 'identity', show.legend = FALSE) +
facet_wrap(~item1, scales = 'free') +
theme_lyrics() + theme(panel.grid.major.x = element_blank()) +
xlab(NULL) + ylab(NULL) +
ggtitle("Pairwise Correlation") +
coord_flip()
我认为, 仅基于这四个词, 这些便是对歌词的有趣见解!查看这些结果, 你可以开始看到一些主题。这为下一个主题建模教程提供了很好的选择!
总结
在本教程中, 你创建了一个整洁的数据集, 并分析了每个发行年份的词汇多样性和歌曲计数等基本信息, 并检查了发行十年与歌曲是否达到排行榜之间的关系。然后, 你探索了一些情感词典以及它们与歌词的匹配程度。接下来, 你对数据集中的所有歌曲, 随时间变化的情绪, 歌曲级别的情绪以及双字母组的影响执行了情绪分析。你使用了各种有趣的图形来完成此操作, 每个图形都有不同的视角。
那么可以编写一个程序来确定歌词中的情绪吗?为什么是呢!你的结果有多可靠?它取决于广泛的标准, 例如数据准备的数量, 词典的选择, 分析方法, 源数据的质量等等。比较现实生活中的事件, 无论是个人事件还是社会事件, 都可以阐明任何抒情诗的情绪。随着时间的流逝, 王子的极地情绪似乎略有下降, 但总的来说, 喜悦似乎确实脱颖而出。录制的歌曲似乎比未录制的歌曲更积极。预测9/11和他自己的死亡的说法似乎与他的话格格不入。
但是歌词很复杂, 太多的假设会引起问题。如果你将所学的歌词知识带入下一个教程”主题建模的第二部分-B”中, 那么你将可以很好地挖掘主题和主题。没有标点或句子结构出现的歌词可以执行多少NLP?王子到底在唱歌什么?现在, 你已经了解了情绪, 你已经准备好调查可能的主题。最后, 你可以应用机器学习技术来预测歌曲的十年或图表水平吗?和我一起学习接下来的几本教程, 以查找答案!
希望你到目前为止喜欢这个旅程。在第二部分B中, 我们将像1999年一样参加聚会!