尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

第11章 案例研究: 文本统计

第11章 案例研究: 文本统计 到目前为止你见到的代码片段大多只有几行旨在演示某项Python功能。编程新手会很快发现将小小的代码片段组织成完整的程序是一大步。要编写规模较大的程序必须更详尽地规划还需对如何以最佳方式结合使用各项Python功能有所了解。刚开始编写较大的程序时可能需要反复试验。本章将循序渐进地开发一个较大的 Python程序。首先对要解决的问题进行描述然后创建一个解决问题的Python程序并对其进行测试。程序编写工作很棘手但要演示这一点很难。看起来有了清晰的问题描述后我们就找到了简洁的解决方案但实际上绝不可能如此简单需要反复试验开始会遭遇失败还常常需要推倒重来。通过编写程序你将逐渐学会合并使用各种技术的最佳方式并了解哪些解决方案通常对解决哪些问题行之有效。11.1 问题描述受邀为解决重要问题而编写程序时编程新手常常不知道从何处着手。至少从笼统的角度说答案很简单编写大型程序时先得明白要解决的问题。这看似简单但未能正确认识要解决的问题是极其常见的编程错误。有时候编写程序之所以很难是因为你没有真正明白自已要做什么。本章要解决的问题是计算并打印有关文本文件内容的统计数据。我们想知道给定文本文件包含多少个字符、行和单词。除单词外我们还想知道在文件中出现次数最多的前10个单词并按出现次数排列它们。来看一个包含一小段文本的例子。A long time ago, in a galaxy far away...它包含如下内容一行文本。我们假定换行字符(\n)被用于标识行尾且不为空的文本文件至少包含一行。46个字符包括空格和标点在内。总共10个单词。然而不同的单词只有8个因为far和A都出现了两次在Python解释器中进行试验很有帮助例如正如你看到的函数len指出这个字符串包含46个字符函数split将字符串划分为单词——如果忽略末尾...字符串s总共包含10个单词。如果仔细查看split返回的单词列表将发现单词far出现了两次但split将它们视为两个不同的字符串far,末尾有逗号和far(没有逗号)。同样A和a也是相同的单词只是大小写不同。为处理上述细节我们将精确地定义字符串为单词的含义单词是包含一个或多个字符的字符串其中每个字符都必须是小写字母a~z。我们将忽略非字母字符如数字和标点并将大写字母转换为小写。因此前面的示例如下。原始文本A long time ago, in a galaxy far, far away ...修改后的文本a long time ago, in a galaxy far, far away通过将修改后的句子划分为单词得到的结果更准确要计算不同的单词数可将列表转换为集合本书前面介绍过集合不存储重复的值删除非字母字符存在一些缺点。首先字符数不对因为有些字符被删除。但我们可采取这样应对的措施即在修改前计算字符数。其次对于有些单词没有将其标点符号删除的好办法例如该如何 处理I‘d中的撇号呢如果将其删除并将I转换为小写如果将为id与原来的单词不同。如果将撇号替换为空格结果将为I和d——一个是单词一个不是。为解决这个问题我们将撇号还有连字符视为字母。第三改变大小定可能改变单词的含义。例如将有些名字的首字母小定后它将变成单词如Polish(polish)和Bonnie(bonnie)。我们不考虑这个问题因为它看起来并非什么大问题。11.2 保留想要的字母接下来考虑如何自动将字符串转换成所需的格式。将字符串转换为小写很容易如下所示。删除不想要的字符有点棘手一种办法是使用字符串函数replace将不要的字符替换为空字符例如这种做法的问题在于需要调用replace很多次每种不需要的字符一次。相比于要保留的字符要删除的字符多得多因此这种做法的效率极低。一种更佳的方法是保留想要的字母例如#keep.py #包含所有要保留的字符的集合 keep {a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, , -, } def normalize(s): Convert s to a normalized string. result for c in s.lower(): if c in keep: result c return result运行这个函数以每次一个字符的方式遍历字符串s仅当字符包含在要保留的字符集合中时才将其附加到 result末尾。11.3 使用大型数据文件测试代码我们编写的代码不多但足够做一些有用的实验。在下面的示例中我们将使用文件PythonStandardLibraryCoreModules1999.txt作为测试文件要处理文本文件方式之一是将整个文件作为一个字符串读取到内存中。下面在解释器中手工完成这项任务。从输出可知这个文件包含82651个字符 2210行 12224个单词下面将所有代码放在一个函数中以自动完成这项任务#filecount.py #包含所有要保留的字符的集合 keep {a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, , -, } def normalize(s): Convert s to a normalized string. result for c in s.lower(): if c in keep: result c return result def file_stats(fname): Print statistics for the given file. s open(fname, r).read() num_chars len(s) num_lines s.count(\n) num_words len(normalize(s).split()) print(The file %s has: % fname) print( %s characters % num_chars) print( %s lines % num_lines) print( %s words % num_words)运行如下11.4 找出出现次数较多的单词来考虑如下问题找出文本文件中出现次数较多的单词。这里的解决方案是创建一个字典其中的键为单词值为单词在文件中出现的次数。例如对于前面的示例文本经过规范化a long time ago in a galaxy far far away各个单词出现的次如下a: 2 long: 1 time: 1 ago: 1 in: 1 galaxy: 1 far: 2 away: 1如果我们将上述内容转换为一个Python字典结果钭类似于下面这样d { a: 2, long: 1 time: 1 ago: 1 in: 1 galaxy: 1 far: 2 away: 1 }可从这个字典提取很多有用的信息。d.keys()是一个列表包含文件中所有不同的单词len(d.keys())是文件中不同的单词数sum(d[k] for k in d)是d中所有值之和即文件包含的单词总数(包括重复的单词)。sum是一个Python内置函数返回序列的总和。字典存储的数据未经排序因此要获取一个清单按出现次数从高到低的顺序列出所有单词需要将字典转换为元组列表如下所示。#findword.py 找出出现次数较多的单词 d { a: 2, long: 1, time: 1, ago: 1, in: 1, galaxy: 1, far: 2, away: 1 } ​ def findword(): lst [] for k in d: pair (d[k], k) lst.append(pair) print(lst) lst.sort() #排序 print(lst) lst.reverse() #逆序 print(lst)其中的for循环将字典d转换为由元组(count, word)组成的列表。经过这样的转换后就可使用列表函数sort按出现次数对数据排序。默认情况下函数sort按从小到大的顺序排列数据因为我们反转列表的排列顺序将出现次数最多的单词通常也是我们最感兴趣的单词放在列表开头。将lst中的单词按出现次数从高到低排列后就可使用切片来获取出现次数最多的3个单词print(lst[:3])如果要让输出更整洁可以这样做#findword.py 找出出现次数较多的单词 d { a: 2, long: 1, time: 1, ago: 1, in: 1, galaxy: 1, far: 2, away: 1 } def findword(): lst [] for k in d: pair (d[k], k) lst.append(pair) print(lst) lst.sort() #排序 print(lst) lst.reverse() #逆序 #print(lst) #print(lst[:3]) for count, word in lst: print(%4s %s % (count, word))注意到在每个单词的出现次数前面都有3个空格。这是因为print语句包含格式命令%4s它让数字在宽度为4的字段中右对齐。只要没有单词出现的次数达到或超过1000这就可确保出现次数完全对齐。11.5 将字符串转换为次数字典下面来编写一个函数它接受字符串s并生成一个字典该字典的键为s中的单词值为单词出现的次数#stringToDict.py #包含所有要保留的字符的集合 keep {a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, , -, } def normalize(s): Convert s to a normalized string. result for c in s.lower(): if c in keep: result c return result def make_freq_dict(s): Returns a dictionary whose keys are the words of s, dnd whose values are the counts of those words. s normalize(s) words s.split() d {} for w in words: if w in d: #看到w出现过 d[w] 1 else: d[w] 1 return d运行这个函数遍历字符串s的每个单词并将其加入到字典中。如果w是包含在d中的键if语句if w in d 的条件将为True否则为False。如果w是包含在d中的键则说明w出现过因此将其出现次数加1如果w未包含在d中就使用语句d[w] 1将其作为新键加入到字典中。组织在一起现在万事俱备可以编写一个函数计算并显示给定文本文件的统计数据#countfileword.py #包含所有要保留的字符的集合 keep {a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, , -, } def normalize(s): Convert s to a normalized string. result for c in s.lower(): if c in keep: result c return result def make_freq_dict(s): Returns a dictionary whose keys are the words of s, dnd whose values are the counts of those words. s normalize(s) words s.split() d {} for w in words: if w in d: #看到w出现过 d[w] 1 else: d[w] 1 return d def print_file_stats(fname): Print statistics for the given file. s open(fname, r).read() num_chars len(s) #在规范化s之前计算 num_lines s.count(\n) #在规范化s之前计算 d make_freq_dict(s) num_words sum(d[w] for w in d) #计算s包含多少个单词 #创建一个列表其中的元素由出现次数和单词组成的元组 #并按出现次数从高到低排列 lst [(d[w], w) for w in d] lst.sort() lst.reverse() print(The file %s has: % fname) print( %s characters % num_chars) print( %s lines % num_lines) print( %s words % num_words) print(\nThe top 10 most frequent words are:) i 1 # i为列表元素编号 for count, word in lst[:10]: print(%2s. %4s %s % (i, count, word)) i 1运行练习1.修改函数print_file_stats,使其也打印文件中不同的单词总数。2.修改函数print_file_stats,使其打印文件中单词的平均长度。3.罕用语hapax hegomenon是在文件中只出现过一次的单词。请修改函数print_file_stats使其打印罕用语总数。4。前面说过文件bill.txt中出现频率最高的10个单词都是功能词如the和and。我们通常对这些单词不感兴趣因此我们可创建一个排除词(stop word)集合其中包含要忽略的所有单词。在函数print_file_stats中新增一个名为stop_words的变量如下所示stop_words {the, and, i, to, of, a, you, my, that, in}当然你可根据自已的喜好修改排除词集合。现在修改程序的代码在计算所有统计数据时都将stop_list中的单词排除在外。5.较难函数print_file_stats将一个文件名作为输入并将整个文件都读取到一个字符串变量中。这种做法的问题在于如果文件很大将整个文件都放在一个字符串变量中将占用大量内存。另一种做法是以每次一行的方式读取文件这样占用的内存通常少得多。请编写一个名为print_file_stats_lines的新函数其功能与print_file_stats完全相同但逐行读取输入文件。使用相同的文件调用这两个函数时它们的输出应该相同。#countwordpractice.pyfrom time import strftime, localtime #时间相关的模块import datetime #获取系统时间​#包含所有要保留的字符的集合keep {a, b, c, d, e,f, g, h, i, j,k, l, m, n, o,p, q, r, s, t,u, v, w, x, y,z, , -, }def normalize(s):Convert s to a normalized string.result for c in s.lower():if c in keep:result creturn result​def make_freq_dict(s):Returns a dictionary whose keys are the words of s,dnd whose values are the counts of those words.s normalize(s)words s.split()d {}for w in words:if w in d: #看到w出现过d[w] 1else:d[w] 1return d​def print_file_stats(fname):Print statistics for the given file.start datetime.datetime.now()s open(fname, r).read()num_chars len(s) #在规范化s之前计算num_lines s.count(\n) #在规范化s之前计算d make_freq_dict(s)#print(print_file_statsd, d)num_words sum(d[w] for w in d) #计算s包含多少个单词#创建一个列表其中的元素由出现次数和单词组成的元组#并按出现次数从高到低排列lst [(d[w], w) for w in d]lst.sort()lst.reverse()print(The file %s has: % fname)print( %s characters % num_chars)print( %s lines % num_lines)print( %s words % num_words)end datetime.datetime.now()print(end - start) #打印运行时间#returnprint(\nThe top 10 most frequent words are:)i 1 # i为列表元素编号for count, word in lst[:10]:print(%2s. %4s %s % (i, count, word))i 1​#1.打印文件中不同的单词总数。nWordCount len(lst)print(1.打印文件中不同的单词总数 word count %d % nWordCount)#print(lst)#2.打印文件中单词的平均长度。#[(12, the), (10, modules), (8, of), (6, are)...]#(count, word)是一组键值对# | |# 12 thenCharCount 0for count, word in lst:#print(%2s. %4s %s % (i, count, len(word)))#i 1nCharCount len(word)print(2.打印文件中单词的平均长度 average word length %d % (nCharCount / nWordCount))#罕用语hapax hegomenon是在文件中只出现过一次的单词。#请修改函数print_file_stats使其打印罕用语总数hapax 0for count, word in lst:if count 1: #单词数大于1过滤掉continuehapax 1print(3.打印文件中打印罕用语总数hapax hegomenon %d % hapax)#4。前面说过文件bill.txt中出现频率最高的10个单词都是功能词如the和and。#我们通常对这些单词不感兴趣因此我们可创建一个排除词(stop word)集合#其中包含要忽略的所有单词。在函数print_file_stats中新增一个名为stop_words的变量#如下所示stop_words {the, and, i, to, of, a, you, my, that, in}#当然你可根据自已的喜好修改排除词集合。现在修改程序的代码在计算所有统计数据时#都将stop_list中的单词排除在外。validWord [] #创建空的列表j 0for count, word in lst:if not (word in stop_words):#print(validWord[%3s] %4s %s % (j, count, word))j 1validWord.append(word) #如果单词不在stop_words里添加到有效单词中print(4.有效单词个数 validWard %d % len(validWord))​5.较难函数print_file_stats将一个文件名作为输入并将整个文件都读取到一个字符串变量中。这种做法的问题在于如果文件很大将整个文件都放在一个字符串变量中将占用大量内存。另一种做法是以每次一行的方式读取文件这样占用的内存通常少得多。请编写一个名为print_file_stats_lines的新函数其功能与print_file_stats完全相同但逐行读取输入文件。使用相同的文件调用这两个函数时它们的输出应该相同。def print_file_stats_lines(fname):Print statistics for the given file.#with open(fname, r) as file_object:# contents file_object.read()# print(contents)start datetime.datetime.now()#print(strftime(%Y-%m-%d %H:%M:%S, localtime())) # 打印当前时间#逐行读取num_lines 0num_chars 0d {}s with open(fname, r) as file_object:lines file_object.readlines()for line in lines:#print(line[%3s]%s % (num_lines, line))num_lines 1num_chars len(line)#d make_freq_dict(line)#s normalize(line)for c in line.lower():if c in keep:s c​words s.split()for w in words:if w in d: #看到w出现过d[w] 1else:d[w] 1#print(print_file_stats_linesd, d)num_words sum(d[w] for w in d) #计算s包含多少个单词#print(num_words%d % num_words)#创建一个列表其中的元素由出现次数和单词组成的元组#并按出现次数从高到低排列lst [(d[w], w) for w in d]lst.sort()lst.reverse()print(The file %s has: % fname)print( %s characters % num_chars)print( %s lines % num_lines)print( %s words % num_words)end datetime.datetime.now()print(end - start) #打印运行时间#returnprint(\nThe top 10 most frequent words are:)i 1 # i为列表元素编号for count, word in lst[:10]:print(%2s. %4s %s % (i, count, word))i 1​#1.打印文件中不同的单词总数。nWordCount len(lst)print(1.打印文件中不同的单词总数 word count %d % nWordCount)#print(lst)#2.打印文件中单词的平均长度。#[(12, the), (10, modules), (8, of), (6, are)...]#(count, word)是一组键值对# | |# 12 thenCharCount 0for count, word in lst:#print(%2s. %4s %s % (i, count, len(word)))#i 1nCharCount len(word)print(2.打印文件中单词的平均长度 average word length %d % (nCharCount / nWordCount))#罕用语hapax hegomenon是在文件中只出现过一次的单词。#请修改函数print_file_stats使其打印罕用语总数hapax 0for count, word in lst:if count 1: #单词数大于1过滤掉continuehapax 1print(3.打印文件中打印罕用语总数hapax hegomenon %d % hapax)#4。前面说过文件bill.txt中出现频率最高的10个单词都是功能词如the和and。#我们通常对这些单词不感兴趣因此我们可创建一个排除词(stop word)集合#其中包含要忽略的所有单词。在函数print_file_stats中新增一个名为stop_words的变量#如下所示stop_words {the, and, i, to, of, a, you, my, that, in}#当然你可根据自已的喜好修改排除词集合。现在修改程序的代码在计算所有统计数据时#都将stop_list中的单词排除在外。validWord [] #创建空的列表j 0for count, word in lst:if not (word in stop_words):#print(validWord[%3s] %4s %s % (j, count, word))j 1validWord.append(word) #如果单词不在stop_words里添加到有效单词中print(4.有效单词长度 length %d % len(validWord))​11.8 最终的程序最终的程序代码如下#wordstats.py #包含所有要保留的字符的集合 keep {a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, , -, } def normalize(s): Convert s to a normalized string. result for c in s.lower(): if c in keep: result c return result def make_freq_dict(s): Returns a dictionary whose keys are the words of s, and whose values are the counts of those words. s normalize(s) words s.split() d {} for w in words: if w in d: #如果w出现过就将其出现次数加1 d[w] 1 else: d[w] 1 #如果w是第一次出现就将其出现次数设置为1 return d def print_file_stats(fname): Print statistics for the given file. s open(fname, r).read() num_chars len(s) #在规范化s之前计算字符数 num_lines s.count(\n) #在规范化s之前计算行数 d make_freq_dict(s) num_words sum(d[w] for w in d) #计算s包含多少个单词 #创建一个列表其中的元素由出现次数和单词组成的元组 #并按出现次数从高到低排列 lst [(d[w], w) for w in d] lst.sort() lst.reverse() #在屏幕上打印结果 print(The file %s has: % fname) print( %s characters % num_chars) print( %s lines % num_lines) print( %s words % num_words) print(\n The top 10 most frequent words are:) i 1 #i为列表元素编号 for count, word in lst[:10]: print(%2s. %4s %s % (i, count, word)) i 1 def main(): print_file_stats(bill.txt) ​ if __name__ __main__: main()运行
返回列表