我遇到一个问题,我必须使用公式计算列,该公式使用上一行中的计算所得的值。
我无法使用withColumn
API来解决这个问题。
我需要使用以下公式计算新列:
MovingRate = MonthlyRate + (0.7 * MovingRatePrevious)
...其中MovingRatePrevious
是上一行的MovingRate
。
对于第1个月,我具有该值,因此无需重新计算该值,但是需要该值才能计算后续的行。我需要按类型分区。
这是我的原始数据集:
MovingRate列中所需的结果:
答案 0 :(得分:1)
鉴于必须从先前的速率递归计算每个移动速率的要求,因此面向列的DataFrame API不会发光,尤其是在数据集很大的情况下。
也就是说,如果数据集不大,一种方法是使Spark通过UDF以窗口划分的费率列表作为输入重新计算行进费率:
import org.apache.spark.sql.expressions.Window
val df = Seq(
(1, "blue", 0.4, Some(0.33)),
(2, "blue", 0.3, None),
(3, "blue", 0.7, None),
(4, "blue", 0.9, None),
(1, "red", 0.5, Some(0.2)),
(2, "red", 0.6, None),
(3, "red", 0.8, None)
).toDF("Month", "Type", "MonthlyRate", "MovingRate")
val win = Window.partitionBy("Type").orderBy("Month").
rowsBetween(Window.unboundedPreceding, 0)
def movingRate(factor: Double) = udf( (initRate: Double, monthlyRates: Seq[Double]) =>
monthlyRates.tail.foldLeft(initRate)( _ * factor + _ )
)
df.
withColumn("MovingRate", when($"Month" === 1, $"MovingRate").otherwise(
movingRate(0.7)(last($"MovingRate", ignoreNulls=true).over(win), collect_list($"MonthlyRate").over(win))
)).
show
// +-----+----+-----------+------------------+
// |Month|Type|MonthlyRate| MovingRate|
// +-----+----+-----------+------------------+
// | 1| red| 0.5| 0.2|
// | 2| red| 0.6| 0.74|
// | 3| red| 0.8| 1.318|
// | 1|blue| 0.4| 0.33|
// | 2|blue| 0.3|0.5309999999999999|
// | 3|blue| 0.7|1.0716999999999999|
// | 4|blue| 0.9|1.6501899999999998|
// +-----+----+-----------+------------------+
答案 1 :(得分:1)
尽管它可能与寡妇函数有关(请参阅@Leo C的答案),但我敢打赌它的性能更高,可以使用Type
每groupBy
进行一次汇总。然后,分解UDF的结果以取回所有行:
val df = Seq(
(1, "blue", 0.4, Some(0.33)),
(2, "blue", 0.3, None),
(3, "blue", 0.7, None),
(4, "blue", 0.9, None)
)
.toDF("Month", "Type", "MonthlyRate", "MovingRate")
// this udf produces an Seq of Tuple3 (Month, MonthlyRate, MovingRate)
val calcMovingRate = udf((startRate:Double,rates:Seq[Row]) => rates.tail
.scanLeft((rates.head.getInt(0),startRate,startRate))((acc,curr) => (curr.getInt(0),curr.getDouble(1),acc._3+0.7*curr.getDouble(1)))
)
df
.groupBy($"Type")
.agg(
first($"MovingRate",ignoreNulls=true).as("startRate"),
collect_list(struct($"Month",$"MonthlyRate")).as("rates")
)
.select($"Type",explode(calcMovingRate($"startRate",$"rates")).as("movingRates"))
.select($"Type",$"movingRates._1".as("Month"),$"movingRates._2".as("MonthlyRate"),$"movingRates._3".as("MovingRate"))
.show()
给予:
+----+-----+-----------+------------------+
|Type|Month|MonthlyRate| MovingRate|
+----+-----+-----------+------------------+
|blue| 1| 0.33| 0.33|
|blue| 2| 0.3| 0.54|
|blue| 3| 0.7| 1.03|
|blue| 4| 0.9|1.6600000000000001|
+----+-----+-----------+------------------+
答案 2 :(得分:0)
您要尝试的是计算一个类似于以下内容的递归公式:
x[i] = y[i] + 0.7 * x[i-1]
其中x[i]
是您在MovingRate
行中的i
,y[i]
是您在MonthlyRate
行中的i
。
问题在于,这是一个纯粹的顺序公式。每行都需要前一个的结果,而后者又需要前一个的结果。 Spark是一个并行计算引擎,很难使用它来加速无法真正并行化的计算。