AS07 Collocation for finding enthusiastic commentors#

在文字探勘的第二單元我們介紹了Collocation來找出significant word pair。同樣的方法,我也可以把他改造來找出,在討論板上哪兩個人老是一起出現。你可以想像說在一個討論板中,某一主題一出來,某些人就會突然冒出來開始Comment。並且A下了Comment後,很快的B也會跟著下Comment。我們可以用Collocation的概念來找出這些總是一起出現的Commentors。但社會科學會把這樣的關係稱為Cooccurrence(共現)。

Convert Collocation as Network#

# colab
import pickle
# !wget https://github.com/P4CSS/PSS/raw/master/data/pttpost_20210509_n178.dat -O pttpost_20210509_n178.dat
with open("data/pttpost_20210509_n178.dat", "rb") as fin:
    all_post = pickle.load(fin)
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[1], line 4
      2 import pickle
      3 # !wget https://github.com/P4CSS/PSS/raw/master/data/pttpost_20210509_n178.dat -O pttpost_20210509_n178.dat
----> 4 with open("data/pttpost_20210509_n178.dat", "rb") as fin:
      5     all_post = pickle.load(fin)

File ~/anaconda3/lib/python3.10/site-packages/IPython/core/interactiveshell.py:282, in _modified_open(file, *args, **kwargs)
    275 if file in {0, 1, 2}:
    276     raise ValueError(
    277         f"IPython won't let you open fd={file} by default "
    278         "as it is likely to crash IPython. If you know what you are doing, "
    279         "you can use builtins' open."
    280     )
--> 282 return io_open(file, *args, **kwargs)

FileNotFoundError: [Errno 2] No such file or directory: 'data/pttpost_20210509_n178.dat'
import pandas as pd
df = pd.DataFrame(all_post)
post_df = df[['author', 'title', 'content', 'authorid', 'nickname', 'link', 'timestamp']]
import sqlite3
conn = sqlite3.connect('data/pttpost.db')
post_df.to_sql('posts', conn, if_exists='replace', index=False)
178
print(all_post[0].keys())
print(all_post[0]['comments'][0])
all_post[0]
dict_keys(['author', 'authorid', 'nickname', 'link', 'title', 'timestamp', 'content', 'comments'])
{'tag': '推 ', 'userid': 'shiriri', 'content': ': 有很多了 黑人跟拉丁裔比較高 亞裔跟白人差不多', 'timestamp': ' 05/09 10:59\n'}
{'author': 's72005ming (QQ)',
 'authorid': 's72005ming',
 'nickname': 'QQ',
 'link': 'https://www.ptt.cc/bbs/Gossiping/M.1620528765.A.DBD.html',
 'title': '[問卦] 美國會研究因武漢肺炎死亡的人種嗎?',
 'timestamp': 'Sun May  9 10:52:43 2021',
 'content': '美國是民族大熔爐\n\n全世界各種人種都有\n\n但是依照各個人種做研究又有歧視的問題\n\n以美國這麼重視人權和觀感的國家\n\n會有研究因武漢肺炎死亡人種的論文嗎?\n\n\n好奇如果有哪一個人種死亡數最少\n\n我猜是華裔就是了!\n\n聽說疫情發生後一堆華裔老人連出門剪頭髮都不敢!--',
 'comments': [{'tag': '推 ',
   'userid': 'shiriri',
   'content': ': 有很多了 黑人跟拉丁裔比較高 亞裔跟白人差不多',
   'timestamp': ' 05/09 10:59\n'},
  {'tag': '→ ',
   'userid': 'HELLDIVER',
   'content': ': 有趣的是 剛開始大爆發時 還說亞洲人比較會得武漢病毒',
   'timestamp': ' 05/09 11:01\n'},
  {'tag': '噓 ',
   'userid': 'redsa12',
   'content': ': 網路即時數據就都有按人種按年齡區分的數據了...',
   'timestamp': ' 05/09 11:06\n'},
  {'tag': '→ ',
   'userid': 'redsa12',
   'content': ': 問之前先估狗好嗎 加油好嗎',
   'timestamp': ' 05/09 11:06\n'}]}
# jupyterlab
# import pickle
# # with open("../data/pttpost_20210509_n178.dat", "rb") as fin:
#     all_post = pickle.load(fin)
print(len(all_post))
print(all_post[0])
print("-"*80)
for comment in all_post[5]['comments']:
    print(comment)
178
{'author': 's72005ming (QQ)', 'authorid': 's72005ming', 'nickname': 'QQ', 'link': 'https://www.ptt.cc/bbs/Gossiping/M.1620528765.A.DBD.html', 'title': '[問卦] 美國會研究因武漢肺炎死亡的人種嗎?', 'timestamp': 'Sun May  9 10:52:43 2021', 'content': '美國是民族大熔爐\n\n全世界各種人種都有\n\n但是依照各個人種做研究又有歧視的問題\n\n以美國這麼重視人權和觀感的國家\n\n會有研究因武漢肺炎死亡人種的論文嗎?\n\n\n好奇如果有哪一個人種死亡數最少\n\n我猜是華裔就是了!\n\n聽說疫情發生後一堆華裔老人連出門剪頭髮都不敢!--', 'comments': [{'tag': '推 ', 'userid': 'shiriri', 'content': ': 有很多了 黑人跟拉丁裔比較高 亞裔跟白人差不多', 'timestamp': ' 05/09 10:59\n'}, {'tag': '→ ', 'userid': 'HELLDIVER', 'content': ': 有趣的是 剛開始大爆發時 還說亞洲人比較會得武漢病毒', 'timestamp': ' 05/09 11:01\n'}, {'tag': '噓 ', 'userid': 'redsa12', 'content': ': 網路即時數據就都有按人種按年齡區分的數據了...', 'timestamp': ' 05/09 11:06\n'}, {'tag': '→ ', 'userid': 'redsa12', 'content': ': 問之前先估狗好嗎 加油好嗎', 'timestamp': ' 05/09 11:06\n'}]}
--------------------------------------------------------------------------------
{'tag': '推 ', 'userid': 'bignoob', 'content': ': 邊抱怨邊打啊 台灣人的不都這樣', 'timestamp': ' 05/08 14:32\n'}
{'tag': '→ ', 'userid': 'ethan0419', 'content': ': 好騙', 'timestamp': ' 05/08 14:32\n'}
{'tag': '噓 ', 'userid': 'geesegeese', 'content': ': 感染源不明出現了,你不打我先打', 'timestamp': ' 05/08 14:33\n'}
{'tag': '→ ', 'userid': 's3z15a3z15a', 'content': ': 沒人搶得時候不急,現在人多就開始搶了', 'timestamp': ' 05/08 14:33\n'}
{'tag': '→ ', 'userid': 'ckvir', 'content': ': 你怎知抱怨的人和打的人是同一個?', 'timestamp': ' 05/08 14:33\n'}
{'tag': '推 ', 'userid': 'clv', 'content': ': 為什麼 就跟當初口罩不夠用有人拿內褲當口罩一樣啊', 'timestamp': ' 05/08 14:34\n'}
{'tag': '→ ', 'userid': 'clv', 'content': ': 怕死啊 沒有最好 只好勉強啦', 'timestamp': ' 05/08 14:34\n'}
{'tag': '噓 ', 'userid': 'Dia149', 'content': ': 以前的我ok你先打呢', 'timestamp': ' 05/08 14:41\n'}
{'tag': '→ ', 'userid': 'Dia149', 'content': ': 綠共真的很善變', 'timestamp': ' 05/08 14:41\n'}
{'tag': '推 ', 'userid': 'radi035', 'content': ': 早在爆發前就先打了 不過小道消息月底莫德那會進來', 'timestamp': ' 05/08 14:44\n'}
{'tag': '→ ', 'userid': 'radi035', 'content': ': 所以符合公費施打的人 可以忍忍  等莫德納', 'timestamp': ' 05/08 14:44\n'}

1. Collocation as Cooccurrence#

只要在同一則貼文的comments內,我們把任兩個commentor視為有co-comment,也就是Cooccurrence(共現)的關係。請計算出共現於本資料集中,頻率最高的前20對commentor(必須印出Collocation times作為參考)。

s72005ming	sl11pman	450
loham	sl11pman	450
sl11pman	s72005ming	450
sl11pman	loham	450
cwh0105	sl11pman	360
sl11pman	cwh0105	360
iampig951753	Runna	294
Runna	iampig951753	294
frank355571	sl11pman	270
sl11pman	frank355571	270
NICEGOGO	sl11pman	180
sl11pman	NICEGOGO	180
sl11pman	dawson0130	180
sl11pman	userlance	180
sl11pman	carryton	180
sl11pman	CheshireS	180
sl11pman	vic4580849	180
sl11pman	justeit	180
dawson0130	sl11pman	180
userlance	sl11pman	180
# YOUR CODE SHOULD BE HERE

2. Using MI#

MI的計算方式主要是為了要標準化任一字的出現次數和任兩個字的出現次數的影響。請用MI的方式計算出哪兩個人特別常一起出現在同一則貼文的comments中。請用most_common()印出M前20大MI的Pairs(必須印出MI值作為參考)。

loham sl11pman 450 5.535381
sl11pman loham 450 5.535381
frank355571 sl11pman 270 5.535381
sl11pman frank355571 270 5.535381
NICEGOGO sl11pman 180 5.535381
sl11pman NICEGOGO 180 5.535381
sl11pman dawson0130 180 5.535381
sl11pman CheshireS 180 5.535381
sl11pman vic4580849 180 5.535381
sl11pman justeit 180 5.535381
dawson0130 sl11pman 180 5.535381
CheshireS sl11pman 180 5.535381
vic4580849 sl11pman 180 5.535381
justeit sl11pman 180 5.535381
goddamnhuge sl11pman 90 5.535381
donyin sl11pman 90 5.535381
crazywiwi sl11pman 90 5.535381
kaerusiro sl11pman 90 5.535381
mmrhahaha sl11pman 90 5.535381
EBOD081 sl11pman 90 5.535381

# YOUR CODE SHOULD BE HERE

3. Cooccurrence with distance#

就上述的資料集,我想定義的人與人的關係是「這兩個人老是一前一後出現」,所以我規劃僅計算前後5則以內的comments,也就是說,在同一貼文中,如果A是第一則comment,B是第六則comment,C是第七則,那我不列計A和C的關係,但列計A和B的關係。請用collocation with distance的觀念,計算任兩個comment間的平均距離,並用most_comment()列印出平均距離最短的前二十對commentors。

chen0625-Qinsect	1.500000	2
kenryu-bar1005	1.500000	2
bar1005-jetalpha	1.500000	2
typeklng-GARRETH	1.500000	2
GARRETH-fenix220	1.500000	2
KaiManSo-nikewang	1.500000	2
gwenwoo-s359999	1.500000	2
a410046-apatosaurus	1.500000	2
apatosaurus-t934140225	1.500000	2
username1-TsmcEE	1.500000	2
bigwun73-yheb88	1.500000	2
l88-sali921	1.500000	2
ab4daa-cecille	1.500000	2
kingstongyu-ntlutw	1.500000	2
ntlutw-kid1a2b3c4d	1.500000	2
kuan12065-lazarus1121	1.500000	2
show282-kuosambition	1.000000	2
Yonhao-jump693	1.000000	2
sellgd-smalltwo	1.000000	2
lazarus1121-tenka92417	1.000000	2
# YOUR CODE SHOULD BE HERE

Drawing collocation network#

以下已經提供給你部分不同網絡的視覺化方法和參數調整方法。如果你要看懂每個函式有可能要查閱Networkx的Document,不過這是為了push你去查閱document來理解這些程式碼。

列印出mi值或count值前500大、前1000大、前2000大(會有點吃力)的pairs of user,並觀察該圖型。基於co-commentor的網絡視覺化,你認為這群co-commenter有什麼特性?請多列印幾種版本,並將你的看法寫在以下的ANSWER後:

Construct edgelist dataframe#

import pandas as pd

li = [(u1, u2, user_pair_counts[(u1, u2)], mi) for (u1, u2), mi in pmi_scores.most_common()]
df = pd.DataFrame.from_records(li, columns =['u1', 'u2', 'count', 'mi'])
df
u1 u2 count mi
0 devilscry kinghung88 6 10.892489
1 kinghung88 devilscry 6 10.892489
2 SwordGod duoloveyang 6 10.892489
3 duoloveyang SwordGod 6 10.892489
4 enso kmaj7 6 10.892489
... ... ... ... ...
36349 s505015 jma306 1 2.307526
36350 gt0404 mudee 1 2.277779
36351 mudee gt0404 1 2.277779
36352 marktak iampig951753 1 2.130938
36353 iampig951753 marktak 1 2.130938

36354 rows × 4 columns

Filter high frequency pairs#

li = [(u1, u2, n)for (u1, u2), n in user_pair_counts.most_common() if n > 3]
df = pd.DataFrame.from_records(li, columns =['u1', 'u2', 'n'])
df
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [5], in <cell line: 1>()
----> 1 li = [(u1, u2, n)for (u1, u2), n in user_pair_counts.most_common() if n > 3]
      2 df = pd.DataFrame.from_records(li, columns =['u1', 'u2', 'n'])
      3 df

NameError: name 'user_pair_counts' is not defined
import pandas as pd
import matplotlib.pyplot as plt
li = [(w1, w2, pair_count[(w1, w2)], mi) for (w1, w2), mi in pmi.most_common()]
df = pd.DataFrame.from_records(li, columns =['w1', 'w2', 'count', 'mi'])

import networkx as nx
fig = plt.figure(1, figsize=(30, 30), dpi=60)

G = nx.from_pandas_edgelist(df[:2000],
                            source = 'w1',
                            target = 'w2',
                            edge_attr = 'mi')
widths = nx.get_edge_attributes(G, 'mi')
nodelist = G.nodes()
# nx.draw_kamada_kawai(G,
#                     node_size = 5,
#                     edge_color = "#8833FF",
#                     with_labels = True)
pos = nx.spring_layout(G)
# nx.draw_spring(G,
#                node_size = 5,
#                edge_color = "#8833FF",
#                font_size = 16,
#                with_labels = True)
nx.draw_networkx_nodes(G,pos,
                       nodelist=nodelist,
                       node_size=15,
                       node_color='black',
                       alpha=0.7)
nx.draw_networkx_edges(G,pos,
                       edgelist = widths.keys(),
                       width=list([w/2 for w in widths.values()]),
                       edge_color='black',
                       alpha=0.2)
nx.draw_networkx_labels(G, pos=pos,
                        labels=dict(zip(nodelist,nodelist)),
                        font_color='blue')
plt.box(False)
plt.show()