想象一下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上执行此操作?
答案 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)
我曾经回答了一个类似的问题,这有点像黑客,但这个想法在你的情况下是有道理的。将每个值映射到列表,然后垂直展平列表。
您可以生成时间戳范围,展平它们并选择行
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|
+---------+----------+