一、数据清洗简介
(一)缺失值处理
填充或删除缺失值。以下代码展示均值填充数值列、众数填充分类列及删除全空行:
scala
import org.apache.spark.sql.functions._
// 数值列均值填充
val numericCols = Array("age", "salary")
val meanValues = df.select(numericCols.map(mean(_)): _*).first()
val dfFilledNumeric = numericCols.foldLeft(df)((acc, col) =>
acc.withColumn(col, coalesce(col(col), lit(meanValues.getAs[Double](col))))
)
// 分类列众数填充(示例列"department")
val mode = df.groupBy("department").count().orderBy(desc("count")).first().getString(0)
val dfFilled = dfFilledNumeric.na.fill(Map("department" -> mode))
// 删除全空行
val dfCleaned = dfFilled.na.drop("all")
(二)重复数据删除
基于关键列去重:
scala
val dfDeduplicated = df.dropDuplicates("id") // 按id列去重
// 多列联合去重
val dfDeduplicatedMulti = df.dropDuplicates(Seq("id", "transaction_date"))
(三)异常值处理
通过分位数过滤或条件替换处理异常值。示例过滤薪资大于3倍四分位距的数据:
scala
val quantiles = df.stat.approxQuantile("salary", Array(0.25, 0.75), 0.05)
val IQR = quantiles(1) - quantiles(0)
val lowerBound = quantiles(0) - 1.5 * IQR
val upperBound = quantiles(1) + 1.5 * IQR
val dfFiltered = df.filter(col("salary") > lowerBound && col("salary") < upperBound)
(四) 数据类型转换
统一数据类型确保一致性:
scala
val dfConverted = df.withColumn("age", col("age").cast("integer"))
.withColumn("join_date", to_date(col("join_date_str"), "yyyy-MM-dd"))
(五)字符串清洗
使用正则表达式和内置函数标准化文本:
scala
val dfCleanedText = df.withColumn("name", trim(regexp_replace(col("name"), "[^a-zA-Z\\s]", "")))
.withColumn("email", lower(col("email")))
二、前置基础知识准备
(一)coalesce函数简介:
coalesce是Spark SQL中一个常用的函数,用于从多个列或表达式中返回第一个非空值。该函数在数据处理中非常实用,特别是在处理可能包含空值的数据时。
1. 基本语法
在Scala中使用Spark SQL的DSL风格时,coalesce函数的语法如下:
scala
import org.apache.spark.sql.functions.coalesce
val result = df.select(coalesce(col("column1"), col("column2"), ...))
2.参数说明
- 参数可以是多个列名、表达式或常量值。
- 函数会从左到右依次检查每个参数,返回第一个非空值。
- 如果所有参数均为空,则返回
null。
3.使用示例
假设有一个DataFrame包含三列:col1、col2和col3,其中某些值为空:
scala
import org.apache.spark.sql.functions._
import spark.implicits._
val data = Seq(
(Some(1), None, Some(3)),
(None, Some(5), None),
(None, None, None)
).toDF("col1", "col2", "col3")
// 使用coalesce函数
val result = data.select(coalesce(col("col1"), col("col2"), col("col3")).alias("result"))
result.show()
4.输出结果
+------+
|result|
+------+
| 1|
| 5|
| null|
+------+
5.注意事项
coalesce函数在Spark SQL和DataFrame API中均可使用。- 该函数通常用于数据清洗或填充默认值。
- 性能上,
coalesce比复杂的条件表达式(如when/otherwise)更高效。
(二)withColumn函数
withColumn 是 SparkSQL DataFrame API 中的一个核心函数,用于添加新列或替换现有列。通过 Scala 语言调用时,它可以结合表达式或用户定义的函数(UDF)实现动态列操作。
1. 基本语法
scala
def withColumn(colName: String, col: Column): DataFrame
- colName: 新列名称(若与现有列名相同,则替换原列)。
- col : 新列的表达式,通常通过
col、lit或运算生成。
2.典型用法示例
(1)** 添加新列**
scala
import org.apache.spark.sql.functions._
val df = spark.createDataFrame(Seq(("Alice", 25), ("Bob", 30))).toDF("name", "age")
// 新增列:年龄加1
val dfWithNewCol = df.withColumn("age_plus_one", col("age") + 1)
(2)替换现有列
scala
// 将age列值加倍
val dfReplaced = df.withColumn("age", col("age") * 2)
(3)条件赋值
scala
// 当age大于28时标记为"Senior",否则为"Junior"
val dfWithCondition = df.withColumn("status", when(col("age") > 28, "Senior").otherwise("Junior"))
(4)结合UDF使用
scala
val toUpper = udf((s: String) => s.toUpperCase)
val dfWithUDF = df.withColumn("name_upper", toUpper(col("name")))
3.注意事项
- 性能影响 :频繁调用
withColumn会生成多个中间 DataFrame,可能降低性能。建议链式操作或使用select批量处理。 - 不可变性:Spark DataFrame 不可变,每次操作返回新 DataFrame,原数据不变。
- 列名冲突:若新列名与现有列名相同,原列会被静默替换,无警告提示。
withColumn 是构建动态数据转换逻辑的关键工具,适合列级简单操作,复杂场景可结合 select 或 expr 使用。
(三)UDF自定义函数
1. UDF(自定义函数)简介
SparkSQL UDF(User Defined Function)允许用户扩展SQL或DataFrame的功能,通过自定义逻辑处理数据。Scala作为Spark的主要开发语言之一,可以方便地实现UDF。
2.UDF的核心概念
- 作用:处理SQL或DataFrame中内置函数无法满足需求的场景。
- 类型 :
- 普通UDF:输入一行数据,返回一个值。
- UDAF(聚合函数):输入多行数据,返回一个聚合值(需继承
UserDefinedAggregateFunction)。 - UDTF(表生成函数):输入一行数据,返回多行(SparkSQL暂未直接支持)。
3.实现Scala UDF的步骤
(1)注册UDF
在SparkSession中注册自定义函数,使其能在SQL或DataFrame中使用。
scala
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions.udf
val spark = SparkSession.builder().appName("UDFExample").getOrCreate()
// 定义函数逻辑
val toUpper = (s: String) => s.toUpperCase
// 注册为UDF
val toUpperUDF = udf(toUpper)
(2) 在DataFrame中使用
通过withColumn或select调用UDF。
scala
val df = spark.createDataFrame(Seq(("hello"), ("world"))).toDF("text")
val dfUpper = df.withColumn("upper_text", toUpperUDF(col("text")))
dfUpper.show()
(3)在SQL中使用
需先注册到SparkSQL的临时函数库。
scala
spark.udf.register("sql_upper", toUpper)
df.createOrReplaceTempView("strings")
spark.sql("SELECT text, sql_upper(text) FROM strings").show()
(4)复杂UDF示例
处理多参数或复杂类型(如结构体)。
scala
// 定义多参数函数
val concat = (s1: String, s2: String) => s"$s1-$s2"
spark.udf.register("concat_udf", concat)
// 调用示例
spark.sql("SELECT concat_udf('a', 'b')").show()
4.注意事项
- 性能:UDF会脱离Spark的优化器,可能影响性能。优先使用内置函数。
- 序列化:确保UDF逻辑可序列化(避免使用闭包中的不可序列化对象)。
- 类型匹配:输入/输出类型需与SparkSQL类型系统兼容。
(四)greatest函数
greatest 函数用于返回一组列或表达式中的最大值。以下是在 SparkSQL 中使用 Scala 实现 greatest 函数的详细方法:
示例数据准备
创建一个包含多列的 DataFrame,用于演示 greatest 函数:
scala
import spark.implicits._
val data = Seq(
(10, 20, 30),
(15, 5, 25),
(8, 12, 18)
)
val df = data.toDF("col1", "col2", "col3")
df.show()
输出:
+----+----+----+
|col1|col2|col3|
+----+----+----+
| 10| 20| 30|
| 15| 5| 25|
| 8| 12| 18|
+----+----+----+
使用 greatest 函数
调用 greatest 函数计算每行中多列的最大值:
scala
val result = df.withColumn("max_value", greatest(col("col1"), col("col2"), col("col3")))
result.show()
输出:
+----+----+----+---------+
|col1|col2|col3|max_value|
+----+----+----+---------+
| 10| 20| 30| 30|
| 15| 5| 25| 25|
| 8| 12| 18| 18|
+----+----+----+---------+
结合其他函数使用
greatest 可以与其他列或表达式结合使用。例如,计算列与固定值的最大值:
scala
val resultWithLiteral = df.withColumn("max_with_literal", greatest(col("col1"), lit(15)))
resultWithLiteral.show()
输出:
+----+----+----+---------------+
|col1|col2|col3|max_with_literal|
+----+----+----+---------------+
| 10| 20| 30| 15|
| 15| 5| 25| 15|
| 8| 12| 18| 15|
+----+----+----+---------------+
使用 SQL 语法
如果更熟悉 SQL 语法,可以通过注册临时表后直接执行 SQL 查询:
scala
df.createOrReplaceTempView("temp_table")
val sqlResult = spark.sql("""
SELECT col1, col2, col3, GREATEST(col1, col2, col3) AS max_value
FROM temp_table
""")
sqlResult.show()
注意事项
- 数据类型一致性:确保所有输入列或表达式具有相同的数据类型,否则会引发错误。
- NULL 值处理 :如果任一输入为 NULL,
greatest会返回 NULL。若需忽略 NULL,需先使用coalesce或na.fill处理。 - 性能优化:对大规模数据,避免动态生成复杂表达式,建议提前筛选列。
通过以上方法,可以灵活地在 SparkSQL 中使用 greatest 函数完成多列最大值的计算任务。
5.高级应用:UDAF示例
实现自定义聚合(如求几何平均数)。
scala
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
import org.apache.spark.sql.types._
class GeometricMean extends UserDefinedAggregateFunction {
// 定义输入/输出数据类型
def inputSchema: StructType = StructType(StructField("value", DoubleType) :: Nil)
def bufferSchema: StructType = StructType(StructField("product", DoubleType) :: StructField("count", LongType) :: Nil)
def dataType: DataType = DoubleType
def deterministic: Boolean = true
// 初始化缓冲区
def initialize(buffer: MutableAggregationBuffer): Unit = {
buffer(0) = 1.0 // product
buffer(1) = 0L // count
}
// 更新逻辑
def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
buffer(0) = buffer.getDouble(0) * input.getDouble(0)
buffer(1) = buffer.getLong(1) + 1
}
// 合并逻辑
def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
buffer1(0) = buffer1.getDouble(0) * buffer2.getDouble(0)
buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1)
}
// 最终结果计算
def evaluate(buffer: Row): Double = {
math.pow(buffer.getDouble(0), 1.0 / buffer.getLong(1))
}
}
// 注册并使用
val gm = new GeometricMean
spark.udf.register("geom_mean", gm)