【Spark学习】数据清洗

一、数据清洗简介

(一)缺失值处理

填充或删除缺失值。以下代码展示均值填充数值列、众数填充分类列及删除全空行:

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包含三列:col1col2col3,其中某些值为空:

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 : 新列的表达式,通常通过 collit 或运算生成。

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 是构建动态数据转换逻辑的关键工具,适合列级简单操作,复杂场景可结合 selectexpr 使用。

(三)UDF自定义函数

1. UDF(自定义函数)简介

SparkSQL UDF(User Defined Function)允许用户扩展SQL或DataFrame的功能,通过自定义逻辑处理数据。Scala作为Spark的主要开发语言之一,可以方便地实现UDF。


2.UDF的核心概念

  1. 作用:处理SQL或DataFrame中内置函数无法满足需求的场景。
  2. 类型
    • 普通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中使用

通过withColumnselect调用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.注意事项

  1. 性能:UDF会脱离Spark的优化器,可能影响性能。优先使用内置函数。
  2. 序列化:确保UDF逻辑可序列化(避免使用闭包中的不可序列化对象)。
  3. 类型匹配:输入/输出类型需与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()

注意事项

  1. 数据类型一致性:确保所有输入列或表达式具有相同的数据类型,否则会引发错误。
  2. NULL 值处理 :如果任一输入为 NULL,greatest 会返回 NULL。若需忽略 NULL,需先使用 coalescena.fill 处理。
  3. 性能优化:对大规模数据,避免动态生成复杂表达式,建议提前筛选列。

通过以上方法,可以灵活地在 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)
相关推荐
RisunJan1 小时前
【行测】实词辨析
学习
m0_626535202 小时前
双线性插值学习
学习
YJlio2 小时前
进程和诊断工具学习笔记(8.24):Handle——谁占着不放?句柄泄漏排查、强制解锁与检索技巧
服务器·笔记·学习
charlie1145141912 小时前
面向C++程序员的JavaScript 语法实战学习4
开发语言·前端·javascript·学习·函数
wdfk_prog3 小时前
[Linux]学习笔记系列 -- [kernel]trace
linux·笔记·学习
charlie1145141913 小时前
勇闯前后端Week2:后端基础——HTTP与REST
开发语言·网络·笔记·网络协议·学习·http
一 乐4 小时前
学习辅导系统|数学辅导小程序|基于java+小程序的数学辅导小程序设计与实现(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·学习·小程序
走在路上的菜鸟4 小时前
Android学Dart学习笔记第四节 基本类型
android·笔记·学习
陈橘又青4 小时前
CANN在智能安防场景中的落地实践:释放硬件潜能,简化AI开发
人工智能·网络协议·学习·ai·编辑器