蘇軾的朋友圈——基于 networkx 進(jìn)行社交網(wǎng)絡(luò)分析(SNA)

??社交網(wǎng)絡(luò)分析方法(Social Network Analysis, SNA)赁项,是由社會(huì)學(xué)家根據(jù)數(shù)學(xué)方法茫蛹、圖論等發(fā)展起來的定量分析方法逻锐。歷史學(xué)家 Lawrence Stone 將其作為方法論引入群體傳記學(xué)中岸夯。 如同學(xué)者 Charles Wetherell 所述:

“個(gè)人關(guān)系組成之集合體的概念化授艰,提供歷史學(xué)家評(píng)估古人于何時(shí)、如何赌渣,及為何利用親族與非親族關(guān)系魏铅。 社會(huì)網(wǎng)絡(luò)關(guān)系分析家發(fā)現(xiàn),人們須從不同的社會(huì)關(guān)系中坚芜、不同的人身上览芳,尋求情緒上與經(jīng)濟(jì)上的支持。 因此鸿竖,僅研究人們?nèi)绾斡谖C(jī)時(shí)刻利用親族關(guān)系已不足夠沧竟;相反地,歷史學(xué)的研究必須涵蓋過去人們?nèi)绾螢椴煌康亩糜H族與朋友關(guān)系缚忧,以及此一利用關(guān)系的優(yōu)勢(shì)與限制悟泵。 事實(shí)上,社會(huì)網(wǎng)絡(luò)關(guān)系做為一種研究方法不僅有助于此一論辯闪水,更幫助歷史學(xué)家 Charles Tilly 所提出的挑戰(zhàn):將平民百姓的日常生活與大規(guī)模的社會(huì)變遷作有意義的鏈接糕非。 ”

??本文將以宋代政治人物蘇軾為例,從蘇軾及其親友的往來書信中歸納出社交網(wǎng)絡(luò)關(guān)系(“朋友圈”)球榆,然后借助 networkx 對(duì)其社交網(wǎng)絡(luò)關(guān)系進(jìn)行可視化和分析朽肥。

0 準(zhǔn)備工作

??按照慣例,先導(dǎo)入相關(guān)包持钉。除了常用的幾個(gè)包外衡招,還有這次的主角—— networkx 包。該包用于創(chuàng)建網(wǎng)絡(luò)對(duì)象每强,以各種數(shù)據(jù)格式加載或存儲(chǔ)網(wǎng)絡(luò)始腾,并可以分析網(wǎng)絡(luò)結(jié)構(gòu)、建立網(wǎng)絡(luò)模型舀射、設(shè)計(jì)生成網(wǎng)絡(luò)的算法以及繪制網(wǎng)絡(luò)窘茁。

import sqlite3

import numpy as np
import pandas as pd
import networkx as nx

import matplotlib as mpl
import matplotlib.pyplot as plt
plt.style.use('seaborn')
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

??接著怀伦,設(shè)置數(shù)據(jù)庫(kù)地址(使用 SQLite 數(shù)據(jù)庫(kù)脆烟,保存在本地),方便加載數(shù)據(jù):

db = 'F:\pydata\dataset\CBDB_aw_20180831_sqlite.db'

數(shù)據(jù)來源于 CBDC(China Biographical Database)房待,即中國(guó)歷代人物傳記數(shù)據(jù)庫(kù)

1 搜索人物

??從數(shù)據(jù)庫(kù)中查找蘇軾的 person_id

# 查找 person_id 函數(shù)
def getPersonId(person_name):
    ''' Get person_id
    @param person_name: str
    @return person_id: str
    '''
    sql = '''
    SELECT c_personid
    FROM biog_main
    WHERE c_name_chn = '{0}'
    '''.format(person_name)

    try:
        person_id = str(pd.read_sql(sql, con=sqlite3.connect(db)).iloc[0, 0])
        return person_id
    except:
        print("No such person.")

# 查找蘇軾的 person_id邢羔,注意要使用繁體中文
person_id = getPersonId('蘇軾')

查找 person_id 有待優(yōu)化,應(yīng)支持簡(jiǎn)體輸入以及人物別名

# 打印 person_id
print(person_id)
'3767'

2 獲取數(shù)據(jù)

??在獲得目標(biāo)人物(蘇軾)的 person_id 后桑孩,需要通過該 id 在數(shù)據(jù)庫(kù)中查找相關(guān)記錄拜鹤,得到蘇軾與其親友、及其親友與其他人的書信往來關(guān)系:

這里聯(lián)查了 3 張表流椒,分別是傳記主表(biog_main)敏簿、關(guān)系信息表(assoc_data)、關(guān)系代碼表(assoc_codes)

sql = '''
SELECT a.c_personid person_id
    , b1.c_name_chn person_a
    , c_assoc_id assoc_id
    , b2.c_name_chn person_b
    , a.c_assoc_code assoc_code
    , c.c_assoc_desc_chn assoc_desc
FROM assoc_data a
LEFT JOIN biog_main b1 
    ON a.c_personid = b1.c_personid
LEFT JOIN biog_main b2 
    ON a.c_assoc_id = b2.c_personid
LEFT JOIN assoc_codes c 
    ON a.c_assoc_code = c.c_assoc_code
WHERE (a.c_personid = {0}
    OR a.c_personid IN (
        SELECT c_personid 
        FROM assoc_data 
        WHERE c_assoc_id = {0}
        AND c_assoc_code IN ('429', '430', '431', '432', '433', '434', '435', '436'))
    OR a.c_assoc_id IN (
        SELECT c_assoc_id
        FROM assoc_data 
        WHERE c_personid = {0}
        AND c_assoc_code IN ('429', '430', '431', '432', '433', '434', '435', '436'))) 
    AND a.c_assoc_code IN ('429', '430', '431', '432', '433', '434', '435', '436')
'''.format(person_id)

person_assoc = pd.read_sql(sql, con=sqlite3.connect(db))

??在對(duì)數(shù)據(jù)庫(kù)執(zhí)行了查詢操作后,我們將得到一個(gè) DataFrame惯裕,其中包括了:

  • person_id:關(guān)系人A id
  • person_a: 關(guān)系人A姓名
  • assoc_id:關(guān)系人B id
  • person_b:關(guān)系人B姓名
  • assoc_code:關(guān)系代碼
  • assoc_desc:關(guān)系名稱
# 查看 DataFrame 信息
person_assoc.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2595 entries, 0 to 2594
Data columns (total 6 columns):
person_id     2595 non-null int64
person_a      2595 non-null object
assoc_id      2595 non-null int64
person_b      2595 non-null object
assoc_code    2595 non-null int64
assoc_desc    2595 non-null object
dtypes: int64(3), object(3)
memory usage: 121.7+ KB

??關(guān)系人A或關(guān)系人B為蘇軾的記錄數(shù):

person_assoc[(person_assoc['person_id'] == int(person_id)) | (person_assoc['assoc_id'] == int(person_id))].count()
person_id     550
person_a      550
assoc_id      550
person_b      550
assoc_code    550
assoc_desc    550
dtype: int64

??DataFrame 中包含了 8 種關(guān)系(均為書信往來關(guān)系):

# 打印所有關(guān)系的名稱
for i in person_assoc['assoc_desc'].unique():
    print(i)
致書Y
被致書由Y
答Y書
收到Y(jié)的答書
致Y啓
收到Y(jié)的啓
答Y啓
收到Y(jié)的答啓

3 生成社交網(wǎng)絡(luò)

??從包含邊列表(至少兩列節(jié)點(diǎn)名稱和零個(gè)或多個(gè)邊緣屬性列)的 DataFrame 返回圖温数。

# 生成圖
person_G = nx.from_pandas_edgelist(person_assoc, source='person_a', target='person_b', edge_attr='assoc_desc')

??圖中描述了 906 個(gè)關(guān)系,其中包含 614 個(gè)唯一個(gè)體蜻势。蘇軾的社交網(wǎng)絡(luò)中的隨機(jī)個(gè)體在社交網(wǎng)絡(luò)的其余部分平均有近 3 個(gè)聯(lián)系人撑刺。由于存在大量與蘇軾親友有書信往來的但與蘇軾本人無關(guān)系的記錄,整個(gè)社會(huì)網(wǎng)絡(luò)的密度較低握玛。

# 打印圖信息
print(nx.info(person_G))
print('Density: {0}'.format(nx.density(person_G)))
Name: 
Type: Graph
Number of nodes: 614
Number of edges: 906
Average degree:   2.9511
Density: 0.004814257855051517

??接著够傍,通過 networkx 生成社交網(wǎng)絡(luò)的中心度和 PR 值,中心度包括:接近中心度(或緊密中心度挠铲,Closeness centrality)冕屯,中介中心度(或間距中心度,Betweenness centrality)拂苹,度中心度(Degree centrality)愕撰,

person_betweenness = pd.Series(nx.betweenness_centrality(person_G), name='Betweenness')
person_person = pd.Series.to_frame(person_betweenness)
person_person['Closeness'] = pd.Series(nx.closeness_centrality(person_G))
person_person['PageRank'] = pd.Series(nx.pagerank_scipy(person_G))
person_person['Degree'] = pd.Series(dict(nx.degree(person_G)))
desc_betweenness = person_person.sort_values('Betweenness', ascending=False)
desc_betweenness.head(10)

4 可視化

??在繪制可視化圖形前,需要提前創(chuàng)建一致的圖形布局醋寝,這里選用了 kamada_kawai_layout 的圖形布局:

#pos = nx.circular_layout(person_G)
pos = nx.kamada_kawai_layout(person_G)
#pos = nx.shell_layout(person_G)
#pos = nx.spring_layout(person_G)
#pos = nx.random_layout(person_G)

??繪制圖形的函數(shù):

# 繪制函數(shù)
def draw_graph(df, top):
    ''' Draw Graph
    @param df: DataFrame
    @param top: int, numbers of top
    '''    
    nodes = df.index.values.tolist() #生成節(jié)點(diǎn)列表
    edges = nx.to_edgelist(person_G) #生成邊列表
    # 生成無向度量圖
    metric_G = nx.Graph()
    metric_G.add_nodes_from(nodes)
    metric_G.add_edges_from(edges)
    # 生成 Top n 的標(biāo)簽列表
    top_labels = {}
    for node in nodes[:top]:
        top_labels[node] = node
    # 生成節(jié)點(diǎn)尺寸列表
    node_sizes = []
    for node in nodes:
            node_sizes.append(df.loc[node]['Degree'] * 16 ** 2)
    # 設(shè)置圖形尺寸
    plt.figure(1, figsize=(64, 64))
    # 繪制圖形
    nx.draw(metric_G, pos=pos, node_color='#cf1322, with_labels=False)
    nx.draw_networkx_nodes(metric_G, pos=pos, nodelist=nodes[:top], node_color='#a8071a', node_size=node_sizes[:top])
    nx.draw_networkx_nodes(metric_G, pos=pos, nodelist=nodes[top:], node_color='#a3b1bf', node_size=node_sizes[top:])
    nx.draw_networkx_edges(metric_G, pos=pos, edgelist=edges, edge_color='#d9d9d9', arrows=False)
    nx.draw_networkx_labels(metric_G, pos=pos, font_size=20, font_color='#555555')
    nx.draw_networkx_labels(metric_G, pos=pos, labels=top_labels, font_size=28, font_color='#1890ff')
    # 保存圖片
    plt.savefig('tmp.png')

??最后搞挣,生成網(wǎng)絡(luò)圖,圖中的每個(gè)節(jié)點(diǎn)都對(duì)應(yīng)蘇軾朋友圈中的一個(gè)人音羞,而在朋友圈中與蘇軾最親近的 20 個(gè)人的節(jié)點(diǎn)以紅底藍(lán)字突出顯示囱桨,節(jié)點(diǎn)大小對(duì)應(yīng)程度大小:

draw_graph(desc_betweenness, 20)

??從圖中不難看出嗅绰,蘇軾處于該社交網(wǎng)絡(luò)的最中心舍肠,而“六一居士”歐陽(yáng)修、王安石窘面、黃庭堅(jiān)等社交達(dá)人也有著較高的中心程度翠语。有趣的是,宋代書法四大家——蘇黃米蔡财边,原來都處于同一個(gè)社交網(wǎng)絡(luò)中(其實(shí)是筆者孤陋寡聞了)肌括。

5 One More Thing

??CBDB 還提供了查詢 API,可以通過輸入人物的姓名或 id 快速查找人物的傳記信息酣难,下面以朱熹為例簡(jiǎn)單介紹下如何使用該 API:

import requests

url = 'https://cbdb.fas.harvard.edu/cbdbapi/person.php?name=%E6%9C%B1%E7%86%B9&o=json'

my_headers = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Host': 'cbdb.fas.harvard.edu',
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36'
}

def getJSON(url, headers):
    """ Get JSON from the destination URL
    @ param url: str, destination url
    @ param headers: dict, request headers
    @ return json: json, result
    """
    res = requests.get(url, headers=headers) 
    res.raise_for_status()  #拋出異常
    res.encoding = 'utf-8'  
    json = res.json()
    return json

# 獲取 json
json = getJSON(url, headers=my_headers)
# 解析 json谍夭,打印基本信息
json['Package']['PersonAuthority']['PersonInfo']['Person']['BasicInfo'] 
{'PersonId': '3257',
 'EngName': 'Zhu Xi',
 'ChName': '朱熹',
 'IndexYear': '1189',
 'Gender': '0',
 'YearBirth': '1130',
 'DynastyBirth': '南宋',
 'EraBirth': '建炎',
 'EraYearBirth': '4',
 'YearDeath': '1200',
 'DynastyDeath': '南宋',
 'EraDeath': '慶元',
 'EraYearDeath': '2',
 'YearsLived': '71',
 'Dynasty': '宋',
 'JunWang': '吳郡',
 'Notes': "Zhu Xi [3257] Shengzheng, p. 2224; Jiangxi TZ, 10.21b; SHY:ZG, 72.33a, 36a. CBD, 1, 587-597.From Hartwell's ACTIVITY table:1181:  Apt. Liangzhe Dong tiju1182:  In office as Liangzhe Dong tiju1182:  As Liangzhe Dong tiju, impeached Tang Zhoungyou.淳祐中從祀孔廟。\x7f 《唐代人物知識(shí)ベース》記其生卒年為:1130 - 1200.\x7f\x7f"}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末憨募,一起剝皮案震驚了整個(gè)濱河市紧索,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌菜谣,老刑警劉巖珠漂,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晚缩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡媳危,警方通過查閱死者的電腦和手機(jī)橡羞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來济舆,“玉大人卿泽,你說我怎么就攤上這事∽叹酰” “怎么了签夭?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)椎侠。 經(jīng)常有香客問我第租,道長(zhǎng),這世上最難降的妖魔是什么我纪? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任慎宾,我火速辦了婚禮,結(jié)果婚禮上浅悉,老公的妹妹穿的比我還像新娘趟据。我一直安慰自己,他們只是感情好术健,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布汹碱。 她就那樣靜靜地躺著,像睡著了一般荞估。 火紅的嫁衣襯著肌膚如雪咳促。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天勘伺,我揣著相機(jī)與錄音跪腹,去河邊找鬼。 笑死飞醉,一個(gè)胖子當(dāng)著我的面吹牛冲茸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冒掌,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼噪裕,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了股毫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤召衔,失蹤者是張志新(化名)和其女友劉穎铃诬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡趣席,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年兵志,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宣肚。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡想罕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出霉涨,到底是詐尸還是另有隱情按价,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布笙瑟,位于F島的核電站楼镐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏往枷。R本人自食惡果不足惜框产,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望错洁。 院中可真熱鬧秉宿,春花似錦、人聲如沸屯碴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)窿锉。三九已至酌摇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嗡载,已是汗流浹背窑多。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洼滚,地道東北人埂息。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像遥巴,于是被迫代替她去往敵國(guó)和親千康。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • 畢業(yè)季铲掐,離別的季節(jié)拾弃。 回到學(xué)校,和朋友拍畢業(yè)照摆霉。不顧太陽(yáng)的無情豪椿,大汗淋漓地跑遍每個(gè)角落奔坟。就像一個(gè)任性的孩子,試圖將...
    十兩糧食閱讀 430評(píng)論 0 4
  • 一搭盾、學(xué)習(xí)與實(shí)踐 1.付出不亞于任何人的努力 2.要謙虛咳秉,不要驕傲 3.要每天反省 4.活著,就要感謝 5.積善行鸯隅,...
    Lucien光閱讀 169評(píng)論 0 0
  • 夜已深澜建,聽著女兒的故事機(jī)播放的故事,有一句話蝌以,引起了我的注意炕舵,對(duì)待壞人和對(duì)待好人不能用一個(gè)方法。 聽到這句話饼灿,我想...
    安紅霞閱讀 291評(píng)論 0 1
  • 下載初始化倉(cāng)庫(kù) 一:使用短的SHA-1值幕侠,看單一commit對(duì)象 以及查找分支指向的commit的SHA-1值 二...
    老沈Rosen閱讀 1,262評(píng)論 0 0
  • 又失眠。記不清這是第幾個(gè)晚上了碍彭。 睡不著晤硕,想的事就多了,亂糟糟的庇忌。 人與人之間的感情舞箍,真的很微妙。曾經(jīng)我以為皆疹,最值...
    memory如此閱讀 207評(píng)論 0 0