pyspark的"between"函数:时间戳的范围查询不包含边界。

29

pyspark的'between'函数在时间戳输入上不是包含的。

例如,如果我们想要在两个日期之间获取所有行,比如'2017-04-13'和'2017-04-14',那么当以字符串形式传递日期时,它执行一个“排他性”搜索。也就是说,它省略了'2017-04-14 00:00:00'字段。

然而,文档似乎暗示它是包含的(尽管没有关于时间戳的参考)。

当然,一种方法是从上限添加微秒并将其传递给函数。但这不是一个很好的解决方法。有没有更干净的方法来做包含搜索?

例子:

import pandas as pd
from pyspark.sql import functions as F
... sql_context creation ...
test_pd=pd.DataFrame([{"start":'2017-04-13 12:00:00', "value":1.0},{"start":'2017-04-14 00:00:00', "value":1.1}])
test_df = sql_context.createDataFrame(test_pd).withColumn("start", F.col("start").cast('timestamp'))
test_df.show()

+--------------------+-----+
|               start|value|
+--------------------+-----+
|2017-04-13 12:00:...|  1.0|
|2017-04-14 00:00:...|  1.1|
+--------------------+-----+

test_df.filter(F.col("start").between('2017-04-13','2017-04-14')).show()

+--------------------+-----+
|               start|value|
+--------------------+-----+
|2017-04-13 12:00:...|  1.0|
+--------------------+-----+
3个回答

30

找到了答案。pyspark的"between"函数在处理时间戳输入时不一致。

  1. 如果您以字符串格式提供输入,没有时间信息,则执行排除搜索(与上面链接的文档所期望的不同)。
  2. 如果您使用日期时间对象或具有确切时间的输入(例如,“2017-04-14 00:00:00”),则执行包含搜索。

对于上述示例,以下是使用pd.to_datetime进行排除搜索的输出:

test_df.filter(F.col("start").between(pd.to_datetime('2017-04-13'),pd.to_datetime('2017-04-14'))).show()

+--------------------+-----+
|               start|value|
+--------------------+-----+
|2017-04-13 12:00:...|  1.0|
|2017-04-14 00:00:...|  1.1|
+--------------------+-----+

同样地,如果我们以字符串格式提供日期和时间,则似乎执行一个包含搜索:

test_df.filter(F.col("start").between('2017-04-13 12:00:00','2017-04-14 00:00:00')).show()

+--------------------+-----+
|               start|value|
+--------------------+-----+
|2017-04-13 12:00:...|  1.0|
|2017-04-14 00:00:...|  1.1|
+--------------------+-----+

有趣的观点。但输出结果看起来一样。你能详细说明一下吗? - PR102012
以上两种方法都可以得到包含搜索(因此输出相同)。然而,如果我们只是将日期作为字符串传递(参见问题),我们会得到一个排除性搜索。关键在于:如果您想要包含性搜索,请使用上述任一方法,不要仅使用日期字符串(例如 F.between('2017-04-13','2017-04-14'))。 - Vinay Kolar
2
@VinayKolar between()函数的源代码似乎表明它是一个包含搜索。 - Yuan Zhao
这可能是因为筛选的列包含时间。搜索是包容性的,但是“2022-01-01 13:17”不在“2021-12-31”和“2022-01-01”之间(隐含为“2022-01-01 00:00”)。 - MilitaryCoo

12

.between()方法始终包含范围内的值。在您的示例中出现问题是因为当您将字符串传递给.between()方法时,它也会将您的数据视为字符串。对于字符串比较,'2017-04-14 00:00:00'严格大于'2017-04-14',因为前者是一个比后者更长的字符串,这就是为什么第二个日期在您的示例中被过滤掉的原因。为了避免“不一致性”,您应该按照以下方式以datetime格式传递参数到.between()方法:

filtered_df = (test_df.filter(F.col("start")
                .between(dt.strptime('2017-04-13 12:00:00', '%Y-%m-%d %H:%M:%S'), 
                         dt.strptime('2017-04-14 00:00:00', '%Y-%m-%d %H:%M:%S'))))

这将会产生预期的结果:

+--------------------+-----+
|               start|value|
+--------------------+-----+
|2017-04-13 12:00:...|  1.0|
|2017-04-14 00:00:...|  1.1|
+--------------------+-----+

1
'2017-04-14 00:00:00'严格大于'2017-04-14' - 这是一个字符串转换问题,但我认为不同于你所描述的。在内部,Spark似乎将'2017-04-14'解析为'2017-04-14 00:00:00',这使得OPs查询不包括结束范围。这与一个字符串比另一个字符串长无关(否则您将不会得到任何搜索结果,因为所有时间戳都大于您的between条件)。 - Markus

4

仅获取单日数据时最好指定确切时间

例如) 仅检索单日数据 (2017-04-13)

test_df.filter(F.col("start").between('2017-04-13 00:00:00','2017-04-13 23:59:59.59') 

注:如果您将日期设置在“2017-04-13”和“2017-04-14”之间,则会包括2017-04-14 00:00:00的数据,这实际上并不是您想要提取的数据,因为它属于2017-04-14的数据。


谢谢解释。在pyspark dataframe中,是否可以使用另一个dataframe中的"timeStart"和"timeEnd"列来过滤一个列,就像SQL中的"between"子句一样? - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接