You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spark按Instance分区替换前后值一致的连续Error块及统计需求

Spark数据处理需求解决方案(Java/Scala)

我是Spark新手,遇到以下数据处理问题多日未解决,寻求Java或Scala实现方案。

数据集

Instancetimepower_source
I12024-09-01-00:00:00Electricity
I12024-09-01-00:15:00Error
I12024-09-01-00:30:00Error
I12024-09-01-00:45:00Error
I12024-09-01-01:00:00Electricity
I12024-09-01-01:15:00Error
I12024-09-01-01:30:00Battery
I12024-09-01-01:45:00Error
I12024-09-01-02:00:00Battery

需求说明

需求1:Error值替换逻辑

按Instance分区,对连续的Error块:

  • 若该块前后的非Error值相同,将块内所有Error替换为该值
  • 若前后值不同,则保留Error

期望输出:

Instancetimepower_source
I12024-09-01-00:00:00Electricity
I12024-09-01-00:15:00Electricity
I12024-09-01-00:30:00Electricity
I12024-09-01-00:45:00Electricity
I12024-09-01-01:00:00Electricity
I12024-09-01-01:15:00Error <-- 上下值不同,未替换
I12024-09-01-01:30:00Battery
I12024-09-01-01:45:00Battery
I12024-09-01-02:00:00Battery

需求2:统计指标

统计每个Instance下各power_source的:

  • 总数量
  • 最大连续出现次数
  • 最小连续出现次数

示例结果:

Electricity -> 总数量:5, 最大连续次数:5, 最小连续次数:5
Battery -> 总数量:3, 最大连续次数:3, 最小连续次数:3

目前需求1尚未解决,需求2计划将每个Instance的power_source收集为列表后通过UDF统计。


解决方案

需求1:Error替换实现(Scala)

核心思路:通过窗口函数标记连续的Error块,再获取每个块的前后非Error值,判断后替换。

import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.functions._

object SparkErrorReplace {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder()
      .appName("ErrorReplace")
      .master("local[*]")
      .getOrCreate()
    import spark.implicits._

    // 构造示例数据
    val df = Seq(
      ("I1", "2024-09-01-00:00:00", "Electricity"),
      ("I1", "2024-09-01-00:15:00", "Error"),
      ("I1", "2024-09-01-00:30:00", "Error"),
      ("I1", "2024-09-01-00:45:00", "Error"),
      ("I1", "2024-09-01-01:00:00", "Electricity"),
      ("I1", "2024-09-01-01:15:00", "Error"),
      ("I1", "2024-09-01-01:30:00", "Battery"),
      ("I1", "2024-09-01-01:45:00", "Error"),
      ("I1", "2024-09-01-02:00:00", "Battery")
    ).toDF("Instance", "time", "power_source")

    // 1. 按Instance分区,time排序
    val windowSpec = Window.partitionBy("Instance").orderBy("time")

    // 2. 标记非Error的行,用于后续分组连续Error块
    val dfWithNonErrorFlag = df.withColumn(
      "is_non_error",
      when(col("power_source") =!= "Error", 1).otherwise(0)
    )

    // 3. 生成连续Error块的分组ID:累加非Error标记,相同组的Error属于同一块
    val dfWithBlockId = dfWithNonErrorFlag.withColumn(
      "block_id",
      sum("is_non_error").over(windowSpec.rangeBetween(Window.unboundedPreceding, 0))
    )

    // 4. 获取每个block的前一个非Error值(向前填充非Error值)
    val dfWithPrevValue = dfWithBlockId.withColumn(
      "prev_value",
      last(when(col("power_source") =!= "Error", col("power_source")), ignoreNulls = true)
        .over(windowSpec.rangeBetween(Window.unboundedPreceding, -1))
    )

    // 5. 获取每个block的后一个非Error值(向后填充非Error值)
    val dfWithNextValue = dfWithPrevValue.withColumn(
      "next_value",
      first(when(col("power_source") =!= "Error", col("power_source")), ignoreNulls = true)
        .over(windowSpec.rangeBetween(1, Window.unboundedFollowing))
    )

    // 6. 替换Error值:前后值相同则替换,否则保留Error
    val resultDf = dfWithNextValue.withColumn(
      "power_source",
      when(
        col("power_source") === "Error" && col("prev_value") === col("next_value"),
        col("prev_value")
      ).otherwise(col("power_source"))
    ).drop("is_non_error", "block_id", "prev_value", "next_value")

    resultDf.show(false)
  }
}

Java版本思路

Java实现逻辑与Scala完全一致,核心是通过Window类定义窗口,调用functions静态方法使用when、sum、last、first等函数,最终完成条件替换,可参考Scala代码步骤逐步转换。

需求2:统计指标实现(Scala)

无需收集列表,直接通过窗口函数标记连续相同的power_source分组,再聚合统计:

import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.functions._

object SparkSourceStats {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder()
      .appName("SourceStats")
      .master("local[*]")
      .getOrCreate()
    import spark.implicits._

    // 使用需求1处理后的结果作为输入
    val resultDf = Seq(
      ("I1", "2024-09-01-00:00:00", "Electricity"),
      ("I1", "2024-09-01-00:15:00", "Electricity"),
      ("I1", "2024-09-01-00:30:00", "Electricity"),
      ("I1", "2024-09-01-00:45:00", "Electricity"),
      ("I1", "2024-09-01-01:00:00", "Electricity"),
      ("I1", "2024-09-01-01:15:00", "Error"),
      ("I1", "2024-09-01-01:30:00", "Battery"),
      ("I1", "2024-09-01-01:45:00", "Battery"),
      ("I1", "2024-09-01-02:00:00", "Battery")
    ).toDF("Instance", "time", "power_source")

    // 1. 按Instance分区,time排序
    val windowSpec = Window.partitionBy("Instance").orderBy("time")

    // 2. 标记连续相同的power_source分组:当前值与前一个不同时,分组ID+1
    val dfWithGroup = resultDf.withColumn(
      "group_id",
      sum(when(col("power_source") =!= lag("power_source", 1).over(windowSpec), 1).otherwise(0))
        .over(windowSpec.rangeBetween(Window.unboundedPreceding, 0))
    )

    // 3. 统计每个分组的连续次数
    val groupStats = dfWithGroup.groupBy("Instance", "power_source", "group_id")
      .agg(count("*").alias("continuous_count"))

    // 4. 聚合得到总数量、最大/最小连续次数
    val finalStats = groupStats.groupBy("Instance", "power_source")
      .agg(
        sum("continuous_count").alias("总数量"),
        max("continuous_count").alias("最大连续次数"),
        min("continuous_count").alias("最小连续次数")
      )

    // 格式化输出结果
    finalStats.select(
      concat(
        col("power_source"),
        lit(" -> 总数量:"), col("总数量"),
        lit(", 最大连续次数:"), col("最大连续次数"),
        lit(", 最小连续次数:"), col("最小连续次数")
      ).alias("统计结果")
    ).show(false)
  }
}

内容的提问来源于stack exchange,提问作者Shubham Ingle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 15:09:51