【R语言文本挖掘】:tidy数据格式及词频计算
- 🌸个人主页:JoJo的数据分析历险记
- 📝个人介绍:小编大四统计在读,目前保研到统计学top3高校继续攻读统计研究生
- 💌如果文章对你有帮助,欢迎✌
关注
、👍点赞
、✌收藏
、👍订阅
专栏- ✨本文收录于【R语言文本挖掘】本系列主要介绍R语言在文本挖掘领域的应用包括:情感分析、TF-IDF、主题模型等。本系列会坚持完成下去,请大家多多关注点赞支持,一起学习~,尽量坚持每周持续更新,欢迎大家订阅交流学习!
引言
使用tidydata原则是一种使处理数据更容易、更有效的强大方法,在处理文本时也是如此。tidydata具有特定的结果
- 每个变量都是一列
- 每个观察都是一行
- 每种类型的观察单元都是一个表格
我们将整洁的文本格式定义为每行一个标记的表格。以这种方式构建文本数据意味着它符合tidy data原则,并且可以使用一系列的r语言内置函数处理,这使得文本挖掘更方便。
- 字符串:文本当然可以在 r 中存储为字符串,即字符向量,并且通常文本数据首先以这种形式读入内存。
- 语料库:这些类型的对象通常包含带有附加元数据和详细信息的原始字符串。
- 文档-单词矩阵:这是一个稀疏矩阵,描述文档的集合(即语料库),每个文档一行,每个词一列。
1.unnest_tokens分词函数
unnest_tokens函数是用于分词处理的的函数。我们下面先通过一个简短的例子来了解
text <- c("JoJo loves YoYo",
"小明喜欢打篮球")
text
- 'JoJo loves YoYo'
- '小明喜欢打篮球'
这是我们可能想要分析的典型特征向量。为了把它变成一个整洁的文本数据集,我们首先需要把它放到一个数据框中。
library(dplyr)
text_df <- tibble(line = 1:2, text = text)
text_df
line | text |
---|---|
<int> | <chr> |
1 | JoJo loves YoYo |
2 | 小明喜欢打篮球 |
tibble 是 r 中的一个数据框类,在 dplyr 和 tibble 包中可用,具有方便的打印方法,不会将字符串转换为因子,并且不使用行名。 Tibbles 非常适合与tidydata一起使用。
注意,这个包含文本的数据框还不能直接与tidydata文本分析兼容。我们无法过滤掉最频繁出现的单词或计数,因为每一行都是由多个组合单词组成的。我们需要将其转换为每个文档每行一个单词 (one-row-one-token)。
token是一个有意义的文本单元,通常是一个词,我们有兴趣将其用于进一步分析,而标记化是将文本拆分为标记的过程。
在第一个示例中,我们只有一个文档,在tidydata数据集框架中,我们需要将文本分解为单独的单词(称为tokenization的过程)并将其转换为整洁的数据结构。为此,我们使用 tidytext 的 unnest_tokens() 函数。
library(tidytext)
text_df %>%
unnest_tokens(word, text)
line | word |
---|---|
<int> | <chr> |
1 | jojo |
1 | loves |
1 | yoyo |
2 | 小明 |
2 | 喜欢 |
2 | 打 |
2 | 篮球 |
这里使用的 unnest_tokens 的两个基本参数是列名。首先,我们有将在文本未嵌套到其中时创建的输出列名称(在本例中为 word),然后是文本来自的输入列(在本例中为 text)。请记住,上面的 text_df 有一个名为 text 的列,其中包含感兴趣的数据。
使用 unnest_tokens 后,我们对每一行进行了拆分,以便在新数据帧的每一行中都有一个标记(单词); unnest_tokens() 中的默认标记化是针对单个单词的,如此处所示。另请注意:
- 保留其他列,例如每个单词来自的行号。
- 标点符号已被删除。
- 默认情况下,unnest_tokens() 将标记转换为小写,这使得它们更容易与其他数据集进行比较或组合。 (使用 to_lower = FALSE 参数关闭此行为)。
拥有这种格式的文本数据,我们可以使用标准的 tidy 工具集(即 dplyr、tidyr 和 ggplot2)来操作、处理和可视化文本,如图 所示。
2. 简奥斯汀数据集
为了方便说明,这里我们直接使用Jane Austen 的 6 部已完成、已出版的小说从janeaustenr包中的文本,并将它们转换为整洁的格式。janeaustenr包以每行一行的格式提供这些文本,在这种情况下,一行类似于实体书中的文字印刷行。让我们从这个开始,并且还使用mutate()来注释行号数量以跟踪原始格式的行,并使用章节 (使用正则表达式) 来查找所有章节的位置。
library(janeaustenr)
library(dplyr)
library(stringr)
original_books <- austen_books() %>%#加载数据集
group_by(book) %>%
mutate(linenumber = row_number(),#计算行数
chapter = cumsum(str_detect(text,
regex("^chapter [\\divxlc]",
ignore_case = TrUE)))) %>%#使用正则来提取章节信息
ungroup()
original_books %>% head(10)#查看数据集
text | book | linenumber | chapter |
---|---|---|---|
<chr> | <fct> | <int> | <int> |
SENSE AND SENSIBILITY | Sense & Sensibility | 1 | 0 |
Sense & Sensibility | 2 | 0 | |
by Jane Austen | Sense & Sensibility | 3 | 0 |
Sense & Sensibility | 4 | 0 | |
(1811) | Sense & Sensibility | 5 | 0 |
Sense & Sensibility | 6 | 0 | |
Sense & Sensibility | 7 | 0 | |
Sense & Sensibility | 8 | 0 | |
Sense & Sensibility | 9 | 0 | |
CHAPTEr 1 | Sense & Sensibility | 10 | 1 |
为了将这个文本转换成tidy数据集,我们需要将这个数据结构转换成一行一个token的形式,因此需要使用unnest_tokens()函数
library(tidytext)
tidy_books <- original_books %>%
unnest_tokens(word, text)#将text进行分词处理
#查看数据集
tidy_books %>% head(10)
book | linenumber | chapter | word |
---|---|---|---|
<fct> | <int> | <int> | <chr> |
Sense & Sensibility | 1 | 0 | sense |
Sense & Sensibility | 1 | 0 | and |
Sense & Sensibility | 1 | 0 | sensibility |
Sense & Sensibility | 3 | 0 | by |
Sense & Sensibility | 3 | 0 | jane |
Sense & Sensibility | 3 | 0 | austen |
Sense & Sensibility | 5 | 0 | 1811 |
Sense & Sensibility | 10 | 1 | chapter |
Sense & Sensibility | 10 | 1 | 1 |
Sense & Sensibility | 13 | 1 | the |
此函数使用 tokenizers 包将原始数据帧中的每一行文本分隔为单词。默认token是针对单词的,但其他选项包括string、n-gram、句子、行、段落或围绕正则表达式模式的分隔。 现在数据是每行一个单词的格式,我们可以使用 dplyr 等整洁的工具对其进行操作。
通常在文本分析中,我们会想要移除停用词;停用词是对分析没有太大的词,通常是英语中非常常见的词,例如 “the”、“of”、“to” 等这些介词。我们可以使用 anti_join() 删除停用词,包含在tidytext的stop_words数据中
我们现在先来看一下停用词
stop_words %>% head()
word | lexicon |
---|---|
<chr> | <chr> |
a | SMArT |
a's | SMArT |
able | SMArT |
about | SMArT |
above | SMArT |
according | SMArT |
然后我们使用anti_join,反连接来将文本中的停用词删除
tidy_books <- tidy_books %>%
anti_join(stop_words)#删除常见的停用词
tidytext包中的stop_words数据集包含来自三个词典的停用词。我们可以一起使用它们,就像我们在上面使用的那样,或者filter()只使用一组停用词。这取决于某个具体的分析情况。
我们还可以使用dplyr的**count()**来查找所有书籍中最常见的单词。
tidy_books %>%
count(word, sort = TrUE) %>%#按照单词出现的次数排序
head()
word | n |
---|---|
<chr> | <int> |
miss | 1855 |
time | 1337 |
fanny | 862 |
dear | 822 |
lady | 817 |
sir | 806 |
我们使用ggplot2来进行可视化分析,进一步分析各个单词的情况
library(ggplot2)
tidy_books %>%
count(word, sort = TrUE) %>%
filter(n > 600) %>%#这里我们筛选出现次数大于600的词
mutate(word = reorder(word, n)) %>%#这里需要重新再进行排序,因为过滤之后重新排序了。
ggplot(aes(n, word)) +
geom_col() +
labs(y = NULL)
请注意,
austen_books()
函数以我们想要分析的文本开始,但在其他情况下,我们可能需要执行文本数据的清理,例如删除版权标题或格式。
现在我们已经使用
janeaustenr
包来探索文本,让我们介绍一下 gutenbergr 包(robinson 2016)。 Gutenbergr 包提供对 Project Gutenberg 收藏中公共领域作品的访问。该软件包包括用于下载书籍的工具(去除无用的页眉/页脚信息),以及可用于查找感兴趣的作品的古腾堡项目元数据的完整数据集。在本书中,我们将主要使用函数 gutenberg_download(),通过 ID 从 Project Gutenberg 下载一个或多个作品,但您也可以使用其他函数来探索数据,将 Gutenberg ID 与标题、作者、语言等配对,或者收集有关作者的信息。
3.词频计算及相关性分析
文本挖掘中的一个常见任务是查看词频,就像我们在上面对简·奥斯汀的小说所做的那样,并比较不同文本中的词频。我们可以使用
tidy data
原则直观而流畅地做到这一点。我们已经有了简奥斯汀的作品;让我们再获取两组文本进行比较。首先,让我们看一下19 世纪末和 20 世纪初 H.G. Wells 的一些科幻和奇幻小说。让我们来看看时间机器、世界大战、隐形人和莫罗博士岛。 我们可以使用gutenberg_download() 和每本小说的Project Gutenberg ID 号访问这些作品。
library(gutenbergr)
hgwells <- gutenberg_download(c(35, 36, 5230, 159))
tidy_hgwells <- hgwells %>%
unnest_tokens(word, text) %>%
anti_join(stop_words)
我们来看看H.G. Wells的小说中的单词情况
tidy_hgwells %>%
count(word, sort = TrUE) %>% filter(n>200)
word | n |
---|---|
<chr> | <int> |
time | 461 |
people | 302 |
door | 260 |
heard | 249 |
black | 232 |
stood | 229 |
white | 224 |
hand | 218 |
kemp | 213 |
eyes | 210 |
suddenly | 210 |
现在让我们来看看勃朗特的一些著名作品,他和简奥斯汀的生活时期有些重叠,但写作风格却截然不同。让我们来看看简·爱、呼啸山庄、荒野庄园的房客、维莱特和艾格尼丝·格雷。我们将再次为每部小说使用 Project Gutenberg ID 编号,并使用 gutenberg_download() 访问文本。
bronte <- gutenberg_download(c(1260, 768, 969, 9182, 767))
tidy_bronte <- bronte %>%
unnest_tokens(word, text) %>%
anti_join(stop_words)#删除停用词
tidy_bronte %>%
count(word, sort = TrUE) %>%head()
word | n |
---|---|
<chr> | <int> |
time | 1065 |
miss | 854 |
day | 825 |
hand | 767 |
eyes | 714 |
don’t | 666 |
有趣的是,H.G. Wells 和 Bronte的 “time”、“eyes”和“hand” 出现的次数都在前 10 名。
现在,让我们通过将数据框连接在一起来计算 Jane Austen、Bronte和 H.G. Wells 作品中每个单词的频率。我们可以使用 tidyr 中的 pivot_wider() 和 pivot_longer() 来重塑我们的数据框,以便我们需要绘制和比较三组小说。
library(tidyr)
frequency <- bind_rows(mutate(tidy_bronte, author = "Bronte Sisters"),
mutate(tidy_hgwells, author = "H.G. Wells"),
mutate(tidy_books, author = "Jane Austen")) %>%
mutate(word = str_extract(word, "[a-z']+")) %>%#筛选单词
count(author, word) %>%
group_by(author) %>%#根据作者进行分组
mutate(proportion = n / sum(n)) %>% #计算词频
select(-n) %>%
pivot_wider(names_from = author, values_from = proportion) %>%#将数据转换
pivot_longer(`Bronte Sisters`:`H.G. Wells`,
names_to = "author", values_to = "proportion")
frequency%>%head()
word | Jane Austen | author | proportion |
---|---|---|---|
<chr> | <dbl> | <chr> | <dbl> |
a | 9.190796e-06 | Bronte Sisters | 5.869785e-05 |
a | 9.190796e-06 | H.G. Wells | 1.471844e-05 |
aback | NA | Bronte Sisters | 3.913190e-06 |
aback | NA | H.G. Wells | 1.471844e-05 |
abaht | NA | Bronte Sisters | 3.913190e-06 |
abaht | NA | H.G. Wells | NA |
我们在这里使用 str_extract() 是因为来自 Project Gutenberg 的 UTF-8 编码文本有一些带有下划线的单词示例,以表示强调(如斜体)。分词器将这些视为单词,但我们不像我们在选择使用 str_extract() 之前的初始数据探索中看到的那样,将“_any_”与“any”分开计算。
library(scales)
# 会返回
ggplot(frequency, aes(x = proportion, y = `Jane Austen`,
color = abs(`Jane Austen` - proportion))) +
geom_abline(color = "gray20", lty = 2) +#拟合线
geom_jitter(alpha = 0.1, size = 2.5, width = 0.3, height = 0.3) +#抖动图
geom_text(aes(label = word), check_overlap = TrUE, vjust = 1.5) +#增加文本
scale_x_log10(labels = percent_format()) +#坐标轴取对数
scale_y_log10(labels = percent_format()) +
scale_color_gradient(limits = c(0, 0.001),
low = "darkslategray4", high = "gray75") +#设置颜色
facet_wrap(~author, ncol = 2) +#分面绘图
theme(legend.position="none") +#去除图例
labs(y = "Jane Austen", x = NULL)#y标签
在这些图中,靠近线的单词在两组文本中具有相似的频率,例如,在奥斯汀和勃朗特文本中 (“miss”、“time”、“day”在高频端) 或在奥斯汀文本中和 Wells 文本 (高频端的“time”、“date”、“brother”)。离线较远的词是在一组文本中比在另一组文本中更多的词。例如,在奥斯汀-勃朗特面板中,像 “elizabeth”、“emma”和“fanny”(都是专有名词) 这样的词在奥斯汀的文本中出现,但在勃朗特的文本中却不多,而像 “arthur”和“dog” 出现在勃朗特的文本中,但没有出现在奥斯汀的文本中。在将 H.G. Wells 与简·奥斯汀进行比较时,威尔斯使用了奥斯汀没有使用的 “beast”、“gun”、“fell”和“black” 等词,而奥斯汀使用了 “family”、“friend”、“letter” 等词,而威尔斯没有。
总体来说, 上图的 Austen-Bronte 面板中的单词比 Austen-Wells 面板中的更接近斜率线。 Austen-Wells 面板在低频时有空白空间。这些特征表明,奥斯汀和勃朗特使用的相似词比奥斯汀和威尔斯更多。此外,我们看到并非在所有三组文本中都找到了所有单词,并且 Austen 和 H.G. Wells 面板中的数据点较少。
让我们使用相关性测试来量化这些词频集的相似和不同之处。奥斯汀和勃朗特之间以及奥斯汀和威尔斯之间的词频有多相关?
cor.test(data = frequency[frequency$author == "Bronte Sisters",],
~ proportion + `Jane Austen`)
Pearson’s product-moment correlation
data: proportion and Jane Austen
t = 111.06, df = 10346, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
0.7285370 0.7461189
sample estimates:
cor
0.7374529
cor.test(data = frequency[frequency$author == "H.G. Wells",],
~ proportion + `Jane Austen`)
Pearson’s product-moment correlation
data: proportion and Jane Austen
t = 35.229, df = 6008, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
0.3925914 0.4345047
sample estimates:
cor
0.4137673
正如我们在图中看到的那样,奥斯汀和勃朗特小说之间的词频相关性(0.737) 比奥斯汀和 H.G.威尔斯之间相关性(0.414) 更高。
4.总结
在本文,我们探讨了关于tidy data的含义,以及如何将tidy data原则应用于自然语言处理。当文本以每行一个单词的格式组织时,删除停用词或计算词频等任务是tidy data生态系统中很自然的应用。每行一个token的框架可以从单个单词扩展到 n-gram 和其他有意义的文本单元,这些我们在后续继续讨论。如果文章对你有帮助,请多多点赞、收藏、评论支持,您的支持是我创作最大的动力!
参考资料:Text Mining with R
转载:https://blog.csdn.net/weixin_45052363/article/details/125774148