Scala 在我的窗口中满足条件之前,如何打开窗口?

Scala 在我的窗口中满足条件之前,如何打开窗口?,scala,apache-spark,Scala,Apache Spark,我有一组数据,有3列感兴趣的内容。第一列是表示月份的日期。第二列是该月的起始金额。第三列表示当月金额的减少。我有一段时间内每个月的多行数据 例如,我们可能会得到2020-01-01的日期,起始值为5MM,减少值为2MM。这意味着我们预计在本月底会有3毫米的剩余量 我需要计算一下,在接下来的几个月里,燃烧这个起始量需要多长时间 在上面的例子中,如果我们从5MM开始,而这个月消耗2MM,我们还有3MM。如果下个月2020-02-01消耗1.5毫米,则剩余1.5毫米。如果下一个月2020-03-01消

我有一组数据,有3列感兴趣的内容。第一列是表示月份的日期。第二列是该月的起始金额。第三列表示当月金额的减少。我有一段时间内每个月的多行数据

例如,我们可能会得到2020-01-01的日期,起始值为5MM,减少值为2MM。这意味着我们预计在本月底会有3毫米的剩余量

我需要计算一下,在接下来的几个月里,燃烧这个起始量需要多长时间

在上面的例子中,如果我们从5MM开始,而这个月消耗2MM,我们还有3MM。如果下个月2020-02-01消耗1.5毫米,则剩余1.5毫米。如果下一个月2020-03-01消耗了2毫米,我们还有-0.5毫米剩余,我们在2020-03-01月份完成了消耗量。2020-03-01的这个结果就是我希望得到的

如何获得该值

我想在数据帧的每一行中得到一个结果,我需要对数据帧的其余部分执行聚合以查看历史行。因此,我假设我需要使用Window来计算这个值。但是,我不知道如何正确地获得实际的窗口设置

我在窗口中的功能是取称为“打开量”的起始量,减去窗口上称为“消耗量”的燃尽量。比如说,

  def followingWindowSpec: WindowSpec = Window.partitionBy(
    partitionCols : _*
  )
    .orderBy(orderByCols: _*)
    .rangeBetween(0, Window.unboundedFollowing)

  val burndownCompleteDateExpr = min(
    when(
      col("opening_amount")
        - sum(col("consume_amount"))
        .over(followingWindowSpec)
        <= lit(0),
      col("fiscal_dt")
    )
  )
    .over(followingWindowSpec)
对于每一行,我将消耗的值从该行向前加n行,每次增加n行,以查看期初值何时被完全消耗

例如,对于2020-01-01中的项目101,它以3200开头,第一个月消耗2000,最后一个月消耗1200。在2020-02-01年,1500辆车完全消耗了这1200辆车,所以2020-02-01是我要找的月份


对于2020-02-01中的101项,它永远不会被完全消耗,因此我将返回空值作为默认值。

我建议使用
窗口/rowsBetween()
为每一行汇编以下行中消耗的
数据列表,然后由UDF处理以捕获缺货
日期

val df = Seq(
  (101, "2020-01-01", 3200, 2000),
  (101, "2020-02-01", 4600, 1500),
  (101, "2020-03-01", 1500, 1300),
  (101, "2020-04-01", 4000,  500),
  (220, "2020-01-01", 3400, 2000),
  (220, "2020-02-01", 1600, 3000),
  (220, "2020-03-01",  310, 1000),
  (220, "2020-04-01",  680,  500)
).toDF("item", "date", "opening", "consumed")

import org.apache.spark.sql.Row
import org.apache.spark.sql.expressions.Window
val win1 = Window.partitionBy("item").orderBy("date").
  rowsBetween(0, Window.unboundedFollowing)

val outOfStockDate = udf { (opening: Int, list: Seq[Row]) =>
  @scala.annotation.tailrec
  def loop(ls: List[(String, Int)], dt: String, acc: Int): String = ls match {
    case Nil =>
      null
    case head :: tail =>
      val accNew = acc + head._2
      if (opening <= accNew) head._1 else loop(tail, head._1, accNew)
  }
  loop(list.map{ case Row(d: String, c: Int) => (d, c) }.toList, null, 0)
}

df.
  withColumn("consumed_list", collect_list(struct($"date", $"consumed")).over(win1)).
  withColumn("out_of_supply", outOfStockDate($"opening", $"consumed_list")).
  drop("consumed_list").
  show
// +----+----------+-------+--------+-------------+
// |item|      date|opening|consumed|out_of_supply|
// +----+----------+-------+--------+-------------+
// | 101|2020-01-01|   3200|    2000|   2020-02-01|
// | 101|2020-02-01|   4600|    1500|         null|
// | 101|2020-03-01|   1500|    1300|   2020-04-01|
// | 101|2020-04-01|   4000|     500|         null|
// | 220|2020-01-01|   3400|    2000|   2020-02-01|
// | 220|2020-02-01|   1600|    3000|   2020-02-01|
// | 220|2020-03-01|    310|    1000|   2020-03-01|
// | 220|2020-04-01|    680|     500|         null|
// +----+----------+-------+--------+-------------+
val df=Seq(
(101, "2020-01-01", 3200, 2000),
(101, "2020-02-01", 4600, 1500),
(101, "2020-03-01", 1500, 1300),
(101, "2020-04-01", 4000,  500),
(220, "2020-01-01", 3400, 2000),
(220, "2020-02-01", 1600, 3000),
(220, "2020-03-01",  310, 1000),
(220, "2020-04-01",  680,  500)
).toDF(“项目”、“日期”、“期初”、“消耗”)
导入org.apache.spark.sql.Row
导入org.apache.spark.sql.expressions.Window
val win1=Window.partitionBy(“项目”).orderBy(“日期”)。
行之间(0,窗口。无界跟随)
val outOfStockDate=udf{(期初:Int,列表:Seq[Row])=>
@scala.annotation.tailrec
def循环(ls:List[(String,Int)],dt:String,acc:Int):String=ls匹配{
案例无=>
无效的
案例头部::尾部=>
val accNew=acc+压头
if(打开(d,c)}.toList,null,0)
}
df。
withColumn(“已消费的”列表,“收集的”列表(结构($“日期”,“$”已消费”)).over(win1))。
带列(“缺货”,缺货日期($“期初”,“消耗清单”))。
删除(“已消费列表”)。
显示
// +----+----------+-------+--------+-------------+
//|项目|日期|开业|消耗|缺货||
// +----+----------+-------+--------+-------------+
// | 101|2020-01-01|   3200|    2000|   2020-02-01|
//| 101 | 2020-02-01 | 4600 | 1500 |空|
// | 101|2020-03-01|   1500|    1300|   2020-04-01|
//| 101 | 2020-04-01 | 4000 | 500 |空|
// | 220|2020-01-01|   3400|    2000|   2020-02-01|
// | 220|2020-02-01|   1600|    3000|   2020-02-01|
// | 220|2020-03-01|    310|    1000|   2020-03-01|
//220 | 2020-04-01 | 680 | 500 |空|
// +----+----------+-------+--------+-------------+

如果每行都有“期初金额”和“消费金额”,为什么不简单地执行
groupBy()
来聚合
min(当($“期初金额”)问题在于,每行的期初金额都很容易获得,但消费金额需要累积,因此需要查看其他行。鉴于以下三行,
(<2020-01-01',5000,2000),([2020-02-01',3000,1500),([2020-03-01',1500,2000)
,难道第三行本身没有足够的信息来导致
when()
条件下的
true
吗?第三行会,是的。前两行不会。前两行会导致
null
因此被排除。因此,类似于
df.groupBy(“item”).agg(min)(when($“期初金额”)感谢您的回答。实际用例中的一个混淆因素是,每一行都有自己的期初值,需要有自己的日期。即,项目101可能在1月份有5000个期初库存,但在2月份有3000个期初库存。这也意味着结果不能按项目,而必须按项目和日期-这使得它是eem要复杂得多。我不知道你说的“每行都有自己的开始值,需要有自己的日期”是什么意思。也许你可以通过编辑你的问题来详细说明,根据某个样本数据集添加所需的输出。很抱歉延迟。我使用了另一种不太优雅的方法,并且有其他工作优先级。我添加了样本数据,以向你展示我心目中的结果。我自己在空闲时间一直在进行此工作,但尚未恢复解决它。因此,根据您最新的样本数据,每行的“期初”数量受外部变化的影响,而不是从之前的“期初”/“消耗”中扣除数据。请参阅我的修订解决方案。是的,期初金额来自另一个系统,每月都会发生变化。例如,如果一个月期初为3000,我们收到2000次交付,消耗1000,则下个月期初为4000。我们不知道实际交付量和消耗量是多少,直到他们p
val df = Seq(
  (101, "2020-01-01", 3200, 2000),
  (101, "2020-02-01", 4600, 1500),
  (101, "2020-03-01", 1500, 1300),
  (101, "2020-04-01", 4000,  500),
  (220, "2020-01-01", 3400, 2000),
  (220, "2020-02-01", 1600, 3000),
  (220, "2020-03-01",  310, 1000),
  (220, "2020-04-01",  680,  500)
).toDF("item", "date", "opening", "consumed")

import org.apache.spark.sql.Row
import org.apache.spark.sql.expressions.Window
val win1 = Window.partitionBy("item").orderBy("date").
  rowsBetween(0, Window.unboundedFollowing)

val outOfStockDate = udf { (opening: Int, list: Seq[Row]) =>
  @scala.annotation.tailrec
  def loop(ls: List[(String, Int)], dt: String, acc: Int): String = ls match {
    case Nil =>
      null
    case head :: tail =>
      val accNew = acc + head._2
      if (opening <= accNew) head._1 else loop(tail, head._1, accNew)
  }
  loop(list.map{ case Row(d: String, c: Int) => (d, c) }.toList, null, 0)
}

df.
  withColumn("consumed_list", collect_list(struct($"date", $"consumed")).over(win1)).
  withColumn("out_of_supply", outOfStockDate($"opening", $"consumed_list")).
  drop("consumed_list").
  show
// +----+----------+-------+--------+-------------+
// |item|      date|opening|consumed|out_of_supply|
// +----+----------+-------+--------+-------------+
// | 101|2020-01-01|   3200|    2000|   2020-02-01|
// | 101|2020-02-01|   4600|    1500|         null|
// | 101|2020-03-01|   1500|    1300|   2020-04-01|
// | 101|2020-04-01|   4000|     500|         null|
// | 220|2020-01-01|   3400|    2000|   2020-02-01|
// | 220|2020-02-01|   1600|    3000|   2020-02-01|
// | 220|2020-03-01|    310|    1000|   2020-03-01|
// | 220|2020-04-01|    680|     500|         null|
// +----+----------+-------+--------+-------------+