《python数据分析(第2版)-阿曼多.凡丹戈》读书笔记第9章-分析文本数据和社交媒体

python数据分析个人学习读书笔记-目录索引

第9章分析文本数据和社交媒体

   在前几章中,我们着重讨论了结构化数据的分析,主要涉及列表格式的数据。实际上,跟结构化数据一样,纯文本也是最常见的格式之一。文本分析需要用到词频分布分析、模式识别、标注、链接和关联分析(link and association analysis)、情感分析和可视化等。这里将借助Python自然语言工具包(The Python Natural Language Toolkit,NLTK)来分析文本。NLTK自身带有一个文本样本集,这个样本集名为corpora。同时,scikitlearn程序库也提供了许多文本分析工具,本章也会对这些工具进行简要的介绍。

  此外,在本章中还会举例说明网络分析。本章涉及的主题如下。

  • 安装NLTK
  • NLTK简介
  • 滤除停用字、姓名和数字
  • 词袋模型
  • 词频分析
  • 朴素贝叶斯分类
  • 情感分析
  • 创建词云
  • 社交网络分析

9.1NLTK

  安装NLTK:

1 pip3 install nltk scikit-learn

 

9.2NLTK简介

   NLTK是一个用来分析自然语言文本(如英文句子)的Python应用程序接口。NLTK起源于2001年,最初设计是用来进行教学的。

  虽然我们在9.1节安装了NLTK,但还不够:还需要下载NLTK语料库。需要注意的是,这里的下载量相对较大(约1.8 GB),但是只需要下载一次。除非我们确切地知道自己需要哪种语料库,否则最好下载所有可用的语料库。从Python shell下载语料库的命令如下。

1 $ python3
2 >>> import nltk
3 >>> nltk.download()

  这时,会出现一个GUI应用程序,你可以指定要下载的文件以及放到何处,如图9-1所示。

 邀月工作室

  如果是NLTK新手,最方便的方法就是选择默认选项并下载所有内容。在本章中,我们将需要用到停用词、电影评论、名字和古腾堡语料库。所以,我们鼓励读者根据ch-09.ipynb文件中的代码来学习下列部分。

    Tips:如果安装NLTK方面有疑惑,这篇文章或许对你有帮助《数据分析实战-托马兹.卓巴斯》读书笔记第9章--自然语言处理NLTK(分析文本、词性标注、主题抽取、文本数据分类)

 

9.3滤除停用字、姓名和数字

   进行文本分析时,我们经常需要对停用字(Stopwords)进行剔除。这里所谓的停用字,就是那些经常见、但是没有多大信息含量的词。NLTK为很多语种都提供了停用字语料库。下面加载英语停用字语料并输出部分单词。

1 sw = set(nltk.corpus.stopwords.words('english'))
2 print("Stop words:", list(sw)[:7])

  下面是输出的部分常用字。

Stop words: ['yours', 'off', 'own', 'so', "won't", 'all', 'doing']

  请注意,这个语料库中的所有单词都是小写的。

  此外,NLTK还提供了一个Gutenberg语料库。Gutenberg项目是一个数字图书馆计划,供人们在互联网上阅读图书。

  下面加载Gutenberg语料库并输出部分文件的名称。

1 gb = nltk.corpus.gutenberg
2 print("Gutenberg files:\n", gb.fileids()[-5:])

  下面是输出的某些书籍的名称,其中有些你可能比较熟悉。

Gutenberg files:
 ['milton-paradise.txt', 'shakespeare-caesar.txt', 'shakespeare-hamlet.txt', 'shakespeare-macbeth.txt', 'whitman-leaves.txt']

  现在,从milton-paradise.txt文件中提取前两句内容,供下面过滤使用。下面给出具体代码。

1 text_sent = gb.sents("milton-paradise.txt")[:2]
2 print("Unfiltered:", text_sent)

  下面是输出的句子。

Unfiltered: [['[', 'Paradise', 'Lost', 'by', 'John', 'Milton', '1667', ']'], ['Book', 'I']]

  现在,过滤掉下面的停用字。

1 for sent in text_sent:
2     filtered = [w for w in sent if w.lower() not in sw]
3     print("Filtered:\n", filtered)

  对于第一句,过滤后变成以下格式。

Filtered:
 ['[', 'Paradise', 'Lost', 'John', 'Milton', '1667', ']']

  注意,与之前相比,单词by已经被过滤掉了,因为它出现在停用字语料库中了。有时,我们希望将文本中的数字和姓名也删掉。我们可以根据词性(Part of Speech,POS)标签来删除某些单词。在这个标注方案中,数字对应着基数(Cardinal Number,CD)标签。

  姓名对应着单数形式的专有名词(the proper noun singular,NNP)标签。标注是基于启发式方法进行处理的,当然,这不可能非常精确。这个主题过于宏大,恐怕需要整本书来进行描述,详见前言部分。在过滤文本时,我们可以使用pos_tag()函数获取文本内所含的标签,具体如下。

1     tagged = nltk.pos_tag(filtered)
2     print("Tagged:\n", tagged)

  对于我们的文本,将得到如下各种标签。

Tagged:
 [('[', 'JJ'), ('Paradise', 'NNP'), ('Lost', 'NNP'), ('John', 'NNP'), ('Milton', 'NNP'), ('1667', 'CD'), (']', 'NN')]

  上面的pos_tag()函数将返回一个元组列表,其中各元组的第二个元素就是文本对应的标签。可见,一些单词虽然被标注为NNP,但是它们并不是。这里所谓的启发式方法,就是如果单词的第一个字母是大写的,那么就将其标注为NNP。如果将上面的文本全部转换为小写,那么就会得到不同的结果,这个留给读者自行练习。实际上,我们很容易就能利用NNP和CD标签来删除列表中的单词,代码如下。

1     words= []
2     for word in tagged:
3         if word[1] != 'NNP' and word[1] != 'CD':
4            words.append(word[0]) 
5 
6     print("Words:\n",words)

  下列代码摘自本书代码包中的ch-09.ipynb文件。

 1 import nltk
 2 from nltk import data
 3 data.path.append(r'D:\download\nltk_data') # 这里的路径需要换成自己数据文件下载的路径
 4 for sent in text_sent:
 5     filtered = [w for w in sent if w.lower() not in sw]
 6     print("Filtered:\n", filtered)
 7     tagged = nltk.pos_tag(filtered)
 8     print("Tagged:\n", tagged)
 9 
10     words= []
11     for word in tagged:
12         if word[1] != 'NNP' and word[1] != 'CD':
13            words.append(word[0]) 
14 
15     print("Words:\n",words)

 

9.4词袋模型

   所谓词袋模型,即它认为一篇文档是由其中的词构成的一个集合(即袋子),词与词之间没有顺序以及先后的关系。对于文档中的每个单词,我们都要计算它出现的次数,即单词计数(word counts)。据此,我们可以进行类似垃圾邮件识别之类的统计分析。

  如果有一批文档,那么每个唯一字(unique word)都可以视为语料库中的一个特征。这里所谓的“特征”,可以理解为参数或者变量。利用所有的单词计数,我们可以为每个文档建立一个特征向量这里的“向量”一词借用的是其数学含义。如果一个单词存在于语料库中,但是不存在于文档中,那么这个特征的值就为0。令人惊讶的是,NLTK至今尚未提供可以方便创建特征向量的实用程序。不过,我们可以借用Python机器学习库scikit-learn中的CountVectorizer类来轻松创建特征向量。第10章将对scikit-learn进行更详尽的介绍。

  下面从NLTK的Gutenberg语料库加载两个文本文档,代码如下。

1 hamlet = gb.raw("shakespeare-hamlet.txt")
2 macbeth = gb.raw("shakespeare-macbeth.txt")

  现在我们去掉英语停用词并创建特征向量,代码如下。

1 cv = sk.feature_extraction.text.CountVectorizer(stop_words='english')
2 print("Feature vector:\n", cv.fit_transform([hamlet, macbeth]).toarray())

  下面是两个文档对应的特征向量。

Feature vector:
 [[ 1  0  1 ... 14  0  1]
 [ 0  1  0 ...  1  1  0]]

  下面,我们输出部分特征(唯一字)值。

1 print("Features:\n", cv.get_feature_names()[:5])

  这些特征是按字母顺序排序的。

Features:
 ['1599', '1603', 'abhominably', 'abhorred', 'abide']

  下列代码摘自本书代码包中的ch-09.ipynb文件。

 1 import nltk
 2 import sklearn as sk
 3 from nltk import data
 4 data.path.append(r'D:\download\nltk_data') # 这里的路径需要换成自己数据文件下载的路径
 5 
 6 hamlet = gb.raw(“shakespeare-hamlet.txt”)
 7 macbeth = gb.raw(“shakespeare-macbeth.txt”)
 8 
 9 cv = sk.feature_extraction.text.CountVectorizer(stop_words=’english’)
10 
11 print(“Feature vector:\n”, cv.fit_transform([hamlet, macbeth]).toarray())
12 
13 print(“Features:\n”, cv.get_feature_names()[:5])

 

9.5词频分析

   NLTK提供的FreqDist类可以用来将单词封装成字典并计算给定单词列表中各个单词出现的次数。下面,我们来加载Gutenberg项目中莎士比亚的Julius Caesar中的文本。

  首先,把停用词和标点符号剔除掉,代码如下。

1 punctuation = set(string.punctuation)
2 filtered = [w.lower() for w in words if w.lower() not in sw and w.lower() not in punctuation]

  然后,创建一个FreqDist对象并输出频率最高的键和值,代码如下。

1 fd = nltk.FreqDist(filtered)
2 print("Words", list(fd.keys())[:5])
3 print("Counts", list(fd.values())[:5])

  输出的键和值如下。

Words ['tragedie', 'julius', 'caesar', 'william', 'shakespeare']
Counts [2, 1, 190, 1, 1]

  很明显,这个列表中的第一个字并非英语单词,因此需要添加一种启发式搜索(Heuristic)方法,只选择最少含有两个字符的单词。对于NLTK提供的FreqDist类,我们既可以使用类似于字典的访问方法,也可以使用更便利的其他方法。下面,我们要提取最常出现的单词以及对应的出现次数。

1 print("Max", fd.max())
2 print("Count", fd['d'])

  对应下面的结果,你肯定不会感到吃惊。

Max caesar
Count 0

  目前,我们只是针对单个单词构成的词汇进行了分析,不过可以推而广之,将分析扩展到含有两个单词和3个单词的词汇上,对于后面这两种,我们分别称之为双字词和三字词。对于这两种分析,分别有对应的函数,即bigrams()函数和trigrams()函数。现在,我们再次进行文本分析,不过,这次针对的是双字词。

1 fd = nltk.FreqDist(nltk.bigrams(filtered))
2 print("Bigrams", list(fd.keys())[:5])
3 print("Counts", list(fd.values())[:5])
4 print("Bigram Max", fd.max())
5 print("Bigram count", fd[('let', 'vs')])

  结果如下。

Bigrams [('tragedie', 'julius'), ('julius', 'caesar'), ('caesar', 'william'), ('william', 'shakespeare'), ('shakespeare', '1599')]
Counts [1, 1, 1, 1, 1]
Bigram Max ('let', 'vs')
Bigram count 16

  下列代码摘自本书代码包中的ch-09.ipynb文件。

 1 import nltk
 2 import string
 3 from nltk import data
 4 data.path.append(r'D:\download\nltk_data') # 这里的路径需要换成自己数据文件下载的路径
 5 
 6 
 7 gb = nltk.corpus.gutenberg
 8 words = gb.words("shakespeare-caesar.txt")
 9 
10 sw = set(nltk.corpus.stopwords.words('english'))
11 punctuation = set(string.punctuation)
12 filtered = [w.lower() for w in words if w.lower() not in sw and w.lower() not in punctuation]
13 fd = nltk.FreqDist(filtered)
14 print("Words", list(fd.keys())[:5])
15 print("Counts", list(fd.values())[:5])
16 print("Max", fd.max())
17 print("Count", fd['d'])
18 
19 fd = nltk.FreqDist(nltk.bigrams(filtered))
20 print("Bigrams", list(fd.keys())[:5])
21 print("Counts", list(fd.values())[:5])
22 print("Bigram Max", fd.max())
23 print("Bigram count", fd[('let', 'vs')])

 

9.6朴素贝叶斯分类

   分类算法是机器学习算法中的一种,用来判断给定数据项所属的类别,即种类或类型。比如,我们可以根据某些特征来分辨一部电影属于哪个流派等。这样,流派就是我们要预测的类别。第10章还会对机器学习做进一步介绍。此刻,我们要讨论的是一个名为朴素贝叶斯分类的流行算法,它常常用于进行文本文档的分析。

  朴素贝叶斯分类是一个概率算法,它基于概率与数理统计中的贝叶斯定理。贝叶斯定理给出了利用新证据修正某事件发生的概率的方法。例如,假设一个袋子里装有一些巧克力和其他物品,但是这些我们没法看到。这时,我们可以用P(D)表示从袋子中掏出一块深色巧克力的概率。同时,我们用P©代表掏出一块巧克力的概率。当然,因为全概率是1,所以P(D)和P©的最大取值也只能是1。贝叶斯定理指出,后验概率与先验概率和相似度的乘积成正比,具体公式如下。

  P(D|C)

  在上面的公式中,P(D|C)是在事件C发生的情况下事件D发生的可能性。在我们还没有掏出任何物品之前,P(D) = 0.5,因为我们尚未获得任何信息。实际应用这个公式时,我们必须知道P(C|D)和P(C),或者能够间接求出这两个概率。

  朴素贝叶斯分类之所以称为朴素,是因为它简单假设特征之间是相互独立的。实践中,朴素贝叶斯分类的效果通常都会很好,说明这个假设得到了一定程度的保证。近来,人们发现这个假设之所以有意义,理论上是有依据的。不过,由于机器学习领域发展迅猛,现在已经发明了多种效果更佳的算法。

  下面,我们将利用停用词或标点符号对单词进行分类。这里,我们将字长作为一个特征,因为停用词和标点符号往往都比较短。

  为此,我们需要定义如下所示的函数。

1 def word_features(word):
2    return {'len': len(word)}
3 
4 def isStopword(word):
5     return word in sw or word in punctuation

  下面,我们对取自古登堡项目的shakespeare-caesar.txt中的单词进行标注,以区分是否为停用词,代码如下。

1 labeled_words = ([(word.lower(), isStopword(word.lower())) for 
2 word in words])
3 random.seed(42)
4 random.shuffle(labeled_words)
5 print(labeled_words[:5])

  下面显示了5个标注后的单词。

[('i', True), ('is', True), ('in', True), ('he', True), ('ambitious', False)]

  对于每个单词,我们可以求出其长度。

1 featuresets = [(word_features(n), word) for (n, word) in 
2 labeled_words]

  前几章我们介绍过拟合以及通过训练数据集和测试数据集的交叉验证来避免这种情况的方法。下面我们将要训练一个朴素贝叶斯分类器,其中90%的单词用于训练,剩下的10%用于测试。首先,创建训练数据集和测试数据集并针对数据展开训练,代码如下。

1 cutoff = int(.9 * len(featuresets))
2 train_set, test_set = featuresets[:cutoff], featuresets[cutoff:]
3 classifier = nltk.NaiveBayesClassifier.train(train_set)

  现在,我们拿出一些单词,检查该分类器的效果。

1 classifier = nltk.NaiveBayesClassifier.train(train_set)
2 print("'behold' class", classifier.classify(word_features('behold')))
3 print("'the' class", classifier.classify(word_features('the')))

  幸运的是,这些单词的分类完全正确:

'behold' class False
'the' class True

  然后,根据测试数据集来计算分类器的准确性,代码如下。

1 print("Accuracy", nltk.classify.accuracy(classifier, test_set))

  这个分类器的准确度非常高,几乎达到85%。下面来看哪些特征的贡献最大。

1 print(classifier.show_most_informative_features(5))

  结果显示如图9-2所示,在分类过程中字长的作用最大。

 邀月工作室

  下列代码摘自本书代码包中的ch-09.ipynb文件。

 1 mport nltk
 2 from nltk import data
 3 data.path.append(r'D:\download\nltk_data') # 这里的路径需要换成自己数据文件下载的路径
 4 import string
 5 import random
 6 
 7 sw = set(nltk.corpus.stopwords.words('english'))
 8 punctuation = set(string.punctuation)
 9 
10 def word_features(word):
11    return {'len': len(word)}
12 
13 def isStopword(word):
14     return word in sw or word in punctuation
15 gb = nltk.corpus.gutenberg
16 words = gb.words("shakespeare-caesar.txt")
17 
18 labeled_words = ([(word.lower(), isStopword(word.lower())) for 
19 word in words])
20 random.seed(42)
21 random.shuffle(labeled_words)
22 print(labeled_words[:5])
23 
24 featuresets = [(word_features(n), word) for (n, word) in 
25 labeled_words]
26 cutoff = int(.9 * len(featuresets))
27 train_set, test_set = featuresets[:cutoff], featuresets[cutoff:]
28 classifier = nltk.NaiveBayesClassifier.train(train_set)
29 print("'behold' class", classifier.classify(word_features('behold')))
30 print("'the' class", classifier.classify(word_features('the')))
31 
32 print("Accuracy", nltk.classify.accuracy(classifier, test_set))
33 print(classifier.show_most_informative_features(5))

 

9.7情感分析

  随着社交媒体、产品评论网站及论坛的兴起,用来自动抽取意见的观点挖掘或情感分析也随之变成一个炙手可热的新研究领域。通常情况下,我们希望知道某个意见的性质是正面的、中立的还是负面的。当然,这种类型的分类我们前面就曾遇到过。也就是说,我们有大量的分类算法可用。还有一个方法就是,通过半自动的(经过某些人工编辑)方法来编制一个单词列表,每个单词赋予一个情感分,即一个数值(如单词“good”的情感分为5,而单词“bad”的情感分为−5)。如果有了这样一张表,就可以给文本文档中的所有单词打分,从而得出一个情感总分。当然,类别的数量可以大于3,如五星级评级方案。

  我们会应用朴素贝叶斯分类方法对NLTK的影评语料库进行分析,从而将影评分为正面的或负面的评价。首先,加载影评语料库并过滤掉停用词和标点符号。这些步骤在此略过,因为之前就介绍过了。也可以考虑更精细的过滤方案,不过,需要注意的是,如果过滤得过火了,就会影响准确性。下面通过categories()方法对影评文档进行标注,代码如下。

1 labeled_docs = [(list(movie_reviews.words(fid)), cat)
2         for cat in movie_reviews.categories()
3         for fid in movie_reviews.fileids(cat)]

 完整的语料库拥有数以万计的唯一字,这些唯一字都可以用作特征,不过,这样做会极大地影响效率。这里,我们选用词频最高的前5%的单词作为特征。

1 words = FreqDist(filtered)
2 N = int(.05 * len(words.keys()))
3 word_features = list(words.keys())[:N]

  对于每个文档,可以通过一些方法来提取特征,其中包括以下方法:

  • 检查给定文档是否含有某个单词;
  • 求出某个单词在给定文档中出现的次数;
  • 正则化单词计数,使得正则化后的最大单词计数小于或等于1;
  • 将上面的数值加1,然后取对数。这里之所以加1,是为了防止对0取对数;
  • 利用上面的数值组成一个度量指标。

  俗话说“条条大路通罗马”,不过,这其中肯定不乏有些道路能够让我们更安全,也能更快地到达罗马。下面定义一个函数,该函数会使用原始单词计数来作为度量指标,代码为如下。

1 def doc_features(doc):
2     doc_words = FreqDist(w for w in doc if not isStopWord(w))
3     features = {}
4     for word in word_features:
5         features['count (%s)' % word] = (doc_words.get(word, 0))
6     return features

  现在,可以训练我们的分类器了,具体方法与前面的例子相似。这里准确性达到78%,这个成绩已经相当不错了,非常接近情感分析成绩的上限。因为据研究发现,即便是人类,也无法对给定文档的表达出的感情的看法达成一致。因此,要想让情感分析软件获得满分是不可能的事情。

下面的图9-3给出包含信息量最大的特征。

邀月工作室

  如果仔细检查这个列表,我们很容易发现有一些褒义词,如“wonderful(妙不可言的)”和“outstanding(杰出的)”。而单词“bad(恶劣的)”“stupid(愚蠢的)”和“boring(无趣的)”则明显是一些贬义词。如果有兴趣,读者也可以分析其他的那些特征,这将作为一个作业留给读者自己完成。以下代码摘自本书代码包中的sentiment.py文件。

 1 import random
 2 from nltk import data
 3 data.path.append(r'D:\download\nltk_data') # 这里的路径需要换成自己数据文件下载的路径
 4 from nltk.corpus import movie_reviews
 5 from nltk.corpus import stopwords
 6 from nltk import FreqDist
 7 from nltk import NaiveBayesClassifier
 8 from nltk.classify import accuracy
 9 import string
10 
11 labeled_docs = [(list(movie_reviews.words(fid)), cat)
12         for cat in movie_reviews.categories()
13         for fid in movie_reviews.fileids(cat)]
14 random.seed(42)
15 random.shuffle(labeled_docs)
16 
17 review_words = movie_reviews.words()
18 print("# Review Words", len(review_words))
19 
20 sw = set(stopwords.words('english'))
21 punctuation = set(string.punctuation)
22 
23 def isStopWord(word):
24     return word in sw or word in punctuation
25 
26 filtered = [w.lower() for w in review_words if not isStopWord(w.lower())]
27 print("# After filter", len(filtered))
28 words = FreqDist(filtered)
29 N = int(.05 * len(words.keys()))
30 word_features = list(words.keys())[:N]
31 
32 def doc_features(doc):
33     doc_words = FreqDist(w for w in doc if not isStopWord(w))
34     features = {}
35     for word in word_features:
36         features['count (%s)' % word] = (doc_words.get(word, 0))
37     return features
38 
39 featuresets = [(doc_features(d), c) for (d,c) in labeled_docs]
40 train_set, test_set = featuresets[200:], featuresets[:200]
41 classifier = NaiveBayesClassifier.train(train_set)
42 print("Accuracy", accuracy(classifier, test_set))
43 
44 print(classifier.show_most_informative_features())

 

9.8创建词云

   阅读本书前,我们也许已在Wordle或其他地方见过词云了;如果没见过也不要紧,因为可以在本章看个够了。虽然Python有两个程序库都可以用来生成词云,但是它们生成词云的效果尚不能与Wordle网站的效果相媲美。所以,我们不妨利用Wordle网站的页面来创建词云。利用Wordle生成词云时,需要提供一个单词列表及其对应的权值,具体格式如下。

Word1 : weight
Word2 : weight

  现在,对前面例子中的代码稍作修改,让它输出这个字表。这里,我们将使用词频作为度量指标,选取词频排名靠前的单词。实际上,这里不需要任何新的代码,具体内容可以参考本书代码包中的ch-09.ipynb文件。

 1 from nltk import data
 2 data.path.append(r'D:\download\nltk_data') # 这里的路径需要换成自己数据文件下载的路径
 3 from nltk.corpus import movie_reviews
 4 from nltk.corpus import stopwords
 5 from nltk import FreqDist
 6 import string
 7 
 8 sw = set(stopwords.words('english'))
 9 punctuation = set(string.punctuation)
10 
11 def isStopWord(word):
12     return word in sw or word in punctuation
13 review_words = movie_reviews.words()
14 filtered = [w.lower() for w in review_words if not isStopWord(w.lower())]
15 
16 words = FreqDist(filtered)
17 N = int(.01 * len(words.keys()))
18 tags = list(words.keys())[:N]
19 
20 for tag in tags:
21     print(tag, ':', words[tag])

  将上面代码的输出结果复制粘贴到上面提到的Wordle页面,就能得到如图9-4所示的词云了。

plot : 1513
two : 1911
teen : 151
couples : 27
go : 1113
church : 69
party : 183
drink : 32
drive : 105
get : 1949
accident : 104
one : 5852
guys : 268
dies : 104
girlfriend : 218
continues : 88
see : 1749
life : 1586
nightmares : 26
deal : 219
watch : 603
movie : 5771
sorta : 10
find : 782
critique : 61
mind : 451
fuck : 17
generation : 96
touches : 55
cool : 208
idea : 386
presents : 78
bad : 1395
package : 30
makes : 992
review : 295
even : 2565
harder : 33
write : 119
since : 768
generally : 103
applaud : 10
films : 1536
attempt : 263
break : 175
mold : 14
mess : 159
head : 387
lost : 409
highway : 28
memento : 10
good : 2411
ways : 189
making : 602
types : 48
folks : 74
snag : 2
correctly : 17
seem : 574
taken : 225
pretty : 528
neat : 32
concept : 114
executed : 46
terribly : 58
problems : 293
well : 1906
main : 399
problem : 396
simply : 428
jumbled : 12
starts : 316
normal : 111
downshifts : 2
fantasy : 97
world : 1037
audience : 914
member : 126
going : 888
dreams : 131
characters : 1859
coming : 275
back : 1060
dead : 418
others : 288
look : 835
like : 3690
strange : 185
apparitions : 5
disappearances : 3
looooot : 1
chase : 159
scenes : 1274
tons : 21
weird : 100
things : 852
happen : 220
explained : 70
personally : 44
trying : 566
unravel : 9
film : 9517
every : 947
give : 561
clue : 45
kind : 559
fed : 29
biggest : 149
obviously : 228
got : 470
big : 1064
secret : 184
hide : 40
seems : 1033
want : 560
completely : 440
final : 380
five : 284
minutes : 644
make : 1642
entertaining : 314
thrilling : 46
engaging : 79
meantime : 19
really : 1558
sad : 108
part : 714
arrow : 29
dig : 19
flicks : 81
actually : 837
figured : 29
half : 535
way : 1693
point : 685
strangeness : 6
start : 312
little : 1501
bit : 568
sense : 555
still : 1047
guess : 226
bottom : 93
line : 435
movies : 1206
always : 586
sure : 523
given : 502
password : 4
enter : 68
understanding : 57
mean : 242
showing : 147
melissa : 12
sagemiller : 8
running : 323
away : 655
visions : 31
20 : 98
throughout : 302
plain : 82
lazy : 34
okay : 125
people : 1455
chasing : 43
know : 1217
need : 316
giving : 214
us : 1073
different : 430
offering : 38
insight : 54
apparently : 209
studio : 163
took : 164
director : 1237
chopped : 5
shows : 410
might : 635
decent : 164
somewhere : 127
suits : 45
decided : 104
turning : 80
music : 480
video : 322
edge : 104
would : 2109
actors : 706
although : 795
wes : 50
bentley : 9
seemed : 212
playing : 362
exact : 64
character : 2020
american : 559
beauty : 135
new : 1292
neighborhood : 21
kudos : 21
holds : 90
entire : 408
feeling : 225
unraveling : 2
overall : 160
stick : 69
entertain : 53
confusing : 94
rarely : 102
excites : 2
feels : 216
redundant : 14
runtime : 8
despite : 352
ending : 423
explanation : 69
craziness : 4
came : 185
oh : 216
horror : 473
slasher : 84
flick : 196
packaged : 3
someone : 401
assuming : 15
genre : 268
hot : 127
kids : 328
also : 1967
wrapped : 39
production : 300
years : 846
ago : 199
sitting : 93
shelves : 13
ever : 776
whatever : 136
skip : 45
joblo : 30
nightmare : 60
elm : 14
street : 140
3 : 222
7 : 115
10 : 449
blair : 98
witch : 108
2 : 439
crow : 55
9 : 75
salvation : 14
4 : 190
stir : 27
echoes : 31
8 : 140
happy : 215
bastard : 46
quick : 139
damn : 88
y2k : 4
bug : 81
starring : 184
jamie : 42
lee : 266
curtis : 37
another : 1121
baldwin : 89
brother : 268
william : 206
time : 2411
story : 2169
regarding : 31
crew : 214
tugboat : 2
comes : 733
across : 221
deserted : 21
russian : 68
tech : 29
ship : 264
kick : 52
power : 238
within : 227
gore : 110
bringing : 81
action : 1172
sequences : 293
virus : 77
empty : 67
flash : 62
substance : 73
middle : 222
nowhere : 98
origin : 8
pink : 24
flashy : 41
thing : 809
hit : 285
mir : 7
course : 648
donald : 38
sutherland : 39
stumbling : 15
around : 903
drunkenly : 2
hey : 70
let : 425
robots : 27
acting : 695
average : 119
likes : 99
likely : 164
work : 1020
halloween : 60
h20 : 7
wasted : 118
real : 915
star : 761
stan : 12
winston : 4
robot : 45
design : 87
schnazzy : 1
cgi : 50
occasional : 62
shot : 348
picking : 28
brain : 93
body : 269
parts : 207
turn : 363
otherwise : 156
much : 2049
sunken : 4
jaded : 12
viewer : 218
thankful : 7
invention : 11
timex : 1
indiglo : 1
based : 389
late : 238
1960 : 22
television : 220
show : 741
name : 392
mod : 17
squad : 40
tells : 255
tale : 216
three : 695
reformed : 7
criminals : 35
employ : 16
police : 241
undercover : 28
however : 989
wrong : 385
evidence : 66
gets : 865
stolen : 58
immediately : 163
suspicion : 19
ads : 33
cuts : 60
claire : 70
dane : 5
nice : 344
hair : 109
cute : 134
outfits : 21
car : 321
chases : 50
stuff : 208
blowing : 26
sounds : 133
first : 1836
fifteen : 64
quickly : 255
becomes : 526
apparent : 100
certainly : 361
slick : 39
looking : 501
complete : 197
costumes : 71
enough : 910
best : 1333
described : 57
cross : 111
hour : 355
long : 836
cop : 208
stretched : 19
View Code

     https://wordart.com/create

邀月工作室

  仔细研究这个词云,会发现它远非完美,还有很大的改进空间。因此,我们不妨做进一步的尝试。

  进一步过滤:我们应当剔除包含数字符号和姓名的那些单词。这时,可以借助NLTK的names语料库。此外,对于在所有语料库中仅出现一次的那些单词,我们也可以置之不理,因为它们不大可能提供足够有价值的信息。

  使用更好的度量指标:词频和逆文档频率(The Term Frequency-Inverse Document Frequency,TF-IDF)看起来是一个不错的选择。

  度量指标TF-IDF可以通过对语料库中的单词进行排名并据此赋予这些单词相应的权重。这个权重的值与单词在特定文档中出现的次数即词频成正比。同时,它还与语料库中含有该单词的文档数量成反比,即逆文档频率。TF-IDF的值为词频和逆文档频率之积。如果需要自己动手实现TF-IDF,那么还必须考虑对数标定处理。幸运的是,实际上我们根本无需考虑这些实现方面的细节,因为scikit-learn已经为我们准备好了一个TfidfVectorizer类,它有效地实现了TF-IDF。该类能够生成一个稀疏的SciPy矩阵,用术语来说就是一个词条-文档矩阵,这个矩阵存放的是单词和文档的每种可能组合的TF-IDF值。因此,对于一个含有2 000个文档和25 000个不重复的词的语料库,可以得到一个2 000×25 000的矩阵。由于大部分矩阵值都为0,我们将它作为一个稀疏矩阵来处理比较省劲。对每个单词来说,其最终的排名权重可以通过对其TF-IDF值求和来得到。

下面我们通过isalpha()方法和姓名语料库来改善过滤效果,代码如下。

1 all_names = set([name.lower() for name in names.words()])
2 
3 def isStopWord(word):
4     return (word in sw or word in punctuation) or not word.isalpha() or word in all_names

  下面创建一个NLTK的FreqDist类,从而过滤掉那些只出现一次的单词。对于TfidfVectorizer类,需要为其提供一个字符串列表来指出语料库中的各个文档。

  下面创建这个列表,代码如下。

1 for fid in movie_reviews.fileids():
2     texts.append(" ".join([w.lower() for w in movie_reviews.words(fid) if not isStopWord(w.lower()) and words[w.lower()]

  创建向量化程序,为了保险起见,令其忽略停用词。

1 vectorizer = TfidfVectorizer(stop_words='english')

  创建稀疏的词条-文档矩阵。

1 matrix = vectorizer.fit_transform(texts)

  为每个单词的TF-IDF值求和并将结果存放到NumPy数组中。

1 sums = np.array(matrix.sum(axis=0)).ravel()

  下面通过单词的排名权值来创建一个Pandas DataFrame并进行相应的排序,代码如下。

1 ranks = []
2 
3 for word, val in zip(vectorizer.get_feature_names(), sums):
4     ranks.append((word, val))
5 
6 df = pd.DataFrame(ranks, columns=["term", "tfidf"])
7 df = df.sort_values(['tfidf'])
8 print(df.head())

  排名最低的值将被输出,同时供将来过滤用。

                 term    tfidf
19963  superintendent  0.03035
8736            greys  0.03035
14010           ology  0.03035
2406          briefer  0.03035
2791      cannibalize  0.03035
matter : 10.160156320157853
review : 10.162109208095815
seeing : 10.193962242951153
jokes : 10.195055387739588
past : 10.229789978743266
romantic : 10.270767948140588
directed : 10.27679275085064
start : 10.302358509215921
finally : 10.315385095902199
video : 10.356897657857315
despite : 10.36356758711268
ship : 10.370281211670585
beautiful : 10.415601266078719
scream : 10.421970655899635
sequence : 10.461140540373234
supposed : 10.473608248283002
shot : 10.497822532176006
face : 10.520647846526677
turn : 10.535466043788666
lives : 10.536265259335323
later : 10.536596993112912
tell : 10.54178804022045
camera : 10.580870634146848
works : 10.585001927065935
children : 10.59229934724306
live : 10.658879764040353
daughter : 10.685408819519905
earth : 10.6855987888017
mr : 10.711280266859085
car : 10.715492238654587
believe : 10.724994487616465
maybe : 10.738295943695265
person : 10.766043701757003
book : 10.799070874951035
worst : 10.801808893863994
hand : 10.815936702218032
named : 10.818013961831461
game : 10.86383795127332
fight : 10.865544776692566
use : 10.88431817114859
used : 10.955534955009918
killer : 11.000641030461054
certainly : 11.001525429842374
begins : 11.070728771014815
perfect : 11.096242361915083
relationship : 11.102758849863765
said : 11.108251601369561
nice : 11.124424984182713
days : 11.138293311572346
kids : 11.181482360800292
called : 11.192445668887236
run : 11.198723318188565
playing : 11.216003589219902
final : 11.223074577571513
tries : 11.242871986273729
unfortunately : 11.295825206128804
group : 11.326924605147562
comic : 11.389992817954504
left : 11.438340356187926
entire : 11.444592282847251
idea : 11.461929302207174
based : 11.487214286939588
head : 11.516296149111216
wrong : 11.562368605644979
second : 11.585319233830582
summer : 11.586686728126798
shows : 11.63522507695588
main : 11.660671883754478
soon : 11.711290550319069
true : 11.754181091867876
turns : 11.821623440558202
getting : 11.874446133551853
human : 11.899923970603947
problem : 11.99620702280271
written : 12.006014424193982
hour : 12.018041625879004
different : 12.151447465665578
boy : 12.202291415418031
performances : 12.239403934569973
house : 12.251961733491308
simply : 12.29153134772739
war : 12.297910070957098
mind : 12.324864356668593
small : 12.327723933187466
especially : 12.352783302524191
rest : 12.359092724325766
tv : 12.368538157058103
lost : 12.399034233379663
completely : 12.435026821722477
looks : 12.4500832891327
humor : 12.480741394166033
line : 12.531463602753462
reason : 12.549615155979115
dead : 12.550701937661323
friend : 12.554066106444134
let : 12.557077785028916
thought : 12.650011584913317
stars : 12.688790737853829
couple : 12.731565830451245
alien : 12.811802155159485
moments : 12.890094530509302
evil : 12.90918008264871
wants : 12.91523366454186
friends : 12.971455692528544
night : 12.972118832796
mother : 13.069867009873525
given : 13.163657271730541
ending : 13.241085606272085
play : 13.241256041448459
feel : 13.260355813064143
gives : 13.541933025342574
got : 13.58120683575867
watching : 13.633462479391907
death : 13.638264772375894
looking : 13.717090584113414
girl : 13.728552228886974
instead : 13.774615981791037
probably : 13.808813918690175
city : 13.842578473593091
school : 13.897593833405203
father : 14.066422069009711
music : 14.075762648197905
help : 14.120498729117202
sure : 14.145480680268529
dialogue : 14.23176869122043
kind : 14.399967422476037
black : 14.447741822146453
actor : 14.522615872729146
sense : 14.629745462015816
want : 14.725766439030554
pretty : 14.809940495890388
making : 14.817117131361535
series : 14.821081643716946
set : 14.887819969400011
half : 14.89251185190553
money : 14.901355104392845
bit : 14.934268735728764
home : 14.976582330659667
place : 15.049919713647064
trying : 15.114785990368087
times : 15.118763478807493
sex : 15.166419440553032
american : 15.359810523477407
hard : 15.454208298004339
picture : 15.506551578323716
woman : 15.639843619779164
hollywood : 15.650704478670258
horror : 15.688047135511049
far : 15.758603032319007
watch : 15.781648059164944
fun : 15.799106034203923
special : 15.894929367222112
course : 15.931964229777236
away : 15.944636479597987
takes : 15.954305763102612
men : 16.036233030752417
wife : 16.103810528049053
interesting : 16.110566312034386
screen : 16.330798169836488
goes : 16.355214344649127
minutes : 16.578665989245824
point : 16.593231934007573
quite : 16.743903021148146
lot : 16.7556321332947
comes : 16.945112986149024
high : 16.96333443299414
day : 17.469931907378594
young : 17.61907491805685
come : 17.710951023055475
plays : 17.825218944178573
actors : 17.841139061718554
acting : 18.056556176317212
effects : 18.156452985282307
fact : 18.358966964131444
family : 18.436351324270554
cast : 18.462652990794695
right : 18.51721470872539
look : 18.709286795233268
original : 18.815142636940738
played : 18.96169328195863
years : 19.058321835992686
long : 19.063431217630267
actually : 19.081359841119173
thing : 19.347634515439985
script : 19.466362068961164
old : 19.68484647913068
things : 19.711840751606182
gets : 19.789969444538826
think : 19.79997034212867
role : 19.829843863051224
performance : 19.991860531957602
better : 20.069030711347395
audience : 20.229377054374694
going : 20.384999917030928
year : 20.404754669356507
seen : 20.741715274906582
real : 20.782565933126254
makes : 20.916315796004714
work : 21.48974389558135
funny : 22.184963867355552
world : 22.430195409736793
end : 22.501145434405117
comedy : 22.724158274326264
big : 23.394018499746736
director : 23.689769490697007
great : 24.489611034104875
scenes : 25.243560471583756
know : 25.25908281181752
new : 25.424409388545484
movies : 25.46489805929262
best : 25.793314078809164
scene : 26.65609949406872
man : 27.271016365028732
people : 27.772749971079577
action : 27.8317318022786
little : 27.912251658495467
make : 28.36244923373348
films : 29.081817882359086
bad : 29.1633844710237
plot : 29.81049966927315
really : 30.211353157494234
life : 30.844185813234848
characters : 33.204457851658944
character : 33.73419369933767
story : 36.69429364590541
time : 36.76869565644592
good : 38.945456240101606
like : 49.087665729852674
movie : 80.3332102380712
film : 109.34008874939663
View Code

接下来,我们输出排名靠前的单词,这样就可以利用Wordle网站生成如图9-5所示的词云了。

邀月工作室

  令人遗憾的是,我们必须亲自运行代码,才能看到上面词云中的不同色彩。相较于单调的词频而言,TF-IDF度量指标更富于变化,因此我们就能得到更加丰富的色彩。此外,这样还能使得云雾中的单词看起来联系更加紧密。下列代码摘自本书代码包中的ch-09.ipynb文件。

 1 from nltk import data
 2 data.path.append(r'D:\download\nltk_data') # 这里的路径需要换成自己数据文件下载的路径
 3 from nltk.corpus import movie_reviews
 4 from nltk.corpus import stopwords
 5 from nltk.corpus import names
 6 from nltk import FreqDist
 7 from sklearn.feature_extraction.text import TfidfVectorizer
 8 import itertools
 9 import pandas as pd
10 import numpy as np
11 import string
12 
13 sw = set(stopwords.words('english'))
14 punctuation = set(string.punctuation)
15 all_names = set([name.lower() for name in names.words()])
16 
17 def isStopWord(word):
18     return (word in sw or word in punctuation) or not word.isalpha() or word in all_names
19 
20 review_words = movie_reviews.words()
21 filtered = [w.lower() for w in review_words if not isStopWord(w.lower())]
22 
23 words = FreqDist(filtered)
24 
25 texts = []
26 
27 for fid in movie_reviews.fileids():
28     texts.append(" ".join([w.lower() for w in movie_reviews.words(fid) if not isStopWord(w.lower()) and words[w.lower()] > 1]))
29 
30 vectorizer = TfidfVectorizer(stop_words='english')
31 matrix = vectorizer.fit_transform(texts)
32 sums = np.array(matrix.sum(axis=0)).ravel()
33 
34 ranks = []
35 
36 for word, val in zip(vectorizer.get_feature_names(), sums):
37     ranks.append((word, val))
38 
39 df = pd.DataFrame(ranks, columns=["term", "tfidf"])
40 df = df.sort_values(['tfidf'])
41 print(df.head())
42 
43 N = int(.01 * len(df))
44 df = df.tail(N)
45 
46 for term, tfidf in zip(df["term"].values, df["tfidf"].values):
47     print(term, ":", tfidf)

 

9.9社交网络分析

   所谓社交网络分析,实际上就是利用网络理论来研究社会关系,其中,网络中的节点代表的是网络中的参与者。节点之间的连线代表的是参与者之间的相互关系。

  严格来讲,这应该称为一个图。本书仅介绍如何利用流行的Python库NetworkX来分析简单的图。这里,通过Python库matplotlib来对这些网络图进行可视化。

  为了安装NetworkX,可以使用如下命令。

1 $ pip3 install networkx

  关于Networkx,也可以参照这篇文章《数据分析实战-托马兹.卓巴斯》读书笔记第8章--图(NetworkX、Gephi)修订版

  下面导入NetworkX并指定一个简单的别名。

1 Import networkx as nx

  NetworkX提供了许多示例图,下面将其列出,代码如下。

1 print([s for s in dir(nx) if s.endswith('graph')])

  下面导入Davis Southern women图并绘制出各个节点的度的柱状图,代码如下。

1 G = nx.davis_southern_women_graph()
2 plt.hist(list(nx.degree(G).values())) #AttributeError: 'DegreeView' object has no attribute 'values',去掉values即可,邀月注。
3 plt.show()

  最终得到如图9-6所示的柱状图。

邀月工作室

  下面来绘制带有节点标签的网络图,所需命令如下。

1 plt.figure(figsize=(8,8))
2 pos = nx.spring_layout(G)
3 nx.draw(G, node_size=10)
4 nx.draw_networkx_labels(G, pos)
5 plt.show()

  得到的图形如图9-7所示。

邀月工作室

 

     图像的可读性可以再优化:

1 plt.figure(figsize=(8,8))
2 print(help(nx.spring_layout))
3 pos = nx.spring_layout(G, scale=2)
4 
5 nx.draw(G)
6 nx.draw_networkx_labels(G, pos)
7 plt.show()

  效果如下图:

邀月工作室

  虽然这个例子很短,但是对于简单体验NetworkX的功能特性来说已经足够了。我们可以借助NetworkX来探索、可观化和分析社交媒体网络,如Twitter、Facebook以及LinkedIn等。当然,本节讲述的方法不仅适用于社交网络,实际上,所有类似的网络图都可以使用NetworkX来处理。

 

9.10小结

   本章讲述了文本分析方面的知识。首先,我们给出了文本分析中剔除停用词的一个最佳实践。

  介绍词袋模型时,我们还创建了一个词袋来存放文档内出现的单词。此外,我们还根据所有的单词计数,为每个文档生成了一个特征向量。

  分类算法是机器学习算法中的一种,用来给特定事物进行分类。朴素贝叶斯分类是一种概率算法,它基于概率与数理统计中的贝叶斯定理。这里,贝叶斯定理指出,后验概率与先验概率和相似度之积成正比。

  第10章将对机器学习进行更深入的介绍,因为这是一个充满了无限希望的研究领域。也许有一天,它将完全替代人类劳动力。届时,以气象数据为例,来说明Python机器学习库scikit-learn的具体使用方法。

 

 第9章完。

python数据分析个人学习读书笔记-目录索引

 

随书源码官方下载:
https://www.ptpress.com.cn/shopping/buy?bookId=bae24ecb-a1a1-41c7-be7c-d913b163c111

需要登录后免费下载。

 

posted @ 2020-04-06 13:08  邀月  阅读(606)  评论(0编辑  收藏  举报