如何应用窗口规范,但在2个不同的列(PySpark)

时间:2017-09-04 16:24:04

标签: apache-spark pyspark spark-dataframe

我遇到的问题是,API的解决方案对我来说并不明显,我想知道最有效的方法是什么。

用例:

我收集了两种类型的推文并将其存储在数据框中(我们称之为 Type1 Type2 )。对于这个问题,重要的是它发布的 TimeStamp 。对于 Type1 的每条推文,我需要获取 Type2 的所有推文,这些推文属于某个+ / - Type1的TimeStamp周围的 时间窗口 ,并计算一个度量,该度量取决于每个 Type2 推文在此时间窗口内的时间增量。

根据我的尝试,TimeStamp列上的一个简单的 WindowSpec 在这里不起作用。如果我这样做,我会为每个时间戳获得一个windowSpec,但我只需要一些时间戳(Type1s)。我尝试为每种类型的推文创建两个TimeStamp列,并按Type1时间戳排序。但是“rangesBetween”选项似乎只适用于我排序的列(我需要排序Type1-Timestamp列,但rangeBetween Type2之间) -Timestamp列。)

我想出的解决方案是创建2个数据帧,每种类型一个。然后,对于 Type1 推文,我收集到所有时间戳的列表中,并且对于每个 Type2 推文,我计算时间差值此列表中的每个时间戳都使用它。这可行,但解决方案对我来说效率低下,而且对于足够大的数据帧,收集过程可能会因内存问题而失败。

希望我对问题描述很清楚:)

1 个答案:

答案 0 :(得分:1)

这些可能会重叠窗口,因此dat <- structure(list(A = c(1L, 5L), B = c(2L, 7L), C = c(3L, 9L)), .Names = c("A", "B", "C"), class = "data.frame", row.names = c(NA, -2L)) dat1 <- structure(list(A = 1:4), .Names = "A", row.names = c(NA, -4L), class = "data.frame") 无法实现,并且帧不能“动态”(它们不能基于当前行的时间戳值)。

在我看来,您应该使用partitionBy流式传输Twitter API并对批次执行操作。但是,既然你已经拥有了数据帧,那么我们就试试吧。

收集值不是正确的解决方案(它会将所有内容都带到驱动程序的内存中)。另一种可能性是做一个非常昂贵的笛卡尔连接,你最终会在最后的操作中过滤很多行。我们可以通过创建自己的动态批次来解决问题。

首先让我们为类型推文1和2创建两个示例数据帧:

Kafka

假设我们的窗口应为+/- import datetime as dt import numpy as np np.random.seed(0) time_deltas1 = np.random.randint(0, 10, 5).cumsum() time_deltas2 = np.random.randint(0, 10, 20).cumsum() df1 = spark.createDataFrame( sc.parallelize(zip( [dt.datetime(2017,1,1,0,0,0) + dt.timedelta(minutes=4*int(x)) for x in time_deltas1], [chr(c) for c in range(ord('a'), ord('f'))] )), ["ts1", "text1"] ) df2 = spark.createDataFrame( sc.parallelize(zip( [dt.datetime(2017,1,1,0,0,0) + dt.timedelta(minutes=int(x)) + dt.timedelta(seconds = 30) for x in time_deltas2], [chr(c) for c in range(ord('f'), ord('z'))] )), ["ts2", "text2"] ) +-------------------+-----+ | ts1|text1| +-------------------+-----+ |2017-01-01 00:20:00| a| |2017-01-01 00:20:00| b| |2017-01-01 00:32:00| c| |2017-01-01 00:44:00| d| |2017-01-01 01:12:00| e| +-------------------+-----+ +-------------------+-----+ | ts2|text2| +-------------------+-----+ |2017-01-01 00:09:30| f| |2017-01-01 00:12:30| g| |2017-01-01 00:17:30| h| |2017-01-01 00:19:30| i| |2017-01-01 00:23:30| j| |2017-01-01 00:30:30| k| |2017-01-01 00:36:30| l| |2017-01-01 00:44:30| m| |2017-01-01 00:52:30| n| |2017-01-01 00:53:30| o| |2017-01-01 00:59:30| p| |2017-01-01 01:06:30| q| |2017-01-01 01:13:30| r| |2017-01-01 01:21:30| s| |2017-01-01 01:22:30| t| |2017-01-01 01:27:30| u| |2017-01-01 01:36:30| v| |2017-01-01 01:44:30| w| |2017-01-01 01:53:30| x| |2017-01-01 01:57:30| y| +-------------------+-----+ 分钟,因此我们需要n分钟窗口。

首先,我们将2*n转换为timestamps然后我们将它们四舍五入到最接近int分钟的两个时间戳:

2*n

将unix时间戳转换为时间戳以显示:

import pyspark.sql.functions as psf

n = 5*60   # for +/- 5 minutes  
df1 = df1.withColumn("ts1", psf.unix_timestamp(df1.ts1)).withColumn(
    "time_range", 
    psf.explode(psf.array((psf.col("ts1")/(2*n)).cast("int") * (2*n), ((1 + psf.col("ts1")/(2*n)).cast("int")) * (2*n)))
)

df2 = df2.withColumn("ts2", psf.unix_timestamp(df2.ts2)).withColumn(
    "time_range", 
    psf.explode(psf.array((psf.col("ts2")/(2*n)).cast("int") * (2*n), ((1 + psf.col("ts2")/(2*n)).cast("int")) * (2*n)))
)

我们现在可以加入2个数据帧,最终表将小于笛卡尔连接表,并在 +-------------------+-----+-------------------+ | ts1|text1| time_range| +-------------------+-----+-------------------+ |2017-01-01 00:20:00| a|2017-01-01 00:20:00| |2017-01-01 00:20:00| a|2017-01-01 00:30:00| |2017-01-01 00:20:00| b|2017-01-01 00:20:00| |2017-01-01 00:20:00| b|2017-01-01 00:30:00| |2017-01-01 00:32:00| c|2017-01-01 00:30:00| |2017-01-01 00:32:00| c|2017-01-01 00:40:00| |2017-01-01 00:44:00| d|2017-01-01 00:40:00| |2017-01-01 00:44:00| d|2017-01-01 00:50:00| |2017-01-01 01:12:00| e|2017-01-01 01:10:00| |2017-01-01 01:12:00| e|2017-01-01 01:20:00| +-------------------+-----+-------------------+ +-------------------+-----+-------------------+ | ts2|text2| time_range| +-------------------+-----+-------------------+ |2017-01-01 00:09:30| f|2017-01-01 00:00:00| |2017-01-01 00:09:30| f|2017-01-01 00:10:00| |2017-01-01 00:12:30| g|2017-01-01 00:10:00| |2017-01-01 00:12:30| g|2017-01-01 00:20:00| |2017-01-01 00:17:30| h|2017-01-01 00:10:00| |2017-01-01 00:17:30| h|2017-01-01 00:20:00| |2017-01-01 00:19:30| i|2017-01-01 00:10:00| |2017-01-01 00:19:30| i|2017-01-01 00:20:00| |2017-01-01 00:23:30| j|2017-01-01 00:20:00| |2017-01-01 00:23:30| j|2017-01-01 00:30:00| |2017-01-01 00:30:30| k|2017-01-01 00:30:00| |2017-01-01 00:30:30| k|2017-01-01 00:40:00| |2017-01-01 00:36:30| l|2017-01-01 00:30:00| |2017-01-01 00:36:30| l|2017-01-01 00:40:00| |2017-01-01 00:44:30| m|2017-01-01 00:40:00| |2017-01-01 00:44:30| m|2017-01-01 00:50:00| |2017-01-01 00:52:30| n|2017-01-01 00:50:00| |2017-01-01 00:52:30| n|2017-01-01 01:00:00| |2017-01-01 00:53:30| o|2017-01-01 00:50:00| |2017-01-01 00:53:30| o|2017-01-01 01:00:00| |2017-01-01 00:59:30| p|2017-01-01 00:50:00| |2017-01-01 00:59:30| p|2017-01-01 01:00:00| |2017-01-01 01:06:30| q|2017-01-01 01:00:00| |2017-01-01 01:06:30| q|2017-01-01 01:10:00| |2017-01-01 01:13:30| r|2017-01-01 01:10:00| |2017-01-01 01:13:30| r|2017-01-01 01:20:00| |2017-01-01 01:21:30| s|2017-01-01 01:20:00| |2017-01-01 01:21:30| s|2017-01-01 01:30:00| |2017-01-01 01:22:30| t|2017-01-01 01:20:00| |2017-01-01 01:22:30| t|2017-01-01 01:30:00| |2017-01-01 01:27:30| u|2017-01-01 01:20:00| |2017-01-01 01:27:30| u|2017-01-01 01:30:00| |2017-01-01 01:36:30| v|2017-01-01 01:30:00| |2017-01-01 01:36:30| v|2017-01-01 01:40:00| |2017-01-01 01:44:30| w|2017-01-01 01:40:00| |2017-01-01 01:44:30| w|2017-01-01 01:50:00| |2017-01-01 01:53:30| x|2017-01-01 01:50:00| |2017-01-01 01:53:30| x|2017-01-01 02:00:00| |2017-01-01 01:57:30| y|2017-01-01 01:50:00| |2017-01-01 01:57:30| y|2017-01-01 02:00:00| +-------------------+-----+-------------------+ 上过滤:

|ts1 - ts2| <= n min

将时间戳转换回时间戳格式后,我们可以收集类型2的不同推文:

df = df1.join(df2, "time_range").filter(
    (psf.abs(df1.ts1 - df2.ts2) <= n) | (psf.isnull(df2.ts2))
).withColumn("ts1", psf.from_unixtime("ts1")).withColumn("ts2", psf.from_unixtime("ts2"))