”聚类一时爽,判断两行泪“——这是解决任何一个无监督问题时都会面临的苦恼:最近接到了一个无监督问题的项目——给一群无标签的结构化数据贴标签,随后我便立即展开了工作,首先开始查阅资料,然后把EDA(数据探索) 、特征工程、数据标准等三板斧悉数打出,最后输入'KMeans(n_clusters= num).fit(x)'这个命令行之后胸有成竹,自信得打开手机,边“吃鸡”边等待跑数结果,所谓的数据挖掘工作就……就这!?
——然而,在几分钟之后,映入眼帘的只是一个个'簇'的索引,真是一顿操作猛如虎,定睛一看都是'簇’,看着这一连串‘莫的感情’、没有任何意义的簇的序列 ,到底如何才能根据合适的理由判断哪一簇(类)是学生、哪一簇(类)是教师?
方法一:实际调研总结规律。由于学生群体过于庞大,可以从询问教师群体开始,因为教师授课的同时学生也在听课,所以二者具有一定的关联性,通过总结教师群体用户的行为特征,推测学生用户的行为特征,例如可以询问教师们最早从二月份的哪一日开始使用钉钉,通常在工作日和节假日的具体哪个时段在哪个教育类app比较活跃等等。
随即我便打消了这个念头,首先,找到目标群体——该地区教师用户耗费的成本过大;其次,虽然上课有统一的时间,但是上传课件、打卡等操作时间因人而异,最后可能只得到一个分布平均的数据,不便于总结教师用户行为特征;假如统计上课时间,虽然分布特征明显,但因为和学生用户是统一的,所以也难以区分是教师的行为特征还是学生的。
方法二:发放问卷。让不同的被调查者根据每一簇的行为特征决定该簇用户所属标签的最大可能性,然后根据每一簇的票数分布,判断该簇用户的所属群体。设定这一方法的前提是,聚类之后,簇与簇之间的行为特征差异明显,虽然也可以主观判断,但是说服力不足,让不同的调查者共同决定,可以推测不同群体的行为习惯,增强判断的可信度。(具体执行过程于本文第七节)
废话不多说,开始我们的探索之旅!
1 项目背景
1.1 2020年教育行业井喷式爆发
教育部公布首批通过备案教育应用 钉钉等152款APP在列baijiahao.baidu.com阿里旗下优酷和钉钉发起“在家上课”计划 提供免费课程new.qq.com因为本次疫情各学校纷纷开展线上教学,在通信网络数据上突出了这群高初小学生的行为特征,如使用钉钉打卡、在直播平台的流量使用增多、观看时间有规律、基站位置无改变等等,因此移动通信运营商希望结合以上疫情期间教育类app的使用特征合理推测用户的属性(学生or老师or其他),然后进行精准营销、完成商品套餐推荐等一系列操作,而在仅仅给定用户行为数据的情况下,如何合理地把学生用户从茫茫用户中区分开来,也是一个不小的难题。
1.2 为什么选择钉钉
由图1-1可知,该地区的钉钉用户在二月份15—17日有一段明显的峰值,原来这段时间恰好是该地区网上开学的日子(见南方都市报),这给用户中存在不少学生用户这一论据提供了不小的佐证。在出现峰值的日期之外,钉钉每日的流量使用情况相较不规则,其余教育类app的流量使用情况周期性明显,app之间只有使用的量的差别,挖掘难度较大,而且钉钉在用户数量相对较少的情况产生的流量数据并不低于猿辅导、学而思,有量的支撑,才有挖掘的价值。
1.3 项目要求
在给定的结构化数据下:该地区二月份钉钉用户数量为45万,产生了大约90万条数据,对这90万条数据进行数据清洗、特征工程,然后使用合理的算法,找到目标用户群体——学生。
1.4 项目计划
对于二月份的该地区用户,单独找出钉钉用户进行KMeans聚类后,根据簇的行为特征筛选出学生用户;对于三、四、五月份,找出特地的app(带'家长'/'学生'字眼的客户端,如学而思网校学生版等)用户,设置特定的时段和流量使用最低值,进行规则识别。
2 模型介绍
2.1 什么是KMeans聚类?
k-均值是一种聚类算法。聚类算法根据数据在空间中的分布方式将其分组。聚类是一种非监督学习方法,它不需要任何形式的标签——这种算法的目的就是基于数据本身的结构推测出簇标签。一个简单的例子,假如从事相同体育项目的运动员是同一类运动员,他们在具体的身体特征上是相似的,表现为空间上比较接近,自然得将其归为一类。
具体算法过程见下:
3 数据分析
3.1 app的使用具有明显的时段效应
和生活经验相符,8-18点是人们活动和工作的时间,该时段的数据量最多;19点之后进入下班时间,数据量逐渐减少;0-7点是休息时间,数据量最少。
3.2 重要的时间节点
由图1-1可知,在当日使用的平均流量上,该地区的用户在二月份的15-16期间存在一个明显的峰值,而且这段期间恰好就是该地区中小学的网上开学日。不妨假设,教师用户在开学前就开始使用钉钉进行教学的准备,学生用户多数在开学日当天以及开学日之后才第一次使用钉钉。
4 特征工程
4.1 构建特征
4.1.1 对使用时间进行分箱
把原本取值为0-23的连续变量——使用时间,进行分箱,转化为取值0-2的分类变量
使用时间为0-7的,用0代替;
使用时间为8-18点的,用1代替;
使用时间为19-23点的,用2代替
data['using_time'] = pd.cut(data['using_time'],bins = [-np.inf,8,18,23],labels = False)
4.1.2 对使用时间的翌日进行分类
根据数据量产生的当天是工作日与否先将数据量分开
data_work = data[data[self.daycol].dt.weekday <= 4]
data_holiday = data[data[self.daycol].dt.weekday>4]
4.1.3 根据使用时段和流量进行交互
二月份各个用户在工作日和节假日对应三个时段的使用流量总和
data_bins_flux = data.groupby(['c_usr_nbr','using_time_bins'])['gprs_flux'].sum().unstack()
#'c_usr_nbr'是用户号码这一变量名称
4.1.4 根据时间节点构建0-1变量
因为给定的数据集中 ,有显示每条数据数据的装载时间,为了将此方法推广到后续地区使用,方便起见,直接取二月份平均流量的峰值日为开学日,故将最早使用时间在16号之前的变量取值为1,最早使用时间在16号之后(包括16号)设为0
data['before_schooldate_ornot'] = data['date'].apply(lambda x:1 if x < schooldate else 0)
4.1.5 计算用户在特定时间段的数据量
data_installed_times = data.groupby([self.object_col,'using_time_bins'])['gprs_flux'].count().unstack()
因为数据量出现的次数代表用户使用app的频率,若某时段用户使用app的流量较小,可以把数据装载次数作为代替的参考指标之一。
最终使用的特征有:
['0-7installed_times_holiday','8-18installed_times_holiday', '19-23installed_times_holiday','0-7installed_times_work', '8-18installed_times_work','19-23installed_times_work', '0-7sum_gprs_flux_holiday','8-18sum_gprs_flux_holiday', '19-23sum_gprs_flux_holiday','0-7sum_gprs_flux_work', '8-18sum_gprs_flux_work','19-23sum_gprs_flux_work', 'before_schooldate_ornot']
4.2 离群值的处理以及数据标准化
因为在各个使用时段流量使用的分布上,存在明显的右偏重尾分布,算法进行时容易受到离群值的影响,所以需要使用’对数变换‘将高值区间压缩并扩展至低值区间。
np.log10(data['0-7gprs_flux_holiday']+1)
……
因为K-means聚类算法涉及距离计算,还需要对数值型数据进行标准化,在这里选择‘z-score'数据标准方法,即每个特征各自减去各自的均值再除以各自的标准差,将各个特征的均值转化为0、方差转化为1
(data[num_feas]-data[num_feas].mean()) / data[num_feas].std()
5 建模调参
5.1 超参数——'k'
关于聚类效果的评估标准,现在主要参考的指标有轮廓系数和CH指数(Calinski-Harabaz Index)。考虑到数据量大,轮廓系数计算速度较慢,这里采用‘CH’指数。在进行K从2-20的参数搜索之后,把K决定为15。
5.2 KMeans聚类
由于数据量较大,决定把最大迭代次数设为10000,为了保证每次聚类的效果不变,需要设定‘random_state = 10'
cluster = KMeans(n_clusters = 15,max_iter = 10000,random_state = 10)
6 结果分析
6.1 各个簇的数量
6.2 各个簇的特征
经过多次的迭代计算之后,簇与簇之间行为特征区别较明显,表现为某簇用户会在工作日/节假日的特定时段较为活跃,其他时段在钉钉使用的流量较少,或者干脆不适用钉钉。
例如簇的索引为12的9359位用户,总体用户中只有百分之48的用户在开学前使用钉钉,但是该簇的百分之81的用户在开学前就使用钉钉,说明该簇用户使用钉钉时间较早,而且该簇用户在工作日和节假日的任意时段使用的流量都远大于总体均值,由此可知该簇用户可以说是钉钉的深度使用用户了,根据生活常识,他们有可能是互联网人,但大概率既不是学生、也不是教师。
再看这一簇的152919位用户,只有在工作日的8-18时段才有使用钉钉的痕迹,而且流量不低,其余时间段可以说不打开钉钉。有听直播课经验的读者也许深有体会,学生一般在上课时间才会使用钉钉,其余时间段几乎不会打开钉钉;而且通过实际调研可知,学生的课后作业提交多数通过QQ邮箱发送或者微信拍照的形式,意味着在非上课时间使用钉钉的概率较小。综上,我们有一定的信心可以认为该簇用户有可能是我们需要寻找的学生用户。
以上推断看似合理,但毕竟都是主观推测,说服性较小,因为我一个人的习惯,并不代表所有人的习惯,所以决定设立问卷调查协助分类。
7 发放问卷协助分类
具体问卷见:二月份学生用户识别(准备发放)
问题节选:
果然大部分被调查者和我的想法基本吻合,但是也有人认为该簇用户有可能是教师,也许该部分调查者对教师这个群体充满敬畏,认为教师群体中存在工作狂;但是这并不影响我的大致判断。
再看我们推测有可能为学生的那一簇:
虽然仍然有一部分的被调查者认为也有可能是'教师群体',但是推测该簇用户为学生者居多,说明我的推测大致合理。
因为该项目的目标是找出学生群体,所以统计每一题选择学生的票数分布,每一题的具体票数分布如下:
根据簇1的用户行为习惯,有百分之45的被调查者认为该簇是学生,远远大于其他簇为学生的可能性。
8 项目结论
从项目的二月份原数据集中,筛选出属于‘钉钉’的数据,然后进行特征工程、数据标准化,最后进行KMeans聚类(k=15),找到索引为1的用户便是我们的目标用户 ——学生群体。
由于三月份已经不是开学的月份了,各个app(包括钉钉)的流量使用分布开始有了明显的周期性;而且四月份、五月份不少中小学生开始逐渐回到校园,所以对后续月份的新增用户,决定采用针对特定app产生的数据(明确带'学生'/家长'字眼的客户端,如学而思网校学生版等),进行规则识别的方法:规则的设定参考问卷调查结果,即该特定app用户在'工作日的8-18时段使用流量是否大于总体均值'。
由于涉及用户隐私,数据集概不外传;
需要项目数据分析、特征工程源码的同学可以填完问卷、点赞本文章之后,可以私聊我发送。