使用窗口在scala中使用if条件对行进行计数

时间:2018-11-16 07:58:26

标签: scala apache-spark

我希望你能帮助我:-)

我有一个发布了advert的数据框。 我想为每个广告ID计算同一电子邮件之前两个月前发布的广告数量。

我创建了以下数据框以更好地说明问题:

var df = sc.parallelize(Array(
(1,  "2017-06-29 10:53:53.0","boulanger.fr" ,"2017-06-28","2017-04-29"), 
(2,  "2017-07-05 10:48:57.0","patissier.fr","2017-07-04","2017-05-05"), 
(3,  "2017-06-28 10:31:42.0","boulanger.fr" ,"2017-08-16","2017-06-17"), 
(4,  "2017-08-21 17:31:12.0","patissier.fr","2017-08-20","2017-06-21"), 
(5,  "2017-07-28 11:22:42.0","boulanger.fr" ,"2017-08-22","2017-06-23"), 
(6,  "2017-08-23 17:03:43.0","patissier.fr","2017-08-22","2017-06-23"), 
(7,  "2017-08-24 16:08:07.0","boulanger.fr" ,"2017-08-23","2017-06-24"), 
(8,  "2017-08-31 17:20:43.0","patissier.fr","2017-08-30","2017-06-30"), 
(9,  "2017-09-04 14:35:38.0","boulanger.fr" ,"2017-09-03","2017-07-04"), 
(10, "2017-09-07 15:10:34.0","patissier.fr","2017-09-06","2017-07-07"))).toDF("id_advert", "creation_date",
    "email", "date_minus1","date_minus2m")

df = df.withColumn("date_minus1", to_date(unix_timestamp($"date_minus1", "yyyy-MM-dd").cast("timestamp")))  
df = df.withColumn("date_minus2", to_date(unix_timestamp($"date_minus2", "yyyy-MM-dd").cast("timestamp"))) 
df = df.withColumn("date_crecreation", (unix_timestamp($"creation_date", "yyyy-MM-dd HH:mm:ss").cast("timestamp")))
  • date_minus1 =发布广告的前一天
  • date_minus2m =广告发布前2个月

我想计算在这两个日期之间使用相同电子邮件发送的广告数量...

我想要的结果是:

+---------+----------------+
|id_advert|nb_prev_advert  |
+---------+----------------+
|6        |2               |
|3        |3               |
|5        |3               |
|9        |2               |
|4        |1               |
|8        |3               |
|7        |3               |
|10       |3               |
+--------+-----------------+

我可以通过从数据帧本身进行可怕的连接来做到这一点,但由于我有数百万行,因此运行将近2个小时...

我在,我们可以做类似的事情:

val w = Window.partitionBy("id_advert").orderBy("creation_date").rowsBetween(-50000000, -1)

并使用它遍历数据框并仅对行进行计数

  • 该行的电子邮件=当前行的电子邮件
  • 该行的date_minus2m <当前行的日期创建<该行的date_minus1

3 个答案:

答案 0 :(得分:1)

这是使用具有一定范围的Window的答案

创建一个范围介于当前和过去六十天之间的窗口规范

val w = Window
          .partitionBy(col("email"))
          .orderBy(col("creation_date").cast("timestamp").cast("long"))
          .rangeBetween(-60*86400,-1)

然后在数据框架中选择它

df
 .select(col("*"),count("email").over(w).alias("trailing_count"))
 .orderBy("email","creation_date") //using this for display purpose 
 .show()

注意:您的预期输出可能是错误的。一个,对于广告,至少要为零,因为某些内容必须在邮件的起始行。另外,计算广告3似乎是错误的。

输入数据:

df.select("id_advert","creation_date","email").orderBy("email", "creation_date").show()

+---------+--------------------+------------+
|id_advert|       creation_date|       email|
+---------+--------------------+------------+
|        3|2017-06-28 10:31:...|boulanger.fr|
|        1|2017-06-29 10:53:...|boulanger.fr|
|        5|2017-07-28 11:22:...|boulanger.fr|
|        7|2017-08-24 16:08:...|boulanger.fr|
|        9|2017-09-04 14:35:...|boulanger.fr|
|        2|2017-07-05 10:48:...|patissier.fr|
|        4|2017-08-21 17:31:...|patissier.fr|
|        6|2017-08-23 17:03:...|patissier.fr|
|        8|2017-08-31 17:20:...|patissier.fr|
|       10|2017-09-07 15:10:...|patissier.fr|
+---------+--------------------+------------+

输出:

+---------+--------------------+------------+-------------+--------------+
|id_advert|       creation_date|       email|date_creation|trailing_count|
+---------+--------------------+------------+-------------+--------------+
|        3|2017-06-28 10:31:...|boulanger.fr|   1498645902|             0|
|        1|2017-06-29 10:53:...|boulanger.fr|   1498733633|             1|
|        5|2017-07-28 11:22:...|boulanger.fr|   1501240962|             2|
|        7|2017-08-24 16:08:...|boulanger.fr|   1503590887|             3|
|        9|2017-09-04 14:35:...|boulanger.fr|   1504535738|             2|
|        2|2017-07-05 10:48:...|patissier.fr|   1499251737|             0|
|        4|2017-08-21 17:31:...|patissier.fr|   1503336672|             1|
|        6|2017-08-23 17:03:...|patissier.fr|   1503507823|             2|
|        8|2017-08-31 17:20:...|patissier.fr|   1504200043|             3|
|       10|2017-09-07 15:10:...|patissier.fr|   1504797034|             3|
+---------+--------------------+------------+-------------+--------------+

答案 1 :(得分:1)

由于不同而将其添加为不同的答案

输入:

df.select("*").orderBy("email","creation_date").show()

+---------+--------------------+------------+----+
|id_advert|       creation_date|       email|sold|
+---------+--------------------+------------+----+
|        1|2015-06-29 10:53:...|boulanger.fr|   1|
|        5|2015-07-28 11:22:...|boulanger.fr|   0|
|        3|2017-06-28 10:31:...|boulanger.fr|   1|
|        7|2017-08-24 16:08:...|boulanger.fr|   1|
|        9|2017-09-04 14:35:...|boulanger.fr|   1|
|       10|2012-09-07 15:10:...|patissier.fr|   0|
|        8|2014-08-31 17:20:...|patissier.fr|   1|
|        2|2016-07-05 10:48:...|patissier.fr|   1|
|        4|2017-08-21 17:31:...|patissier.fr|   0|
|        6|2017-08-23 17:03:...|patissier.fr|   0|
+---------+--------------------+------------+----+

现在,您将窗口规范定义为

val w = Window.
          partitionBy("email").
          orderBy(col("creation_date"). 
          cast("timestamp").
          cast("long")).rangeBetween(-60*24*60*60,-1)

主要查询将是:

df.
  select(
      col("*"),count("email").over(w).alias("all_prev_mail_advert"), 
      sum("sold").over(w).alias("all_prev_sold_mail_advert")
  ).orderBy("email","creation_date").show()

输出:

+---------+--------------------+------------+----+--------------------+-------------------------+
|id_advert|       creation_date|       email|sold|all_prev_mail_advert|all_prev_sold_mail_advert|
+---------+--------------------+------------+----+--------------------+-------------------------+
|        1|2015-06-29 10:53:...|boulanger.fr|   1|                   0|                     null|
|        5|2015-07-28 11:22:...|boulanger.fr|   0|                   1|                        1|
|        3|2017-06-28 10:31:...|boulanger.fr|   1|                   0|                     null|
|        7|2017-08-24 16:08:...|boulanger.fr|   1|                   1|                        1|
|        9|2017-09-04 14:35:...|boulanger.fr|   1|                   1|                        1|
|       10|2012-09-07 15:10:...|patissier.fr|   0|                   0|                     null|
|        8|2014-08-31 17:20:...|patissier.fr|   1|                   0|                     null|
|        2|2016-07-05 10:48:...|patissier.fr|   1|                   0|                     null|
|        4|2017-08-21 17:31:...|patissier.fr|   0|                   0|                     null|
|        6|2017-08-23 17:03:...|patissier.fr|   0|                   1|                        0|
+---------+--------------------+------------+----+--------------------+-------------------------+

说明:

我们正在定义最近两个月的窗口功能(按电子邮件划分)。并且该窗口的计数为同一封电子邮件提供了所有先前的广告。

要获得所有先前出售的广告,我们只需在同一窗口上添加出售的列即可。由于已售出商品的已售出数量为1,因此总和为该窗口中所有已售出商品的数量。

答案 2 :(得分:0)

由于不可能正确地构造评论,因此我将使用“答案”按钮,但实际上它是一个问题而不是答案。

我简化了问题,以为您的答案也许可以做我想做的事,但我不确定您的答案是否正确……

它如何工作?对我来说:

  • 如果我执行.rangeBetween(-3,-1),我将使用一个窗口,该窗口看起来在当前行之前的3行到当前行之前的一行。但是在这里,rangeBetween似乎是指orderby变量,而不是行数总数。.???
  • 如果我执行“ partitionBy(col(“ email”))“,我应该通过电子邮件发送一行,但是在这里我仍然通过advert_id获得一条......

我真正想要做的是,用相同的电子邮件分别计算广告发布日期前两个月中已售出商品的数量和未售出商品的数量。

这是使用您所做的并将其应用于我的真实问题的简便方法吗?

我的数据框如下:

var df = sc.parallelize(Array(
(1,  "2015-06-29 10:53:53.0","boulanger.fr", 1),
(2,  "2016-07-05 10:48:57.0","patissier.fr", 1),
(3,  "2017-06-28 10:31:42.0","boulanger.fr", 1),
(4,  "2017-08-21 17:31:12.0","patissier.fr", 0),
(5,  "2015-07-28 11:22:42.0","boulanger.fr", 0),
(6,  "2017-08-23 17:03:43.0","patissier.fr", 0),
(7,  "2017-08-24 16:08:07.0","boulanger.fr", 1),
(8,  "2014-08-31 17:20:43.0","patissier.fr", 1),
(9,  "2017-09-04 14:35:38.0","boulanger.fr", 1),
(10, "2012-09-07 15:10:34.0","patissier.fr", 0))).toDF("id_advert", "creation_date","email", "sold")

对于每个id_advert,我希望有2行。一种用于售出商品的数量,另一种用于未售商品的数量...

提前谢谢!!!如果您无法放松,我会以更肮脏的方式进行;-)。