有这样一句话:数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。
在利用SparkML进行特征转换前,使用SparkDataFrame可以很方便的进行提炼数据中的统计类特征 。
下面给出一个利用SparkDataFrame进行二分类问题的特征工程的小栗子。
问题描述:
现有一二分类问题,提供了(用户id,行为,行为日期,等级,标签)原始数据。标签列为Label列,请根据原始数据进行特征工程。
原始数据:
val dataDF = List(
("id1", "click",1,1,1.0),
("id1", "view",2,2,1.0),
("id2", "buy",3,3,0.0),
("id2", "click",1,4,0.0),
("id2", "click",2,5,1.0),
("id3", "buy",3,6,1.0),
("id3", "view",5,7,1.0),
("id3", "view",1,8,1.0),
("id3", "view",5,9,0.0),
("id3", "view",6,10,0.0),
("id5", "view",1,11,1.0),
("id5", "click",3,12,.0)).toDF("id", "action","date","grade","label")
//date列表示该行为出现在第几天
dataDF.show()
要构建的特征,以及使用的方法概要:
- 各行为(action列)频次特征:使用groupBy/agg/alias 方法近统计,使用pivot将各行为展开。
- 各行为转化率(action列)特征:使用withColumn方法新增列。
- 最近行为距今时间间隔/行为平均时间 特征:使用withColumn新增列,groupBy/agg聚合/pivot展开/.toDF重命名列。
- 使用 join 的 left_outer 方法进行特征合并
下面进行详细介绍:
1. 构建 各"action"频次特征
使用groupBy/agg/alias 方法近统计,使用pivot将各行为展开。
//1.新增取值为1的一列,使用agg(sum)聚合得到各行为频次,使用alias重命名得到列"action_count"
val actionFeaTmpDF = dataDF.withColumn("tmp", lit(1)).groupBy("id","action").agg(sum("tmp").alias("action_count"))
//2.展开各行为频次列
val actionFeaDF = actionFeaTmpDF.groupBy("id").pivot("action").agg(sum("action_count"))//再展开得到特征
scala> actionFeaTmpDF.show
+---+------+------------+
| id|action|action_count|
+---+------+------------+
|id1| click| 1|
|id5| click| 1|
|id1| view| 1|
|id2| buy| 1|
|id3| buy| 1|
|id2| click| 2|
|id3| view| 4|
|id5| view| 1|
+---+------+------------+
scala> actionFeaDF.show
+---+----+-----+----+
| id| buy|click|view|
+---+----+-----+----+
|id3| 1| null| 4|
|id5|null| 1| 1|
|id1|null| 1| 1|
|id2| 1| 2|null|
+---+----+-----+----+
2. 构建 各"action"行为转化率特征
使用withColumn方法新增列。
var actiontransformDF = actionFeaDF.withColumn("clickbuyratio",col(s"click")/(col(s"buy")))///点击->购买转化率.withColumn("viewbuyratio",col(s"view")/(col(s"buy"))) //曝光购买转化率
scala> actiontransformDF.show
+---+----+-----+----+-------------+
| id| buy|click|view|clickbuyratio|
+---+----+-----+----+-------------+
|id3| 1| null| 4| null|
|id5|null| 1| 1| null|
|id1|null| 1| 1| null|
|id2| 1| 2|null| 2.0|
+---+----+-----+----+-------------+
3. 构建 最近行为距今时间间隔/行为平均时间特征
使用withColumn新增列,groupBy/agg聚合/pivot展开/.toDF重命名列
//假设今天是第9天
val last_actionFea = dataDF.withColumn("today",lit(9)).withColumn("last_uc_actiondiff",col("today")-col("date")).groupBy("id").pivot("action").agg(min("last_uc_actiondiff")).toDF("id","last_buy_diff","last_click_diff","last_view_diff")
//1. 行为距今时间间隔(用户最近一次行为距今是几天)
scala> last_actionFea.show
+---+-------------+---------------+--------------+
| id|last_buy_diff|last_click_diff|last_view_diff|
+---+-------------+---------------+--------------+
|id3| 6| null| 3|
|id5| null| 6| 8|
|id1| null| 8| 7|
|id2| 6| 7| null|
+---+-------------+---------------+--------------+
//可见,id3最近一次view行为是第6天,last_view_diff = 3
//2. 行为平均时间(用户平均多久进行一次该行为?)
val avg_actionFea = dataDF.withColumn("today",lit(9)).withColumn("last_uc_actiondiff",col("today")-col("date")).groupBy("id").pivot("action").agg(max("last_uc_actiondiff")-min("last_uc_actiondiff") /count("last_uc_actiondiff")).toDF("id","avg_buy_diff","avg_click_diff","avg_view_diff")
scala> avg_actionFea.show
+---+------------+--------------+-------------+
| id|avg_buy_diff|avg_click_diff|avg_view_diff|
+---+------------+--------------+-------------+
|id3| 0.0| null| 7.25|
|id5| null| 0.0| 0.0|
|id1| null| 0.0| 0.0|
|id2| 0.0| 4.5| null|
+---+------------+--------------+-------------+
4. 特征合并
使用 join 的 left_outer 方法进行特征合并
val trainDF = dataDF.join(actionFeaDF,Seq("id"),joinType = "left_outer").join(actiontransformDF,Seq("id"),joinType = "left_outer").join(last_actionFea,Seq("id"),joinType = "left_outer").join(avg_actionFea,Seq("id"),joinType = "left_outer")
这样就得到了我们的训练数据
scala> trainDF.show
+---+------+----+-----+-----+----+-----+----+----+-----+----+-------------+-------------+---------------+--------------+------------+--------------+-------------+
| id|action|date|grade|label| buy|click|view| buy|click|view|clickbuyratio|last_buy_diff|last_click_diff|last_view_diff|avg_buy_diff|avg_click_diff|avg_view_diff|
+---+------+----+-----+-----+----+-----+----+----+-----+----+-------------+-------------+---------------+--------------+------------+--------------+-------------+
|id1| click| 1| 1| 1.0|null| 1| 1|null| 1| 1| null| null| 8| 7| null| 0.0| 0.0|
|id1| view| 2| 2| 1.0|null| 1| 1|null| 1| 1| null| null| 8| 7| null| 0.0| 0.0|
|id2| buy| 3| 3| 0.0| 1| 2|null| 1| 2|null| 2.0| 6| 7| null| 0.0| 4.5| null|
|id2| click| 1| 4| 0.0| 1| 2|null| 1| 2|null| 2.0| 6| 7| null| 0.0| 4.5| null|
|id2| click| 2| 5| 1.0| 1| 2|null| 1| 2|null| 2.0| 6| 7| null| 0.0| 4.5| null|
|id3| buy| 3| 6| 1.0| 1| null| 4| 1| null| 4| null| 6| null| 3| 0.0| null| 7.25|
|id3| view| 5| 7| 1.0| 1| null| 4| 1| null| 4| null| 6| null| 3| 0.0| null| 7.25|
|id3| view| 1| 8| 1.0| 1| null| 4| 1| null| 4| null| 6| null| 3| 0.0| null| 7.25|
|id3| view| 5| 9| 0.0| 1| null| 4| 1| null| 4| null| 6| null| 3| 0.0| null| 7.25|
|id3| view| 6| 10| 0.0| 1| null| 4| 1| null| 4| null| 6| null| 3| 0.0| null| 7.25|
|id5| view| 1| 11| 1.0|null| 1| 1|null| 1| 1| null| null| 6| 8| null| 0.0| 0.0|
|id5| click| 3| 12| 0.0|null| 1| 1|null| 1| 1| null| null| 6| 8| null| 0.0| 0.0|
+---+------+----+-----+-----+----+-----+----+----+-----+----+-------------+-------------+---------------+--------------+------------+--------------+-------------+