Преобразовать столбец с секундами в удобочитаемую продолжительность - PullRequest
3 голосов
/ 03 марта 2020

У меня есть следующий фрейм данных в pyspark:

Name                 | Seconds

|Enviar solicitud ...| 1415

|Analizar mapa de ...| 1209|

|Modificar solicit...|  591|

|Entregar servicio...|91049|

I wi sh для преобразования столбца seconds в дату или метку времени (надеюсь, на дату), я пытаюсь использовать следующую функцию

def to_date(seconds=0):
    dat = ''
    if seconds == 0:
        dat = '0'
    if (seconds / 86400) >= 1:
        day = (int(seconds / 86400))
        seconds = (seconds - 86400 * int(seconds / 86400))
        dat = f'{day}d '
    if (seconds / 3600) >= 1:
        hour = (int(seconds / 3600))
        seconds = (seconds - 3600 * int(seconds / 3600))
        dat = dat + f'{hour}hr '
    if (seconds / 60) >= 1:
        minutes = (int(seconds / 60))
        dat = dat + f'{minutes}min'   
    else:
        return '0min'
    return dat

Но в pyspark нет простого способа, например Pandas .apply(to_date), есть ли способ достичь того, что я пытаюсь сделать?

ОЖИДАЕМЫЙ ВЫХОД :

Analizar mapa de comparacion de presupuestos         1209         20min
Crear mapa de comparacion de presupuestos           12155     3hr 22min
Entregar servicios de bienes                        91049  1d 1hr 17min

Ответы [ 3 ]

1 голос
/ 03 марта 2020

Я думаю, что этого можно достичь без UDF, и это будет намного быстрее и масштабируемо для больших данных. Попробуйте это и дайте мне знать, если в моей логи есть дыра c.

from pyspark.sql import functions as F
from pyspark.sql.functions import when
df.withColumn("Minutes", F.round((F.col("Seconds")/60),2))\
.withColumn("Hours", F.floor((F.col("Minutes")/60)))\
.withColumn("hourmin", F.floor(F.col("Minutes")-(F.col("Hours").cast("int") * 60)))\
.withColumn("Days", F.floor((F.col("Hours")/24)))\
.withColumn("Days2", F.col("Days")*24)\
.withColumn("Time", F.when((F.col("Hours")==0) &(F.col("Days")==0), F.concat(F.col("hourmin"),F.lit("min"))).when((F.col("Hours")!=0)&(F.col("Days")==0), F.concat(F.col("Hours"),F.lit("hr "),F.col("hourmin"),F.lit("min"))).when(F.col("Days")!=0, F.concat(F.col("Days"),F.lit("d "),(F.col("Hours")-F.col("Days2")),F.lit("hr "),F.col("hourmin"),F.lit("min"))))\
.drop("Minutes","Hours","hourmin","Days","Days2")\
.show()


+-----------------+-------+---------------+
|             Name|Seconds|           Time|
+-----------------+-------+---------------+
| Enviar solicitud|   1209|          20min|
| Analizar mapa de|  12155|      3hr 22min|
|Entregar servicio|  91049|   1d 1hr 17min|
|         example1|   1900|          31min|
|         example2|   2500|          41min|
|         example3|9282398|107d 10hr 26min|
+-----------------+-------+---------------+
1 голос
/ 03 марта 2020

Для этого в Spark нет встроенной функции, но это можно сделать без UDF. Вы можете просто рассчитать его, используя операции деления и по модулю, чтобы получить разные части (дни, часы, ...), и объединить, чтобы получить желаемое форматирование.

Для Spark 2.4+ вы можете использовать более высокий порядок функция zip_with и array_join. Сначала создайте столбец parts, который содержит количество дней, часов, минут и секунд из столбца Seconds. Затем зафиксируйте его с помощью буквального массива единиц array('d', 'hr', 'min', 'sec'), чтобы объединить каждую деталь с ее единицей и, наконец, объединить все элементы с разделителем запятыми.

duration_parts = [(86400, 7), (3600, 24), (60, 60), (1, 60)]
exp = "zip_with(parts, array('d', 'hr', 'min', 'sec'), (x, y) -> IF(x > 0, concat(x, y), null))"

df.withColumn("parts", array(*[(floor(col("Seconds") / d)) % m for d, m in duration_parts]))\
  .withColumn("duration", array_join(expr(exp), ", "))\
  .drop("parts")\
  .show(truncate=False)

#+--------------------------------------------+-------+---------------------+
#|Name                                        |Seconds|duration             |
#+--------------------------------------------+-------+---------------------+
#|Analizar mapa de comparacion de presupuestos|1209   |20min, 9sec          |
#|Crear mapa de comparacion de presupuestos   |12155  |3hr, 22min, 35sec    |
#|Entregar servicios de bienes                |91049  |1d, 1hr, 17min, 29sec|
#+--------------------------------------------+-------+---------------------+

Другой способ - использовать concat и добавить выражение when, если вы не хотите, чтобы части были равны 0:

df.withColumn("duration", concat(
            floor(col("Seconds") / 86400), lit("d, "),
            floor(col("Seconds") % 86400 / 3600), lit("hr, "),
            floor((col("Seconds") % 86400) % 3600 / 60), lit("min, "),
            floor(((col("Seconds") % 86400) % 3600) % 60), lit("sec "),
        )).show(truncate=False)
0 голосов
/ 03 марта 2020

Это должно дать вам вывод в формате DD:HH:MM:SS.

df = spark.createDataFrame([
    (1, 1209), 
    (2, 12155),
    (3, 91049)
], ("ID","timeSec"))


def convert(seconds):
    days = seconds // (24 * 3600) 
    seconds = seconds % (24 * 3600) 
    hour = seconds // 3600
    seconds %= 3600
    minutes = seconds // 60
    seconds %= 60

    return "%02d:%02d:%02d:%02d" % (days, hour, minutes, seconds) 

from pyspark.sql.functions import udf
from pyspark.sql.types import StringType

apply_my_udf = udf(lambda z: convert(z), StringType())

df2 = df.withColumn("timeStr", apply_my_udf(df.timeSec))

df2.show()

+---+-------+-----------+
| ID|timeSec|    timeStr|
+---+-------+-----------+
|  1|   1209|00:00:20:09|
|  2|  12155|00:03:22:35|
|  3|  91049|01:01:17:29|
+---+-------+-----------+
...