星火指数移动平均线

时间:2018-06-05 13:24:17

标签: scala apache-spark apache-spark-sql

我有一个时间序列定价数据的数据框,包含ID,日期和价格。

我需要为价格列计算指数移动平均线,并将其作为新列添加到数据框中。

之前我一直在使用Spark的窗口函数,它看起来很适合这个用例,但给出了EMA的公式:

multiplier = (2 / (Time periods + 1)) //let's assume Time period is 10 days for now

其中

var window = Window.partitionBy("ID").orderBy("Date").rowsBetween(-windowSize, Window.currentRow)
dataFrame.withColumn(avg(col("Price")).over(window).alias("SMA"))

我对如何访问列中的先前计算值有点困惑,同时实际上在列上进行窗口化。 使用简单的移动平均线,它很简单,因为你需要做的就是计算一个新列,同时平均窗口中的元素:

{{1}}

但似乎EMA的情况有点复杂,因为每一步我都需要先前的计算值。

我也查看了Weighted moving average in Pyspark,但我需要一种Spark / Scala方法,以及10天或30天的EMA。

有什么想法吗?

1 个答案:

答案 0 :(得分:2)

最后,我分析了如何在熊猫数据框中实现指数移动平均。除了我上面描述的递归公式,该递归公式很难在任何sql或window函数中实现(因为它是递归的),还有另一个公式,在their issue tracker中有详细介绍:

y[t] = (x[t] + (1-a)*x[t-1] + (1-a)^2*x[t-2] + ... + (1-a)^n*x[t-n]) /
       ((1-a)^0 + (1-a)^1 + (1-a)^2 + ... + (1-a)^n).

鉴于此,并得到here的其他Spark实施帮助,我最终完成了以下实施,大致相当于执行 pandas_dataframe.ewm(span = window_size).mean()

def exponentialMovingAverage(partitionColumn: String, orderColumn: String, column: String, windowSize: Int): DataFrame = {
  val window = Window.partitionBy(partitionColumn)
  val exponentialMovingAveragePrefix = "_EMA_"

  val emaUDF = udf((rowNumber: Int, columnPartitionValues: Seq[Double]) => {
    val alpha = 2.0 / (windowSize + 1)
    val adjustedWeights = (0 until rowNumber + 1).foldLeft(new Array[Double](rowNumber + 1)) { (accumulator, index) =>
      accumulator(index) = pow(1 - alpha, rowNumber - index); accumulator
    }
    (adjustedWeights, columnPartitionValues.slice(0, rowNumber + 1)).zipped.map(_ * _).sum / adjustedWeights.sum
  })
  dataFrame.withColumn("row_nr", row_number().over(window.orderBy(orderColumn)) - lit(1))
    .withColumn(s"$column$exponentialMovingAveragePrefix$windowSize", emaUDF(col("row_nr"), collect_list(column).over(window)))
    .drop("row_nr")
}

(我假设需要为其计算指数移动平均值的列的类型为Double。)

我希望这对其他人有帮助。