- 一、案例背景
- 二、明确目标
- 三、数据处理
- 3.1关于数据清洗,略...
- 3.2数据整理
- 四、数据分析
- 五、数据展现
- 5.1 绘制多组柱状图
- 5.2案例展现
- 5.2.1紧接前面提取到含有促进关系(提升度大于 1)的强关联规则,我先来画一下它的支持度及置信度的双组柱状图。
- 5.2.2绘制提升度小于 1 的关联规则柱状图。
- 5.2.3 绘制所有强关联规则的提升度柱状图:
- 总结:
订阅号每天只能推送一次,一次最多可以发 8 篇图文,但这 8 篇文章必须打包在一块,一个含有大海报&大标题的“头条”,以及7个“次条”。公司的业务范围正在迅速扩大,所以在推送文章时,需要考虑到文章类型的多样性,因此,每天的推送排版,他都要考虑不同的文章类型之间的契合性。
要是陈列不当,次条的阅读量就很惨淡。当然,要是选择恰当,头条和次条就能相互促进,表现喜人,只是,这并不常见。
头条一般都是确定的,主要是次条文章的挑选。那针对不同类型的头条文章,到底应该怎么罗列订阅号的“次条”比较好呢?或者,换句话说,读者可能会同时喜欢看什么类型的内容呢?
那公众号阅读行为能否像用户的购买行为一样,能挖掘出不同类型文章之间的关联关系?
答案是肯定的。但问题的关键是,怎么定义阅读行为?
在订阅号的场景下,可以将用户在一天内看的所有文章当作是用户的一次阅读行为。
通过对用户阅读行为的关联分析,就能更多地了解顾客的阅读习惯,从而为订阅号每日推文的组合提供参考。
基于这一目标,以及使用 Apr 算法实现关联分析的流程,我们拆解到如下三个分目标:
从运营那我们拿到了一份记录用户阅读轨迹的数据。公众号用户访问数据.csv
In [ 37 ]
1 import pandas as pd
2 # 以 gbk 的编码模式读取并查看【公众号用户访问数据.csv】数据
3 user_data = pd.read_csv('./工作/公众号用户访问数据.csv',encoding='gbk')
4 user_data
运行
Out [ 37 ]
用户编号 文章类别 阅读数 看一看 点赞 赞赏 被转载 访问日期
0 1 数据分析 20051 1203 2406 80 601 2020/9/21
1 2 数据分析 11690 584 1519 46 233 2020/9/17
2 3 数据分析 5720 400 572 45 228 2020/9/2
3 4 数据分析 22502 1125 3150 45 900 2020/9/24
4 5 数据分析 11201 560 1344 89 448 2020/9/8
... ... ... ... ... ... ... ... ...
194 98 pandas 7454 521 819 29 223 2020/9/20
195 98 pandas 7587 379 910 22 151 2020/9/20
196 99 数据分析 12179 852 1217 48 487 2020/9/23
197 99 pandas 11715 585 1288 46 351 2020/9/23
198 99 matplotlib 11542 692 1269 11 230 2020/9/23
一天内会有多名用户进入订阅号,而一个用户一天内可能阅读了多篇文章
比如2020/9/21这一天,其中3名用户的阅读轨迹如下图所示。同一个用户在同一天阅读的所有文章我记做 1 次阅读,如此,我就有了以下 3 条阅读记录(事务)。
我们分析的是文章类型之间的联系,而不是单篇文章的数量关系,因此,尽管编号为74的用户同时看了2篇Python,这里也只记做1个。
综上,初步判断这份数据是可以达成总目标的,但不能直接进行关联分析,需要对数据进一步处理。
我们的第一个分目标是:获取可以直接进行关联分析的用户数据。
目标 1:获取可以进行关联分析的用户数据,基于这个分目标,我们需要先对原数据进行处理,而处理这一环节,由两个步骤组成:数据清洗和数据整理。
3.1关于数据清洗,略… 3.2数据整理前面,经过我们对目标的探讨,可以直接进行关联分析的数据,理想状态应该是长这样的:
处理这份数据的关键在于:① 提取数据 ② 数据去重 ③数据转换(列表) ④分组聚合
汇集同一个用户阅读的文章类型,也就是数据的合并,可能会联想到用sum()函数,将“文章类型”列进行求和,字符串就会“拼接”起来,像这样:
In [ 39 ]
1 test1 = pd.Dataframe({'班级': [1, 2, 2, 1], '姓名': ['王薇', '刘敏', '陈芳', '袁芬']})
2 test1 = test1.groupby(['班级']).sum()
3 test1
运行
Out [ 39 ]
姓名
班级
1 王薇袁芬
2 刘敏陈芳
数据确实是“并”在一起了,但字符串首尾相连,连个空格都没有,后续不好处理。
问题似乎出在数据的格式上,既然字符串在聚合时会无缝拼接,不如先转换成列表,再来合并。
In [ 40 ]
1 test2 = pd.Dataframe({'班级': [1, 2, 2, 1], '姓名': [['王薇'], ['刘敏'], ['陈芳'], ['袁芬']]})
2 test2 = test2.groupby(['班级']).sum()
3 test2
运行
Out [ 40 ]
姓名
班级
1 [王薇, 袁芬]
2 [刘敏, 陈芳]
怎么把字符串数据转换成列表,
第一步:提取列数据
In [ 41 ] 1 # 提取列数据('用户编号', '文章类别', '访问日期'),并进行赋值 2 user_data = user_data[['用户编号','文章类别','访问日期']] 3 # 查看提取的数据 4 user_data 运行 Out [ 41 ] 用户编号 文章类别 访问日期 0 1 数据分析 2020/9/21 1 2 数据分析 2020/9/17 2 3 数据分析 2020/9/2 3 4 数据分析 2020/9/24 4 5 数据分析 2020/9/8 ... ... ... ... 194 98 pandas 2020/9/20 195 98 pandas 2020/9/20 196 99 数据分析 2020/9/23 197 99 pandas 2020/9/23 198 99 matplotlib 2020/9/23 199 rows × 3 columns
第二步:数据去重
同一时间内,一个用户阅读同类型的文章只能记作一次。
# 查看提取数据后的重复数据 analysis_data[analysis_data.duplicated()] # 去除重复数据 analysis_data = analysis_data.drop_duplicates()
第三步:批量转换数据类型
In [ 178 ]
1
# 定义“格式转换”函数
2
def conversion_data(category):
3
4
# 判断文章类别是否已经转成了列表格式
5
if str(category)[0] == '[':
6
# 直接返回文章类别
7
return category
8
# 返回转成列表格式后的文章类别
9
return [category]
10
11
12
# 获取'文章类别'列,调用 agg() 方法
13
analysis_data['文章类别'] = analysis_data['文章类别'].agg(conversion_data)
14
# 查看处理后的数据
15
analysis_data
运行
Out [ 178 ]
用户编号 文章类别 访问日期
0 1 [数据分析] 2020/9/21
1 2 [数据分析] 2020/9/17
2 3 [数据分析] 2020/9/2
3 4 [数据分析] 2020/9/24
4 5 [数据分析] 2020/9/8
... ... ... ...
193 98 [数据分析] 2020/9/20
194 98 [pandas] 2020/9/20
196 99 [数据分析] 2020/9/23
197 99 [pandas] 2020/9/23
198 99 [matplotlib] 2020/9/23
192 rows × 3 columns
第四步:分组聚合
In [ 179 ] 1 # 根据'访问日期'和'用户编号'进行分组,并聚合'文章类别'列 2 analysis_data = analysis_data.groupby(['访问日期','用户编号']).sum() 3 # 查看整理后的数据 4 analysis_data 运行
这一步,要注意分组条件,既然是对同一访问日期下,相同的用户编号进行聚合,访问日期和用户编号就都是分组的条件
在这里,我们要完成两个分目标,1.确认初始的最小支持度和最小置信度。2.确定最小支持度,最小置信度
如果想根据过往的经验设置最小支持度和最小置信度,那么,很遗憾,这是一个全新的项目,没有“作业”可抄。不妨尝试从业务和数据本身出发,寻找些“蛛丝马迹”。
观察处理过后的数据可知,抽样数据的访问日期均来自于2020 年的 9 月,用户总数和事务总数都为99。假设对访问频繁的文章组合的要求是,至少三天被访问一次。9 月有 30 天,事件发生的概率是:30/3/99 ≈ 0.1,由此,暂定最小支持度为0.1。
至于最小置信度,目前还没有啥头绪,不如先根据apriori()函数的默认值,也就是0.0,先筛选出一波数据,根据结果再做判断。
目标 3:调用 apriori() 函数,获取强关联规则
apriori()函数用法
In [ 185 ]
1 # 执行Apriori 算法
2 results = apriori(adjusted_data['文章类别'], min_support=0.1, min_confidence=0.3)
3
4
# 创建列表
5 extract_result = []
6
7 for result in results:
8
# 获取支持度,并保留3位小数
9
support = round(result.support, 3)
10
11
# 遍历ordered_statistics对象
12
for rule in result.ordered_statistics:
13
# 获取前件和后件并转成列表
14
head_set = list(rule.items_base)
15
tail_set = list(rule.items_add)
16
17
# 跳过前件为空的数据
18
if head_set == []:
19
continue
20
21
# 将前件、后件拼接成关联规则的形式
22
related_catogory = str(head_set)+'→'+str(tail_set)
23
24
# 提取置信度,并保留3位小数
25
confidence = round(rule.confidence, 3)
26
# 提取提升度,并保留3位小数
27
lift = round(rule.lift, 3)
28
29
# 将提取的数据保存到提取列表中
30
extract_result.append(
31
[related_catogory, support, confidence, lift])
32
33
# 将数据转成 Dataframe 的形式
34
rules_data = pd.Dataframe(extract_result, columns=[
35
'关联规则', '支持度', '置信度', '提升度'])
36
37
# 将数据按照“支持度”排序
38
sorted_by_support = rules_data.sort_values(by='支持度')
39
40
# 查看排序后的数据
41
sorted_by_support
运行
Out [ 185 ]
关联规则 支持度 置信度 提升度
0 ['sql']→['Python'] 0.101 1.000 2.302
3 ['Python']→['爬虫'] 0.141 0.326 1.040
4 ['爬虫']→['Python'] 0.141 0.452 1.040
5 ['pandas']→['数据分析'] 0.182 1.000 1.222
6 ['爬虫']→['数据分析'] 0.192 0.613 0.749
1 ['Python']→['数据分析'] 0.323 0.744 0.910
2 ['数据分析']→['Python'] 0.323 0.395 0.910
关系是促进还是抑制,关键是要看它们的提升度。
本次筛选出来的强关联规则提升度大于 1(促进)和小于 1(抑制)的都有,但它们的指导作用是截然相反的,将数据分开来分析是一个明智的选择。
In [ 186 ] 1 # 提取出提升度大于1的数据,并重置数据的索引 2 promoted_rules = sorted_by_support[sorted_by_support['提升度'] > 1].reset_index(drop=True) 3 promoted_rules 运行 Out [ 186 ] 关联规则 支持度 置信度 提升度 0 ['sql']→['Python'] 0.101 1.000 2.302 1 ['Python']→['爬虫'] 0.141 0.326 1.040 2 ['爬虫']→['Python'] 0.141 0.452 1.040 3 ['pandas']→['数据分析'] 0.182 1.000 1.222
有促进作用的关联规则一共有 4 个,其中“爬虫”和“Python”互为促进关系,阅读“sql”文章的用户更有可能阅读“Python”文章,喜欢阅读“pandas”文章的读者大概率也会喜欢阅读“数据分析”。
五、数据展现查看数据的分布或者排序,一般会考虑使用柱状图来呈现。
5.1 绘制多组柱状图这里需要绘制多组柱状图,重点在于规划好各组数据的 x 轴位置。而控制位置的方法,是调整plt.bar(x,height,width)语句的默认参数x。
参数x用于设置 x 轴的坐标,值为序列。如果参数x传入 df 的行索引作为 x 轴的坐标,柱形图的柱体会落在中间位置。
In [ 189 ]
1 import matplotlib.pyplot as plt
2 import warnings
3
4
# 关闭警告显示
5 warnings.filterwarnings('ignore')
6
# 设置中文字体
7 plt.rcParams['font.family'] = ['Source Han Sans CN']
8
9
# 创建df数据
10 hairs = pd.Dataframe([['许钢铁', 6, 5], ['王铁锤', 8, 7], ['林志龙', 9, 7]], columns=['姓名', '去年发量', '今年发量'])
11
12 # 绘制基本柱状图
13 plt.bar(hairs.index, height=hairs['去年发量'], width=0.4)
运行
Out [ 189 ]
当对行索引进行加减运算,就可以改变坐标的位置,实现柱体的左右平移,注意观察代码,以及x轴坐标和柱体的位置。如下:
plt.bar(hairs.index-0.2, height=hairs[‘去年发量’], width=0.4)
柱体宽度为0.4,0.2刚好是柱体的一半,每个行索引值减去 0.2,相当于将柱状图整体向左边挪了半个体积。此时,再增加一组柱状图,以同一套行索引为基础,每个行索引值增加 0.2,向右边移动半个体积,将位置错开,再和前面的柱状图“组装”在一块,你就在一个图上绘制出了多组柱状图。
In [ 191 ]
1 # 设置柱体宽度
2 bar_width = 0.4
3
4 # 使用两次 bar 函数画出两组条形图
5 plt.bar(hairs.index-bar_width/2, height=hairs['去年发量'], width=bar_width)
6 plt.bar(hairs.index+bar_width/2, height=hairs['今年发量'], width=bar_width)
7
8 # 纵坐标和横坐标标题
9 plt.ylabel('发量')
10 plt.xlabel('姓名')
11 # 图形标题
12 plt.title('RY 工作室发量调查结果')
13 # 设置x轴的刻度名称
14 plt.xticks(hairs.index, hairs['姓名'])
运行
Out [ 191 ]
5.2案例展现
回到案例,前面提到,提升度不同,筛选出来的强关联规则含义不同,所以最好分开讨论。
实际上,由于提升度的取值范围和作用不同,有时候也会在绘制图表过程中,将它和支持度、置信度分开来看。
于是乎,我们将数据的可视化分成以下两部分:
1)根据提升度的作用绘制支持度和置信度的双组柱状图。
2)单独绘制所有强关联规则的提升度柱状图。
In [ 192 ]
1 # 功能:绘制提升度大于 1 的强关联规则柱状图
2
3
# 设置画布尺寸
4 plt.figure(figsize=(20, 8))
5
6 # 设置横纵坐标以及柱子的宽度
7 width = 0.2
8
9
# 画出柱状图
10 plt.bar(promoted_rules.index-width/2, promoted_rules['支持度'], width=width)
11 plt.bar(promoted_rules.index+width/2, promoted_rules['置信度'], width=width)
12
13
# 设置图例
14 plt.legend(['支持度', '置信度'], fontsize=20)
15# 设置标题
16 plt.title('促进关系的关联规则的支持度、置信度', fontsize=25)
17 # 设置刻度名称
18 plt.xticks(promoted_rules.index, promoted_rules['关联规则'], fontsize=15)
19 # 设置坐标轴标签
20 plt.xlabel('关联规则', fontsize=20)
21 plt.ylabel('数值', fontsize=20)
5.2.2绘制提升度小于 1 的关联规则柱状图。
In [ 194 ]
1
# 功能:绘制提升度小于 1 的强关联规则柱状图
2
3
# 设置画布尺寸
4
plt.figure(figsize=(20, 8))
5
6
# 画出柱状图
7
plt.bar(restricted_rules.index-width/2, restricted_rules['支持度'], width=width)
8
plt.bar(restricted_rules.index+width/2, restricted_rules['置信度'], width=width)
9
10
# 设置图例
11
plt.legend(['支持度', '置信度'], fontsize=20)
12
# 设置标题
13
plt.title('抑制关系的关联规则的支持度、置信度', fontsize=25)
14
# 设置刻度名称
15
plt.xticks(restricted_rules.index, restricted_rules['关联规则'], fontsize=15)
16
# 设置坐标轴标签
17
plt.xlabel('关联规则', fontsize=20)
18
plt.ylabel('数值', fontsize=20)
输出结果略…
基于这两份结果和公众号的排版需求,可以提出以下3条建议:
1)当“头条”文章是介绍有关“Python”的内容时,不妨考虑搭配“爬虫”的“次条”。
2)当“爬虫”类文章成为“头条”时,可以提高“Python”、降低“数据分析”的“次条”排布。
3)阅读“pandas”的用户大概率也喜欢“数据分析”,可在文章末尾,添加“数据分析”相关的拓展链接,提升阅读量。
In [ 196 ]
1
# 功能:绘制提升度柱状图
2
3
# 设置画布尺寸
4
plt.figure(figsize=(20, 8))
5
6
# 画出柱状图
7
plt.bar(sorted_by_support.index, sorted_by_support['提升度'], width=width)
8
# 设置标题
9
plt.title('提升度柱状图', fontsize=25)
10
# 设置刻度名称
11
plt.xticks(sorted_by_support.index, sorted_by_support['关联规则'], fontsize=15)
12
13
# 设置坐标轴标签
14
plt.xlabel('关联规则', fontsize=20)
15
plt.ylabel('提升度', fontsize=20)
16
17
# 设置数据标签
18
for a, b in zip(sorted_by_support.index, sorted_by_support['提升度']):
19
plt.text(a, b, b, ha='center', va='bottom', fontsize=12)
可以看到,{Python}→{数据分析}、{数据分析}→{Python}的提升度都在 0.91 左右,接近于 1(无影响)。也就是说,喜欢阅读“数据分析”类的读者,可能在一定程度上影响“Python”的阅读量,反之亦然。具体情况,需要进一步分析,如扩大样本量或抽取其他的样本再次进行关联分析。



