应用可视化探索在线教育业务中的数据
作芒,大龄程序员,就职于某在线教育公司研发部。
缘起
疫情以来,越来越多的家长和学生开始主动或者被动的接受在线这种教学方式。在线教育行业迎来了一波流量增长,积累的数据也越来越多。与此同时,越来越的创业者开始进入这个行业,行业内的竞争也越来越激烈。能否高效的利用这些数据,成为一个公司能否装上涡轮发动机迎风起飞的关键因素。
一. 业务背景
在线教育的成交转化流程一般如下:用户报名 -> 上体验课 -> 付费购课 。体验课一般包含 3 - 5 节,每天 1 节。每节课程有若干个模块,模块中会包含各种形式的互动问题。问题是封闭式的,都会有正确答案,用户的每次答题结果会保存下来。
二. 数据维度和数据结构
数据维度划分
- 用户维度
- 家长信息,包括性别、年龄、报名时间、地域信息、报名渠道
- 孩子信息,包括性别、年龄、用户报名之前是否购买过本公司的其他科目的课程
- 用户课程维度
- 是否学完某个课节、模块、问题
- 某个问题是否答对
- 时序维度
- 每一道题的答题反应时间 (听完/看完题目到最终作出回答的时间)
- 第一次答题是否正确,第二次、第三次答题是否正确(答错之后的可以重试)
三. 数据收集和整理
设计矩形数据的字段
为了方便统计,采用最常见的矩形数据来作为分析的基础。具体来说,每个用户在某个体验课下会有一行记录,每一列就是一个特征数据。针对体验课可能有 N 天,N 并不相同的情况,在需要的字段上通过添加数字编号来解决。例如:d1s ( day one state )表示第 1 天的课程完成情况,当完整上完时字段值为 2,未开始时值为 0 ,已开始未完成时是 1。依次类推。数据表共计 40 列。
采集数据
通常数据采集有推流和拉流两种方式,各自的优缺点这里不再赘叙。感兴趣的同学可以画一个损益矩阵选择自己适用的方式。这里我们采用了拉流的方式实现数据采集,每次在业务流量低峰时跑脚本。脚本会先拉取目标用户群,然后循环抽取每个用户的具体数据。在抽取具体数据时并没有大量使用数据库表 join ,也没有过多的使用 where 条件,把计算和逻辑放到本地进行离线计算。得益于 Golang 强大的 GMP 调度器,纯 CPU 计算并不会成为瓶颈。
数据清洗
- 每次分析时先将目标维度的空值字段所在行删除,例如分析孩子年龄时将 0 岁、大于 15 岁的行删除。使用 Pandas 的 dropna 可以很方便的实现数据清洗。
df.dropna(subset=[column_a, column_b])
- 删除 outlier 。使用 Pandas 删除异常值,
clean_df = df.loc[df[column_a] > column_a_bound]
其中 column_a_bound 可以通过 4 分位数、方差、均值等得出。选择合适的值即可;
可视化探索
- 单维度数据的探索 通常性别是一个单维度的数据,适合用饼图来表示:
对应的代码:
plt.figure(figsize=(5,5))
vc = df[‘gender'].value_counts()
y = [vc.values[0], vc.values[1]]
patches,l_text,p_text = plt.pie(y,labels=['女', '男'],
explode=(0.1,0),
colors=['pink','yellowgreen'],
labeldistance = 1.1,
autopct = '%3.1f%%',
shadow = True,
startangle = 90,
pctdistance = 0.6)
for t in l_text:
t.set_size=(30)
for t in p_text:
t.set_size=(20)
# 设置x,y轴刻度一致,这样饼图才能是圆的
plt.axis('equal')
plt.legend()
plt.show()
类似可以探索一下不同性别在样本用户中的比例、在成交用户中的比例。体现多个比例数据适合矩状图。
代码如下:
df_gender_deal = df.loc[df['deal'] == 1]
gender_rate = df['gender'].value_counts()
gender_deal_rate = df_gender_deal['gender'].value_counts()
gender_dist = pd.DataFrame({'all': gender_rate, 'deal': gender_deal_rate})
gender_dist['all_rate'] = gender_dist['all'].apply(lambda x: x / gender_dist.shape[0] * 100)
gender_dist['deal_rate'] = gender_dist['deal'].apply(lambda x: x / df_gender_deal.shape[0] * 100)
x = gender_dist.index
bar_width = 0.35
plt.figure(figsize=(10,5))
plt.bar(x, gender_dist['all_rate'], bar_width, align="center", color="c", label="样本占比", alpha=0.5)
plt.bar(x+bar_width, gender_dist['deal_rate'], bar_width, color="b", align="center", label="成交占比", alpha=0.5)
plt.xlabel("Gender")
plt.ylabel("Percentage")
plt.xticks(x+bar_width/2, x)
plt.legend()
plt.show()
从对比中可以提出假设,男性用户在成交中的比例上升了。上升的比例并不明显,所以我们可以认为性别对成交没有明显的影响。
1.多维度联合探索
单一维度提供的信息有限,有些数据需要跟时间维度一起观察变化趋势。例如:为了衡量第二天的课程有多少人能够坚持到课,我们需要对比第一天的到课用户数占比和第二天的到课用户占比。
其中完课比是到课用户中完成所有课程内容的用户占比,这个数字可以衡量有课程内容和教学环节是否存在问题。上图中可以得出结论:第一天课程的完课比偏低,需要排查原因。
折线图很适合做这种趋势变化的体现,多条折线叠加到同一个图上能够更直观的表现数据。
def course_brief(df):
active_rate = []
finish_rate = []
finish_at_active = []
for i in range(4):
vc = df['d{}'.format(i+1)].value_counts()
n = sum(vc.values)
active_rate.append((vc[1]+vc[2])/n * 100)
finish_rate.append((vc[2])/n * 100)
finish_at_active.append(vc[2]/(vc[1]+vc[2]) * 100)
plt.figure(figsize=(10, 5))
dayx = ['Day_1', 'Day_2', 'Day_3', 'Day_4']
plt.plot(dayx, active_rate, color="r", label="到课率", linestyle='solid', alpha=0.5)
plt.plot(dayx, finish_rate, color="b", label="完课率", alpha=0.5, linestyle='dotted')
plt.plot(dayx, finish_at_active, color="g", label="完课比", alpha=0.5, linestyle='dashed')
plt.legend()
course_brief(df)
2. 降维和特征构造
还有一些信息必须用多维数据表现,可视化效果并不好,我们需要构造特征进行降维。例如:我们想验证封闭式问题的答对率的变化趋势是否和成交有关联,也就是说我们想知道课堂表现越来越好的人更愿意成交还是越来越差的人更愿意成交。
基于原始数据抽象出“首次答对率” 的特征,统计每节课的所有首次答题记录中答对的题目数,该数目在回答过的题目中的比例即视为 “首次答对率”。计算出每节课的首次答对率,得到下列数据:
为了衡量这个数字的变化趋势,我们可以构造两列新的数据,startFR 和 endFR。其中 startFR = (d1fr + d2fr + d3fr) / 3,endFR = (d3fr + d4fr + d5fr) / 3。通过 pandas 可以很容易计算出两列新特征:
df['startFR'] = (df['d1fr'] + df['d2fr'] + df['d3fr']) / 3
df['endFR'] = (df['d3fr'] + df['d4fr'] + df['d5fr']) / 3
成功的将 5 维数据降到 2 维,但是还不够,还可以继续。
df['upRate'] = (df['startFR'] - df['endFR']) * 100 / df['startFR']
这里构造了一个新特征 upRate 来表示 “首次答对率” 的变化趋势,为正时表示 “首次答对率”处于上升趋势,为负时表示下降。数值表示变化的斜率,数字越大表示变化越明显。结合其他数据列,可以画出下列图来探索数据:
上图中灰色的 x 点表示未成交用户,蓝色圆点表示成交用户。X 轴为 upRate,Y 轴为第一节课的首次答对率。虚线为中位数所在的位置。散点图适合探索数据分布,观察数据的关联性。
df_deal = df.loc[df['deal']==1]
df_none = df.loc[df['deal']==0]
plt.scatter(df_deal['upRate'], df_deal['av1'], marker = 'o',color = 'blue', label = 'Deal')
plt.scatter(df_none['upRate'], df_none['av1'], marker = 'x',color = 'gray', label = 'Deal')
plt.axhline(y=df['av1'].median(),ls=":",c="black")#添加水平直线
plt.axvline(x=df['upRate'].median(),ls=":",c="green")#添加垂直直线
plt.show()
四. 总结和问题
- 数据可视化可以更直观的表现出数据趋势、挖掘数据的价值;
- 精细化运营需要从业务的各种环境开始收集数据;例如上面的数据中缺乏有效的数据来表现客户的购买力。
五. 展望
可以从一下几个方向深入的挖掘和使用数据,让数据的价值发挥出来。
产品端:
- 数据可视化是精细化运营的基础,通过可视化数据大盘可以及时发现产品设计、内容、运营策略上存在的问题
- 可视化数据的对比,可以为策略调整提供决策信息;
- 配合数据基线进行数据可视化,可以验证产品方案调整的效果;
销售端:
- 依托数据进行机器学习建模,实现高潜客户的预测和识别,提高销售人员的人效;
- 课程数据的挖掘可以给销售提供钩子信息、促成用户跟销售的对话;
用户端:
- 可以依托数据和算法进行效果外化
- 提供个性化的难度曲线、课程内容、学习路径
- FZU 2167 大王叫我来巡山呐
- HDU 1021 Fibonacci Again
- Hadoop数据分析平台实战——180Oozie工作流使用介绍离线数据分析平台实战——180Oozie工作流使用介绍
- 博弈论及算法实现
- Hadoop数据分析平台实战——160Sqoop介绍离线数据分析平台实战——160Sqoop介绍
- HDU 2186 悼念512汶川大地震遇难同胞——一定要记住我爱你
- Hadoop数据分析平台实战——150Flume介绍离线数据分析平台实战——150Flume介绍
- Codeforces 714A Meeting of Old Friends
- Code forces 719A Vitya in the Countryside
- Hadoop数据分析平台实战——190Highcharts介绍离线数据分析平台实战——190Highcharts介绍
- HUST 1555 A Math Homework
- HUST 1541 Student’s question
- HDU 3785 寻找大富翁
- Hadoop数据分析平台实战——250JSSDK数据收集引擎编写离线数据分析平台实战——250JSSDK数据收集引擎编写
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- iota: Golang 中优雅的常量
- Go template高级用法、深入详解、手册、指南、剖析
- Docker开启Remote API 访问 2375端口
- Docker实用debug调试技巧锦集
- shell 命名管道,进程间通信, ncat作http server
- Linux atop监控
- go modules中使用replace用法
- 分布式文件系统Ceph的挂载方式
- 在CentOS 7上挂载CephFS
- Qt音视频开发14-mpv读取和控制
- 深入理解Nginx的rewrite模块
- max os 安装各种问题/Error解决方法
- 不要启用 net.ipv4.tcp_tw_recycle
- 利用SSH(无密码)免登录来节省你的生命
- 深入linux下磁盘Disk,分区Partition,挂载Mount