【问题标题】:how to design a customized window function to select a column values within a time window of pyspark dataframe如何设计自定义窗口函数以在pyspark数据框的时间窗口内选择列值
【发布时间】:2020-05-26 09:04:21
【问题描述】:

这个问题与我之前的问题有关。 pyspark dataframe aggregate a column by sliding time window

但是,我想创建一个帖子,以澄清我上一个问题中缺少的一些关键点。

原始数据框:

client_id    value1    name1    a_date
 dhd         561       ecdu     2019-10-8
 dhd         561       tygp     2019-10-8  
 dhd         561       rdsr     2019-10-8
 dhd         561       rgvd     2019-8-12
 dhd         561       bhnd     2019-8-12
 dhd         561       prti     2019-8-12
 dhd         561       teuq     2019-5-7
 dhd         561       wnva     2019-5-7
 dhd         561       pqhn     2019-5-7

我需要为每个“client_id”、每个“value1”以及某个给定的滑动时间窗口找到“name1”的值。

我定义了一个窗口函数:

 w = window().partitionBy("client_id", "value1").orderBy("a_date")

但我不知道如何为窗​​口大小 1、2、6、9、12 选择“name1”的值。

这里,窗口大小是指“a_date”当前月份的月份长度。

例如

 client_id     value1    names1_within_window_size_1  names1_within_window_size_2
  dhd           561       [ecdu,tygp,rdsr]             [ecdu,tygp,rdsr]   

  names1_within_window_size_6
  [ecdu,tygp,rdsr, rgvd,bhnd,prti, teuq, wnva,pqhn ]  

 names1_within_window_size_1   : the month window 2019-10
 names1_within_window_size_2    : the month window 2019-10 and 2019-9 (no data in 2019-9 so just keep the data from 2019-10)
 names1_within_window_size_6    : the month window 2019-10 and 2019-9 (no data in 2019-9 so just keep the data from 2019-10) but there are data in 2019-8

谢谢

============================================= 更新

from pyspark.sql import functions as F
from pyspark.sql.window import Window

data=  [['dhd',589,'ecdu','2020-1-5'],
    ['dhd',575,'tygp','2020-1-5'],  
    ['dhd',821,'rdsr','2020-1-5'],
    ['dhd',872,'rgvd','2019-12-1'],
    ['dhd',619,'bhnd','2019-12-15'],
    ['dhd',781,'prti','2019-12-18'],
    ['dhd',781,'prti1','2019-12-18'],
    ['dhd',781,'prti2','2019-11-18'],
    ['dhd',781,'prti3','2019-10-31'],
    ['dhd',781,'prti4','2019-09-30'],
    ['dhd',781,'prt1','2019-07-31'],
    ['dhd',781,'pr4','2019-06-30'],
    ['dhd',781,'pr2','2019-08-31'],
    ['dhd',781,'prt4','2019-01-31'],
    ['dhd',781,'prti6','2019-02-28'],
    ['dhd',781,'prti7','2019-02-02'],
    ['dhd',781,'prti8','2019-03-29'],
    ['dhd',781,'prti9','2019-04-29'],
    ['dhd',781,'prti10','2019-05-04'],
    ['dhd',781,'prti11','2019-03-01'],
    ['dhd',781,'prti12','2018-12-17'],
    ['dhd',781,'prti15','2018-11-21'],
    ['dhd',781,'prti17','2018-10-31'],
    ['dhd',781,'prti19','2018-09-5']

   ]
columns= ['client_id','value1','name1','a_date']

df= spark.createDataFrame(data,columns)

df2 = df.withColumn("year_val", F.year("a_date"))\
    .withColumn("month_val", F.month("a_date"))\
    .withColumn("year_month", F.year(F.col("a_date")) * 100 + 
    F.month(F.col("a_date")))\
    .groupBy("client_id", "value1", "year_month")\
    .agg(F.concat_ws(", ", F.collect_list("name1")).alias("init_list"))

 df2.sort(F.col("value1").desc(), F.col("year_month").desc()).show()

 w = Window().partitionBy("client_id", "value1")\
    .orderBy("year_month")
df4 = df2.withColumn("a_rank", F.dense_rank().over(w))
df4.sort(F.col("value1"), F.col("year_month")).show()


month_range = 3
w = Window().partitionBy("client_id", "value1")\
    .orderBy("a_rank")\
    .rangeBetween(-(month_range),0)

 df5 = df4.withColumn("last_" + str(month_range) + "_month", F.collect_list(F.col("init_list")).over(w))\
    .orderBy("value1", "a_rank")

 df6 = df5.sort(F.col("value1").desc(), F.col("year_month").desc())
 df6.show(100,False)

【问题讨论】:

    标签: sql python-3.x dataframe pyspark


    【解决方案1】:

    我为此从您之前的问题中窃取了数据,因为我懒得自己做,而且某个好人已经为那里的输入数据制作了列表。

    当窗口在记录数而不是月数上滑动时,我将给定月份的所有记录(当然按client_idvalue1 分组)合并到.groupBy("client_id", "value1", "year_val", "month_val") 中的一条记录中,该记录存在在计算df2

    from pyspark.sql import functions as F
    from pyspark.sql.window import Window
    
    data=  [['dhd',589,'ecdu','2020-1-5'],
            ['dhd',575,'tygp','2020-1-5'],  
            ['dhd',821,'rdsr','2020-1-5'],
            ['dhd',872,'rgvd','2019-12-1'],
            ['dhd',619,'bhnd','2019-12-15'],
            ['dhd',781,'prti','2019-12-18'],
            ['dhd',781,'prti1','2019-12-18'],
            ['dhd',781,'prti2','2019-11-18'],
            ['dhd',781,'prti3','2019-10-31'],
            ['dhd',781,'prti4','2019-09-30'],
            ['dhd',781,'prt1','2019-07-31'],
            ['dhd',781,'pr4','2019-06-30'],
            ['dhd',781,'pr2','2019-08-31'],
            ['dhd',781,'prt4','2019-01-31'],
            ['dhd',781,'prti6','2019-02-28'],
            ['dhd',781,'prti7','2019-02-02'],
            ['dhd',781,'prti8','2019-03-29'],
            ['dhd',781,'prti9','2019-04-29'],
            ['dhd',781,'prti10','2019-05-04'],
            ['dhd',781,'prti11','2019-03-01']]
    columns= ['client_id','value1','name1','a_date']
    
    df= spark.createDataFrame(data,columns)
    
    df2 = df.withColumn("year_val", F.year("a_date"))\
            .withColumn("month_val", F.month("a_date"))\
            .groupBy("client_id", "value1", "year_val", "month_val")\
            .agg(F.concat_ws(", ", F.collect_list("name1")).alias("init_list"))
    
    df2.show()
    

    在这里,我们得到init_list

    +---------+------+--------+---------+-------------+
    |client_id|value1|year_val|month_val|    init_list|
    +---------+------+--------+---------+-------------+
    |      dhd|   781|    2019|       12|  prti, prti1|
    |      dhd|   589|    2020|        1|         ecdu|
    |      dhd|   781|    2019|        8|          pr2|
    |      dhd|   781|    2019|        3|prti8, prti11|
    |      dhd|   575|    2020|        1|         tygp|
    |      dhd|   781|    2019|        5|       prti10|
    |      dhd|   781|    2019|        9|        prti4|
    |      dhd|   781|    2019|       11|        prti2|
    |      dhd|   781|    2019|       10|        prti3|
    |      dhd|   821|    2020|        1|         rdsr|
    |      dhd|   781|    2019|        6|          pr4|
    |      dhd|   619|    2019|       12|         bhnd|
    |      dhd|   781|    2019|        7|         prt1|
    |      dhd|   781|    2019|        4|        prti9|
    |      dhd|   781|    2019|        1|         prt4|
    |      dhd|   781|    2019|        2| prti6, prti7|
    |      dhd|   872|    2019|       12|         rgvd|
    +---------+------+--------+---------+-------------+
    

    使用这个,我们可以通过简单地在记录上运行窗口来获得最终结果:

    month_range = 6
    w = Window().partitionBy("client_id", "value1")\
            .orderBy("month_val")\
            .rangeBetween(-(month_range+1),0)
    
    df3 = df2.withColumn("last_0_month", F.collect_list(F.col("init_list")).over(w))\
            .orderBy("value1", "year_val", "month_val")
    
    df3.show(100,False)
    

    这给了我们:

    +---------+------+--------+---------+-------------+-------------------------------------------------------------------+
    |client_id|value1|year_val|month_val|init_list    |last_0_month                                                       |
    +---------+------+--------+---------+-------------+-------------------------------------------------------------------+
    |dhd      |575   |2020    |1        |tygp         |[tygp]                                                             |
    |dhd      |589   |2020    |1        |ecdu         |[ecdu]                                                             |
    |dhd      |619   |2019    |12       |bhnd         |[bhnd]                                                             |
    |dhd      |781   |2019    |1        |prt4         |[prt4]                                                             |
    |dhd      |781   |2019    |2        |prti6, prti7 |[prt4, prti6, prti7]                                               |
    |dhd      |781   |2019    |3        |prti8, prti11|[prt4, prti6, prti7, prti8, prti11]                                |
    |dhd      |781   |2019    |4        |prti9        |[prt4, prti6, prti7, prti8, prti11, prti9]                         |
    |dhd      |781   |2019    |5        |prti10       |[prt4, prti6, prti7, prti8, prti11, prti9, prti10]                 |
    |dhd      |781   |2019    |6        |pr4          |[prt4, prti6, prti7, prti8, prti11, prti9, prti10, pr4]            |
    |dhd      |781   |2019    |7        |prt1         |[prt4, prti6, prti7, prti8, prti11, prti9, prti10, pr4, prt1]      |
    |dhd      |781   |2019    |8        |pr2          |[prt4, prti6, prti7, prti8, prti11, prti9, prti10, pr4, prt1, pr2] |
    |dhd      |781   |2019    |9        |prti4        |[prti6, prti7, prti8, prti11, prti9, prti10, pr4, prt1, pr2, prti4]|
    |dhd      |781   |2019    |10       |prti3        |[prti8, prti11, prti9, prti10, pr4, prt1, pr2, prti4, prti3]       |
    |dhd      |781   |2019    |11       |prti2        |[prti9, prti10, pr4, prt1, pr2, prti4, prti3, prti2]               |
    |dhd      |781   |2019    |12       |prti, prti1  |[prti10, pr4, prt1, pr2, prti4, prti3, prti2, prti, prti1]         |
    |dhd      |821   |2020    |1        |rdsr         |[rdsr]                                                             |
    |dhd      |872   |2019    |12       |rgvd         |[rgvd]                                                             |
    +---------+------+--------+---------+-------------+-------------------------------------------------------------------+
    

    限制:

    遗憾的是,到第二部分,a_date 字段丢失了,对于在其上定义范围的滑动窗口操作,orderBy 不能指定多个列(请注意,窗口定义中的 orderBy 仅在 @ 987654334@)。因此,这种精确的解决方案不适用于跨越多年的数据。但是,可以通过将诸如 month_id 之类的东西作为结合年份和月份值的单列,然后在 orderBy 子句中使用它来轻松克服这一问题。

    如果你想拥有多个窗口,你可以将month_range 转换为一个列表,然后在最后的代码sn-p 中循环遍历它以覆盖所有范围。

    虽然最后一列 (last_0_month) 看起来像一个数组,但它包含来自先前 agg 操作的逗号分隔字符串。您可能还想清理它。

    【讨论】:

    • 我根据您的代码在我的 OP 的更新部分发布了代码。我创建了一个新列作为“年 * 100 + 月”(作为“年月”),并通过新的“年月”列在“窗口”顺序上创建了 dense_rank()。现在,它可以收集不同年份的“name1”。如有遗漏请指出,谢谢!
    • 请注意限制中的最后一条注释,groupByconcat_ws 会产生连接的逗号分隔字符串,然后 collect_list 返回一个列表。在打印输出中,结果看起来完全是一个列表。不要为此而堕落。此外,如果您真的想创建 UDF(如标题所示),请尝试 spark.apache.org/docs/latest/api/python/_modules/pyspark/sql/… 但还要注意 spark 建议使用 Aggregators 而不是 UDF。但是,afaik,聚合器功能尚未内置到 pyspark 中
    猜你喜欢
    • 2017-03-17
    • 2019-10-03
    • 2022-08-16
    • 2018-07-11
    • 1970-01-01
    • 1970-01-01
    • 2021-04-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多