小蛇学python(12)分析《今生今世》人物关系图谱

《今生今世》是渣男胡兰成所写的一部自传体小说。今天我们就来分析一下在他所写的自传中的人物关系图谱,分析一下胡兰成到底和多少女人有关系。

代码奉上

# -*- coding: utf-8 -*-
#@author: Yuhao Zhang
#2018.5.30

import jieba
import codecs
from collections import defaultdict
from pandas import DataFrame
import pandas as pd

#路径设置。
#文档介绍
#jsjs.txt是胡兰成写的小说《今生今世》。
#person.txt是一个语料库,里面放了很多该小说的角色名称。
#synonymous_dict.txt是角色的别名
#其他两个文件是存放程序计算所得各个人物关系之间的边的权重
TEXT_PATH = 'jsjs.txt'
DICT_PATH = 'person.txt'
SYNONYMOUS_DICT_PATH = 'synonymous_dict.txt'
SAVE_NODE_PATH = 'node.csv'
SAVE_EDGE_PATH = 'edge.csv'

#类的初始化
class RelationshipView:
    def __init__(self, text_path, dict_path, synonymous_dict_path):
        self._text_path = text_path
        self._dict_path = dict_path
        self._synonymous_dict_path = synonymous_dict_path
        self._person_counter = defaultdict(int)
        self._person_per_paragraph = []
        self._relationships = {}
        self._synonymous_dict = {}

    def generate(self):
        self.count_person()
        self.calc_relationship()
        self.save_node_and_edge()

    def synonymous_names(self):
        with codecs.open(self._synonymous_dict_path, 'r', 'utf-8') as f:
            lines = f.read().split('\n')
        for l in lines:
            self._synonymous_dict[l.split(' ')[0]] = l.split(' ')[1]
        return self._synonymous_dict

    def get_clean_paragraphs(self):
        new_paragraphs = []
        last_paragraphs = []
        with codecs.open(self._text_path, 'r', 'utf-8') as f:
            paragraphs = f.read().split('\r\n')
            paragraphs = paragraphs[0].split('\u3000')
        for i in range(len(paragraphs)):
            if paragraphs[i] != '':
                new_paragraphs.append(paragraphs[i])
        for i in range(len(new_paragraphs)):
            new_paragraphs[i] = new_paragraphs[i].replace('\n', '')
            new_paragraphs[i] = new_paragraphs[i].replace(' ', '')
            last_paragraphs.append(new_paragraphs[i])
        return last_paragraphs

    def count_person(self):
        paragraphs = self.get_clean_paragraphs()
        synonymous = self.synonymous_names()
        print('start process node')
        with codecs.open(self._dict_path, 'r', 'utf-8') as f:
            name_list = f.read().split(' 10 nr\n')
        for p in paragraphs:
            jieba.load_userdict(self._dict_path)
            poss = jieba.cut(p)
            self._person_per_paragraph.append([])
            for w in poss:
                if w not in name_list:
                    continue
                if synonymous.get(w):
                    w = synonymous[w]
                self._person_per_paragraph[-1].append(w)
                if self._person_counter.get(w) is None:
                    self._relationships[w] = {}
                self._person_counter[w] += 1
        return self._person_counter

    def calc_relationship(self):
        print("start to process edge")
        for p in self._person_per_paragraph:
            for name1 in p:
                for name2 in p:
                    if name1 == name2:
                        continue
                    if self._relationships[name1].get(name2) is None:
                        self._relationships[name1][name2] = 1
                    else:
                        self._relationships[name1][name2] += 1
        return self._relationships

    def save_node_and_edge(self):
        excel = []
        for name, times in self._person_counter.items():
            excel.append([])
            excel[-1].append(name)
            excel[-1].append(name)
            excel[-1].append(str(times))
        data = DataFrame(excel, columns=['Id', 'Label', 'Weight'])
        data.to_csv('node.csv', encoding='gbk')

        excel = []
        for name, edges in self._relationships.items():
            for v, w in edges.items():
                if w > 3:
                    excel.append([])
                    excel[-1].append(name)
                    excel[-1].append(v)
                    excel[-1].append(str(w))
        data = DataFrame(excel, columns=['Source', 'Target', 'Weight'])
        data.to_csv('edge.csv', encoding='gbk')

        print('save file successful!')

if __name__ == '__main__':
    v = RelationshipView(TEXT_PATH, DICT_PATH, SYNONYMOUS_DICT_PATH)
    v.generate()

先将代码全部贴出来,大家复制粘贴即可运行,接下来我再慢慢得一行一行仔细讲解代码逻辑。

先把程序运行效果图贴出来。

基础关系图谱
恋人关系图谱

设计思想

整个程序的实现过程是这样的。

首先,我们预先准备好语料库。(里面含有小说主人公姓名以及别名,这样做的目的降低了程序的难度,不然还要涉及到知识图谱的实体识别。有关知识图谱的东西我最近在看,以后会写这方面博客)

然后我们将这篇将近三十万字的小说按段落分开,对每一段进行单独分析,对两个实体之间的边的权重进行计算。具体的说,它的权重是如何计算的呢?比如第一段我们结合语料库发现里面有三个胡兰成,一个张爱玲,一个周佛海。那么我们就给胡兰成和张爱玲之间的边权重更新为3,张爱玲和周佛海之间更新为1,胡兰成和周佛海之间更新为3。然后将每个段落的每两个点之间的权重加和,最后写入excel表中保存为csv格式。这是为了方便使用Gephi可视化。

当然同时我们也进行了节点大小的计算,其实就是单纯计算这个实体名字在文中出现的次数。比如胡兰成出现了5752次,那么它在可视化图中的大小你可以理解为5752个单位这么大。

node.csv
edge.csv

代码详解

我只讲我认为最难懂的部分,一些我认为简单的如果你不懂可以私信评论我都可以,我一般会很快回复。

我认为最难懂的就是count_person()函数中的一部分,当然这一部分也可以说是精华。

        for p in paragraphs:
            jieba.load_userdict(self._dict_path)
            poss = jieba.cut(p)
            self._person_per_paragraph.append([])
            for w in poss:
                if w not in name_list:
                    continue
                if synonymous.get(w):
                    w = synonymous[w]
                self._person_per_paragraph[-1].append(w)
                if self._person_counter.get(w) is None:
                    self._relationships[w] = {}
                self._person_counter[w] += 1
        return self._person_counter

首先在一个将小说每段作为一个字符串元素的列表paragraphs中循环,也就是说p是小说中的每一段。

然后在加载了语料库之后,对该段进行结巴分词,并将该段分词结果存在poss列表中。如果语料库中的实体在poss中可以找的到,那么我们就从存储别名字典中依据这个实体来找他的唯一名字(因为有可能是别名,我们要统一换成真正的姓名进行加权)。

get()是字典这个类型所有的属性,在该段代码中体现为,判断该字典中有无含有以w为key的元素。

大家也许会疑惑,为何self._person_counter是一个字典呢?这源于程序一开头的一行代码。

self._person_counter = defaultdict(int)

想要理解这个defaultdict的作用,将我下面贴出来的这几行代码敲进去试试就知道了。它其实起到一个给字典的值设置一个默认值的快捷方式。

from collections import defaultdict

person = defaultdict(int)

test_list = ['胡兰成', '张爱玲', 'Bob', 'Bob', 'Nick', '胡兰成']

for p in test_list:
    person[p] += 1
print(person)

可视化

可视化我是用到了Gephi这个软件。

PS下载这个软件需要用到java组件,需要下载一个jre,听过来人一句劝告,千万不要下载最新的那个10的那个版本。

然后就是Gephi简单的使用教程。放几张网图,大家学习一下。

新建工程
导入数据
选择样式
修改字体
调整布局
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • 随着天气渐暖,每天六点多太阳就已高高升起,清晨的阳光透过窗帘,房间里渐渐明亮起来。而我再也不如之前那般贪睡。睁开双...
    猫宁馨儿阅读 563评论 3 7
  • 所有别人浪漫的事情,我都会被感动,因为我希望它能发生在我身上~ 可是~你会给我一个浪漫的求婚吗~
    坦荡兮兮阅读 169评论 0 0
  • 《萤火之森》 《夏目友人帐》 “此生无悔入夏目,来世愿做帐中妖”
    我叫偶尔阅读 755评论 0 0