此篇内容为:2.用户留存率的分析、3.活跃用户分析
如需完成2、3的功能实现,须完成1.日志数据清洗篇,并且mysql中须有logDetail日志文件
1.日志数据清洗
2.用户留存分析
3.活跃用户分析
4.将各结果导入mysql
使用工具:IDEA,Maven工程下的Scala项目
关于1.的内容可直接点击此连接跳转:(30条消息) Spark-ETL日志数据清洗分析项目(上)--个人学习解析(保姆级)_weixin_53414609的博客-CSDN博客
二、用户留存率的分析
1)我们首先要理解用户留存率是指什么,1日的用户留存率又该怎么计算
留存率指再次回到产品的用户数量与初始用户数量的比率,在logDetail或test.log中体现为有重复的用户(即userID有重复)并且这个重复用户的actionName值有"Registered"或"Signin",即这个用户在一定时间内即有注册行为又有登陆行为
而一日的用户留存率是指:
当天新增的用户中,新增日之后的第1天还登录的用户数 / 第一天新增总用户数
接着再分析,当天新增用户次日还登陆的用户数是怎么得到的,大体思路是将event_time列取出来然后再分别以actionName为Registered和Signin字段分成两组数据,那么按照”当天新增的用户中,新增日之后的第1天还登录的用户数“的字面意思来理解,就可以得到:
字段为"Registered"的event_time列 = 字段为"Signin"的event_time列 - 1天(毫秒数为86400000)
那么筛选计算出符合以上条件的用户数,再与当天所有有注册行为的用户数作除法即可得到最终结果,代码如下(代码注释已经作了比较详细的解释):
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.execution.datasources.jdbc.JdbcUtils
import org.apache.spark.{SparkConf, SparkContext}
import java.text.SimpleDateFormat
import java.util.Properties
/**
* 用户留存率篇*/
object UserRetention {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("retention")
val sc = SparkContext.getOrCreate(conf)
val spark = SparkSession.builder().config(conf).getOrCreate()
import spark.implicits._
//连接mysql,将清洗出的数据读取出来
val url = "jdbc:mysql://hadoop01:3306/spark_dataclear"
val properties = new Properties()
properties.setProperty("user", "root")
properties.setProperty("password", "123456")
properties.setProperty("driver", "com.mysql.jdbc.Driver")
val detailDF = spark.read.jdbc(url,"logDetail",properties)
//调用java方法SimpleDateFormat
val changeTimeFun = spark.udf.register("changeTime", (x: String) => {
val time = new SimpleDateFormat("yyyy-MM-dd")
.parse(x.substring(0, 10)).getTime //截取第一列日期字段,年-月-日共10位
time
})
//注入所有的注册用户信息(userID,register_time,注册行为)
//过滤出test.log/logDeatil(此处的说明仅为了方便看筛选结果的内容,两个文件内的actionName内容是一致的)中用户行为(actionName)值为“Registered”的
val registDF = detailDF.filter(detailDF("actionName") === ("Registered"))
.select("userUID", "event_time", "actionName") //取出userUID,event_time,actionname
.withColumnRenamed("event_time", "register_time") //将event_time更名
.withColumnRenamed("userUID", "regUID") //将userID更名
val registDF2 = registDF.select($"regUID", changeTimeFun($"register_time") //调用更改时间格式方法并更改字段名
.as("register_date"), $"actionName").distinct()
//填入所有用户登录信息(userUID,signin_time,登陆行为)
val signinDF = detailDF.filter(detailDF("actionName") === ("Signin")) //过滤出test.log/logDeatil中用户行为(actionName)值为“Signin”的
.select("userUID","event_time","actionName") //取出userUID,event_time,actionname
.withColumnRenamed("event_time","signin_time") //将event_time更名
.withColumnRenamed("userUID","signUID") //将userID更名
.distinct()
val signinDF2 = signinDF.select($"signUID",changeTimeFun($"signin_time") //调用更改时间格式方法并更改字段名
.as("signin_date"),$"actionName").distinct()
//过滤出有注册和登录行为的用户,内连接表格的列格式大致为:regUID register_date "Registered" signin_date "Signin"
val joinDF = registDF2.join(signinDF2
,signinDF("signUID") === registDF("regUID")
,joinType = "inner")
//1.一日留存率指:当天新增的用户中,新增日之后的第1天还登录的用户数/第一天新增总用户数
//2.所以等式为 登入时间-1天毫秒数(86400000)=注册时间
//3.因为日期取出是年-月-日格式,所以等式使用恒等是没问题的,不涉及到时-分-秒不等的情况
//frame中存的是符合2.的用户
val frame = joinDF.filter(
joinDF("register_date") === joinDF("signin_date") - 86400000)
.groupBy($"register_date").count()
.withColumnRenamed("count","signcount")
val frame1 = registDF2.groupBy($"register_date").count()
.withColumnRenamed("count","regcount")
frame1.show()
println("这里是显示总注册人数")
//frame2内大致列格式为:signincount regcount
val frame2 = frame.join(frame1,"register_date")
frame2.show()
println("这里是显示登录和注册人数组成的dataFrame")
//将frame2 map成列为【register_date signcount regcount 一日用户留存率】的格式
frame2.map(x => (
x.getAs[Long]("register_date"),
x.getAs[Long]("signcount"),
x.getAs[Long]("regcount"),
x.getAs[Long]("signcount").toDouble / x.getAs[Long]("regcount")
)
).show()
}
}
如需上传结果至mysql,可参考日志清洗篇的代码末段,都是类似的写法
不过这个留存率分析的代码开头部分已经连接过mysql,所以想要将结果上传至mysql应该只需要将frame2用变量接收,然后write进mysql即可,可参考如下写法
val resMap = frame2.map(x => (
x.getAs[Long]("register_date"),
x.getAs[Long]("signcount"),
x.getAs[Long]("regcount"),
x.getAs[Long]("signcount").toDouble / x.getAs[Long]("regcount")
)
)
resMap.show()
resMap.write.mode(saveMode = "overwrite").jdbc(url, "userRetention", properties)
运行留存率代码的部分结果截图:
三、活跃用户分析
这部分相对比较简单,大体思路是将logDetail(可以直接打开test.log查找字段对应也可)里的actionName为BuyCourse(logDetail、test.log中其实不存在这个字段)或StartLearn值的字段筛选出来,再以一定的格式(作者使用Map来规范格式)存放,最后用去重聚合函数
( agg(countDistinct()) )将指定的字段聚合即可
代码如下:
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions._
import java.util.Properties
/**
*活跃用户分析篇*/
object UserActive {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("retention")
val sc = SparkContext.getOrCreate(conf)
val spark = SparkSession.builder().config(conf).getOrCreate()
import spark.implicits._
//连接mysql,将清洗出的数据读取出来
val url = "jdbc:mysql://hadoop01:3306/spark_dataclear"
val properties = new Properties()
properties.setProperty("user", "root")
properties.setProperty("password", "123456")
properties.setProperty("driver", "com.mysql.jdbc.Driver")
val logs = spark.read.jdbc(url, "logDetail", properties)
//将logDeatil中的用户行为为:BuyCourse或者StartLearn的筛选出来
val ds1 = logs.filter($"actionName" === "BuyCourse" || $"actionName" === "StartLearn")
ds1.show(false)
//将ds1 map成列为【userUID event_time】的格式
val ds2 = ds1.map(x => (x.getAs[String]("userUID")
, x.getAs[String]("event_time").substring(0, 10)))
println("这里是显示userUID event_time")
ds2.show(false)
//将ds2按照活跃日期(即对应test.log/logDetail里第一列产生用户行为的日期)分组
//并且对去重聚合ds2的第一列(即userUID)求出不重复的用户人数
ds2.withColumnRenamed("_2", "日期")
.groupBy($"日期").agg(countDistinct($"_1").as("活跃人数")).show()
}
}
如果也想将此结果上传至mysql,那么操作"二、用户留存率的分析"代码末尾处有解释
部分代码结果截图
至此,此项目功能已全部实现