在日期列比较中过滤DataFrame

时间:2019-01-15 15:01:39

标签: scala apache-spark apache-spark-sql

我正在尝试使用Scala和Spark筛选比较两个日期列的DataFrame。基于过滤后的DataFrame,将在顶部运行一些计算以计算新列。 简化后,我的数据框具有以下架构:

|-- received_day: date (nullable = true)
|-- finished: int (nullable = true)

最重要的是,我创建了两个新列t_startt_end,这些列将用于过滤DataFrame。它们与原始列received_day相差10天和20天:

val dfWithDates= df
      .withColumn("t_end",date_sub(col("received_day"),10))
      .withColumn("t_start",date_sub(col("received_day"),20))

我现在希望有一个新的计算列,该列为t_startt_end期间中的每一行数据指示数据帧中有多少行。我以为可以通过以下方式实现此目标:

val dfWithCount = dfWithDates
       .withColumn("cnt", lit(
        dfWithDates.filter(
          $"received_day".lt(col("t_end")) 
          && $"received_day".gt(col("t_start"))).count()))

但是,此计数仅返回0,我认为问题出在我传递给ltgt的参数中。

从这里Filtering a spark dataframe based on date开始,我意识到我需要传递一个字符串值。如果我尝试使用lt(lit("2018-12-15"))之类的硬编码值,则过滤有效。因此,我尝试将列转换为StringType

val dfWithDates= df
      .withColumn("t_end",date_sub(col("received_day"),10).cast(DataTypes.StringType))
      .withColumn("t_start",date_sub(col("received_day"),20).cast(DataTypes.StringType))

但是过滤器仍然返回一个空的dataFrame。 我假设我没有正确处理数据类型。

我正在使用Spark 2.0.2在Scala 2.11.0上运行。

2 个答案:

答案 0 :(得分:1)

是的,您是对的。对于$"received_day".lt(col("t_end"),将每个reveived_day值与当前行的t_end值(而不是整个数据帧)进行比较。因此,每次您获得零计数。 您可以通过编写简单的udf解决此问题。这是解决问题的方法:

创建样本输入数据集

import org.apache.spark.sql.{Row, SparkSession}
import java.sql.Date
import org.apache.spark.sql.functions._
import spark.implicits._
val df = Seq((Date.valueOf("2018-10-12"),1),
              (Date.valueOf("2018-10-13"),1),
              (Date.valueOf("2018-09-25"),1),
              (Date.valueOf("2018-10-14"),1)).toDF("received_day", "finished")

val dfWithDates= df
  .withColumn("t_start",date_sub(col("received_day"),20))
  .withColumn("t_end",date_sub(col("received_day"),10))
dfWithDates.show()
    +------------+--------+----------+----------+
|received_day|finished|   t_start|     t_end|
+------------+--------+----------+----------+
|  2018-10-12|       1|2018-09-22|2018-10-02|
|  2018-10-13|       1|2018-09-23|2018-10-03|
|  2018-09-25|       1|2018-09-05|2018-09-15|
|  2018-10-14|       1|2018-09-24|2018-10-04|
+------------+--------+----------+----------+

在这里2018-09-25我们希望计数3

生成输出

val count_udf = udf((received_day:Date) => {
        (dfWithDates.filter((col("t_end").gt(s"$received_day")) && col("t_start").lt(s"$received_day")).count())
    })
    val dfWithCount = dfWithDates.withColumn("count",count_udf(col("received_day")))
    dfWithCount.show()
    +------------+--------+----------+----------+-----+
|received_day|finished|   t_start|     t_end|count|
+------------+--------+----------+----------+-----+
|  2018-10-12|       1|2018-09-22|2018-10-02|    0|
|  2018-10-13|       1|2018-09-23|2018-10-03|    0|
|  2018-09-25|       1|2018-09-05|2018-09-15|    3|
|  2018-10-14|       1|2018-09-24|2018-10-04|    0|
+------------+--------+----------+----------+-----+

为了使计算更快,我建议缓存dfWithDates,因为每一行都重复相同的操作。

答案 1 :(得分:0)

您可以使用DateTimeFormatter将日期值转换为具有任何模式的字符串

import java.time.format.DateTimeFormatter

date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))