大家在抖音,头条上都见过那种随着时间变化,排位不断变化的柱形动态图,比如全世界各国高铁总里程伴随年份增长的那种。最近庆余年大火,然后想到能不能用python把整本书中大部分的人物给抓出来,然后分析一下,随着情节的发展,这些人出现的状况是个什么样子。
废话不多说,我们开始,思路大概是这样的:
1. 如何实现动态柱状图?
经过查询,发现GitHub上的这个模板:
https://github.com/Jannchie/Historical-ranking-data-visualization-based-on-d3.js
此模板用起来特别方便,如果没有特别的要求的话,把数据加载进去就能用,使用的数据格式为csv, 结构为:
name,type,value,date
name1, type1, 12000, 2013-12-31
name2, type2, 10000, 2013-12-31
name3, type3, 10000, 2013-12-31
name1, type1, 12300, 2014-12-31
name2, type1, 12040, 2014-12-31
name3, type1, 12001, 2014-12-31
OK,那我们需要的格式就应该为
name, type, value, page
其中,name为角色名称,type根据作者说,是变化颜色的,我们这里面就给他一个简介的含义,value和page比较麻烦,我们细说:
page - 庆余年全书大概有三百八十万以上的字数,这样我们按照36000字分一页,大概能分出100页左右。那么value就是截止到此页,该人物一共出现了多少次。
这个概念弄懂了,那么我们的程序最后应该生成的数据大概长这样:
name,type,value,date
叶流云,南庆大宗师父,10,1
陈萍萍,监察院长,6,1
苦荷,北齐大宗师,6,1
太子,太子,4,1
范闲,本书男主,249,1
影子,陈萍萍护卫,1,1
皇帝,书中皇帝,26,1
范建,主角他爹,53,1
范若若,范闲妹妹,2,1
范建,主角他爹,98,2
叶流云,南庆大宗室,12,2
柳思思,范闲妾,18,2
洪四庠,太后太监,2,2
影子,陈萍萍护卫,3,2
OK搞懂了我们继续
2 开始分词
在这里我们使用 结巴分词
jieba里面有个方法
jieba.tokenize(text)
这个方法会把每一个分出来的词,在文章中出现的位置,结束的位置都返回来,这样的话,通过每个分出来的人在文章中出现的启示位置就可以知道这个人出现在“按照36000字分一页”这个分法的第几页。
好好理解这句话,然后我们上代码。这里需要注意的是:
- 我们加载了一个字典,这个字典是干啥用的呢?就是定义了我们想要看到的人名,除了这些人以外其他分出来的词就不要了,免得什么小太监,士兵甲乙丙,宫女子丑寅都进来。
- 我们将最后分出来的词,以字典的方式存到一个列表中,最后这个列表会用pandas生成dataframe进行下一步操作
# 定义分页方法
def page_generate(book, wordsPerPage):
page_list = []
n = 0
for i in range(0, len(book), wordsPerPage):
n = n+1
pageInfo = {
'page_number':n,
'start':i,
'end':i+wordsPerPage
}
page_list.append(pageInfo)
return page_list
# 定义分词方法
def get_words_details(text, dict_url):
jieba.load_userdict(dict_url)
results = jieba.tokenize(text)
final_info = []
for result in results:
name = result[0]
location = result[1]
pagenumber = 0
for subpage in page_list:
if location >= subpage['start'] and location <= subpage['end']:
pagenumber = subpage['page_number']
worddetail = {
'name':name,
'location':location,
'page': pagenumber
}
final_info.append(worddetail)
return final_info
3 主程序
# 定义书路径
bookurl = 'C:\\Users\\zhigang.zhang\\Downloads\\qyn.txt'
# 定义字典路径
dict_url = 'C:\\Users\\zhigang.zhang\\Documents\\Python Scripts\\dict.txt'
# 打开书,读取内容为str
file = open(bookurl, 'r')
text = file.read()
# 生成每100个字为一页的页面信息,包含每一页第一个字和最后一个字在整本书里面的位置
page_list = page_generate(text, 36000)
# subtext_list = cut_book(text, 100)
#利用jieba分词进行分词,读取自定义字典提高分词精确性
print('分词开始')
final_info = get_words_details(text, dict_url)
print('分词结束')
# 将分此后每个词出现的位置读取成list
print('分页统计开始')
l = [final_info[i]['location'] for i in range(0, len(final_info))]
print('分页统计结束')
# 生成pandas dataframe, index为每个词出现位置
df = pd.DataFrame(final_info, index = l)
print('生成dataframe')
#读取用户字典里人名, 生成list,将分词后的数据进行过滤,只保留字典中存储的人名数据
file2 = open(dict_url, 'r')
role_list = file2.read().splitlines()
df2 = df[df['name'].isin(role_list)]
好的,到这里以你多年print('hello world')的功力,基本都能看懂了。需要注意的是最后两行:
我们过滤出 只有用户字典里面定义的 人物
然后是比较关键的两步
- 如果我们的字典里只是单纯包含了范闲,五竹,那就会漏掉很多关键的同义词,因此我们再创建一个字典,把提司大人,小范大人,这样的词都替换成范闲,老五,五大人,瞎子这样的词也都替换成五竹,这样的话得到的数据会更加精确(不要杠精,我这个也不是都准)
#部分字典内容
nickname = {'范慎':'范闲',
'提司大人':'范闲',
'范提司':'范闲',
'小范大人':'范闲',
'澹泊公':'范闲',
'庆帝':'皇帝',
'寡人':'皇帝',
'朕':'皇帝',
'陛下':'皇帝',
'户部尚书':'范建',
'户部侍郎':'范建',
'司南伯':'范建',
'伯爵':'范建',
'范侍郎':'范建',
'范尚书':'范建',
'五大人':'五竹',
'老五':'五竹',
'瞎子':'五竹',
'院长':'陈萍萍',
'跛子':'陈萍萍',
...
}
- 定义一个给dataframe 依据name列的值,赋予不同type值的方法。为啥要用一个方法?因为我们要用lamda,往后看
def get_type(x):
dic = {'范闲':'本书男主',
'叶轻眉':'主角母亲',
'皇帝':'书中皇帝',
'范建':'主角他爹',
'五竹':'机器人',
'陈萍萍':'监察院长',
'靖王':'王爷',
'李云睿':'长公主',
'林若甫':'范闲岳父',
...}
return dic.get(x)# x是key值,return一个对应的value出去
然后我们开始替换,利用dataframe的replace功能
df3 = df2.replace(nickname)
#按照name,page,对于出现次数进行统计
df4 = df3.groupby(['name', 'page']).count()
#dataframe进行group by之后,index发生改变,进行重设并重命名
df4.reset_index(inplace=True)
df5 = df4.rename(columns={'location':'numberOfAppearance'})
#################################################################
#新增一列,名为 totalAppear, 意为截至此页,这个人物 累计 出现多少次#
#################################################################
df5['totalAppear'] = df5.groupby(['name'])['numberOfAppearance'].cumsum()
#根据name列的值,新增type列并赋值
df5['type'] = df5['name'].apply(lambda x: get_type(x))
#因为只需要累加,所以去掉num of Apperaance 列
df5.drop(['numberOfAppearance'], axis=1)
#因为那个js工具只只支持标准顺序,所以我们把df的字段顺序调整一下,然后根据page字段排序,从小到大排列
order = ['name', 'type', 'totalAppear', 'page']
df6 = df5[order].sort_values(by = 'page')
#这个地方想实验一下重命名,就没有在js的那个config文件配置而是直接改df的字段名,所以最终字段名比较诡异
df7 = df6.rename(columns={'totalAppear':'value', 'page':'date'})
df7.to_csv('C:\\Users\\xxxxxx\\Downloads\\data.csv', index = False)
print('done')
好的,至此我们代码完成,10m多的庆余年加载进去,大概也就1,2分钟就能出来最终的csv文件
然后我们打开那个动态柱状图工具,把csv加载进去
最终结果在此
https://www.ixigua.com/i6774988371577012740/
通过本次分析,我们能够大概得出几个初步结论:
- 在整本书的前半部分,最关心范闲的是 皇帝,五竹,范建,他们几个在前十几页基本处于柱状图最上面
- 五竹的确是神龙见首不见尾,一会消失,一会又出现。
- 北齐圣女出现的次数,频率跟正房林婉儿差不多,难道这俩也是真爱?
-
究竟是谁和谁和谁们配范闲走到了最后?在整书的最后几万字里面,皇帝在那(就不剧透了), 院长在那(也不剧透了), 海棠朵朵力压林婉儿,五竹也在,捧哏王启年还在。
好了,还有更多的结论,你们自己研究吧。
所有代码我一会都放GitHub,什么时候更新上来看看再说了