微信群裡的二三事(下)

MirrorTan發表於2020-03-10

前期,我們將微信聊天記錄匯出後,並選擇微信群聊天記錄作為分析物件,進行了一些預處理工作後,從發言頻次和發言型別進行了簡單的分析。本次,我們從微信群聊的時間偏好進行分析,並選定記錄最多的群聊分析該群中最受關注的人物,最後,對文字訊息進行分詞並繪製詞雲。

與上期一樣,本次資料分析工作在 Jupyter 環境下開展,使用到的庫主要有:

  • pandas
  • numpy
  • matplotlib
  • seaborn
  • jieba
  • wordcloud
  • collections

1 聊天的時間偏好

即通過對微信群中不同時間的發言頻次進行統計分析。

# 後續分析需要用到的新庫
from collections import Counter

import jieba
import jieba.analyse
from wordcloud import WordCloud

首先匯入需要使用到的庫並進行簡單的設定。

print('Min Time: ', message.index.min())
print('Max Time: ', message.index.max())
print('Total Message: ', message['real_content'].count())
Min Time:  2018-08-11 13:52:14+08:00
Max Time:  2020-02-16 11:38:21+08:00
Total Message:  155407

即本次分析的微信群聊天記錄發言時間在 2018-08-11 到 2020-02-06 之間,共計 155407 條記錄,下面將從三個時間進度對發言頻次進行分析:

  • 按 ‘%Y-%m’ 精度進行頻次統計;
  • 按 ‘%w’ 精度進行頻次統計;
  • 按 ‘%H’ 進度進行頻次統計。

1.1 按年月的發言統計

month_count = message['real_content'].resample('M', kind='period').count()
month_count.head()
createTime
2018-08    1064
2018-09    4092
2018-10    3214
2018-11    3116
2018-12    4376
Freq: M, Name: real_content, dtype: int64
fig, ax = plt.subplots(figsize=(9, 6))

month_count.plot(kind='bar', ax = ax)

chat times

也即,我們可以明顯得看出從 2019 年 7 月開始,微信群聊天的記錄明顯增加 ~ 實際上是自己加入了一些很活躍的聊天群的緣故。

1.2 每週發言情況

weekday_count = message.groupby(lambda x: x.weekday_name)['real_content'].count()
weekday_count
Friday       27563
Monday       25577
Saturday     12692
Sunday       10291
Thursday     26028
Tuesday      27318
Wednesday    25938
Name: real_content, dtype: int64
weekday_per = weekday_count / weekday_count.sum() * 100
ax = weekday_per.plot.pie(figsize=(6, 6), autopct='%.2f')

ax.set_ylabel('')
plt.title('微信群中一週發言情況')

plt.show()

chat weeks

也即,整體而言,微信群中的發言活躍程度工作日明顯高於週末。

1.3 一天內發言情況

hour_count = message.groupby(message.index.hour)['real_content'].count()
hour_count
createTime
0     1994
1      169
2      155
3       15
4       18
      ... 
19    8381
20    7666
21    5600
22    5791
23    3902
Name: real_content, Length: 24, dtype: int64
fig = plt.figure(figsize=(10, 8))

plt.step(hour_count.index.values, hour_count.values, where='post')
plt.plot(hour_count.index.values, hour_count.values, 'o--')

plt.xticks(hour_count.index)
plt.xlabel('發言時間')
plt.ylabel('發言次數')
plt.title('微信群中每天發言情況')

plt.show()

chat days

我們發現兩個在微信群中發言的高峰期分別出現在 11-12 點以及 17-18 點,大致是在飯點左右,哈哈。

當然,上述三種基於時間的發言計次都是基於所有微信群的,如果要對特定的微信群的聊天時間習慣進行分析,只需將 message.talker 限定為指定的微信群即可。

2 微信群裡最受關注的人物

微信群的聊天記錄,我們可以依據「聊天是否連續」進行分段,當某個人參與到這段聊天后,這段聊天參與的人數明顯增多、聊天記錄總量明顯變多。我們就認為,這個人在群裡受到比較多的關注。

target_group =  message.groupby('talker')['real_content'].count().idxmax()
target_message = message[message.talker == target_group]

print('聊天記錄最多的微信群:', target_group)
print('   該群聊天記錄總數:', target_message['real_content'].count())
聊天記錄最多的微信群: 12***92@chatroom
   該群聊天記錄總數: 79606

2.1 聊天記錄「分段」

選定聊天記錄最多的群進行分析,將兩次聊天的時間差作為 target_message 的新列 diff_second,設定為 60 秒(實際上就是不太好找拆分的時間點隨便選的)。

diff_second = (target_message.index[1:] - target_message.index[0:-1]).values / 1e9
diff_second = diff_second.astype('d')

target_message['diff_second'] = np.concatenate(([0.], diff_second))

len(diff_second[diff_second <= 60]) / target_message['real_content'].count()
0.8264578046880888

兩次聊天間隔在 60 秒以內佔比達到 82.65% 左右,拆分時也具備一定的合理性。

target_message['talk_segid'] = np.where(target_message['diff_second'] <= 60, 0, 1).cumsum()

target_message = target_message[['isSend', 'message_type', 'username', 'real_content', 
                                 'diff_second', 'talk_segid']]

target_message.talk_segid.value_counts().value_counts().sort_index()
1      5370
2      2722
3      1447
4       883
5       539
       ... 
371       1
378       1
527       1
638       1
925       1
Name: talk_segid, Length: 146, dtype: int64

藉助於 cumsum() 實現分段標記,並選取後續要用到的資料列。也即,有 5370 次聊天與上一句聊天和下一句聊天的時間差均超過 60 秒,佔聊天總記錄數的 6.75% 左右。連續聊天最長在 925 句。

2.2 發言影響力計算

def impact_factor(df):
    '''
    計算一段發言中每個人的影響力
    '''
    impact_list = []
    if len(df) == 1:
        impact_list.append([df.username.iloc[0], 0, 0])
    else:
        names = df.username.unique()
        for name in names:
            # 第一次發言的位置
            first_index = np.argmax(df.username == name) - df.index.min()
            diff_people = len(df[first_index+1:].username.unique()) - len(df[:first_index+1].username.unique())
            diff_talks  = len(df) - first_index*2 - 1
            impact_list.append([name, diff_people, diff_talks])

#     print('第 {} 段對話處理完成'.format(df.talk_segid.iloc[0]))
    return pd.DataFrame(impact_list, columns=['username', 'diff_people', 'diff_talks'])

計算一個人在這段聊天中第一次發言前後「參與發言人數變化、發言數量變化」。

  • 計算髮言前後參與聊天的人數差;
  • 計算髮言前後聊天總量的差;

說明:此處發言前後以一個人在該段聊天中第一次發言為準;聊天密度採用聊天時間間隔平均值。

impact = target_message.reset_index().groupby('talk_segid').apply(impact_factor)

impact_people_mean = impact.groupby('username')['diff_people'].mean()
impact_talks_mean  = impact.groupby('username')['diff_talks'].mean()

talks = target_message[['username', 'talk_segid']].drop_duplicates().groupby('username')['talk_segid'].count()

分別計算參與聊天的每個人在所有聊天的參與次數 talks,參與每段聊天影響的人數差的均值 impact_people_mean,參與每段聊天影響的聊天記錄的均值 impact_talks_mean

talks = pd.merge(pd.merge(talks, impact_people_mean, left_index=True, right_index=True),
                 impact_talks_mean, left_index=True, right_index=True)
talks.columns = ['total_talks', 'diff_people_mean', 'diff_talks_mean']
talks
username total_talks diff_people_mean diff_talks_mean
FJ***21 20 -0.950000 -5.900000
Ha***73 32 -0.093750 1.500000
Hx***55 1166 0.845626 9.343911
JA***37 76 -0.828947 0.763158
JJ***er 4 -2.500000 -1.500000
zj***mc 1 11.000000 37.000000
zl***69 186 0.032258 2.365591
zs***24 11 0.727273 7.090909
zs***41 43 0.232558 8.209302
zw***247459 14 -0.785714 -0.214286

313 rows × 3 columns

2.3 最受關注人選

fig = plt.figure(figsize=(10, 8))
talks['sqrt_total_talks'] = np.sqrt(talks.total_talks)

sns.scatterplot(x='sqrt_total_talks', y='diff_people_mean', hue='diff_people_mean',
                size='diff_people_mean', data=talks)

plt.annotate('target', xy=(5, 10), xytext=(10, 15), 
             arrowprops=dict(width=1, headwidth=5))
plt.show()

diff people mean

因為群中部分成員很活躍,因此將參與的「聊天段」開方後畫散點圖。

實際上,計算髮言引起的人數差和聊天量差都有一個明顯的 bug ~ 如果一個人發言次數很少,並且緊隨受大家關注的人發言(或者在熱點話題討論時發言較早),那麼相應的 diff_people_mean, diff_talks_mean 值就比較大。

發言次數較多後,發言人是否受到大家的關注用 diff_people_mean, diff_talks_mean 來度量就有一定的合理性了,如兩圖中 target 點。

fig = plt.figure(figsize=(10, 8))

sns.scatterplot(x='sqrt_total_talks', y='diff_talks_mean', hue='diff_talks_mean',
                size='diff_talks_mean', data=talks)

plt.annotate('target', xy=(5, 95), xytext=(10, 150), 
             arrowprops=dict(width=1, headwidth=5))
plt.show()

diff talks mean

print('發言五次及以上最受關注人:', talks[talks.total_talks >= 5]['diff_people_mean'].idxmax())
print('發言五次及以上話題引領人:', talks[talks.total_talks >= 5]['diff_talks_mean'].idxmax())
發言五次及以上最受關注人: ad****47
發言五次及以上話題引領人: ad****47

也即,'ad****47' 發言後發言的人和發言量都比較多 ~ 實際上這個群也是因他而建立的,與預期相符。

talks.loc['ad****47']
total_talks         23.000000
diff_people_mean     9.695652
diff_talks_mean     92.565217
sqrt_total_talks     4.795832
Name: ad814156147, dtype: float64

也即,'ad***47' 總共參與 23 次聊天,平均每次聊天后有 9 個多人會出來冒泡、讓發言量增加 92 次左右。

最終評選出 'ad****47' 為「整個群裡最靚的崽」!

3 聊天的用詞偏好

最後,我們利用 target_message 中的聊天記錄簡單做個詞雲。主要使用 jieba 進行分詞,使用 wordcloud 庫繪製詞雲圖。

3.1 分詞

chats = target_message[target_message.message_type == '文字']['real_content']

chats
createTime
2019-07-01 22:04:12+08:00                                昨天的圖片還在嗎
2019-07-01 22:04:17+08:00                                    各位大佬們
2019-07-01 22:10:03+08:00                                     出現了
2019-07-01 22:10:20+08:00                                     ????
2019-07-01 22:10:28+08:00                                     謝謝
                                             ...                  
2020-02-16 10:30:54+08:00                                     ??
2020-02-16 10:38:48+08:002020-02-16 10:43:05+08:00                                 我感覺我回不去了
2020-02-16 11:33:02+08:00                             健康碼就能進,也不用隔離了。
2020-02-16 11:36:42+08:00                                綠的健康碼就可以了
Name: real_content, Length: 69171, dtype: object

進行分詞時僅選擇 '文字' 類的聊天記錄。

為新增微信表情所轉化成的文字新增到字典中,如 '[捂臉], [奸笑], [笑哭]' 等,需對 jieba 庫的 __init__.py 檔案進行修改,具體可參見 https://github.com/fxsjy/jieba/issues/423.

jieba.load_userdict('wechat_emoji_dict.txt')
jieba.analyse.set_stop_words('stop_words.txt')
Building prefix dict from the default dictionary ...
Dumping model to file cache /var/folders/bf/yt56193d35lbzfh4rn_xxct40000gn/T/jieba.cache
Loading model cost 0.966 seconds.
Prefix dict has been built successfully.

在詞典中新增微信表情符,並去除掉一些停用詞,藉助於 collections.Counter 簡化詞頻統計。

word_counts = Counter()
for chat in chats:
    word_counts.update(jieba.analyse.extract_tags(chat))

word_counts = pd.Series(word_counts)
word_counts.sort_values().tail()
不是      1402
這個      1787
[奸笑]    1818
可以      1825
[捂臉]    2783
dtype: int64

3.2 詞雲製作

藉助於 wordcloud 繪製詞頻最高的兩百個詞的詞雲。

wc = WordCloud(font_path='/fontpath/simhei.ttf', background_color="white", repeat=False)
wc.fit_words(word_counts.sort_values()[-200:])

fig, ax = plt.subplots(figsize=(10, 8))

ax.axis('off')
ax.imshow(wc)

word cloud

那我們微信群聊天記錄分析就到這裡啦。

4 參考資料

  1. 韋陽的部落格, 韋陽, 微信聊天記錄匯出為電腦txt檔案教程, 2020/3/2.
  2. hangcom寫字的地方, hangcom, 微信聊天記錄匯出–釋出, 2020/3/2.
  3. Asher117, 【Python】DataFrame一列拆成多列以及一行拆成多行, 2020/3/2.
  4. 風淺安然, Matplotlib及Seaborn中文顯示問題, 2020/3/2.
  5. alpiny, 【分享】好多人需要的:關鍵詞帶空格和特殊字元方法~~, 2020/3/9.
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章