Pyspark:重新采样频率低至毫秒

时间:2019-06-03 22:18:53

标签: python pyspark resampling

我需要在一个时间戳列上加入两个spark数据帧。问题在于它们具有不同的频率:第一个数据帧(df1)每10分钟观察一次,而第二个数据帧(df2)为25 hz(每秒观察25次,比df1高15000倍)。每个数据框都有100多个列和数百万行。为了使连接平滑,我尝试将df1重新采样至25 Hz,预先填充由重采样导致的Null值,然后在数据帧处于相同频率时将它们加入。数据框太大,这就是为什么我要使用Spark而不是熊猫的原因。

所以,这是一个问题:假设我有以下Spark数据框:

sample_df

我想将其重新采样到25 Hz(每秒25次观察),这样看起来就这样:

Expected_result

如何在pyspark中有效地做到这一点?

注意:

我尝试使用来自先前问题(PySpark: how to resample frequencies)的代码重新采样df1,如下所示:

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

freq = x   # x is the frequency in seconds

epoch = (col("timestamp").cast("bigint") / freq).cast("bigint") * freq 

with_epoch  = df1.withColumn("dummy", epoch)

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

new_df = spark.range(min_epoch, max_epoch + 1, freq).toDF("dummy")

new_df.join(with_epoch, "dummy", "left").orderBy("dummy")
.withColumn("timestamp_resampled", col("dummy").cast("timestamp"))

似乎,以上代码仅在预期频率大于或等于秒时有效。例如,当freq = 1时,将产生下表:

undesired_result

但是,当我通过25 hz作为频率(即freq = 1/25)时,代码将失败,因为spark.range函数中的“步长”不能小于1。

是否有解决此问题的解决方法?还是以其他方式将频率重新采样到毫秒?

1 个答案:

答案 0 :(得分:1)

如果您的目标是联接2个数据框,则建议直接使用内部联接:

df = df1.join(df2, df1.Timestamp == df2.Timestamp)

但是,如果要尝试对数据帧进行降采样,则可以将时间戳转换为毫秒,并保留那些mod(timestamp, 25) == 0的行。只有在确定数据采样完美后,才能使用此功能。

from pyspark.sql.functions import col
df1 = df1.filter( ((col("Timestamp") % 25) == 0 )

其他选择是对每行编号,并每25保持1个。使用此解决方案,您将在不考虑时间戳的情况下减少行数。此解决方案的另一个问题是您需要对数据进行排序(效率不高)。

PD:过早的优化是万恶之源

编辑:时间戳为int

让我们使用纪元标准(以毫秒为单位)创建一个充满时间戳的假数据集。

>>>  df = sqlContext.range(1559646513000, 1559646520000)\
                    .select( (F.col('id')/1000).cast('timestamp').alias('timestamp'))
>>> df
DataFrame[timestamp: timestamp]
>>> df.show(5,False)
+-----------------------+
|timestamp              |
+-----------------------+
|2019-06-04 13:08:33    |
|2019-06-04 13:08:33.001|
|2019-06-04 13:08:33.002|
|2019-06-04 13:08:33.003|
|2019-06-04 13:08:33.004|
+-----------------------+
only showing top 5 rows

现在,转换回整数:

>>> df.select( (df.timestamp.cast('double')*1000).cast('bigint').alias('epoch') )\
      .show(5, False)
+-------------+
|epoch        |
+-------------+
|1559646513000|
|1559646513001|
|1559646513002|
|1559646513003|
|1559646513004|
+-------------+
only showing top 5 rows