from collections import defaultdict
import pandas as pd
text_list =['france','spain','spain beaches','france beaches','spain best beaches']
word_freq = defaultdict(int)for text in text_list:for word in text.split():
word_freq[word]+=1
pd.DataFrame.from_dict(word_freq, orient='index') \
.sort_values(0, ascending=False) \
.rename(columns={0:'abs_freq'})技术支持
# default value is now a list with two ints
word_freq = defaultdict(lambda:[0,0])# the `views` column you had in the first DataFrame
num_list =[200,180,170,160,160]# looping is now over both the text and the numbersfor text, num inzip(text_list, num_list):for word in text.split():# same as before
word_freq[word][0]+=1# new line, incrementing the numeric value for each word
word_freq[word][1]+= num
columns ={0:'abs_freq',1:'wtd_freq'}
abs_wtd_df = pd.DataFrame.from_dict(word_freq, orient='index') \
.rename(columns=columns) \
.sort_values('wtd_freq', ascending=False) \
.assign(rel_value=lambda df: df['wtd_freq']/ df['abs_freq']) \
.round()
abs_wtd_df.style.background_gradient(low=0, high=.7, subset=['rel_value'])技术支持
final_list =[]for i inrange(1,156):ifnot i%10:print(i)
page ='http://www.boxofficemojo.com/alltime/domestic.htm?page='+str(i)+'&p=.htm'
resp = requests.get(page)
soup = BeautifulSoup(resp.text,'lxml')# trial and error to get the exact positions
table_data =[x.text for x in soup.select('tr td')[11:511]]# put every 5 values in a row
temp_list =[table_data[i:i+5]for i inrange(0,len(table_data[:-4]),5)]for temp in temp_list:
final_list.append(temp)技术支持
na_year_idx =[i for i, x inenumerate(final_list)if x[4]=='n/a']# get the indexes of the 'n/a' values
new_years =[1998,1999,1960,1973]# got them by checking onlineprint(*[(i, x)for i, x inenumerate(final_list)if i in na_year_idx], sep='\n')print('new year values:', new_years)技术支持
(8003,['8004','Warner Bros. 75th Anniversary Film Festival','WB','$741,855','n/a'])(8148,['8149','Hum Aapke Dil Mein Rahte Hain','Eros','$668,678','n/a'])(8197,['8198','Purple Moon (Re-issue)','Mira.','$640,945','n/a'])(10469,['10470','Amarcord','Jan.','$125,493','n/a'])newyearvalues:[1998,1999,1960,1973]技术支持
for na_year, new_year inzip(na_year_idx, new_years):
final_list[na_year][4]= new_year
print(final_list[na_year], new_year)技术支持
['8004','Warner Bros. 75th Anniversary Film Festival','WB','$741,855',1998]1998['8149','Hum Aapke Dil Mein Rahte Hain','Eros','$668,678',1999]1999['8198','Purple Moon (Re-issue)','Mira.','$640,945',1960]1960['10470','Amarcord','Jan.','$125,493',1973]1973技术支持
现在,将该列表转换为 pandas 的 DataFrame,为列命名,并转换为需要的数据类型。
import re
regex ='|'.join(['\$',',','\^'])
columns =['rank','title','studio','lifetime_gross','year']
boxoffice_df = pd.DataFrame({'rank':[int(x[0])for x in final_list],# convert ranks to integers'title':[x[1]for x in final_list],# get titles as is'studio':[x[2]for x in final_list],# get studio names as is'lifetime_gross':[int(re.sub(regex,'', x[3]))for x in final_list],# remove special characters and convert to integer'year':[int(re.sub(regex,'',str(x[4])))for x in final_list],# remove special characters and convert to integer})print('rows:', boxoffice_df.shape[0])print('columns:', boxoffice_df.shape[1])print('\ndata types:')print(boxoffice_df.dtypes)
boxoffice_df.head(15)技术支持
rows:15500columns:5
data types:
lifetime_gross int64
rank int64
studio object
title object
year int64
dtype: object
技术支持
# words will be expandeddefword_frequency(text_list, num_list, sep=None, rm_words=('the','and','a')):
word_freq = defaultdict(lambda:[0,0])for text, num inzip(text_list, num_list):for word in text.split(sep=sep):# This should take care of ignoring the word if it's in the stop wordsif word.lower()in rm_words:continue# .lower() makes sure we are not duplicating words
word_freq[word.lower()][0]+=1
word_freq[word.lower()][1]+= num
columns ={0:'abs_freq',1:'wtd_freq'}
abs_wtd_df =(pd.DataFrame.from_dict(word_freq, orient='index').rename(columns=columns ).sort_values('wtd_freq', ascending=False).assign(rel_value=lambda df: df['wtd_freq']/ df['abs_freq']).round())
abs_wtd_df.insert(1,'abs_perc', value=abs_wtd_df['abs_freq']/ abs_wtd_df['abs_freq'].sum())
abs_wtd_df.insert(2,'abs_perc_cum', abs_wtd_df['abs_perc'].cumsum())
abs_wtd_df.insert(4,'wtd_freq_perc', abs_wtd_df['wtd_freq']/ abs_wtd_df['wtd_freq'].sum())
abs_wtd_df.insert(5,'wtd_freq_perc_cum', abs_wtd_df['wtd_freq_perc'].cumsum())
abs_wtd_df = abs_wtd_df.reset_index().rename(columns={'index':'word'})return abs_wtd_df技术支持
import matplotlib.pyplot as plt
plt.figure(figsize=(20,8))
plt.subplot(1,2,1)
word_freq_df_abs = word_freq_df.sort_values('abs_freq', ascending=False).reset_index()
plt.barh(range(20),list(reversed(word_freq_df_abs['abs_freq'][:20])), color='#288FB7')for i, word inenumerate(word_freq_df_abs['word'][:20]):
plt.text(word_freq_df_abs['abs_freq'][i],20-i-1,
s=str(i+1)+'. '+ word +': '+str(word_freq_df_abs['abs_freq'][i]),
ha='right', va='center', fontsize=14, color='white', fontweight='bold')
plt.text(0.4,-1.1, s='Number of times the word was used in a movie title; out of 15500 movies.', fontsize=14)
plt.text(0.4,-1.8, s='Data: boxofficemojo.com Apr. 2018. Feedback: @eliasdabbas', fontsize=14)
plt.vlines(range(0,210,10),-1,20, colors='gray', alpha=0.1)
plt.hlines(range(0,20,2),0,210, colors='gray', alpha=0.1)
plt.yticks([])
plt.xticks([])
plt.title('Words Most Used in Movie Titles', fontsize=22, fontweight='bold')# =============
plt.subplot(1,2,2)# plt.axis('off')
plt.barh(range(20),list(reversed(word_freq_df['wtd_freq'][:20])), color='#288FB7')for i, word inenumerate(word_freq_df['word'][:20]):
plt.text(word_freq_df['wtd_freq'][i],20-i-1,
s=str(i+1)+'. '+ word +': '+'$'+str(round(word_freq_df['wtd_freq'][i]/1000_000_000,2))+'b',
ha='right', va='center', fontsize=14, color='white', fontweight='bold')
plt.text(0.4,-1.1, s='Alltime boxoffice revenue of all movies whos title contained the word. (Top word is "2") ', fontsize=14)
plt.text(0.4,-1.8, s='Data collection & methodology: http://bit.ly/word_freq', fontsize=14)
plt.vlines(range(0,9_500_000_000,500_000_000),-1,20, colors='gray', alpha=0.1)
plt.hlines(range(0,20,2),0,10_000_000_000, colors='gray', alpha=0.1)
plt.xlim((-70_000_000,9_500_000_000))
plt.yticks([])
plt.xticks([])
plt.title('Words Most Associated With Boxoffice Revenue', fontsize=22, fontweight='bold')
plt.tight_layout(pad=0.01)
plt.show()技术支持
文本挖掘中的一组重要指标与某个语料库(文本文档集合)中词语(或任意标记)的出现频率相关。不过,当每篇文档都带有一个用于描述某个属性的数值时,您还可以使用另一组附加指标。
一些示例:
在本教程中,
collections模块的defaultdict数据结构完成主要计算,并使用 pandas 的DataFrame管理最终输出。绝对词频与加权词频:简介
假设您有两条推文,它们的内容与展示量(浏览量)如下:
做基础分析很简单,您会发现词语在“france”和“spain”之间以 50:50 分布。很多情况下,我们只有这些信息,只能衡量词语的绝对频率,并据此推断某些关系。在本例中,您还掌握了每条文档的一些数据。
此处的加权频率显然不同,分布为 80:20。换言之,尽管“spain”和“france”在您的推文中各出现了一次,但从读者的视角看,前者出现了 800 次,而后者出现了 200 次。差别很大!
使用
defaultdict计算简单词频现在来看一个稍微复杂些的相似文档示例:
您现在遍历这些文档,将其切分为单词,并统计每个单词的出现次数:
在上面的循环中,第一行逐一遍历
text_list。第二行(在每个文档内)遍历按空格分隔的各个词(当然也可以是其他分隔符,如“—”、“,”、“_” 等)。当您尝试给
word_freq[word]赋值时,有两种可能:word已存在:此时直接完成赋值(加一)。word不在word_freq中:此时defaultdict会调用其在定义时指定的默认函数,这里为int。调用
int会返回 0。此时键已存在,其值为 0,接下来即可在其基础上再加 1。尽管在第一张表中“france”是出现最多的词,但在统计每个文档中的所有词后,我们可以看到“spain”和“beaches”并列第一。这对于揭示隐藏趋势很重要,尤其是当您处理的文档列表有成千上万条时。
加权词频
现在您已经统计了语料库中每个词的出现次数,接下来希望查看加权频率。也就是说,您想看到相对于您使用这些词的次数,它们在读者眼中出现了多少次。
在第一张表中,词语的绝对频率在“spain”和“france”之间平均分配,但“spain”的权重大得多,因为其数值为 800,而“france”为 200。
那么,对于第二张略微复杂的表,加权词频会是什么样?
让我们来找出答案!
您可以复用上面的部分代码,但需要做一些补充:
一些观察:
rel_value是一个简单的除法,用于得到每次出现对应的数值。rel_value,您会看到,尽管“france”在wtd_freq指标上相对较低,但其每次出现的价值较高,似乎具有潜力。例如,这可能提示您可以增加对“france”的内容覆盖。您或许还想加入其他指标,展示各类频率的百分比与累计百分比,这样可以更好地了解是否有少数词语占据了总量的大头:
可以做的分析还有很多,数据越多,惊喜通常也越多。
那么,在真实世界的真实数据中会是什么样?
您将查看电影片名,看看哪些词在片名中使用最多(即绝对频率),以及哪些词与最高票房相关(即加权频率)。
Boxoffice Mojo 提供了 15,000 多部电影的列表,以及对应的总票房和排名。先使用
requests和BeautifulSoup抓取数据——如果您愿意,也可以先在此处浏览 Boxoffice Mojo:您会看到某些数值包含特殊字符(
$、,和^),且部分值实际上为N/A。因此需要进行处理:现在,将该列表转换为 pandas 的
DataFrame,为列命名,并转换为需要的数据类型。“star”是高频词之一,因为它出现在前十五部电影中的五部之中,而且我们也知道《星球大战》系列还有更多影片,其中几部也位居前列。
现在让我们利用您编写的代码,看看它在该数据集上的表现。下面的代码并无新内容,我们只是把它封装成了一个函数:
不出所料,“停用词”位居前列,这在大多数文档集合中都差不多。您还会发现重复项,一些为首字母大写,一些不是。因此有两件事需要处理:
下面是对函数的简单更新(新增
rm_words参数,以及第 6、7、8 行):让我们基于
abs_freq对同一 DataFrame 排序查看:现在让我们进行可视化,对比两者并寻找隐藏趋势:
至少在制片人和编剧的心中,爱似乎确实无往不胜!它是所有电影片名中最常用的词。不过在加权频率(票房)方面,它并不靠前。
换句话说,如果您查看所有电影的片名,“love”将是您最常见到的词。而若用总票房作为度量来估计在观众眼中出现最多的词,那么“2”、“star”和“man”则是出现最多或与最高票房相关的词。
需要说明的是:这些都是非常简单的计算。当我们说“love”的加权频率为 1,604,106,767 时,仅表示所有片名包含“love”的电影的总票房之和就是这个数值。
“2”位居榜首也很有意思。显然它并不是一个词,但这表明系列电影的第二部累计票房非常可观。“3”位居第五位,情况类似。注意“part”和“ii”也进入前十,进一步印证了这一点。
“American”和“movie”的相对值较高。
关于函数中停用词的一点说明
通常,您会提供比这里更全面的停用词列表,尤其是在处理文章或社交媒体帖子时。例如,
nltk包提供了多种语言的停用词列表,可下载使用。这里的词是在检查部分热门电影后选定的。许多词通常被视为停用词,但在电影片名的场景中,保留部分词可能更有意义,因为它们能提供一些洞见。比如,“I”“me”“you”可能暗示一些社会关系动态。另一个原因是电影片名本身很短,我们希望尽可能从中提炼信息。
您完全可以尝试使用自己的停用词集合,并看到略有不同的结果。
回看原始的电影片名列表,我们会发现一些最常用的词甚至未出现在前十,这正是我们希望通过这种方法揭示的洞见。
接下来,进一步探索一些有意思的高频词可能更有意义。我们先筛选包含“2”的电影看看:
我们也来看看“star”相关的热门电影:
最后,看看“man”相关的热门电影:
后续步骤与改进
首先,您可以尝试获取更多词语:电影片名极短,很多时候并不能传达词语的字面含义。例如,godfather(教父)原本应指在洗礼中作证并承诺照顾孩子的人(抑或是一位嗜杀成性的黑手党?!)。
进一步的练习可以是在电影片名之外,获取更详细的描述。例如:
比起“The Matrix”,这段话传达的信息要丰富得多。
或者,您也可以沿以下路径之一来丰富分析:
您也许会对自行探索数据以及其他数据感兴趣:
好的,如今您已经探索了电影片名中的词语计数,了解了绝对频率与加权频率之间的差异,以及忽略其中任何一个如何可能错失全貌的一大块内容。您也看到了这种方法的局限性,并获得了一些改进分析的建议。
您完成了创建一个专用函数的过程,可轻松用于分析任何带有数值的相似文本数据集,也理解了这能如何提升对这类数据集的认识。
不妨试试分析您的推文表现、网站 URL、Facebook 帖子,或您遇到的任何类似数据集。
直接克隆该仓库获取代码,亲自尝试或许更便捷。
word_frequency函数已收录在 advertools 包中,您可以下载并在工作/研究中试用。欢迎查看并告诉我您的想法!@eliasdabbas
查看 DataCamp 的Python 字典教程以学习如何创建词频,以及Python 中的网页抓取与 NLP教程以学习如何绘制词频分布。