PySpark:如何重新采样频率

时间:2016-09-01 12:17:16

标签: apache-spark pyspark apache-spark-sql time-series

想象一下Spark数据帧由变量的值观察组成。每个观察都有一个特定的时间戳,这些时间戳在不同的变量之间是不一样的。这是因为当变量的值发生变化并被记录时,会生成时间戳。

#Variable     Time                Value
#852-YF-007   2016-05-10 00:00:00 0
#852-YF-007   2016-05-09 23:59:00 0
#852-YF-007   2016-05-09 23:58:00 0

问题我想使用forward-fill将所有变量放入相同的频率(例如10分钟)。为了形象化,我从Book" Python for Data Analysis"中复制了一个页面。 问题:如何以高效的方式在Spark Dataframe上执行此操作?

Python for Data Analysis

2 个答案:

答案 0 :(得分:11)

  

问题:如何以有效的方式在Spark Dataframe上执行此操作?

Spark DataFrame对于像这样的操作来说根本不是一个好选择。通常,SQL原语不够表达,PySpark DataFrame不提供实现它所需的低级访问。

虽然可以使用纪元/时间戳算法轻松表示重新采样。使用这样的数据:

from pyspark.sql.functions import col, max as max_, min as min_

df = (spark  
    .createDataFrame([
        ("2012-06-13", 0.694), ("2012-06-20", -2.669), ("2012-06-27", 0.245)],   
        ["ts", "val"])        
   .withColumn("ts", col("ts").cast("date").cast("timestamp")))

我们可以重新输入输入:

day = 60 * 60 * 24
epoch = (col("ts").cast("bigint") / day).cast("bigint") * day

with_epoch = df.withColumn("epoch", epoch)

min_epoch, max_epoch = with_epoch.select(min_("epoch"), max_("epoch")).first()

并加入参考:

# Reference range 
ref = spark.range(
    min_epoch, max_epoch + 1, day
).toDF("epoch")

(ref
    .join(with_epoch, "epoch", "left")
    .orderBy("epoch")
    .withColumn("ts_resampled", col("epoch").cast("timestamp"))
    .show(15, False))

## +----------+---------------------+------+---------------------+   
## |epoch     |ts                   |val   |ts_resampled         |
## +----------+---------------------+------+---------------------+
## |1339459200|2012-06-13 00:00:00.0|0.694 |2012-06-12 02:00:00.0|
## |1339545600|null                 |null  |2012-06-13 02:00:00.0|
## |1339632000|null                 |null  |2012-06-14 02:00:00.0|
## |1339718400|null                 |null  |2012-06-15 02:00:00.0|
## |1339804800|null                 |null  |2012-06-16 02:00:00.0|
## |1339891200|null                 |null  |2012-06-17 02:00:00.0|
## |1339977600|null                 |null  |2012-06-18 02:00:00.0|
## |1340064000|2012-06-20 00:00:00.0|-2.669|2012-06-19 02:00:00.0|
## |1340150400|null                 |null  |2012-06-20 02:00:00.0|
## |1340236800|null                 |null  |2012-06-21 02:00:00.0|
## |1340323200|null                 |null  |2012-06-22 02:00:00.0|
## |1340409600|null                 |null  |2012-06-23 02:00:00.0|
## |1340496000|null                 |null  |2012-06-24 02:00:00.0|
## |1340582400|null                 |null  |2012-06-25 02:00:00.0|
## |1340668800|2012-06-27 00:00:00.0|0.245 |2012-06-26 02:00:00.0|
## +----------+---------------------+------+---------------------+

使用低级API可以像我在Spark / Scala: forward fill with last observation的答案中所示填写这样的数据。使用RDD我们还可以避免两次混洗数据(一次用于连接,一次用于重新排序)。

但这里有更重要的问题。当问题可以简化为元素计算或分区计算时,Spark可以最佳地执行。尽管前向填充是可能的,但据我所知,这通常不是常用时间序列模型的情况,如果某些操作需要顺序访问,则Spark根本不会提供任何好处。

因此,如果您使用的系列大到足以需要分布式数据结构,您可能希望将其聚合到一个可以由一台机器轻松处理的对象,然后使用您最喜欢的非分布式工具来处理其余部分。

如果你使用多个时间序列,每个时间序列都可以在内存中处理,那么当然有sparkts,但我知道你已经知道了。

答案 1 :(得分:1)

我曾经回答了一个类似的问题,这有点像黑客,但这个想法在你的情况下是有道理的。将每个值映射到列表,然后垂直展平列表。

  

来自:Inserting records in a spark dataframe

您可以生成时间戳范围,展平它们并选择行

import pyspark.sql.functions as func

from pyspark.sql.types import IntegerType, ArrayType


a=sc.parallelize([[670098928, 50],[670098930, 53], [670098934, 55]])\
.toDF(['timestamp','price'])

f=func.udf(lambda x:range(x,x+5),ArrayType(IntegerType()))

a.withColumn('timestamp',f(a.timestamp))\
.withColumn('timestamp',func.explode(func.col('timestamp')))\
.groupBy('timestamp')\
.agg(func.max(func.col('price')))\
.show()

+---------+----------+
|timestamp|max(price)|
+---------+----------+
|670098928|        50|
|670098929|        50|
|670098930|        53|
|670098931|        53|
|670098932|        53|
|670098933|        53|
|670098934|        55|
|670098935|        55|
|670098936|        55|
|670098937|        55|
|670098938|        55|
+---------+----------+