红楼梦人物分词及可视化 |
您所在的位置:网站首页 › 四大名著地点 › 红楼梦人物分词及可视化 |
本文首先使用jieba分词提取出红楼梦人物及出现次数,然后使用pyecharts进行可视化。文本分词并非重点关注,主要是做pyecharts的可视化练习。 import pandas as pd import jieba import re from pyecharts.globals import CurrentConfig, OnlineHostType CurrentConfig.ONLINE_HOST = OnlineHostType.NOTEBOOK_HOST 分章节 def split_chapters(content, path): # 每章之间有五个空行分割 episodes = content.split('\n\n\n\n\n\n') # 回目标题存储 chapter_titles = [] for i in range(len(episodes)): # 去除多余字符 e = episodes[i].replace('\n\u3000\u3000','') # 提取回目标题,标题中间用空格隔开,最后一个替换是在某一回标题中单独出现的 title = re.search(r'回(.*?)\n', e).group().replace('回', '').replace('\u3000', ' ').replace('\n','').replace('\ue7a3',' ').strip() chapter_titles.append(title) # 提取内容,去除空白字符,标题与内容之间有一个空行分割 chapter_content = re.sub('[\s]', '', e.split('\n\n')[1]) # 保存 with open(path+str(i)+'.txt', 'w', encoding='utf-8') as f: f.write(title+'\n') f.write(chapter_content) f.close() return chapter_titles 分词,返回分词列表 def content_cut(content): # 分词,生成器变为列表 segments = jieba.cut(content, cut_all=False) segments = [s for s in segments] return segments 停止词 def get_stop_words(num, path): # 找到每一章节都出现的字作为停用字 stop_words = set() for i in range(num): # 读取内容 with open(path+str(i)+'.txt', 'r', encoding='utf-8') as f: words = content_cut(f.readlines()[1]) f.close() # 取交集 if i == 0: stop_words = set(words) else: stop_words &= set(words) stop_words = list(stop_words) # 添加停止词和自定义的红楼梦中的停止词 with open('stopwords.txt', 'r', encoding='gbk') as f1: lines = f1.readlines() for line in lines: stop_words.append(line.strip()) with open('红楼梦stopwords.txt', 'r', encoding='utf-8') as f2: lines = f2.readlines() for line in lines: stop_words.append(line.strip()) stop_words = list(set(stop_words)) return stop_words 统一人物名称有些是根据第一次分词结果添加的 maps = {'贾宝玉':['宝二爷','宝兄弟','怡红公子','绛洞花主','二哥哥','天魔星', '宝玉'], '贾母': ['史太君','老祖宗','老太太','贾母笑'], '林黛玉': ['颦颦','颦儿','林姑娘','林妹妹','潇湘妃子','黛玉'], '王熙凤': ['凤姐','琏二奶奶','凤辣子','凤哥儿' , '凤丫头','凤姐儿' ,'熙凤'], '薛宝钗': ['蘅芜君','宝姐姐','宝丫头' ,'宝姑娘','宝钗','宝钗笑','宝钗道'], '袭人': ['袭人道'], '史湘云': ['湘云'], '薛姨妈':['姨妈'], '贾雨村': ['雨村'], '小红': ['红玉','小红道','红玉道','红玉笑'], '紫鹃': ['鹦哥'], '贾元春': ['贵妃','元妃','元春'], '贾迎春':['二妹妹','迎春'], '贾探春': ['三妹妹','探春'], '贾惜春':['四妹妹','惜春'], '秦可卿': ['秦氏','可儿','可卿','秦氏笑'], '李纨': ['李宫裁'], '尤氏':['尤氏笑'], '贾蓉':['贾蓉笑', '贾蓉道'], '香菱': ['英莲'], '贾政':['贾政道','贾政笑'], '空空道人': ['那道', '道士', '道人','那道士'],'癞头和尚':['僧道', '那僧道','那僧笑'], '冷子兴':['子兴道','子兴','子兴笑'],'林如海':['如海','如海笑'] } def clear_words(segments, stop_words): for i in range(len(segments)): for k,v in maps.items(): if segments[i] in v: segments[i]=k # 删除停止词和一个字的 segments = [seg for seg in segments if seg not in stop_words and len(seg)>1] return segments 每回出现人物及次数,生成df character_list = ['贾宝玉','贾母','林黛玉','薛宝钗', '史湘云', '王熙凤', '巧姐', '王夫人', '邢夫人', '薛姨妈', '尤氏', '贾元春', '贾迎春', '贾探春','贾惜春','秦可卿', '尤二姐', '尤三姐', '李纨','妙玉','紫鹃','袭人','晴雯','麝月','茗烟','刑岫烟','平儿', '鸳鸯','玉钏儿','金钏儿','赵姨娘','周瑞家的','林之孝家的','小红','坠儿','芳官','龄官','藕官','翠缕', '夏金桂','宝蟾','香菱','甄士隐','甄宝玉','冷子兴','智能儿','娇杏','贾琏','贾政', '贾雨村','贾珍','薛蟠','薛蟠','薛蝌','贾蓉','贾蔷','贾瑞','贾芸','贾敬','贾赫', '秦钟','蒋玉菡','柳湘莲','焦大','贾环','贾兰','刘姥姥','空空道人','癞头和尚'] def count_character(chapter_num, path,titles, stop_words): char_df = pd.DataFrame() for i in range(chapter_num): # 章节内容加载,并分词 with open(path +str(i)+'.txt', 'r', encoding='utf-8') as f: words = content_cut(f.readlines()[1]) f.close() # 统一人称 words = clear_words(words, stop_words) # 在人名列表中的,并且出现大于1次的 characters = pd.value_counts([w for w in words if w in character_list and words.count(w)>1]) # 写入 df = pd.DataFrame(characters).reset_index().rename(columns={'index':'characters',0:'times'}) df.insert(0,'chapter_titles',titles[i]) if chapter_num==80: df.insert(0,'chapter',i+1) else: df.insert(0, 'chapter', i+81) char_df = char_df.append(df,ignore_index=True) return char_df 一 处理前八十回和后四十回的文本 with open('红楼梦前八十回.txt', 'r', encoding='utf-8') as f: content80 = f.read() print(content80[:100]) with open('红楼梦后四十回.txt', 'r', encoding='utf-8') as f: content40 = f.read() print(content40[:100])上卷 第一回 甄士隐梦幻识通灵 贾雨村风尘怀闺秀 更新时间:2006-7-26 11:43:00 本章字数:8717 此开卷第一回也.作者自云:因曾历过一番梦幻之后,故将 下卷 第八十一回 占旺相四美钓游鱼 奉严词两番入家塾 更新时间:2006-7-26 11:43:00 本章字数:7913 且说迎春归去之后,邢夫人象没有这事,倒是王夫人抚 # 加载红楼梦词库 jieba.load_userdict('红楼梦词库.txt')Building prefix dict from the default dictionary … Loading model from cache C:\Users\hp\AppData\Local\Temp\jieba.cache Loading model cost 1.079 seconds. Prefix dict has been built succesfully. # 分章节 chapter_titles80 = split_chapters(content80,'前八十回/') # 停止词 stop_words80 = get_stop_words(80, '前八十回/') # 分词,清理,人物出场次数生成df words80 = content_cut(content80) words80 = clear_words(words80, stop_words80) char_df80 = count_character(80, '前八十回/', chapter_titles80, stop_words80) char_df80.tail()chapter chapter_titles characters times 1068 80 美香菱屈受贪夫棒 王道士胡诌妒妇方 茗烟 6 1069 80 美香菱屈受贪夫棒 王道士胡诌妒妇方 薛宝钗 4 1070 80 美香菱屈受贪夫棒 王道士胡诌妒妇方 贾母 4 1071 80 美香菱屈受贪夫棒 王道士胡诌妒妇方 邢夫人 3 1072 80 美香菱屈受贪夫棒 王道士胡诌妒妇方 空空道人 2 chapter_titles40 = split_chapters(content40, '后四十回/') stop_words40 = get_stop_words(40, '后四十回/') words40 = content_cut(content40) words40 = clear_words(words40, stop_words40) char_df40 = count_character(40, '后四十回/', chapter_titles40, stop_words40) char_df40.head()chapter chapter_titles characters times 0 81 占旺相四美钓游鱼 奉严词两番入家塾 贾宝玉 67 1 81 占旺相四美钓游鱼 奉严词两番入家塾 贾母 31 2 81 占旺相四美钓游鱼 奉严词两番入家塾 王夫人 20 3 81 占旺相四美钓游鱼 奉严词两番入家塾 贾政 16 4 81 占旺相四美钓游鱼 奉严词两番入家塾 贾探春 16 二 比较不同前八十回和后四十回作者不是同一人,写作用词习惯有所不同,这里仅作简单比较 1 停止词比较(除去添加的停止词,剩下的是两个文本中每回都出现的,比较其不同) print('前后回停止词不一样的',set(stop_words40)^set(stop_words80)) print('其中,前八十回独有的:',set(stop_words80)-set(stop_words40)) print('后四十回独有的:', set(stop_words40)-set(stop_words80))前后回停止词不一样的 {‘事’, ‘请’, ‘话’, ‘想’, ‘笑’, ‘问’, ‘走’, ‘倒’, ‘老太太’} 其中,前八十回独有的: {‘问’, ‘笑’} 后四十回独有的: {‘倒’, ‘老太太’, ‘走’, ‘事’, ‘请’, ‘话’, ‘想’} 2 主要人物、词汇比较 (1)主要词汇 print('前八十回出现次数前20的词汇有:', '、'.join(pd.value_counts(words80).index.tolist()[:20])) print('后四十回出现次数前20的词汇有:', '、'.join(pd.value_counts(words40).index.tolist()[:20]))前八十回出现次数前20的词汇有: 贾宝玉、贾母、王熙凤、林黛玉、袭人、薛宝钗、王夫人、姑娘、太太、奶奶、平儿、贾琏、史湘云、贾探春、姐姐、薛姨妈、晴雯、尤氏、鸳鸯、银子 后四十回出现次数前20的词汇有: 贾宝玉、贾母、林黛玉、王熙凤、王夫人、贾政、袭人、薛宝钗、姑娘、老爷、太太、贾琏、紫鹃、奶奶、薛姨妈、二爷、平儿、鸳鸯、姐姐、贾探春 (2)主要人物 # 回目数,前八十回折半 c1 = char_df80['characters'].value_counts()[:20].to_frame()/2 c2 = char_df40['characters'].value_counts()[:20].to_frame() p1 = pd.merge(c1, c2, how='outer',right_index=True, left_index=True).fillna(0).sort_values(by='characters_x',ascending=False) # 画图 from pyecharts.charts import Bar from pyecharts import options as opts bar = Bar(init_opts=opts.InitOpts(width='850px', height='350px')) bar.add_xaxis(xaxis_data=p1.index.tolist()) bar.add_yaxis('前八十回', yaxis_data=p1['characters_x'].tolist()) bar.add_yaxis('后四十回', yaxis_data=p1['characters_y'].tolist()) bar.set_global_opts(title_opts=opts.TitleOpts(title='人物出现回目数top20',subtitle='0表示前20位没有此人')) bar.render_notebook() # 史湘云、贾珍、贾迎春、在后四十回出场回目数没在前20,紫鹃、贾惜春、麝月反之,此外差别较大的是贾琏,王夫人、尤氏
没有分回目 without_char80 = (pd.value_counts([w for w in words80 if w not in character_list])[:20]/2).to_frame() without_char40 = pd.value_counts([w for w in words40 if w not in character_list])[:20].to_frame() p3 = pd.merge(without_char80,without_char40, how='outer', left_index=True, right_index=True).fillna(0).astype(int) # 堆积柱状图-使用主题 from pyecharts.globals import ThemeType b = Bar(init_opts=opts.InitOpts(width='850px', height='350px',theme=ThemeType.ROMA)) b.add_xaxis(xaxis_data=p3.index.tolist()) b.add_yaxis('前八十回', p3['0_x'].tolist(), stack='stack1',category_gap='5%', label_opts=opts.LabelOpts(is_show=False)) b.add_yaxis('后四十回', p3['0_y'].tolist(), stack='stack1', category_gap='5%', label_opts=opts.LabelOpts(is_show=False)) b.set_global_opts(title_opts=opts.TitleOpts(title='其它词汇出现次数前20')) b.render_notebook()根据学者分析,出现的所有人物都有伏笔,前八十回中出现的着墨较少的人物在后期会有着重表现,但补写的后四十回是否如此? # 前八十回出场较少的人物 less_appear = c3[-20:].index.tolist() appearance = [int(c4.loc[i]) if i in c4.index else 0 for i in less_appear] # 画图:涟漪特效散点-特效 from pyecharts.globals import SymbolType from pyecharts.charts import EffectScatter e = EffectScatter(init_opts=opts.InitOpts(height='350px', width='1050px', theme=ThemeType.WESTEROS)) e.add_xaxis(xaxis_data=less_appear) e.add_yaxis('前八十回', (c3['times'][-20:]/2).astype(int).tolist(), symbol=SymbolType.ARROW) e.add_yaxis('后四十回', appearance) e.set_global_opts(title_opts=opts.TitleOpts(title='前八十回出场次数少的人物',subtitle='出场次数比较(前八十回折半)')) e.render_notebook() # 大部分人都没有再出场,除金钏在前八十回已去世外,续作中将这些人忘了?巧姐、宝蟾、贾兰、薛蝌、贾元春“戏份”增多哪一回目出场人物最多,都有谁,出场人物较多的回目也就是人物关系表现复杂的戏份,也就是重头戏 # 每回的人物数 c5 = char_df.groupby('chapter')[['characters']].count() max_char = c5.idxmax().values[0] print('出场人物最多的回目是第%s回: %s,角色有%s人:\n' %(max_char, chapter_titles[max_char-1], c5.max().values[0]), '、'.join(char_df['characters'][char_df['chapter']==max_char])) # 画图 p4 = char_df[['characters','times']][char_df['chapter']==max_char] from pyecharts.charts import Polar from pyecharts import options as opts polar = Polar(init_opts=opts.InitOpts(width='500px',height='500px')) polar.add_schema(angleaxis_opts=opts.AngleAxisOpts(data=list(range(1,121)), type_="category")) polar.add("", c5['characters'].tolist(), type_="bar") polar.set_global_opts(title_opts=opts.TitleOpts(title='各回目出场人数')) polar.render_notebook()出场人物最多的回目是第62回: 憨湘云醉眠芍药茵 呆香菱情解石榴裙,角色有25人: 贾宝玉、香菱、贾探春、袭人、薛宝钗、史湘云、林黛玉、平儿、芳官、薛姨妈、林之孝家的、晴雯、尤氏、薛蝌、李纨、王熙凤、王夫人、赵姨娘、鸳鸯、麝月、贾母、贾环、藕官、贾惜春、玉钏儿 画图:某角色在各回目出现次数 c6 = char_df.pivot_table(index='characters', columns='chapter',values='times') p5 = c6.fillna(0).applymap(lambda x: 1 if x>0 else 0) # 回目查询:查询角色出场的回目及出场的总回目数 c = input('你要查找谁的出场回目?请输入完整人名:') chaps = p5.loc[c][p5.loc[c]==1].index.astype(str).tolist() if c not in p5.index: print('该角色不存在') else: print('%s出场回目为第%s回,共%s回' %(c, '、'.join(chaps), p5.loc[c].sum())) # 出场次数查询:具体回目的出场次数查询 chapter = input('您想查询%s哪个回目的出场次数?请输入数字,若查询总次数输入“全部”:' %c) if chapter=='全部': print('%s在前八十回共出现%s次' %(c, c6.loc[c].sum())) else: print('%s在第%s回出现%s次' %(c, chapter, c6.loc[c, int(chapter)].astype(int)))你要查找谁的出场回目?请输入完整人名:贾宝玉 贾宝玉出场回目为第2、3、5、6、7、8、9、10、11、12、13、14、15、16、17、18、19、20、21、22、23、24、25、26、27、28、29、30、31、32、33、34、35、36、37、38、39、40、41、42、43、44、45、46、47、48、49、50、51、52、53、54、55、56、57、58、59、60、61、62、63、64、66、67、70、71、72、73、74、75、76、77、78、79、80、81、82、83、84、85、86、87、88、89、90、91、92、93、94、95、96、97、98、99、100、101、102、104、105、106、107、108、109、110、111、112、113、114、115、116、117、118、119、120回,共114回 您想查询贾宝玉哪个回目的出场次数?请输入数字,若查询总次数输入“全部”:19 贾宝玉在第19回出现116次 # 画图:桑基图-该角色每个回目出场次数 nodes = [{'name':'回目%s-%s'%(i*10+1,(i+1)*10)} for i in range(0,12)] nodes.extend([{'name': c}, {'name': '前八十'}, {'name': '后四十'}]) links = [{'source': c, 'target':'前八十', 'value': int(c3.loc[c,'times'])}, {'source': c, 'target':'后四十', 'value': int(c4.loc[c,'times'])} ] # [c6.loc[c,i*10+1:(i+1)*10].sum() for i in range(0,12)] for i in range(0,12): if i 300].fillna(0) from pyecharts.charts import HeatMap heatmap = HeatMap(init_opts=opts.InitOpts(height='500px', width='1000px')) heatmap.add_xaxis(xaxis_data=p6.columns.tolist()) heatmap.add_yaxis('', yaxis_data=p6.index.tolist(), value=[[i,j, int(p6.iloc[j,i])] for i in range(p6.shape[1]) for j in range(p6.shape[0])] #label_opts=opts.LabelOpts(is_show=False, position='inside') 关于值是否显示及位置的设定 ) heatmap.set_global_opts(title_opts=opts.TitleOpts(title='人物各回目出场次数'), visualmap_opts=opts.VisualMapOpts()) heatmap.render_notebook() # 热力图必须加上visualmap,否则是一个颜色 # 注意add_yaxis里面的value,需要给每个值设定位置,还要注意p6的数据排列顺序表现一个人物的故事从开始、高潮、退场,用平行坐标系图表示出来 p7 = [[int(p5.loc[i].idxmax()), int(c6.loc[i].idxmax()), int(c6.loc[i].idxmin()), int(p5.loc[i][::-1].idxmax()), i] for i in c6.index] from pyecharts.charts import Parallel parallel = Parallel(init_opts=opts.InitOpts(width='850px',height='650px')) parallel.add_schema( schema=[ opts.ParallelAxisOpts(dim=0, name="出场回", min_=0, max_=120), # 固定刻度 opts.ParallelAxisOpts(dim=1, name="主场回", min_=0, max_=120), opts.ParallelAxisOpts(dim=2, name="最低回", min_=0, max_=120), opts.ParallelAxisOpts(dim=3, name="末场回", min_=0, max_=120), opts.ParallelAxisOpts(dim=4, name="人物", type_="category", data=p5.index.tolist()) ], parallel_opts=opts.ParallelOpts(pos_left=150, pos_right=50,pos_top=70) ) for character in c6.index: parallel.add(character, [p7[c6.index.tolist().index(character)]]) parallel.set_global_opts(title_opts=opts.TitleOpts(title="人物出场-主场-末场回目"), legend_opts=opts.LegendOpts(type_='scroll', selected_mode='single', pos_top=50,pos_bottom=50, pos_left=50, orient='vertical')) parallel.render_notebook() # 全局设置中,将图例的selected_mode该为'multiple',则多选,此时点击某个图例为排除一个人物的故事的关键情节在他所有出场中的位置 # 最大最小百分数表示位置 main_chap = [0 if row[3]-row[0]==0 else int(100*(row[1]-row[0])/(row[3]-row[0])) for row in p7] c = input('您想查询谁的主回目?') v = main_chap[c6.index.tolist().index(c)] # 画图 from pyecharts.charts import Gauge g = Gauge(init_opts=opts.InitOpts(width='450px',height='450px')) g.add('',[("主场位置", v)],radius='50%', axisline_opts=opts.AxisLineOpts( linestyle_opts=opts.LineStyleOpts( color=[(0.33, "#67e0e3"), (0.66, "#37a2da"), (1, "#fd666d")], width=30) ) ) g.set_global_opts( title_opts=opts.TitleOpts(title="%s主回目位置"%c), legend_opts=opts.LegendOpts(is_show=False, pos_top=50), ) g.render_notebook()人物关系越近,它们在一起出现的次数就多。 links2 = [] relations = {} i = 1 for x in c6.index: for y in c6.index[i:]: z = set(char_df['chapter'][char_df['characters']==x]) & set(char_df['chapter'][char_df['characters']==y]) if len(z) != 0: links2.append(opts.GraphLink(source=x, target=y, value=len(z))) relations['%s-%s'%(x,y)] = len(z) # 保存一份 i += 1 # symbol_size*0.8,让图形大小差距小一些 nodes = [opts.GraphNode(name=i, symbol_size=j*0.8, value=j) for i,j in zip(p5.index, p5.sum(1))] # 关系图 from pyecharts.charts import Graph g = Graph(init_opts=opts.InitOpts(height='850px',width='850px')) g.add('', nodes=nodes, links=links2, repulsion=5000, layout='force', edge_length=[1,100], itemstyle_opts=opts.ItemStyleOpts(border_color='black', opacity='0.9'), # 节点样式设置 label_opts=opts.LabelOpts(is_show=True, position='inside', color='black')) # 节点标签(人物名称)设置 g.set_global_opts(title_opts=opts.TitleOpts(title='人物关系',subtitle='(以在同一回目出现为基础)'), visualmap_opts=opts.VisualMapOpts(max_=120)) # 根据节点的value值映射,最大120,因为有120回 g.render_notebook() |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |