PySpark时间戳的毫秒

时间:2019-03-01 19:46:11

标签: pyspark

我正在尝试获取两个时间戳列之间的差,但是毫秒已经过去了。

该如何纠正?

from pyspark.sql.functions import unix_timestamp
timeFmt = "yyyy-MM-dd' 'HH:mm:ss.SSS"

data = [
    (1, '2018-07-25 17:15:06.39','2018-07-25 17:15:06.377'),
    (2,'2018-07-25 11:12:49.317','2018-07-25 11:12:48.883')

]

df = spark.createDataFrame(data, ['ID', 'max_ts','min_ts']).withColumn('diff',F.unix_timestamp('max_ts', format=timeFmt) - F.unix_timestamp('min_ts', format=timeFmt))
df.show(truncate = False)

6 个答案:

答案 0 :(得分:3)

假设您已经有一个带有时间戳类型列的数据框:

from datetime import datetime

data = [
    (1, datetime(2018, 7, 25, 17, 15, 6, 390000), datetime(2018, 7, 25, 17, 15, 6, 377000)),
    (2, datetime(2018, 7, 25, 11, 12, 49, 317000), datetime(2018, 7, 25, 11, 12, 48, 883000))
]

df = spark.createDataFrame(data, ['ID', 'max_ts','min_ts'])
df.printSchema()

# root
#  |-- ID: long (nullable = true)
#  |-- max_ts: timestamp (nullable = true)
#  |-- min_ts: timestamp (nullable = true)

您可以通过将timestamp-type列强制转换为double类型来获取时间(以秒为单位),或者通过将结果乘以1000来获得毫秒数(如果需要整数则可以强制转换为long )。 例如

df.select(
    F.col('max_ts').cast('double').alias('time_in_seconds'),
    (F.col('max_ts').cast('double') * 1000).cast('long').alias('time_in_milliseconds'),
).toPandas()

#     time_in_seconds  time_in_milliseconds
# 0    1532538906.390         1532538906390
# 1    1532517169.317         1532517169317

最后,如果您希望两次之间的差值以毫秒为单位,则可以执行以下操作:

df.select(
    ((F.col('max_ts').cast('double') - F.col('min_ts').cast('double')) * 1000).cast('long').alias('diff_in_milliseconds'),
).toPandas()

#    diff_in_milliseconds
# 0                    13
# 1                   434

我正在PySpark 2.4.2上执行此操作。完全不需要使用字符串连接。

答案 1 :(得分:1)

当值的类型为timestamp并且毫秒为整数时(例如390、500),Tanjin的答案不起作用。 Python将在末尾切掉0,示例中的时间戳看起来像这样2018-07-25 17:15:06.39

问题是F.substring('max_ts', -3, 3)中的硬编码值。如果末尾的0不存在,那么substring就会变得疯狂。

要将tmpColumn类型的timestamp转换为tmpLongColumn类型的long,我使用了以下代码段:

timeFmt = "yyyy-MM-dd HH:mm:ss.SSS"

df = df \
  .withColumn('tmpLongColumn', F.substring_index('tmpColumn', '.', -1).cast('float')) \
  .withColumn('tmpLongColumn', F.when(F.col('tmpLongColumn') < 100, F.col('tmpLongColumn')*10).otherwise(F.col('tmpLongColumn')).cast('long')) \
  .withColumn('tmpLongColumn', (F.unix_timestamp('tmpColumn', format=timeFmt)*1000 + F.col('tmpLongColumn'))) \

第一个转换提取包含毫秒的子字符串。接下来,如果该值小于100,则将其乘以10。最后,转换时间戳记并添加毫秒。

答案 2 :(得分:1)

当您不能保证精确到亚秒的格式(长度?尾随零吗?)时,我建议使用以下小算法,该算法适用于所有长度和格式:

算法

timeFmt = "yyyy-MM-dd' 'HH:mm:ss.SSS"

current_col = "time"
df = df.withColumn("subsecond_string", F.substring_index(current_col, '.', -1))
df = df.withColumn("subsecond_length",   F.length(F.col("subsecond_string")))
df = df.withColumn("divisor",   F.pow(10,"subsecond_length"))
df = df.withColumn("subseconds", F.col("subsecond_string").cast("int") / F.col("divisor")  )
# Putting it all together
df = df.withColumn("timestamp_subsec", F.unix_timestamp(current_col, format=timeFmt) + F.col("subseconds"))

根据亚秒级字符串的长度,计算适当的除数(10乘以子字符串长度的幂)。

随后删除多余的列应该不是问题。

演示

我的示范结果如下:

+----------------------+----------------+----------------+-------+----------+----------------+
|time                  |subsecond_string|subsecond_length|divisor|subseconds|timestamp_subsec|
+----------------------+----------------+----------------+-------+----------+----------------+
|2019-04-02 14:34:16.02|02              |2               |100.0  |0.02      |1.55420845602E9 |
|2019-04-02 14:34:16.03|03              |2               |100.0  |0.03      |1.55420845603E9 |
|2019-04-02 14:34:16.04|04              |2               |100.0  |0.04      |1.55420845604E9 |
|2019-04-02 14:34:16.05|05              |2               |100.0  |0.05      |1.55420845605E9 |
|2019-04-02 14:34:16.06|06              |2               |100.0  |0.06      |1.55420845606E9 |
|2019-04-02 14:34:16.07|07              |2               |100.0  |0.07      |1.55420845607E9 |
|2019-04-02 14:34:16.08|08              |2               |100.0  |0.08      |1.55420845608E9 |
|2019-04-02 14:34:16.09|09              |2               |100.0  |0.09      |1.55420845609E9 |
|2019-04-02 14:34:16.1 |1               |1               |10.0   |0.1       |1.5542084561E9  |
|2019-04-02 14:34:16.11|11              |2               |100.0  |0.11      |1.55420845611E9 |
|2019-04-02 14:34:16.12|12              |2               |100.0  |0.12      |1.55420845612E9 |
|2019-04-02 14:34:16.13|13              |2               |100.0  |0.13      |1.55420845613E9 |
|2019-04-02 14:34:16.14|14              |2               |100.0  |0.14      |1.55420845614E9 |
|2019-04-02 14:34:16.15|15              |2               |100.0  |0.15      |1.55420845615E9 |
|2019-04-02 14:34:16.16|16              |2               |100.0  |0.16      |1.55420845616E9 |
|2019-04-02 14:34:16.17|17              |2               |100.0  |0.17      |1.55420845617E9 |
|2019-04-02 14:34:16.18|18              |2               |100.0  |0.18      |1.55420845618E9 |
|2019-04-02 14:34:16.19|19              |2               |100.0  |0.19      |1.55420845619E9 |
|2019-04-02 14:34:16.2 |2               |1               |10.0   |0.2       |1.5542084562E9  |
|2019-04-02 14:34:16.21|21              |2               |100.0  |0.21      |1.55420845621E9 |
+----------------------+----------------+----------------+-------+----------+----------------+

答案 3 :(得分:0)

这是unix_timestamp的预期行为-它在source code docstring中明确指出,它仅返回秒,因此在进行计算时会删除毫秒部分。

如果要进行计算,可以使用substring函数来合并数字,然后进行求和。请参见下面的示例。请注意,这是假设格式完全正确的数据,例如毫秒完全满足(全部3位数字):

import pyspark.sql.functions as F

timeFmt = "yyyy-MM-dd' 'HH:mm:ss.SSS"
data = [
    (1, '2018-07-25 17:15:06.390', '2018-07-25 17:15:06.377'),  # note the '390'
    (2, '2018-07-25 11:12:49.317', '2018-07-25 11:12:48.883')
]

df = spark.createDataFrame(data, ['ID', 'max_ts', 'min_ts'])\
    .withColumn('max_milli', F.unix_timestamp('max_ts', format=timeFmt) + F.substring('max_ts', -3, 3).cast('float')/1000)\
    .withColumn('min_milli', F.unix_timestamp('min_ts', format=timeFmt) + F.substring('min_ts', -3, 3).cast('float')/1000)\
    .withColumn('diff', (F.col('max_milli') - F.col('min_milli')).cast('float') * 1000)

df.show(truncate=False)

+---+-----------------------+-----------------------+----------------+----------------+---------+
|ID |max_ts                 |min_ts                 |max_milli       |min_milli       |diff     |
+---+-----------------------+-----------------------+----------------+----------------+---------+
|1  |2018-07-25 17:15:06.390|2018-07-25 17:15:06.377|1.53255330639E9 |1.532553306377E9|13.000011|
|2  |2018-07-25 11:12:49.317|2018-07-25 11:12:48.883|1.532531569317E9|1.532531568883E9|434.0    |
+---+-----------------------+-----------------------+----------------+----------------+---------+

答案 4 :(得分:0)

与@kaichi不同,我没有发现substring_index命令会截断尾随的零,因此不需要将毫秒乘以10,并且可以给您错误的答案,例如,如果毫秒原本是099,则将变为990。此外,您可能还需要添加对具有零毫秒的时间戳的处理。为了处理这两种情况,我修改了@kaichi的答案,以给出以下两个时间戳之间的时差(以毫秒为单位):

df = (
    df
    .withColumn('tmpLongColumn', f.substring_index(tmpColumn, '.', -1).cast('long'))
    .withColumn(
        'tmpLongColumn',
        f.when(f.col('tmpLongColumn').isNull(), 0.0)
        .otherwise(f.col('tmpLongColumn')))
    .withColumn(
        tmpColumn, 
        (f.unix_timestamp(tmpColumn, format=timeFmt)*1000 + f.col('tmpLongColumn')))
      .drop('tmpLongColumn'))

答案 5 :(得分:0)

原因pyspark to_timestamp仅解析到几秒钟,而TimestampType可以保留毫秒。

以下解决方法可能会起作用:

如果时间戳记模式包含S,则调用UDF以获取要在表达式中使用的字符串'INTERVAL MILLISECONDS'

ts_pattern = "YYYY-MM-dd HH:mm:ss:SSS"
my_col_name = "time_with_ms"

# get the time till seconds
df = df.withColumn(my_col_name, to_timestamp(df["updated_date_col2"],ts_pattern))

if S in timestamp_pattern:
   df = df.withColumn(my_col_name, df[my_col_name] + expr("INTERVAL 256 MILLISECONDS"))

要获取间隔256个错误,我们可以使用Java UDF:

df = df.withColumn(col_name,df [col_name] + expr(getIntervalStringUDF(df [my_col_name],ts_pattern)))

内部UDF:getIntervalStringUDF(字符串timeString,字符串模式)

  1. 使用SimpleDateFormat根据模式解析日期
  2. 使用模式“'INTERVAL'SSS'MILLISECONDS'”以字符串形式返回格式化日期
  3. 针对解析/格式异常返回“ INTERVAL 0 MILLISECONDS”

请参阅pyspark to_timestamp does not include milliseconds