SparkCore项目之用户行为数据分析经验总结

数据格式

在这里插入图片描述

样例类

//用户访问动作表
case class UserVisitAction(date: String,//用户点击行为的日期
                           user_id: Long,//用户的ID
                           session_id: String,//Session的ID
                           page_id: Long,//某个页面的ID
                           action_time: String,//动作的时间点
                           search_keyword: String,//用户搜索的关键词
                           click_category_id: Long,//某一个商品品类的ID
                           click_product_id: Long,//某一个商品的ID
                           order_category_ids: String,//一次订单中所有品类的ID集合
                           order_product_ids: String,//一次订单中所有商品的ID集合
                           pay_category_ids: String,//一次支付中所有品类的ID集合
                           pay_product_ids: String,//一次支付中所有商品的ID集合
                           city_id: Long)//城市 id
// 输出结果表
case class CategoryCountInfo(var categoryId: String,//品类id
                             var clickCount: Long,//点击次数
                             var orderCount: Long,//订单次数
                             var payCount: Long)//支付次数
//伴生对象
object Project_demand2 {
  //1.创建SparkConf并设置App名称
  val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

  //2.创建SparkContext,该对象是提交Spark App的入口
  val sc: SparkContext = new SparkContext(conf)

}

需求1:Top10热门品类

需求分析:
1.先求热门品类Top10,就是统计品类下面的:点击数,下单数,支付数后,根据以上信息排名
    统计优先级:点击数 > 下单数 > 支付数
1.1 将数据封装到样例类 UserVisitAction
1.2 结构转换,提取与需求相关的信息 封装到CategoryCountInfo对象中
1.3 聚合点击数,下单数,支付数 => RDD[CategoryCountInfo]
1.4 排序取Top10
class Project_demand2 {
  @After
  def close(): Unit = {
    //5.关闭
    sc.stop()
  }
   @Test
  def test: Unit = {
    val rdd01: RDD[String] = sc.textFile("input/user_visit_action.txt")
      //1.1 将数据封装到样例类 UserVisitAction
    val actionRdd: RDD[UserVisitAction] = rdd01.map {
      line => {
        val infos: Array[String] = line.split("_")
        UserVisitAction(
          infos(0),
          infos(1).toLong,
          infos(2),
          infos(3).toLong,
          infos(4),
          infos(5),
          infos(6).toLong,
          infos(7).toLong,
          infos(8),
          infos(9),
          infos(10),
          infos(11),
          infos(12).toLong
        )
      }
    }

    println("----------------------需求1 : Top10热门品类------------------------------")

    //1.2 结构转换,提取与需求相关的信息 封装到CategoryCountInfo对象中
    //1.2.1 根据每条记录分析:
    // 包含一次点击数(id,1,0,0)
    // 多次下单数(id,0,1,0)...
    // 多次支付数(id,0,0,1)...
    // 可映射出多个CategoryCountInfo对象的集合,然后扁平化 => RDD[CategoryCountInfo]
    val countInfoRdd: RDD[(String, CategoryCountInfo)] = actionRdd.flatMap {
      case action: UserVisitAction => {
        //有点击就统计点击数,没有再统计下单数,再没有就统计支付数
        if (action.click_category_id != -1) {
          List((action.click_category_id.toString, CategoryCountInfo(action.click_category_id.toString, 1, 0, 0)))
        } else if (action.order_category_ids != "null") {
          val orderCountInfos = new ListBuffer[(String, CategoryCountInfo)]()
          val ids: Array[String] = action.order_category_ids.split(",")
          for (elem <- ids) {
            orderCountInfos.append((elem, CategoryCountInfo(elem, 0, 1, 0)))
          }
          orderCountInfos
        } else if (action.pay_category_ids != "null") {
          val payCountInfos = new ListBuffer[(String, CategoryCountInfo)]()
          val ids: Array[String] = action.pay_category_ids.split(",")
          for (elem <- ids) {
            payCountInfos.append((elem, CategoryCountInfo(elem, 0, 0, 1)))
          }
          payCountInfos
        } else {
          Nil
        }
      }
      case _ => {
        Nil
      }
    }
    //1.3 聚合点击数,下单数,支付数 => RDD[CategoryCountInfo]
    //考虑,到此可以使用两种方法
    // 一:使用groupBy分组,组内元素,使用reduce对相同品类的CategoryCountInfo进行聚合
    // 二:由于业务逻辑是累加,可以换成reduceByKey() 分区内(预聚合) 不会影响业务逻辑 (求平均值不可用reduceByKey())
    //显然方法二 更优,但是此时的RDD类型为 RDD[CategoryCountInfo],没有key
    //如果是 RDD[(cateId,CategoryCountInfo)]类型即可,因此需迭代上面的代码
    val countReduceInfoRdd: RDD[(String, CategoryCountInfo)] = countInfoRdd.reduceByKey {
      case (info1, info2) => {
        info1.clickCount = info1.clickCount + info2.clickCount
        info1.orderCount = info1.orderCount + info2.orderCount
        info1.payCount = info1.payCount + info2.payCount
        info1
      }
    }
    //1.4 排序取Top10
    val categoryTop10: Array[CategoryCountInfo] = countReduceInfoRdd.map(_._2).sortBy(countInfo => {
      (countInfo.clickCount, countInfo.orderCount, countInfo.payCount)
    }, false).take(10)

    categoryTop10.foreach(println)

    

需求2:Top10热门品类中每个品类的Top10活跃城市统计

println("----------------------需求2 : Top10热门品类中每个品类的Top10活跃城市统计------------------------------")

    //2.top10品类下再求Top10城市,
    // 需求分析:就是求这些品类在那些城市比较火
    // 2.0 先过滤 top10的品类数据
    // 2.0.1 使用广播变量  一种优化手段
    val categoryIds: Array[String] = categoryTop10.map(_.categoryId)
    val b_categoryIds: Broadcast[Array[String]] = sc.broadcast(categoryIds)
    // 2.0.2 过滤
    val cateTop10Rdd: RDD[UserVisitAction] = actionRdd.filter(
      action => b_categoryIds.value.contains(action.click_category_id.toString))
    // 2.1 将(品类id-城市)绑定在一起,统计次数
    // 2.1.1 结构变换
    val cateAndCity_singleRdd: RDD[(String, Int)] = cateTop10Rdd.map {
      action => {
        ((action.click_category_id + "-" + action.city_id), 1)
      }
    }
    // 2.1.2 统计总数
    val cateAndCity_sumRdd: RDD[(String, Int)] = cateAndCity_singleRdd.reduceByKey(_ + _)
    // 统计好后,求品类下面的城市Top10,即对品类分组,对城市排名
    // 2.3 结构转化: ((品类-城市), sum) => (品类,(城市,sum))
    val cateWithCityAndSum: RDD[(String, (String, Int))] = cateAndCity_sumRdd.map {
      case (k, sum) => {
        val cateIdAndCity: Array[String] = k.split("-")
        (cateIdAndCity(0), (cateIdAndCity(1), sum))
      }
    }
    // 2.4 品类分组,组内排序取Top10
    val cateUnderCityTop10: RDD[(String, List[(String, Int)])] = cateWithCityAndSum.groupByKey().mapValues(datas => {
      datas.toList.sortWith((left, right) => {
        left._2 > right._2
      }).take(10)
    })
    cateUnderCityTop10.collect().foreach(println)

    

需求3:页面单跳转化率

println("----------------------需求3 : 页面单跳转化率------------------------------")

    // 需求分析: 页面单跳转化率
    // 单跳: page1 - page2 、 page2 - page3 、......
    // 转化率 = (page1-page2)次数 / page1 的总次数

    // 1.  计算分母,相当于wordCount
    // 1.1 要统计的页面(1,2,3,4,5,6,7) 过滤数据
    val pageIds = List(1L, 2L, 3L, 4L, 5L, 6L, 7L)
    val b_pageIds: Broadcast[List[Long]] = sc.broadcast(pageIds.init) //分母不需统计page7,最后的运算式为:6-7 / 6
    val actionPageInfoRdd: RDD[UserVisitAction] =
      actionRdd.filter(action => b_pageIds.value.contains(action.page_id))
    //将分母数据整理成 pageId -> sum 的形式
    val pageIdAndSumMap: collection.Map[Long, Long] = actionPageInfoRdd.map(
      action =>
        (action.page_id, 1L))
      .reduceByKey(_ + _).collectAsMap()

    //2.  计算分子
    //2.0 根据用户信息(session_id)分组 : 因为一个session_id对应多个用户动作
    //2.1 组内排序(由于采集的数据可能来自不同的服务器,用户动作顺序会错乱,但是有动作时间
    //    所以只需按照action_time排序, 即可得到page1、page2、page3、page4...的正确顺序)
    val pageAndPageSingleRdd: RDD[((Long, Long), Long)] = actionRdd.groupBy(_.session_id).mapValues {
      datas => {
        val sortList: List[UserVisitAction] = datas.toList.sortWith((left, right) => {
          //自然时间排序
          left.action_time < left.action_time
        })
        //2.2 组内提取关键信息,需形成 (page1-page2)的形式。
        val pageIdList: List[Long] = sortList.map(_.page_id)
        //拉链
        val pageToPage: List[(Long, Long)] = pageIdList.zip(pageIdList.tail)
        val demandPageIds: List[(Long, Long)] = pageIds.zip(pageIds.tail)
        //过滤出 1-2 2-3 3-4 4-5 5-6
        //结构转换 => ((page1,page2),1)的形式。
        pageToPage.filter(demandPageIds.contains(_)).map {
          case (id1, id2) => {
            ((id1, id2), 1L)
          }
        }
      }
    }.flatMap(_._2)     //2.4  去除session_id, 打散成只有(pageN-PageN+1)的数据形式后聚合,统计(pageN-PageN+1)次数
    val pageAndPageSumRdd: RDD[((Long, Long), Long)] = pageAndPageSingleRdd.reduceByKey(_+_)
    //遍历分子((页面1-页面2)次数),分别计算转化率
    // 转化率 = (页面1-页面2)次数 / 页面1 的总次数
	pageAndPageSumRdd.foreach{
      case (pageAndPage,sum) =>{
        val pageId: Long = pageAndPage._1.toLong
        println(pageAndPage + ":" + sum.toDouble / pageIdAndSumMap.getOrElse(pageId,1L))
      }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值