模式

模式定义DataFrame 的列明以及列的数据类型,它可以由数据源来定义模式,也可以显式地定义。
在处理CSV和JSON 等纯文本文件时速度较慢。
一个模式是由许多字段构成的StructType。这些字段即为StructField,具有名称、类型、布尔标志(该标志指定该列是否可以包含缺失值或空值),并且用户可指定与该列关联的元数据(metadta)。

  • 例子:创建一个DataFrame 并指定模式:
// 创建一个 DataFrame 并指定模式Schema
    // 模式Schema 由StructField 字段构成
    val myManualSchema = StructType(Array(
      StructField("DEST_COUNTRY_NAME", StringType, true),
      StructField("ORIGIN_COUNTRY_NAME", StringType, true),
      StructField("count", LongType, false, Metadata.fromJson("{\"hello\":\"world\"}"))
    ))
    //
    val df = spark.read.format("json").schema(myManualSchema).load(inputPath)

列和表达式

显示列引用
例子:查找DEST_COUNTRY_NAME这一列数据,并展示两行
函数:select()

// select 函数 和 selectExpr 函数
    df.select("DEST_COUNTRY_NAME").show(2) // 只展示两行

例子:查找:DEST_COUNTRY_NAME 和 ORIGIN_COUNTRY_NAME这两列数据,并展示两行
函数:

// 使用相同格式查询来选择多个列
    df.select("DEST_COUNTRY_NAME", "ORIGIN_COUNTRY_NAME").show(2) // 只展示两行

例子:改列名,使用as 语句,这里展示两种操作,一个是使用select,一个是selectexpr
函数:as()

// 改列名
    df.select(expr("DEST_COUNTRY_NAME AS destination")).show(2)
    df.selectExpr("DEST_COUNTRY_NAME AS destination").show(2)

例子:给列起别名
函数:alias()
用例:用as更改列名 ,然后用alias 改会原来的列名

// 用as列名 ,然后用alias 该会原来的列名
    df.select(expr("DEST_COUNTRY_NAME as destination")).alias("DEST_COUNTRY_NAME")
    //selectExpr 构建复杂表达式来创建DataFrame ,等于上面的代码
    df.selectExpr("DEST_COUNTRY_NAME as destination").alias("DEST_COUNTRY_NAME")

例子:在DataFrame 中增加一个新列withinCountry ,该列描述了destination 和origin 是否相同

//在DataFrame 中增加一个新列withinCountry ,该列描述了destination 和origin 是否相同
    df.selectExpr(
      "*", // 包含所有原始表中的列
      "(DEST_COUNTRY_NAME = ORIGIN_COUNTRY_NAME) as withinCountry")
      .show(2)

例子:使用select 语句,可以利用系统预定义好的聚合函数来指定在整个DataFrame 上的聚合操作。
函数:聚合函数

//使用select 语句,可以利用系统预定义好的聚合函数来指定在整个DataFrame 上的聚合操作。
    println("使用select 语句,可以利用系统预定义好的聚合函数来指定在整个DataFrame 上的聚合操作。:")
    df.selectExpr("avg(count)", "count(distinct(DEST_COUNTRY_NAME))").show(2)
    df.select(expr("avg(count)"),expr( "count(distinct(DEST_COUNTRY_NAME))")).show(2)

例子:将其他语言的类型转换为spark类型给spark 传递显式的值,他们只是一个值而非新列(这可能是一个常量值或者接下来需要比较的值)。通过字面量literal 传递

// 转换操作成Spark 类型(字面量)给spark 传递显式的值,他们只是一个值而非新列(这可能是一个常量值或者接下来需要比较的值)
    // 通过字面量literal 传递
    df.select(expr("*"), lit(1).as("One")).show(2)

例子:添加一列
函数:withColumn

// 添加列
    df.withColumn("numberOne", lit(1)).show(2)

例子:当出发国家与目的地国家相同时,我们将为其设置一个布尔标志。

//当出发国家与目的地国家相同时,我们将为其设置一个布尔标志
    df.withColumn("withinCountry", expr("ORIGIN_COUNTRY_NAME == DEST_COUNTRY_NAME"))

例子:withColumn 有两个参数:列名和为给定行赋值的表达式

//withColumn 有两个参数:列名和为给定行赋值的表达式
    val columns = df.withColumn("Destination", expr("DEST_COUNTRY_NAME")).columns
    println("withColumn:")
    columns.foreach(println)
    println()

例子:重命名列,可以使用withColumnRenamed 实现对列的重命名,WithColumnRenamed 中第一个参数是要被修改的列的名,第二个参数是新的列名。
函数:withColumnRenamed

val rename_columns = df.withColumnRenamed("DEST_COUNTRY_NAME", "dest").columns
    println("重命名列:")
    rename_columns.foreach(println)

例子:删除列
函数:drop()

val drop_columns = df.drop("ORIGIN_COUNTRY_NAME").columns

例子:过滤行
函数:filter 和where 是等价的

df.filter(col("count") < 2).show(2)
    df.where("count < 2").show(2)

例子:我们本能地想把多个过滤条件发到一个表达式中,尽管这种方式可行,但是并不是总是有效的,因为Spark 会同时执行所有过滤操作,不管过滤条件的先后顺序,因此当想指定多个AND 过滤操作时,只要按照先后顺序以链式的方式把这些过滤条件串联起来,然后让Spark 执行。

df.where(col("count") < 2).where(col("ORIGIN_COUNTRY_NAME") =!= "Croatia")
      .show(2)

例子:去重
函数:distinct()
用例:去除DataFrame 中重复的行,使用distinct 方法。找出所有不用的出发国家-目的地国家组合或者所有不同的出发国家。

val ORIGIN_COUNTRY_NAME_DEST_COUNTRY_NAME_count = df.select("ORIGIN_COUNTRY_NAME", "DEST_COUNTRY_NAME").distinct().count()
    val ORIGIN_COUNTRY_NAME_count = df.select("ORIGIN_COUNTRY_NAME").distinct().count()
    println("去重前数量:" + ORIGIN_COUNTRY_NAME_DEST_COUNTRY_NAME_count)
    println("去重后数量:" + ORIGIN_COUNTRY_NAME_count)

例子:随机抽样
函数:sample()
用例:可以使用Datarame 的sample 方法来实现此操作,它按照一定比例从Dataframe 中随即抽取一部分行,也可以通过withReplacement 参数指定是否放回抽样,true 为放回的抽样(可以有重复的样本) ,false 为无放回的抽样(无重复样本)

val seed = 5 //设置随机数种子
    val withReplacement = false // 不放回
    val fraction = 0.5 //分数 比例
    val l2 = df.sample(withReplacement, fraction, seed).count()
    println(l2)

例子:随机分割
函数:randomSplit()
用例:当需要将原始的DataFrame 随机分割成多个分片时,可以使用随机分割。可以用来分割数据集来创建训练集,验证集和测试集。可以设置分割比例。

println("随机分割:")
    // 当需要将原始的DataFrame 随机分割成多个分片时,可以使用随机分割
    // 可以用来分割数据集来创建训练集,验证集和测试集
    // 可以设置分割比例
    val dataFrames = df.randomSplit(Array(0.25, 0.75), seed)
    println(dataFrames(0).count() > dataFrames(1).count()) // False

例子:连接和追加行(联合操作)
函数:union
用例:如果想要向DataFrame 追加行,必须将原始的DataFrame 与新的DataFrame联合起来,就是union 操作,若想联合两个DataFrame ,必须确保他们具有相同的模式和列数。 在Scala 中,需要使用 =!= 运算符,这是因为它不仅能比较字符串,也能够比较表达式。当DataFrame 追加了记录后,需要对产生的新DataFrame 进行引用,一个常见的方法是将这个新的DataFrame 变成视图View 或者注册成一个数据表,以便在代码中使用。

val schema = df.schema
    val newRows = Seq(
      Row("New Country", "Other Country", 5L),
      Row("New Country 2", "Other Country 3", 1L)
    )
    val parallelizedRows = spark.sparkContext.parallelize(newRows)
    val newDF = spark.createDataFrame(parallelizedRows, schema)
    df.union(newDF)
      .where("count = 1")
      .where($"ORIGIN_COUNTRY_NAME" =!= "United States")
      .show() //把它们都拿出来,我们会在最后看到我们的新行
    //    =!= 不等于

例子:行排序
函数:使用sort 和orderby 方法时相互等价的,执行的方式也是一样的,
用例:当对DataFrame 中的值进行排序时,想要获得DataFrame 中的一些最大值或最小值,使用sort 和orderby 方法时相互等价的,执行的方式也是一样的,均接收列表达式和字符串,以及多个列,默认设置时按升序排序。

df.sort("count").show(5)
    df.orderBy("count", "DEST_COUNTRY_NAME").show(5)
    df.orderBy(col("count"), col("DEST_COUNTRY_NAME")).show(5)
       
    // asc 函数升序
    // desc 函数降序
    df.orderBy(expr("count desc")).show(2)
    df.orderBy(desc("count"), asc("DEST_COUNTRY_NAME")).show(2)
    // 高级技巧:可以指定空值在排序列表中的位置,
    // 使用asc_nulls_first 指定空值安排在升序排序的前面,
    // 使用desc_nulls_first 指示空值安排在降序排列的前面,
    // 使用asc_nulls_last 指示空值安排在升序排列的后面,
    // 使用desc_nulls_last 指示空值安排在降序排列的后面

例子:对分区进行内部排序
函数:sortWithinPartitions
用例:处于优化性能的目的,最好在进行别的转换之前,先对每个分区进行内部排序,可以使用 sortWithinPartitions 方法来实现这一操作

// 处于优化性能的目的,最好在进行别的转换之前,先对每个分区进行内部排序,可以使用 sortWithinPartitions 方法来实现这一操作
    val sort_init_df = spark.read.format("json").load("D:\\MySoftWare\\MyDownload\\GithubDownload\\BigDataLearn\\SparkDefinitiveGuide\\src\\main\\resources\\data\\flight-data\\json\\*-summary.json")
      .sortWithinPartitions("count")
    println("sort_init_df 分区数量: " + sort_init_df.rdd.getNumPartitions)

例子:重划分 和 合并
函数:合并操作(coalesce)
用例:另一个重要的优化是根据一些经常过滤的列对数据进行分区,控制跨群集数据的物理布局,包括分区方案和分区数。

// 重划分 和 合并
    // 另一个重要的优化是根据一些经常过滤的列对数据进行分区,控制跨群集数据的物理布局,包括分区方案和分区数。
    // 不管是否必要,重新分区都会导致数据的全面洗牌,如果将来的分区数大于当前的分区数,或者当你想要基于某一组特定的列进行分区时,通常只能重新分区
    val partitions = df.rdd.getNumPartitions
    println("获取分区:" + partitions)

    //设置分区
    df.repartition(5)

    // 如果经常对某一列执行过滤操作,则根据该列进行重新分区是很有必要的
    df.repartition(col("DEST_COUNTRY_NAME"))
    // 还可以指定你想要的分区数量
    df.repartition(5, col("DEST_COUNTRY_NAME"))

    // 合并操作(coalesce) 不会导致数据的全面洗牌,但会尝试合并分区
    df.repartition(5, col("DEST_COUNTRY_NAME")).coalesce(2)