Data_StackExchange_Python

本文用到的包為

import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
from collections import defaultdict
from collections import Counter
from numpy import linalg as LA
import statsmodels.api as sm
import matplotlib.cm as cm
from datetime import datetime as dt
import sys
from os import listdir
from scipy.stats.stats import pearsonr
from matplotlib.dates import YearLocator

StackExchange(以下簡稱SE)是世界上最大的專業(yè)性問答社區(qū)之一娃弓。最早只有一個StackOverflow塘辅,后來慢慢發(fā)展出其他的問答社區(qū)挪蹭,現(xiàn)在一共有一百多社區(qū)浓镜。在這里可以看到所有社區(qū)锭魔。

圖1. StackExchange的一部分社區(qū)
圖1. StackExchange的一部分社區(qū)

[這個]問答給出了SE歷史數(shù)據(jù)的下載地址蜕企。本文給出對SE數(shù)據(jù)的初步處理示例咬荷。

首先定義一些數(shù)據(jù)處理函數(shù):

def dailyQA(site):
    F = defaultdict(lambda:[0,0])
    path='/Users/csid/Documents/bigdata/stackexchange/unzip/'
    filename = path + site + '/Posts.xml'
    with open(filename,'r') as f:
        for line in f:
            try:
                label = line.split('PostTypeId=')[1][1:2]
                day = line.split('CreationDate=')[1][1:11]
                if label == '1':
                    F[day][0]+=1
                if label == '2':
                    F[day][1]+=1
            except:
                pass
    return F

#plot the monthly growth of sites in terms of Na and Nq
def plotMonth(site,ax,col):
    M=defaultdict(lambda:np.array([0,0]))
    f=F[site]
    for i in f:
        M[i[:7]]+=np.array(f[i])
    ms=sorted(M.keys())[1:-1]
    if len(ms)>3:
        x,y = np.array([M[i] for i in ms]).T
        mm=[dt.strptime(j,'%Y-%m') for j in ms]
        #ax.vlines(mm[0], x[0], y[0],color=col,linestyle='-')
        ax.fill_between(mm, x, y,color=col, alpha=0.1)
        ax.plot(mm,x,color="white",linestyle='-',marker='',alpha=0.1)
        ax.plot(mm,y,color="white",linestyle='-',marker='',alpha=0.1)

def plotMonthSpecial(site,ax,col):
    M=defaultdict(lambda:np.array([0,0]))
    f=F[site]
    for i in f:
        M[i[:7]]+=np.array(f[i])
    ms=sorted(M.keys())[2:-1]
    x,y = np.array([M[i] for i in ms]).T
    mm=[dt.strptime(j,'%Y-%m') for j in ms]
    ax.vlines(mm[0], x[0], y[0],color=col,linestyle='-')
    ax.plot(mm,x,color=col,linestyle='-',marker='')
    ax.plot(mm,y,color=col,linestyle='-',marker='')

通過下列代碼得到每個社區(qū)每天新增的問題和答案數(shù)

path='/Users/csid/Documents/bigdata/stackexchange/unzip/'
sites = [ f for f in listdir(path) if f[-1]=='m']
F={}
for i in sites:
    flushPrint(sites.index(i))
    F[i] = dailyQA(i)

好的可視化,需要層次分明轻掩,所以在繪制各個社區(qū)問答數(shù)量增長曲線時幸乒,往往需要排序來決定繪制的先后疊加順序。下列代碼將各個社區(qū)按照總的問答數(shù)量排序唇牧。

# plot good sites at first then plot bad sites
S={}
for i in sites:
    q,a=zip(*F[i].values())
    S[i]=sum(q),sum(a)
rsites=[i for i,j in sorted(S.items(),key=lambda x:-x[1][0])]

然后就可以繪制圖2了

圖1. StackExchange的一部分社區(qū)
圖1. StackExchange的一部分社區(qū)

每條帶子是一個社區(qū)罕扎,上界是每月新增答案數(shù),下界是每月新增問題數(shù)丐重。一共有110個社區(qū)腔召。顏色代表社區(qū)的問答總數(shù)(取對數(shù)再減去5)。我們還可以選擇性地標示出某些社區(qū)扮惦,例如本圖中標示出了物理類(藍色)和烹調(diào)類(深綠色)兩個社區(qū)臀蛛。

繪制代碼為

fig = plt.figure(figsize=(12, 5),facecolor='white')
ax = plt.subplot(111)
years = YearLocator()
cmap = cm.get_cmap('PiYG', 10)
for i in rsites:
    c = int(np.log(S[i][0])-5)
    plotMonth(i,ax,cmap(c))
plotMonthSpecial('physics.stackexchange.com',ax,'RoyalBlue')
plotMonthSpecial('cooking.stackexchange.com',ax,'DarkOliveGreen')
ax.set_yscale('log')
ax.set_ylim(1,10**6)
ax.set_xlabel('Time')
ax.set_ylabel('Monthly increased N of Q&A')
ax.xaxis.set_major_locator(years)
smm = plt.cm.ScalarMappable(cmap=cmap, norm=plt.Normalize(vmin=0, vmax=10)) 
smm._A = []
cbaxes = fig.add_axes([0.15, 0.85, 0.5, 0.015]) 
cbar = plt.colorbar(smm,cax=cbaxes,orientation='horizontal')
plt.show()

接下來,我們考慮使用節(jié)點到源和匯的流距離構(gòu)造一個相空間崖蜜,分析用戶在這個相空間中游走的軌跡產(chǎn)生的角度熵(把在兩個節(jié)點間的每一步跳躍合并到一個原點上掺栅,考察角度的分布)與社區(qū)可持續(xù)發(fā)展之間的關(guān)系。我們的假設(shè)是纳猪,用戶游走的熵越大氧卧,說明用戶越有創(chuàng)造性,對問答社區(qū)的長期發(fā)展也越有利氏堤。

首先要定義一系列函數(shù)

def userDailyAnswers(site):
    C={}
    filename = path + site + '/Posts.xml'
    with open(filename,'r') as f:
        for line in f:
            try:
                label = line.split('PostTypeId=')[1][1:2]
                if label == '2':
                    date = line.split('CreationDate=')[1][1:11]
                    time = line.split('CreationDate=')[1][12:20]
                    author = int(line.split('OwnerUserId=')[1].split(r'"')[1])
                    questionID = int(line.split('ParentId=')[1].split(r'"')[1])
                    if date in C:
                        if author in C[date]:
                            C[date][author]+=[(time,questionID)]
                        else:
                            C[date][author]=[(time,questionID)]
                    else:
                        C[date]={author:[(time,questionID)]}
            except:
                pass
    return C

# calculate entropy of path angles
def entropy(G,O,K,T):
    angles=[]
    for i,j in G.edges():
        #wi = G[i][j]['weight']
        dx,dy = np.array([O[j],K[j]])-np.array([O[i],K[i]])
        dis = LA.norm(np.array([O[j],K[j]])-np.array([O[i],K[i]]))
        if dy>=0:
            angle = np.round(180*np.arccos(dx/dis)/np.pi,1)
        else:
            angle = 360-np.round(180*np.arccos(dx/dis)/np.pi,1)
        angles.append(angle)
    l = len(angles)
    ps=np.array(Counter(angles).values())
    ps=ps/float(ps.sum())
    #ent = -(ps*np.log(ps)).sum()/np.log(l)
    ent = -(ps*np.log(ps)).sum()
    return ent


def getSiteFlowdata(site):
    C=userDailyAnswers(site)
    days=sorted(C.keys())
    E=defaultdict(lambda:0)
    n=0
    maxuser=100
    for day in days[len(days)/2:]:
        d = C[day]
        f = sorted(d.items(),key=lambda x:x[1])
        for i,j in f:
            if n<maxuser:
                n+=1
                q = [p for o,p in j]
                q = ['source']+q+['sink']
                for a,b in zip(q[:-1],q[1:]):
                    E[(a,b)]+=1
    
    G=nx.DiGraph()
    for x,y in E:
        w = E[(x,y)]
        G.add_edge(x,y,weight=w)
    O = flowDistanceFromSource(G)
    K = flowDistanceToSink(G)
    T = G.out_degree(weight='weight')
    return G,O,K,T

# orthogonal okplot
def okplot(G,O,K,T):
    plt.plot([0,4],[0,4],'r-',alpha=0.5)
    for i,j in G.edges():
        wi = G[i][j]['weight']
        x1,y1=O[i],K[i]
        x2,y2=O[j],K[j]
        dx=x2-x1
        dy=y2-y1
        plt.arrow(x1, y1, dx, dy, head_width=0.1, head_length=0.2, fc='gray', ec='gray',alpha=0.2)
        #plt.text(x2,y2,wi,color='brown')
    plt.xlabel(L_{oi},size=16)
    plt.ylabel(L_{ik},size=16)

# rescaled orthogonal okplot
def rescaledokplot(G,O,K,T):
    r = 0
    Dx=0;Dy=0
    tr=0
    for i,j in G.edges():
        wi = G[i][j]['weight']
        x1,y1=O[i],K[i]
        x2,y2=O[j],K[j]
        dx=x2-x1
        dy=y2-y1
        Dx+=dx
        Dy+=dy
        rr = np.sqrt(dx**2+dy**2)
        tr+=rr
        if rr>r:
            r=rr
        plt.arrow(0, 0, dx, dy, head_width=0.05, head_length=0.1, fc='gray', ec='gray',alpha=0.1)
    plt.arrow(0, 0, Dx/float(tr), Dy/float(tr), head_width=0.1, 
              head_length=0.2, fc='red', ec='red',alpha=0.7)
    lim=2
    plt.xlim(-lim,lim)
    plt.ylim(-lim,lim)

接著就可以比較物理和烹調(diào)這兩個規(guī)模相近的社區(qū)沙绝,取其總天數(shù)一半時的一百個用戶產(chǎn)生的游走軌跡的角度熵

i='physics.stackexchange.com'
j='cooking.stackexchange.com'
G1,O1,K1,T1=getSiteFlowdata(i)
G2,O2,K2,T2=getSiteFlowdata(j)
# okplot demo
fig = plt.figure(figsize=(12, 6),facecolor='white')
ax = plt.subplot(121)
okplot(G1,O1,K1,T1)
ax = plt.subplot(122)
okplot(G2,O2,K2,T2)
plt.tight_layout()
plt.show()

得到下圖

物理和烹調(diào)社區(qū)一百個用戶的游走軌跡搏明,橫軸是問題節(jié)點離源的距離,縱軸是其到匯的距離闪檬。問題節(jié)點在此圖中不顯示星著,只以箭頭顯示用戶在節(jié)點之間的跳躍。
物理和烹調(diào)社區(qū)一百個用戶的游走軌跡粗悯,橫軸是問題節(jié)點離源的距離虚循,縱軸是其到匯的距離。問題節(jié)點在此圖中不顯示样傍,只以箭頭顯示用戶在節(jié)點之間的跳躍横缔。
把所有用戶的所有一步跳躍矢量合并到一個原點上以計算角度熵,紅色代表矢量合并的結(jié)果衫哥。矢量合并的結(jié)果一定是一單位長的箭頭以45度角指向右下方茎刚,因為所有用戶的所有游走都是從源開始,到匯結(jié)束撤逢。
把所有用戶的所有一步跳躍矢量合并到一個原點上以計算角度熵膛锭,紅色代表矢量合并的結(jié)果。矢量合并的結(jié)果一定是一單位長的箭頭以45度角指向右下方蚊荣,因為所有用戶的所有游走都是從源開始初狰,到匯結(jié)束。

可以通過下列代碼

entropy(G1,O1,K1,T1),entropy(G2,O2,K2,T2)

來計算得到兩個社區(qū)的熵分別為3.47和2.67互例。物理社區(qū)的熵更大奢入,實際發(fā)展也更好,驗證了我們的假設(shè)敲霍。

接下來俊马,我們考察所有社區(qū)在發(fā)展一半時的一百個用戶記錄,以此預(yù)測其最終發(fā)展規(guī)模

# construct network and calculate path entropy
D={}
for site in sites:
    if site=='ebooks.stackexchange.com' or site=='stackoverflow.com':
        continue
    flushPrint(sites.index(site))
    C=userDailyAnswers(site)
    days=sorted(C.keys())
    E=defaultdict(lambda:0)
    n=0
    maxuser=100
    for day in days[len(days)/2:]:
        d = C[day]
        f = sorted(d.items(),key=lambda x:x[1])
        for i,j in f:
            if n<maxuser:
                n+=1
                q = [p for o,p in j]
                q = ['source']+q+['sink']
                for a,b in zip(q[:-1],q[1:]):
                    E[(a,b)]+=1
    G=nx.DiGraph()
    for x,y in E:
        w = E[(x,y)]
        G.add_edge(x,y,weight=w)
    O = flowDistanceFromSource(G)
    K = flowDistanceToSink(G)
    T = G.out_degree(weight='weight')
    D[site]=entropy(G,O,K,T)

l,a,q=np.array([(D[i],S[i][0],S[i][1]) for i in D if i in S and i!='aviation.stackexchange.com']).T
cs,beta,r2=OLSRegressFit(l,np.log(q))
fig = plt.figure(figsize=(8, 8))
plt.plot(l,q,linestyle='',marker='s',color='RoyalBlue',label='N of Questions')
plt.plot(l,a,linestyle='',marker='^',color='Chocolate',label='N of Answers')
plt.plot(l,np.exp(cs+beta*l),linestyle='-',marker='',color='Brown')
plt.yscale('log')
plt.legend(loc=1,numpoints=1)
plt.xlabel('Entropy of angles', size=16)
plt.ylabel('N of Questions & Answers', size=16)
plt.show()

得到下圖

角度熵與社區(qū)規(guī)模之間的相關(guān)
角度熵與社區(qū)規(guī)模之間的相關(guān)

考察其皮爾遜相關(guān)系數(shù)

pearsonr(l,np.log(q))

得到0.42肩杈,p-value小于0.001柴我。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市扩然,隨后出現(xiàn)的幾起案子艘儒,更是在濱河造成了極大的恐慌,老刑警劉巖夫偶,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件界睁,死亡現(xiàn)場離奇詭異,居然都是意外死亡兵拢,警方通過查閱死者的電腦和手機翻斟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來说铃,“玉大人访惜,你說我怎么就攤上這事嘹履。” “怎么了债热?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵砾嫉,是天一觀的道長。 經(jīng)常有香客問我窒篱,道長焕刮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任墙杯,我火速辦了婚禮配并,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘霍转。我一直安慰自己荐绝,他們只是感情好一汽,可當我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布避消。 她就那樣靜靜地躺著,像睡著了一般召夹。 火紅的嫁衣襯著肌膚如雪岩喷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天监憎,我揣著相機與錄音纱意,去河邊找鬼。 笑死鲸阔,一個胖子當著我的面吹牛偷霉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播褐筛,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼类少,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了渔扎?” 一聲冷哼從身側(cè)響起硫狞,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晃痴,沒想到半個月后残吩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡倘核,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年泣侮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片紧唱。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡活尊,死狀恐怖祖凫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酬凳,我是刑警寧澤惠况,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站宁仔,受9級特大地震影響稠屠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜翎苫,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一权埠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧煎谍,春花似錦攘蔽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至作岖,卻和暖如春唆垃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痘儡。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工辕万, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沉删。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓渐尿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親矾瑰。 傳聞我的和親對象是個殘疾皇子砖茸,可洞房花燭夜當晚...
    茶點故事閱讀 42,700評論 2 345

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,498評論 25 707
  • 隨著Web 2.0時代的到來,網(wǎng)絡(luò)用戶之間的交互關(guān)系開始被重視脯倚,網(wǎng)絡(luò)社區(qū)中以用戶為中心的互動也變得愈發(fā)頻繁渔彰。不少注...
    medisol閱讀 6,398評論 0 26
  • 能走開的都不是最愛,走不開的是命定推正。
    清顧閱讀 134評論 0 1
  • 昨天晚上恍涂,你對我說:“媽媽,我想再補一門課植榕,我只補了一門新概念英語是不夠的再沧。”孩子尊残,媽媽很為你這種努力求上進的學(xué)習(xí)...
    生活饋贈與我閱讀 276評論 2 3
  • 我國發(fā)現(xiàn)的最早的釣魚文物是陜西省西安半坡村發(fā)現(xiàn)的骨制魚釣和黑龍江小興凱湖崗上出土的骨制魚鉤炒瘸,距今大約有六千...
    文澄澈閱讀 483評論 0 3