微信群里的二三事(下)

前期,我们将微信聊天记录导出后,并选择微信群聊天记录作为分析对象,进行了一些预处理工作后,从发言频次和发言类型进行了简单的分析。本次,我们从微信群聊的时间偏好进行分析,并选定记录最多的群聊分析该群中最受关注的人物,最后,对文字消息进行分词并绘制词云。

与上期一样,本次数据分析工作在 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 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!