Python Spark标记首次登录后24小时内重复用户登录

Python Spark标记首次登录后24小时内重复用户登录,python,scala,apache-spark,pyspark,apache-spark-sql,Python,Scala,Apache Spark,Pyspark,Apache Spark Sql,我有一个包含用户和登录时间的数据集。如果在首次登录后的24小时内有/额外登录,我需要标记重复。活动窗口打开,用户登录。例如,下面是示例数据集 user login ----------------------------- user1 12/1/19 8:00 user1 12/1/19 10:00 user1 12/1/19 23:00 user1 12/2/19 7:00 user1 12/2/19 8:00 user1 12/2/19 10:00 user1

我有一个包含用户和登录时间的数据集。如果在首次登录后的24小时内有/额外登录,我需要标记重复。活动窗口打开,用户登录。例如,下面是示例数据集

user login ----------------------------- user1 12/1/19 8:00 user1 12/1/19 10:00 user1 12/1/19 23:00 user1 12/2/19 7:00 user1 12/2/19 8:00 user1 12/2/19 10:00 user1 12/3/19 23:00 user1 12/4/19 7:00 user2 12/4/19 8:00 user2 12/5/19 5:00 user2 12/6/19 0:00 用户登录 ----------------------------- 用户1 19年1月12日8:00 用户1 19年1月12日10:00 用户1 12/1/19 23:00 用户1 19年12月2日7:00 用户1 19年12月2日8:00 用户1 19年12月2日10:00 用户1 12/3/19 23:00 用户1 19年4月12日7:00 用户2 19年4月12日8:00 用户2 19年5月12日5:00 用户2 19年6月12日0:00 预期结果

user login Duplicate --------------------------------- user1 12/1/19 8:00 N this is first login for user1 - 24 hour window opens here user1 12/1/19 10:00 Y since this is within 24 hours user1 12/1/19 23:00 Y user1 12/2/19 7:00 Y user1 12/2/19 8:00 Y user1 12/2/19 10:00 N This activity time is greater than (previous window open + 24 hrs). previous window closes and second window opens here user1 12/3/19 23:00 N user1 12/4/19 7:00 Y user2 12/4/19 8:00 N user2 12/5/19 5:00 Y user2 12/6/19 0:00 N 用户登录副本 --------------------------------- user1 12/1/19 8:00 N这是user1的第一次登录-24小时窗口在此打开 用户1 12/1/19 10:00 Y,因为这是在24小时内 用户1 12/1/19 23:00 Y 用户1 19年12月2日7:00 Y 用户1 19年12月2日8:00 Y user1 12/2/19 10:00 N此活动时间大于(前一窗口打开+24小时)。上一个窗口关闭,第二个窗口在此打开 用户1 12/3/19 23:00北 用户1 2012/4/19 7:00 Y 用户2 12/4/19 8:00北 用户2 19年5月12日5:00 Y 用户2 12/6/19 0:00 N 我看了一下,但是如果用户以固定的时间间隔(比如说每18小时)登录,这个解决方案就不起作用了

这里是另一个示例(如果解决方案只考虑第一个活动来计算24小时窗口,那么它将为下面的记录#7给出不正确的结果(非重复)

user1 12/1/19 8:00 N这是user1的第一次登录-24小时窗口在此打开 用户1 12/1/19 10:00 Y,因为这是在24小时内 用户1 12/1/19 23:00 Y 用户1 19年12月2日7:00 Y 用户1 19年12月2日8:00 Y user1 12/2/19 10:00 N此活动时间大于(前一窗口打开+24小时)。上一个窗口关闭,第二个窗口在此打开 **用户1 12/3/19 09:00北** 用户1 12/3/19 23:00北 用户1 2012/4/19 7:00 Y 用户2 12/4/19 8:00北 用户2 19年5月12日5:00 Y 用户2 12/6/19 0:00 N
Python:下面是我的scala代码的转换

from pyspark.sql.functions import col, lag, unix_timestamp, to_timestamp, lit, when, row_number, first
from pyspark.sql import Window

w = Window.partitionBy("user", "index").orderBy("login")
df2 = df.withColumn("login", to_timestamp(col("login"), "MM/dd/yy HH:mm"))

df2.join(df2.groupBy("user").agg(first("login").alias("firstLogin")), "user", "left") \
   .withColumn("index", ((unix_timestamp(col("login")) - unix_timestamp(col("firstLogin"))) / 86400).cast("int")) \
   .withColumn("Duplicate", when(row_number().over(w) == 1, lit("N")).otherwise(lit("Y"))) \
   .orderBy("user", "login") \
   .show(20)
Scala:您可以使用
lag
函数和我创建的时差索引列

import org.apache.spark.sql.expressions.Window

val w = Window.partitionBy("user", "index").orderBy("login")
val df2 = df.withColumn("login", to_timestamp($"login", "MM/dd/yy HH:mm"))

df2.join(df2.groupBy("user").agg(first("login").as("firstLogin")), Seq("user"), "left")
   .withColumn("index", ((unix_timestamp(col("login")) - unix_timestamp(col("firstLogin"))) / 86400).cast("int"))
   .withColumn("Duplicate", when(row_number.over(w) === 1, lit("N")).otherwise(lit("Y")))
   .orderBy("user", "login")

   .show
结果是:

+-----+-------------------+-------------------+-----+---------+
| user|              login|         firstLogin|index|Duplicate|
+-----+-------------------+-------------------+-----+---------+
|user1|2019-12-01 08:00:00|2019-12-01 08:00:00|    0|        N|
|user1|2019-12-01 10:00:00|2019-12-01 08:00:00|    0|        Y|
|user1|2019-12-01 23:00:00|2019-12-01 08:00:00|    0|        Y|
|user1|2019-12-02 07:00:00|2019-12-01 08:00:00|    0|        Y|
|user1|2019-12-02 08:00:00|2019-12-01 08:00:00|    1|        N|
|user1|2019-12-02 10:00:00|2019-12-01 08:00:00|    1|        Y|
|user1|2019-12-03 23:00:00|2019-12-01 08:00:00|    2|        N|
|user1|2019-12-04 07:00:00|2019-12-01 08:00:00|    2|        Y|
|user2|2019-12-04 08:00:00|2019-12-04 08:00:00|    0|        N|
|user2|2019-12-05 05:00:00|2019-12-04 08:00:00|    0|        Y|
|user2|2019-12-06 00:00:00|2019-12-04 08:00:00|    1|        N|
+-----+-------------------+-------------------+-----+---------+

我不知道有任何内置的Spark函数可以根据前一个会话以动态方式结束的位置来连续识别下一个24小时会话(或任何给定时间段)的开始。处理此类需求的一种方法是通过利用Scala的
fold
功能的UDF:

def dupeFlags(tLimit: Long) = udf{ (logins: Seq[String], tsDiffs: Seq[Long]) =>
  val flags = tsDiffs.foldLeft( (List[String](), 0L) ){ case ((flags, tsAcc), ts) =>
    if (ts == 0 || tsAcc + ts > tLimit)
      ("N" :: flags, 0L)
    else
      ("Y" :: flags, tsAcc + ts)
  }._1.reverse
  logins zip flags
}
UDF获取要处理的
时间差
(当前行和前一行之间的秒数)列表。请注意,UDF中的
foldLeft
累加器是(flags,tsAcc)的元组,其中:

  • flags
    是要返回的重复标志列表
  • tsAcc
    用于将有条件累积的时间戳值带入下一次迭代
还请注意,
登录日期的列表
仅为“传递”,以便包含在最终数据集中

import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.functions._
import spark.implicits._

val df = Seq(
  ("user1", "12/1/19 8:00"),
  ("user1", "12/1/19 10:00"),
  ("user1", "12/1/19 23:00"),
  ("user1", "12/2/19 7:00"),
  ("user1", "12/2/19 8:00"),
  ("user1", "12/2/19 10:00"),
  ("user1", "12/3/19 9:00"),
  ("user1", "12/3/19 23:00"),
  ("user1", "12/4/19 7:00"),
  ("user2", "12/4/19 8:00"),
  ("user2", "12/5/19 5:00"),
  ("user2", "12/6/19 0:00")
).toDF("user", "login")
使用
groupBy/collect_list
,将
time diff
列表以及
登录日期
列表提供给UDF,以生成所需的重复标志,然后使用
explode
将其展平:

val win1 = Window.partitionBy("user").orderBy("ts")

df.
  withColumn("ts", unix_timestamp(to_timestamp($"login", "MM/dd/yy HH:mm"))).
  withColumn("tsPrev", coalesce(lag($"ts", 1).over(win1), $"ts")).
  groupBy("user").agg(collect_list($"login").as("logins"), collect_list($"ts" - $"tsPrev").as("tsDiffs")).
  withColumn("tuple", explode(dupeFlags(60 * 60 * 24L)($"logins", $"tsDiffs"))).
  select($"user", $"tuple._1".as("login"), $"tuple._2".as("duplicate")).
  show
// +-----+-------------+---------+
// | user|        login|duplicate|
// +-----+-------------+---------+
// |user1| 12/1/19 8:00|        N|
// |user1|12/1/19 10:00|        Y|
// |user1|12/1/19 23:00|        Y|
// |user1| 12/2/19 7:00|        Y|
// |user1| 12/2/19 8:00|        Y|
// |user1|12/2/19 10:00|        N|
// |user1| 12/3/19 9:00|        Y|
// |user1|12/3/19 23:00|        N|
// |user1| 12/4/19 7:00|        Y|
// |user2| 12/4/19 8:00|        N|
// |user2| 12/5/19 5:00|        Y|
// |user2| 12/6/19 0:00|        N|
// +-----+-------------+---------+

如果我理解正确,此解决方案与[链接]存在相同的问题。这与以前的活动相比,不考虑登录窗口的启动。例如,如果我在第5行和第6行之间添加[code]user1 | 2019-12-02 08:01:0[code]。此活动应该是一个新的窗口开始,但将用上述代码标记为重复@拉曼努斯,这合理吗?@Lamanus我认为解决方案考虑了第一次登录。在我看来,这将产生与以前相同的结果。我添加了另一个示例场景。第三个数据集中的记录#7可能会产生不正确的结果,这是我的问题。我仍然在想。。。太难了:)我真的很感谢拉马努斯的帮助。同意没有迭代或每个用户条目的缓存是很困难的,这需要自定义项。我会看看我能做些什么。重复列的最后一行不应该是Y,最后一行应该是N?U r right@MohammadMurtazaHashmi。编辑的问题。是的,我也这么认为。没有udf,我无法找到实现这一目标的方法。据我所知,唯一的方法是迭代。
val win1 = Window.partitionBy("user").orderBy("ts")

df.
  withColumn("ts", unix_timestamp(to_timestamp($"login", "MM/dd/yy HH:mm"))).
  withColumn("tsPrev", coalesce(lag($"ts", 1).over(win1), $"ts")).
  groupBy("user").agg(collect_list($"login").as("logins"), collect_list($"ts" - $"tsPrev").as("tsDiffs")).
  withColumn("tuple", explode(dupeFlags(60 * 60 * 24L)($"logins", $"tsDiffs"))).
  select($"user", $"tuple._1".as("login"), $"tuple._2".as("duplicate")).
  show
// +-----+-------------+---------+
// | user|        login|duplicate|
// +-----+-------------+---------+
// |user1| 12/1/19 8:00|        N|
// |user1|12/1/19 10:00|        Y|
// |user1|12/1/19 23:00|        Y|
// |user1| 12/2/19 7:00|        Y|
// |user1| 12/2/19 8:00|        Y|
// |user1|12/2/19 10:00|        N|
// |user1| 12/3/19 9:00|        Y|
// |user1|12/3/19 23:00|        N|
// |user1| 12/4/19 7:00|        Y|
// |user2| 12/4/19 8:00|        N|
// |user2| 12/5/19 5:00|        Y|
// |user2| 12/6/19 0:00|        N|
// +-----+-------------+---------+