模式
模式定义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)