基于Spark实现电影点评系统用户行为分析—DataFrame篇(二)

1、介绍

  • Spark SQL有三种不同实现方式:(1)使用DataFrame与RDD结合的方式。(2)纯粹使用DataFrame的方式。(3)使用DataSet的方式。本文仅介绍第二种方式,其他方式可以参考源码(见底部)。
  • DataFrameAPI是从Spark1.3开始就有的,它是一种以RDD为基础的分布式无类型数据集,类似于传统数据库中的二维表格。DataFrame 与 RDD 的注意区别在于,前者带有schema 元信息,即 DataFrame 表示的二维表数据集的每一列都带有名称和类型。
  • DataSetAPI 是从 Spark1.6 版本提出的,DataSet 是强类型,而 DataFrame 实际上是Dataset[Row]。DataSet 是 lazy 级别的,Transformation 级别的算子作用于 DataSet 会得到一个新的 DataSet。当 Action 算子被调用时,Spark 的查询优化器会优化 Transformation 算子形成的逻辑计划,并生成一个物理计划,该物理计划可以通过并行和分布式的方式来执行。

2、业务统计

  • 统计某特定电影观看者中男性和女性不同年龄分别有多少人。
  • 统计电影中平均得分最高(口碑最好)的电影及观看人数最高的电影(流行度最高)。

3、代码实现

1)数据读取。

    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("MovieUsersAnalyzerDataFrame")
    /**
      * SparkSession统一了Spark SQL执行时候的不同的上下文环境,也就是说Spark SQL无论运行在那种环境下我们都可以只使用
      * SparkSession这样一个统一的编程入口来处理DataFrame和DataSet编程,不需要关注底层是否有Hive等。
      */
    val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
    //从SparkSession获得的上下文,这是因为我们读原生文件的时候或者实现一些Spark SQL目前还不支持的功能的时候需要使用SparkContext
    val sc: SparkContext = spark.sparkContext
    val dataPath: String = "hdfs://hadoop3:8020/input/movieRecom/moviedata/medium/"
    val outputDir: String = "hdfs://hadoop3:8020/out/movieRecom_out2"
    val usersRDD: RDD[String] = sc.textFile(dataPath + "users.dat")
    val moviesRDD: RDD[String] = sc.textFile(dataPath + "movies.dat")
    val occupationsRDD: RDD[String] = sc.textFile(dataPath + "occupations.dat")
    val ratingsRDD: RDD[String] = sc.textFile(dataPath + "ratings.dat")
    val ratings: RDD[(String, String, String)] = ratingsRDD.map(_.split("::"))
      .map(x => (x(0), x(1), x(2))).cache()

2)统计某特定电影观看者中男性和女性不同年龄分别有多少人。

	println("通过DataFrame实现某特定电影观看者中男性和女性不同年龄分别有多少人?")
    // 使用Struct方式把Users的数据格式化,即在RDD的基础上增加数据的元数据信息
    val schemaforusers: StructType = StructType("UserID::Gender::Age::OccupationID::Zip-code".split("::")
      .map(column => StructField(column, StringType, true)))
    // 把我们的每一条数据变成以Row为单位的数据
    val usersRDDRows: RDD[Row] = usersRDD.map(_.split("::")).map(line => Row(line(0).trim, line(1)
      .trim, line(2).trim, line(3).trim, line(4).trim))
    // 结合Row和StructType的元数据信息基于RDD创建DataFrame,这个时候RDD就有了元数据信息的描述
    val usersDataFrame: DataFrame = spark.createDataFrame(usersRDDRows, schemaforusers)

    val schemaforratings: StructType = StructType("UserID::MovieID".split("::")
      .map(column => StructField(column, StringType, true)))
      .add("Rating", DoubleType, true)
      .add("Timestamp", StringType, true)

    val ratingsRDDRows: RDD[Row] = ratingsRDD.map(_.split("::")).map(line => Row(line(0).trim, line(1).trim
      , line(2).trim.toDouble, line(3).trim))
    val ratingsDataFrame: DataFrame = spark.createDataFrame(ratingsRDDRows, schemaforratings)

    //使用Struct方式把Users的数据格式化,即在RDD的基础上增加数据的元数据信息
    val schemaformovies: StructType = StructType("MovieID::Title::Genres".split("::")
      .map(column => StructField(column, StringType, true)))
    //把我们的每一条数据变成以Row为单位的数据
    val moviesRDDRows: RDD[Row] = moviesRDD.map(_.split("::")).map(line => Row(line(0).trim,
      line(1).trim, line(2).trim))
    //结合Row和StructType的元数据信息基于RDD创建DataFrame,这个时候RDD就有了元数据信息的描述
    val moviesDataFrame: DataFrame = spark.createDataFrame(moviesRDDRows, schemaformovies)

    ratingsDataFrame.filter(s"MovieID = 1193") // 这里能够直接指定MovieID的原因是DataFrame中有该元数据信息!
      .join(usersDataFrame, "UserID") // Join的时候直接指定基于UserID进行Join,这相对于原生的RDD操作而言更加方便快捷
      .select("Gender", "Age")  // 直接通过元数据信息中的Gender和Age进行数据的筛选
      .groupBy("Gender", "Age") // 直接通过元数据信息中的Gender和Age进行数据的groupBy操作
      .count()  // 基于groupBy分组信息进行count统计操作
      .show(10) // 显示出分组统计后的前10条信息

3) 统计电影中平均得分最高(口碑最好)的电影及观看人数最高的电影(流行度最高)。
代码中第5行可以查看Spark SQL解析DataFrame的过程。

  • 第一,经sql解析器词法分析生成未解析的逻辑计划,从[UserID#4, MovieID#5, Rating#6, Timestamp#7]中投影选择未解析的两列数据:电影ID,评分数据。
  • 第二,通过语法分析器,形成解析以后的逻辑计划。取到电影ID、评分数据,即 [MovieID#5, Rating#6]。
  • 第三,经优化器进行优化,生成优化以后的逻辑计划。这里仅做了 select 简单操作,不用优化。
  • 第四,然后通过 Spark 计划,生成物理计划。
1   import spark.sqlContext.implicits._
2   println("通过纯粹使用DataFrame方式计算所有电影中平均得分最高(口碑最好)的电影TopN2:")
3   ratingsDataFrame.select("MovieID", "Rating").groupBy("MovieID")
4      .avg("Rating").orderBy($"avg(Rating)".desc).show(10)
5   ratingsDataFrame.select("MovieID", "Rating").explain(true)
6   
7   println("纯粹通过DataFrame的方式计算最流行电影即所有电影中粉丝或者观看人数最多(最流行电影)的电影TopN:")
8   ratingsDataFrame.groupBy("MovieID").count()
9      .orderBy($"count".desc).show(10)

4) 源码和数据
https://github.com/fengqijie001/movieRecommendation

希望可以帮到各位,不当之处,请多指教~?

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值